Skip to content

Commit

Permalink
Improve player movement (#356)
Browse files Browse the repository at this point in the history
* Fix terminal velocity for falling, fix jumping direction

- There is now 0 drag on Y for players falling, giving no terminal velocity
- Remove quirk with floating point rounding by applying fixedUpdate too early

* Fix maxVelocity

* Add queueable crouching in air for more fluid crouching

* Add dashing with consecutive leaps
  • Loading branch information
Fueredoriku authored Dec 2, 2023
1 parent fd3b0f6 commit df55830
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 52 deletions.
19 changes: 11 additions & 8 deletions Assets/Prefabs/Input/Player.prefab
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,19 @@ MonoBehaviour:
serializedVersion: 2
m_Bits: 65335
lookSpeed: 3
maxVelocity: 10
groundDrag: 6
airDrag: 1
maxVelocityBeforeExtraDamping: 10
extraDamping: 25
strafeForceGrounded: 60
strafeForceCrouched: 30
strafeForceInAir: 16
drag: 6
airDrag: 2
strafeForceInAir: 30
jumpForce: 10
leapForce: 12.5
jumpTimeout: 0.5
leapTimeout: 0.875
leapTimeout: 0.25
dashHeightMultiplier: 0.5
dashForwardMultiplier: 1
dashDamping: 0.5
crouchedHeightOffset: 0.8
airThreshold: 0.4
slopeAngleThreshold: 50
Expand Down Expand Up @@ -145,8 +148,8 @@ Rigidbody:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 71015680234268067}
serializedVersion: 4
m_Mass: 10
m_Drag: 4
m_Mass: 5
m_Drag: 0
m_AngularDrag: 0.05
m_CenterOfMass: {x: 0, y: 0, z: 0}
m_InertiaTensor: {x: 1, y: 1, z: 1}
Expand Down
131 changes: 87 additions & 44 deletions Assets/Scripts/Control&Input/PlayerMovement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,22 @@ public class PlayerMovement : MonoBehaviour
[SerializeField]
private float lookSpeed = 3;

[Header("Drag")]
[SerializeField]
private float maxVelocity = 10;
private float groundDrag = 6f;

private float strafeForce;
[SerializeField]
private float airDrag = 2f;

[SerializeField]
private float maxVelocityBeforeExtraDamping = 20f;

[SerializeField]
private float extraDamping = 25f;

private float dragForce = 0;

[Header("Strafe")]
[SerializeField]
private float strafeForceGrounded = 60;

Expand All @@ -37,33 +48,36 @@ public class PlayerMovement : MonoBehaviour
[SerializeField]
private float strafeForceInAir = 16;

private float strafeForce;

[Header("Jumping")]
[SerializeField]
private float drag = 6f;
private float jumpForce = 10;

[SerializeField]
private float airDrag = 2f;
private float leapForce = 12.5f;

[SerializeField]
private float jumpForce = 10;
private float leapTimeout = 0.25f;

[SerializeField]
private float leapForce = 12.5f;
private float dashHeightMultiplier = 0.5f;

[SerializeField]
private float jumpTimeout = 0.5f;
private float dashForwardMultiplier = 1.5f;

[SerializeField]
private float leapTimeout = 0.875f;
private float dashDamping = 4f;

private const float MarsGravity = 3.72076f;
[SerializeField]
private float minDashVelocity = 8f;

private bool canJump = true;
private bool isDashing = false;

[Header("State")]
[SerializeField]
private float crouchedHeightOffset = 0.3f;

private float localCameraHeight;

[SerializeField]
private float airThreshold = 0.4f;

Expand All @@ -76,8 +90,15 @@ public class PlayerMovement : MonoBehaviour
[SerializeField]
private Animator animator;

private const float MarsGravity = 3.7f;

private float localCameraHeight;

private Vector2 aimAngle = Vector2.zero;

private delegate void MovementEvent();
private MovementEvent onLanding;

