using System; using System.Collections; using UnityEngine; namespace RootMotion.Demos { public class CharacterThirdPerson : CharacterBase { [Serializable] public enum MoveMode { Directional = 0, Strafe = 1 } public struct AnimState { public Vector3 moveDirection; public bool jump; public bool crouch; public bool onGround; public bool isStrafing; public float yVelocity; public bool doubleJump; } [Header("References")] public CharacterAnimationBase characterAnimation; public UserControlThirdPerson userControl; public CameraController cam; [Header("Movement")] public MoveMode moveMode; public bool smoothPhysics = true; public float smoothAccelerationTime = 0.2f; public float linearAccelerationSpeed = 3f; public float platformFriction = 7f; public float groundStickyEffect = 4f; public float maxVerticalVelocityOnGround = 3f; public float velocityToGroundTangentWeight; [Header("Rotation")] public bool lookInCameraDirection; public float turnSpeed = 5f; public float stationaryTurnSpeedMlp = 1f; [Header("Jumping and Falling")] public bool smoothJump = true; public float airSpeed = 6f; public float airControl = 2f; public float jumpPower = 12f; public float jumpRepeatDelayTime; public bool doubleJumpEnabled; public float doubleJumpPowerMlp = 1f; [Header("Wall Running")] public LayerMask wallRunLayers; public float wallRunMaxLength = 1f; public float wallRunMinMoveMag = 0.6f; public float wallRunMinVelocityY = -1f; public float wallRunRotationSpeed = 1.5f; public float wallRunMaxRotationAngle = 70f; public float wallRunWeightSpeed = 5f; [Header("Crouching")] public float crouchCapsuleScaleMlp = 0.6f; public AnimState animState; protected Vector3 moveDirection; private Animator animator; private Vector3 normal; private Vector3 platformVelocity; private Vector3 platformAngularVelocity; private RaycastHit hit; private float jumpLeg; private float jumpEndTime; private float forwardMlp; private float groundDistance; private float lastAirTime; private float stickyForce; private Vector3 wallNormal = Vector3.up; private Vector3 moveDirectionVelocity; private float wallRunWeight; private float lastWallRunWeight; private float fixedDeltaTime; private Vector3 fixedDeltaPosition; private Quaternion fixedDeltaRotation = Quaternion.identity; private bool fixedFrame; private float wallRunEndTime; private Vector3 gravity; private Vector3 verticalVelocity; private float velocityY; private bool doubleJumped; private bool jumpReleased; public bool fullRootMotion { get; set; } public bool onGround { get; private set; } protected override void Start() { base.Start(); animator = GetComponent(); if (animator == null) { animator = characterAnimation.GetComponent(); } wallNormal = -gravity.normalized; onGround = true; animState.onGround = true; if (cam != null) { cam.enabled = false; } } private void OnAnimatorMove() { Move(animator.deltaPosition, animator.deltaRotation); } public override void Move(Vector3 deltaPosition, Quaternion deltaRotation) { fixedDeltaTime += Time.deltaTime; fixedDeltaPosition += deltaPosition; fixedDeltaRotation *= deltaRotation; } private void FixedUpdate() { gravity = (fullRootMotion ? Vector3.zero : GetGravity()); verticalVelocity = V3Tools.ExtractVertical(r.linearVelocity, gravity, 1f); velocityY = verticalVelocity.magnitude; if (Vector3.Dot(verticalVelocity, gravity) > 0f) { velocityY = 0f - velocityY; } r.interpolation = (smoothPhysics ? RigidbodyInterpolation.Interpolate : RigidbodyInterpolation.None); characterAnimation.smoothFollow = smoothPhysics; MoveFixed(fixedDeltaPosition); fixedDeltaTime = 0f; fixedDeltaPosition = Vector3.zero; r.MoveRotation(base.transform.rotation * fixedDeltaRotation); fixedDeltaRotation = Quaternion.identity; Rotate(); GroundCheck(); if (userControl.state.move == Vector3.zero && groundDistance < airborneThreshold * 0.5f) { HighFriction(); } else { ZeroFriction(); } bool flag = !fullRootMotion && onGround && userControl.state.move == Vector3.zero && r.linearVelocity.magnitude < 0.5f && groundDistance < airborneThreshold * 0.5f; if (gravityTarget != null) { r.useGravity = false; if (!flag) { r.AddForce(gravity); } } if (flag) { r.useGravity = false; r.linearVelocity = Vector3.zero; } else if (gravityTarget == null) { r.useGravity = true; } if (onGround) { animState.jump = Jump(); jumpReleased = false; doubleJumped = false; } else { if (!userControl.state.jump) { jumpReleased = true; } if (jumpReleased && userControl.state.jump && !doubleJumped && doubleJumpEnabled) { jumpEndTime = Time.time + 0.1f; animState.doubleJump = true; Vector3 linearVelocity = userControl.state.move * airSpeed; r.linearVelocity = linearVelocity; r.linearVelocity += base.transform.up * jumpPower * doubleJumpPowerMlp; doubleJumped = true; } } ScaleCapsule(userControl.state.crouch ? crouchCapsuleScaleMlp : 1f); fixedFrame = true; } protected virtual void Update() { animState.onGround = onGround; animState.moveDirection = GetMoveDirection(); animState.yVelocity = Mathf.Lerp(animState.yVelocity, velocityY, Time.deltaTime * 10f); animState.crouch = userControl.state.crouch; animState.isStrafing = moveMode == MoveMode.Strafe; } protected virtual void LateUpdate() { if (!(cam == null)) { cam.UpdateInput(); if (fixedFrame || r.interpolation != RigidbodyInterpolation.None) { cam.UpdateTransform((r.interpolation == RigidbodyInterpolation.None) ? Time.fixedDeltaTime : Time.deltaTime); fixedFrame = false; } } } private void MoveFixed(Vector3 deltaPosition) { WallRun(); Vector3 vector = ((fixedDeltaTime > 0f) ? (deltaPosition / fixedDeltaTime) : Vector3.zero); if (!fullRootMotion) { vector += V3Tools.ExtractHorizontal(platformVelocity, gravity, 1f); if (onGround) { if (velocityToGroundTangentWeight > 0f) { Quaternion b = Quaternion.FromToRotation(base.transform.up, normal); vector = Quaternion.Lerp(Quaternion.identity, b, velocityToGroundTangentWeight) * vector; } } else { Vector3 b2 = V3Tools.ExtractHorizontal(userControl.state.move * airSpeed, gravity, 1f); vector = Vector3.Lerp(r.linearVelocity, b2, Time.deltaTime * airControl); } if (onGround && Time.time > jumpEndTime) { r.linearVelocity -= base.transform.up * stickyForce * Time.deltaTime; } Vector3 vector2 = V3Tools.ExtractVertical(r.linearVelocity, gravity, 1f); Vector3 vector3 = V3Tools.ExtractHorizontal(vector, gravity, 1f); if (onGround && Vector3.Dot(vector2, gravity) < 0f) { vector2 = Vector3.ClampMagnitude(vector2, maxVerticalVelocityOnGround); } r.linearVelocity = vector3 + vector2; } else { r.linearVelocity = vector; } forwardMlp = 1f; } private void WallRun() { bool flag = CanWallRun(); if (wallRunWeight > 0f && !flag) { wallRunEndTime = Time.time; } if (Time.time < wallRunEndTime + 0.5f) { flag = false; } wallRunWeight = Mathf.MoveTowards(wallRunWeight, flag ? 1f : 0f, Time.deltaTime * wallRunWeightSpeed); if (wallRunWeight <= 0f && lastWallRunWeight > 0f) { Vector3 forward = V3Tools.ExtractHorizontal(base.transform.forward, gravity, 1f); base.transform.rotation = Quaternion.LookRotation(forward, -gravity); wallNormal = -gravity.normalized; } lastWallRunWeight = wallRunWeight; if (!(wallRunWeight <= 0f)) { if (onGround && velocityY < 0f) { r.linearVelocity = V3Tools.ExtractHorizontal(r.linearVelocity, gravity, 1f); } Vector3 vector = V3Tools.ExtractHorizontal(base.transform.forward, gravity, 1f); RaycastHit hitInfo = new RaycastHit { normal = -gravity.normalized }; Physics.Raycast(onGround ? base.transform.position : capsule.bounds.center, vector, out hitInfo, 3f, wallRunLayers); wallNormal = Vector3.Lerp(wallNormal, hitInfo.normal, Time.deltaTime * wallRunRotationSpeed); wallNormal = Vector3.RotateTowards(-gravity.normalized, wallNormal, wallRunMaxRotationAngle * (MathF.PI / 180f), 0f); Vector3 tangent = base.transform.forward; Vector3 vector2 = wallNormal; Vector3.OrthoNormalize(ref vector2, ref tangent); base.transform.rotation = Quaternion.Slerp(Quaternion.LookRotation(vector, -gravity), Quaternion.LookRotation(tangent, wallNormal), wallRunWeight); } } private bool CanWallRun() { if (fullRootMotion) { return false; } if (Time.time < jumpEndTime - 0.1f) { return false; } if (Time.time > jumpEndTime - 0.1f + wallRunMaxLength) { return false; } if (velocityY < wallRunMinVelocityY) { return false; } if (userControl.state.move.magnitude < wallRunMinMoveMag) { return false; } return true; } private Vector3 GetMoveDirection() { switch (moveMode) { case MoveMode.Directional: moveDirection = Vector3.SmoothDamp(moveDirection, new Vector3(0f, 0f, userControl.state.move.magnitude), ref moveDirectionVelocity, smoothAccelerationTime); moveDirection = Vector3.MoveTowards(moveDirection, new Vector3(0f, 0f, userControl.state.move.magnitude), Time.deltaTime * linearAccelerationSpeed); return moveDirection * forwardMlp; case MoveMode.Strafe: moveDirection = Vector3.SmoothDamp(moveDirection, userControl.state.move, ref moveDirectionVelocity, smoothAccelerationTime); moveDirection = Vector3.MoveTowards(moveDirection, userControl.state.move, Time.deltaTime * linearAccelerationSpeed); return base.transform.InverseTransformDirection(moveDirection); default: return Vector3.zero; } } protected virtual void Rotate() { if (gravityTarget != null) { r.MoveRotation(Quaternion.FromToRotation(base.transform.up, base.transform.position - gravityTarget.position) * base.transform.rotation); } if (platformAngularVelocity != Vector3.zero) { r.MoveRotation(Quaternion.Euler(platformAngularVelocity) * base.transform.rotation); } float num = GetAngleFromForward(GetForwardDirection()); if (userControl.state.move == Vector3.zero) { num *= (1.01f - Mathf.Abs(num) / 180f) * stationaryTurnSpeedMlp; } r.MoveRotation(Quaternion.AngleAxis(num * Time.deltaTime * turnSpeed, base.transform.up) * r.rotation); } private Vector3 GetForwardDirection() { bool flag = userControl.state.move != Vector3.zero; switch (moveMode) { case MoveMode.Directional: if (flag) { return userControl.state.move; } if (!lookInCameraDirection) { return base.transform.forward; } return userControl.state.lookPos - r.position; case MoveMode.Strafe: if (flag) { return userControl.state.lookPos - r.position; } if (!lookInCameraDirection) { return base.transform.forward; } return userControl.state.lookPos - r.position; default: return Vector3.zero; } } protected virtual bool Jump() { if (!userControl.state.jump) { return false; } if (userControl.state.crouch) { return false; } if (!characterAnimation.animationGrounded) { return false; } if (Time.time < lastAirTime + jumpRepeatDelayTime) { return false; } onGround = false; jumpEndTime = Time.time + 0.1f; Vector3 vector = userControl.state.move * airSpeed; vector += base.transform.up * jumpPower; if (smoothJump) { StopAllCoroutines(); StartCoroutine(JumpSmooth(vector - r.linearVelocity)); } else { r.linearVelocity = vector; } return true; } private IEnumerator JumpSmooth(Vector3 jumpVelocity) { int steps = 0; int stepsToTake = 3; while (steps < stepsToTake) { r.AddForce(jumpVelocity / stepsToTake, ForceMode.VelocityChange); steps++; yield return new WaitForFixedUpdate(); } } private void GroundCheck() { Vector3 b = Vector3.zero; platformAngularVelocity = Vector3.zero; float num = 0f; hit = GetSpherecastHit(); normal = base.transform.up; groundDistance = Vector3.Project(r.position - hit.point, base.transform.up).magnitude; if (Time.time > jumpEndTime && velocityY < jumpPower * 0.5f) { bool num2 = onGround; onGround = false; float num3 = ((!num2) ? (airborneThreshold * 0.5f) : airborneThreshold); float magnitude = V3Tools.ExtractHorizontal(r.linearVelocity, gravity, 1f).magnitude; if (groundDistance < num3) { num = groundStickyEffect * magnitude * num3; if (hit.rigidbody != null) { b = hit.rigidbody.GetPointVelocity(hit.point); platformAngularVelocity = Vector3.Project(hit.rigidbody.angularVelocity, base.transform.up); } onGround = true; } } platformVelocity = Vector3.Lerp(platformVelocity, b, Time.deltaTime * platformFriction); if (fullRootMotion) { stickyForce = 0f; } stickyForce = num; if (!onGround) { lastAirTime = Time.time; } } } }