我有一个输入脚本,它将触摸转换为方向(左、右、上、下(,在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);
}
}
}
我从 Unity 检查器订阅了这个OnAnalogStickInput
事件,以调用我的CharacterController2D.Move
方法,该方法采用方向和量级:
public class CharacterController2D : MonoBehaviour
{
public void Move(Direction movementDirection, float normalizedInputMagnitude)
{
// Move the character.
}
}
然后我还有另一个GameObject
,我希望使用相同的输入脚本进行旋转。 所以我TouchAnalogStickInput
和SnapRotator
附加到这个GameObject
,订阅OnAnalogStickInput
事件来调用SnapRotator.Rotate
:
public class SnapRotator : MonoBehaviour
{
public void Rotate(Direction movementDirection, float normalizedInputMagnitude)
{
// Rotate object.
}
}
在这一点上,我已经意识到我不再负责从哪些游戏循环调用这些方法,例如,我应该在Update
中检测输入,在FixedUpdate
中使用此输入进行移动,也许在我的情况下,我想在LateUpdate
中最后进行旋转。相反,CharacterController2D.Move
和SnapRotator.Rotate
都是从运行输入代码的Update
循环中触发的。
我能想到的唯一其他选择可能是将输入脚本的代码重构为方法调用。然后让CharacterController2D
和SnapRotator
在Update
循环中调用此方法,根据需要在FixedUpdate
或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);
}
}
}
订阅OnFixedUpdateOnAnalogStickInput
事件的字符控制器。
public class CharacterController2D : MonoBehaviour
{
public void Move(InputInfo inputInfo)
{
// Use inputInfo to decide how to move.
}
}
旋转类大致相同,但订阅OnLateUpdateOnAnalogStickInput
事件。
这里有一种选择,它主要需要更改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...
事件会向每一种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;
}
希望这是有道理的。
您可能希望考虑自定义脚本执行顺序,并让您的手势处理器先于其他所有操作运行。
希望有帮助。 =(