├── LICENSE ├── LICENSE.meta ├── Quake3Movement.meta ├── Quake3Movement ├── Demo.meta ├── Demo │ ├── DemoScene.unity │ ├── DemoScene.unity.meta │ ├── DevGrid.mat │ ├── DevGrid.mat.meta │ ├── GridTexture.tga │ └── GridTexture.tga.meta ├── Scripts.meta └── Scripts │ ├── MouseLook.cs │ ├── MouseLook.cs.meta │ ├── Q3PlayerController.cs │ ├── Q3PlayerController.cs.meta │ ├── Q3PlayerDebug.cs │ └── Q3PlayerDebug.cs.meta ├── readme.md └── readme.md.meta /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 82a42cde4d3c318498bed29c68252c77 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Quake3Movement.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b9104c4cd6043c94bbd7afe101365143 3 | folderAsset: yes 4 | timeCreated: 1518912078 5 | licenseType: Free 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Quake3Movement/Demo.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2c0c7a5c82a49a4418c54959811c2193 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Quake3Movement/Demo/DemoScene.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bca40ad4dce30034d93f9cdb2a5aea0b 3 | timeCreated: 1518911892 4 | licenseType: Free 5 | DefaultImporter: 6 | externalObjects: {} 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Quake3Movement/Demo/DevGrid.mat: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!21 &2100000 4 | Material: 5 | serializedVersion: 6 6 | m_ObjectHideFlags: 0 7 | m_PrefabParentObject: {fileID: 0} 8 | m_PrefabInternal: {fileID: 0} 9 | m_Name: DevGrid 10 | m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} 11 | m_ShaderKeywords: 12 | m_LightmapFlags: 4 13 | m_EnableInstancingVariants: 0 14 | m_DoubleSidedGI: 0 15 | m_CustomRenderQueue: -1 16 | stringTagMap: {} 17 | disabledShaderPasses: [] 18 | m_SavedProperties: 19 | serializedVersion: 3 20 | m_TexEnvs: 21 | - _BumpMap: 22 | m_Texture: {fileID: 0} 23 | m_Scale: {x: 1, y: 1} 24 | m_Offset: {x: 0, y: 0} 25 | - _DetailAlbedoMap: 26 | m_Texture: {fileID: 0} 27 | m_Scale: {x: 0, y: 0} 28 | m_Offset: {x: 0, y: 0} 29 | - _DetailMask: 30 | m_Texture: {fileID: 0} 31 | m_Scale: {x: 1, y: 1} 32 | m_Offset: {x: 0, y: 0} 33 | - _DetailNormalMap: 34 | m_Texture: {fileID: 0} 35 | m_Scale: {x: 1, y: 1} 36 | m_Offset: {x: 0, y: 0} 37 | - _EmissionMap: 38 | m_Texture: {fileID: 0} 39 | m_Scale: {x: 128, y: 128} 40 | m_Offset: {x: 0, y: 0} 41 | - _MainTex: 42 | m_Texture: {fileID: 2800000, guid: d26bb6c8aebf04b4f8014c4990146141, type: 3} 43 | m_Scale: {x: 128, y: 128} 44 | m_Offset: {x: 0, y: 0} 45 | - _MetallicGlossMap: 46 | m_Texture: {fileID: 0} 47 | m_Scale: {x: 1, y: 1} 48 | m_Offset: {x: 0, y: 0} 49 | - _OcclusionMap: 50 | m_Texture: {fileID: 0} 51 | m_Scale: {x: 1, y: 1} 52 | m_Offset: {x: 0, y: 0} 53 | - _ParallaxMap: 54 | m_Texture: {fileID: 0} 55 | m_Scale: {x: 1, y: 1} 56 | m_Offset: {x: 0, y: 0} 57 | m_Floats: 58 | - _BumpScale: 1 59 | - _Cutoff: 0.5 60 | - _DetailNormalMapScale: 1 61 | - _DstBlend: 0 62 | - _GlossMapScale: 1 63 | - _Glossiness: 0 64 | - _GlossyReflections: 1 65 | - _Metallic: 0 66 | - _Mode: 0 67 | - _OcclusionStrength: 1 68 | - _Parallax: 0.02 69 | - _SmoothnessTextureChannel: 0 70 | - _SpecularHighlights: 1 71 | - _SrcBlend: 1 72 | - _UVSec: 0 73 | - _ZWrite: 1 74 | m_Colors: 75 | - _Color: {r: 1, g: 1, b: 1, a: 1} 76 | - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} 77 | -------------------------------------------------------------------------------- /Quake3Movement/Demo/DevGrid.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 77b53e8b78caa8545b9c671e23aba398 3 | timeCreated: 1518911743 4 | licenseType: Free 5 | NativeFormatImporter: 6 | externalObjects: {} 7 | mainObjectFileID: 2100000 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Quake3Movement/Demo/GridTexture.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaiahKelly/quake3-movement-for-unity/71f8c72a929e13adc477208770a5e61e7a28c421/Quake3Movement/Demo/GridTexture.tga -------------------------------------------------------------------------------- /Quake3Movement/Demo/GridTexture.tga.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d26bb6c8aebf04b4f8014c4990146141 3 | timeCreated: 1518911731 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | externalObjects: {} 8 | serializedVersion: 4 9 | mipmaps: 10 | mipMapMode: 0 11 | enableMipMap: 1 12 | sRGBTexture: 1 13 | linearTexture: 0 14 | fadeOut: 0 15 | borderMipMap: 0 16 | mipMapsPreserveCoverage: 0 17 | alphaTestReferenceValue: 0.5 18 | mipMapFadeDistanceStart: 1 19 | mipMapFadeDistanceEnd: 3 20 | bumpmap: 21 | convertToNormalMap: 0 22 | externalNormalMap: 0 23 | heightScale: 0.25 24 | normalMapFilter: 0 25 | isReadable: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -1 37 | wrapU: -1 38 | wrapV: -1 39 | wrapW: -1 40 | nPOTScale: 1 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 0 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 49 | spritePixelsToUnits: 100 50 | alphaUsage: 1 51 | alphaIsTransparency: 0 52 | spriteTessellationDetail: -1 53 | textureType: 0 54 | textureShape: 1 55 | maxTextureSizeSet: 0 56 | compressionQualitySet: 0 57 | textureFormatSet: 0 58 | platformSettings: 59 | - buildTarget: DefaultTexturePlatform 60 | maxTextureSize: 2048 61 | resizeAlgorithm: 0 62 | textureFormat: -1 63 | textureCompression: 1 64 | compressionQuality: 50 65 | crunchedCompression: 0 66 | allowsAlphaSplitting: 0 67 | overridden: 0 68 | androidETC2FallbackOverride: 0 69 | spriteSheet: 70 | serializedVersion: 2 71 | sprites: [] 72 | outline: [] 73 | physicsShape: [] 74 | spritePackingTag: 75 | userData: 76 | assetBundleName: 77 | assetBundleVariant: 78 | -------------------------------------------------------------------------------- /Quake3Movement/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9cd76b6cde812544e9cfc625a68ff15e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Quake3Movement/Scripts/MouseLook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace Q3Movement 5 | { 6 | /// 7 | /// Custom script based on the version from the Standard Assets. 8 | /// 9 | [Serializable] 10 | public class MouseLook 11 | { 12 | [SerializeField] private float m_XSensitivity = 2f; 13 | [SerializeField] private float m_YSensitivity = 2f; 14 | [SerializeField] private bool m_ClampVerticalRotation = true; 15 | [SerializeField] private float m_MinimumX = -90F; 16 | [SerializeField] private float m_MaximumX = 90F; 17 | [SerializeField] private bool m_Smooth = false; 18 | [SerializeField] private float m_SmoothTime = 5f; 19 | [SerializeField] private bool m_LockCursor = true; 20 | 21 | private Quaternion m_CharacterTargetRot; 22 | private Quaternion m_CameraTargetRot; 23 | private bool m_cursorIsLocked = true; 24 | 25 | public void Init(Transform character, Transform camera) 26 | { 27 | m_CharacterTargetRot = character.localRotation; 28 | m_CameraTargetRot = camera.localRotation; 29 | } 30 | 31 | public void LookRotation(Transform character, Transform camera) 32 | { 33 | float yRot = Input.GetAxis("Mouse X") * m_XSensitivity; 34 | float xRot = Input.GetAxis("Mouse Y") * m_YSensitivity; 35 | 36 | m_CharacterTargetRot *= Quaternion.Euler(0f, yRot, 0f); 37 | m_CameraTargetRot *= Quaternion.Euler(-xRot, 0f, 0f); 38 | 39 | if (m_ClampVerticalRotation) 40 | { 41 | m_CameraTargetRot = ClampRotationAroundXAxis(m_CameraTargetRot); 42 | } 43 | 44 | if (m_Smooth) 45 | { 46 | character.localRotation = Quaternion.Slerp(character.localRotation, m_CharacterTargetRot, 47 | m_SmoothTime * Time.deltaTime); 48 | camera.localRotation = Quaternion.Slerp(camera.localRotation, m_CameraTargetRot, 49 | m_SmoothTime * Time.deltaTime); 50 | } 51 | else 52 | { 53 | character.localRotation = m_CharacterTargetRot; 54 | camera.localRotation = m_CameraTargetRot; 55 | } 56 | 57 | UpdateCursorLock(); 58 | } 59 | 60 | public void SetCursorLock(bool value) 61 | { 62 | m_LockCursor = value; 63 | if (!m_LockCursor) 64 | {//we force unlock the cursor if the user disable the cursor locking helper 65 | Cursor.lockState = CursorLockMode.None; 66 | Cursor.visible = true; 67 | } 68 | } 69 | 70 | public void UpdateCursorLock() 71 | { 72 | //if the user set "lockCursor" we check & properly lock the cursos 73 | if (m_LockCursor) 74 | { 75 | InternalLockUpdate(); 76 | } 77 | } 78 | 79 | private void InternalLockUpdate() 80 | { 81 | if (Input.GetKeyUp(KeyCode.Escape)) 82 | { 83 | m_cursorIsLocked = false; 84 | } 85 | else if (Input.GetMouseButtonUp(0)) 86 | { 87 | m_cursorIsLocked = true; 88 | } 89 | 90 | if (m_cursorIsLocked) 91 | { 92 | Cursor.lockState = CursorLockMode.Locked; 93 | Cursor.visible = false; 94 | } 95 | else if (!m_cursorIsLocked) 96 | { 97 | Cursor.lockState = CursorLockMode.None; 98 | Cursor.visible = true; 99 | } 100 | } 101 | 102 | private Quaternion ClampRotationAroundXAxis(Quaternion q) 103 | { 104 | q.x /= q.w; 105 | q.y /= q.w; 106 | q.z /= q.w; 107 | q.w = 1.0f; 108 | 109 | float angleX = 2.0f * Mathf.Rad2Deg * Mathf.Atan(q.x); 110 | 111 | angleX = Mathf.Clamp(angleX, m_MinimumX, m_MaximumX); 112 | 113 | q.x = Mathf.Tan(0.5f * Mathf.Deg2Rad * angleX); 114 | 115 | return q; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Quake3Movement/Scripts/MouseLook.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3feaa5f827897f64997c0dc6960e1c5f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Quake3Movement/Scripts/Q3PlayerController.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Q3Movement 4 | { 5 | /// 6 | /// This script handles Quake III CPM(A) mod style player movement logic. 7 | /// 8 | [RequireComponent(typeof(CharacterController))] 9 | public class Q3PlayerController : MonoBehaviour 10 | { 11 | [System.Serializable] 12 | public class MovementSettings 13 | { 14 | public float MaxSpeed; 15 | public float Acceleration; 16 | public float Deceleration; 17 | 18 | public MovementSettings(float maxSpeed, float accel, float decel) 19 | { 20 | MaxSpeed = maxSpeed; 21 | Acceleration = accel; 22 | Deceleration = decel; 23 | } 24 | } 25 | 26 | [Header("Aiming")] 27 | [SerializeField] private Camera m_Camera; 28 | [SerializeField] private MouseLook m_MouseLook = new MouseLook(); 29 | 30 | [Header("Movement")] 31 | [SerializeField] private float m_Friction = 6; 32 | [SerializeField] private float m_Gravity = 20; 33 | [SerializeField] private float m_JumpForce = 8; 34 | [Tooltip("Automatically jump when holding jump button")] 35 | [SerializeField] private bool m_AutoBunnyHop = false; 36 | [Tooltip("How precise air control is")] 37 | [SerializeField] private float m_AirControl = 0.3f; 38 | [SerializeField] private MovementSettings m_GroundSettings = new MovementSettings(7, 14, 10); 39 | [SerializeField] private MovementSettings m_AirSettings = new MovementSettings(7, 2, 2); 40 | [SerializeField] private MovementSettings m_StrafeSettings = new MovementSettings(1, 50, 50); 41 | 42 | /// 43 | /// Returns player's current speed. 44 | /// 45 | public float Speed { get { return m_Character.velocity.magnitude; } } 46 | 47 | private CharacterController m_Character; 48 | private Vector3 m_MoveDirectionNorm = Vector3.zero; 49 | private Vector3 m_PlayerVelocity = Vector3.zero; 50 | 51 | // Used to queue the next jump just before hitting the ground. 52 | private bool m_JumpQueued = false; 53 | 54 | // Used to display real time friction values. 55 | private float m_PlayerFriction = 0; 56 | 57 | private Vector3 m_MoveInput; 58 | private Transform m_Tran; 59 | private Transform m_CamTran; 60 | 61 | private void Start() 62 | { 63 | m_Tran = transform; 64 | m_Character = GetComponent(); 65 | 66 | if (!m_Camera) 67 | m_Camera = Camera.main; 68 | 69 | m_CamTran = m_Camera.transform; 70 | m_MouseLook.Init(m_Tran, m_CamTran); 71 | } 72 | 73 | private void Update() 74 | { 75 | m_MoveInput = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")); 76 | m_MouseLook.UpdateCursorLock(); 77 | QueueJump(); 78 | 79 | // Set movement state. 80 | if (m_Character.isGrounded) 81 | { 82 | GroundMove(); 83 | } 84 | else 85 | { 86 | AirMove(); 87 | } 88 | 89 | // Rotate the character and camera. 90 | m_MouseLook.LookRotation(m_Tran, m_CamTran); 91 | 92 | // Move the character. 93 | m_Character.Move(m_PlayerVelocity * Time.deltaTime); 94 | } 95 | 96 | // Queues the next jump. 97 | private void QueueJump() 98 | { 99 | if (m_AutoBunnyHop) 100 | { 101 | m_JumpQueued = Input.GetButton("Jump"); 102 | return; 103 | } 104 | 105 | if (Input.GetButtonDown("Jump") && !m_JumpQueued) 106 | { 107 | m_JumpQueued = true; 108 | } 109 | 110 | if (Input.GetButtonUp("Jump")) 111 | { 112 | m_JumpQueued = false; 113 | } 114 | } 115 | 116 | // Handle air movement. 117 | private void AirMove() 118 | { 119 | float accel; 120 | 121 | var wishdir = new Vector3(m_MoveInput.x, 0, m_MoveInput.z); 122 | wishdir = m_Tran.TransformDirection(wishdir); 123 | 124 | float wishspeed = wishdir.magnitude; 125 | wishspeed *= m_AirSettings.MaxSpeed; 126 | 127 | wishdir.Normalize(); 128 | m_MoveDirectionNorm = wishdir; 129 | 130 | // CPM Air control. 131 | float wishspeed2 = wishspeed; 132 | if (Vector3.Dot(m_PlayerVelocity, wishdir) < 0) 133 | { 134 | accel = m_AirSettings.Deceleration; 135 | } 136 | else 137 | { 138 | accel = m_AirSettings.Acceleration; 139 | } 140 | 141 | // If the player is ONLY strafing left or right 142 | if (m_MoveInput.z == 0 && m_MoveInput.x != 0) 143 | { 144 | if (wishspeed > m_StrafeSettings.MaxSpeed) 145 | { 146 | wishspeed = m_StrafeSettings.MaxSpeed; 147 | } 148 | 149 | accel = m_StrafeSettings.Acceleration; 150 | } 151 | 152 | Accelerate(wishdir, wishspeed, accel); 153 | if (m_AirControl > 0) 154 | { 155 | AirControl(wishdir, wishspeed2); 156 | } 157 | 158 | // Apply gravity 159 | m_PlayerVelocity.y -= m_Gravity * Time.deltaTime; 160 | } 161 | 162 | // Air control occurs when the player is in the air, it allows players to move side 163 | // to side much faster rather than being 'sluggish' when it comes to cornering. 164 | private void AirControl(Vector3 targetDir, float targetSpeed) 165 | { 166 | // Only control air movement when moving forward or backward. 167 | if (Mathf.Abs(m_MoveInput.z) < 0.001 || Mathf.Abs(targetSpeed) < 0.001) 168 | { 169 | return; 170 | } 171 | 172 | float zSpeed = m_PlayerVelocity.y; 173 | m_PlayerVelocity.y = 0; 174 | /* Next two lines are equivalent to idTech's VectorNormalize() */ 175 | float speed = m_PlayerVelocity.magnitude; 176 | m_PlayerVelocity.Normalize(); 177 | 178 | float dot = Vector3.Dot(m_PlayerVelocity, targetDir); 179 | float k = 32; 180 | k *= m_AirControl * dot * dot * Time.deltaTime; 181 | 182 | // Change direction while slowing down. 183 | if (dot > 0) 184 | { 185 | m_PlayerVelocity.x *= speed + targetDir.x * k; 186 | m_PlayerVelocity.y *= speed + targetDir.y * k; 187 | m_PlayerVelocity.z *= speed + targetDir.z * k; 188 | 189 | m_PlayerVelocity.Normalize(); 190 | m_MoveDirectionNorm = m_PlayerVelocity; 191 | } 192 | 193 | m_PlayerVelocity.x *= speed; 194 | m_PlayerVelocity.y = zSpeed; // Note this line 195 | m_PlayerVelocity.z *= speed; 196 | } 197 | 198 | // Handle ground movement. 199 | private void GroundMove() 200 | { 201 | // Do not apply friction if the player is queueing up the next jump 202 | if (!m_JumpQueued) 203 | { 204 | ApplyFriction(1.0f); 205 | } 206 | else 207 | { 208 | ApplyFriction(0); 209 | } 210 | 211 | var wishdir = new Vector3(m_MoveInput.x, 0, m_MoveInput.z); 212 | wishdir = m_Tran.TransformDirection(wishdir); 213 | wishdir.Normalize(); 214 | m_MoveDirectionNorm = wishdir; 215 | 216 | var wishspeed = wishdir.magnitude; 217 | wishspeed *= m_GroundSettings.MaxSpeed; 218 | 219 | Accelerate(wishdir, wishspeed, m_GroundSettings.Acceleration); 220 | 221 | // Reset the gravity velocity 222 | m_PlayerVelocity.y = -m_Gravity * Time.deltaTime; 223 | 224 | if (m_JumpQueued) 225 | { 226 | m_PlayerVelocity.y = m_JumpForce; 227 | m_JumpQueued = false; 228 | } 229 | } 230 | 231 | private void ApplyFriction(float t) 232 | { 233 | // Equivalent to VectorCopy(); 234 | Vector3 vec = m_PlayerVelocity; 235 | vec.y = 0; 236 | float speed = vec.magnitude; 237 | float drop = 0; 238 | 239 | // Only apply friction when grounded. 240 | if (m_Character.isGrounded) 241 | { 242 | float control = speed < m_GroundSettings.Deceleration ? m_GroundSettings.Deceleration : speed; 243 | drop = control * m_Friction * Time.deltaTime * t; 244 | } 245 | 246 | float newSpeed = speed - drop; 247 | m_PlayerFriction = newSpeed; 248 | if (newSpeed < 0) 249 | { 250 | newSpeed = 0; 251 | } 252 | 253 | if (speed > 0) 254 | { 255 | newSpeed /= speed; 256 | } 257 | 258 | m_PlayerVelocity.x *= newSpeed; 259 | // playerVelocity.y *= newSpeed; 260 | m_PlayerVelocity.z *= newSpeed; 261 | } 262 | 263 | // Calculates acceleration based on desired speed and direction. 264 | private void Accelerate(Vector3 targetDir, float targetSpeed, float accel) 265 | { 266 | float currentspeed = Vector3.Dot(m_PlayerVelocity, targetDir); 267 | float addspeed = targetSpeed - currentspeed; 268 | if (addspeed <= 0) 269 | { 270 | return; 271 | } 272 | 273 | float accelspeed = accel * Time.deltaTime * targetSpeed; 274 | if (accelspeed > addspeed) 275 | { 276 | accelspeed = addspeed; 277 | } 278 | 279 | m_PlayerVelocity.x += accelspeed * targetDir.x; 280 | m_PlayerVelocity.z += accelspeed * targetDir.z; 281 | } 282 | } 283 | } -------------------------------------------------------------------------------- /Quake3Movement/Scripts/Q3PlayerController.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 689c7b4cc1dbae54cbfa41c0c84fb174 3 | timeCreated: 1518909934 4 | licenseType: Free 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Quake3Movement/Scripts/Q3PlayerDebug.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Q3Movement 4 | { 5 | /// 6 | /// This script is used to display real-time statistics to help with tweaking and debugging. 7 | /// 8 | [RequireComponent(typeof(Q3PlayerController))] 9 | public class Q3PlayerDebug : MonoBehaviour 10 | { 11 | [Tooltip("How many times per second to update stats")] 12 | [SerializeField] private float m_RefreshRate = 4; 13 | 14 | private int m_FrameCount = 0; 15 | private float m_Time = 0; 16 | private float m_FPS = 0; 17 | private float m_TopSpeed = 0; 18 | private Q3PlayerController m_Player; 19 | 20 | private void Start() 21 | { 22 | m_Player = GetComponent(); 23 | } 24 | 25 | private void LateUpdate() 26 | { 27 | // Calculate frames-per-second. 28 | m_FrameCount++; 29 | m_Time += Time.deltaTime; 30 | if (m_Time > 1.0 / m_RefreshRate) 31 | { 32 | m_FPS = Mathf.Round(m_FrameCount / m_Time); 33 | m_FrameCount = 0; 34 | m_Time -= 1.0f / m_RefreshRate; 35 | } 36 | 37 | // Calculate top velocity. 38 | if (m_Player.Speed > m_TopSpeed) 39 | { 40 | m_TopSpeed = m_Player.Speed; 41 | } 42 | } 43 | 44 | private void OnGUI() 45 | { 46 | GUI.Box(new Rect(0, 0, 130, 60), 47 | "FPS: " + m_FPS + "\n" + 48 | "Speed: " + Mathf.Round(m_Player.Speed * 100) / 100 + " (ups)\n" + 49 | "Top: " + Mathf.Round(m_TopSpeed * 100) / 100 + " (ups)"); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Quake3Movement/Scripts/Q3PlayerDebug.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 56a75dc49fa305c4d87d2854fe203452 3 | timeCreated: 1518909804 4 | licenseType: Free 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Implementation of Quake III "vanilla" and Challenge ProMode Arena (CPMA) strafe jumping mechanics in the Unity engine. 2 | 3 | This is my own updated and enhanced fork of the original scripts created by [WiggleWizard](https://github.com/WiggleWizard). So most of the credit goes to him for porting it over. I am just trying to improve upon it some here. 4 | 5 | ## Notes: 6 | 7 | ### Coordinate System 8 | Quake uses a right-handed coordinate system while Unity uses a left-handed one. So coordinate values (X,Y,Z) have been swapped to reflect this difference. 9 | 10 | ### World Scale 11 | UPS (units per second) is measured in Unity units (meters) and not idTech units. 12 | 13 | ### Configuration 14 | Default script values emulate Quake III Arena movement with CPM(A) physics. 15 | 16 | ### Demo Assets 17 | Demo scene meshes were built with ProBuilder 4.4 so this package must be installed in your project for demo scene to function. 18 | -------------------------------------------------------------------------------- /readme.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a235d40150426c94983e2bf2e4ef0db0 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------