├── .gitignore ├── LICENSE.meta ├── README.md.meta ├── Input.meta ├── Scripts.meta ├── GameObjects.meta ├── GameObjects ├── FPController-URP.prefab.meta ├── FPController-BuiltIn.prefab.meta ├── FPController-URP.prefab └── FPController-BuiltIn.prefab ├── Scripts ├── FPController.meta └── FPController │ ├── FPJumping.cs.meta │ ├── CameraShake.cs.meta │ ├── FPController.cs.meta │ ├── FPFootsteps.cs.meta │ ├── FPHeadBobbing.cs.meta │ ├── FPInputManager.cs.meta │ ├── FPSprinting.cs.meta │ ├── FPSprinting.cs │ ├── FPFootsteps.cs │ ├── FPHeadBobbing.cs │ ├── CameraShake.cs │ ├── FPInputManager.cs │ ├── FPJumping.cs │ └── FPController.cs ├── Input ├── PlayerInputControls.cs.meta ├── PlayerInputControls.inputactions.meta ├── PlayerInputControls.inputactions └── PlayerInputControls.cs ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 794d5fb93f098a34e979a25ce401ec6a 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f2c6c58d7e094134c8abfbb18e13eecf 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Input.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 748743231825070438910f55359b17a8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7ae61b1de7459f546aac74e2052b6cab 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /GameObjects.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0d34b7065f4633f47a58b1acb7e9591d 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /GameObjects/FPController-URP.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9ac2257120c593c4d9da39fcff24f645 3 | PrefabImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /GameObjects/FPController-BuiltIn.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 42d7d8fc792d28e40a3371b5b1913a30 3 | PrefabImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Scripts/FPController.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e469bec7226014d4c9d69055f3c73da5 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Input/PlayerInputControls.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 058804a830ba6c744866cc5212d5399d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/FPController/FPJumping.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 37614c034b070c0428fbda7407508d46 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/FPController/CameraShake.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3815b447c65dcca4f910bf16823b15cb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/FPController/FPController.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 116d5b2b2ac5d3f48a8db4260dbab72f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/FPController/FPFootsteps.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6e173bde8b130a142b6bc377c3018d9b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/FPController/FPHeadBobbing.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b218d6d4137ecfb47b14cc38207afff5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/FPController/FPInputManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dcd80274256d993408e1364716168d0d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/FPController/FPSprinting.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f34a6134550dca84191873450eeb1eb3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Input/PlayerInputControls.inputactions.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 20561030496d44f4baf25d6a88a588ac 3 | ScriptedImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 2 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3} 11 | generateWrapperCode: 0 12 | wrapperCodePath: 13 | wrapperClassName: 14 | wrapperCodeNamespace: 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 GTroubley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Scripts/FPController/FPSprinting.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace FPController 4 | { 5 | [RequireComponent(typeof(FPController))] 6 | public class FPSprinting : MonoBehaviour 7 | { 8 | [Header("Sprinting")] 9 | [SerializeField] private float _speedMultiplier = 1.5f; 10 | 11 | FPController _player; 12 | FPInputManager _input; 13 | 14 | private void Awake() 15 | { 16 | _player = GetComponent(); 17 | _input = GetComponent(); 18 | } 19 | 20 | private void OnEnable() => _player.OnBeforeMove += PrepareForSprint; 21 | 22 | private void OnDisable() => _player.OnBeforeMove -= PrepareForSprint; 23 | 24 | /// 25 | /// Responsible for checking if player is moving forward, if so then will apply the sprinting multiplier. 26 | /// Sprinting does not work when moving backwards! 27 | /// This method is invoked at OnBeforeMove in UpdateMovement() of the FPController.cs 28 | /// 29 | private void PrepareForSprint() 30 | { 31 | if (!_input.SprintInput || _player.IsJumping) return; 32 | // Compute how much the player is moving in the forward direction (1 = forward, 0 = sideways, -1 = backward) 33 | float forwardMovementFactor = Mathf.Clamp01(Vector3.Dot(_player.transform.forward, _player.Velocity.normalized)); 34 | float multiplier = Mathf.Lerp(1f, _speedMultiplier, forwardMovementFactor); 35 | _player.MovementSpeedMultiplier *= multiplier; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Scripts/FPController/FPFootsteps.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace FPController 4 | { 5 | [RequireComponent(typeof(FPController))] 6 | public class FPFootsteps : MonoBehaviour 7 | { 8 | [SerializeField] private AudioSource _audioSource; 9 | [SerializeField] private bool _playFootsteps = true; 10 | [SerializeField] private float _minRequiredSpeed = 3f; 11 | [SerializeField] private float _walkStepInterval = 0.8f; 12 | [SerializeField] private float _sprintStepInterval = 0.5f; 13 | [SerializeField] private AudioClip[] _sounds; 14 | 15 | private FPController _player; 16 | private float _lastStepTimer; 17 | 18 | private void Awake() => _player = GetComponent(); 19 | 20 | private void Update() 21 | { 22 | // Play footsteps only if _playFootsteps is enabled and has an AudioSource 23 | if (!_playFootsteps || _audioSource == null || _player == null) return; 24 | // If audioSource is not already playing a sound and player is grounded and moves using input with a speed greater than the _minRequiredSpeed 25 | if (!_audioSource.isPlaying && _player.IsGrounded && _player.IsInputMoving && _player.Velocity.magnitude > _minRequiredSpeed) 26 | { 27 | float interval = _player.IsSprinting ? _sprintStepInterval : _walkStepInterval; 28 | if (IsTimeToSprint(interval) && _sounds.Length > 0) 29 | { 30 | _audioSource.PlayOneShot(_sounds[Random.Range(0, _sounds.Length)]); 31 | _lastStepTimer = Time.time; 32 | } 33 | } 34 | } 35 | 36 | private bool IsTimeToSprint(float interval) => Time.time - _lastStepTimer > interval; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Scripts/FPController/FPHeadBobbing.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace FPController 4 | { 5 | public class FPHeadBobbing : MonoBehaviour 6 | { 7 | [SerializeField, Range(0, 20f)] float _frequency = 15f; 8 | [SerializeField, Range(0, 0.1f)] float _amplitude = 0.04f; 9 | [SerializeField, Range(1, 2f)] float _sprintMultiplier = 1.3f; 10 | 11 | private FPController _player; 12 | private Transform _cameraHolderTransform; 13 | private float _defaultPosY = 0; 14 | private float _timer = 0; 15 | 16 | private void Awake() 17 | { 18 | _player = GetComponent(); 19 | _cameraHolderTransform = GetComponentInChildren().transform.parent; 20 | } 21 | 22 | // On Start caches the camera position on the Y-Axis 23 | private void Start() => _defaultPosY = _cameraHolderTransform.localPosition.y; 24 | 25 | private void Update() 26 | { 27 | // Depending wether player is sprinting or not to determine the frequency and amplitude of the bob 28 | float frequency = _player.IsSprinting ? _frequency * _sprintMultiplier : _frequency; 29 | float amplitude = _player.IsSprinting ? _amplitude * _sprintMultiplier : _amplitude; 30 | 31 | // If player is moving 32 | if (IsPlayerMoving()) 33 | { 34 | // Start the timer and move the camera on the Y-Axis for the bob 35 | _timer += Time.deltaTime * frequency; 36 | _cameraHolderTransform.localPosition = new Vector3(_cameraHolderTransform.localPosition.x, _defaultPosY + Mathf.Sin(_timer) * amplitude, _cameraHolderTransform.localPosition.z); 37 | } 38 | else 39 | { 40 | // Resets the timer and gradually reset the camera position on the Y-Axis 41 | _timer = 0; 42 | _cameraHolderTransform.localPosition = new Vector3(_cameraHolderTransform.localPosition.x, Mathf.Lerp(_cameraHolderTransform.localPosition.y, _defaultPosY, Time.deltaTime * frequency), _cameraHolderTransform.localPosition.z); 43 | } 44 | } 45 | 46 | private bool IsPlayerMoving() 47 | { 48 | return _player.IsGrounded && (Mathf.Abs(_player.Velocity.x) > 0.1f || Mathf.Abs(_player.Velocity.z) > 0.1f); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Scripts/FPController/CameraShake.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | 4 | public class CameraShake : MonoBehaviour 5 | { 6 | public static CameraShake Instance; 7 | 8 | [SerializeField, Range(0, 10)] private float _duration = 1f; 9 | [SerializeField, Range(0, 2)] private float _strengthMultiplier = 0.2f; 10 | [SerializeField] private AnimationCurve _curve; 11 | 12 | public bool IsShaking; 13 | 14 | // Setup the curve, remove to change to another curve 15 | private void OnValidate() => CurveSetup(); 16 | 17 | void Awake() 18 | { 19 | if (Instance == null) 20 | Instance = this; 21 | else 22 | Destroy(gameObject); 23 | } 24 | 25 | public static void Shake(float duration, float strengthMultiplier) => Instance.StartCoroutine(Instance.ShakeCoroutine(duration, strengthMultiplier)); 26 | 27 | private IEnumerator ShakeCoroutine(float duration, float strengthMultiplier) 28 | { 29 | // Store the original local position 30 | Vector3 originalPos = transform.localPosition; 31 | float elapsed = 0f; 32 | IsShaking = true; 33 | 34 | // Loop until the total shake duration has passed 35 | while (elapsed < duration) 36 | { 37 | // Calculate shake offset based on curve intensity over time and apply it to the position and then update elapsed time 38 | float strengthOverTime = _curve.Evaluate(elapsed / duration); 39 | transform.localPosition = originalPos + Random.insideUnitSphere * strengthOverTime * strengthMultiplier; 40 | elapsed += Time.deltaTime; 41 | yield return null; 42 | } 43 | 44 | transform.localPosition = originalPos; 45 | IsShaking = false; 46 | } 47 | 48 | [ContextMenu("Curve Setup")] 49 | private void CurveSetup() 50 | { 51 | Keyframe[] keyframes = new Keyframe[3]; 52 | // First keyframe, values 0,0 53 | keyframes[0].time = 0f; 54 | keyframes[0].value = 0f; 55 | // Second keyframe, values 0.2,0.25 56 | keyframes[1].time = 0.2f; 57 | keyframes[1].value = 0.25f; 58 | // Third keyframe, values 1,0 59 | keyframes[2].time = 1f; 60 | keyframes[2].value = 0f; 61 | // Adjusting the of the curve for the second keyframe 62 | keyframes[1].inTangent = -0.01f; 63 | keyframes[1].inWeight = -0.3f; 64 | _curve.keys = keyframes; 65 | } 66 | 67 | #if UNITY_EDITOR 68 | [ContextMenu("Shake")] 69 | public void TestShake() => Shake(_duration, _strengthMultiplier); 70 | #endif 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🎮 Simple First Person Controller for Unity - **FPController** 2 | 3 | A lightweight, modular first-person controller built using Unity's **New Input System** and the built-in `CharacterController` component. Supports sprinting, jumping, slope and stair navigation, full air control, head bobbing, and dynamic footstep audio. 4 | 5 | --- 6 | 7 | ## 🧰 Features 8 | 9 | - ✅ Walk, sprint, jump 10 | - ✅ Slope and stair navigation 11 | - ✅ Full air control 12 | - ✅ Camera headbobbing 13 | - ✅ Footstep sounds 14 | - ✅ Camera shake support 15 | - ✅ Modular architecture (plug & play) 16 | 17 | --- 18 | 19 | ## 📦 Requirements 20 | 21 | - **Unity Version:** works up to Unity 6.1 22 | - **Packages:** 23 | - Input System (`com.unity.inputsystem`) 24 | - SRP & URP support 25 | 26 | --- 27 | 28 | ## 🚀 Getting Started 29 | 30 | ### 1. Clone or Download 31 | 32 | Download or clone this repository into your Unity project: 33 | 34 | ```bash 35 | https://github.com/GTroubley/Simple-First-Person-Controller.git 36 | ``` 37 | 38 | ### 2. Create Required Layer‼️ 39 | 40 | Create a layer named **Player** and assign it to the controller prefab and all its child objects. 41 | 42 | ### 3. Add Prefab to Scene 43 | 44 | Use one of the prefabs from the GameObjects folder based on your render pipeline: 45 | 46 | ``` 47 | GameObjects/ 48 | ``` 49 | 50 | - `FPController-Builtin` 51 | - `FPController-URP` 52 | 53 | ### 4. Assign Input Actions 54 | 55 | Input actions can be found at: 56 | 57 | ``` 58 | Input/PlayerInputControls 59 | ``` 60 | 61 | --- 62 | 63 | ## 🧩 Script Components 64 | 65 | | Script | Description | 66 | |-------------------|-----------------------------------------------------------------------------| 67 | | `FPInputManager` | Handles all player input using Unity's New Input System. | 68 | | `FPController` | Core movement logic: walking, gravity, slopes, stairs, and camera rotation. | 69 | | `FPJumping` | Enables jumping behavior. | 70 | | `FPSprint` | Enables sprinting functionality. | 71 | | `FPHeadbobbing` | Adds vertical camera motion while walking/running. | 72 | | `FPFootsteps` | Plays footstep audio based on movement. | 73 | | `CameraShake` | Triggers screen shake on the camera object. | 74 | 75 | --- 76 | 77 | ## 💡 Some Examples 78 | 79 | Shake the camera using: 80 | 81 | ```csharp 82 | CameraShake.Shake(0.5f, 0.15f); 83 | ``` 84 | 85 | --- 86 | 87 | ## 🙏 Special Thanks 88 | 89 | This controller was inspired by [passivestar](https://www.youtube.com/@passivestar). 90 | 91 | --- 92 | 93 | ## 🚧 Development Notice 94 | 95 | The controller is still under active development, alongside my next game project. 96 | If you use this controller in your own game, please credit me. 😊 97 | -------------------------------------------------------------------------------- /Scripts/FPController/FPInputManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEngine.InputSystem; 4 | 5 | namespace FPController 6 | { 7 | public class FPInputManager : MonoBehaviour 8 | { 9 | public Vector2 MovementInput { get; private set; } // Movement input 2D Vector (X-axis: AD keys) (Y-axis: WS keys) 10 | public Vector2 LookInput { get; private set; } // Mouse delta input 11 | public bool SprintInput { get; private set; } // Sprint input 12 | public bool JumpInput { get; private set; } // Jump Input 13 | 14 | public event Action OnJumpPressed; 15 | private PlayerInputControls _input; 16 | 17 | private void Awake() => _input = new PlayerInputControls(); 18 | 19 | // Registering events 20 | private void OnEnable() 21 | { 22 | _input.PlayerControls.Movement.performed += OnMovement; 23 | _input.PlayerControls.Movement.canceled += OnMovement; 24 | _input.PlayerControls.Look.performed += OnLook; 25 | _input.PlayerControls.Look.canceled += OnLook; 26 | _input.PlayerControls.Sprint.performed += OnSprint; 27 | _input.PlayerControls.Sprint.canceled += OnSprint; 28 | _input.PlayerControls.Jump.performed += OnJump; 29 | _input.PlayerControls.Jump.canceled += OnJump; 30 | _input.Enable(); 31 | } 32 | 33 | // De-Registering events 34 | private void OnDisable() 35 | { 36 | _input.PlayerControls.Movement.performed -= OnMovement; 37 | _input.PlayerControls.Movement.canceled -= OnMovement; 38 | _input.PlayerControls.Look.performed -= OnLook; 39 | _input.PlayerControls.Look.canceled -= OnLook; 40 | _input.PlayerControls.Sprint.performed -= OnSprint; 41 | _input.PlayerControls.Sprint.canceled -= OnSprint; 42 | _input.PlayerControls.Jump.performed -= OnJump; 43 | _input.PlayerControls.Jump.canceled -= OnJump; 44 | _input.Disable(); 45 | } 46 | 47 | /// 48 | /// Captures and assigns player movement input as a 2D vector (Vector2) (WASD keys) 49 | /// 50 | private void OnMovement(InputAction.CallbackContext obj) => MovementInput = obj.ReadValue(); 51 | 52 | /// 53 | /// Captures and assigns look direction input as a 2D vector (Vector2) (Mouse) 54 | /// 55 | private void OnLook(InputAction.CallbackContext obj) => LookInput = obj.ReadValue(); 56 | 57 | /// 58 | /// Reads and assigns a bool value for the sprint input (Shift key) 59 | /// 60 | private void OnSprint(InputAction.CallbackContext obj) => SprintInput = obj.ReadValueAsButton(); 61 | 62 | /// 63 | /// Reads and assigns a boolean value for the jump input and triggers OnJumpPressed event (Space key) 64 | /// 65 | private void OnJump(InputAction.CallbackContext obj) 66 | { 67 | JumpInput = obj.ReadValueAsButton(); 68 | if (JumpInput) 69 | OnJumpPressed?.Invoke(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Scripts/FPController/FPJumping.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace FPController 4 | { 5 | [RequireComponent(typeof(FPController))] 6 | public class FPJumping : MonoBehaviour 7 | { 8 | [Header("Jumping")] 9 | 10 | [Tooltip("Desired jump height")] 11 | [SerializeField] float _jumpHeight = 1f; 12 | 13 | [Tooltip("Jump buffer time to forgive bad timing jump attempts")] 14 | [SerializeField][Range(0, 0.2f)] float _jumpBufferTime = 0.05f; 15 | 16 | [Tooltip("Jump grace time to allow jumping even after not being grounded")] 17 | [SerializeField] float _jumpGroundGraceTime = 0.2f; 18 | 19 | private FPController _player; 20 | private FPInputManager _input; 21 | private bool _tryingToJump; 22 | private float _lastJumpPressTime; 23 | private float _lastGroundedTime; 24 | 25 | private void Awake() 26 | { 27 | _player = GetComponent(); 28 | _input = GetComponent(); 29 | } 30 | 31 | private void OnEnable() 32 | { 33 | _player.OnBeforeMove += PrepareForJump; 34 | _input.OnJumpPressed += OnJump; 35 | _player.OnGroundStateChange += OnGroundStateChange; 36 | } 37 | 38 | private void OnDisable() 39 | { 40 | _player.OnBeforeMove -= PrepareForJump; 41 | _input.OnJumpPressed -= OnJump; 42 | _player.OnGroundStateChange -= OnGroundStateChange; 43 | } 44 | 45 | /// 46 | /// Resets _lastGroundedTime 47 | /// This method is invoked at OnGroundStateChange in UpdateGround() of the FPController.cs 48 | /// 49 | /// 50 | private void OnGroundStateChange(bool isGrounded) 51 | { 52 | if (!isGrounded) _lastGroundedTime = Time.time; 53 | } 54 | 55 | /// 56 | /// This method is invoked at FPInputManager in OnJump() of the FPInputManager.cs 57 | /// 58 | private void OnJump() 59 | { 60 | _tryingToJump = true; 61 | _lastJumpPressTime = Time.time; 62 | } 63 | 64 | /// 65 | /// Handles jump logic 66 | /// This method is invoked at OnBeforeMove in UpdateMovement() of the FPController.cs 67 | /// 68 | private void PrepareForJump() 69 | { 70 | // If player is not pressing jump and not jumping already or is sliding 71 | // then don't proceed with jump preparation calculations 72 | if ((!_input.JumpInput && !_player.IsJumping) || _player.IsSliding) return; 73 | 74 | // Resets IsJumping if player is grounded 75 | if (_player.IsGrounded) _player.IsJumping = false; 76 | 77 | // Checks if player was trying to jump during the jump buffer time 78 | bool wasTryingToJump = Time.time - _lastJumpPressTime < _jumpBufferTime; 79 | // Checks if player was grounded the last _jumpGroundGraceTime 80 | // (allows the player to jump even for a short time after not being grounded) 81 | bool wasGrounded = Time.time - _lastGroundedTime < _jumpGroundGraceTime; 82 | 83 | bool isOrWasTryingToJump = _tryingToJump || (wasTryingToJump && _player.IsGrounded); 84 | bool isOrWasGrounded = _player.IsGrounded || wasGrounded; 85 | 86 | // If player isn't on a slope and not already jumping and is or was grounded and trying to jump 87 | // Adds to his vertical velocity to perform the jump 88 | if (!_player.IsOnSlope && !_player.IsJumping && isOrWasTryingToJump && isOrWasGrounded) 89 | { 90 | _player.Velocity.y += Mathf.Sqrt(-2 * Physics.gravity.y * _player.PlayerMass * _player.GravityMultiplier * _jumpHeight); 91 | _player.IsJumping = true; 92 | _player.MovementSpeedMultiplier = 0f; 93 | } 94 | _tryingToJump = false; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Input/PlayerInputControls.inputactions: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PlayerInputControls", 3 | "maps": [ 4 | { 5 | "name": "PlayerControls", 6 | "id": "6a7ca873-1dd7-4ac5-a917-93030f79d012", 7 | "actions": [ 8 | { 9 | "name": "Movement", 10 | "type": "Value", 11 | "id": "6fc64ca0-deaf-4e78-b810-dc0ef478b906", 12 | "expectedControlType": "Vector2", 13 | "processors": "", 14 | "interactions": "", 15 | "initialStateCheck": true 16 | }, 17 | { 18 | "name": "Look", 19 | "type": "Value", 20 | "id": "40a5718f-0d19-404e-a2b7-f0b96708810b", 21 | "expectedControlType": "Vector2", 22 | "processors": "", 23 | "interactions": "", 24 | "initialStateCheck": true 25 | }, 26 | { 27 | "name": "Sprint", 28 | "type": "Button", 29 | "id": "9c54fbb5-a1b5-43a9-8bff-84c13261be8d", 30 | "expectedControlType": "Button", 31 | "processors": "", 32 | "interactions": "", 33 | "initialStateCheck": false 34 | }, 35 | { 36 | "name": "Jump", 37 | "type": "Button", 38 | "id": "bab18d9f-7453-463f-9d55-3e518e835fe1", 39 | "expectedControlType": "Button", 40 | "processors": "", 41 | "interactions": "", 42 | "initialStateCheck": false 43 | } 44 | ], 45 | "bindings": [ 46 | { 47 | "name": "2D Vector", 48 | "id": "1e9e5fa8-7494-4425-95e8-43e22f21b062", 49 | "path": "2DVector", 50 | "interactions": "", 51 | "processors": "", 52 | "groups": "", 53 | "action": "Movement", 54 | "isComposite": true, 55 | "isPartOfComposite": false 56 | }, 57 | { 58 | "name": "up", 59 | "id": "6a8b8ca4-d43b-4ce0-9f57-8fca8fc9e749", 60 | "path": "/w", 61 | "interactions": "", 62 | "processors": "", 63 | "groups": "", 64 | "action": "Movement", 65 | "isComposite": false, 66 | "isPartOfComposite": true 67 | }, 68 | { 69 | "name": "down", 70 | "id": "31a52b3d-83ab-4b56-9dff-1162528fe3c2", 71 | "path": "/s", 72 | "interactions": "", 73 | "processors": "", 74 | "groups": "", 75 | "action": "Movement", 76 | "isComposite": false, 77 | "isPartOfComposite": true 78 | }, 79 | { 80 | "name": "left", 81 | "id": "0918b289-4530-433f-8c15-77da48541378", 82 | "path": "/a", 83 | "interactions": "", 84 | "processors": "", 85 | "groups": "", 86 | "action": "Movement", 87 | "isComposite": false, 88 | "isPartOfComposite": true 89 | }, 90 | { 91 | "name": "right", 92 | "id": "1e05f40c-4385-4de9-95d6-41b8f66273a1", 93 | "path": "/d", 94 | "interactions": "", 95 | "processors": "", 96 | "groups": "", 97 | "action": "Movement", 98 | "isComposite": false, 99 | "isPartOfComposite": true 100 | }, 101 | { 102 | "name": "", 103 | "id": "eaf37269-4f49-4bcf-9c29-6b46a2868dcc", 104 | "path": "/delta", 105 | "interactions": "", 106 | "processors": "", 107 | "groups": "", 108 | "action": "Look", 109 | "isComposite": false, 110 | "isPartOfComposite": false 111 | }, 112 | { 113 | "name": "", 114 | "id": "5156bae7-63be-4ae0-8615-bf617a037b9c", 115 | "path": "/leftShift", 116 | "interactions": "", 117 | "processors": "", 118 | "groups": "", 119 | "action": "Sprint", 120 | "isComposite": false, 121 | "isPartOfComposite": false 122 | }, 123 | { 124 | "name": "", 125 | "id": "99d6f003-0cad-4cfe-b955-60214be1ae83", 126 | "path": "/space", 127 | "interactions": "", 128 | "processors": "", 129 | "groups": "", 130 | "action": "Jump", 131 | "isComposite": false, 132 | "isPartOfComposite": false 133 | } 134 | ] 135 | } 136 | ], 137 | "controlSchemes": [] 138 | } -------------------------------------------------------------------------------- /Input/PlayerInputControls.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was auto-generated by com.unity.inputsystem:InputActionCodeGenerator 4 | // version 1.4.2 5 | // from Assets/Scripts/FPController/Input/PlayerInputControls.inputactions 6 | // 7 | // Changes to this file may cause incorrect behavior and will be lost if 8 | // the code is regenerated. 9 | // 10 | //------------------------------------------------------------------------------ 11 | 12 | using System; 13 | using System.Collections; 14 | using System.Collections.Generic; 15 | using UnityEngine.InputSystem; 16 | using UnityEngine.InputSystem.Utilities; 17 | 18 | public partial class @PlayerInputControls : IInputActionCollection2, IDisposable 19 | { 20 | public InputActionAsset asset { get; } 21 | public @PlayerInputControls() 22 | { 23 | asset = InputActionAsset.FromJson(@"{ 24 | ""name"": ""PlayerInputControls"", 25 | ""maps"": [ 26 | { 27 | ""name"": ""PlayerControls"", 28 | ""id"": ""6a7ca873-1dd7-4ac5-a917-93030f79d012"", 29 | ""actions"": [ 30 | { 31 | ""name"": ""Movement"", 32 | ""type"": ""Value"", 33 | ""id"": ""6fc64ca0-deaf-4e78-b810-dc0ef478b906"", 34 | ""expectedControlType"": ""Vector2"", 35 | ""processors"": """", 36 | ""interactions"": """", 37 | ""initialStateCheck"": true 38 | }, 39 | { 40 | ""name"": ""Look"", 41 | ""type"": ""Value"", 42 | ""id"": ""40a5718f-0d19-404e-a2b7-f0b96708810b"", 43 | ""expectedControlType"": ""Vector2"", 44 | ""processors"": """", 45 | ""interactions"": """", 46 | ""initialStateCheck"": true 47 | }, 48 | { 49 | ""name"": ""Sprint"", 50 | ""type"": ""Button"", 51 | ""id"": ""9c54fbb5-a1b5-43a9-8bff-84c13261be8d"", 52 | ""expectedControlType"": ""Button"", 53 | ""processors"": """", 54 | ""interactions"": """", 55 | ""initialStateCheck"": false 56 | }, 57 | { 58 | ""name"": ""Jump"", 59 | ""type"": ""Button"", 60 | ""id"": ""bab18d9f-7453-463f-9d55-3e518e835fe1"", 61 | ""expectedControlType"": ""Button"", 62 | ""processors"": """", 63 | ""interactions"": """", 64 | ""initialStateCheck"": false 65 | } 66 | ], 67 | ""bindings"": [ 68 | { 69 | ""name"": ""2D Vector"", 70 | ""id"": ""1e9e5fa8-7494-4425-95e8-43e22f21b062"", 71 | ""path"": ""2DVector"", 72 | ""interactions"": """", 73 | ""processors"": """", 74 | ""groups"": """", 75 | ""action"": ""Movement"", 76 | ""isComposite"": true, 77 | ""isPartOfComposite"": false 78 | }, 79 | { 80 | ""name"": ""up"", 81 | ""id"": ""6a8b8ca4-d43b-4ce0-9f57-8fca8fc9e749"", 82 | ""path"": ""/w"", 83 | ""interactions"": """", 84 | ""processors"": """", 85 | ""groups"": """", 86 | ""action"": ""Movement"", 87 | ""isComposite"": false, 88 | ""isPartOfComposite"": true 89 | }, 90 | { 91 | ""name"": ""down"", 92 | ""id"": ""31a52b3d-83ab-4b56-9dff-1162528fe3c2"", 93 | ""path"": ""/s"", 94 | ""interactions"": """", 95 | ""processors"": """", 96 | ""groups"": """", 97 | ""action"": ""Movement"", 98 | ""isComposite"": false, 99 | ""isPartOfComposite"": true 100 | }, 101 | { 102 | ""name"": ""left"", 103 | ""id"": ""0918b289-4530-433f-8c15-77da48541378"", 104 | ""path"": ""/a"", 105 | ""interactions"": """", 106 | ""processors"": """", 107 | ""groups"": """", 108 | ""action"": ""Movement"", 109 | ""isComposite"": false, 110 | ""isPartOfComposite"": true 111 | }, 112 | { 113 | ""name"": ""right"", 114 | ""id"": ""1e05f40c-4385-4de9-95d6-41b8f66273a1"", 115 | ""path"": ""/d"", 116 | ""interactions"": """", 117 | ""processors"": """", 118 | ""groups"": """", 119 | ""action"": ""Movement"", 120 | ""isComposite"": false, 121 | ""isPartOfComposite"": true 122 | }, 123 | { 124 | ""name"": """", 125 | ""id"": ""eaf37269-4f49-4bcf-9c29-6b46a2868dcc"", 126 | ""path"": ""/delta"", 127 | ""interactions"": """", 128 | ""processors"": """", 129 | ""groups"": """", 130 | ""action"": ""Look"", 131 | ""isComposite"": false, 132 | ""isPartOfComposite"": false 133 | }, 134 | { 135 | ""name"": """", 136 | ""id"": ""5156bae7-63be-4ae0-8615-bf617a037b9c"", 137 | ""path"": ""/leftShift"", 138 | ""interactions"": """", 139 | ""processors"": """", 140 | ""groups"": """", 141 | ""action"": ""Sprint"", 142 | ""isComposite"": false, 143 | ""isPartOfComposite"": false 144 | }, 145 | { 146 | ""name"": """", 147 | ""id"": ""99d6f003-0cad-4cfe-b955-60214be1ae83"", 148 | ""path"": ""/space"", 149 | ""interactions"": """", 150 | ""processors"": """", 151 | ""groups"": """", 152 | ""action"": ""Jump"", 153 | ""isComposite"": false, 154 | ""isPartOfComposite"": false 155 | } 156 | ] 157 | } 158 | ], 159 | ""controlSchemes"": [] 160 | }"); 161 | // PlayerControls 162 | m_PlayerControls = asset.FindActionMap("PlayerControls", throwIfNotFound: true); 163 | m_PlayerControls_Movement = m_PlayerControls.FindAction("Movement", throwIfNotFound: true); 164 | m_PlayerControls_Look = m_PlayerControls.FindAction("Look", throwIfNotFound: true); 165 | m_PlayerControls_Sprint = m_PlayerControls.FindAction("Sprint", throwIfNotFound: true); 166 | m_PlayerControls_Jump = m_PlayerControls.FindAction("Jump", throwIfNotFound: true); 167 | } 168 | 169 | public void Dispose() 170 | { 171 | UnityEngine.Object.Destroy(asset); 172 | } 173 | 174 | public InputBinding? bindingMask 175 | { 176 | get => asset.bindingMask; 177 | set => asset.bindingMask = value; 178 | } 179 | 180 | public ReadOnlyArray? devices 181 | { 182 | get => asset.devices; 183 | set => asset.devices = value; 184 | } 185 | 186 | public ReadOnlyArray controlSchemes => asset.controlSchemes; 187 | 188 | public bool Contains(InputAction action) 189 | { 190 | return asset.Contains(action); 191 | } 192 | 193 | public IEnumerator GetEnumerator() 194 | { 195 | return asset.GetEnumerator(); 196 | } 197 | 198 | IEnumerator IEnumerable.GetEnumerator() 199 | { 200 | return GetEnumerator(); 201 | } 202 | 203 | public void Enable() 204 | { 205 | asset.Enable(); 206 | } 207 | 208 | public void Disable() 209 | { 210 | asset.Disable(); 211 | } 212 | public IEnumerable bindings => asset.bindings; 213 | 214 | public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false) 215 | { 216 | return asset.FindAction(actionNameOrId, throwIfNotFound); 217 | } 218 | public int FindBinding(InputBinding bindingMask, out InputAction action) 219 | { 220 | return asset.FindBinding(bindingMask, out action); 221 | } 222 | 223 | // PlayerControls 224 | private readonly InputActionMap m_PlayerControls; 225 | private IPlayerControlsActions m_PlayerControlsActionsCallbackInterface; 226 | private readonly InputAction m_PlayerControls_Movement; 227 | private readonly InputAction m_PlayerControls_Look; 228 | private readonly InputAction m_PlayerControls_Sprint; 229 | private readonly InputAction m_PlayerControls_Jump; 230 | public struct PlayerControlsActions 231 | { 232 | private @PlayerInputControls m_Wrapper; 233 | public PlayerControlsActions(@PlayerInputControls wrapper) { m_Wrapper = wrapper; } 234 | public InputAction @Movement => m_Wrapper.m_PlayerControls_Movement; 235 | public InputAction @Look => m_Wrapper.m_PlayerControls_Look; 236 | public InputAction @Sprint => m_Wrapper.m_PlayerControls_Sprint; 237 | public InputAction @Jump => m_Wrapper.m_PlayerControls_Jump; 238 | public InputActionMap Get() { return m_Wrapper.m_PlayerControls; } 239 | public void Enable() { Get().Enable(); } 240 | public void Disable() { Get().Disable(); } 241 | public bool enabled => Get().enabled; 242 | public static implicit operator InputActionMap(PlayerControlsActions set) { return set.Get(); } 243 | public void SetCallbacks(IPlayerControlsActions instance) 244 | { 245 | if (m_Wrapper.m_PlayerControlsActionsCallbackInterface != null) 246 | { 247 | @Movement.started -= m_Wrapper.m_PlayerControlsActionsCallbackInterface.OnMovement; 248 | @Movement.performed -= m_Wrapper.m_PlayerControlsActionsCallbackInterface.OnMovement; 249 | @Movement.canceled -= m_Wrapper.m_PlayerControlsActionsCallbackInterface.OnMovement; 250 | @Look.started -= m_Wrapper.m_PlayerControlsActionsCallbackInterface.OnLook; 251 | @Look.performed -= m_Wrapper.m_PlayerControlsActionsCallbackInterface.OnLook; 252 | @Look.canceled -= m_Wrapper.m_PlayerControlsActionsCallbackInterface.OnLook; 253 | @Sprint.started -= m_Wrapper.m_PlayerControlsActionsCallbackInterface.OnSprint; 254 | @Sprint.performed -= m_Wrapper.m_PlayerControlsActionsCallbackInterface.OnSprint; 255 | @Sprint.canceled -= m_Wrapper.m_PlayerControlsActionsCallbackInterface.OnSprint; 256 | @Jump.started -= m_Wrapper.m_PlayerControlsActionsCallbackInterface.OnJump; 257 | @Jump.performed -= m_Wrapper.m_PlayerControlsActionsCallbackInterface.OnJump; 258 | @Jump.canceled -= m_Wrapper.m_PlayerControlsActionsCallbackInterface.OnJump; 259 | } 260 | m_Wrapper.m_PlayerControlsActionsCallbackInterface = instance; 261 | if (instance != null) 262 | { 263 | @Movement.started += instance.OnMovement; 264 | @Movement.performed += instance.OnMovement; 265 | @Movement.canceled += instance.OnMovement; 266 | @Look.started += instance.OnLook; 267 | @Look.performed += instance.OnLook; 268 | @Look.canceled += instance.OnLook; 269 | @Sprint.started += instance.OnSprint; 270 | @Sprint.performed += instance.OnSprint; 271 | @Sprint.canceled += instance.OnSprint; 272 | @Jump.started += instance.OnJump; 273 | @Jump.performed += instance.OnJump; 274 | @Jump.canceled += instance.OnJump; 275 | } 276 | } 277 | } 278 | public PlayerControlsActions @PlayerControls => new PlayerControlsActions(this); 279 | public interface IPlayerControlsActions 280 | { 281 | void OnMovement(InputAction.CallbackContext context); 282 | void OnLook(InputAction.CallbackContext context); 283 | void OnSprint(InputAction.CallbackContext context); 284 | void OnJump(InputAction.CallbackContext context); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /Scripts/FPController/FPController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace FPController 5 | { 6 | [RequireComponent(typeof(FPInputManager))] 7 | public class FPController : MonoBehaviour 8 | { 9 | #region Inspector Serialized Fields 10 | [Header("Movement")] // Movement Fields 11 | 12 | [Tooltip("Player's walking speed value")] 13 | [SerializeField] private float _movementSpeed = 4f; 14 | 15 | [Tooltip("Player's movement acceleration")] 16 | [SerializeField] private float _acceleration = 15f; 17 | 18 | [Tooltip("Percentage of player's mid-air movement control [0,1]")] 19 | [SerializeField][Range(0, 1)] private float _midAirControlMultiplier = 0.4f; 20 | 21 | [Header("Slopes")] 22 | 23 | [Tooltip("This field is used to calculate how fast the player is going to slide on a slope [Infinity,0]")] 24 | [SerializeField][Range(float.NegativeInfinity, 0)] private float _slopeSlideMultiplier = -4f; 25 | 26 | [Tooltip("Minimum slide speed while sliding on slopes")] 27 | [SerializeField] private float _minSlideSpeed = 0.01f; 28 | 29 | [Tooltip("Maximum slide speed while sliding on slopes")] 30 | [SerializeField] private float _maxSlideSpeed = 4f; 31 | 32 | 33 | [Header("Gravity")] // Gravity Fields 34 | 35 | [Tooltip("Player's mass")] 36 | [SerializeField] private float _mass = 1f; 37 | 38 | [Tooltip("Gravity multiplier")] 39 | [SerializeField] private float _gravityMultiplier = 1f; 40 | 41 | [Header("Camera")] // Camera fields 42 | 43 | [Tooltip("Reference to the camera holder game object")] 44 | [SerializeField] private Transform _cameraHolderTransform; 45 | 46 | [Tooltip("Mouse sensitivity")] 47 | [SerializeField] private float _mouseSensitivity = 0.05f; 48 | 49 | [Tooltip("Will clamp the rotation of Y axis on this value")] 50 | [SerializeField] private float _maxCameraY = 90f; 51 | #endregion 52 | 53 | #region Events 54 | public event Action OnBeforeMove; 55 | public event Action OnGroundStateChange; 56 | #endregion 57 | 58 | #region Public Properties 59 | public bool IsGrounded => _controller.isGrounded; 60 | public bool IsOnSlope => _isOnSlope; 61 | public float MovementSpeedMultiplier 62 | { 63 | get { return _movementSpeedMultiplier; } 64 | set { _movementSpeedMultiplier = value; } 65 | } 66 | public bool IsJumping 67 | { 68 | get { return _isJumping; } 69 | set { _isJumping = value; } 70 | } 71 | public bool IsSprinting => _controller.isGrounded && _input.SprintInput; 72 | public bool IsSliding 73 | { 74 | get { return _isSliding; } 75 | set { _isSliding = value; } 76 | } 77 | public bool IsInputMoving => _controller.velocity.magnitude > 0.1f && _input.MovementInput != Vector2.zero; 78 | public float PlayerMass => _mass; 79 | public float GravityMultiplier => _gravityMultiplier; 80 | #endregion 81 | 82 | #region Public Fields 83 | internal Vector3 Velocity; 84 | #endregion 85 | 86 | #region Local Fields 87 | private FPInputManager _input; 88 | private CharacterController _controller; 89 | private Vector3 _moveDirection; 90 | private Vector3 _lastMoveDirection; 91 | private Vector2 _lookDirection; 92 | private float _movementSpeedMultiplier; 93 | private bool _wasGrounded; 94 | private bool _isJumping; 95 | private bool _isOnSlope; 96 | private bool _isSliding; 97 | private bool _didSphereCast; 98 | private RaycastHit _slopeHit; 99 | #endregion 100 | 101 | #region MonoBehaviour Methods 102 | private void Awake() 103 | { 104 | _controller = GetComponent(); 105 | _input = GetComponent(); 106 | } 107 | 108 | private void Start() 109 | { 110 | Cursor.lockState = CursorLockMode.Locked; 111 | } 112 | 113 | private void Update() 114 | { 115 | SlopeCheck(); // Slope Check and logic 116 | UpdateGround(); // Ground Check and logic 117 | UpdateGravity(); // Gravity calculations 118 | UpdateMovement(); // Movement calculations 119 | } 120 | 121 | private void LateUpdate() 122 | { 123 | UpdateCamera(); 124 | } 125 | #endregion 126 | 127 | #region Private Methods 128 | 129 | /// 130 | /// Updates player's look direction 131 | /// - Applies horizontal rotation to the player. 132 | /// - Applies vertical rotation to the camera holder and clamps it's Y-axis rotation 133 | /// 134 | private void UpdateCamera() 135 | { 136 | _lookDirection += new Vector2(_input.LookInput.x * _mouseSensitivity, _input.LookInput.y * _mouseSensitivity); // Get the mouse delta input 137 | _lookDirection.y = Mathf.Clamp(_lookDirection.y, -_maxCameraY, _maxCameraY); // Clamps vertical rotation to prevent excesive up/down rotation 138 | _cameraHolderTransform.localRotation = Quaternion.Euler(-_lookDirection.y, 0, 0); // Applies vertical(Y) rotation to camera holder 139 | transform.localRotation = Quaternion.Euler(0, _lookDirection.x, 0); // Applies horizontal(X) rotation to player 140 | } 141 | 142 | /// 143 | /// Updates the player's movement 144 | /// - First invokes OnBeforeMove for pre-movement logic (Sprinting & Jumping) 145 | /// 146 | private void UpdateMovement() 147 | { 148 | _movementSpeedMultiplier = 1f; 149 | OnBeforeMove?.Invoke(); // Invoke methods before the actual movement calculations if there are any 150 | 151 | 152 | if (_controller.isGrounded) // When player is grounded, calculate & normalize the move direction from the input 153 | { 154 | _moveDirection = transform.forward * _input.MovementInput.y + transform.right * _input.MovementInput.x; 155 | _moveDirection.Normalize(); 156 | _moveDirection *= _movementSpeed * _movementSpeedMultiplier; 157 | if (!_isJumping) 158 | _lastMoveDirection = _moveDirection; 159 | } 160 | // Else if player is not grounded, then use the lastMoveDirection and by getting again a mid air direction, 161 | // player will be able to move slightly while on air, but that depends on the _midAirControlMultiplier 162 | else 163 | { 164 | // Get the movement direction, normalize it and apply the speed 165 | Vector3 midAirDirection = transform.forward * _input.MovementInput.y + transform.right * _input.MovementInput.x; 166 | midAirDirection.Normalize(); 167 | midAirDirection *= _movementSpeed * _movementSpeedMultiplier; 168 | // Interpolate the last saved move direction with the mid air control multiplier 169 | // 0: no mid air control , 1: full mid air control 170 | _moveDirection.x = Mathf.Lerp(_lastMoveDirection.x, midAirDirection.x, _midAirControlMultiplier); 171 | _moveDirection.z = Mathf.Lerp(_lastMoveDirection.z, midAirDirection.z, _midAirControlMultiplier); 172 | } 173 | 174 | // If player is not sliding, then smooth the movement speed before applying it 175 | if (!_isSliding) 176 | { 177 | float smoothMovementFactor = _acceleration * Time.deltaTime; 178 | Velocity.x = Mathf.Lerp(Velocity.x, _moveDirection.x, smoothMovementFactor); 179 | Velocity.z = Mathf.Lerp(Velocity.z, _moveDirection.z, smoothMovementFactor); 180 | } 181 | 182 | //Apply the move direction 183 | _controller.Move(Velocity * Time.deltaTime); 184 | } 185 | 186 | /// 187 | /// Applies vertical gravity force to the player based on current state: 188 | /// - Player is on slope (also applies slope force along with gravity) 189 | /// - Player is just grounded 190 | /// - player is mid-air 191 | /// 192 | private void UpdateGravity() 193 | { 194 | float gravityForce = Physics.gravity.y * _gravityMultiplier * _mass; 195 | if (_isOnSlope && IsGrounded) 196 | { 197 | Vector3 slopeForce = Vector3.Cross(Vector3.Cross(Vector3.up, _slopeHit.normal), _slopeHit.normal); 198 | Velocity += slopeForce * Mathf.Abs(_slopeSlideMultiplier); 199 | Velocity.y += gravityForce * Time.deltaTime; 200 | if (_isSliding) 201 | ClampSlopeSlidingSpeed(); 202 | } 203 | else if (IsGrounded) 204 | Velocity.y = -1f; 205 | else 206 | Velocity.y += gravityForce * Time.deltaTime; 207 | } 208 | 209 | /// 210 | /// Clamps horizontal velocity when player is sliding on a slope 211 | /// 212 | private void ClampSlopeSlidingSpeed() 213 | { 214 | Vector3 horizontalVelocity = new Vector3(Velocity.x, 0, Velocity.z); 215 | float speed = horizontalVelocity.magnitude; 216 | if (speed > _minSlideSpeed) 217 | { 218 | float clampedSpeed = Mathf.Clamp(speed, _minSlideSpeed, _maxSlideSpeed); 219 | horizontalVelocity = horizontalVelocity.normalized * clampedSpeed; 220 | Velocity.x = horizontalVelocity.x; 221 | Velocity.z = horizontalVelocity.z; 222 | } 223 | } 224 | 225 | /// 226 | /// Responsible for checking if player's grounded state has changed since last frame. 227 | /// If it has, then invokes OnGroundStateChange event and updates _wasGrounded to reflect the current grounded state. 228 | /// 229 | private void UpdateGround() 230 | { 231 | if (_wasGrounded != IsGrounded) 232 | { 233 | OnGroundStateChange?.Invoke(IsGrounded); 234 | _wasGrounded = IsGrounded; 235 | } 236 | } 237 | 238 | /// 239 | /// Checks if player is standing on a slope. 240 | /// First uses a Raycast directly downward to detect ground. But if player is near an edge and Raycast fails, then uses a SphereCast. 241 | /// SphereCast manages to detect that edge (stairs commonly). If player starts sliding and SphereCast was used that frame, it will not go back to Raycast until they stop 242 | /// 243 | /// !!! Need to check this later !!! 244 | /// There might be an issue with stairs 245 | /// 246 | private void SlopeCheck() 247 | { 248 | if (_controller.isGrounded) 249 | { 250 | // The cast origin will be on character's feet 251 | var castOrigin = transform.position - new Vector3(0, _controller.height / 2 - _controller.radius, 0); 252 | // If didn't slide using SphereCast and Raycast finds a hit then, invoke TryApplySlopeForce 253 | if (!_didSphereCast && Physics.Raycast(castOrigin, Vector3.down, out var hit, 1.5f, ~LayerMask.GetMask("Player"), QueryTriggerInteraction.Ignore)) 254 | SlopeHitPointCheck(hit); 255 | // Else use a Spherecast to find a hit 256 | else if (Physics.SphereCast(castOrigin, _controller.radius - 0.01f, Vector3.down, out hit, 0.05f, ~LayerMask.GetMask("Player"), QueryTriggerInteraction.Ignore)) 257 | { 258 | _didSphereCast = true; 259 | SlopeHitPointCheck(hit); 260 | } 261 | else if (IsGrounded && IsSliding) { 262 | _didSphereCast = false; 263 | SlopeHitPointCheck(_slopeHit); 264 | } 265 | } 266 | } 267 | 268 | /// 269 | /// Responsible for checking the angle of the hit point and adjust logic 270 | /// 271 | private void SlopeHitPointCheck(RaycastHit hit) 272 | { 273 | _slopeHit = hit; 274 | var angle = Vector3.Angle(hit.normal, Vector3.up); 275 | if (angle > _controller.slopeLimit) // If angle exceeds the limit (hit is a steep slope) 276 | { 277 | _isOnSlope = true; 278 | _isSliding = true; 279 | } 280 | else 281 | { 282 | _isOnSlope = false; // is no longer on slope 283 | _isSliding = false; // is not sliding 284 | _didSphereCast = false; // do not use SphereCast on next frame 285 | } 286 | } 287 | #endregion 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /GameObjects/FPController-URP.prefab: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1 &4812071752871371028 4 | GameObject: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | serializedVersion: 6 10 | m_Component: 11 | - component: {fileID: 8390729986721986591} 12 | - component: {fileID: 690595866509419865} 13 | m_Layer: 8 14 | m_Name: Footsteps Audio Source 15 | m_TagString: Untagged 16 | m_Icon: {fileID: 0} 17 | m_NavMeshLayer: 0 18 | m_StaticEditorFlags: 0 19 | m_IsActive: 1 20 | --- !u!4 &8390729986721986591 21 | Transform: 22 | m_ObjectHideFlags: 0 23 | m_CorrespondingSourceObject: {fileID: 0} 24 | m_PrefabInstance: {fileID: 0} 25 | m_PrefabAsset: {fileID: 0} 26 | m_GameObject: {fileID: 4812071752871371028} 27 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 28 | m_LocalPosition: {x: 0, y: -1, z: 0} 29 | m_LocalScale: {x: 1, y: 1, z: 1} 30 | m_ConstrainProportionsScale: 0 31 | m_Children: [] 32 | m_Father: {fileID: 7787427698462517471} 33 | m_RootOrder: 1 34 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 35 | --- !u!82 &690595866509419865 36 | AudioSource: 37 | m_ObjectHideFlags: 0 38 | m_CorrespondingSourceObject: {fileID: 0} 39 | m_PrefabInstance: {fileID: 0} 40 | m_PrefabAsset: {fileID: 0} 41 | m_GameObject: {fileID: 4812071752871371028} 42 | m_Enabled: 1 43 | serializedVersion: 4 44 | OutputAudioMixerGroup: {fileID: 0} 45 | m_audioClip: {fileID: 0} 46 | m_PlayOnAwake: 0 47 | m_Volume: 0.1 48 | m_Pitch: 1 49 | Loop: 0 50 | Mute: 0 51 | Spatialize: 0 52 | SpatializePostEffects: 0 53 | Priority: 128 54 | DopplerLevel: 1 55 | MinDistance: 1 56 | MaxDistance: 500 57 | Pan2D: 0 58 | rolloffMode: 0 59 | BypassEffects: 0 60 | BypassListenerEffects: 0 61 | BypassReverbZones: 0 62 | rolloffCustomCurve: 63 | serializedVersion: 2 64 | m_Curve: 65 | - serializedVersion: 3 66 | time: 0 67 | value: 1 68 | inSlope: 0 69 | outSlope: 0 70 | tangentMode: 0 71 | weightedMode: 0 72 | inWeight: 0.33333334 73 | outWeight: 0.33333334 74 | - serializedVersion: 3 75 | time: 1 76 | value: 0 77 | inSlope: 0 78 | outSlope: 0 79 | tangentMode: 0 80 | weightedMode: 0 81 | inWeight: 0.33333334 82 | outWeight: 0.33333334 83 | m_PreInfinity: 2 84 | m_PostInfinity: 2 85 | m_RotationOrder: 4 86 | panLevelCustomCurve: 87 | serializedVersion: 2 88 | m_Curve: 89 | - serializedVersion: 3 90 | time: 0 91 | value: 0 92 | inSlope: 0 93 | outSlope: 0 94 | tangentMode: 0 95 | weightedMode: 0 96 | inWeight: 0.33333334 97 | outWeight: 0.33333334 98 | m_PreInfinity: 2 99 | m_PostInfinity: 2 100 | m_RotationOrder: 4 101 | spreadCustomCurve: 102 | serializedVersion: 2 103 | m_Curve: 104 | - serializedVersion: 3 105 | time: 0 106 | value: 0 107 | inSlope: 0 108 | outSlope: 0 109 | tangentMode: 0 110 | weightedMode: 0 111 | inWeight: 0.33333334 112 | outWeight: 0.33333334 113 | m_PreInfinity: 2 114 | m_PostInfinity: 2 115 | m_RotationOrder: 4 116 | reverbZoneMixCustomCurve: 117 | serializedVersion: 2 118 | m_Curve: 119 | - serializedVersion: 3 120 | time: 0 121 | value: 1 122 | inSlope: 0 123 | outSlope: 0 124 | tangentMode: 0 125 | weightedMode: 0 126 | inWeight: 0.33333334 127 | outWeight: 0.33333334 128 | m_PreInfinity: 2 129 | m_PostInfinity: 2 130 | m_RotationOrder: 4 131 | --- !u!1 &5453493278238917423 132 | GameObject: 133 | m_ObjectHideFlags: 0 134 | m_CorrespondingSourceObject: {fileID: 0} 135 | m_PrefabInstance: {fileID: 0} 136 | m_PrefabAsset: {fileID: 0} 137 | serializedVersion: 6 138 | m_Component: 139 | - component: {fileID: 5453493278238917420} 140 | m_Layer: 8 141 | m_Name: Camera Holder 142 | m_TagString: Untagged 143 | m_Icon: {fileID: 0} 144 | m_NavMeshLayer: 0 145 | m_StaticEditorFlags: 0 146 | m_IsActive: 1 147 | --- !u!4 &5453493278238917420 148 | Transform: 149 | m_ObjectHideFlags: 0 150 | m_CorrespondingSourceObject: {fileID: 0} 151 | m_PrefabInstance: {fileID: 0} 152 | m_PrefabAsset: {fileID: 0} 153 | m_GameObject: {fileID: 5453493278238917423} 154 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 155 | m_LocalPosition: {x: 0, y: 0.79999995, z: 0} 156 | m_LocalScale: {x: 1, y: 1, z: 1} 157 | m_ConstrainProportionsScale: 0 158 | m_Children: 159 | - {fileID: 5453493279221145104} 160 | m_Father: {fileID: 7787427698462517471} 161 | m_RootOrder: 0 162 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 163 | --- !u!1 &5453493279221145117 164 | GameObject: 165 | m_ObjectHideFlags: 0 166 | m_CorrespondingSourceObject: {fileID: 0} 167 | m_PrefabInstance: {fileID: 0} 168 | m_PrefabAsset: {fileID: 0} 169 | serializedVersion: 6 170 | m_Component: 171 | - component: {fileID: 5453493279221145104} 172 | - component: {fileID: 5453493279221145107} 173 | - component: {fileID: 5453493279221145106} 174 | - component: {fileID: 5453493279221145105} 175 | - component: {fileID: 5453493279221145110} 176 | m_Layer: 8 177 | m_Name: Main Camera 178 | m_TagString: MainCamera 179 | m_Icon: {fileID: 0} 180 | m_NavMeshLayer: 0 181 | m_StaticEditorFlags: 0 182 | m_IsActive: 1 183 | --- !u!4 &5453493279221145104 184 | Transform: 185 | m_ObjectHideFlags: 0 186 | m_CorrespondingSourceObject: {fileID: 0} 187 | m_PrefabInstance: {fileID: 0} 188 | m_PrefabAsset: {fileID: 0} 189 | m_GameObject: {fileID: 5453493279221145117} 190 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 191 | m_LocalPosition: {x: 0, y: 0, z: 0} 192 | m_LocalScale: {x: 1, y: 1, z: 1} 193 | m_ConstrainProportionsScale: 0 194 | m_Children: [] 195 | m_Father: {fileID: 5453493278238917420} 196 | m_RootOrder: 0 197 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 198 | --- !u!20 &5453493279221145107 199 | Camera: 200 | m_ObjectHideFlags: 0 201 | m_CorrespondingSourceObject: {fileID: 0} 202 | m_PrefabInstance: {fileID: 0} 203 | m_PrefabAsset: {fileID: 0} 204 | m_GameObject: {fileID: 5453493279221145117} 205 | m_Enabled: 1 206 | serializedVersion: 2 207 | m_ClearFlags: 1 208 | m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} 209 | m_projectionMatrixMode: 1 210 | m_GateFitMode: 2 211 | m_FOVAxisMode: 0 212 | m_SensorSize: {x: 36, y: 24} 213 | m_LensShift: {x: 0, y: 0} 214 | m_FocalLength: 50 215 | m_NormalizedViewPortRect: 216 | serializedVersion: 2 217 | x: 0 218 | y: 0 219 | width: 1 220 | height: 1 221 | near clip plane: 0.3 222 | far clip plane: 1000 223 | field of view: 60 224 | orthographic: 0 225 | orthographic size: 5 226 | m_Depth: -1 227 | m_CullingMask: 228 | serializedVersion: 2 229 | m_Bits: 4294967295 230 | m_RenderingPath: -1 231 | m_TargetTexture: {fileID: 0} 232 | m_TargetDisplay: 0 233 | m_TargetEye: 3 234 | m_HDR: 1 235 | m_AllowMSAA: 1 236 | m_AllowDynamicResolution: 0 237 | m_ForceIntoRT: 0 238 | m_OcclusionCulling: 1 239 | m_StereoConvergence: 10 240 | m_StereoSeparation: 0.022 241 | --- !u!81 &5453493279221145106 242 | AudioListener: 243 | m_ObjectHideFlags: 0 244 | m_CorrespondingSourceObject: {fileID: 0} 245 | m_PrefabInstance: {fileID: 0} 246 | m_PrefabAsset: {fileID: 0} 247 | m_GameObject: {fileID: 5453493279221145117} 248 | m_Enabled: 1 249 | --- !u!114 &5453493279221145105 250 | MonoBehaviour: 251 | m_ObjectHideFlags: 0 252 | m_CorrespondingSourceObject: {fileID: 0} 253 | m_PrefabInstance: {fileID: 0} 254 | m_PrefabAsset: {fileID: 0} 255 | m_GameObject: {fileID: 5453493279221145117} 256 | m_Enabled: 1 257 | m_EditorHideFlags: 0 258 | m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} 259 | m_Name: 260 | m_EditorClassIdentifier: 261 | m_RenderShadows: 1 262 | m_RequiresDepthTextureOption: 2 263 | m_RequiresOpaqueTextureOption: 2 264 | m_CameraType: 0 265 | m_Cameras: [] 266 | m_RendererIndex: -1 267 | m_VolumeLayerMask: 268 | serializedVersion: 2 269 | m_Bits: 1 270 | m_VolumeTrigger: {fileID: 0} 271 | m_VolumeFrameworkUpdateModeOption: 2 272 | m_RenderPostProcessing: 1 273 | m_Antialiasing: 0 274 | m_AntialiasingQuality: 2 275 | m_StopNaN: 0 276 | m_Dithering: 0 277 | m_ClearDepth: 1 278 | m_AllowXRRendering: 1 279 | m_RequiresDepthTexture: 0 280 | m_RequiresColorTexture: 0 281 | m_Version: 2 282 | --- !u!114 &5453493279221145110 283 | MonoBehaviour: 284 | m_ObjectHideFlags: 0 285 | m_CorrespondingSourceObject: {fileID: 0} 286 | m_PrefabInstance: {fileID: 0} 287 | m_PrefabAsset: {fileID: 0} 288 | m_GameObject: {fileID: 5453493279221145117} 289 | m_Enabled: 1 290 | m_EditorHideFlags: 0 291 | m_Script: {fileID: 11500000, guid: 3815b447c65dcca4f910bf16823b15cb, type: 3} 292 | m_Name: 293 | m_EditorClassIdentifier: 294 | _duration: 1 295 | _strengthMultiplier: 0.2 296 | _curve: 297 | serializedVersion: 2 298 | m_Curve: 299 | - serializedVersion: 3 300 | time: 0 301 | value: 0 302 | inSlope: 0 303 | outSlope: 0 304 | tangentMode: 0 305 | weightedMode: 0 306 | inWeight: 0 307 | outWeight: 0 308 | - serializedVersion: 3 309 | time: 0.2 310 | value: 0.25 311 | inSlope: -0.01 312 | outSlope: 0 313 | tangentMode: 0 314 | weightedMode: 0 315 | inWeight: -0.3 316 | outWeight: 0 317 | - serializedVersion: 3 318 | time: 1 319 | value: 0 320 | inSlope: 0 321 | outSlope: 0 322 | tangentMode: 0 323 | weightedMode: 0 324 | inWeight: 0 325 | outWeight: 0 326 | m_PreInfinity: 2 327 | m_PostInfinity: 2 328 | m_RotationOrder: 4 329 | IsShaking: 0 330 | --- !u!1 &7787427698462517458 331 | GameObject: 332 | m_ObjectHideFlags: 0 333 | m_CorrespondingSourceObject: {fileID: 0} 334 | m_PrefabInstance: {fileID: 0} 335 | m_PrefabAsset: {fileID: 0} 336 | serializedVersion: 6 337 | m_Component: 338 | - component: {fileID: 7787427698462517471} 339 | - component: {fileID: 7787427698462517470} 340 | - component: {fileID: 7787427698462517469} 341 | - component: {fileID: 7787427698462517468} 342 | - component: {fileID: 7787427698462517459} 343 | - component: {fileID: 7787427698462517456} 344 | - component: {fileID: 7787427698462517464} 345 | - component: {fileID: 8425700915552814015} 346 | m_Layer: 8 347 | m_Name: FPController-URP 348 | m_TagString: Untagged 349 | m_Icon: {fileID: -964228994112308473, guid: 0000000000000000d000000000000000, type: 0} 350 | m_NavMeshLayer: 0 351 | m_StaticEditorFlags: 0 352 | m_IsActive: 1 353 | --- !u!4 &7787427698462517471 354 | Transform: 355 | m_ObjectHideFlags: 0 356 | m_CorrespondingSourceObject: {fileID: 0} 357 | m_PrefabInstance: {fileID: 0} 358 | m_PrefabAsset: {fileID: 0} 359 | m_GameObject: {fileID: 7787427698462517458} 360 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 361 | m_LocalPosition: {x: 0, y: 1, z: 0} 362 | m_LocalScale: {x: 1, y: 1, z: 1} 363 | m_ConstrainProportionsScale: 0 364 | m_Children: 365 | - {fileID: 5453493278238917420} 366 | - {fileID: 8390729986721986591} 367 | m_Father: {fileID: 0} 368 | m_RootOrder: 0 369 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 370 | --- !u!114 &7787427698462517470 371 | MonoBehaviour: 372 | m_ObjectHideFlags: 0 373 | m_CorrespondingSourceObject: {fileID: 0} 374 | m_PrefabInstance: {fileID: 0} 375 | m_PrefabAsset: {fileID: 0} 376 | m_GameObject: {fileID: 7787427698462517458} 377 | m_Enabled: 1 378 | m_EditorHideFlags: 0 379 | m_Script: {fileID: 11500000, guid: dcd80274256d993408e1364716168d0d, type: 3} 380 | m_Name: 381 | m_EditorClassIdentifier: 382 | MovementInput: {x: 0, y: 0} 383 | LookInput: {x: 0, y: 0} 384 | SprintInput: 0 385 | JumpInput: 0 386 | --- !u!114 &7787427698462517469 387 | MonoBehaviour: 388 | m_ObjectHideFlags: 0 389 | m_CorrespondingSourceObject: {fileID: 0} 390 | m_PrefabInstance: {fileID: 0} 391 | m_PrefabAsset: {fileID: 0} 392 | m_GameObject: {fileID: 7787427698462517458} 393 | m_Enabled: 1 394 | m_EditorHideFlags: 0 395 | m_Script: {fileID: 11500000, guid: 116d5b2b2ac5d3f48a8db4260dbab72f, type: 3} 396 | m_Name: 397 | m_EditorClassIdentifier: 398 | _movementSpeed: 4 399 | _acceleration: 15 400 | _midAirControlMultiplier: 0.4 401 | _slopeSlideMultiplier: -4 402 | _mass: 1 403 | _gravityMultiplier: 1 404 | _cameraHolderTransform: {fileID: 5453493278238917420} 405 | _mouseSensitivity: 0.05 406 | _maxCameraY: 90 407 | --- !u!114 &7787427698462517468 408 | MonoBehaviour: 409 | m_ObjectHideFlags: 0 410 | m_CorrespondingSourceObject: {fileID: 0} 411 | m_PrefabInstance: {fileID: 0} 412 | m_PrefabAsset: {fileID: 0} 413 | m_GameObject: {fileID: 7787427698462517458} 414 | m_Enabled: 1 415 | m_EditorHideFlags: 0 416 | m_Script: {fileID: 11500000, guid: f34a6134550dca84191873450eeb1eb3, type: 3} 417 | m_Name: 418 | m_EditorClassIdentifier: 419 | _speedMultiplier: 1.5 420 | --- !u!114 &7787427698462517459 421 | MonoBehaviour: 422 | m_ObjectHideFlags: 0 423 | m_CorrespondingSourceObject: {fileID: 0} 424 | m_PrefabInstance: {fileID: 0} 425 | m_PrefabAsset: {fileID: 0} 426 | m_GameObject: {fileID: 7787427698462517458} 427 | m_Enabled: 1 428 | m_EditorHideFlags: 0 429 | m_Script: {fileID: 11500000, guid: 37614c034b070c0428fbda7407508d46, type: 3} 430 | m_Name: 431 | m_EditorClassIdentifier: 432 | _jumpHeight: 1 433 | _jumpBufferTime: 0.05 434 | _jumpGroundGraceTime: 0.2 435 | --- !u!114 &7787427698462517456 436 | MonoBehaviour: 437 | m_ObjectHideFlags: 0 438 | m_CorrespondingSourceObject: {fileID: 0} 439 | m_PrefabInstance: {fileID: 0} 440 | m_PrefabAsset: {fileID: 0} 441 | m_GameObject: {fileID: 7787427698462517458} 442 | m_Enabled: 1 443 | m_EditorHideFlags: 0 444 | m_Script: {fileID: 11500000, guid: b218d6d4137ecfb47b14cc38207afff5, type: 3} 445 | m_Name: 446 | m_EditorClassIdentifier: 447 | _frequency: 15 448 | _amplitude: 0.02 449 | _sprintMultiplier: 1.3 450 | --- !u!143 &7787427698462517464 451 | CharacterController: 452 | m_ObjectHideFlags: 0 453 | m_CorrespondingSourceObject: {fileID: 0} 454 | m_PrefabInstance: {fileID: 0} 455 | m_PrefabAsset: {fileID: 0} 456 | m_GameObject: {fileID: 7787427698462517458} 457 | m_Material: {fileID: 0} 458 | m_IsTrigger: 0 459 | m_Enabled: 1 460 | serializedVersion: 2 461 | m_Height: 2 462 | m_Radius: 0.5 463 | m_SlopeLimit: 50 464 | m_StepOffset: 0.3 465 | m_SkinWidth: 0.0001 466 | m_MinMoveDistance: 0 467 | m_Center: {x: 0, y: 0, z: 0} 468 | --- !u!114 &8425700915552814015 469 | MonoBehaviour: 470 | m_ObjectHideFlags: 0 471 | m_CorrespondingSourceObject: {fileID: 0} 472 | m_PrefabInstance: {fileID: 0} 473 | m_PrefabAsset: {fileID: 0} 474 | m_GameObject: {fileID: 7787427698462517458} 475 | m_Enabled: 1 476 | m_EditorHideFlags: 0 477 | m_Script: {fileID: 11500000, guid: 6e173bde8b130a142b6bc377c3018d9b, type: 3} 478 | m_Name: 479 | m_EditorClassIdentifier: 480 | _audioSource: {fileID: 690595866509419865} 481 | _playFootsteps: 1 482 | _minRequiredSpeed: 3 483 | _walkStepInterval: 0.8 484 | _sprintStepInterval: 0.5 485 | _sounds: [] 486 | -------------------------------------------------------------------------------- /GameObjects/FPController-BuiltIn.prefab: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1 &4812071752871371028 4 | GameObject: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | serializedVersion: 6 10 | m_Component: 11 | - component: {fileID: 8390729986721986591} 12 | - component: {fileID: 690595866509419865} 13 | m_Layer: 8 14 | m_Name: Footsteps Audio Source 15 | m_TagString: Untagged 16 | m_Icon: {fileID: 0} 17 | m_NavMeshLayer: 0 18 | m_StaticEditorFlags: 0 19 | m_IsActive: 1 20 | --- !u!4 &8390729986721986591 21 | Transform: 22 | m_ObjectHideFlags: 0 23 | m_CorrespondingSourceObject: {fileID: 0} 24 | m_PrefabInstance: {fileID: 0} 25 | m_PrefabAsset: {fileID: 0} 26 | m_GameObject: {fileID: 4812071752871371028} 27 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 28 | m_LocalPosition: {x: 0, y: -1, z: 0} 29 | m_LocalScale: {x: 1, y: 1, z: 1} 30 | m_ConstrainProportionsScale: 0 31 | m_Children: [] 32 | m_Father: {fileID: 7787427698462517471} 33 | m_RootOrder: 1 34 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 35 | --- !u!82 &690595866509419865 36 | AudioSource: 37 | m_ObjectHideFlags: 0 38 | m_CorrespondingSourceObject: {fileID: 0} 39 | m_PrefabInstance: {fileID: 0} 40 | m_PrefabAsset: {fileID: 0} 41 | m_GameObject: {fileID: 4812071752871371028} 42 | m_Enabled: 1 43 | serializedVersion: 4 44 | OutputAudioMixerGroup: {fileID: 0} 45 | m_audioClip: {fileID: 0} 46 | m_PlayOnAwake: 0 47 | m_Volume: 0.1 48 | m_Pitch: 1 49 | Loop: 0 50 | Mute: 0 51 | Spatialize: 0 52 | SpatializePostEffects: 0 53 | Priority: 128 54 | DopplerLevel: 1 55 | MinDistance: 1 56 | MaxDistance: 500 57 | Pan2D: 0 58 | rolloffMode: 0 59 | BypassEffects: 0 60 | BypassListenerEffects: 0 61 | BypassReverbZones: 0 62 | rolloffCustomCurve: 63 | serializedVersion: 2 64 | m_Curve: 65 | - serializedVersion: 3 66 | time: 0 67 | value: 1 68 | inSlope: 0 69 | outSlope: 0 70 | tangentMode: 0 71 | weightedMode: 0 72 | inWeight: 0.33333334 73 | outWeight: 0.33333334 74 | - serializedVersion: 3 75 | time: 1 76 | value: 0 77 | inSlope: 0 78 | outSlope: 0 79 | tangentMode: 0 80 | weightedMode: 0 81 | inWeight: 0.33333334 82 | outWeight: 0.33333334 83 | m_PreInfinity: 2 84 | m_PostInfinity: 2 85 | m_RotationOrder: 4 86 | panLevelCustomCurve: 87 | serializedVersion: 2 88 | m_Curve: 89 | - serializedVersion: 3 90 | time: 0 91 | value: 0 92 | inSlope: 0 93 | outSlope: 0 94 | tangentMode: 0 95 | weightedMode: 0 96 | inWeight: 0.33333334 97 | outWeight: 0.33333334 98 | m_PreInfinity: 2 99 | m_PostInfinity: 2 100 | m_RotationOrder: 4 101 | spreadCustomCurve: 102 | serializedVersion: 2 103 | m_Curve: 104 | - serializedVersion: 3 105 | time: 0 106 | value: 0 107 | inSlope: 0 108 | outSlope: 0 109 | tangentMode: 0 110 | weightedMode: 0 111 | inWeight: 0.33333334 112 | outWeight: 0.33333334 113 | m_PreInfinity: 2 114 | m_PostInfinity: 2 115 | m_RotationOrder: 4 116 | reverbZoneMixCustomCurve: 117 | serializedVersion: 2 118 | m_Curve: 119 | - serializedVersion: 3 120 | time: 0 121 | value: 1 122 | inSlope: 0 123 | outSlope: 0 124 | tangentMode: 0 125 | weightedMode: 0 126 | inWeight: 0.33333334 127 | outWeight: 0.33333334 128 | m_PreInfinity: 2 129 | m_PostInfinity: 2 130 | m_RotationOrder: 4 131 | --- !u!1 &5453493278238917423 132 | GameObject: 133 | m_ObjectHideFlags: 0 134 | m_CorrespondingSourceObject: {fileID: 0} 135 | m_PrefabInstance: {fileID: 0} 136 | m_PrefabAsset: {fileID: 0} 137 | serializedVersion: 6 138 | m_Component: 139 | - component: {fileID: 5453493278238917420} 140 | m_Layer: 8 141 | m_Name: Camera Holder 142 | m_TagString: Untagged 143 | m_Icon: {fileID: 0} 144 | m_NavMeshLayer: 0 145 | m_StaticEditorFlags: 0 146 | m_IsActive: 1 147 | --- !u!4 &5453493278238917420 148 | Transform: 149 | m_ObjectHideFlags: 0 150 | m_CorrespondingSourceObject: {fileID: 0} 151 | m_PrefabInstance: {fileID: 0} 152 | m_PrefabAsset: {fileID: 0} 153 | m_GameObject: {fileID: 5453493278238917423} 154 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 155 | m_LocalPosition: {x: 0, y: 0.79999995, z: 0} 156 | m_LocalScale: {x: 1, y: 1, z: 1} 157 | m_ConstrainProportionsScale: 0 158 | m_Children: 159 | - {fileID: 5453493279221145104} 160 | m_Father: {fileID: 7787427698462517471} 161 | m_RootOrder: 0 162 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 163 | --- !u!1 &5453493279221145117 164 | GameObject: 165 | m_ObjectHideFlags: 0 166 | m_CorrespondingSourceObject: {fileID: 0} 167 | m_PrefabInstance: {fileID: 0} 168 | m_PrefabAsset: {fileID: 0} 169 | serializedVersion: 6 170 | m_Component: 171 | - component: {fileID: 5453493279221145104} 172 | - component: {fileID: 5453493279221145107} 173 | - component: {fileID: 5453493279221145106} 174 | - component: {fileID: 5453493279221145105} 175 | - component: {fileID: 5453493279221145110} 176 | m_Layer: 8 177 | m_Name: Main Camera 178 | m_TagString: MainCamera 179 | m_Icon: {fileID: 0} 180 | m_NavMeshLayer: 0 181 | m_StaticEditorFlags: 0 182 | m_IsActive: 1 183 | --- !u!4 &5453493279221145104 184 | Transform: 185 | m_ObjectHideFlags: 0 186 | m_CorrespondingSourceObject: {fileID: 0} 187 | m_PrefabInstance: {fileID: 0} 188 | m_PrefabAsset: {fileID: 0} 189 | m_GameObject: {fileID: 5453493279221145117} 190 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 191 | m_LocalPosition: {x: 0, y: 0, z: 0} 192 | m_LocalScale: {x: 1, y: 1, z: 1} 193 | m_ConstrainProportionsScale: 0 194 | m_Children: [] 195 | m_Father: {fileID: 5453493278238917420} 196 | m_RootOrder: 0 197 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 198 | --- !u!20 &5453493279221145107 199 | Camera: 200 | m_ObjectHideFlags: 0 201 | m_CorrespondingSourceObject: {fileID: 0} 202 | m_PrefabInstance: {fileID: 0} 203 | m_PrefabAsset: {fileID: 0} 204 | m_GameObject: {fileID: 5453493279221145117} 205 | m_Enabled: 1 206 | serializedVersion: 2 207 | m_ClearFlags: 1 208 | m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} 209 | m_projectionMatrixMode: 1 210 | m_GateFitMode: 2 211 | m_FOVAxisMode: 0 212 | m_SensorSize: {x: 36, y: 24} 213 | m_LensShift: {x: 0, y: 0} 214 | m_FocalLength: 50 215 | m_NormalizedViewPortRect: 216 | serializedVersion: 2 217 | x: 0 218 | y: 0 219 | width: 1 220 | height: 1 221 | near clip plane: 0.3 222 | far clip plane: 1000 223 | field of view: 60 224 | orthographic: 0 225 | orthographic size: 5 226 | m_Depth: -1 227 | m_CullingMask: 228 | serializedVersion: 2 229 | m_Bits: 4294967295 230 | m_RenderingPath: -1 231 | m_TargetTexture: {fileID: 0} 232 | m_TargetDisplay: 0 233 | m_TargetEye: 3 234 | m_HDR: 1 235 | m_AllowMSAA: 1 236 | m_AllowDynamicResolution: 0 237 | m_ForceIntoRT: 0 238 | m_OcclusionCulling: 1 239 | m_StereoConvergence: 10 240 | m_StereoSeparation: 0.022 241 | --- !u!81 &5453493279221145106 242 | AudioListener: 243 | m_ObjectHideFlags: 0 244 | m_CorrespondingSourceObject: {fileID: 0} 245 | m_PrefabInstance: {fileID: 0} 246 | m_PrefabAsset: {fileID: 0} 247 | m_GameObject: {fileID: 5453493279221145117} 248 | m_Enabled: 1 249 | --- !u!114 &5453493279221145105 250 | MonoBehaviour: 251 | m_ObjectHideFlags: 0 252 | m_CorrespondingSourceObject: {fileID: 0} 253 | m_PrefabInstance: {fileID: 0} 254 | m_PrefabAsset: {fileID: 0} 255 | m_GameObject: {fileID: 5453493279221145117} 256 | m_Enabled: 1 257 | m_EditorHideFlags: 0 258 | m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} 259 | m_Name: 260 | m_EditorClassIdentifier: 261 | m_RenderShadows: 1 262 | m_RequiresDepthTextureOption: 2 263 | m_RequiresOpaqueTextureOption: 2 264 | m_CameraType: 0 265 | m_Cameras: [] 266 | m_RendererIndex: -1 267 | m_VolumeLayerMask: 268 | serializedVersion: 2 269 | m_Bits: 1 270 | m_VolumeTrigger: {fileID: 0} 271 | m_VolumeFrameworkUpdateModeOption: 2 272 | m_RenderPostProcessing: 1 273 | m_Antialiasing: 0 274 | m_AntialiasingQuality: 2 275 | m_StopNaN: 0 276 | m_Dithering: 0 277 | m_ClearDepth: 1 278 | m_AllowXRRendering: 1 279 | m_RequiresDepthTexture: 0 280 | m_RequiresColorTexture: 0 281 | m_Version: 2 282 | --- !u!114 &5453493279221145110 283 | MonoBehaviour: 284 | m_ObjectHideFlags: 0 285 | m_CorrespondingSourceObject: {fileID: 0} 286 | m_PrefabInstance: {fileID: 0} 287 | m_PrefabAsset: {fileID: 0} 288 | m_GameObject: {fileID: 5453493279221145117} 289 | m_Enabled: 1 290 | m_EditorHideFlags: 0 291 | m_Script: {fileID: 11500000, guid: 3815b447c65dcca4f910bf16823b15cb, type: 3} 292 | m_Name: 293 | m_EditorClassIdentifier: 294 | _duration: 1 295 | _strengthMultiplier: 0.2 296 | _curve: 297 | serializedVersion: 2 298 | m_Curve: 299 | - serializedVersion: 3 300 | time: 0 301 | value: 0 302 | inSlope: 0 303 | outSlope: 0 304 | tangentMode: 0 305 | weightedMode: 0 306 | inWeight: 0 307 | outWeight: 0 308 | - serializedVersion: 3 309 | time: 0.2 310 | value: 0.25 311 | inSlope: -0.01 312 | outSlope: 0 313 | tangentMode: 0 314 | weightedMode: 0 315 | inWeight: -0.3 316 | outWeight: 0 317 | - serializedVersion: 3 318 | time: 1 319 | value: 0 320 | inSlope: 0 321 | outSlope: 0 322 | tangentMode: 0 323 | weightedMode: 0 324 | inWeight: 0 325 | outWeight: 0 326 | m_PreInfinity: 2 327 | m_PostInfinity: 2 328 | m_RotationOrder: 4 329 | IsShaking: 0 330 | --- !u!1 &7787427698462517458 331 | GameObject: 332 | m_ObjectHideFlags: 0 333 | m_CorrespondingSourceObject: {fileID: 0} 334 | m_PrefabInstance: {fileID: 0} 335 | m_PrefabAsset: {fileID: 0} 336 | serializedVersion: 6 337 | m_Component: 338 | - component: {fileID: 7787427698462517471} 339 | - component: {fileID: 7787427698462517470} 340 | - component: {fileID: 7787427698462517469} 341 | - component: {fileID: 7787427698462517468} 342 | - component: {fileID: 7787427698462517459} 343 | - component: {fileID: 7787427698462517456} 344 | - component: {fileID: 7787427698462517464} 345 | - component: {fileID: 8425700915552814015} 346 | m_Layer: 8 347 | m_Name: FPController-BuiltIn 348 | m_TagString: Untagged 349 | m_Icon: {fileID: -964228994112308473, guid: 0000000000000000d000000000000000, type: 0} 350 | m_NavMeshLayer: 0 351 | m_StaticEditorFlags: 0 352 | m_IsActive: 1 353 | --- !u!4 &7787427698462517471 354 | Transform: 355 | m_ObjectHideFlags: 0 356 | m_CorrespondingSourceObject: {fileID: 0} 357 | m_PrefabInstance: {fileID: 0} 358 | m_PrefabAsset: {fileID: 0} 359 | m_GameObject: {fileID: 7787427698462517458} 360 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 361 | m_LocalPosition: {x: 0, y: 1, z: 0} 362 | m_LocalScale: {x: 1, y: 1, z: 1} 363 | m_ConstrainProportionsScale: 0 364 | m_Children: 365 | - {fileID: 5453493278238917420} 366 | - {fileID: 8390729986721986591} 367 | m_Father: {fileID: 0} 368 | m_RootOrder: 0 369 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 370 | --- !u!114 &7787427698462517470 371 | MonoBehaviour: 372 | m_ObjectHideFlags: 0 373 | m_CorrespondingSourceObject: {fileID: 0} 374 | m_PrefabInstance: {fileID: 0} 375 | m_PrefabAsset: {fileID: 0} 376 | m_GameObject: {fileID: 7787427698462517458} 377 | m_Enabled: 1 378 | m_EditorHideFlags: 0 379 | m_Script: {fileID: 11500000, guid: dcd80274256d993408e1364716168d0d, type: 3} 380 | m_Name: 381 | m_EditorClassIdentifier: 382 | MovementInput: {x: 0, y: 0} 383 | LookInput: {x: 0, y: 0} 384 | SprintInput: 0 385 | JumpInput: 0 386 | --- !u!114 &7787427698462517469 387 | MonoBehaviour: 388 | m_ObjectHideFlags: 0 389 | m_CorrespondingSourceObject: {fileID: 0} 390 | m_PrefabInstance: {fileID: 0} 391 | m_PrefabAsset: {fileID: 0} 392 | m_GameObject: {fileID: 7787427698462517458} 393 | m_Enabled: 1 394 | m_EditorHideFlags: 0 395 | m_Script: {fileID: 11500000, guid: 116d5b2b2ac5d3f48a8db4260dbab72f, type: 3} 396 | m_Name: 397 | m_EditorClassIdentifier: 398 | _movementSpeed: 4 399 | _acceleration: 15 400 | _midAirControlMultiplier: 0.4 401 | _slopeSlideMultiplier: -4 402 | _mass: 1 403 | _gravityMultiplier: 1 404 | _cameraHolderTransform: {fileID: 5453493278238917420} 405 | _mouseSensitivity: 0.05 406 | _maxCameraY: 90 407 | --- !u!114 &7787427698462517468 408 | MonoBehaviour: 409 | m_ObjectHideFlags: 0 410 | m_CorrespondingSourceObject: {fileID: 0} 411 | m_PrefabInstance: {fileID: 0} 412 | m_PrefabAsset: {fileID: 0} 413 | m_GameObject: {fileID: 7787427698462517458} 414 | m_Enabled: 1 415 | m_EditorHideFlags: 0 416 | m_Script: {fileID: 11500000, guid: f34a6134550dca84191873450eeb1eb3, type: 3} 417 | m_Name: 418 | m_EditorClassIdentifier: 419 | _speedMultiplier: 1.5 420 | --- !u!114 &7787427698462517459 421 | MonoBehaviour: 422 | m_ObjectHideFlags: 0 423 | m_CorrespondingSourceObject: {fileID: 0} 424 | m_PrefabInstance: {fileID: 0} 425 | m_PrefabAsset: {fileID: 0} 426 | m_GameObject: {fileID: 7787427698462517458} 427 | m_Enabled: 1 428 | m_EditorHideFlags: 0 429 | m_Script: {fileID: 11500000, guid: 37614c034b070c0428fbda7407508d46, type: 3} 430 | m_Name: 431 | m_EditorClassIdentifier: 432 | _jumpHeight: 1 433 | _jumpBufferTime: 0.05 434 | _jumpGroundGraceTime: 0.2 435 | --- !u!114 &7787427698462517456 436 | MonoBehaviour: 437 | m_ObjectHideFlags: 0 438 | m_CorrespondingSourceObject: {fileID: 0} 439 | m_PrefabInstance: {fileID: 0} 440 | m_PrefabAsset: {fileID: 0} 441 | m_GameObject: {fileID: 7787427698462517458} 442 | m_Enabled: 1 443 | m_EditorHideFlags: 0 444 | m_Script: {fileID: 11500000, guid: b218d6d4137ecfb47b14cc38207afff5, type: 3} 445 | m_Name: 446 | m_EditorClassIdentifier: 447 | _frequency: 15 448 | _amplitude: 0.02 449 | _sprintMultiplier: 1.3 450 | --- !u!143 &7787427698462517464 451 | CharacterController: 452 | m_ObjectHideFlags: 0 453 | m_CorrespondingSourceObject: {fileID: 0} 454 | m_PrefabInstance: {fileID: 0} 455 | m_PrefabAsset: {fileID: 0} 456 | m_GameObject: {fileID: 7787427698462517458} 457 | m_Material: {fileID: 0} 458 | m_IsTrigger: 0 459 | m_Enabled: 1 460 | serializedVersion: 2 461 | m_Height: 2 462 | m_Radius: 0.5 463 | m_SlopeLimit: 50 464 | m_StepOffset: 0.3 465 | m_SkinWidth: 0.0001 466 | m_MinMoveDistance: 0 467 | m_Center: {x: 0, y: 0, z: 0} 468 | --- !u!114 &8425700915552814015 469 | MonoBehaviour: 470 | m_ObjectHideFlags: 0 471 | m_CorrespondingSourceObject: {fileID: 0} 472 | m_PrefabInstance: {fileID: 0} 473 | m_PrefabAsset: {fileID: 0} 474 | m_GameObject: {fileID: 7787427698462517458} 475 | m_Enabled: 1 476 | m_EditorHideFlags: 0 477 | m_Script: {fileID: 11500000, guid: 6e173bde8b130a142b6bc377c3018d9b, type: 3} 478 | m_Name: 479 | m_EditorClassIdentifier: 480 | _audioSource: {fileID: 690595866509419865} 481 | _playFootsteps: 1 482 | _minRequiredSpeed: 3 483 | _walkStepInterval: 0.8 484 | _sprintStepInterval: 0.5 485 | _sounds: [] 486 | --------------------------------------------------------------------------------