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的答案,使我的逻辑更具确定性!