/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.Character
{
using Opsive.Shared.Events;
using Opsive.Shared.Game;
using Opsive.Shared.Utility;
using Opsive.UltimateCharacterController.Events;
using Opsive.UltimateCharacterController.Game;
using Opsive.UltimateCharacterController.Character.MovementTypes;
using Opsive.UltimateCharacterController.Character.Abilities;
using Opsive.UltimateCharacterController.Character.Abilities.Items;
using Opsive.UltimateCharacterController.Character.Effects;
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
using Opsive.UltimateCharacterController.Networking;
using Opsive.UltimateCharacterController.Networking.Character;
#endif
using Opsive.UltimateCharacterController.StateSystem;
using Opsive.UltimateCharacterController.Utility;
using System.Collections.Generic;
using UnityEngine;
///
/// The UltimateCharacterLocomotion component extends the CharacterLocomotion functionality by handling the following features:
/// - Movement Types
/// - Abilities
/// - Effects
/// - Animator Knowledge
///
public class UltimateCharacterLocomotion : CharacterLocomotion
{
[Tooltip("Specifies the location that the object should be updated.")]
[SerializeField] protected KinematicObjectManager.UpdateLocation m_UpdateLocation = KinematicObjectManager.UpdateLocation.FixedUpdate;
[Tooltip("The name of the state that should be activated when the character is in a first person perspective.")]
[SerializeField] protected string m_FirstPersonStateName = "FirstPerson";
[Tooltip("The name of the state that should be activated when the character is in a third person perspective.")]
[SerializeField] protected string m_ThirdPersonStateName = "ThirdPerson";
[Tooltip("The name of the state that should be activated when the character is moving.")]
[SerializeField] protected string m_MovingStateName = "Moving";
[Tooltip("The name of the state that should be activated when the character is airborne.")]
[SerializeField] protected string m_AirborneStateName = "Airborne";
[Tooltip("The full name of the active movement type.")]
[SerializeField] protected string m_MovementTypeFullName;
[Tooltip("The name of the active first person movement type.")]
[SerializeField] protected string m_FirstPersonMovementTypeFullName;
[Tooltip("The name of the active third person movement type.")]
[SerializeField] protected string m_ThirdPersonMovementTypeFullName;
[Tooltip("Specifies how much to multiply the yaw parameter by when turning in place.")]
[SerializeField] protected float m_YawMultiplier = 7;
[Tooltip("Specifies the value of the Speed Parameter when the character is moving.")]
[SerializeField] protected float m_MovingSpeedParameterValue = 1;
[Tooltip("The serialization data for the MovementTypes.")]
[SerializeField] protected Serialization[] m_MovementTypeData;
[Tooltip("The serialization data for the Abilities.")]
[SerializeField] protected Serialization[] m_AbilityData;
[Tooltip("The serialization data for the Item Abilities.")]
[SerializeField] protected Serialization[] m_ItemAbilityData;
[Tooltip("The serialization data for the Effects.")]
[SerializeField] protected Serialization[] m_EffectData;
[Tooltip("Unity event invoked when an ability has been started or stopped.")]
[SerializeField] protected UnityMovementTypeBoolEvent m_OnMovementTypeActiveEvent;
[Tooltip("Unity event invoked when an movement type has been started or stopped.")]
[SerializeField] protected UnityAbilityBoolEvent m_OnAbilityActiveEvent;
[Tooltip("Unity event invoked when an item ability has been started or stopped.")]
[SerializeField] protected UnityItemAbilityBoolEvent m_OnItemAbilityActiveEvent;
[Tooltip("Unity event invoked when the character has changed grounded state.")]
[SerializeField] protected UnityBoolEvent m_OnGroundedEvent;
[Tooltip("Unity event invoked when the character has landed on the ground.")]
[SerializeField] protected UnityFloatEvent m_OnLandEvent;
[Tooltip("Unity event invoked when the time scale has changed.")]
[SerializeField] protected UnityFloatEvent m_OnChangeTimeScaleEvent;
[Tooltip("Unity event invoked when the moving platforms have changed.")]
[SerializeField] protected UnityTransformEvent m_OnChangeMovingPlatformsEvent;
public string FirstPersonStateName { get { return m_FirstPersonStateName; } set { m_FirstPersonStateName = value; } }
public string ThirdPersonStateName { get { return m_ThirdPersonStateName; } set { m_ThirdPersonStateName = value; } }
public string MovingStateName { get { return m_MovingStateName; } set { m_MovingStateName = value; } }
public string AirborneStateName { get { return m_AirborneStateName; } set { m_AirborneStateName = value; } }
public string MovementTypeFullName { get { return m_MovementTypeFullName; } set { SetMovementType(value); } }
public string FirstPersonMovementTypeFullName
{
get { return m_FirstPersonMovementTypeFullName; }
set
{
if (m_FirstPersonMovementTypeFullName != value) {
if (!string.IsNullOrEmpty(value) && Application.isPlaying && m_FirstPersonPerspective) {
SetMovementType(value);
} else {
m_FirstPersonMovementTypeFullName = value;
}
}
}
}
public string ThirdPersonMovementTypeFullName
{
get { return m_ThirdPersonMovementTypeFullName; }
set
{
if (m_ThirdPersonMovementTypeFullName != value) {
if (!string.IsNullOrEmpty(value) && Application.isPlaying && !m_FirstPersonPerspective) {
SetMovementType(value);
} else {
m_ThirdPersonMovementTypeFullName = value;
}
}
}
}
public float YawMultiplier { get { return m_YawMultiplier; } set { m_YawMultiplier = value; } }
public float MovingSpeedParameterValue { get { return m_MovingSpeedParameterValue; } set { m_MovingSpeedParameterValue = value; } }
public float YawAngle { get { return m_YawAngle; } }
public UnityMovementTypeBoolEvent OnMovementTypeActiveEvent { get { return m_OnMovementTypeActiveEvent; } set { m_OnMovementTypeActiveEvent = value; } }
public UnityAbilityBoolEvent OnAbilityActiveEvent { get { return m_OnAbilityActiveEvent; } set { m_OnAbilityActiveEvent = value; } }
public UnityItemAbilityBoolEvent OnItemAbilityActiveEvent { get { return m_OnItemAbilityActiveEvent; } set { m_OnItemAbilityActiveEvent = value; } }
public UnityBoolEvent OnGroundedEvent { get { return m_OnGroundedEvent; } set { m_OnGroundedEvent = value; } }
public UnityFloatEvent OnLandEvent { get { return m_OnLandEvent; } set { m_OnLandEvent = value; } }
public UnityFloatEvent OnChangeTimeScaleEvent { get { return m_OnChangeTimeScaleEvent; } set { m_OnChangeTimeScaleEvent = value; } }
public UnityTransformEvent OnChangeMovingPlatformsEvent { get { return m_OnChangeMovingPlatformsEvent; } set { m_OnChangeMovingPlatformsEvent = value; } }
private GameObject m_GameObject;
private Animator m_Animator;
private int m_KinematicObjectIndex = -1;
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
private INetworkInfo m_NetworkInfo;
private INetworkCharacter m_NetworkCharacter;
#endif
private float m_MaxHeight;
private Vector3 m_MaxHeightPosition;
private float m_YawAngle;
private Vector2 m_RawInputVector;
private bool m_Moving;
private bool m_MovingParameter;
private bool m_SpeedParameterOverride;
private ILookSource m_LookSource;
private MovementType[] m_MovementTypes;
private Ability[] m_Abilities;
private Ability[] m_ActiveAbilities;
private int m_ActiveAbilityCount;
private bool m_DirtyAbilityParameter;
private MoveTowards m_MoveTowardsAbility;
private ItemEquipVerifier m_ItemEquipVerifierAbility;
private ItemAbility[] m_ItemAbilities;
private ItemAbility[] m_ActiveItemAbilities;
private int m_ActiveItemAbilityCount;
private bool m_DirtyItemAbilityParameter;
private Effect[] m_Effects;
private Effect[] m_ActiveEffects;
private int m_ActiveEffectCount;
private Dictionary m_MovementTypeNameMap = new Dictionary();
private MovementType m_MovementType;
private bool m_FirstPersonPerspective;
private bool m_Alive;
private bool m_Aiming;
private Vector3 m_AbilityMotor;
private int[] m_ItemSlotStateIndex;
private int[] m_ItemSlotSubstateIndex;
[NonSerialized] public int KinematicObjectIndex { get { return m_KinematicObjectIndex; } set { m_KinematicObjectIndex = value; } }
public KinematicObjectManager.UpdateLocation UpdateLocation { get { return m_UpdateLocation; }
set {
if (m_UpdateLocation == value) { return; }
m_UpdateLocation = value;
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangeUpdateLocation", m_UpdateLocation == KinematicObjectManager.UpdateLocation.FixedUpdate);
}
}
public ILookSource LookSource { get { return m_LookSource; } }
public MovementType[] MovementTypes { get { return m_MovementTypes; }
set
{
m_MovementTypes = value;
m_MovementTypeNameMap.Clear();
for (int i = 0; i < m_MovementTypes.Length; ++i) {
m_MovementTypeNameMap.Add(m_MovementTypes[i].GetType().FullName, i);
}
}
}
public Serialization[] MovementTypeData { get { return m_MovementTypeData; } set { m_MovementTypeData = value; } }
public Ability[] Abilities { get { return m_Abilities; } set { m_Abilities = value;
if (Application.isPlaying && m_Abilities != null) {
if (m_ActiveAbilities == null) {
m_ActiveAbilities = new Ability[m_Abilities.Length];
} else {
System.Array.Resize(ref m_ActiveAbilities, m_Abilities.Length);
}
// The ability can be added after the character has already been initialized.
for (int i = 0; i < m_Abilities.Length; ++i) {
if (m_Abilities[i].Index == -1) {
m_Abilities[i].Initialize(this, i);
m_Abilities[i].Awake();
}
}
}
}
}
public Serialization[] AbilityData { get { return m_AbilityData; } set { m_AbilityData = value; } }
public ItemAbility[] ItemAbilities { get { return m_ItemAbilities; } set { m_ItemAbilities = value;
if (Application.isPlaying && m_ItemAbilities != null) {
if (m_ActiveItemAbilities == null) {
m_ActiveItemAbilities = new ItemAbility[m_ItemAbilities.Length];
} else {
System.Array.Resize(ref m_ActiveItemAbilities, m_ItemAbilities.Length);
}
// The ability can be added after the character has already been initialized.
for (int i = 0; i < m_ItemAbilities.Length; ++i) {
if (m_ItemAbilities[i].Index == -1) {
m_ItemAbilities[i].Initialize(this, i);
m_ItemAbilities[i].Awake();
}
}
}
}
}
public Serialization[] ItemAbilityData { get { return m_ItemAbilityData; } set { m_ItemAbilityData = value; } }
[Snapshot] public Ability[] ActiveAbilities { get { return m_ActiveAbilities; } protected set { m_ActiveAbilities = value; } }
[Snapshot] public int ActiveAbilityCount { get { return m_ActiveAbilityCount; } protected set { m_ActiveAbilityCount = value; } }
[Snapshot] public bool DirtyAbilityParameter { get { return m_DirtyAbilityParameter; } protected set { m_DirtyAbilityParameter = value; } }
[Snapshot] public ItemAbility[] ActiveItemAbilities { get { return m_ActiveItemAbilities; } protected set { m_ActiveItemAbilities = value; } }
[Snapshot] public int ActiveItemAbilityCount { get { return m_ActiveItemAbilityCount; } protected set { m_ActiveItemAbilityCount = value; } }
[Snapshot] public bool DirtyItemAbilityParameter { get { return m_DirtyItemAbilityParameter; } protected set { m_DirtyItemAbilityParameter = value; } }
public Effect[] Effects { get { return m_Effects; } set { m_Effects = value; } }
public Serialization[] EffectData { get { return m_EffectData; } set { m_EffectData = value; } }
public MovementType ActiveMovementType { get { return m_MovementType; } }
public MoveTowards MoveTowardsAbility { get { return m_MoveTowardsAbility; } }
public ItemEquipVerifier ItemEquipVerifierAbility { get { return m_ItemEquipVerifierAbility; } }
public override float TimeScale
{
get { return base.TimeScale; }
set
{
// Override the TimeScale setter to allow an event to be sent when the time scale changes.
if (base.TimeScale != value) {
if (base.TimeScale == 0 && value != 0) {
EventHandler.ExecuteEvent(m_GameObject, "OnEnableGameplayInput", true);
} else if (base.TimeScale != 0 && value == 0) {
EventHandler.ExecuteEvent(m_GameObject, "OnEnableGameplayInput", false);
}
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangeTimeScale", value);
if (m_OnChangeTimeScaleEvent != null) {
m_OnChangeTimeScaleEvent.Invoke(value);
}
}
base.TimeScale = value;
}
}
[NonSerialized] public Vector2 RawInputVector { get { return m_RawInputVector; } set { m_RawInputVector = value; } }
[NonSerialized] public Vector2 InputVector { get { return m_InputVector; } set { m_InputVector = value; } }
[NonSerialized] public Vector3 DeltaRotation { get { return m_DeltaRotation; } set { m_DeltaRotation = value; } }
[NonSerialized] public bool Moving { get { return m_Moving || m_InputVector.sqrMagnitude > 0.001f; } set {
if (m_Moving != value) {
m_Moving = value;
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterMoving", m_Moving);
if (!string.IsNullOrEmpty(m_MovingStateName)) {
StateManager.SetState(m_GameObject, m_MovingStateName, m_Moving);
}
}
}
}
[NonSerialized] public bool SpeedParameterOverride { get { return m_SpeedParameterOverride; } set { m_SpeedParameterOverride = value; } }
[NonSerialized] public float SpeedParameterValue { get { return Moving ? m_MovingSpeedParameterValue : 0; } }
[NonSerialized] public Vector3 AbilityMotor { get { return m_AbilityMotor; } set { m_AbilityMotor = value; } }
[NonSerialized] public bool FirstPersonPerspective { get { return m_FirstPersonPerspective; } set { m_FirstPersonPerspective = value; } }
public bool Alive { get { return m_Alive; } }
///
/// Cache the component references and initialize the default values.
///
protected override void Awake()
{
m_GameObject = gameObject;
m_Animator = m_GameObject.GetCachedComponent();
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
m_NetworkInfo = m_GameObject.GetCachedComponent();
m_NetworkCharacter = m_GameObject.GetCachedComponent();
#endif
// Create any movement types, abilities, and effects from the serialized data.
DeserializeMovementTypes(true);
DeserializeAbilities(true);
DeserializeItemAbilities(true);
DeserializeEffects(true);
base.Awake();
// Use the local y position in determining the max height.
m_MaxHeight = float.NegativeInfinity;
m_MaxHeightPosition = m_Transform.position;
var forwardDirection = m_Transform.rotation * m_Transform.forward;
m_YawAngle = Mathf.Atan2(forwardDirection.x, forwardDirection.z) * Mathf.Rad2Deg;
m_Alive = true;
EventHandler.RegisterEvent(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
EventHandler.RegisterEvent(m_GameObject, "OnCharacterChangePerspectives", OnChangePerspectives);
EventHandler.RegisterEvent(m_GameObject, "OnAimAbilityAim", OnAiming);
EventHandler.RegisterEvent(m_GameObject, "OnDeath", OnDeath);
EventHandler.RegisterEvent(m_GameObject, "OnRespawn", OnRespawn);
EventHandler.RegisterEvent(m_GameObject, "OnCharacterImmediateTransformChange", OnImmediateTransformChange);
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorSnapped", AnimatorSnapped);
// Call Awake on all of the deserialized objects after the character controller's Awake method is complete.
if (m_MovementTypes != null) {
for (int i = 0; i < m_MovementTypes.Length; ++i) {
m_MovementTypes[i].Awake();
}
}
if (m_Abilities != null) {
m_ActiveAbilities = new Ability[m_Abilities.Length];
for (int i = 0; i < m_Abilities.Length; ++i) {
m_Abilities[i].Awake();
}
}
if (m_ItemAbilities != null) {
m_ActiveItemAbilities = new ItemAbility[m_ItemAbilities.Length];
for (int i = 0; i < m_ItemAbilities.Length; ++i) {
m_ItemAbilities[i].Awake();
}
}
if (m_Effects != null) {
m_ActiveEffects = new Effect[m_Effects.Length];
for (int i = 0; i < m_Effects.Length; ++i) {
m_Effects[i].Awake();
}
}
// The controller needs to start with a movement type.
SetMovementType(m_MovementTypeFullName);
if (m_MovementType != null) {
m_FirstPersonPerspective = m_MovementType.FirstPersonPerspective;
}
}
///
/// Deserialize the movement types.
///
/// Were any movement types removed?
public bool DeserializeMovementTypes()
{
return DeserializeMovementTypes(false);
}
///
/// Deserialize the movement types.
///
/// Should the movement types be force deserialized?
/// Were any movement types removed?
public bool DeserializeMovementTypes(bool forceDeserialization)
{
// The Movement Types only need to be deserialized once.
if (m_MovementTypes != null && !forceDeserialization) {
return false;
}
var dirty = false;
if (m_MovementTypeData != null && m_MovementTypeData.Length > 0) {
m_MovementTypes = new MovementType[m_MovementTypeData.Length];
m_MovementTypeNameMap.Clear();
for (int i = 0; i < m_MovementTypes.Length; ++i) {
m_MovementTypes[i] = m_MovementTypeData[i].DeserializeFields(MemberVisibility.Public) as MovementType;
if (m_MovementTypes[i] == null) {
dirty = true;
continue;
}
m_MovementTypeNameMap.Add(m_MovementTypes[i].GetType().FullName, i);
if (Application.isPlaying) {
m_MovementTypes[i].Initialize(this);
}
}
}
return dirty;
}
///
/// Deserialize the abilities.
///
/// Were any abilities removed?
public bool DeserializeAbilities()
{
return DeserializeAbilities(false);
}
///
/// Deserialize the abilities.
///
/// Should the abilities be force deserialized?
/// Were any abilities removed?
public bool DeserializeAbilities(bool forceDeserialization)
{
// The abilities only need to be deserialized once.
if (m_Abilities != null && !forceDeserialization) {
return false;
}
var dirty = false;
if (m_AbilityData != null && m_AbilityData.Length > 0) {
m_Abilities = new Ability[m_AbilityData.Length];
for (int i = 0; i < m_AbilityData.Length; ++i) {
m_Abilities[i] = m_AbilityData[i].DeserializeFields(MemberVisibility.Public) as Ability;
if (m_Abilities[i] == null) {
dirty = true;
continue;
}
if (Application.isPlaying) {
m_Abilities[i].Initialize(this, i);
}
// The MoveTowards and ItemToggleAbilityBlock abilities are a special type of ability in that it is started by the controller.
if (m_Abilities[i] is MoveTowards) {
m_MoveTowardsAbility = m_Abilities[i] as MoveTowards;
} else if (m_Abilities[i] is ItemEquipVerifier) {
m_ItemEquipVerifierAbility = m_Abilities[i] as ItemEquipVerifier;
}
}
}
return dirty;
}
///
/// Deserialize the item abilities.
///
/// Were any item abilities removed?
public bool DeserializeItemAbilities()
{
return DeserializeItemAbilities(false);
}
///
/// Deserialize the item abilities.
///
/// Should the item abilities be force deserialized?
/// Were any item abilities removed?
public bool DeserializeItemAbilities(bool forceDeserialization)
{
// The Item Abilities only need to be deserialized once.
if (m_ItemAbilities != null && !forceDeserialization) {
return false;
}
var dirty = false;
if (m_ItemAbilityData != null && m_ItemAbilityData.Length > 0) {
m_ItemAbilities = new ItemAbility[m_ItemAbilityData.Length];
for (int i = 0; i < m_ItemAbilityData.Length; ++i) {
m_ItemAbilities[i] = m_ItemAbilityData[i].DeserializeFields(MemberVisibility.Public) as ItemAbility;
if (m_ItemAbilities[i] == null) {
dirty = true;
continue;
}
if (Application.isPlaying) {
m_ItemAbilities[i].Initialize(this, i);
}
}
}
return dirty;
}
///
/// Deserialize the effects.
///
/// Were any effects removed?
public bool DeserializeEffects()
{
return DeserializeEffects(false);
}
///
/// Deserialize the effects.
///
/// Should the effects be force deserialized?
/// Were any effects removed?
public bool DeserializeEffects(bool forceDeserialization)
{
// The Effects only need to be deserialized once.
if (m_Effects != null && !forceDeserialization) {
return false;
}
var dirty = false;
if (m_EffectData != null && m_EffectData.Length > 0) {
m_Effects = new Effect[m_EffectData.Length];
for (int i = 0; i < m_EffectData.Length; ++i) {
m_Effects[i] = m_EffectData[i].DeserializeFields(MemberVisibility.Public) as Effect;
if (m_Effects[i] == null) {
dirty = true;
continue;
}
if (Application.isPlaying) {
m_Effects[i].Initialize(this, i);
}
}
}
return dirty;
}
///
/// Returns an array of serialized movement types. Useful for editor scripts when the movement types haven't already been deserialized at runtime.
///
/// An array of serialized movement types.
public MovementType[] GetSerializedMovementTypes()
{
if (m_MovementTypeData != null && m_MovementTypeData.Length > 0 && (m_MovementTypes == null || m_MovementTypes.Length == 0)) { DeserializeMovementTypes(); }
return m_MovementTypes;
}
///
/// Returns an array of serialized abilities. Useful for editor scripts when the abilities haven't already been deserialized at runtime.
///
/// An array of serialized abilities.
public Ability[] GetSerializedAbilities()
{
if (m_AbilityData != null && m_AbilityData.Length > 0 && (m_Abilities == null || m_Abilities.Length == 0)) { DeserializeAbilities(); }
return m_Abilities;
}
///
/// Returns an array of serialized item abilities. Useful for editor scripts when the item abilities haven't already been deserialized at runtime.
///
/// An array of serialized item abilities.
public ItemAbility[] GetSerializedItemAbilities()
{
if (m_ItemAbilityData != null && m_ItemAbilityData.Length > 0 && (m_ItemAbilities == null || m_ItemAbilities.Length == 0)) { DeserializeItemAbilities(); }
return m_ItemAbilities;
}
///
/// Returns an array of serialized effects. Useful for editor scripts when the effects haven't already been deserialized at runtime.
///
/// An array of serialized effects.
public Effect[] GetSerializedEffects()
{
if (m_EffectData != null && m_EffectData.Length > 0 && (m_Effects == null || m_Effects.Length == 0)) { DeserializeEffects(); }
return m_Effects;
}
///
/// Sets the movement type to the object with the specified type which should be set.
///
/// The type name of the MovementType which should be set.
private void SetMovementType(string typeName)
{
SetMovementType(UnityEngineUtility.GetType(typeName));
}
///
/// Sets the movement type to the object with the specified type.
///
/// The type of the MovementType which should be set.
public void SetMovementType(System.Type type)
{
if (type == null || (m_MovementType != null && m_MovementType.GetType() == type)) {
return;
}
// The MovementTypes may not be deserialized yet.
if (m_MovementTypeNameMap.Count == 0) {
DeserializeMovementTypes();
}
int index;
if (!m_MovementTypeNameMap.TryGetValue(type.FullName, out index)) {
Debug.LogError($"Error: Unable to find the movement type with name {type.FullName}.", this);
return;
}
// Notify the previous movement type that it is no longer active.
if (m_MovementType != null && Application.isPlaying) {
m_MovementType.ChangeMovementType(false);
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangeMovementType", m_MovementType, false);
if (m_OnMovementTypeActiveEvent != null) {
m_OnMovementTypeActiveEvent.Invoke(m_MovementType, false);
}
}
m_MovementTypeFullName = type.FullName;
m_MovementType = m_MovementTypes[index];
// Notify the current movement type that is now active.
if (Application.isPlaying) {
if (m_MovementType.FirstPersonPerspective) {
m_FirstPersonMovementTypeFullName = m_MovementTypeFullName;
} else {
m_ThirdPersonMovementTypeFullName = m_MovementTypeFullName;
}
m_MovementType.ChangeMovementType(true);
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangeMovementType", m_MovementType, true);
if (m_OnMovementTypeActiveEvent != null) {
m_OnMovementTypeActiveEvent.Invoke(m_MovementType, true);
}
}
}
///
/// A new ILookSource object has been attached to the character.
///
/// The ILookSource object attached to the character.
private void OnAttachLookSource(ILookSource lookSource)
{
m_LookSource = lookSource;
#if THIRD_PERSON_CONTROLLER
var hasPerspectiveMonitor = m_GameObject.GetComponent() != null;
#else
var hasPerspectiveMonitor = false;
#endif
// If the character doesn't have the PerspectiveMonitor then the perspective depends on the look source.
if (!hasPerspectiveMonitor) {
if (lookSource != null) {
var cameraController = lookSource as UltimateCharacterController.Camera.CameraController;
if (cameraController != null) {
m_FirstPersonPerspective = cameraController.ActiveViewType.FirstPersonPerspective;
} else {
m_FirstPersonPerspective = false;
}
}
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangePerspectives", m_FirstPersonPerspective);
}
}
///
/// The character has been enabled.
///
protected override void OnEnable()
{
// The KinematicObjectManager is responsible for calling the move method.
m_KinematicObjectIndex = KinematicObjectManager.RegisterCharacter(this);
// If the previous index is not -1 then the character has already been enabled. Send events so all of the components correctly reset.
if (m_KinematicObjectIndex != -1) {
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterMoving", false);
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterImmediateTransformChange", true);
}
base.OnEnable();
}
///
/// Call Start on all of the character's abilities and effects in addition to checking the TimeScale.
///
public override void Start()
{
base.Start();
// The animator needs to know how many slots there are.
var inventory = m_GameObject.GetCachedComponent();
if (inventory != null) {
var slotCount = inventory.SlotCount;
m_ItemSlotStateIndex = new int[slotCount];
m_ItemSlotSubstateIndex = new int[slotCount];
}
// Start the abilities and effects.
if (m_Abilities != null) {
for (int i = 0; i < m_Abilities.Length; ++i) {
m_Abilities[i].Start();
}
}
if (m_ItemAbilities != null) {
for (int i = 0; i < m_ItemAbilities.Length; ++i) {
m_ItemAbilities[i].Start();
}
}
if (m_Effects != null) {
for (int i = 0; i < m_Effects.Length; ++i) {
m_Effects[i].Start();
}
}
// Do a pass on trying to start any abilities in case they should be started on the first frame.
UpdateAbilities(m_Abilities);
UpdateAbilities(m_ItemAbilities);
UpdateDirtyAbilityAnimatorParameters();
// The abilities may have updated the animator.
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterSnapAnimator");
// The character isn't moving at the start.
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterMoving", false);
// Notify those interested in the time scale isn't set to 1 at the start. The TimeScale property will notify those interested of the change during runtime.
if (m_TimeScale != 1) {
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangeTimeScale", m_TimeScale);
if (m_OnChangeTimeScaleEvent != null) {
m_OnChangeTimeScaleEvent.Invoke(m_TimeScale);
}
}
}
///
/// Callback from CharacterLocomotion.Move when the UltimateCharacterLocomotion should perform its updates (such as updating the abilities).
///
protected override void UpdateUltimateLocomotion()
{
// The MovementType may change the InputVector.
m_RawInputVector = m_InputVector;
// Abilities may disallow input.
bool allowPositionalInput, allowRotationalInput;
AbilitiesAllowInput(out allowPositionalInput, out allowRotationalInput);
if (allowPositionalInput) {
// Positional input is allowed - use the movement type to determine how the character should move.
m_InputVector = m_MovementType.GetInputVector(m_InputVector);
} else {
m_InputVector = Vector2.zero;
}
if (!allowRotationalInput) {
m_DeltaRotation = Vector3.zero;
}
// Start and update the abilities.
UpdateAbilities(m_Abilities);
UpdateAbilities(m_ItemAbilities);
// Update the effects.
for (int i = 0; i < m_ActiveEffectCount; ++i) {
m_ActiveEffects[i].Update();
}
// The Moving value is based on the input vector. It does not include vertical movement.
m_MovingParameter = Moving;
if (m_Moving != (m_InputVector.sqrMagnitude > 0.001f)) {
Moving = !m_Moving;
}
}
///
/// Do the abilities allow positional and rotational input?
///
/// A reference to a bool which indicates if the abilities allow positional input.
/// A reference to a bool which indicates if the abilities allow rotational input.
private void AbilitiesAllowInput(out bool allowPositionalInput, out bool allowRotationalInput)
{
allowPositionalInput = allowRotationalInput = true;
// Check the abilities to see if any disallow input.
for (int i = 0; i < m_ActiveAbilityCount; ++i) {
if (!m_ActiveAbilities[i].AllowPositionalInput) {
allowPositionalInput = false;
}
if (!m_ActiveAbilities[i].AllowRotationalInput) {
allowRotationalInput = false;
}
}
// If neither the positional or rotational input is allowed then the item abilities don't need to be checked.
if (!allowPositionalInput && !allowRotationalInput) {
return;
}
// Check the item abilities to see if any disallow input.
for (int i = 0; i < m_ActiveItemAbilityCount; ++i) {
if (!m_ActiveItemAbilities[i].AllowPositionalInput) {
allowPositionalInput = false;
}
if (!m_ActiveItemAbilities[i].AllowRotationalInput) {
allowRotationalInput = false;
}
}
}
///
/// Try to start an automatic inactive ability and also try to stop an automatic active ability. The Update or InactiveUpdate will also be called.
///
/// An array of all of the abilities.
private void UpdateAbilities(Ability[] abilities)
{
if (abilities != null) {
for (int i = 0; i < abilities.Length; ++i) {
if (!abilities[i].IsActive) {
if (abilities[i].StartType == Ability.AbilityStartType.Automatic) {
TryStartAbility(abilities[i]);
} else if (!(abilities[i] is ItemAbility) && abilities[i].StartType != Ability.AbilityStartType.Manual && abilities[i].CheckForAbilityMessage &&
(m_MoveTowardsAbility == null || !m_MoveTowardsAbility.IsActive)) {
// The ability message can show if the non-automatic/manual ability can start.
abilities[i].AbilityMessageCanStart = abilities[i].Enabled && abilities[i].CanStartAbility();
}
}
if (abilities[i].IsActive && abilities[i].StopType == Ability.AbilityStopType.Automatic) {
TryStopAbility(abilities[i]);
}
if (abilities[i].IsActive) {
abilities[i].Update();
} else if (abilities[i].Enabled) {
abilities[i].InactiveUpdate();
}
}
}
}
///
/// Update the animator parameters.
///
protected override void UpdateAnimator()
{
// In the case of a first person view the character may not have an animator.
if (m_AnimatorMonitor == null) {
return;
}
for (int i = 0; i < m_ActiveAbilityCount; ++i) {
m_ActiveAbilities[i].UpdateAnimator();
}
for (int i = 0; i < m_ActiveItemAbilityCount; ++i) {
m_ActiveItemAbilities[i].UpdateAnimator();
}
m_AnimatorMonitor.SetHorizontalMovementParameter(m_InputVector.x, m_TimeScale);
m_AnimatorMonitor.SetForwardMovementParameter(m_InputVector.y, m_TimeScale);
if (m_LookSource != null) {
m_AnimatorMonitor.SetPitchParameter(m_LookSource.Pitch, m_TimeScale, 0);
}
m_AnimatorMonitor.SetYawParameter(m_YawAngle * m_YawMultiplier, m_TimeScale);
if (!m_SpeedParameterOverride) {
m_AnimatorMonitor.SetSpeedParameter(SpeedParameterValue, m_TimeScale);
}
m_AnimatorMonitor.SetMovingParameter(m_MovingParameter);
// The ability parameters should only be updated once each move call.
UpdateDirtyAbilityAnimatorParameters();
m_AnimatorMonitor.UpdateItemIDParameters();
}
///
/// Updates the position and rotation. This should be done after the animator has updated so the root motion is accurate for the current frame.
///
protected override void UpdatePositionAndRotation()
{
KinematicObjectManager.BeginCharacterMovement(m_KinematicObjectIndex);
base.UpdatePositionAndRotation();
LateUpdateUltimateLocomotion();
KinematicObjectManager.EndCharacterMovement(m_KinematicObjectIndex);
}
///
/// Updates the grounded state.
///
/// Is the character grounded?
/// Should the events be sent if the grounded status changes?
/// True if the grounded state changed.
protected override bool UpdateGroundState(bool grounded, bool sendEvents)
{
var groundedStatusChanged = base.UpdateGroundState(grounded, sendEvents);
if (groundedStatusChanged) {
// Notify interested objects of the ground change.
if (sendEvents) {
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterGrounded", m_Grounded);
if (m_OnGroundedEvent != null) {
m_OnGroundedEvent.Invoke(m_Grounded);
}
}
if (m_Grounded) {
if (sendEvents && m_MaxHeight != float.NegativeInfinity) {
var height = m_MaxHeight - m_Transform.InverseTransformDirection(m_Transform.position - m_MaxHeightPosition).y;
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterLand", height);
if (m_OnLandEvent != null) {
m_OnLandEvent.Invoke(height);
}
}
} else {
m_MaxHeightPosition = m_Transform.position;
m_MaxHeight = float.NegativeInfinity;
}
} else if (!m_Grounded && UsingGravity) {
// Save out the max height of the character in the air so the fall height can be calculated.
var height = m_Transform.InverseTransformDirection(m_Transform.position - m_MaxHeightPosition).y;
if (height > m_MaxHeight) {
m_MaxHeightPosition = m_Transform.position;
m_MaxHeight = height;
}
}
// Set the airborne state if the grounded status has changed or no events are being sent. No events will be sent when the grounded status is initially checked.
if ((groundedStatusChanged || !sendEvents) && !string.IsNullOrEmpty(m_AirborneStateName)) {
StateManager.SetState(m_GameObject, m_AirborneStateName, !m_Grounded);
}
return groundedStatusChanged;
}
///
/// Update the rotation forces.
///
protected override void UpdateRotation()
{
// If using root motion rotation the animation will specify the rotation.
if (UsingRootMotionRotation) {
m_DeltaRotation = Vector3.zero;
}
// Give the abilities a chance to update the rotation.
for (int i = 0; i < m_ActiveAbilityCount; ++i) {
m_ActiveAbilities[i].UpdateRotation();
}
for (int i = 0; i < m_ActiveItemAbilityCount; ++i) {
m_ActiveItemAbilities[i].UpdateRotation();
}
base.UpdateRotation();
}
///
/// Applies the final rotation to the transform.
///
protected override void ApplyRotation()
{
// Give the abilities a chance to verify the rotation.
for (int i = 0; i < m_ActiveAbilityCount; ++i) {
m_ActiveAbilities[i].ApplyRotation();
}
for (int i = 0; i < m_ActiveItemAbilityCount; ++i) {
m_ActiveItemAbilities[i].ApplyRotation();
}
// Save the local yaw angle before the rotation is applied so the animator knows how much the character turned.
m_YawAngle = 0;
if (m_Platform == null) {
if (Mathf.Abs(m_Torque.eulerAngles.y) > 0.1f) {
m_YawAngle = MathUtility.ClampInnerAngle(m_Torque.eulerAngles.y);
}
} else {
var delta = m_Torque * Quaternion.Inverse(m_PlatformTorque);
if (Mathf.Abs(delta.eulerAngles.y) > 0.1f) {
m_YawAngle = MathUtility.ClampInnerAngle(delta.eulerAngles.y);
}
}
base.ApplyRotation();
}
///
/// Sets the moving platform to the specified transform.
///
/// The platform transform that should be set. Can be null.
/// Is the default moving platform logic being overridden?
/// True if the platform was changed.
public override bool SetPlatform(Transform platform, bool platformOverride)
{
var movingPlatformChanged = base.SetPlatform(platform, platformOverride);
if (movingPlatformChanged) {
// Notify interested objects of the platform change.
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangeMovingPlatforms", m_Platform);
if (m_OnChangeMovingPlatformsEvent != null) {
m_OnChangeMovingPlatformsEvent.Invoke(m_Platform);
}
}
return movingPlatformChanged;
}
///
/// Move according to the forces.
///
protected override void UpdatePosition()
{
// Give the abilities a chance to update the movement.
for (int i = 0; i < m_ActiveAbilityCount; ++i) {
m_ActiveAbilities[i].UpdatePosition();
}
for (int i = 0; i < m_ActiveItemAbilityCount; ++i) {
m_ActiveItemAbilities[i].UpdatePosition();
}
base.UpdatePosition();
}
///
/// Updates the motor forces.
///
protected override void UpdateMotorThrottle()
{
base.UpdateMotorThrottle();
MotorThrottle += m_AbilityMotor;
}
///
/// Applies the final move direction to the transform.
///
protected override void ApplyPosition()
{
// Give the abilities a chance to verify the movement.
for (int i = 0; i < m_ActiveAbilityCount; ++i) {
m_ActiveAbilities[i].ApplyPosition();
}
for (int i = 0; i < m_ActiveItemAbilityCount; ++i) {
m_ActiveItemAbilities[i].ApplyPosition();
}
base.ApplyPosition();
}
///
/// Updates the ability and item ability parameters if they are dirty.
///
private void UpdateDirtyAbilityAnimatorParameters()
{
if (m_DirtyAbilityParameter) {
UpdateAbilityAnimatorParameters(true);
}
if (m_DirtyItemAbilityParameter) {
UpdateItemAbilityAnimatorParameters(true);
}
}
///
/// Callback after the animator has updated and the character should perform its post movement updates (such as updating the item abilities).
///
private void LateUpdateUltimateLocomotion()
{
// If the animator is being updated within LateUpdate then the character colliders will be enabled.
var collisionLayerEnabled = CollisionLayerEnabled;
EnableColliderCollisionLayer(false);
// Start and update the item abilities. This is done after the controller has moved so the items will be using the latest character position/rotation (such as a melee item
// for collision detection).
LateUpdateActiveAbilities(m_ActiveAbilities, ref m_ActiveAbilityCount);
LateUpdateActiveAbilities(m_ActiveItemAbilities, ref m_ActiveItemAbilityCount);
EnableColliderCollisionLayer(collisionLayerEnabled);
}
///
/// Calls LateUpdate on the active abilities.
///
/// An array of all of the abilities.
/// The number of active abilities.
private void LateUpdateActiveAbilities(Ability[] abilities, ref int abilityCount)
{
if (abilities != null) {
for (int i = 0; i < abilityCount; ++i) {
abilities[i].LateUpdate();
}
}
}
///
/// Tries to start the specified ability.
///
/// The ability to try to start.
/// True if the ability was started.
public bool TryStartAbility(Ability ability)
{
return TryStartAbility(ability, false);
}
///
/// Tries to start the specified ability.
///
/// The ability to try to start.
/// Should the ability priority be ignored?
/// True if the ability was started.
public bool TryStartAbility(Ability ability, bool ignorePriority)
{
return TryStartAbility(ability, ignorePriority, false);
}
///
/// Tries to start the specified ability.
///
/// The ability to try to start.
/// Should the ability priority be ignored?
/// Should the CanStartAbility check be ignored?
/// True if the ability was started.
public bool TryStartAbility(Ability ability, bool ignorePriority, bool ignoreCanStartCheck)
{
// ItemAbilities have a different startup process than regular abilities.
if (ability is ItemAbility) {
return TryStartAbility(ability as ItemAbility, ignoreCanStartCheck);
}
// Start the ability if it is not active or can be started multiple times, enabled, and can be started.
if ((!ability.IsActive || ability.CanReceiveMultipleStarts) && ability.Enabled && (ignoreCanStartCheck || ability.CanStartAbility())) {
// The ability may already be active if the ability can receive multiple starts. Multiple starts are useful for item abilities that need to be active
// over a period of time but can be updated with the input start type while active. A good example of this is the Toggle Equip Item ability. When
// this ability starts it sets an Animator parameter to equip or unequip the item. The ability continues to run while equipping or unequipping the item
// but it should trigger the reverse of the equip or unequip when another start is triggered.
int index;
if (!ability.IsActive) {
// The priority can be ignored if the ability should be force started.
if (!ignorePriority) {
// If the ability is not a concurrent ability then it can only be started if it has a lower index than any other active abilities.
if (!ability.IsConcurrent) {
for (int i = 0; i < m_ActiveAbilityCount; ++i) {
var ignoreLocalPriority = m_ActiveAbilities[i].IgnorePriority && ability.IgnorePriority;
if (m_ActiveAbilities[i].IsConcurrent) {
// The ability cannot be started if a concurrent ability is active and has a lower index.
if (((!ignoreLocalPriority && m_ActiveAbilities[i].Index < ability.Index) || ignoreLocalPriority) && m_ActiveAbilities[i].ShouldBlockAbilityStart(ability)) {
return false;
}
} else {
// The ability cannot be started if another ability is already active and has a lower index or if the active ability says the current ability cannot be started.
if ((m_ActiveAbilities[i].Index < ability.Index && !ignoreLocalPriority) || m_ActiveAbilities[i].ShouldBlockAbilityStart(ability)) {
return false;
} else {
// Stop any abilities that have a higher index to prevent two non-concurrent abilities from running at the same time.
TryStopAbility(m_ActiveAbilities[i], true);
}
}
}
}
// The ability cannot be started if the active ability says the current ability cannot be started.
for (int i = 0; i < m_ActiveAbilityCount; ++i) {
if (m_ActiveAbilities[i].ShouldBlockAbilityStart(ability)) {
return false;
}
}
for (int i = 0; i < m_ActiveItemAbilityCount; ++i) {
if (m_ActiveItemAbilities[i].ShouldBlockAbilityStart(ability)) {
return false;
}
}
}
// The ability can start. Stop any currently active abilities that should not be started because the current ability has started.
for (int i = m_ActiveAbilityCount - 1; i >= 0; --i) {
if (ability.ShouldStopActiveAbility(m_ActiveAbilities[i])) {
TryStopAbility(m_ActiveAbilities[i], true);
}
}
for (int i = m_ActiveItemAbilityCount - 1; i >= 0; --i) {
if (ability.ShouldStopActiveAbility(m_ActiveItemAbilities[i])) {
TryStopAbility(m_ActiveItemAbilities[i], true);
}
}
// Give the ability one more chance to initialize any variables or stop from starting. This is important for abilities that are being started
// after a period of time such as from the Move Towards ability or the Item Equip Verifier.
if (!ability.AbilityWillStart()) {
return false;
}
var moveEquipStarted = false;
// The ability may require the character to first move to a specific location before it can start.
if (!(ability is MoveTowards) && m_MoveTowardsAbility != null) {
// If StartMoving returns true then the MoveTowards ability has started and it will start the original
// ability after the character has arrived at the destination.
if (m_MoveTowardsAbility.StartMoving(ability.GetMoveTowardsLocations(), ability)) {
moveEquipStarted = true;
}
}
// The ability may first need to unequip any equipped items before it can start.
if (!(ability is ItemEquipVerifier) && m_ItemEquipVerifierAbility != null) {
// If TryToggleItem returns true then the ItemEquipVerifier ability has started and it will start the original ability after
// the character has finished unequipping the equipped items.
if (m_ItemEquipVerifierAbility.TryToggleItem(ability, true) && !ability.ImmediateStartItemVerifier) {
moveEquipStarted = true;
}
}
// Wait for the MoveTowards and ItemEquipVerifier abilities to end before starting the new ability.
if (moveEquipStarted) {
return true;
}
// Insert in the active abilities array according to priority.
index = m_ActiveAbilityCount;
for (int i = 0; i < m_ActiveAbilityCount; ++i) {
if (m_ActiveAbilities[i].Index > ability.Index) {
index = i;
break;
}
}
// Make space for the new ability.
for (int i = m_ActiveAbilityCount; i > index; --i) {
m_ActiveAbilities[i] = m_ActiveAbilities[i - 1];
m_ActiveAbilities[i].ActiveIndex = i;
}
m_ActiveAbilities[index] = ability;
m_ActiveAbilityCount++;
} else {
// The ability is already active - start it again for a multiple start.
index = ability.ActiveIndex;
}
// Execute the event before the ability is started in case the ability is stopped within the start.
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterAbilityActive", ability, true);
if (m_OnAbilityActiveEvent != null) {
m_OnAbilityActiveEvent.Invoke(ability, true);
}
ability.StartAbility(index);
m_DirtyAbilityParameter = true;
return true;
}
return false;
}
///
/// Tries to start the specified item ability.
///
/// The item ability to try to start.
/// True if the ability was started.
public bool TryStartAbility(ItemAbility itemAbility)
{
return TryStartAbility(itemAbility, false);
}
///
/// Tries to start the specified item ability.
///
/// The item ability to try to start.
/// Should the CanStartAbility check be ignored?
/// True if the ability was started.
public bool TryStartAbility(ItemAbility itemAbility, bool ignoreCanStartCheck)
{
// Start the ability if it is not active or can be started multiple times, enabled, and can be started.
if ((!itemAbility.IsActive || itemAbility.CanReceiveMultipleStarts) && itemAbility.Enabled && (ignoreCanStartCheck || itemAbility.CanStartAbility())) {
// The ability cannot be started if the active ability says the current ability cannot be started.
for (int i = 0; i < m_ActiveItemAbilityCount; ++i) {
if (m_ActiveItemAbilities[i].ShouldBlockAbilityStart(itemAbility)) {
return false;
}
}
for (int i = 0; i < m_ActiveAbilityCount; ++i) {
if (m_ActiveAbilities[i].ShouldBlockAbilityStart(itemAbility)) {
return false;
}
}
// The ability can start. Stop any currently active abilities that should not be started because the current ability has started.
for (int i = m_ActiveItemAbilityCount - 1; i >= 0; --i) {
if (itemAbility.ShouldStopActiveAbility(m_ActiveItemAbilities[i])) {
TryStopAbility(m_ActiveItemAbilities[i], true);
}
}
for (int i = m_ActiveAbilityCount - 1; i >= 0; --i) {
if (itemAbility.ShouldStopActiveAbility(m_ActiveAbilities[i])) {
TryStopAbility(m_ActiveAbilities[i], true);
}
}
// The ability may already be active if the ability can receive multiple starts. Multiple starts are useful for item abilities that need to be active
// over a period of time but can be updated with the input start type while active. A good example of this is the Toggle Equip Item ability. When
// this ability starts it sets an Animator parameter to equip or unequip the item. The ability continues to run while equipping or unequipping the item
// but it should trigger the reverse of the equip or unequip when another start is triggered.
int index;
if (!itemAbility.IsActive) {
// Notify the ability that it will start. This method isn't as useful for ItemAbilities because the ability will always be immediately started after this,
// but it is added for consistency with the ability system.
if (!itemAbility.AbilityWillStart()) {
return false;
}
// Insert in the active abilities array according to priority.
index = m_ActiveItemAbilityCount;
for (int i = 0; i < m_ActiveItemAbilityCount; ++i) {
if (m_ActiveItemAbilities[i].Index > itemAbility.Index) {
index = i;
break;
}
}
// Make space for the new item ability.
for (int i = m_ActiveItemAbilityCount; i > index; --i) {
m_ActiveItemAbilities[i] = m_ActiveItemAbilities[i - 1];
m_ActiveItemAbilities[i].ActiveIndex = i;
}
m_ActiveItemAbilities[index] = itemAbility;
m_ActiveItemAbilityCount++;
} else {
// The ability is already active - start it again for a multiple start.
index = itemAbility.ActiveIndex;
}
// Execute the event before the ability is started in case the ability is stopped within the start.
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterItemAbilityActive", itemAbility, true);
if (m_OnItemAbilityActiveEvent != null) {
m_OnItemAbilityActiveEvent.Invoke(itemAbility, true);
}
itemAbility.StartAbility(index);
m_DirtyItemAbilityParameter = true;
return true;
}
return false;
}
///
/// Sets the ability animator parameters to the ability with the highest priority.
///
public void UpdateAbilityAnimatorParameters()
{
UpdateAbilityAnimatorParameters(false);
}
///
/// Sets the ability animator parameters to the ability with the highest priority.
///
/// Should the parameters be updated immediately?
public void UpdateAbilityAnimatorParameters(bool immediateUpdate)
{
if (m_AnimatorMonitor == null) {
return;
}
// Wait to update until the proper time so the animator synchronizes properly.
if (!immediateUpdate) {
m_DirtyAbilityParameter = true;
return;
}
m_DirtyAbilityParameter = false;
int abilityIndex = 0, intData = 0;
var floatData = 0f;
bool concurrentAbilityIndex = false;
bool setAbilityIndex = true, setStateIndex = true, setAbilityFloatData = true;
for (int i = 0; i < m_ActiveAbilityCount; ++i) {
if (setAbilityIndex && m_ActiveAbilities[i].AbilityIndexParameter != -1) {
abilityIndex = m_ActiveAbilities[i].AbilityIndexParameter;
concurrentAbilityIndex= m_ActiveAbilities[i].IsConcurrent;
setAbilityIndex = false;
}
if (setStateIndex && m_ActiveAbilities[i].AbilityIntData != -1) {
intData = m_ActiveAbilities[i].AbilityIntData;
setStateIndex = false;
}
if (setAbilityFloatData && m_ActiveAbilities[i].AbilityFloatData != -1) {
floatData = m_ActiveAbilities[i].AbilityFloatData;
setAbilityFloatData = false;
}
}
// A negative ability index indicates that the move towards ability is active.
if (m_MoveTowardsAbility != null && m_MoveTowardsAbility.IsActive && !concurrentAbilityIndex) {
abilityIndex *= -1;
}
m_AnimatorMonitor.SetAbilityIndexParameter(abilityIndex);
m_AnimatorMonitor.SetAbilityIntDataParameter(intData);
m_AnimatorMonitor.SetAbilityFloatDataParameter(floatData, m_TimeScale);
}
///
/// Sets the ability animator parameters to the ability with the highest priority.
///
public void UpdateItemAbilityAnimatorParameters()
{
UpdateItemAbilityAnimatorParameters(false);
}
///
/// Sets the item animator parameters to the item ability with the highest priority.
///
/// Should the parameters be updated immediately?
public void UpdateItemAbilityAnimatorParameters(bool immediateUpdate)
{
if (m_AnimatorMonitor == null || m_ItemSlotStateIndex == null) {
return;
}
// Wait to update until the proper time so the animator synchronizes properly.
if (!immediateUpdate) {
m_DirtyItemAbilityParameter = true;
return;
}
m_DirtyItemAbilityParameter = false;
// Reset the values.
for (int i = 0; i < m_ItemSlotStateIndex.Length; ++i) {
m_ItemSlotStateIndex[i] = -1;
m_ItemSlotSubstateIndex[i] = -1;
}
// The value can only be assigned if it hasn't already been assigned.
int value;
for (int i = 0; i < m_ActiveItemAbilityCount; ++i) {
for (int j = 0; j < m_ItemSlotSubstateIndex.Length; ++j) {
if (m_ItemSlotStateIndex[j] == -1 && (value = m_ActiveItemAbilities[i].GetItemStateIndex(j)) != -1) {
m_ItemSlotStateIndex[j] = value;
}
if (m_ItemSlotSubstateIndex[j] == -1 && (value = m_ActiveItemAbilities[i].GetItemSubstateIndex(j)) != -1) {
m_ItemSlotSubstateIndex[j] = value;
}
}
}
// Assign the values to the animator.
for (int i = 0; i < m_ItemSlotStateIndex.Length; ++i) {
if (m_ItemSlotStateIndex[i] == -1) { m_ItemSlotStateIndex[i] = 0; }
if (m_ItemSlotSubstateIndex[i] == -1) { m_ItemSlotSubstateIndex[i] = 0; }
m_AnimatorMonitor.SetItemStateIndexParameter(i, m_ItemSlotStateIndex[i]);
m_AnimatorMonitor.SetItemSubstateIndexParameter(i, m_ItemSlotSubstateIndex[i]);
}
m_AnimatorMonitor.SetAimingParameter(m_Aiming);
}
///
/// Tries to stop the specified ability.
///
/// The ability to try to stop.
/// True if the ability was stopped.
public bool TryStopAbility(Ability ability)
{
return TryStopAbility(ability, false);
}
///
/// Tries to stop the specified ability.
///
/// The ability to try to stop.
/// Should the ability be force stopped?
public bool TryStopAbility(Ability ability, bool force)
{
// The ability can't be stopped if it isn't active.
if (!ability.IsActive) {
return false;
}
ability.WillTryStopAbility();
// CanStopAbility and CanForceStopAbility can prevent the ability from stopping.
if ((!force && !ability.CanStopAbility()) || (force && !ability.CanForceStopAbility())) {
return false;
}
// Update the active ability array by removing the stopped ability.
if (ability is ItemAbility) {
for (int i = ability.ActiveIndex; i < m_ActiveItemAbilityCount - 1; ++i) {
m_ActiveItemAbilities[i] = m_ActiveItemAbilities[i + 1];
m_ActiveItemAbilities[i].ActiveIndex = i;
}
m_ActiveItemAbilityCount--;
m_ActiveItemAbilities[m_ActiveItemAbilityCount] = null;
ability.StopAbility(force, true);
// Let others know that the ability has stopped.
var itemAbility = ability as ItemAbility;
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterItemAbilityActive", itemAbility, false);
if (m_OnItemAbilityActiveEvent != null) {
m_OnItemAbilityActiveEvent.Invoke(itemAbility, false);
}
m_DirtyItemAbilityParameter = true;
} else {
for (int i = ability.ActiveIndex; i < m_ActiveAbilityCount - 1; ++i) {
m_ActiveAbilities[i] = m_ActiveAbilities[i + 1];
m_ActiveAbilities[i].ActiveIndex = i;
}
m_ActiveAbilityCount--;
m_ActiveAbilities[m_ActiveAbilityCount] = null;
ability.StopAbility(force, true);
// Let others know that the ability has stopped.
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterAbilityActive", ability, false);
if (m_OnAbilityActiveEvent != null) {
m_OnAbilityActiveEvent.Invoke(ability, false);
}
m_DirtyAbilityParameter = true;
// After the ability has stopped it may need to equip the unequipped items again.
if (!(ability is ItemEquipVerifier) && !(ability is MoveTowards) && m_ItemEquipVerifierAbility != null) {
m_ItemEquipVerifierAbility.TryToggleItem(ability, false);
}
}
// If the AnimatorMonitor is enabled then the character is dead and the animator parameters should be updated immediately.
if (m_AnimatorMonitor != null && m_AnimatorMonitor.enabled) {
UpdateDirtyAbilityAnimatorParameters();
}
return true;
}
///
/// Returns the ability of type T.
///
/// The type of ability to return.
/// The ability of type T. Can be null.
public T GetAbility() where T : Ability
{
return GetAbility(-1);
}
///
/// Returns the ability of type T with the specified index.
///
/// The type of ability to return.
/// The index of the ability. -1 will ignore the index.
/// The ability of type T with the specified index. Can be null.
public T GetAbility(int index) where T : Ability
{
var type = typeof(T);
var allAbilities = (typeof(ItemAbility).IsAssignableFrom(type) ? m_ItemAbilities : m_Abilities);
if (allAbilities != null) {
for (int i = 0; i < allAbilities.Length; ++i) {
if (type.IsInstanceOfType(allAbilities[i]) && (index == -1 || index == allAbilities[i].Index)) {
return allAbilities[i] as T;
}
}
}
return null;
}
///
/// Returns the abilities of type T.
///
/// The type of ability to return.
/// The abilities of type T. Can be null.
public T[] GetAbilities() where T : Ability
{
return GetAbilities(-1);
}
///
/// Returns the abilities of type T with the specified index.
///
/// The type of ability to return.
/// The index of the ability. -1 will ignore the index.
/// The abilities of type T with the specified index. Can be null.
public T[] GetAbilities(int index) where T : Ability
{
if (m_Abilities == null) { DeserializeAbilities(); }
if (m_ItemAbilities == null) { DeserializeItemAbilities(); }
var allAbilities = (typeof(ItemAbility).IsAssignableFrom(typeof(T)) ? m_ItemAbilities : m_Abilities);
var count = 0;
if (allAbilities != null) {
// Determine the total number of abilities first so only one allocation is made.
for (int i = 0; i < allAbilities.Length; ++i) {
if (allAbilities[i] is T && (index == -1 || index == allAbilities[i].Index)) {
count++;
}
}
if (count > 0) {
var abilities = new T[count];
count = 0;
for (int i = 0; i < allAbilities.Length; ++i) {
if (allAbilities[i] is T && (index == -1 || index == allAbilities[i].Index)) {
abilities[count] = allAbilities[i] as T;
count++;
if (count == abilities.Length) {
break;
}
}
}
return abilities;
}
}
return null;
}
///
/// Returns the abilities of the specified type with the specified index.
///
/// The type of ability.
/// The abilities of the specified type with the specified index. Can be null.
public Ability[] GetAbilities(System.Type type)
{
return GetAbilities(type, -1);
}
///
/// Returns the abilities of the specified type with the specified index.
///
/// The type of ability.
/// The index of the ability. -1 will ignore the index.
/// The abilities of the specified type with the specified index. Can be null.
public Ability[] GetAbilities(System.Type type, int index)
{
if (type == null) { return null; }
if (m_Abilities == null) { DeserializeAbilities(); }
if (m_ItemAbilities == null) { DeserializeItemAbilities(); }
var allAbilities = (typeof(ItemAbility).IsAssignableFrom(type) ? m_ItemAbilities : m_Abilities);
var count = 0;
if (allAbilities != null) {
// Determine the total number of abilities first so only one allocation is made.
for (int i = 0; i < allAbilities.Length; ++i) {
if (type.IsInstanceOfType(allAbilities[i]) && (index == -1 || index == allAbilities[i].Index)) {
count++;
}
}
if (count > 0) {
var abilities = new Ability[count];
count = 0;
for (int i = 0; i < allAbilities.Length; ++i) {
if (type.IsInstanceOfType(allAbilities[i]) && (index == -1 || index == allAbilities[i].Index)) {
abilities[count] = allAbilities[i];
count++;
if (count == abilities.Length) {
break;
}
}
}
return abilities;
}
}
return null;
}
///
/// Is the ability of the specified type active?
///
/// The type of ability.
/// True if the ability is active.
public bool IsAbilityTypeActive() where T : Ability
{
var isItemAbility = typeof(ItemAbility).IsAssignableFrom(typeof(T));
var activeAbilities = isItemAbility ? m_ActiveItemAbilities : m_ActiveAbilities;
var count = isItemAbility ? m_ActiveItemAbilityCount : m_ActiveAbilityCount;
if (activeAbilities != null) {
for (int i = 0; i < count; ++i) {
if (typeof(T).IsInstanceOfType(activeAbilities[i])) {
return true;
}
}
}
return false;
}
///
/// Tries to start the specified effect.
///
/// The effect to try to start.
/// True if the effect was started.
public bool TryStartEffect(Effect effect)
{
// The effect can't be started if it is already active, isn't enabled, or can't be started.
if (effect.IsActive || !effect.Enabled || !effect.CanStartEffect()) {
return false;
}
m_ActiveEffects[m_ActiveEffectCount] = effect;
m_ActiveEffectCount++;
effect.StartEffect(m_ActiveEffectCount);
return true;
}
///
/// Tries to stop the specified effect.
///
/// The effect to try to stop.
/// True if the effect was stopped.
public bool TryStopEffect(Effect effect)
{
// The effect can't be stopped if it isn't active.
if (!effect.IsActive) {
return false;
}
// Store the active index ahead of time because StopEffect will reset the value.
var index = effect.ActiveIndex;
effect.StopEffect(true);
// Update the active effect array by removing the stopped ability.
for (int i = index; i < m_ActiveEffectCount - 1; ++i) {
m_ActiveEffects[i] = m_ActiveEffects[i + 1];
}
m_ActiveEffectCount--;
m_ActiveEffects[m_ActiveEffectCount] = null;
return true;
}
///
/// Returns the effect of type T.
///
/// The type of effect to return.
/// The index of the ability. -1 will ignore the index.
/// The effect of type T. Can be null.
public T GetEffect() where T : Effect
{
return GetEffect(-1);
}
///
/// Returns the effect of type T at the specified index.
///
/// The type of effect to return.
/// The index of the ability. -1 will ignore the index.
/// The effect of type T. Can be null.
public T GetEffect(int index) where T : Effect
{
if (m_Effects == null) { DeserializeEffects(); }
if (m_Effects != null) {
var type = typeof(T);
for (int i = 0; i < m_Effects.Length; ++i) {
if (type.IsInstanceOfType(m_Effects[i]) && (index == -1 || index == m_Effects[i].Index)) {
return m_Effects[i] as T;
}
}
}
return null;
}
///
/// Returns the effect of the specified type.
///
/// The type of effect to retrieve.
/// The effect of the specified type. Can be null.
public Effect GetEffect(System.Type type)
{
return GetEffect(type, -1);
}
///
/// Returns the effect of the specified type at the specified index.
///
/// The type of effect to retrieve.
/// The index of the ability. -1 will ignore the index.
/// The effect of the specified type. Can be null.
public Effect GetEffect(System.Type type, int index)
{
if (type == null) { return null; }
if (m_Effects == null) { DeserializeEffects(); }
if (m_Effects != null) {
for (int i = 0; i < m_Effects.Length; ++i) {
if (type.IsInstanceOfType(m_Effects[i]) && (index == -1 || index == m_Effects[i].Index)) {
return m_Effects[i];
}
}
}
return null;
}
///
/// Callback from the animator when root motion has updated.
///
protected override void OnAnimatorMove()
{
// If using root motion rotation then the delta position should be retrieved from the animation. If it not being retrieved then the abilities
// have an option to change the delta position based on the AnimatorMotion ScriptableObject.
if (UsingRootMotionPosition) {
m_AnimatorDeltaPosition += m_Animator.deltaPosition;
} else {
for (int i = 0; i < m_ActiveAbilityCount; ++i) {
m_ActiveAbilities[i].OnAnimatorMove(true);
}
for (int i = 0; i < m_ActiveItemAbilityCount; ++i) {
m_ActiveItemAbilities[i].OnAnimatorMove(true);
}
}
// If using root motion rotation then the delta rotation should be retrieved from the animation. If it not being retrieved then the abilities
// have an option to change the delta rotation based on the AnimatorMotion ScriptableObject.
if (UsingRootMotionRotation) {
m_AnimatorDeltaRotation *= m_Animator.deltaRotation;
} else {
for (int i = 0; i < m_ActiveAbilityCount; ++i) {
m_ActiveAbilities[i].OnAnimatorMove(false);
}
for (int i = 0; i < m_ActiveItemAbilityCount; ++i) {
m_ActiveItemAbilities[i].OnAnimatorMove(false);
}
}
base.OnAnimatorMove();
}
///
/// The character perspective between first and third person has changed.
///
/// Is the character in a first person perspective?
private void OnChangePerspectives(bool firstPersonPerspective)
{
m_FirstPersonPerspective = firstPersonPerspective;
if (firstPersonPerspective) {
if (!string.IsNullOrEmpty(m_ThirdPersonStateName)) {
StateManager.SetState(m_GameObject, m_ThirdPersonStateName, false);
}
if (!string.IsNullOrEmpty(m_FirstPersonStateName)) {
StateManager.SetState(m_GameObject, m_FirstPersonStateName, true);
}
if (!string.IsNullOrEmpty(m_FirstPersonMovementTypeFullName)) {
SetMovementType(m_FirstPersonMovementTypeFullName);
}
} else {
if (!string.IsNullOrEmpty(m_FirstPersonStateName)) {
StateManager.SetState(m_GameObject, m_FirstPersonStateName, false);
}
if (!string.IsNullOrEmpty(m_ThirdPersonStateName)) {
StateManager.SetState(m_GameObject, m_ThirdPersonStateName, true);
}
if (!string.IsNullOrEmpty(m_ThirdPersonMovementTypeFullName)) {
SetMovementType(m_ThirdPersonMovementTypeFullName);
}
}
if (m_MovementType == null) {
SetMovementType(m_MovementTypeFullName);
}
}
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
///
/// Pushes the target Rigidbody in the specified direction.
///
/// The Rigidbody to push.
/// The direction that the character is moving.
/// The point at which to apply the push force.
/// The radius of the pushing collider.
/// Was the rigidbody pushed?
protected override bool PushRigidbody(Rigidbody targetRigidbody, Vector3 moveDirection, Vector3 point, float radius)
{
var pushed = base.PushRigidbody(targetRigidbody, moveDirection, point, radius);
if (pushed && m_NetworkInfo != null) {
m_NetworkCharacter.PushRigidbody(targetRigidbody, (moveDirection / Time.deltaTime) * (m_Mass / targetRigidbody.mass) * 0.01f, point);
}
return pushed;
}
#endif
///
/// Sets the rotation of the character.
///
/// The rotation to set.
public override void SetRotation(Quaternion rotation)
{
SetRotation(rotation, true);
}
///
/// Sets the rotation of the character.
///
/// The rotation to set.
/// Should the animator be snapped into position?
public void SetRotation(Quaternion rotation, bool snapAnimator)
{
// If the character isn't active then only the transform needs to be set.
if (m_GameObject == null) {
transform.rotation = rotation;
return;
}
base.SetRotation(rotation);
if (snapAnimator) {
StopAllAbilities(false);
}
// If the index is -1 then the character isn't registered with the Kinematic Object Manager.
if (m_KinematicObjectIndex != -1) {
KinematicObjectManager.SetCharacterRotation(m_KinematicObjectIndex, rotation);
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterImmediateTransformChange", snapAnimator);
}
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
m_NetworkCharacter.SetRotation(rotation, snapAnimator);
}
#endif
}
///
/// Sets the position of the character.
///
/// The position to set.
public override void SetPosition(Vector3 position)
{
SetPosition(position, true);
}
///
/// Sets the position of the character.
///
/// The position to set.
/// Should the animator be snapped into position?
public void SetPosition(Vector3 position, bool snapAnimator)
{
// If the character isn't active then only the transform needs to be set.
if (m_GameObject == null) {
transform.position = position;
return;
}
m_MaxHeight = float.NegativeInfinity;
m_MaxHeightPosition = position;
base.SetPosition(position);
if (snapAnimator) {
StopAllAbilities(false);
}
// If the index is -1 then the character isn't registered with the Kinematic Object Manager.
if (m_KinematicObjectIndex != -1) {
KinematicObjectManager.SetCharacterPosition(m_KinematicObjectIndex, position);
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterImmediateTransformChange", snapAnimator);
}
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
m_NetworkCharacter.SetPosition(position, snapAnimator);
}
#endif
}
///
/// Resets the rotation and position to their default values.
///
public override void ResetRotationPosition()
{
if (m_GameObject == null) {
return;
}
base.ResetRotationPosition();
Moving = false;
m_MaxHeight = float.NegativeInfinity;
m_MaxHeightPosition = m_Transform.position;
m_AnimatorDeltaPosition = Vector3.zero;
m_AnimatorDeltaRotation = Quaternion.identity;
// If the index is -1 then the character isn't registered with the Kinematic Object Manager.
if (m_KinematicObjectIndex == -1) {
return;
}
KinematicObjectManager.SetCharacterRotation(m_KinematicObjectIndex, m_Transform.rotation);
KinematicObjectManager.SetCharacterPosition(m_KinematicObjectIndex, m_Transform.position);
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
m_NetworkCharacter.ResetRotationPosition();
}
#endif
}
///
/// Sets the position and rotation of the character.
///
/// The position to set.
/// The rotation to set.
public void SetPositionAndRotation(Vector3 position, Quaternion rotation)
{
SetPositionAndRotation(position, rotation, true, true);
}
///
/// Sets the position and rotation of the character.
///
/// The position to set.
/// The rotation to set.
/// Should the animator be snapped into position?
public void SetPositionAndRotation(Vector3 position, Quaternion rotation, bool snapAnimator)
{
SetPositionAndRotation(position, rotation, snapAnimator, true);
}
///
/// Sets the position and rotation of the character.
///
/// The position to set.
/// The rotation to set.
/// Should the animator be snapped into position?
/// Should all abilities be stopped?
public void SetPositionAndRotation(Vector3 position, Quaternion rotation, bool snapAnimator, bool stopAllAbilities)
{
if (m_GameObject == null) {
return;
}
if (stopAllAbilities) {
StopAllAbilities(false);
}
m_MaxHeight = float.NegativeInfinity;
m_MaxHeightPosition = position;
m_AnimatorDeltaPosition = Vector3.zero;
m_AnimatorDeltaRotation = Quaternion.identity;
base.SetRotation(rotation);
base.SetPosition(position);
// If the index is -1 then the character isn't registered with the Kinematic Object Manager.
if (m_KinematicObjectIndex != -1) {
KinematicObjectManager.SetCharacterRotation(m_KinematicObjectIndex, rotation);
KinematicObjectManager.SetCharacterPosition(m_KinematicObjectIndex, position);
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterImmediateTransformChange", snapAnimator);
}
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
m_NetworkCharacter.SetPositionAndRotation(position, rotation, snapAnimator);
}
#endif
}
///
/// Activates or deactivates the character.
///
/// Is the character active?
public void SetActive(bool active)
{
SetActive(active, false);
}
///
/// Activates or deactivates the character.
///
/// Is the character active?
/// Should the OnShowUI event be executed?
public void SetActive(bool active, bool uiEvent)
{
if (m_GameObject == null) {
m_GameObject = gameObject;
}
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterActivate", active);
m_GameObject.SetActive(active);
if (active) {
// Do a pass on trying to start any abilities in case they should be started immediately when activated.
UpdateAbilities(m_Abilities);
UpdateAbilities(m_ItemAbilities);
UpdateDirtyAbilityAnimatorParameters();
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterImmediateTransformChange", true);
}
if (uiEvent) {
EventHandler.ExecuteEvent(m_GameObject, "OnShowUI", active);
}
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
m_NetworkCharacter.SetActive(active, uiEvent);
}
#endif
}
///
/// Casts a ray using in the specified direction. If the character has multiple colliders added then a ray will be cast from each collider.
/// A CapsuleCast or SphereCast is used depending on the type of collider that has been added.
///
/// The direction to perform the cast.
/// The layers to perform the cast on.
/// The offset of the cast.
/// The hit RaycastHit.
/// Did the cast hit an object?
public bool SingleCast(Vector3 direction, Vector3 offset, int layers, ref RaycastHit result)
{
for (int i = 0; i < m_ColliderCount; ++i) {
// Determine if the collider would intersect any objects.
if (m_Colliders[i] is CapsuleCollider) {
Vector3 startEndCap, endEndCap;
var capsuleCollider = m_Colliders[i] as CapsuleCollider;
MathUtility.CapsuleColliderEndCaps(capsuleCollider, capsuleCollider.transform.position, capsuleCollider.transform.rotation, out startEndCap, out endEndCap);
var radius = capsuleCollider.radius * MathUtility.ColliderRadiusMultiplier(capsuleCollider) - ColliderSpacing;
if (Physics.CapsuleCast(startEndCap + offset, endEndCap + offset, radius, direction.normalized, out result, direction.magnitude + ColliderSpacing, layers, QueryTriggerInteraction.Ignore)) {
return true;
}
} else { // SphereCollider.
var sphereCollider = m_Colliders[i] as SphereCollider;
var radius = sphereCollider.radius * MathUtility.ColliderRadiusMultiplier(sphereCollider) - ColliderSpacing;
if (Physics.SphereCast(sphereCollider.transform.TransformPoint(sphereCollider.center) + offset, radius, direction.normalized,
out result, direction.magnitude + ColliderSpacing, layers, QueryTriggerInteraction.Ignore)) {
return true;
}
}
}
return false;
}
///
/// Casts a ray using in the specified direction. If the character has multiple colliders added then a ray will be cast from each collider.
/// A CapsuleCast or SphereCast is used depending on the type of collider that has been added. The result is stored in the m_CombinedRaycastHits array.
///
/// The direction to perform the cast.
/// Any offset to apply to the cast.
/// The found raycast hits.
/// A mapping between the raycast hit and collider index.
/// The number of objects hit from the cast.
public int Cast(Vector3 direction, Vector3 offset, ref RaycastHit[] combinedRaycastHits, ref Dictionary colliderIndexMap)
{
if (m_ColliderCount > 1) {
if (combinedRaycastHits == null) {
combinedRaycastHits = new RaycastHit[m_ColliderCount * m_RaycastHits.Length];
colliderIndexMap = new Dictionary();
}
// Clear the index map to start it off fresh.
colliderIndexMap.Clear();
}
var hitCount = 0;
for (int i = 0; i < m_ColliderCount; ++i) {
int localHitCount;
// Determine if the collider would intersect any objects.
if (m_Colliders[i] is CapsuleCollider) {
Vector3 startEndCap, endEndCap;
var capsuleCollider = m_Colliders[i] as CapsuleCollider;
MathUtility.CapsuleColliderEndCaps(capsuleCollider, capsuleCollider.transform.position + offset, capsuleCollider.transform.rotation, out startEndCap, out endEndCap);
var radius = capsuleCollider.radius * MathUtility.ColliderRadiusMultiplier(capsuleCollider) - ColliderSpacing;
localHitCount = Physics.CapsuleCastNonAlloc(startEndCap, endEndCap, radius, direction.normalized, m_RaycastHits, direction.magnitude + ColliderSpacing, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore);
} else { // SphereCollider.
var sphereCollider = m_Colliders[i] as SphereCollider;
var radius = sphereCollider.radius * MathUtility.ColliderRadiusMultiplier(sphereCollider) - ColliderSpacing;
localHitCount = Physics.SphereCastNonAlloc(sphereCollider.transform.TransformPoint(sphereCollider.center) + offset, radius, direction.normalized,
m_RaycastHits, direction.magnitude + ColliderSpacing, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore);
}
if (localHitCount > 0) {
// The mapping needs to be saved if there are multiple colliders.
if (m_ColliderCount > 1) {
int validHitCount = 0;
for (int j = 0; j < localHitCount; ++j) {
if (colliderIndexMap.ContainsKey(m_RaycastHits[j])) {
continue;
}
// Ensure the array is large enough.
if (hitCount + j >= combinedRaycastHits.Length) {
Debug.LogWarning("Warning: The maximum number of collisions has been reached. Consider increasing the CharacterLocomotion MaxCollisionCount value.");
continue;
}
colliderIndexMap.Add(m_RaycastHits[j], i);
combinedRaycastHits[hitCount + j] = m_RaycastHits[j];
validHitCount += 1;
}
hitCount += validHitCount;
} else {
combinedRaycastHits = m_RaycastHits;
hitCount += localHitCount;
}
}
}
return hitCount;
}
///
/// Returns the number of colliders that are overlapping the character's collider.
///
/// The offset to apply to the character's collider position.
/// The number of objects which overlap the collider.
public int OverlapCount(Vector3 offset)
{
var count = 0;
for (int i = 0; i < m_ColliderCount; ++i) {
count += OverlapCount(m_Colliders[i], offset);
}
return count;
}
///
/// The character has entered a trigger.
///
/// The trigger collider that the character entered.
private void OnTriggerEnter(Collider other)
{
// Forward the enter to the abilities.
if (m_Abilities != null) {
for (int i = 0; i < m_Abilities.Length; ++i) {
if (!m_Abilities[i].Enabled) {
continue;
}
m_Abilities[i].OnTriggerEnter(other);
}
}
if (m_ItemAbilities != null) {
for (int i = 0; i < m_ItemAbilities.Length; ++i) {
if (!m_ItemAbilities[i].Enabled) {
continue;
}
m_ItemAbilities[i].OnTriggerEnter(other);
}
}
}
///
/// The character has exited a trigger.
///
/// The trigger collider that the character exited.
private void OnTriggerExit(Collider other)
{
// Forward the exit to the abilities.
if (m_Abilities != null) {
for (int i = 0; i < m_Abilities.Length; ++i) {
if (!m_Abilities[i].Enabled) {
continue;
}
m_Abilities[i].OnTriggerExit(other);
}
}
if (m_ItemAbilities != null) {
for (int i = 0; i < m_ItemAbilities.Length; ++i) {
if (!m_ItemAbilities[i].Enabled) {
continue;
}
m_ItemAbilities[i].OnTriggerExit(other);
}
}
}
///
/// The character has started or stopped aiming
///
/// Has the character started to aim?
private void OnAiming(bool aiming)
{
m_Aiming = aiming;
}
///
/// The character has died.
///
/// The position of the force.
/// The amount of force which killed the character.
/// The GameObject that killed the character.
private void OnDeath(Vector3 position, Vector3 force, GameObject attacker)
{
m_Alive = false;
// All of the abilities should stop.
StopAllAbilities(true);
// The animator values should reset.
m_YawAngle = 0;
m_InputVector = Vector3.zero;
Moving = false;
// Remote networked characters will not be registered with the KinematicObjectManager.
if (m_KinematicObjectIndex == -1) {
return;
}
// The character is no longer moving.
KinematicObjectManager.SetCharacterMovementInput(m_KinematicObjectIndex, 0, 0);
KinematicObjectManager.SetCharacterDeltaYawRotation(m_KinematicObjectIndex, 0);
}
///
/// Stops all of the active abilities.
///
/// Are the abilities being stopped from death callback?
private void StopAllAbilities(bool fromDeath)
{
for (int i = m_ActiveAbilityCount - 1; i >= 0; --i) {
if (!fromDeath || !m_ActiveAbilities[i].CanStayActivatedOnDeath) {
TryStopAbility(m_ActiveAbilities[i], true);
}
}
for (int i = m_ActiveItemAbilityCount - 1; i >= 0; --i) {
if (!fromDeath || !m_ActiveItemAbilities[i].CanStayActivatedOnDeath) {
TryStopAbility(m_ActiveItemAbilities[i], true);
}
}
}
///
/// The character has respawned.
///
private void OnRespawn()
{
m_Alive = true;
ResetRotationPosition();
// Do a pass on trying to start any abilities and items to ensure they are in sync.
UpdateAbilities(m_Abilities);
UpdateAbilities(m_ItemAbilities);
if (m_AnimatorMonitor != null) {
if (m_LookSource != null) {
m_AnimatorMonitor.SetPitchParameter(m_LookSource.Pitch, 1, 0);
}
m_AnimatorMonitor.SetYawParameter(m_YawAngle, 1, 0);
m_AnimatorMonitor.SetHorizontalMovementParameter(m_InputVector.x, 1, 0);
m_AnimatorMonitor.SetForwardMovementParameter(m_InputVector.y, 1, 0);
m_AnimatorMonitor.SetMovingParameter(m_Moving);
UpdateDirtyAbilityAnimatorParameters();
}
}
///
/// The character's position or rotation has been teleported.
///
/// Should the animator be snapped?
private void OnImmediateTransformChange(bool snapAnimator)
{
// Do a pass on trying to start any abilities and items to ensure they are in sync.
UpdateAbilities(m_Abilities);
UpdateAbilities(m_ItemAbilities);
UpdateAbilityAnimatorParameters(true);
UpdateItemAbilityAnimatorParameters(true);
// Snap the animator after the abilities have updated.
if (snapAnimator) {
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterSnapAnimator");
}
}
///
/// The Animator has snapped into position.
///
private void AnimatorSnapped()
{
ResetRotationPosition();
}
///
/// The character has been disabled.
///
private void OnDisable()
{
// The KinematicObjectManager is responsible for calling the move method.
KinematicObjectManager.UnregisterCharacter(m_KinematicObjectIndex);
m_KinematicObjectIndex = -1;
}
///
/// The GameObject has been destroyed.
///
private void OnDestroy()
{
EventHandler.UnregisterEvent(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
EventHandler.UnregisterEvent(gameObject, "OnCharacterChangePerspectives", OnChangePerspectives);
EventHandler.UnregisterEvent(m_GameObject, "OnAimAbilityAim", OnAiming);
EventHandler.UnregisterEvent(m_GameObject, "OnDeath", OnDeath);
EventHandler.UnregisterEvent(m_GameObject, "OnRespawn", OnRespawn);
EventHandler.UnregisterEvent(m_GameObject, "OnCharacterImmediateTransformChange", OnImmediateTransformChange);
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorSnapped", AnimatorSnapped);
// The current movement type is no longer active when the object is destroyed.
if (m_MovementType != null) {
m_MovementType.ChangeMovementType(false);
}
// Call OnDestroy to notify all of the abilities and effects that the GameObject has been destroyed.
if (m_Abilities != null) {
for (int i = 0; i < m_Abilities.Length; ++i) {
m_Abilities[i].OnDestroy();
}
}
if (m_ItemAbilities != null) {
for (int i = 0; i < m_ItemAbilities.Length; ++i) {
m_ItemAbilities[i].OnDestroy();
}
}
if (m_Effects != null) {
for (int i = 0; i < m_Effects.Length; ++i) {
m_Effects[i].OnDestroy();
}
}
}
}
}