Camera: Cinemachine-based rig with an anchor, a state director, point-of- interest framing, pan input, and dialogue framing volumes. Player: click-to-move over the NavMesh, a pointer input layer, and a motion arbiter that reconciles direct stick input with navigation-driven movement. The pure math is isolated in NightclubArcadia.Locomotion.Math with an EditMode suite, so the arbitration rules are testable without a scene. Interaction: IClickInteractable plus a bridge that routes pointer clicks to it. Supporting project settings: adds the Cinemachine package, the Interactable and Ground tags the raycasts depend on, and NavMesh agent dimensions matched to the player capsule (radius 0.3, height 1.8, climb 0.25). Co-Authored-By: Claude Opus 5 <[email protected]>
115 lines
3.0 KiB
C#
115 lines
3.0 KiB
C#
using NightclubArcadia.Interaction;
|
|
using NightclubArcadia.UI;
|
|
using StarterAssets;
|
|
using UnityEngine;
|
|
#if ENABLE_INPUT_SYSTEM
|
|
using UnityEngine.InputSystem;
|
|
#endif
|
|
|
|
namespace NightclubArcadia.Player
|
|
{
|
|
public enum MotionSource
|
|
{
|
|
None,
|
|
Direct,
|
|
Navigation
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sole writer of StarterAssetsInputs move/sprint. Picks one input source
|
|
/// per frame: direct keyboard/gamepad or click-to-move navigation.
|
|
/// </summary>
|
|
[DefaultExecutionOrder(-50)]
|
|
public sealed class PlayerMotionArbiter : MonoBehaviour
|
|
{
|
|
[SerializeField] float directInputCancelThreshold = 0.15f;
|
|
[SerializeField] float runDistanceThreshold = 6f;
|
|
|
|
StarterAssetsInputs playerInputs;
|
|
PlayerControlLock controlLock;
|
|
ClickToMoveController clickToMove;
|
|
#if ENABLE_INPUT_SYSTEM
|
|
PlayerInput playerInput;
|
|
#endif
|
|
|
|
public MotionSource ActiveSource { get; private set; }
|
|
|
|
void Awake()
|
|
{
|
|
playerInputs = GetComponent<StarterAssetsInputs>();
|
|
controlLock = GetComponent<PlayerControlLock>();
|
|
clickToMove = GetComponent<ClickToMoveController>();
|
|
#if ENABLE_INPUT_SYSTEM
|
|
playerInput = GetComponent<PlayerInput>();
|
|
#endif
|
|
}
|
|
|
|
public void ResetState()
|
|
{
|
|
ActiveSource = MotionSource.None;
|
|
clickToMove?.Cancel();
|
|
WriteInput(Vector2.zero, false);
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (controlLock != null && controlLock.IsLocked)
|
|
{
|
|
clickToMove?.Cancel();
|
|
ActiveSource = MotionSource.None;
|
|
WriteInput(Vector2.zero, false);
|
|
return;
|
|
}
|
|
|
|
#if ENABLE_INPUT_SYSTEM
|
|
var raw = playerInput.actions["Move"].ReadValue<Vector2>();
|
|
#else
|
|
var raw = Vector2.zero;
|
|
#endif
|
|
|
|
if (raw.sqrMagnitude > directInputCancelThreshold * directInputCancelThreshold)
|
|
{
|
|
clickToMove?.Cancel();
|
|
ActiveSource = MotionSource.Direct;
|
|
WriteInput(raw, ReadDirectSprint());
|
|
return;
|
|
}
|
|
|
|
if (clickToMove != null && clickToMove.HasPath)
|
|
{
|
|
ActiveSource = MotionSource.Navigation;
|
|
WriteInput(clickToMove.DesiredMoveInput, ShouldNavigationSprint());
|
|
return;
|
|
}
|
|
|
|
ActiveSource = MotionSource.None;
|
|
WriteInput(Vector2.zero, false);
|
|
}
|
|
|
|
bool ReadDirectSprint()
|
|
{
|
|
#if ENABLE_INPUT_SYSTEM
|
|
return playerInput.actions["Sprint"].IsPressed();
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool ShouldNavigationSprint()
|
|
{
|
|
return clickToMove != null && clickToMove.WantsSprint;
|
|
}
|
|
|
|
void WriteInput(Vector2 move, bool sprint)
|
|
{
|
|
if (playerInputs == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
playerInputs.MoveInput(move);
|
|
playerInputs.SprintInput(sprint);
|
|
}
|
|
}
|
|
}
|