You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

286 lines
9.6 KiB
C#

3 weeks ago
using System;
using Unity.BossRoom.Gameplay.Configuration;
using Unity.BossRoom.Navigation;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Assertions;
namespace Unity.BossRoom.Gameplay.GameplayObjects.Character
{
public enum MovementState
{
Idle = 0,
PathFollowing = 1,
Charging = 2,
Knockback = 3,
}
/// <summary>
/// Component responsible for moving a character on the server side based on inputs.
/// </summary>
/*[RequireComponent(typeof(NetworkCharacterState), typeof(NavMeshAgent), typeof(ServerCharacter)), RequireComponent(typeof(Rigidbody))]*/
public class ServerCharacterMovement : NetworkBehaviour
{
[SerializeField]
NavMeshAgent m_NavMeshAgent;
[SerializeField]
Rigidbody m_Rigidbody;
private NavigationSystem m_NavigationSystem;
private DynamicNavPath m_NavPath;
private MovementState m_MovementState;
MovementStatus m_PreviousState;
[SerializeField]
private ServerCharacter m_CharLogic;
// when we are in charging and knockback mode, we use these additional variables
private float m_ForcedSpeed;
private float m_SpecialModeDurationRemaining;
// this one is specific to knockback mode
private Vector3 m_KnockbackVector;
#if UNITY_EDITOR || DEVELOPMENT_BUILD
public bool TeleportModeActivated { get; set; }
const float k_CheatSpeed = 20;
public bool SpeedCheatActivated { get; set; }
#endif
void Awake()
{
// disable this NetworkBehavior until it is spawned
enabled = false;
}
public override void OnNetworkSpawn()
{
if (IsServer)
{
// Only enable server component on servers
enabled = true;
// On the server enable navMeshAgent and initialize
m_NavMeshAgent.enabled = true;
m_NavigationSystem = GameObject.FindGameObjectWithTag(NavigationSystem.NavigationSystemTag).GetComponent<NavigationSystem>();
m_NavPath = new DynamicNavPath(m_NavMeshAgent, m_NavigationSystem);
}
}
/// <summary>
/// Sets a movement target. We will path to this position, avoiding static obstacles.
/// </summary>
/// <param name="position">Position in world space to path to. </param>
public void SetMovementTarget(Vector3 position)
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
if (TeleportModeActivated)
{
Teleport(position);
return;
}
#endif
m_MovementState = MovementState.PathFollowing;
m_NavPath.SetTargetPosition(position);
}
public void StartForwardCharge(float speed, float duration)
{
m_NavPath.Clear();
m_MovementState = MovementState.Charging;
m_ForcedSpeed = speed;
m_SpecialModeDurationRemaining = duration;
}
public void StartKnockback(Vector3 knocker, float speed, float duration)
{
m_NavPath.Clear();
m_MovementState = MovementState.Knockback;
m_KnockbackVector = transform.position - knocker;
m_ForcedSpeed = speed;
m_SpecialModeDurationRemaining = duration;
}
/// <summary>
/// Follow the given transform until it is reached.
/// </summary>
/// <param name="followTransform">The transform to follow</param>
public void FollowTransform(Transform followTransform)
{
m_MovementState = MovementState.PathFollowing;
m_NavPath.FollowTransform(followTransform);
}
/// <summary>
/// Returns true if the current movement-mode is unabortable (e.g. a knockback effect)
/// </summary>
/// <returns></returns>
public bool IsPerformingForcedMovement()
{
return m_MovementState == MovementState.Knockback || m_MovementState == MovementState.Charging;
}
/// <summary>
/// Returns true if the character is actively moving, false otherwise.
/// </summary>
/// <returns></returns>
public bool IsMoving()
{
return m_MovementState != MovementState.Idle;
}
/// <summary>
/// Cancels any moves that are currently in progress.
/// </summary>
public void CancelMove()
{
if (m_NavPath != null)
{
m_NavPath.Clear();
}
m_MovementState = MovementState.Idle;
}
/// <summary>
/// Instantly moves the character to a new position. NOTE: this cancels any active movement operation!
/// This does not notify the client that the movement occurred due to teleportation, so that needs to
/// happen in some other way, such as with the custom action visualization in DashAttackActionFX. (Without
/// this, the clients will animate the character moving to the new destination spot, rather than instantly
/// appearing in the new spot.)
/// </summary>
/// <param name="newPosition">new coordinates the character should be at</param>
public void Teleport(Vector3 newPosition)
{
CancelMove();
if (!m_NavMeshAgent.Warp(newPosition))
{
// warping failed! We're off the navmesh somehow. Weird... but we can still teleport
Debug.LogWarning($"NavMeshAgent.Warp({newPosition}) failed!", gameObject);
transform.position = newPosition;
}
m_Rigidbody.position = transform.position;
m_Rigidbody.rotation = transform.rotation;
}
private void FixedUpdate()
{
PerformMovement();
var currentState = GetMovementStatus(m_MovementState);
if (m_PreviousState != currentState)
{
m_CharLogic.MovementStatus.Value = currentState;
m_PreviousState = currentState;
}
}
public override void OnNetworkDespawn()
{
if (m_NavPath != null)
{
m_NavPath.Dispose();
}
if (IsServer)
{
// Disable server components when despawning
enabled = false;
m_NavMeshAgent.enabled = false;
}
}
private void PerformMovement()
{
if (m_MovementState == MovementState.Idle)
return;
Vector3 movementVector;
if (m_MovementState == MovementState.Charging)
{
// if we're done charging, stop moving
m_SpecialModeDurationRemaining -= Time.fixedDeltaTime;
if (m_SpecialModeDurationRemaining <= 0)
{
m_MovementState = MovementState.Idle;
return;
}
var desiredMovementAmount = m_ForcedSpeed * Time.fixedDeltaTime;
movementVector = transform.forward * desiredMovementAmount;
}
else if (m_MovementState == MovementState.Knockback)
{
m_SpecialModeDurationRemaining -= Time.fixedDeltaTime;
if (m_SpecialModeDurationRemaining <= 0)
{
m_MovementState = MovementState.Idle;
return;
}
var desiredMovementAmount = m_ForcedSpeed * Time.fixedDeltaTime;
movementVector = m_KnockbackVector * desiredMovementAmount;
}
else
{
var desiredMovementAmount = GetBaseMovementSpeed() * Time.fixedDeltaTime;
movementVector = m_NavPath.MoveAlongPath(desiredMovementAmount);
// If we didn't move stop moving.
if (movementVector == Vector3.zero)
{
m_MovementState = MovementState.Idle;
return;
}
}
m_NavMeshAgent.Move(movementVector);
transform.rotation = Quaternion.LookRotation(movementVector);
// After moving adjust the position of the dynamic rigidbody.
m_Rigidbody.position = transform.position;
m_Rigidbody.rotation = transform.rotation;
}
/// <summary>
/// Retrieves the speed for this character's class.
/// </summary>
private float GetBaseMovementSpeed()
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
if (SpeedCheatActivated)
{
return k_CheatSpeed;
}
#endif
CharacterClass characterClass = GameDataSource.Instance.CharacterDataByType[m_CharLogic.CharacterType];
Assert.IsNotNull(characterClass, $"No CharacterClass data for character type {m_CharLogic.CharacterType}");
return characterClass.Speed;
}
/// <summary>
/// Determines the appropriate MovementStatus for the character. The
/// MovementStatus is used by the client code when animating the character.
/// </summary>
private MovementStatus GetMovementStatus(MovementState movementState)
{
switch (movementState)
{
case MovementState.Idle:
return MovementStatus.Idle;
case MovementState.Knockback:
return MovementStatus.Uncontrolled;
default:
return MovementStatus.Normal;
}
}
}
}