diff --git a/NightclubArcadia/Assets/Editor/PlayerControlSetup.cs b/NightclubArcadia/Assets/Editor/PlayerControlSetup.cs
new file mode 100644
index 0000000..882be97
--- /dev/null
+++ b/NightclubArcadia/Assets/Editor/PlayerControlSetup.cs
@@ -0,0 +1,316 @@
+using NightclubArcadia.Cinematics;
+using NightclubArcadia.Interaction;
+using NightclubArcadia.Player;
+using StarterAssets;
+using Unity.AI.Navigation;
+using Unity.Cinemachine;
+using UnityEditor;
+using UnityEditor.SceneManagement;
+using UnityEngine;
+using UnityEngine.AI;
+
+///
+/// Wires player control, navigation, and camera systems into DialogueTest.unity.
+/// Run via Tools → Nightclub Arcadia → Set Up Player Control & Camera.
+///
+public static class PlayerControlSetup
+{
+ const string ScenePath = "Assets/Scenes/DialogueTest.unity";
+ const string PlayerPrefabPath = "Assets/StarterAssets/ThirdPersonController/Prefabs/PlayerArmature.prefab";
+ const string SettingsFolder = "Assets/Settings";
+
+ [MenuItem("Tools/Nightclub Arcadia/Set Up Player Control & Camera")]
+ public static void SetUp()
+ {
+ EnsurePlayerPrefab();
+ WireScene();
+ AssetDatabase.SaveAssets();
+ Debug.Log("[PlayerControlSetup] Player control and camera setup complete.");
+ }
+
+ static void EnsurePlayerPrefab()
+ {
+ var prefab = AssetDatabase.LoadAssetAtPath(PlayerPrefabPath);
+ if (prefab == null)
+ {
+ Debug.LogError($"[PlayerControlSetup] Missing prefab at {PlayerPrefabPath}");
+ return;
+ }
+
+ var root = PrefabUtility.LoadPrefabContents(PlayerPrefabPath);
+
+ EnsureComponent(root);
+ EnsureComponent(root);
+ EnsureComponent(root);
+ EnsureComponent(root);
+ EnsureComponent(root);
+
+ var inputs = root.GetComponent();
+ if (inputs != null)
+ {
+ inputs.analogMovement = true;
+ inputs.cursorLocked = false;
+ }
+
+ var controller = root.GetComponent();
+ if (controller != null)
+ {
+ controller.LockCameraPosition = true;
+ }
+
+ var clickToMove = root.GetComponent();
+ var pointerNav = root.GetComponent();
+ var pointerInput = root.GetComponent();
+ var arbiter = root.GetComponent();
+
+ WireSerializedReference(clickToMove, "brainCamera", null);
+ WireSerializedReference(pointerNav, "pointerInput", pointerInput);
+ WireSerializedReference(pointerNav, "clickToMove", clickToMove);
+ WireSerializedReference(pointerNav, "motionArbiter", arbiter);
+
+ PrefabUtility.SaveAsPrefabAsset(root, PlayerPrefabPath);
+ PrefabUtility.UnloadPrefabContents(root);
+ }
+
+ static void WireScene()
+ {
+ var scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single);
+ var player = GameObject.FindGameObjectWithTag("Player");
+ if (player == null)
+ {
+ Debug.LogError("[PlayerControlSetup] No Player-tagged object found in scene.");
+ return;
+ }
+
+ EnsureComponent(player);
+ EnsureComponent(player);
+ EnsureComponent(player);
+ EnsureComponent(player);
+ EnsureComponent(player);
+
+ var inputs = player.GetComponent();
+ if (inputs != null)
+ {
+ inputs.analogMovement = true;
+ inputs.cursorLocked = false;
+ }
+
+ var controller = player.GetComponent();
+ if (controller != null)
+ {
+ controller.LockCameraPosition = true;
+ }
+
+ var brainCamera = Camera.main;
+ var pointerInput = player.GetComponent();
+ var clickToMove = player.GetComponent();
+ var pointerNav = player.GetComponent();
+ var arbiter = player.GetComponent();
+
+ WireSerializedReference(clickToMove, "brainCamera", brainCamera);
+ WireSerializedReference(pointerNav, "brainCamera", brainCamera);
+ WireSerializedReference(pointerNav, "pointerInput", pointerInput);
+ WireSerializedReference(pointerNav, "clickToMove", clickToMove);
+ WireSerializedReference(pointerNav, "motionArbiter", arbiter);
+
+ var groundLayer = LayerMask.NameToLayer("Ground");
+ var interactableLayer = LayerMask.NameToLayer("Interactable");
+ SetLayerIfExists("Floor", groundLayer);
+ SetLayerIfExists("Room_101", groundLayer);
+ SetLayerIfExists("Wall_Back", groundLayer);
+ SetLayerIfExists("Wall_Front", groundLayer);
+ SetLayerIfExists("Table", groundLayer);
+
+ WireBartender("Bartender", interactableLayer);
+ WireBartender("Bartender (1)", interactableLayer);
+
+ EnsureNavigation(player.transform.position);
+ EnsureCameraAnchor(player.transform, brainCamera);
+
+ EditorSceneManager.MarkSceneDirty(scene);
+ EditorSceneManager.SaveScene(scene);
+ }
+
+ static void WireBartender(string objectName, int interactableLayer)
+ {
+ var bartender = GameObject.Find(objectName);
+ if (bartender == null)
+ {
+ return;
+ }
+
+ if (interactableLayer >= 0)
+ {
+ bartender.layer = interactableLayer;
+ }
+
+ EnsureComponent(bartender);
+ EnsureComponent(bartender);
+ }
+
+ static void EnsureNavigation(Vector3 sampleOrigin)
+ {
+ var navigation = GameObject.Find("Navigation");
+ if (navigation == null)
+ {
+ navigation = new GameObject("Navigation");
+ }
+
+ var surface = EnsureComponent(navigation);
+ surface.agentTypeID = 0;
+ surface.collectObjects = CollectObjects.All;
+ surface.useGeometry = NavMeshCollectGeometry.RenderMeshes;
+
+ var included = 0;
+ var defaultLayer = LayerMask.NameToLayer("Default");
+ var groundLayer = LayerMask.NameToLayer("Ground");
+ if (defaultLayer >= 0)
+ {
+ included |= 1 << defaultLayer;
+ }
+
+ if (groundLayer >= 0)
+ {
+ included |= 1 << groundLayer;
+ }
+
+ surface.layerMask = included;
+
+ var table = GameObject.Find("Table");
+ if (table != null)
+ {
+ var modifier = EnsureComponent(table);
+ modifier.overrideArea = true;
+ modifier.area = NavMesh.GetAreaFromName("Not Walkable");
+ }
+
+ surface.BuildNavMesh();
+
+ if (!NavMesh.SamplePosition(sampleOrigin, out _, 2f, NavMesh.AllAreas))
+ {
+ Debug.LogWarning("[PlayerControlSetup] NavMesh bake did not cover the player start position.");
+ }
+ }
+
+ static void EnsureCameraAnchor(Transform followTarget, Camera brainCamera)
+ {
+ var vcamObject = GameObject.Find("CinemachineCamera");
+ if (vcamObject == null)
+ {
+ Debug.LogError("[PlayerControlSetup] CinemachineCamera not found.");
+ return;
+ }
+
+ var vcam = vcamObject.GetComponent();
+ var composer = vcamObject.GetComponent();
+
+ var anchorObject = GameObject.Find("CameraAnchor");
+ if (anchorObject == null)
+ {
+ anchorObject = new GameObject("CameraAnchor");
+ }
+
+ anchorObject.transform.position = followTarget.position;
+
+ var anchor = EnsureComponent(anchorObject);
+ EnsureComponent(anchorObject);
+ EnsureComponent(anchorObject);
+ EnsureComponent(anchorObject);
+
+ WireSerializedReference(anchor, "followTarget", followTarget);
+ WireSerializedReference(anchor, "composer", composer);
+
+ if (vcam != null)
+ {
+ vcam.Target.TrackingTarget = anchorObject.transform;
+ vcam.Priority.Value = 10;
+ }
+
+ var pan = anchorObject.GetComponent();
+ WireSerializedReference(pan, "brainCamera", brainCamera);
+ WireSerializedReference(pan, "anchor", anchor);
+ WireSerializedReference(pan, "stateDirector", anchorObject.GetComponent());
+
+ var poiDirector = anchorObject.GetComponent();
+ WireSerializedReference(poiDirector, "anchor", anchor);
+ WireSerializedReference(poiDirector, "stateDirector", anchorObject.GetComponent());
+
+ var stateDirector = anchorObject.GetComponent();
+ WireSerializedReference(stateDirector, "anchor", anchor);
+
+ EnsureRevealCamera(vcamObject, followTarget);
+ }
+
+ static void EnsureRevealCamera(GameObject followVcam, Transform followTarget)
+ {
+ var revealName = "CM_Reveal_Room101";
+ var revealObject = GameObject.Find(revealName);
+ if (revealObject == null)
+ {
+ revealObject = new GameObject(revealName);
+ revealObject.transform.SetPositionAndRotation(
+ followVcam.transform.position + new Vector3(-4f, 2f, -2f),
+ followVcam.transform.rotation);
+ }
+
+ var revealCam = EnsureComponent(revealObject);
+ revealCam.Target.TrackingTarget = followTarget;
+ revealCam.Priority.Value = 0;
+ revealCam.Lens.FieldOfView = followVcam.GetComponent().Lens.FieldOfView;
+
+ var volumeObject = GameObject.Find("RevealVolume_Room101");
+ if (volumeObject == null)
+ {
+ volumeObject = new GameObject("RevealVolume_Room101");
+ volumeObject.transform.position = followTarget.position + new Vector3(0f, 1f, 2f);
+ }
+
+ var box = EnsureComponent(volumeObject);
+ box.isTrigger = true;
+ box.size = new Vector3(6f, 3f, 4f);
+
+ var volume = EnsureComponent(volumeObject);
+ WireSerializedReference(volume, "revealCamera", revealCam);
+ }
+
+ static T EnsureComponent(GameObject target) where T : Component
+ {
+ var component = target.GetComponent();
+ if (component == null)
+ {
+ component = target.AddComponent();
+ }
+
+ return component;
+ }
+
+ static void SetLayerIfExists(string objectName, int layer)
+ {
+ if (layer < 0)
+ {
+ return;
+ }
+
+ var obj = GameObject.Find(objectName);
+ if (obj != null)
+ {
+ obj.layer = layer;
+ }
+ }
+
+ static void WireSerializedReference(Object target, string propertyName, Object value)
+ {
+ if (target == null)
+ {
+ return;
+ }
+
+ var so = new SerializedObject(target);
+ var property = so.FindProperty(propertyName);
+ if (property != null)
+ {
+ property.objectReferenceValue = value;
+ so.ApplyModifiedPropertiesWithoutUndo();
+ }
+ }
+}
diff --git a/NightclubArcadia/Assets/Editor/PlayerControlSetup.cs.meta b/NightclubArcadia/Assets/Editor/PlayerControlSetup.cs.meta
new file mode 100644
index 0000000..6ca74c5
--- /dev/null
+++ b/NightclubArcadia/Assets/Editor/PlayerControlSetup.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: eedfabcda58c94099b2ad3e19f141b09
\ No newline at end of file
diff --git a/NightclubArcadia/Assets/StarterAssets/InputSystem/StarterAssets.inputactions b/NightclubArcadia/Assets/StarterAssets/InputSystem/StarterAssets.inputactions
index b6642bf..fbd89e0 100644
--- a/NightclubArcadia/Assets/StarterAssets/InputSystem/StarterAssets.inputactions
+++ b/NightclubArcadia/Assets/StarterAssets/InputSystem/StarterAssets.inputactions
@@ -36,6 +36,46 @@
"expectedControlType": "",
"processors": "",
"interactions": ""
+ },
+ {
+ "name": "Menu",
+ "type": "Button",
+ "id": "e81d9782-4d9d-4a8b-b5a3-bc2f31e1cdcc",
+ "expectedControlType": "Button",
+ "processors": "",
+ "interactions": ""
+ },
+ {
+ "name": "Point",
+ "type": "PassThrough",
+ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ "expectedControlType": "Vector2",
+ "processors": "",
+ "interactions": ""
+ },
+ {
+ "name": "Click",
+ "type": "Button",
+ "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
+ "expectedControlType": "Button",
+ "processors": "",
+ "interactions": ""
+ },
+ {
+ "name": "CameraPan",
+ "type": "Button",
+ "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
+ "expectedControlType": "Button",
+ "processors": "",
+ "interactions": ""
+ },
+ {
+ "name": "PointerDelta",
+ "type": "PassThrough",
+ "id": "d4e5f6a7-b8c9-0123-def0-234567890123",
+ "expectedControlType": "Vector2",
+ "processors": "",
+ "interactions": ""
}
],
"bindings": [
@@ -214,6 +254,83 @@
"action": "Sprint",
"isComposite": false,
"isPartOfComposite": false
+ },
+ {
+ "name": "",
+ "id": "0f5e00e8-de3a-454f-8338-4348e69ac91a",
+ "path": "/tab",
+ "interactions": "",
+ "processors": "",
+ "groups": "KeyboardMouse",
+ "action": "Menu",
+ "isComposite": false,
+ "isPartOfComposite": false
+ },
+ {
+ "name": "",
+ "id": "8533b9e3-7efc-442a-9d07-8fe1b29a3bf0",
+ "path": "/start",
+ "interactions": "",
+ "processors": "",
+ "groups": "Gamepad",
+ "action": "Menu",
+ "isComposite": false,
+ "isPartOfComposite": false
+ },
+ {
+ "name": "",
+ "id": "e5f6a7b8-c9d0-1234-ef01-345678901234",
+ "path": "/position",
+ "interactions": "",
+ "processors": "",
+ "groups": "KeyboardMouse",
+ "action": "Point",
+ "isComposite": false,
+ "isPartOfComposite": false
+ },
+ {
+ "name": "",
+ "id": "f6a7b8c9-d0e1-2345-f012-456789012345",
+ "path": "/leftButton",
+ "interactions": "",
+ "processors": "",
+ "groups": "KeyboardMouse",
+ "action": "Click",
+ "isComposite": false,
+ "isPartOfComposite": false
+ },
+ {
+ "name": "",
+ "id": "a7b8c9d0-e1f2-3456-0123-567890123456",
+ "path": "/middleButton",
+ "interactions": "",
+ "processors": "",
+ "groups": "KeyboardMouse",
+ "action": "CameraPan",
+ "isComposite": false,
+ "isPartOfComposite": false
+ },
+ {
+ "name": "",
+ "id": "b8c9d0e1-f2a3-4567-1234-678901234567",
+ "path": "/rightButton",
+ "interactions": "",
+ "processors": "",
+ "groups": "KeyboardMouse",
+ "action": "CameraPan",
+ "isComposite": false,
+ "isPartOfComposite": false
+ },
+ {
+ "name": "",
+ "id": "c9d0e1f2-a3b4-5678-2345-789012345678",
+ "path": "/delta",
+ "interactions": "",
+ "processors": "",
+ "groups": "KeyboardMouse",
+ "action": "PointerDelta",
+ "isComposite": false,
+ "isPartOfComposite": false
}
]
}
diff --git a/NightclubArcadia/Assets/StarterAssets/ThirdPersonController/Prefabs/PlayerArmature.prefab b/NightclubArcadia/Assets/StarterAssets/ThirdPersonController/Prefabs/PlayerArmature.prefab
index 87ce6fa..af0da32 100644
--- a/NightclubArcadia/Assets/StarterAssets/ThirdPersonController/Prefabs/PlayerArmature.prefab
+++ b/NightclubArcadia/Assets/StarterAssets/ThirdPersonController/Prefabs/PlayerArmature.prefab
@@ -1037,6 +1037,11 @@ GameObject:
- component: {fileID: 5885216827943657505}
- component: {fileID: 135756642000475821}
- component: {fileID: 4416926081852918493}
+ - component: {fileID: 4411642300945204457}
+ - component: {fileID: 2899400238410355188}
+ - component: {fileID: 5730189035290769348}
+ - component: {fileID: 1531343089306214720}
+ - component: {fileID: 4718598727395454326}
m_Layer: 8
m_Name: PlayerArmature
m_TagString: Player
@@ -1155,7 +1160,7 @@ MonoBehaviour:
TopClamp: 70
BottomClamp: -30
CameraAngleOverride: 0
- LockCameraPosition: 0
+ LockCameraPosition: 1
--- !u!114 &5885216827943657505
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -1189,8 +1194,8 @@ MonoBehaviour:
look: {x: 0, y: 0}
jump: 0
sprint: 0
- analogMovement: 0
- cursorLocked: 1
+ analogMovement: 1
+ cursorLocked: 0
cursorInputForLook: 1
--- !u!114 &4416926081852918493
MonoBehaviour:
@@ -1290,6 +1295,87 @@ MonoBehaviour:
m_DefaultActionMap: Player
m_SplitScreenIndex: -1
m_Camera: {fileID: 0}
+--- !u!114 &4411642300945204457
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 4416926081852918481}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: ff77faf7e79154095ad57ad50df8c605, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::NightclubArcadia.Player.PlayerPointerInput
+--- !u!114 &2899400238410355188
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 4416926081852918481}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 91bf24256c6694185b4247a736593727, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::NightclubArcadia.Player.PlayerMotionArbiter
+ directInputCancelThreshold: 0.15
+ runDistanceThreshold: 6
+--- !u!195 &5730189035290769348
+NavMeshAgent:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 4416926081852918481}
+ m_Enabled: 1
+ m_AgentTypeID: 0
+ m_Radius: 0.5
+ m_Speed: 3.5
+ m_Acceleration: 8
+ avoidancePriority: 50
+ m_AngularSpeed: 120
+ m_StoppingDistance: 0
+ m_AutoTraverseOffMeshLink: 1
+ m_AutoBraking: 1
+ m_AutoRepath: 1
+ m_Height: 2
+ m_BaseOffset: 0
+ m_WalkableMask: 4294967295
+ m_ObstacleAvoidanceType: 4
+--- !u!114 &1531343089306214720
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 4416926081852918481}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: c2762b30721644611b9672bd4781cf15, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::NightclubArcadia.Player.ClickToMoveController
+ brainCamera: {fileID: 0}
+ runDistanceThreshold: 6
+--- !u!114 &4718598727395454326
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 4416926081852918481}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5f62460e35fda4829bd6ffeffe633a7e, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::NightclubArcadia.Player.PointerNavigationInput
+ brainCamera: {fileID: 0}
+ clickMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ pointerInput: {fileID: 4411642300945204457}
+ clickToMove: {fileID: 1531343089306214720}
+ motionArbiter: {fileID: 2899400238410355188}
--- !u!1 &4523139366846529165
GameObject:
m_ObjectHideFlags: 0
diff --git a/NightclubArcadia/Assets/StarterAssets/ThirdPersonController/Scripts/ThirdPersonController.cs b/NightclubArcadia/Assets/StarterAssets/ThirdPersonController/Scripts/ThirdPersonController.cs
index f551708..e48b582 100644
--- a/NightclubArcadia/Assets/StarterAssets/ThirdPersonController/Scripts/ThirdPersonController.cs
+++ b/NightclubArcadia/Assets/StarterAssets/ThirdPersonController/Scripts/ThirdPersonController.cs
@@ -248,7 +248,11 @@ namespace StarterAssets
_speed = targetSpeed;
}
- _animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);
+ // NightclubArcadia: blend toward speed scaled by input magnitude so
+ // partial stick / click-to-move arrival uses walk cycles, not a
+ // slowed run clip via MotionSpeed alone.
+ _animationBlend = Mathf.Lerp(_animationBlend, targetSpeed * inputMagnitude,
+ Time.deltaTime * SpeedChangeRate);
if (_animationBlend < 0.01f) _animationBlend = 0f;
// normalise input direction