From 8acbd5c9e51c0433e12a2dba8a0f19d874067408 Mon Sep 17 00:00:00 2001 From: Banane_Rotative Date: Thu, 6 Mar 2025 15:52:22 +0100 Subject: [PATCH] Slope handling Player movement is now smooth on slopes. Clamped debug camera vertical rotation. --- Assets/Scenes/SampleScene.unity | 112 ++++++++++++++++----- Assets/Scripts/Control/PlayerController.cs | 46 ++++++++- 2 files changed, 128 insertions(+), 30 deletions(-) diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index 3e2b713..afe74de 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -119,6 +119,39 @@ NavMeshSettings: debug: m_Flags: 0 m_NavMeshData: {fileID: 0} +--- !u!1 &90518571 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 90518572} + m_Layer: 0 + m_Name: Terrain + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &90518572 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 90518571} + 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: + - {fileID: 272243396} + - {fileID: 1192843592} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &99782791 GameObject: m_ObjectHideFlags: 0 @@ -229,9 +262,7 @@ Transform: m_LocalScale: {x: 1, y: 0.9, z: 1} m_ConstrainProportionsScale: 0 m_Children: - - {fileID: 1561010223} - {fileID: 493760350} - - {fileID: 330585546} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &99782796 @@ -250,6 +281,7 @@ MonoBehaviour: speed: 15 sensitivity: 70 maxSpeed: 10 + maxSlopeAngle: 30 --- !u!54 &99782797 Rigidbody: m_ObjectHideFlags: 0 @@ -379,12 +411,12 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 272243392} serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 5, y: 5, z: 5} m_ConstrainProportionsScale: 0 m_Children: [] - m_Father: {fileID: 0} + m_Father: {fileID: 90518572} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &330585543 GameObject: @@ -463,13 +495,13 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 330585543} serializedVersion: 2 - m_LocalRotation: {x: 0.08715578, y: 0, z: 0, w: 0.9961947} - m_LocalPosition: {x: 0, y: 2, z: -3.31} - m_LocalScale: {x: 1, y: 1.1111112, z: 1} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1.1111114, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] - m_Father: {fileID: 99782795} - m_LocalEulerAnglesHint: {x: 10, y: 0, z: 0} + m_Father: {fileID: 493760350} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &330585547 MonoBehaviour: m_ObjectHideFlags: 0 @@ -667,6 +699,7 @@ Transform: m_ConstrainProportionsScale: 0 m_Children: - {fileID: 990919147} + - {fileID: 330585546} m_Father: {fileID: 99782795} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!81 &493760351 @@ -689,6 +722,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 5a895e8a798ee0b4a9a5ea33d6b07f4b, type: 3} m_Name: m_EditorClassIdentifier: + minRotation: -90 + maxRotation: 90 --- !u!1 &990919146 GameObject: m_ObjectHideFlags: 0 @@ -720,7 +755,7 @@ Transform: m_Children: [] m_Father: {fileID: 493760350} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &1561010222 +--- !u!1 &1192843591 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -728,38 +763,61 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 1561010223} - - component: {fileID: 1561010225} - - component: {fileID: 1561010224} + - component: {fileID: 1192843592} + - component: {fileID: 1192843595} + - component: {fileID: 1192843594} + - component: {fileID: 1192843593} m_Layer: 0 - m_Name: Noze + m_Name: Slope m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!4 &1561010223 +--- !u!4 &1192843592 Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1561010222} + m_GameObject: {fileID: 1192843591} serializedVersion: 2 - m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 0, y: 0.2, z: 0.5} - m_LocalScale: {x: 0.3, y: 0.3, z: 0.3} + m_LocalRotation: {x: 0, y: 0, z: -0.24784453, w: 0.9687998} + m_LocalPosition: {x: -5.5, y: 1.13, z: -6.62} + m_LocalScale: {x: 0.5, y: 1, z: 0.5} m_ConstrainProportionsScale: 0 m_Children: [] - m_Father: {fileID: 99782795} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!23 &1561010224 + m_Father: {fileID: 90518572} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: -28.7} +--- !u!64 &1192843593 +MeshCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1192843591} + 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 &1192843594 MeshRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1561010222} + m_GameObject: {fileID: 1192843591} m_Enabled: 1 m_CastShadows: 1 m_ReceiveShadows: 1 @@ -798,14 +856,14 @@ MeshRenderer: m_SortingLayer: 0 m_SortingOrder: 0 m_AdditionalVertexStreams: {fileID: 0} ---- !u!33 &1561010225 +--- !u!33 &1192843595 MeshFilter: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1561010222} - m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} + m_GameObject: {fileID: 1192843591} + m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0} --- !u!1 &1697224667 GameObject: m_ObjectHideFlags: 0 @@ -1126,6 +1184,6 @@ SceneRoots: m_ObjectHideFlags: 0 m_Roots: - {fileID: 410087041} - - {fileID: 272243396} - {fileID: 99782795} - {fileID: 1697224671} + - {fileID: 90518572} diff --git a/Assets/Scripts/Control/PlayerController.cs b/Assets/Scripts/Control/PlayerController.cs index 8325d0c..b87b3d3 100644 --- a/Assets/Scripts/Control/PlayerController.cs +++ b/Assets/Scripts/Control/PlayerController.cs @@ -1,22 +1,35 @@ using UnityEngine; +using UnityEngine.EventSystems; // Handles everything related to player movement public class PlayerController : MonoBehaviour { [SerializeField] private EarsController ears = null; + [Header("Vertical rotation")] + [SerializeField] float minRotation = -90f; + [SerializeField] float maxRotation = 90f; + [Header("Player speed")] [SerializeField] private float speed = 15; [SerializeField] private float sensitivity = 50; [SerializeField] private float maxSpeed = 10; + [Header("Slope handling")] + [SerializeField] private float maxSlopeAngle = 20f; + private RaycastHit slopeHit; + + private float playerHeight = 2f; + private PlayerControlsMap playerControls; private Rigidbody rb; private Vector3 currentRotation = Vector3.zero; + // Awake is always called before Start. Often used to initialize variables void Awake() { + playerHeight = 2f * transform.localScale.y; playerControls = new PlayerControlsMap(); rb = GetComponent(); @@ -50,23 +63,27 @@ public class PlayerController : MonoBehaviour } + // Rotate player according to input void RotatePlayer() { // Update player direction. Very basic // First, rotate player on y axis Vector2 lookInput = playerControls.Player.Look.ReadValue(); lookInput *= sensitivity * Time.deltaTime; - currentRotation += new Vector3(Mathf.Clamp(-lookInput.y, -90, 90), lookInput.x, 0); + currentRotation += new Vector3(-lookInput.y, lookInput.x, 0); + currentRotation.x = Mathf.Clamp(currentRotation.x, minRotation, maxRotation); + transform.localEulerAngles = new Vector3(0, currentRotation.y, 0); // Then, rotate player's ears on both x and z axes (only available with certain controllers such as AR/VR) ears.RotateHead(new Vector3(currentRotation.x, 0, currentRotation.z)); } + // Move player according to input void MovePlayer() { // Update player acceleration, using a force Vector2 mvtInput = playerControls.Player.Move.ReadValue(); - mvtInput *= speed * Time.deltaTime; - rb.AddForce(transform.rotation * new Vector3(mvtInput.x, 0, mvtInput.y), ForceMode.VelocityChange); + Vector3 moveDirection = GetMoveDirection(transform.rotation*new Vector3(mvtInput.x, 0, mvtInput.y)); + rb.AddForce(moveDirection * speed * Time.deltaTime, ForceMode.VelocityChange); // Limit max speed. // maxSpeed affects only max speed, while rigidbody's linear damping affects both max speed and deceleration @@ -74,4 +91,27 @@ public class PlayerController : MonoBehaviour rb.linearVelocity = rb.linearVelocity.normalized * maxSpeed; } } + + // Check if player is on a slope + bool OnSlope() { + if (Physics.Raycast(transform.position, Vector3.down, out slopeHit, playerHeight * 0.5f + 0.2f)) { + float angle = Vector3.Angle(Vector3.up, slopeHit.normal); + return angle < maxSlopeAngle && angle != 0; + } + return false; + } + + // Get move direction, whether there is a slope or not + private Vector3 GetMoveDirection(Vector3 mvtInput) { + if (OnSlope()) { + Vector3 mvtDir = Vector3.ProjectOnPlane(mvtInput, slopeHit.normal); + + // Limit norm of direction to 1, while allowing lower + if (mvtDir.magnitude > 1) { + return mvtDir.normalized; + } + return mvtDir; + } + return mvtInput; + } }