├── README.md ├── PlayerView.cs └── PlayerStrafeJumping.cs /README.md: -------------------------------------------------------------------------------- 1 | # Quake 3 strafe jumping script for Unity 2 | 3 | Basic implementation of the VQ3 strafe jumping mechanic based on Unity's CharacterController. 4 | 5 | To use it, create the following GameObject hierarchy: 6 | - Player [PlayerStrafeJumping.cs|CharacterController] 7 | - Camera [PlayerView.cs] 8 | 9 | Add the PlayerView script to the camera and the PlayerStrafeJumping script to the player. 10 | Make sure the player has a CharacterController component attached to it as well. 11 | To prevent Unity from smoothing out the keyboard input you should also set the following values in the input preferences: 12 | - Edit -> Project Settings -> Input 13 | - Axis:Horizontal:Gravity = Infinity 14 | - Axis:Horizontal:Sensitivity = Infinity 15 | - Axis:Vertical:Gravity = Infinity 16 | - Axis:Vertical:Sensitivity = Infinity 17 | 18 | The implementation should be fairly equivalent to: 19 | 20 | 21 | To verify some of the configuration values see: 22 | 23 | -------------------------------------------------------------------------------- /PlayerView.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | 6 | public class PlayerView : MonoBehaviour { 7 | 8 | public float horizontalSpeed = 100f; 9 | public float verticalSpeed = 100f; 10 | public bool flipVertical = false; 11 | public float clampAngle = 80f; 12 | 13 | private float rotationX; 14 | private float rotationY; 15 | private Vector3 cameraTargetPosition; 16 | 17 | private float flipMultiplier = 1f; 18 | 19 | void Start() { 20 | if (flipVertical) 21 | flipMultiplier = -1; 22 | 23 | rotationX = transform.eulerAngles.x; 24 | rotationY = transform.eulerAngles.y; 25 | 26 | Cursor.lockState = CursorLockMode.Locked; 27 | Cursor.visible = false; 28 | } 29 | 30 | void LateUpdate() { 31 | rotationY += horizontalSpeed * Input.GetAxis ("Mouse X") * Time.deltaTime; 32 | rotationX += flipMultiplier * verticalSpeed * Input.GetAxis ("Mouse Y") * Time.deltaTime; 33 | rotationX = Mathf.Clamp(rotationX, -clampAngle, clampAngle); 34 | 35 | var parentPosition = transform.InverseTransformPoint(transform.parent.position); 36 | transform.Translate(parentPosition); 37 | transform.rotation = Quaternion.Euler(rotationX, rotationY, 0f); 38 | transform.Translate(-parentPosition); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /PlayerStrafeJumping.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | 6 | public class PlayerStrafeJumping : MonoBehaviour { 7 | 8 | #region Variables 9 | private static float quakeScaleFactor = 1f / 26f; // 1 m = 26 qunits 10 | private float maxSpeed = 320f * quakeScaleFactor; 11 | private float jumpSpeed = 270f * quakeScaleFactor; 12 | private float gravity = 800f * quakeScaleFactor; 13 | private float airAcceleration = 320f * quakeScaleFactor; 14 | private float groundAcceleration = 3200f * quakeScaleFactor; 15 | private float stopSpeed = 100f * quakeScaleFactor; 16 | private float friction = 6f; 17 | 18 | private Vector3 velocity = Vector3.zero; 19 | private Transform cameraTransform; 20 | private CharacterController characterController; 21 | #endregion 22 | 23 | void Start() { 24 | cameraTransform = transform.Find("Camera"); 25 | characterController = GetComponent(); 26 | } 27 | 28 | void Update() { 29 | if (characterController.isGrounded || VerifyCharacterGroundedness()) { 30 | if (Input.GetButton("Jump")) { 31 | velocity.y = jumpSpeed; 32 | applyAcceleration(airAcceleration); 33 | } else { 34 | velocity.y = 0f; 35 | applyFriction(); 36 | applyAcceleration(groundAcceleration); 37 | var targetPosition = transform.position + velocity * Time.deltaTime; 38 | var targetOnGround = DowncastFromPosition(targetPosition, 1.02f + characterController.skinWidth); 39 | if (!targetOnGround) { 40 | var targetCloseToGround = DowncastFromPosition(targetPosition, 1.3f); 41 | if (targetCloseToGround) 42 | velocity.y -= 0.3f / Time.deltaTime; 43 | } 44 | } 45 | } else { 46 | velocity.y -= gravity * Time.deltaTime; 47 | applyAcceleration(airAcceleration); 48 | } 49 | 50 | characterController.Move(velocity * Time.deltaTime); 51 | } 52 | 53 | private void applyFriction() { 54 | var speed = velocity.magnitude; 55 | if (speed > 0) { 56 | var control = speed < stopSpeed ? stopSpeed : speed; 57 | var frictionDrop = control * friction * Time.deltaTime; 58 | velocity *= Mathf.Max(speed - frictionDrop, 0f) / speed; 59 | } 60 | } 61 | 62 | private void applyAcceleration(float accelerationValue) { 63 | var accelerationDirection = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical")).normalized; 64 | var yRotation = Quaternion.Euler(0f, cameraTransform.eulerAngles.y, 0f); 65 | accelerationDirection = yRotation * accelerationDirection; 66 | 67 | Debug.DrawLine(transform.position, transform.position + accelerationDirection, Color.red); 68 | 69 | var speedTowardsAcceleration = Vector3.Dot(velocity, accelerationDirection); 70 | 71 | if (speedTowardsAcceleration < maxSpeed) { 72 | float speedGain = accelerationValue * Time.deltaTime; 73 | velocity += accelerationDirection * Mathf.Min(maxSpeed - speedTowardsAcceleration, speedGain); 74 | } 75 | } 76 | 77 | private bool VerifyCharacterGroundedness() { 78 | return DowncastFromPosition(transform.position, 1.02f + characterController.skinWidth); 79 | } 80 | 81 | private bool DowncastFromPosition(Vector3 position, float maxDistance) { 82 | if (Physics.Raycast(position, Vector3.down, maxDistance)) 83 | return true; 84 | else 85 | return false; 86 | } 87 | } 88 | --------------------------------------------------------------------------------