C# 在Unity中将输入与其他脚本分离的最佳实践

C# 在Unity中将输入与其他脚本分离的最佳实践,c#,unity3d,C#,Unity3d,我有一个输入脚本,在Update循环中将触摸转换为大小为(0-1)的方向(左、右、上、下),当检测到输入时,该脚本触发一个UnityEvent: public class TouchAnalogStickInput : MonoBehaviour { [System.Serializable] public class AnalogStickInputEvent : UnityEvent<Direction, float> { } [Header("Even

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

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);
        }
    }
}
然后我有另一个
GameObject
,我希望使用相同的输入脚本旋转它。因此,我已将
TouchAnalogStickInput
SnapRotator
附加到此
GameObject
,订阅NanalogStickinPut事件以调用
SnapRotator.Rotate

public class SnapRotator : MonoBehaviour
{
    public void Rotate(Direction movementDirection, float normalizedInputMagnitude)
    {
        // Rotate object.
    }
}
此时,我意识到我不再负责调用这些方法的游戏循环,例如我应该在
Update
中检测输入,在
FixedUpdate
中使用此输入进行移动,也许在我的情况下,我想在
LateUpdate
中最后进行旋转相反从运行输入代码的
更新
循环触发
字符控制器2D.Move
SnapRotator.Rotate

我能想到的另一个选择可能是将输入脚本的代码重构为方法调用。然后让
CharacterController2D
SnapRotator
Update
循环中调用此方法,根据需要在
fixeupdate
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.
    } 
}
我的问题是:在Unity中分离这样的脚本的最佳实践是什么?或者我是不是把可重用性看得太远了/过于害怕在游戏开发中耦合子类/组件

解决方案 如果这对其他人有帮助,这是我的最终解决方案,在半sudocode中,归功于Ruzihm:

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

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);
        }
    }
}
输入类:

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);
        }
    }
}

旋转类大致相同,但订阅了
OnLateUpdateOnAnalogStickInput
事件。

这里有一个备选方案,主要需要更改
触摸AnalogstickInput

public class CharacterController2D : MonoBehaviour
{
    public void Move(InputInfo inputInfo)
    {
        // Use inputInfo to decide how to move.
    }
}
Update
中设置输入状态标志,并触发任何相关事件。只有这一次,您触发了一个“OnUpdate”事件(在您的特定情况下,它不会注册任何内容):

TouchAnalogStickInput.fixeUpdate
中,调用
OnFixedUpdateOnAnalogStickInput
,它将与您的
移动一起注册

void FixedUpdate()
{
    ...
    if (inputFlag_AnalogStickInput)
    {
        OnFixedUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag);
    }
}
以LateUpdate为例,它触发一个事件,您的
Rotate
注册了该事件

void LateUpdate()
{
    ...
    if (inputFlag_AnalogStickInput)
    {
        OnLateUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag);
    }
}
当然,这些
OnFixedUpdateOn…
事件会在标记为true的每个
FixedUpdate
上触发。对于大多数情况,包括移动,这可能是合适的,但在其他情况下可能并不理想。因此,您可以添加仅在更新后的第一次
FixedUpdate
事件中触发的附加事件。e、 g:

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;
}

希望这是有意义的。

您可能希望先考虑定制和运行手势处理器


希望有帮助

好吧,我不确定在哪张贴这个问题。你能告诉我为什么这个问题对于堆栈溢出是不正确的,这样我就可以知道将来的情况了?实际上,取消这个问题!我对SE&SO之间的边界线有错误的印象:)SnapRotator和CharacterController也有Update()和fixeUpdate()。您可以将方向和大小复制到私有变量,并在Update()/FixedUpdate()中使用它们。也许改变脚本执行顺序,让TouchAnalogStickInput Update()在其他人之前被调用。顺便说一句,统一问题通常会在游戏开发中出现。太棒了!这将需要最少的重构,并且仍然保持一切非常好的解耦。当您谈到为firstFixedUpdateAfterUpdate添加一个附加标志时,这是因为FixedUpdate可能会在每帧中运行多次,并且有些情况/脚本可能只希望订阅这些固定更新中的第一个?您是否有只想在firstFixedUpdateAfterUpdate上运行的脚本示例?好问题。我没有任何具体的例子!对于一次性调试的目的来说,它可能很有用,比如切换一些不应该在“代码>中间更新< <代码> >或<代码> LATEUPDATE < /代码>的调试选项。我想我会把它作为一种可能性提出来,即使它是一个边缘案例。谢谢,我将利用这个脚本执行顺序机制结合Ruzihm的答案,使我的逻辑更具确定性!