Files
nightclub-arcadia/NightclubArcadia/Assets/Scripts/Player/PlayerMotionArbiter.cs
T
lennartandClaude Opus 5 932cd0582f add camera rig, click-to-move locomotion, and click interaction
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]>
2026-08-25 19:03:18 +02:00

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