在Unity中将输入与其他脚本解耦的最佳实践

时间:2018-10-23 19:32:25

标签: c# unity3d

我有一个输入脚本,它在Update循环中将触摸转换为大小为(0-1)的方向(左,右,上,下),并且当检测到输入时,该脚本会触发{ {1}}:

UnityEvent

我正在从Unity Inspector订阅此public class TouchAnalogStickInput : MonoBehaviour { [System.Serializable] public class AnalogStickInputEvent : UnityEvent<Direction, float> { } [Header("Events")] [Space] [Tooltip("Fired when a successful swipe occurs. Event Args: Swipe Direction, A normalized input magnitude between 0 and 1.")] public AnalogStickInputEvent OnAnalogStickInput; void Update() { ... if (successfulInputDetected) { OnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag); } } } 事件来调用我的OnAnalogStickInput方法,该方法需要一个Direction和一个幅度:

CharacterController2D.Move

然后我有另一个public class CharacterController2D : MonoBehaviour { public void Move(Direction movementDirection, float normalizedInputMagnitude) { // Move the character. } } ,希望使用相同的输入脚本进行轮换。因此,我在此GameObject上附加了TouchAnalogStickInputSnapRotator,订阅了GameObject事件以调用OnAnalogStickInput

SnapRotator.Rotate

这时我开始意识到我不再负责这些方法从哪个游戏循环中调用,例如我应该检测public class SnapRotator : MonoBehaviour { public void Rotate(Direction movementDirection, float normalizedInputMagnitude) { // Rotate object. } } 中的输入,并使用Update中的输入进行移动,也许就我而言,我想在{{1}中最后进行旋转}。 相反正在从运行输入代码的FixedUpdate循环中触发LateUpdateCharacterController2D.Move

我能想到的唯一其他选择可能是将Input脚本的代码重构为方法调用。然后让SnapRotator.RotateUpdateCharacterController2D循环中调用此方法,并根据需要在SnapRotatorUpdate循环中执行移动/旋转,例如:

FixedUpdate

我的问题是:在Unity中解耦此类脚本的最佳实践是什么?还是我对可重用性的考虑过于广泛/过于害怕在游戏开发中耦合子类/组件?

解决方案

万一这对其他人有帮助,这是我的最终解决方案,用半sudocode表示感谢Ruzihm:

实用程序类,用于存储有关检测到的输入的信息:

LateUpdate

输入类别:

public class CharacterController2D : MonoBehaviour
{
    public TouchAnalogStickInput Input;

    private var mMovementInfo;

    void Update()
    {
        // Contains a Direction and a Normalized Input Magnitude 
        mMovementInfo = Input.DetectInput();
    }

    void FixedUpdate()
    {
        if (mMovementInfo == Moved)
            // Move the character.
    } 
}

订阅public class InputInfo { public Direction Direction { get; set; } = Direction.None; public float NormalizedMagnitude { get; set; } = 0f; public TouchPhase? CurrentTouchPhase { get; set; } = null; public InputInfo(Direction direction, float normalizedMagnitude, TouchPhase currentTouchPhase) { Direction = direction; NormalizedMagnitude = normalizedMagnitude; CurrentTouchPhase = currentTouchPhase; } public InputInfo() { } } 事件的字符控制器。

public class TouchAnalogStickInput : MonoBehaviour
{
    [System.Serializable]
    public class AnalogStickInputEvent : UnityEvent<InputInfo> { }

    [Header("Events")]
    [Space]
    [Tooltip("Fired from the Update loop when virtual stick movement occurs. Event Args: Swipe Direction, A normalized input magnitude between 0 and 1.")]
    public AnalogStickInputEvent OnUpdateOnAnalogStickInput;

    [Tooltip("Fired from the FixedUpdate loop when virtual stick movement occurs. Event Args: Swipe Direction, A normalized input magnitude between 0 and 1.")]
    public AnalogStickInputEvent OnFixedUpdateOnAnalogStickInput;

    [Tooltip("Fired from the LateUpdate loop when virtual stick movement occurs. Event Args: Swipe Direction, A normalized input magnitude between 0 and 1.")]
    public AnalogStickInputEvent OnLateUpdateOnAnalogStickInput;

    private bool mInputFlag;
    private InputInfo mInputInfo;

    void Update()
    {
        // Important - No input until proven otherwise, reset all members.
        mInputFlag = false;
        mInputInfo = new InputInfo();

        // Logic to detect input
        ...
        if (inputDetected)
        {
            mInputInfo.Direction = direction;
            mInputInfo.NormalizedMagnitude = magnitude;
            mInputInfo.CurrentTouchPhase = touch.phase;

            // Now that the Input Info has been fully populated set the input detection flag.
            mInputFlag = true;

            // Fire Input Event to listeners
            OnUpdateOnAnalogStickInput.Invoke(mInputInfo);
        }

        void FixedUpdate()
        {
            if (mInputFlag)
            {
                OnFixedUpdateOnAnalogStickInput.Invoke(mInputInfo);
            }
        }

        void LateUpdate()
        {
            OnLateUpdateOnAnalogStickInput.Invoke(mInputInfo);
        }
    }
}

旋转类几乎相同,但是订阅了OnFixedUpdateOnAnalogStickInput事件。

2 个答案:

答案 0 :(得分:3)

这是一种替代方法,主要需要在您的TouchAnalogStickInput类中进行更改。

Update中设置输入状态标志,并触发所有相关事件。只有这一次,您才触发“ OnUpdate”事件(在您的特定情况下,该事件不会注册任何东西):

void Update()
{
    ...
    inputFlag_AnalogStickInput = false;
    ...

    if (successfulInputDetected)
    {
        inputFlag_AnalogStickInput = true;
        OnUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag);
    }
}

然后在TouchAnalogStickInput.FixedUpdate中,致电OnFixedUpdateOnAnalogStickInput,该电话将在您的Move中注册

void FixedUpdate()
{
    ...
    if (inputFlag_AnalogStickInput)
    {
        OnFixedUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag);
    }
}

LateUpdate依此类推,该事件会触发您的Rotate注册到的事件。

void LateUpdate()
{
    ...
    if (inputFlag_AnalogStickInput)
    {
        OnLateUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag);
    }
}

这些OnFixedUpdateOn...事件当然会在该标志为true的每个FixedUpdate上触发。在大多数情况下-包括Move-这可能是适当的,但在其他情况下则可能不理想。因此,您可以添加其他事件,这些事件仅在更新后的第一个FixedUpdate事件中触发。例如:

void Update()
{
    ...
    firstFixedUpdateAfterUpdate = true;
    inputFlag_AnalogStickInput = false;
    ...

    if (successfulInputDetected)
    {
        inputFlag_AnalogStickInput = true;
        OnUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag);
    }
}



void FixedUpdate()
{
    ...
    if (inputFlag_AnalogStickInput)
    {
        OnFixedUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag); 

    }
    if (inputFlag_AnalogStickInput && firstFixedUpdateAfterUpdate)  
    {
        OnFirstFixedUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag); 
    }
    ...
    firstFixedUpdateAfterUpdate = false;
}

希望这是有道理的。

答案 1 :(得分:1)

您不妨考虑自定义Script Execution Order并让手势处理器先运行。

希望有帮助。 =)