123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
- #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
- using Animancer.Units;
- using System;
- using UnityEngine;
- using static Animancer.Validate;
- namespace Animancer.Examples.AnimatorControllers.GameKit
- {
- /// <summary>The stats and logic for moving a <see cref="Character"/>.</summary>
- /// <example><see href="https://kybernetik.com.au/animancer/docs/examples/animator-controllers/3d-game-kit">3D Game Kit</see></example>
- /// https://kybernetik.com.au/animancer/api/Animancer.Examples.AnimatorControllers.GameKit/CharacterMovement
- ///
- public sealed class CharacterMovement : MonoBehaviour
- {
- /************************************************************************************************************************/
- [SerializeField] private Character _Character;
- [SerializeField] private CharacterController _CharacterController;
- [SerializeField] private bool _FullMovementControl = true;
- /************************************************************************************************************************/
- [SerializeField, MetersPerSecond(Rule = Value.IsNotNegative)]
- private float _MaxSpeed = 8;
- public float MaxSpeed => _MaxSpeed;
- [SerializeField, MetersPerSecondPerSecond(Rule = Value.IsNotNegative)]
- private float _Acceleration = 20;
- public float Acceleration => _Acceleration;
- [SerializeField, MetersPerSecondPerSecond(Rule = Value.IsNotNegative)]
- private float _Deceleration = 25;
- public float Deceleration => _Deceleration;
- [SerializeField, DegreesPerSecond(Rule = Value.IsNotNegative)]
- private float _MinTurnSpeed = 400;
- public float MinTurnSpeed => _MinTurnSpeed;
- [SerializeField, DegreesPerSecond(Rule = Value.IsNotNegative)]
- private float _MaxTurnSpeed = 1200;
- public float MaxTurnSpeed => _MaxTurnSpeed;
- [SerializeField, MetersPerSecondPerSecond(Rule = Value.IsNotNegative)]
- private float _Gravity = 20;
- public float Gravity => _Gravity;
- [SerializeField, Multiplier(Rule = Value.IsNotNegative)]
- private float _StickingGravityProportion = 0.3f;
- public float StickingGravityProportion => _StickingGravityProportion;
- /************************************************************************************************************************/
- public bool IsGrounded { get; private set; }
- public Material GroundMaterial { get; private set; }
- /************************************************************************************************************************/
- public void UpdateSpeedControl()
- {
- var movement = _Character.Parameters.MovementDirection;
- _Character.Parameters.DesiredForwardSpeed = movement.magnitude * MaxSpeed;
- var deltaSpeed = movement != default ? Acceleration : Deceleration;
- _Character.Parameters.ForwardSpeed = Mathf.MoveTowards(
- _Character.Parameters.ForwardSpeed,
- _Character.Parameters.DesiredForwardSpeed,
- deltaSpeed * Time.deltaTime);
- }
- /************************************************************************************************************************/
- public float CurrentTurnSpeed
- {
- get
- {
- return Mathf.Lerp(
- MaxTurnSpeed,
- MinTurnSpeed,
- _Character.Parameters.ForwardSpeed / _Character.Parameters.DesiredForwardSpeed);
- }
- }
- /************************************************************************************************************************/
- public bool GetTurnAngles(Vector3 direction, out float currentAngle, out float targetAngle)
- {
- if (direction == default)
- {
- currentAngle = float.NaN;
- targetAngle = float.NaN;
- return false;
- }
- var transform = this.transform;
- currentAngle = transform.eulerAngles.y;
- targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;
- return true;
- }
- /************************************************************************************************************************/
- public void TurnTowards(float currentAngle, float targetAngle, float speed)
- {
- currentAngle = Mathf.MoveTowardsAngle(currentAngle, targetAngle, speed * Time.deltaTime);
- transform.eulerAngles = new Vector3(0, currentAngle, 0);
- }
- public void TurnTowards(Vector3 direction, float speed)
- {
- if (GetTurnAngles(direction, out var currentAngle, out var targetAngle))
- TurnTowards(currentAngle, targetAngle, speed);
- }
- /************************************************************************************************************************/
- private void OnAnimatorMove()
- {
- var movement = GetRootMotion();
- CheckGround(ref movement);
- UpdateGravity(ref movement);
- _CharacterController.Move(movement);
- IsGrounded = _CharacterController.isGrounded;
- transform.rotation *= _Character.Animancer.Animator.deltaRotation;
- }
- /************************************************************************************************************************/
- private Vector3 GetRootMotion()
- {
- var motion = _Character.StateMachine.CurrentState.RootMotion;
- if (!_FullMovementControl ||// If Full Movement Control is disabled in the Inspector.
- !_Character.StateMachine.CurrentState.FullMovementControl)// Or the current state does not want it.
- return motion;// Return the raw Root Motion.
- // If the Brain is not trying to move, we do not move.
- var direction = _Character.Parameters.MovementDirection;
- direction.y = 0;
- if (direction == default)
- return default;
- // Otherwise calculate the Root Motion only in the specified direction.
- direction.Normalize();
- var magnitude = Vector3.Dot(direction, motion);
- return direction * magnitude;
- }
- /************************************************************************************************************************/
- private void CheckGround(ref Vector3 movement)
- {
- if (!_CharacterController.isGrounded)
- return;
- const float GroundedRayDistance = 1f;
- var ray = new Ray(transform.position + GroundedRayDistance * 0.5f * Vector3.up, -Vector3.up);
- if (Physics.Raycast(ray, out var hit, GroundedRayDistance, Physics.AllLayers, QueryTriggerInteraction.Ignore))
- {
- // Rotate the movement to lie along the ground vector.
- movement = Vector3.ProjectOnPlane(movement, hit.normal);
- // Store the current walking surface so the correct audio is played.
- var groundRenderer = hit.collider.GetComponentInChildren<Renderer>();
- GroundMaterial = groundRenderer ? groundRenderer.sharedMaterial : null;
- }
- else
- {
- GroundMaterial = null;
- }
- }
- /************************************************************************************************************************/
- private void UpdateGravity(ref Vector3 movement)
- {
- if (_CharacterController.isGrounded && _Character.StateMachine.CurrentState.StickToGround)
- _Character.Parameters.VerticalSpeed = -Gravity * StickingGravityProportion;
- else
- _Character.Parameters.VerticalSpeed -= Gravity * Time.deltaTime;
- movement.y += _Character.Parameters.VerticalSpeed * Time.deltaTime;
- }
- /************************************************************************************************************************/
- // Ignore these Animation Events because the attack animations will only start when we tell them to, so it
- // would be silly to use additional events for something we already directly caused. That sort of thing is only
- // necessary in Animator Controllers because they run their own logic to decide what they want to do.
- private void MeleeAttackStart(int throwing = 0) { }
- private void MeleeAttackEnd() { }
- /************************************************************************************************************************/
- }
- }
|