From ccabd3e7acb2550d41fb25850ddd2e24af661253 Mon Sep 17 00:00:00 2001 From: grabowskil Date: Tue, 25 Aug 2026 19:04:01 +0200 Subject: [PATCH] wire player control setup and tune the third-person controller Editor/PlayerControlSetup.cs is the one-shot menu that assembles the playable player: the armature, the Cinemachine rig, the NavMesh agent, and the pointer input wiring, so a scene can be brought up to a controllable state repeatably rather than by hand. StarterAssets input actions gain the pointer and interaction bindings the new locomotion and interaction layers read, and PlayerArmature is updated to match. ThirdPersonController blends the animator toward target speed scaled by input magnitude. Previously a partial stick deflection or a click-to-move arrival played a slowed run clip via MotionSpeed alone; scaling the blend makes those use the walk cycle instead. Co-Authored-By: Claude Opus 5 --- .../Assets/Editor/PlayerControlSetup.cs | 316 ++++++++++++++++++ .../Assets/Editor/PlayerControlSetup.cs.meta | 2 + .../InputSystem/StarterAssets.inputactions | 117 +++++++ .../Prefabs/PlayerArmature.prefab | 92 ++++- .../Scripts/ThirdPersonController.cs | 6 +- 5 files changed, 529 insertions(+), 4 deletions(-) create mode 100644 NightclubArcadia/Assets/Editor/PlayerControlSetup.cs create mode 100644 NightclubArcadia/Assets/Editor/PlayerControlSetup.cs.meta 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