├── .gitignore ├── Assets ├── Resources.meta ├── Resources │ ├── Crosshair.png │ ├── Crosshair.png.meta │ ├── Prefabs.meta │ ├── Prefabs │ │ ├── Player.prefab │ │ └── Player.prefab.meta │ ├── SFX.meta │ └── SFX │ │ ├── Footstep1.wav │ │ ├── Footstep1.wav.meta │ │ ├── Footstep3.wav │ │ ├── Footstep3.wav.meta │ │ ├── Footstep4.wav │ │ ├── Footstep4.wav.meta │ │ ├── Jump.wav │ │ ├── Jump.wav.meta │ │ ├── Land.wav │ │ └── Land.wav.meta ├── Scenes.meta ├── Scenes │ ├── TestScene.unity │ └── TestScene.unity.meta ├── Scripts.meta └── Scripts │ ├── FpsController.cs │ ├── FpsController.cs.meta │ ├── GuiDraw.cs │ └── GuiDraw.cs.meta ├── FpsController.unitypackage ├── LICENSE ├── Packages └── manifest.json ├── ProjectSettings ├── AudioManager.asset ├── ClusterInputManager.asset ├── DynamicsManager.asset ├── EditorBuildSettings.asset ├── EditorSettings.asset ├── GraphicsSettings.asset ├── InputManager.asset ├── MemorySettings.asset ├── NavMeshAreas.asset ├── NetworkManager.asset ├── PackageManagerSettings.asset ├── Physics2DSettings.asset ├── PresetManager.asset ├── ProjectSettings.asset ├── ProjectVersion.txt ├── QualitySettings.asset ├── SceneTemplateSettings.json ├── TagManager.asset ├── TimeManager.asset ├── UnityConnectSettings.asset ├── VFXManager.asset └── VersionControlSettings.asset └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | Library 3 | /Temp 4 | /Build 5 | CoreCompileInputs.cache 6 | Logs 7 | obj 8 | .vsconfig 9 | *.csproj 10 | *.sln 11 | *.config 12 | UserSettings 13 | packages-lock.json 14 | BillingMode.json 15 | BillingMode.json.meta 16 | -------------------------------------------------------------------------------- /Assets/Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1c9ab856988c4584a8f3db570b4a1e5a 3 | folderAsset: yes 4 | timeCreated: 1497807515 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/Resources/Crosshair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/Assets/Resources/Crosshair.png -------------------------------------------------------------------------------- /Assets/Resources/Crosshair.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a44bf4433c320bb439608b0eaec1c054 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 16 38 | mipBias: 0 39 | wrapU: 0 40 | wrapV: 0 41 | wrapW: 0 42 | nPOTScale: 0 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 1 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 1 55 | spriteTessellationDetail: -1 56 | textureType: 8 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 1 66 | platformSettings: 67 | - serializedVersion: 3 68 | buildTarget: DefaultTexturePlatform 69 | maxTextureSize: 32 70 | resizeAlgorithm: 0 71 | textureFormat: -1 72 | textureCompression: 1 73 | compressionQuality: 50 74 | crunchedCompression: 0 75 | allowsAlphaSplitting: 0 76 | overridden: 0 77 | androidETC2FallbackOverride: 0 78 | forceMaximumCompressionQuality_BC6H_BC7: 1 79 | - serializedVersion: 3 80 | buildTarget: Standalone 81 | maxTextureSize: 32 82 | resizeAlgorithm: 0 83 | textureFormat: -1 84 | textureCompression: 1 85 | compressionQuality: 50 86 | crunchedCompression: 0 87 | allowsAlphaSplitting: 0 88 | overridden: 0 89 | androidETC2FallbackOverride: 0 90 | forceMaximumCompressionQuality_BC6H_BC7: 1 91 | - serializedVersion: 3 92 | buildTarget: Android 93 | maxTextureSize: 32 94 | resizeAlgorithm: 0 95 | textureFormat: -1 96 | textureCompression: 1 97 | compressionQuality: 50 98 | crunchedCompression: 0 99 | allowsAlphaSplitting: 0 100 | overridden: 0 101 | androidETC2FallbackOverride: 0 102 | forceMaximumCompressionQuality_BC6H_BC7: 1 103 | spriteSheet: 104 | serializedVersion: 2 105 | sprites: [] 106 | outline: [] 107 | physicsShape: [] 108 | bones: [] 109 | spriteID: 5e97eb03825dee720800000000000000 110 | internalID: 0 111 | vertices: [] 112 | indices: 113 | edges: [] 114 | weights: [] 115 | secondaryTextures: [] 116 | nameFileIdTable: {} 117 | spritePackingTag: 118 | pSDRemoveMatte: 0 119 | pSDShowRemoveMatteOption: 0 120 | userData: 121 | assetBundleName: 122 | assetBundleVariant: 123 | -------------------------------------------------------------------------------- /Assets/Resources/Prefabs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8830ff8f41a04cd40b32632b1a88822d 3 | folderAsset: yes 4 | timeCreated: 1499024669 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/Resources/Prefabs/Player.prefab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/Assets/Resources/Prefabs/Player.prefab -------------------------------------------------------------------------------- /Assets/Resources/Prefabs/Player.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 680a11299af23874bbc0b94457026c5e 3 | timeCreated: 1512220078 4 | licenseType: Pro 5 | NativeFormatImporter: 6 | mainObjectFileID: 100100000 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/Resources/SFX.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c4b5e53d8fefa0848b273ad9c97d5b4c 3 | folderAsset: yes 4 | timeCreated: 1525091452 5 | licenseType: Pro 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Assets/Resources/SFX/Footstep1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/Assets/Resources/SFX/Footstep1.wav -------------------------------------------------------------------------------- /Assets/Resources/SFX/Footstep1.wav.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: da9bb2a8b8346ad4cbb676fb30c1059f 3 | timeCreated: 1524508501 4 | licenseType: Pro 5 | AudioImporter: 6 | externalObjects: {} 7 | serializedVersion: 6 8 | defaultSettings: 9 | loadType: 0 10 | sampleRateSetting: 0 11 | sampleRateOverride: 44100 12 | compressionFormat: 1 13 | quality: 1 14 | conversionMode: 0 15 | platformSettingOverrides: {} 16 | forceToMono: 0 17 | normalize: 1 18 | preloadAudioData: 1 19 | loadInBackground: 0 20 | ambisonic: 0 21 | 3D: 1 22 | userData: 23 | assetBundleName: 24 | assetBundleVariant: 25 | -------------------------------------------------------------------------------- /Assets/Resources/SFX/Footstep3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/Assets/Resources/SFX/Footstep3.wav -------------------------------------------------------------------------------- /Assets/Resources/SFX/Footstep3.wav.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 307142cd55fbf46419949d43caf77ae8 3 | timeCreated: 1524508500 4 | licenseType: Pro 5 | AudioImporter: 6 | externalObjects: {} 7 | serializedVersion: 6 8 | defaultSettings: 9 | loadType: 0 10 | sampleRateSetting: 0 11 | sampleRateOverride: 44100 12 | compressionFormat: 1 13 | quality: 1 14 | conversionMode: 0 15 | platformSettingOverrides: {} 16 | forceToMono: 0 17 | normalize: 1 18 | preloadAudioData: 1 19 | loadInBackground: 0 20 | ambisonic: 0 21 | 3D: 1 22 | userData: 23 | assetBundleName: 24 | assetBundleVariant: 25 | -------------------------------------------------------------------------------- /Assets/Resources/SFX/Footstep4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/Assets/Resources/SFX/Footstep4.wav -------------------------------------------------------------------------------- /Assets/Resources/SFX/Footstep4.wav.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3e8975e860d60d34a8776aa49b0f7a1f 3 | timeCreated: 1524508500 4 | licenseType: Pro 5 | AudioImporter: 6 | externalObjects: {} 7 | serializedVersion: 6 8 | defaultSettings: 9 | loadType: 0 10 | sampleRateSetting: 0 11 | sampleRateOverride: 44100 12 | compressionFormat: 1 13 | quality: 1 14 | conversionMode: 0 15 | platformSettingOverrides: {} 16 | forceToMono: 0 17 | normalize: 1 18 | preloadAudioData: 1 19 | loadInBackground: 0 20 | ambisonic: 0 21 | 3D: 1 22 | userData: 23 | assetBundleName: 24 | assetBundleVariant: 25 | -------------------------------------------------------------------------------- /Assets/Resources/SFX/Jump.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/Assets/Resources/SFX/Jump.wav -------------------------------------------------------------------------------- /Assets/Resources/SFX/Jump.wav.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: df9999788f5c7fc4393dac671e28fc02 3 | timeCreated: 1524512010 4 | licenseType: Pro 5 | AudioImporter: 6 | externalObjects: {} 7 | serializedVersion: 6 8 | defaultSettings: 9 | loadType: 0 10 | sampleRateSetting: 0 11 | sampleRateOverride: 44100 12 | compressionFormat: 1 13 | quality: 1 14 | conversionMode: 0 15 | platformSettingOverrides: {} 16 | forceToMono: 0 17 | normalize: 1 18 | preloadAudioData: 1 19 | loadInBackground: 0 20 | ambisonic: 0 21 | 3D: 1 22 | userData: 23 | assetBundleName: 24 | assetBundleVariant: 25 | -------------------------------------------------------------------------------- /Assets/Resources/SFX/Land.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/Assets/Resources/SFX/Land.wav -------------------------------------------------------------------------------- /Assets/Resources/SFX/Land.wav.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 37793e39afabb914fae6b01ba884bc0b 3 | timeCreated: 1524512010 4 | licenseType: Pro 5 | AudioImporter: 6 | externalObjects: {} 7 | serializedVersion: 6 8 | defaultSettings: 9 | loadType: 0 10 | sampleRateSetting: 0 11 | sampleRateOverride: 44100 12 | compressionFormat: 1 13 | quality: 1 14 | conversionMode: 0 15 | platformSettingOverrides: {} 16 | forceToMono: 0 17 | normalize: 1 18 | preloadAudioData: 1 19 | loadInBackground: 0 20 | ambisonic: 0 21 | 3D: 1 22 | userData: 23 | assetBundleName: 24 | assetBundleVariant: 25 | -------------------------------------------------------------------------------- /Assets/Scenes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 05266d0c5222bd14496c1eb41bbfce35 3 | folderAsset: yes 4 | timeCreated: 1497782827 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/Scenes/TestScene.unity: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/Assets/Scenes/TestScene.unity -------------------------------------------------------------------------------- /Assets/Scenes/TestScene.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f434e6fc8e255934184b04b6c6b4734e 3 | timeCreated: 1497782827 4 | licenseType: Pro 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8e0fac89ae6402648bb892e5fea3cc20 3 | folderAsset: yes 4 | timeCreated: 1497781807 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/Scripts/FpsController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | /// 5 | /// Q3-based first person controller 6 | /// 7 | public class FpsController : MonoBehaviour 8 | { 9 | #region Drag Drop 10 | [Header("Components")] 11 | [SerializeField] 12 | private Transform _camTransform = default; 13 | 14 | // Collision resolving is done with respect to this volume 15 | [SerializeField] 16 | private CapsuleCollider _collisionVolume = default; 17 | 18 | // Collision will not happend with these layers 19 | // One of them has to be this controller's own layer 20 | [SerializeField] 21 | private LayerMask _excludedLayers = default; 22 | 23 | [SerializeField] 24 | private bool _debugInfo = default; 25 | 26 | [SerializeField] 27 | private List _groundedRayPositions = default; 28 | #endregion 29 | 30 | #region Movement Parameters 31 | [Header("Movement parameters")] 32 | // The controller can collide with colliders within this radius 33 | [SerializeField] 34 | private float _radius = 2f; 35 | 36 | // Ad-hoc approach to make the controller accelerate faster 37 | [SerializeField] 38 | private float _groundAccelerationCoeff = 500.0f; 39 | 40 | // How fast the controller accelerates while it's not grounded 41 | [SerializeField] 42 | private float _airAccelCoeff = 1f; 43 | 44 | // Air deceleration occurs when the player gives an input that's not aligned with the current velocity 45 | [SerializeField] 46 | private float _airDecelCoeff = 1.5f; 47 | 48 | // Along a dimension, we can't go faster than this 49 | // This dimension is relative to the controller, not global 50 | // Meaning that "max speend along X" means "max speed along 'right side' of the controller" 51 | [SerializeField] 52 | private float _maxSpeedAlongOneDimension = 8f; 53 | 54 | // How fast the controller decelerates on the grounded 55 | [SerializeField] 56 | private float _friction = 15; 57 | 58 | // Stop if under this speed 59 | [SerializeField] 60 | private float _frictionSpeedThreshold = 0.5f; 61 | 62 | // Push force given when jumping 63 | [SerializeField] 64 | private float _jumpStrength = 8f; 65 | 66 | // yeah... 67 | [SerializeField] 68 | private float _gravityAmount = 24f; 69 | 70 | // How precise the controller can change direction while not grounded 71 | [SerializeField] 72 | private float _airControlPrecision = 16f; 73 | 74 | // When moving only forward, increase air control dramatically 75 | [SerializeField] 76 | private float _airControlAdditionForward = 8f; 77 | 78 | // Keyboard and mouse input are enabled 79 | [SerializeField] 80 | private bool _canControl = true; 81 | 82 | // Maybe you wanna make a Wolf3D clone? 83 | [SerializeField] 84 | private bool _verticalLookEnabled = true; 85 | 86 | [SerializeField] 87 | private bool _jumpEnabled = true; 88 | 89 | [Header("Footsteps")] 90 | [SerializeField] 91 | private float _distancePerFootstep = 3f; 92 | 93 | [SerializeField] 94 | private AudioSource _audioSource = default; 95 | 96 | [SerializeField] 97 | private AudioClip[] _footstepClips = default; 98 | 99 | [SerializeField] 100 | private AudioClip _jumpClip = default; 101 | 102 | [SerializeField] 103 | private AudioClip _landClip = default; 104 | 105 | private List _shuffledFootstepClips; 106 | private Vector3 _prevPos; 107 | private float _distanceCovered; 108 | #endregion 109 | 110 | #region Fields 111 | // The real velocity of this controller 112 | private Vector3 _velocity; 113 | 114 | // Raw input taken with GetAxisRaw() 115 | private Vector3 _moveInput; 116 | 117 | // Vertical look 118 | private float _pitch = 0; // We keep track of this value since we want to clamp it 119 | private const float Sensitivity = 150; 120 | 121 | // Caching... 122 | private readonly Collider[] _overlappingColliders = new Collider[10]; // Hope no more is needed 123 | private Transform _ghostJumpRayPosition; 124 | 125 | // Some information to persist 126 | private bool _isGroundedInPrevFrame; 127 | private bool _isGonnaJump; 128 | private Vector3 _wishDirDebug; 129 | #endregion 130 | 131 | private void Start() 132 | { 133 | Application.targetFrameRate = 60; // My laptop is shitty and burn itself to death if not for this 134 | _ghostJumpRayPosition = _groundedRayPositions[_groundedRayPositions.Count - 1]; 135 | 136 | _shuffledFootstepClips = new List(_footstepClips); 137 | } 138 | 139 | // Only for debug drawing 140 | private void OnGUI() 141 | { 142 | if (!_debugInfo) 143 | { 144 | return; 145 | } 146 | 147 | // Print current horizontal speed 148 | Vector3 ups = _velocity; 149 | ups.y = 0; 150 | GUI.Box(new Rect(Screen.width / 2f - 50, Screen.height / 2f + 50, 100, 40), 151 | (Mathf.Round(ups.magnitude * 100) / 100).ToString()); 152 | 153 | // Draw horizontal speed as a line 154 | Vector2 mid = new(Screen.width / 2, Screen.height / 2); // Should remain integer division, otherwise GUI drawing gets screwed up 155 | Vector3 v = _camTransform.InverseTransformDirectionHorizontal(_velocity) * (_velocity.ToHorizontal().magnitude * 10f); 156 | if (v.ToHorizontal().magnitude > 0.0001) 157 | { 158 | GuiDraw.DrawLine(mid, mid + Vector2.up * -v.z + Vector2.right * v.x, Color.red, 3f); 159 | } 160 | 161 | // Draw input direction 162 | Vector3 w = _camTransform.InverseTransformDirectionHorizontal(_wishDirDebug) * 100; 163 | if (w.magnitude > 0.001) 164 | { 165 | GuiDraw.DrawLine(mid, mid + Vector2.up * -w.z + Vector2.right * w.x, Color.blue, 2f); 166 | } 167 | } 168 | 169 | private void Update() 170 | { 171 | Cursor.lockState = CursorLockMode.Locked; // Keep doing this. We don't want cursor anywhere just yet 172 | 173 | float dt = Time.deltaTime; 174 | 175 | if (_canControl) 176 | { 177 | // We use GetAxisRaw, since we need it to feel as responsive as possible 178 | _moveInput = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized; 179 | 180 | if (_jumpEnabled && Input.GetKeyDown(KeyCode.Space) && !_isGonnaJump) 181 | { 182 | _isGonnaJump = true; 183 | } 184 | else if (Input.GetKeyUp(KeyCode.Space)) 185 | { 186 | _isGonnaJump = false; 187 | } 188 | 189 | // Mouse look 190 | if (_verticalLookEnabled) 191 | { 192 | _pitch += Input.GetAxis("Mouse Y") * -Sensitivity * dt; 193 | _pitch = Mathf.Clamp(_pitch, -89, 89); 194 | _camTransform.localRotation = Quaternion.Euler(Vector3.right * _pitch); 195 | } 196 | transform.rotation *= Quaternion.Euler(Input.GetAxis("Mouse X") * Sensitivity * dt * Vector3.up); 197 | } 198 | 199 | // MOVEMENT 200 | Vector3 wishDir = _camTransform.TransformDirectionHorizontal(_moveInput); // We want to go in this direction 201 | _wishDirDebug = wishDir.ToHorizontal(); 202 | 203 | bool isGrounded = IsGrounded(out Vector3 groundNormal); 204 | 205 | UpdateFootsteps(_isGonnaJump, isGrounded, isGrounded && !_isGroundedInPrevFrame); 206 | 207 | if (isGrounded) // Ground move 208 | { 209 | // Don't apply friction if just landed or about to jump 210 | if (_isGroundedInPrevFrame && !_isGonnaJump) 211 | { 212 | ApplyFriction(ref _velocity, dt); 213 | } 214 | 215 | Accelerate(ref _velocity, wishDir, _groundAccelerationCoeff, dt); 216 | 217 | // Crop up horizontal velocity component 218 | _velocity = Vector3.ProjectOnPlane(_velocity, groundNormal); 219 | if (_isGonnaJump) 220 | { 221 | // Jump away 222 | _velocity += -Gravity.Down * _jumpStrength; 223 | } 224 | } 225 | else // Air move 226 | { 227 | // If the input doesn't have the same facing with the current velocity 228 | // then slow down instead of speeding up 229 | float coeff = Vector3.Dot(_velocity, wishDir) > 0 ? _airAccelCoeff : _airDecelCoeff; 230 | 231 | Accelerate(ref _velocity, wishDir, coeff, dt); 232 | 233 | if (Mathf.Abs(_moveInput.z) > 0.0001) // Pure side velocity doesn't allow air control 234 | { 235 | ApplyAirControl(ref _velocity, wishDir, dt); 236 | } 237 | 238 | _velocity += Gravity.Down * (_gravityAmount * dt); 239 | } 240 | 241 | Vector3 displacement = _velocity * dt; 242 | 243 | // If we're moving too fast, make sure we don't hollow through any collider 244 | if (displacement.magnitude > _collisionVolume.radius) 245 | { 246 | ClampDisplacement(ref _velocity, ref displacement, transform.position); 247 | } 248 | 249 | transform.position += displacement; 250 | 251 | Vector3 collisionDisplacement = ResolveCollisions(ref _velocity); 252 | 253 | transform.position += collisionDisplacement; 254 | _isGroundedInPrevFrame = isGrounded; 255 | 256 | 257 | // Testing 258 | //if (Input.GetKeyDown(KeyCode.G)) 259 | //{ 260 | // Gravity.Set(Vector3.right); 261 | // _transform.rotation = Quaternion.LookRotation(Gravity.Forward, -Gravity.Down); 262 | //} 263 | } 264 | 265 | private void Accelerate(ref Vector3 playerVelocity, Vector3 accelDir, float accelCoeff, float dt) 266 | { 267 | // How much speed we already have in the direction we want to speed up 268 | float projSpeed = Vector3.Dot(playerVelocity, accelDir); 269 | 270 | // How much speed we need to add (in that direction) to reach max speed 271 | float addSpeed = _maxSpeedAlongOneDimension - projSpeed; 272 | if (addSpeed <= 0) 273 | { 274 | return; 275 | } 276 | 277 | // How much we are gonna increase our speed 278 | // maxSpeed * dt => the real deal. a = v / t 279 | // accelCoeff => ad hoc approach to make it feel better 280 | float accelAmount = accelCoeff * _maxSpeedAlongOneDimension * dt; 281 | 282 | // If we are accelerating more than in a way that we exceed maxSpeedInOneDimension, crop it to max 283 | if (accelAmount > addSpeed) 284 | { 285 | accelAmount = addSpeed; 286 | } 287 | 288 | playerVelocity += accelDir * accelAmount; // Magic happens here 289 | } 290 | 291 | private void ApplyFriction(ref Vector3 playerVelocity, float dt) 292 | { 293 | float speed = playerVelocity.magnitude; 294 | if (speed <= 0.00001f) 295 | { 296 | return; 297 | } 298 | 299 | float downLimit = Mathf.Max(speed, _frictionSpeedThreshold); // Don't drop below treshold 300 | float dropAmount = speed - (downLimit * _friction * dt); 301 | if (dropAmount < 0) 302 | { 303 | dropAmount = 0; 304 | } 305 | 306 | playerVelocity *= dropAmount / speed; // Reduce the velocity by a certain percent 307 | } 308 | 309 | private void ApplyAirControl(ref Vector3 playerVelocity, Vector3 accelDir, float dt) 310 | { 311 | // This only happens in the horizontal plane 312 | // TODO: Verify that these work with various gravity values 313 | Vector3 playerDirHorz = playerVelocity.ToHorizontal().normalized; 314 | float playerSpeedHorz = playerVelocity.ToHorizontal().magnitude; 315 | 316 | float dot = Vector3.Dot(playerDirHorz, accelDir); 317 | if (dot > 0) 318 | { 319 | float k = _airControlPrecision * dot * dot * dt; 320 | 321 | // CPMA thingy: 322 | // If we want pure forward movement, we have much more air control 323 | bool isPureForward = Mathf.Abs(_moveInput.x) < 0.0001f && Mathf.Abs(_moveInput.z) > 0; 324 | if (isPureForward) 325 | { 326 | k *= _airControlAdditionForward; 327 | } 328 | 329 | // A little bit closer to accelDir 330 | playerDirHorz = playerDirHorz * playerSpeedHorz + accelDir * k; 331 | playerDirHorz.Normalize(); 332 | 333 | // Assign new direction, without touching the vertical speed 334 | playerVelocity = (playerDirHorz * playerSpeedHorz).ToHorizontal() + Gravity.Up * playerVelocity.VerticalComponent(); 335 | } 336 | 337 | } 338 | 339 | // Calculates the displacement required in order not to be in a world collider 340 | private Vector3 ResolveCollisions(ref Vector3 playerVelocity) 341 | { 342 | // Get nearby colliders 343 | Physics.OverlapSphereNonAlloc(transform.position, _radius + 0.1f, 344 | _overlappingColliders, ~_excludedLayers); 345 | 346 | Vector3 totalDisplacement = Vector3.zero; 347 | HashSet checkedColliderIndices = new(); 348 | 349 | // If the player is intersecting with that environment collider, separate them 350 | for (int i = 0; i < _overlappingColliders.Length; i++) 351 | { 352 | // Two player colliders shouldn't resolve collision with the same environment collider 353 | if (checkedColliderIndices.Contains(i)) 354 | { 355 | continue; 356 | } 357 | 358 | Collider envColl = _overlappingColliders[i]; 359 | 360 | // Skip empty slots 361 | if (envColl == null) 362 | { 363 | continue; 364 | } 365 | 366 | if (Physics.ComputePenetration( 367 | _collisionVolume, _collisionVolume.transform.position, _collisionVolume.transform.rotation, 368 | envColl, envColl.transform.position, envColl.transform.rotation, 369 | out Vector3 collisionNormal, out float collisionDistance)) 370 | { 371 | // Ignore very small penetrations 372 | // Required for standing still on slopes 373 | // ... still far from perfect though 374 | if (collisionDistance < 0.015f) 375 | { 376 | continue; 377 | } 378 | 379 | checkedColliderIndices.Add(i); 380 | 381 | // Get outta that collider! 382 | totalDisplacement += collisionNormal * collisionDistance; 383 | 384 | // Crop down the velocity component which is in the direction of penetration 385 | playerVelocity -= Vector3.Project(playerVelocity, collisionNormal); 386 | } 387 | } 388 | 389 | // It's better to be in a clean state in the next resolve call 390 | for (int i = 0; i < _overlappingColliders.Length; i++) 391 | { 392 | _overlappingColliders[i] = null; 393 | } 394 | 395 | return totalDisplacement; 396 | } 397 | 398 | // If one of the rays hit, we're considered to be grounded 399 | private bool IsGrounded(out Vector3 groundNormal) 400 | { 401 | groundNormal = -Gravity.Down; 402 | 403 | bool isGrounded = false; 404 | foreach (Transform t in _groundedRayPositions) 405 | { 406 | // The last one is reserved for ghost jumps 407 | // Don't check that one if already on the ground 408 | if (t == _ghostJumpRayPosition && isGrounded) 409 | { 410 | continue; 411 | } 412 | 413 | if (Physics.Raycast(t.position, Gravity.Down, out RaycastHit hit, 0.51f, ~_excludedLayers)) 414 | { 415 | groundNormal = hit.normal; 416 | isGrounded = true; 417 | } 418 | } 419 | 420 | return isGrounded; 421 | } 422 | 423 | // If there's something between the current position and the next, clamp displacement 424 | private void ClampDisplacement(ref Vector3 playerVelocity, ref Vector3 displacement, Vector3 playerPosition) 425 | { 426 | if (Physics.Raycast(playerPosition, playerVelocity.normalized, out RaycastHit hit, displacement.magnitude, ~_excludedLayers)) 427 | { 428 | displacement = hit.point - playerPosition; 429 | } 430 | } 431 | 432 | private void UpdateFootsteps(bool isGonnaJump, bool isGrounded, bool isLandedThisFrame) 433 | { 434 | if (_distanceCovered > _distancePerFootstep) 435 | { 436 | _distanceCovered = 0; 437 | 438 | _audioSource.PlayOneShot(_shuffledFootstepClips[0]); 439 | _shuffledFootstepClips.Shuffle(); 440 | } 441 | 442 | if (isGonnaJump && isGrounded) 443 | { 444 | _audioSource.PlayOneShot(_jumpClip); 445 | } 446 | 447 | if (isLandedThisFrame && !isGonnaJump) 448 | { 449 | _audioSource.PlayOneShot(_landClip); 450 | } 451 | 452 | if (isGrounded) 453 | { 454 | _distanceCovered += Vector3.Distance(_prevPos.ToHorizontal(), transform.position.ToHorizontal()); 455 | } 456 | _prevPos = transform.position; 457 | } 458 | 459 | // Handy when testing 460 | public void ResetAt(Transform t) 461 | { 462 | transform.position = t.position + Vector3.up * 0.5f; 463 | _camTransform.position = transform.position; 464 | _velocity = t.TransformDirection(Vector3.forward); 465 | } 466 | } 467 | 468 | public static class Gravity 469 | { 470 | public static Vector3 Down { get; private set; } 471 | public static Vector3 Forward { get; private set; } 472 | public static Vector3 Up { get { return -Down; } } 473 | 474 | static Gravity() 475 | { 476 | Down = Vector3.down; 477 | Forward = Vector3.forward; 478 | } 479 | 480 | public static void Set(Vector3 down) 481 | { 482 | // Gravity will rotate around this axis with this amount 483 | Vector3 axis = Vector3.Cross(Down, down); 484 | float angle = Vector3.Angle(Down, down); 485 | 486 | Down = down; 487 | Forward = Quaternion.AngleAxis(angle, axis) * Forward; 488 | } 489 | } 490 | 491 | public static class FpsControllerExtensions 492 | { 493 | 494 | public static Vector3 ToHorizontal(this Vector3 v) 495 | { 496 | return Vector3.ProjectOnPlane(v, Gravity.Down); 497 | } 498 | 499 | public static float VerticalComponent(this Vector3 v) 500 | { 501 | return Vector3.Dot(v, Gravity.Up); 502 | } 503 | 504 | public static Vector3 TransformDirectionHorizontal(this Transform t, Vector3 v) 505 | { 506 | return t.TransformDirection(v).ToHorizontal().normalized; 507 | } 508 | 509 | public static Vector3 InverseTransformDirectionHorizontal(this Transform t, Vector3 v) 510 | { 511 | return t.InverseTransformDirection(v).ToHorizontal().normalized; 512 | } 513 | 514 | public static void Shuffle(this IList list) 515 | { 516 | int n = list.Count; 517 | while (n > 1) 518 | { 519 | n--; 520 | int k = Random.Range(0, n + 1); 521 | (list[n], list[k]) = (list[k], list[n]); // Swap 522 | } 523 | } 524 | 525 | } 526 | 527 | -------------------------------------------------------------------------------- /Assets/Scripts/FpsController.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d51bdb5febf4bac42bbc2f1b4af1ca37 3 | timeCreated: 1497783531 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/Scripts/GuiDraw.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | // Taken from http://wiki.unity3d.com/index.php?title=DrawLine (the link is dead now) 4 | public class GuiDraw 5 | { 6 | //**************************************************************************************************** 7 | // static function DrawLine(rect : Rect) : void 8 | // static function DrawLine(rect : Rect, color : Color) : void 9 | // static function DrawLine(rect : Rect, width : float) : void 10 | // static function DrawLine(rect : Rect, color : Color, width : float) : void 11 | // static function DrawLine(Vector2 pointA, Vector2 pointB) : void 12 | // static function DrawLine(Vector2 pointA, Vector2 pointB, color : Color) : void 13 | // static function DrawLine(Vector2 pointA, Vector2 pointB, width : float) : void 14 | // static function DrawLine(Vector2 pointA, Vector2 pointB, color : Color, width : float) : void 15 | // 16 | // Draws a GUI line on the screen. 17 | // 18 | // DrawLine makes up for the severe lack of 2D line rendering in the Unity runtime GUI system. 19 | // This function works by drawing a 1x1 texture filled with a color, which is then scaled 20 | // and rotated by altering the GUI matrix. The matrix is restored afterwards. 21 | //**************************************************************************************************** 22 | 23 | public static Texture2D lineTex; 24 | 25 | public static void DrawLine(Rect rect) { DrawLine(rect, GUI.contentColor, 1.0f); } 26 | public static void DrawLine(Rect rect, Color color) { DrawLine(rect, color, 1.0f); } 27 | public static void DrawLine(Rect rect, float width) { DrawLine(rect, GUI.contentColor, width); } 28 | public static void DrawLine(Rect rect, Color color, float width) { DrawLine(new Vector2(rect.x, rect.y), new Vector2(rect.x + rect.width, rect.y + rect.height), color, width); } 29 | public static void DrawLine(Vector2 pointA, Vector2 pointB) { DrawLine(pointA, pointB, GUI.contentColor, 1.0f); } 30 | public static void DrawLine(Vector2 pointA, Vector2 pointB, Color color) { DrawLine(pointA, pointB, color, 1.0f); } 31 | public static void DrawLine(Vector2 pointA, Vector2 pointB, float width) { DrawLine(pointA, pointB, GUI.contentColor, width); } 32 | public static void DrawLine(Vector2 pointA, Vector2 pointB, Color color, float width) 33 | { 34 | // Save the current GUI matrix, since we're going to make changes to it. 35 | Matrix4x4 matrix = GUI.matrix; 36 | 37 | // Generate a single pixel texture if it doesn't exist 38 | if (!lineTex) { lineTex = new Texture2D(1, 1); } 39 | 40 | // Store current GUI color, so we can switch it back later, 41 | // and set the GUI color to the color parameter 42 | Color savedColor = GUI.color; 43 | GUI.color = color; 44 | 45 | // Determine the angle of the line. 46 | float angle = Vector3.Angle(pointB - pointA, Vector2.right); 47 | 48 | // Vector3.Angle always returns a positive number. 49 | // If pointB is above pointA, then angle needs to be negative. 50 | if (pointA.y > pointB.y) { angle = -angle; } 51 | 52 | // Use ScaleAroundPivot to adjust the size of the line. 53 | // We could do this when we draw the texture, but by scaling it here we can use 54 | // non-integer values for the width and length (such as sub 1 pixel widths). 55 | // Note that the pivot point is at +.5 from pointA.y, this is so that the width of the line 56 | // is centered on the origin at pointA. 57 | GUIUtility.ScaleAroundPivot(new Vector2((pointB - pointA).magnitude, width), new Vector2(pointA.x, pointA.y + 0.5f)); 58 | 59 | // Set the rotation for the line. 60 | // The angle was calculated with pointA as the origin. 61 | GUIUtility.RotateAroundPivot(angle, pointA); 62 | 63 | // Finally, draw the actual line. 64 | // We're really only drawing a 1x1 texture from pointA. 65 | // The matrix operations done with ScaleAroundPivot and RotateAroundPivot will make this 66 | // render with the proper width, length, and angle. 67 | GUI.DrawTexture(new Rect(pointA.x, pointA.y, 1, 1), lineTex); 68 | 69 | // We're done. Restore the GUI matrix and GUI color to whatever they were before. 70 | GUI.matrix = matrix; 71 | GUI.color = savedColor; 72 | } 73 | } -------------------------------------------------------------------------------- /Assets/Scripts/GuiDraw.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 027a04a822b40234e85c2e86b7ff14b8 3 | timeCreated: 1497993312 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /FpsController.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/FpsController.unitypackage -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 atil 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 | -------------------------------------------------------------------------------- /Packages/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "com.prenominal.realtimecsg": "https://github.com/LogicalError/realtime-CSG-for-unity.git", 4 | "com.unity.2d.sprite": "1.0.0", 5 | "com.unity.2d.tilemap": "1.0.0", 6 | "com.unity.ads": "3.7.5", 7 | "com.unity.analytics": "3.6.12", 8 | "com.unity.collab-proxy": "1.17.7", 9 | "com.unity.ide.rider": "3.0.18", 10 | "com.unity.ide.visualstudio": "2.0.17", 11 | "com.unity.ide.vscode": "1.2.5", 12 | "com.unity.purchasing": "4.5.2", 13 | "com.unity.test-framework": "1.1.31", 14 | "com.unity.textmeshpro": "3.0.6", 15 | "com.unity.timeline": "1.6.4", 16 | "com.unity.ugui": "1.0.0", 17 | "com.unity.xr.legacyinputhelpers": "2.1.10", 18 | "com.unity.modules.ai": "1.0.0", 19 | "com.unity.modules.androidjni": "1.0.0", 20 | "com.unity.modules.animation": "1.0.0", 21 | "com.unity.modules.assetbundle": "1.0.0", 22 | "com.unity.modules.audio": "1.0.0", 23 | "com.unity.modules.cloth": "1.0.0", 24 | "com.unity.modules.director": "1.0.0", 25 | "com.unity.modules.imageconversion": "1.0.0", 26 | "com.unity.modules.imgui": "1.0.0", 27 | "com.unity.modules.jsonserialize": "1.0.0", 28 | "com.unity.modules.particlesystem": "1.0.0", 29 | "com.unity.modules.physics": "1.0.0", 30 | "com.unity.modules.physics2d": "1.0.0", 31 | "com.unity.modules.screencapture": "1.0.0", 32 | "com.unity.modules.terrain": "1.0.0", 33 | "com.unity.modules.terrainphysics": "1.0.0", 34 | "com.unity.modules.tilemap": "1.0.0", 35 | "com.unity.modules.ui": "1.0.0", 36 | "com.unity.modules.uielements": "1.0.0", 37 | "com.unity.modules.umbra": "1.0.0", 38 | "com.unity.modules.unityanalytics": "1.0.0", 39 | "com.unity.modules.unitywebrequest": "1.0.0", 40 | "com.unity.modules.unitywebrequestassetbundle": "1.0.0", 41 | "com.unity.modules.unitywebrequestaudio": "1.0.0", 42 | "com.unity.modules.unitywebrequesttexture": "1.0.0", 43 | "com.unity.modules.unitywebrequestwww": "1.0.0", 44 | "com.unity.modules.vehicles": "1.0.0", 45 | "com.unity.modules.video": "1.0.0", 46 | "com.unity.modules.vr": "1.0.0", 47 | "com.unity.modules.wind": "1.0.0", 48 | "com.unity.modules.xr": "1.0.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ProjectSettings/AudioManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/AudioManager.asset -------------------------------------------------------------------------------- /ProjectSettings/ClusterInputManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/ClusterInputManager.asset -------------------------------------------------------------------------------- /ProjectSettings/DynamicsManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/DynamicsManager.asset -------------------------------------------------------------------------------- /ProjectSettings/EditorBuildSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/EditorBuildSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/EditorSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/EditorSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/GraphicsSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/GraphicsSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/InputManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/InputManager.asset -------------------------------------------------------------------------------- /ProjectSettings/MemorySettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!387306366 &1 4 | MemorySettings: 5 | m_ObjectHideFlags: 0 6 | m_EditorMemorySettings: 7 | m_MainAllocatorBlockSize: -1 8 | m_ThreadAllocatorBlockSize: -1 9 | m_MainGfxBlockSize: -1 10 | m_ThreadGfxBlockSize: -1 11 | m_CacheBlockSize: -1 12 | m_TypetreeBlockSize: -1 13 | m_ProfilerBlockSize: -1 14 | m_ProfilerEditorBlockSize: -1 15 | m_BucketAllocatorGranularity: -1 16 | m_BucketAllocatorBucketsCount: -1 17 | m_BucketAllocatorBlockSize: -1 18 | m_BucketAllocatorBlockCount: -1 19 | m_ProfilerBucketAllocatorGranularity: -1 20 | m_ProfilerBucketAllocatorBucketsCount: -1 21 | m_ProfilerBucketAllocatorBlockSize: -1 22 | m_ProfilerBucketAllocatorBlockCount: -1 23 | m_TempAllocatorSizeMain: -1 24 | m_JobTempAllocatorBlockSize: -1 25 | m_BackgroundJobTempAllocatorBlockSize: -1 26 | m_JobTempAllocatorReducedBlockSize: -1 27 | m_TempAllocatorSizeGIBakingWorker: -1 28 | m_TempAllocatorSizeNavMeshWorker: -1 29 | m_TempAllocatorSizeAudioWorker: -1 30 | m_TempAllocatorSizeCloudWorker: -1 31 | m_TempAllocatorSizeGfx: -1 32 | m_TempAllocatorSizeJobWorker: -1 33 | m_TempAllocatorSizeBackgroundWorker: -1 34 | m_TempAllocatorSizePreloadManager: -1 35 | m_PlatformMemorySettings: {} 36 | -------------------------------------------------------------------------------- /ProjectSettings/NavMeshAreas.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/NavMeshAreas.asset -------------------------------------------------------------------------------- /ProjectSettings/NetworkManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/NetworkManager.asset -------------------------------------------------------------------------------- /ProjectSettings/PackageManagerSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!114 &1 4 | MonoBehaviour: 5 | m_ObjectHideFlags: 61 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_GameObject: {fileID: 0} 10 | m_Enabled: 1 11 | m_EditorHideFlags: 0 12 | m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} 13 | m_Name: 14 | m_EditorClassIdentifier: 15 | m_EnablePreReleasePackages: 0 16 | m_EnablePackageDependencies: 0 17 | m_AdvancedSettingsExpanded: 1 18 | m_ScopedRegistriesSettingsExpanded: 1 19 | m_SeeAllPackageVersions: 0 20 | oneTimeWarningShown: 0 21 | m_Registries: 22 | - m_Id: main 23 | m_Name: 24 | m_Url: https://packages.unity.com 25 | m_Scopes: [] 26 | m_IsDefault: 1 27 | m_Capabilities: 7 28 | m_UserSelectedRegistryName: 29 | m_UserAddingNewScopedRegistry: 0 30 | m_RegistryInfoDraft: 31 | m_Modified: 0 32 | m_ErrorMessage: 33 | m_UserModificationsInstanceId: -852 34 | m_OriginalInstanceId: -854 35 | m_LoadAssets: 0 36 | -------------------------------------------------------------------------------- /ProjectSettings/Physics2DSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/Physics2DSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/PresetManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/PresetManager.asset -------------------------------------------------------------------------------- /ProjectSettings/ProjectSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/ProjectSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/ProjectVersion.txt: -------------------------------------------------------------------------------- 1 | m_EditorVersion: 2021.3.17f1 2 | m_EditorVersionWithRevision: 2021.3.17f1 (3e8111cac19d) 3 | -------------------------------------------------------------------------------- /ProjectSettings/QualitySettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/QualitySettings.asset -------------------------------------------------------------------------------- /ProjectSettings/SceneTemplateSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "templatePinStates": [], 3 | "dependencyTypeInfos": [ 4 | { 5 | "userAdded": false, 6 | "type": "UnityEngine.AnimationClip", 7 | "ignore": false, 8 | "defaultInstantiationMode": 0, 9 | "supportsModification": true 10 | }, 11 | { 12 | "userAdded": false, 13 | "type": "UnityEditor.Animations.AnimatorController", 14 | "ignore": false, 15 | "defaultInstantiationMode": 0, 16 | "supportsModification": true 17 | }, 18 | { 19 | "userAdded": false, 20 | "type": "UnityEngine.AnimatorOverrideController", 21 | "ignore": false, 22 | "defaultInstantiationMode": 0, 23 | "supportsModification": true 24 | }, 25 | { 26 | "userAdded": false, 27 | "type": "UnityEditor.Audio.AudioMixerController", 28 | "ignore": false, 29 | "defaultInstantiationMode": 0, 30 | "supportsModification": true 31 | }, 32 | { 33 | "userAdded": false, 34 | "type": "UnityEngine.ComputeShader", 35 | "ignore": true, 36 | "defaultInstantiationMode": 1, 37 | "supportsModification": true 38 | }, 39 | { 40 | "userAdded": false, 41 | "type": "UnityEngine.Cubemap", 42 | "ignore": false, 43 | "defaultInstantiationMode": 0, 44 | "supportsModification": true 45 | }, 46 | { 47 | "userAdded": false, 48 | "type": "UnityEngine.GameObject", 49 | "ignore": false, 50 | "defaultInstantiationMode": 0, 51 | "supportsModification": true 52 | }, 53 | { 54 | "userAdded": false, 55 | "type": "UnityEditor.LightingDataAsset", 56 | "ignore": false, 57 | "defaultInstantiationMode": 0, 58 | "supportsModification": false 59 | }, 60 | { 61 | "userAdded": false, 62 | "type": "UnityEngine.LightingSettings", 63 | "ignore": false, 64 | "defaultInstantiationMode": 0, 65 | "supportsModification": true 66 | }, 67 | { 68 | "userAdded": false, 69 | "type": "UnityEngine.Material", 70 | "ignore": false, 71 | "defaultInstantiationMode": 0, 72 | "supportsModification": true 73 | }, 74 | { 75 | "userAdded": false, 76 | "type": "UnityEditor.MonoScript", 77 | "ignore": true, 78 | "defaultInstantiationMode": 1, 79 | "supportsModification": true 80 | }, 81 | { 82 | "userAdded": false, 83 | "type": "UnityEngine.PhysicMaterial", 84 | "ignore": false, 85 | "defaultInstantiationMode": 0, 86 | "supportsModification": true 87 | }, 88 | { 89 | "userAdded": false, 90 | "type": "UnityEngine.PhysicsMaterial2D", 91 | "ignore": false, 92 | "defaultInstantiationMode": 0, 93 | "supportsModification": true 94 | }, 95 | { 96 | "userAdded": false, 97 | "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", 98 | "ignore": false, 99 | "defaultInstantiationMode": 0, 100 | "supportsModification": true 101 | }, 102 | { 103 | "userAdded": false, 104 | "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", 105 | "ignore": false, 106 | "defaultInstantiationMode": 0, 107 | "supportsModification": true 108 | }, 109 | { 110 | "userAdded": false, 111 | "type": "UnityEngine.Rendering.VolumeProfile", 112 | "ignore": false, 113 | "defaultInstantiationMode": 0, 114 | "supportsModification": true 115 | }, 116 | { 117 | "userAdded": false, 118 | "type": "UnityEditor.SceneAsset", 119 | "ignore": false, 120 | "defaultInstantiationMode": 0, 121 | "supportsModification": false 122 | }, 123 | { 124 | "userAdded": false, 125 | "type": "UnityEngine.Shader", 126 | "ignore": true, 127 | "defaultInstantiationMode": 1, 128 | "supportsModification": true 129 | }, 130 | { 131 | "userAdded": false, 132 | "type": "UnityEngine.ShaderVariantCollection", 133 | "ignore": true, 134 | "defaultInstantiationMode": 1, 135 | "supportsModification": true 136 | }, 137 | { 138 | "userAdded": false, 139 | "type": "UnityEngine.Texture", 140 | "ignore": false, 141 | "defaultInstantiationMode": 0, 142 | "supportsModification": true 143 | }, 144 | { 145 | "userAdded": false, 146 | "type": "UnityEngine.Texture2D", 147 | "ignore": false, 148 | "defaultInstantiationMode": 0, 149 | "supportsModification": true 150 | }, 151 | { 152 | "userAdded": false, 153 | "type": "UnityEngine.Timeline.TimelineAsset", 154 | "ignore": false, 155 | "defaultInstantiationMode": 0, 156 | "supportsModification": true 157 | } 158 | ], 159 | "defaultDependencyTypeInfo": { 160 | "userAdded": false, 161 | "type": "", 162 | "ignore": false, 163 | "defaultInstantiationMode": 1, 164 | "supportsModification": true 165 | }, 166 | "newSceneOverride": 0 167 | } -------------------------------------------------------------------------------- /ProjectSettings/TagManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/TagManager.asset -------------------------------------------------------------------------------- /ProjectSettings/TimeManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/TimeManager.asset -------------------------------------------------------------------------------- /ProjectSettings/UnityConnectSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atil/fpscontroller/2cfb4c7d86d56014324529c0ee5c50620d6ce58b/ProjectSettings/UnityConnectSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/VFXManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!937362698 &1 4 | VFXManager: 5 | m_ObjectHideFlags: 0 6 | m_IndirectShader: {fileID: 0} 7 | m_CopyBufferShader: {fileID: 0} 8 | m_SortShader: {fileID: 0} 9 | m_StripUpdateShader: {fileID: 0} 10 | m_RenderPipeSettingsPath: 11 | m_FixedTimeStep: 0.016666668 12 | m_MaxDeltaTime: 0.05 13 | m_CompiledVersion: 0 14 | m_RuntimeVersion: 0 15 | m_RuntimeResources: {fileID: 0} 16 | -------------------------------------------------------------------------------- /ProjectSettings/VersionControlSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!890905787 &1 4 | VersionControlSettings: 5 | m_ObjectHideFlags: 0 6 | m_Mode: Visible Meta Files 7 | m_CollabEditorSettings: 8 | inProgressEnabled: 1 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # First Person Controller for Unity 2 | 3 | This Q3-inspired first person controller is made with two goals in mind: 4 | 5 | - Arcade feeling facilitates strafejumping / bunnyhopping, allowing easy-to-play-hard-to-master movement. It uses similar movement logic used in quake / halflife games to achieve oldschool feel. 6 | 7 | - Avoid using CharacterController and make every step of the logic as transparent as possible. 8 | 9 | ## Quick start: 10 | 11 | - Unpack [the package](FpsController.unitypackage) 12 | 13 | - Add a layer "PlayerCollider" and set the player's collider to be on that layer 14 | 15 | - Add that layer to `FpsController.ExcludedLayers` on the prefab. Collision and grounded checks will ignore these layers 16 | 17 | ## References: 18 | 19 | [Quake3-movement-unity](https://github.com/Zinglish/quake3-movement-unity3d/blob/master/CPMPlayer.cs) 20 | 21 | [Quake3 source](https://github.com/id-Software/Quake-III-Arena/blob/dbe4ddb10315479fc00086f08e25d968b4b43c49/code/game/bg_pmove.c) 22 | 23 | [Half Life 1 SDK source](https://github.com/ValveSoftware/halflife/blob/5d761709a31ce1e71488f2668321de05f791b405/pm_shared/pm_shared.c#L2941) 24 | 25 | [Bunnyhopping from the Programmer's Perspective](http://flafla2.github.io/2015/02/14/bunnyhop.html) --------------------------------------------------------------------------------