diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity
index 8d5ec2e..47b19e4 100644
--- a/Assets/Scenes/SampleScene.unity
+++ b/Assets/Scenes/SampleScene.unity
@@ -119,6 +119,118 @@ NavMeshSettings:
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
+--- !u!1 &200502118
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 200502122}
+ - component: {fileID: 200502121}
+ - component: {fileID: 200502120}
+ - component: {fileID: 200502119}
+ m_Layer: 0
+ m_Name: Cube (1)
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!65 &200502119
+BoxCollider:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200502118}
+ m_Material: {fileID: 0}
+ m_IncludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_ExcludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_LayerOverridePriority: 0
+ m_IsTrigger: 0
+ m_ProvidesContacts: 0
+ m_Enabled: 1
+ serializedVersion: 3
+ m_Size: {x: 1, y: 1, z: 1}
+ m_Center: {x: 0, y: 0, z: 0}
+--- !u!23 &200502120
+MeshRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200502118}
+ m_Enabled: 1
+ m_CastShadows: 1
+ m_ReceiveShadows: 1
+ m_DynamicOccludee: 1
+ m_StaticShadowCaster: 0
+ m_MotionVectors: 1
+ m_LightProbeUsage: 1
+ m_ReflectionProbeUsage: 1
+ m_RayTracingMode: 2
+ m_RayTraceProcedural: 0
+ m_RayTracingAccelStructBuildFlagsOverride: 0
+ m_RayTracingAccelStructBuildFlags: 1
+ m_SmallMeshCulling: 1
+ m_ForceMeshLod: -1
+ m_MeshLodSelectionBias: 0
+ m_RenderingLayerMask: 1
+ m_RendererPriority: 0
+ m_Materials:
+ - {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
+ m_StaticBatchInfo:
+ firstSubMesh: 0
+ subMeshCount: 0
+ m_StaticBatchRoot: {fileID: 0}
+ m_ProbeAnchor: {fileID: 0}
+ m_LightProbeVolumeOverride: {fileID: 0}
+ m_ScaleInLightmap: 1
+ m_ReceiveGI: 1
+ m_PreserveUVs: 0
+ m_IgnoreNormalsForChartDetection: 0
+ m_ImportantGI: 0
+ m_StitchLightmapSeams: 1
+ m_SelectedEditorRenderState: 3
+ m_MinimumChartSize: 4
+ m_AutoUVMaxDistance: 0.5
+ m_AutoUVMaxAngle: 89
+ m_LightmapParameters: {fileID: 0}
+ m_GlobalIlluminationMeshLod: 0
+ m_SortingLayerID: 0
+ m_SortingLayer: 0
+ m_SortingOrder: 0
+ m_MaskInteraction: 0
+ m_AdditionalVertexStreams: {fileID: 0}
+--- !u!33 &200502121
+MeshFilter:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200502118}
+ m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
+--- !u!4 &200502122
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200502118}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 1.29, y: 0.5, z: -2.5}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &330585543
GameObject:
m_ObjectHideFlags: 0
@@ -205,13 +317,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 330585543}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 0, y: 0.67, z: -1.3}
+ m_LocalRotation: {x: 0.3420201, y: 0, z: 0, w: 0.9396927}
+ m_LocalPosition: {x: 0, y: 4.43, z: -7.8}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_LocalEulerAnglesHint: {x: 40, y: 0, z: 0}
--- !u!114 &330585547
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -410,13 +522,126 @@ MonoBehaviour:
updateColliderEveryFrame: 0
brushShader: {fileID: 0}
dilateShader: {fileID: 0}
+--- !u!1 &721670969
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 721670973}
+ - component: {fileID: 721670972}
+ - component: {fileID: 721670971}
+ - component: {fileID: 721670970}
+ m_Layer: 0
+ m_Name: Plane
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!64 &721670970
+MeshCollider:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 721670969}
+ m_Material: {fileID: 0}
+ m_IncludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_ExcludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_LayerOverridePriority: 0
+ m_IsTrigger: 0
+ m_ProvidesContacts: 0
+ m_Enabled: 1
+ serializedVersion: 5
+ m_Convex: 0
+ m_CookingOptions: 30
+ m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
+--- !u!23 &721670971
+MeshRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 721670969}
+ m_Enabled: 1
+ m_CastShadows: 1
+ m_ReceiveShadows: 1
+ m_DynamicOccludee: 1
+ m_StaticShadowCaster: 0
+ m_MotionVectors: 1
+ m_LightProbeUsage: 1
+ m_ReflectionProbeUsage: 1
+ m_RayTracingMode: 2
+ m_RayTraceProcedural: 0
+ m_RayTracingAccelStructBuildFlagsOverride: 0
+ m_RayTracingAccelStructBuildFlags: 1
+ m_SmallMeshCulling: 1
+ m_ForceMeshLod: -1
+ m_MeshLodSelectionBias: 0
+ m_RenderingLayerMask: 1
+ m_RendererPriority: 0
+ m_Materials:
+ - {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
+ m_StaticBatchInfo:
+ firstSubMesh: 0
+ subMeshCount: 0
+ m_StaticBatchRoot: {fileID: 0}
+ m_ProbeAnchor: {fileID: 0}
+ m_LightProbeVolumeOverride: {fileID: 0}
+ m_ScaleInLightmap: 1
+ m_ReceiveGI: 1
+ m_PreserveUVs: 0
+ m_IgnoreNormalsForChartDetection: 0
+ m_ImportantGI: 0
+ m_StitchLightmapSeams: 1
+ m_SelectedEditorRenderState: 3
+ m_MinimumChartSize: 4
+ m_AutoUVMaxDistance: 0.5
+ m_AutoUVMaxAngle: 89
+ m_LightmapParameters: {fileID: 0}
+ m_GlobalIlluminationMeshLod: 0
+ m_SortingLayerID: 0
+ m_SortingLayer: 0
+ m_SortingOrder: 0
+ m_MaskInteraction: 0
+ m_AdditionalVertexStreams: {fileID: 0}
+--- !u!33 &721670972
+MeshFilter:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 721670969}
+ m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
+--- !u!4 &721670973
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 721670969}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &821516643
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
- m_TransformParent: {fileID: 0}
+ m_TransformParent: {fileID: 1141039639}
m_Modifications:
- target: {fileID: -8679921383154817045, guid: 554c0bdf86263754d862d7effd04045b, type: 3}
propertyPath: m_LocalPosition.x
@@ -440,11 +665,11 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 554c0bdf86263754d862d7effd04045b, type: 3}
propertyPath: m_LocalRotation.y
- value: 0
+ value: -0
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 554c0bdf86263754d862d7effd04045b, type: 3}
propertyPath: m_LocalRotation.z
- value: 0
+ value: -0
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 554c0bdf86263754d862d7effd04045b, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
@@ -519,6 +744,206 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &963088878
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 963088882}
+ - component: {fileID: 963088881}
+ - component: {fileID: 963088880}
+ - component: {fileID: 963088879}
+ m_Layer: 0
+ m_Name: Cube
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!65 &963088879
+BoxCollider:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 963088878}
+ m_Material: {fileID: 0}
+ m_IncludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_ExcludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_LayerOverridePriority: 0
+ m_IsTrigger: 0
+ m_ProvidesContacts: 0
+ m_Enabled: 1
+ serializedVersion: 3
+ m_Size: {x: 1, y: 1, z: 1}
+ m_Center: {x: 0, y: 0, z: 0}
+--- !u!23 &963088880
+MeshRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 963088878}
+ m_Enabled: 1
+ m_CastShadows: 1
+ m_ReceiveShadows: 1
+ m_DynamicOccludee: 1
+ m_StaticShadowCaster: 0
+ m_MotionVectors: 1
+ m_LightProbeUsage: 1
+ m_ReflectionProbeUsage: 1
+ m_RayTracingMode: 2
+ m_RayTraceProcedural: 0
+ m_RayTracingAccelStructBuildFlagsOverride: 0
+ m_RayTracingAccelStructBuildFlags: 1
+ m_SmallMeshCulling: 1
+ m_ForceMeshLod: -1
+ m_MeshLodSelectionBias: 0
+ m_RenderingLayerMask: 1
+ m_RendererPriority: 0
+ m_Materials:
+ - {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
+ m_StaticBatchInfo:
+ firstSubMesh: 0
+ subMeshCount: 0
+ m_StaticBatchRoot: {fileID: 0}
+ m_ProbeAnchor: {fileID: 0}
+ m_LightProbeVolumeOverride: {fileID: 0}
+ m_ScaleInLightmap: 1
+ m_ReceiveGI: 1
+ m_PreserveUVs: 0
+ m_IgnoreNormalsForChartDetection: 0
+ m_ImportantGI: 0
+ m_StitchLightmapSeams: 1
+ m_SelectedEditorRenderState: 3
+ m_MinimumChartSize: 4
+ m_AutoUVMaxDistance: 0.5
+ m_AutoUVMaxAngle: 89
+ m_LightmapParameters: {fileID: 0}
+ m_GlobalIlluminationMeshLod: 0
+ m_SortingLayerID: 0
+ m_SortingLayer: 0
+ m_SortingOrder: 0
+ m_MaskInteraction: 0
+ m_AdditionalVertexStreams: {fileID: 0}
+--- !u!33 &963088881
+MeshFilter:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 963088878}
+ m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
+--- !u!4 &963088882
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 963088878}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 4, y: 0.5, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1141039638
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1141039639}
+ - component: {fileID: 1141039641}
+ - component: {fileID: 1141039642}
+ m_Layer: 0
+ m_Name: Player
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1141039639
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1141039638}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 1, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children:
+ - {fileID: 1944972203}
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!143 &1141039641
+CharacterController:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1141039638}
+ m_Material: {fileID: 0}
+ m_IncludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_ExcludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_LayerOverridePriority: 0
+ m_ProvidesContacts: 0
+ m_Enabled: 1
+ serializedVersion: 3
+ m_Height: 1.35
+ m_Radius: 0.2
+ m_SlopeLimit: 45
+ m_StepOffset: 0.3
+ m_SkinWidth: 0.02
+ m_MinMoveDistance: 0.001
+ m_Center: {x: 0, y: 0.675, z: 0}
+--- !u!114 &1141039642
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1141039638}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 463b6be548abf464287096e1e92fcd01, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::PlayerCharacterController
+ moveSpeed: 4
+ cameraRelative: 1
+ cameraTransform: {fileID: 0}
+ rotateTowardMovement: 1
+ rotationSpeed: 720
+ jumpHeight: 1.2
+ gravity: -19.62
+ groundedStickVelocity: -2
+ slideDownSteepSlopes: 1
+ slideAcceleration: 20
+ maxSlideSpeed: 8
+ slideControl: 0.3
+--- !u!4 &1944972203 stripped
+Transform:
+ m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: 554c0bdf86263754d862d7effd04045b, type: 3}
+ m_PrefabInstance: {fileID: 821516643}
+ m_PrefabAsset: {fileID: 0}
--- !u!1 &2009475132
GameObject:
m_ObjectHideFlags: 0
@@ -552,9 +977,6 @@ MonoBehaviour:
targetFps: 60
disableVSyncWhenLimiting: 1
vSyncCountWhenUnlimited: 0
- toggleLimitKey: 94
- cyclePresetKey: 95
- presets: 1e0000003c0000007800000090000000
--- !u!4 &2009475134
Transform:
m_ObjectHideFlags: 0
@@ -578,4 +1000,7 @@ SceneRoots:
- {fileID: 330585546}
- {fileID: 410087041}
- {fileID: 832575519}
- - {fileID: 821516643}
+ - {fileID: 1141039639}
+ - {fileID: 721670973}
+ - {fileID: 963088882}
+ - {fileID: 200502122}
diff --git a/Assets/Scripts/PlayerCharacterController.cs b/Assets/Scripts/PlayerCharacterController.cs
new file mode 100644
index 0000000..7e1c07b
--- /dev/null
+++ b/Assets/Scripts/PlayerCharacterController.cs
@@ -0,0 +1,212 @@
+using UnityEngine;
+#if ENABLE_INPUT_SYSTEM
+using UnityEngine.InputSystem;
+#endif
+
+///
+/// Simple first/third-person style character controller driven by a
+/// : WASD for horizontal movement and Space to jump.
+///
+/// Movement can be camera-relative (W moves away from the camera) so it feels
+/// natural with the fixed scene camera. Gravity and jumping are integrated
+/// manually because CharacterController does not apply physics on its own.
+///
+/// The script reads input through the new Input System (Keyboard.current) when it
+/// is enabled, and falls back to the legacy Input Manager otherwise. The project
+/// currently runs the new Input System only.
+///
+/// Attach this to the Player GameObject (the one that owns the CharacterController).
+///
+/// Note: the class is intentionally named PlayerCharacterController to avoid a
+/// name clash with Unity.VisualScripting's example "PlayerController" type.
+///
+[RequireComponent(typeof(CharacterController))]
+[DisallowMultipleComponent]
+public class PlayerCharacterController : MonoBehaviour
+{
+ [Header("Movement")]
+ [Tooltip("Horizontal movement speed in units per second.")]
+ public float moveSpeed = 4f;
+
+ [Tooltip("If ON, WASD is interpreted relative to the camera (W = away from " +
+ "the camera). If OFF, WASD moves along the world X/Z axes.")]
+ public bool cameraRelative = true;
+
+ [Tooltip("Camera used for camera-relative movement. Empty = Camera.main.")]
+ public Transform cameraTransform;
+
+ [Header("Rotation")]
+ [Tooltip("If ON, the character smoothly turns to face its movement direction.")]
+ public bool rotateTowardMovement = true;
+
+ [Tooltip("Turn speed in degrees per second (used when 'Rotate Toward Movement' is on).")]
+ public float rotationSpeed = 720f;
+
+ [Header("Jump & Gravity")]
+ [Tooltip("Peak jump height in units.")]
+ public float jumpHeight = 1.2f;
+
+ [Tooltip("Gravity in units per second squared (negative = downward).")]
+ public float gravity = -19.62f;
+
+ [Tooltip("Small downward velocity kept while grounded so the controller " +
+ "stays firmly on the ground (and isGrounded stays reliable).")]
+ public float groundedStickVelocity = -2f;
+
+ [Header("Slope Sliding")]
+ [Tooltip("If ON, the character slides down surfaces steeper than the " +
+ "CharacterController's Slope Limit (and off sharp edges/corners) " +
+ "instead of balancing on them.")]
+ public bool slideDownSteepSlopes = true;
+
+ [Tooltip("How quickly the slide accelerates while on a too-steep surface " +
+ "(units per second squared).")]
+ public float slideAcceleration = 20f;
+
+ [Tooltip("Maximum downhill slide speed (units per second).")]
+ public float maxSlideSpeed = 8f;
+
+ [Tooltip("How much steering control the player keeps while sliding " +
+ "(0 = none, 1 = full normal control).")]
+ [Range(0f, 1f)]
+ public float slideControl = 0.3f;
+
+ private CharacterController _controller;
+ private float _verticalVelocity;
+
+ // Ground contact normal collected from the latest Move (used for slope sliding).
+ private Vector3 _groundNormal = Vector3.up;
+ private bool _groundNormalUpdated;
+ private float _slideSpeed;
+
+ void Awake()
+ {
+ _controller = GetComponent();
+ if (cameraTransform == null && Camera.main != null)
+ cameraTransform = Camera.main.transform;
+ }
+
+ void Update()
+ {
+ // --- Read input -----------------------------------------------------
+ float h = 0f, v = 0f;
+ bool jumpPressed = false;
+
+#if ENABLE_INPUT_SYSTEM
+ var keyboard = Keyboard.current;
+ if (keyboard != null)
+ {
+ if (keyboard.aKey.isPressed) h -= 1f;
+ if (keyboard.dKey.isPressed) h += 1f;
+ if (keyboard.sKey.isPressed) v -= 1f;
+ if (keyboard.wKey.isPressed) v += 1f;
+ jumpPressed = keyboard.spaceKey.wasPressedThisFrame;
+ }
+#else
+ h = Input.GetAxisRaw("Horizontal");
+ v = Input.GetAxisRaw("Vertical");
+ jumpPressed = Input.GetButtonDown("Jump");
+#endif
+
+ // --- Build the horizontal move direction ----------------------------
+ Vector3 inputDir = new Vector3(h, 0f, v);
+ if (inputDir.sqrMagnitude > 1f)
+ inputDir.Normalize();
+
+ Vector3 moveDir;
+ if (cameraRelative && cameraTransform != null)
+ {
+ // Project the camera axes onto the ground plane so movement stays horizontal.
+ Vector3 forward = cameraTransform.forward;
+ forward.y = 0f;
+ forward.Normalize();
+ Vector3 right = cameraTransform.right;
+ right.y = 0f;
+ right.Normalize();
+ moveDir = forward * inputDir.z + right * inputDir.x;
+ }
+ else
+ {
+ moveDir = inputDir;
+ }
+
+ // --- Evaluate the ground slope (for sliding) ------------------------
+ // The ground normal is gathered in OnControllerColliderHit during the
+ // previous Move. If it is steeper than the controller's Slope Limit, the
+ // surface is too steep to stand on (this also covers balancing on a sharp
+ // box edge/corner, whose contact normal is steep).
+ float slopeAngle = Vector3.Angle(_groundNormal, Vector3.up);
+ bool onSteepSlope = slideDownSteepSlopes
+ && _controller.isGrounded
+ && slopeAngle > _controller.slopeLimit + 0.01f;
+
+ // --- Gravity & jumping ---------------------------------------------
+ if (_controller.isGrounded)
+ {
+ // Reset accumulated fall speed and keep a small downward bias on the ground.
+ if (_verticalVelocity < 0f)
+ _verticalVelocity = groundedStickVelocity;
+
+ // No jumping off a slope that is too steep to stand on.
+ if (jumpPressed && !onSteepSlope)
+ _verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
+ }
+
+ _verticalVelocity += gravity * Time.deltaTime;
+
+ // --- Slope sliding --------------------------------------------------
+ Vector3 slideVelocity = Vector3.zero;
+ if (onSteepSlope)
+ {
+ // Steepest-descent direction along the slope surface.
+ Vector3 slideDir = Vector3.ProjectOnPlane(Vector3.down, _groundNormal).normalized;
+ // Accelerate the slide up to the maximum slide speed.
+ _slideSpeed = Mathf.Min(_slideSpeed + slideAcceleration * Time.deltaTime, maxSlideSpeed);
+ slideVelocity = slideDir * _slideSpeed;
+ // While sliding the player keeps only reduced steering control.
+ moveDir *= slideControl;
+ }
+ else
+ {
+ _slideSpeed = 0f;
+ }
+
+ // --- Apply movement -------------------------------------------------
+ Vector3 velocity = moveDir * moveSpeed + slideVelocity + Vector3.up * _verticalVelocity;
+
+ // Reset the per-frame ground-normal collection, then Move (which fires
+ // OnControllerColliderHit), then fall back to "up" if nothing was hit.
+ _groundNormalUpdated = false;
+ _controller.Move(velocity * Time.deltaTime);
+ if (!_groundNormalUpdated)
+ _groundNormal = Vector3.up;
+
+ // --- Rotate toward the movement direction ---------------------------
+ if (rotateTowardMovement && moveDir.sqrMagnitude > 0.0001f)
+ {
+ Quaternion target = Quaternion.LookRotation(moveDir, Vector3.up);
+ transform.rotation = Quaternion.RotateTowards(
+ transform.rotation, target, rotationSpeed * Time.deltaTime);
+ }
+ }
+
+ ///
+ /// Collects the ground contact normal during .
+ /// Walls and ceilings (normal pointing sideways or down) are ignored; among the
+ /// upward-facing contacts the flattest one is kept, which represents the surface
+ /// the character is actually standing on.
+ ///
+ private void OnControllerColliderHit(ControllerColliderHit hit)
+ {
+ // Ignore near-vertical walls and ceilings.
+ if (hit.normal.y <= 0.05f)
+ return;
+
+ // Keep the flattest (most supportive) contact seen this frame.
+ if (!_groundNormalUpdated || hit.normal.y > _groundNormal.y)
+ {
+ _groundNormal = hit.normal;
+ _groundNormalUpdated = true;
+ }
+ }
+}
diff --git a/Assets/Scripts/PlayerCharacterController.cs.meta b/Assets/Scripts/PlayerCharacterController.cs.meta
new file mode 100644
index 0000000..1cd8e4f
--- /dev/null
+++ b/Assets/Scripts/PlayerCharacterController.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 463b6be548abf464287096e1e92fcd01
\ No newline at end of file