// 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.Examples.FineControl;
using Animancer.Units;
using UnityEngine;
namespace Animancer.Examples.Locomotion
{
///
/// Controls a with a and to allow
/// the bot to move around the scene in any direction.
///
/// Directional Blending
/// https://kybernetik.com.au/animancer/api/Animancer.Examples.Locomotion/SpiderBotController
///
[AddComponentMenu(Strings.ExamplesMenuPrefix + "Locomotion - Spider Bot Controller")]
[HelpURL(Strings.DocsURLs.ExampleAPIDocumentation + nameof(Locomotion) + "/" + nameof(SpiderBotController))]
public sealed class SpiderBotController : MonoBehaviour
{
/************************************************************************************************************************/
[SerializeField] private SpiderBot _SpiderBot;
[SerializeField] private Rigidbody _Body;
[SerializeField, DegreesPerSecond] private float _TurnSpeed = 90;
[SerializeField, MetersPerSecond] private float _MovementSpeed = 1.5f;
[SerializeField, Multiplier] private float _SprintMultiplier = 2;
/************************************************************************************************************************/
private MixerState _MoveState;
private Vector3 _MovementDirection;
/************************************************************************************************************************/
private void Awake()
{
// _SpiderBot.Move is an ITransition which doesn't have a Parameter property for us to control in Update.
// So we need to create its state, type cast it to MixerState, and store it in a field.
// Then we will be able to control that field's Parameter in Update.
var state = _SpiderBot.Animancer.States.GetOrCreate(_SpiderBot.Move);
_MoveState = (MixerState)state;
}
/************************************************************************************************************************/
private void Update()
{
// Calculate the movement direction.
_MovementDirection = GetMovementDirection();
// The bot should be moving whenever the direction isn't zero.
_SpiderBot.IsMoving = _MovementDirection != default;
// If the movement state is playing and not fading out:
if (_MoveState.IsActive)
{
// Rotate towards the same angle around the Y axis as the camera.
var eulerAngles = transform.eulerAngles;
var targetEulerY = Camera.main.transform.eulerAngles.y;
eulerAngles.y = Mathf.MoveTowardsAngle(eulerAngles.y, targetEulerY, _TurnSpeed * Time.deltaTime);
transform.eulerAngles = eulerAngles;
// The movement direction is in world space, so we need to convert it to the bot's local space to be
// appropriate for its current rotation. We do this by using dot-products to determine how much of that
// direction lies along each axis. This would be unnecessary if we did not rotate at all.
_MoveState.Parameter = new Vector2(
Vector3.Dot(transform.right, _MovementDirection),
Vector3.Dot(transform.forward, _MovementDirection));
// Set its speed depending on whether you are sprinting or not.
_MoveState.Speed = ExampleInput.LeftMouseHold ? _SprintMultiplier : 1;
}
else// Otherwise stop it entirely.
{
_MoveState.Parameter = default;
_MoveState.Speed = 0;
}
}
/************************************************************************************************************************/
private Vector3 GetMovementDirection()
{
// Get a ray from the main camera in the direction of the mouse cursor.
var ray = Camera.main.ScreenPointToRay(ExampleInput.MousePosition);
// Do a raycast with it and stop trying to move it it does not hit anything.
// Note that this object is set to the Ignore Raycast layer so that the raycast will not hit it.
if (!Physics.Raycast(ray, out var raycastHit))// Note the exclamation mark !
return default;
// If the ray hit something, calculate the horizontal direction from this object to that point.
var direction = raycastHit.point - transform.position;
direction.y = 0;
// Calculate how far we could move this frame at max speed.
var movementThisFrame = _MovementSpeed * _SprintMultiplier * Time.fixedDeltaTime;
// If we are close to the destination, stop moving.
var distance = direction.magnitude;
if (distance <= movementThisFrame)
{
return default;
}
else
{
// Otherwise normalize the direction so that we do not change speed based on distance.
// Calling direction.Normalize() would do the same thing, but would calculate the magnitude again.
return direction / distance;
}
}
/************************************************************************************************************************/
private void FixedUpdate()
{
// Set the velocity so that Unity will move the Rigidbody in the desired direction.
_Body.velocity = _MoveState.Speed * _MovementSpeed * _MovementDirection;
}
/************************************************************************************************************************/
}
}