void Start()
{
body = GetComponent<Rigidbody>();
Expand All @@ -93,67 +114,78 @@ public void SetPlayerInput(InputManager player)
{
inputManager = player;
inputManager.onSelect += OnJump;
inputManager.onCrouchPerformed += SetCrouch;
inputManager.onCrouchCanceled += SetCrouch;
inputManager.onCrouchPerformed += OnCrouch;
inputManager.onCrouchCanceled += OnCrouch;
localCameraHeight = inputManager.transform.localPosition.y;
}

private void OnJump(InputAction.CallbackContext ctx)
{
if (!(canJump && state == PlayerState.GROUNDED))
if (!(state == PlayerState.GROUNDED))
return;

// Leap jump
// Leap/dash jump
if (animator.GetBool("Crouching"))
{
body.AddForce(Vector3.up * leapForce, ForceMode.VelocityChange);
body.AddForce(Vector3.up * leapForce * (isDashing ? dashHeightMultiplier : 1f), ForceMode.VelocityChange);
Vector3 forwardDirection = new Vector3(inputManager.transform.forward.x, 0, inputManager.transform.forward.z);
body.AddForce(forwardDirection * leapForce, ForceMode.VelocityChange);
body.AddForce(forwardDirection * leapForce * (isDashing ? dashForwardMultiplier : 1f), ForceMode.VelocityChange);
animator.SetTrigger("Leap");
StartCoroutine(JumpTimeout(leapTimeout));
onLanding += EnableDash;
return;
}

// Normal jump
body.AddForce(Vector3.up * jumpForce, ForceMode.VelocityChange);
StartCoroutine(JumpTimeout(jumpTimeout));
}

private void SetCrouch(InputAction.CallbackContext ctx)
private void OnCrouch(InputAction.CallbackContext ctx)
{
if (LeanTween.isTweening(inputManager.gameObject))
{
LeanTween.cancel(inputManager.gameObject);
inputManager.transform.localPosition = new Vector3(inputManager.transform.localPosition.x, localCameraHeight, inputManager.transform.localPosition.z);
}


if (ctx.performed)
{
if (IsInAir())
{
onLanding += StartCrouch;
return;
animator.SetBool("Crouching", true);
strafeForce = strafeForceCrouched;
inputManager.gameObject.LeanMoveLocalY(localCameraHeight - crouchedHeightOffset, 0.2f);
}
StartCrouch();
}

if (ctx.canceled)
{
animator.SetBool("Crouching", false);
strafeForce = strafeForceGrounded;
inputManager.gameObject.LeanMoveLocalY(localCameraHeight, 0.2f);
isDashing = false;
onLanding -= StartCrouch;
}

}

private void StartCrouch()
{
animator.SetBool("Crouching", true);
strafeForce = strafeForceCrouched;
inputManager.gameObject.LeanMoveLocalY(localCameraHeight - crouchedHeightOffset, 0.2f);
}

private void EnableDash()
{
StartCoroutine(JumpTimeout(leapTimeout));
}

private IEnumerator JumpTimeout(float time)
{
canJump = false;
yield return new WaitForSeconds(time);
canJump = true;
isDashing = IsInAir();
onLanding -= EnableDash;
}


private bool IsInAir()
{
// Cast a box to detect (partial) ground. See OnDrawGizmos for what I think is the extent of the box cast.
Expand All @@ -175,8 +207,6 @@ private Vector3 GroundNormal()
return Vector3.up;
}



private void UpdatePosition(Vector3 input)
{
// Modify input to addforce with relation to current rotation.
Expand All @@ -185,22 +215,23 @@ private void UpdatePosition(Vector3 input)
{
case PlayerState.IN_AIR:
{
// Strafe slightly with less drag.
body.drag = airDrag;
body.AddForce(input * strafeForceInAir, ForceMode.VelocityChange);
body.AddForce(Vector3.down * MarsGravity, ForceMode.Acceleration);
if (!IsInAir()) state = PlayerState.GROUNDED;
dragForce = airDrag;
body.AddForce(input * strafeForceInAir * Time.deltaTime, ForceMode.VelocityChange);
body.AddForce(Vector3.down * MarsGravity * Time.deltaTime, ForceMode.Acceleration);
if (IsInAir())
break;
state = PlayerState.GROUNDED;
onLanding?.Invoke();
break;
}
case PlayerState.GROUNDED:
{
// Strafe normally with heavy drag.
body.drag = drag;
dragForce = groundDrag;
// Walk along ground normal (adjusted if on heavy slope).
var groundNormal = GroundNormal();
var direction = Vector3.ProjectOnPlane(input, groundNormal);
body.AddForce(direction * strafeForce, ForceMode.VelocityChange);
body.AddForce(direction * strafeForce, ForceMode.Impulse);
body.AddForce(direction * strafeForce * Time.deltaTime, ForceMode.VelocityChange);
body.AddForce(direction * strafeForce * Time.deltaTime, ForceMode.Impulse);
if (IsInAir()) state = PlayerState.IN_AIR;
break;
}
Expand All @@ -210,6 +241,8 @@ private void UpdatePosition(Vector3 input)
break;
}
}
var yDrag = body.velocity.y < 0 ? 0f : body.velocity.y;
body.AddForce(-dragForce * body.mass * new Vector3(body.velocity.x, yDrag, body.velocity.z), ForceMode.Force);
}

private void UpdateRotation()
Expand All @@ -226,8 +259,8 @@ private void UpdateRotation()

private void UpdateAnimatorParameters()
{
animator.SetFloat("Forward", Vector3.Dot(body.velocity, transform.forward) / maxVelocity);
animator.SetFloat("Right", Vector3.Dot(body.velocity, transform.right) / maxVelocity);
animator.SetFloat("Forward", Vector3.Dot(body.velocity, transform.forward) / maxVelocityBeforeExtraDamping);
animator.SetFloat("Right", Vector3.Dot(body.velocity, transform.right) / maxVelocityBeforeExtraDamping);
}

void OnDrawGizmos()
Expand All @@ -241,11 +274,21 @@ void OnDrawGizmos()
void FixedUpdate()
{
var positionInput = new Vector3(inputManager.moveInput.x, 0, inputManager.moveInput.y);
UpdatePosition(positionInput * Time.deltaTime);
UpdatePosition(positionInput);
// Limit velocity when not grounded
if (state == PlayerState.GROUNDED)
return;
body.velocity = new Vector3(Mathf.Clamp(body.velocity.x, -maxVelocity, maxVelocity), body.velocity.y, Mathf.Clamp(body.velocity.z, -maxVelocity, maxVelocity));

if (isDashing)
{
var directionalForces = new Vector3(body.velocity.x, 0, body.velocity.z);
if (directionalForces.magnitude < minDashVelocity)
isDashing = false;
}
// Add extra drag when player velocity is too high
var maxVelocityReached = Mathf.Abs(body.velocity.x) > maxVelocityBeforeExtraDamping || Mathf.Abs(body.velocity.z) > maxVelocityBeforeExtraDamping;
if (maxVelocityReached)
body.AddForce(-(isDashing ? dashDamping : extraDamping) * body.mass * new Vector3(body.velocity.x, 0, body.velocity.z), ForceMode.Force);
}

void Update()
Expand Down

0 comments on commit df55830

Please sign in to comment.