Organize custom scripts under Assets/Baba_yaga and merge Opsive folders to Assets root
This commit is contained in:
8
Assets/Scripts/Character/Abilities/AI.meta
Normal file
8
Assets/Scripts/Character/Abilities/AI.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ceccbd50b42dc3448a66733b390f24ef
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
262
Assets/Scripts/Character/Abilities/AI/NavMeshAgentMovement.cs
Normal file
262
Assets/Scripts/Character/Abilities/AI/NavMeshAgentMovement.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.AI
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Moves the character according to the NavMeshAgent desired velocity.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(NavMeshAgent))]
|
||||
public class NavMeshAgentMovement : PathfindingMovement
|
||||
{
|
||||
[Tooltip("The agent has arrived at the destination when the remaining distance is less than the arrived distance.")]
|
||||
[SerializeField] protected float m_ArrivedDistance = 0.2f;
|
||||
|
||||
public float ArrivedDistance { get { return m_ArrivedDistance; } set { m_ArrivedDistance = value; } }
|
||||
|
||||
private NavMeshAgent m_NavMeshAgent;
|
||||
private Jump m_JumpAbility;
|
||||
private Fall m_FallAbility;
|
||||
|
||||
private bool m_PrevEnabled = true;
|
||||
private Vector2 m_InputVector;
|
||||
private Vector3 m_DeltaRotation;
|
||||
private bool m_UpdateRotation;
|
||||
private int m_LastPathPendingFrame;
|
||||
private int m_NavMeshJumpArea;
|
||||
|
||||
public override Vector2 InputVector { get { return m_InputVector; } }
|
||||
public override Vector3 DeltaRotation { get { return m_DeltaRotation; } }
|
||||
public override bool Enabled { set { base.Enabled = value; if (m_NavMeshAgent != null) { m_NavMeshAgent.enabled = value; } } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_NavMeshAgent = GetComponent<NavMeshAgent>();
|
||||
m_NavMeshAgent.autoTraverseOffMeshLink = false;
|
||||
m_NavMeshAgent.updatePosition = false;
|
||||
m_LastPathPendingFrame = int.MinValue;
|
||||
m_NavMeshJumpArea = NavMesh.GetAreaFromName("Jump");
|
||||
|
||||
m_JumpAbility = m_CharacterLocomotion.GetAbility<Jump>();
|
||||
m_FallAbility = m_CharacterLocomotion.GetAbility<Fall>();
|
||||
|
||||
EventHandler.RegisterEvent<bool>(m_GameObject, "OnCharacterGrounded", OnGrounded);
|
||||
EventHandler.RegisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnRespawn", OnRespawn);
|
||||
|
||||
if (!Enabled) {
|
||||
m_NavMeshAgent.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the destination of the pathfinding agent.
|
||||
/// </summary>
|
||||
/// <param name="target">The position to move towards.</param>
|
||||
/// <returns>True if the destination was set.</returns>
|
||||
public override bool SetDestination(Vector3 target)
|
||||
{
|
||||
// Set the new destination if the ability is already active.
|
||||
if (m_NavMeshAgent.hasPath && IsActive) {
|
||||
return m_NavMeshAgent.SetDestination(target);
|
||||
}
|
||||
|
||||
// The NavMeshAgent must be enabled in order to set the destination.
|
||||
m_PrevEnabled = Enabled;
|
||||
Enabled = true;
|
||||
// Move towards the destination.
|
||||
if (m_NavMeshAgent.isOnNavMesh && m_NavMeshAgent.SetDestination(target)) {
|
||||
StartAbility();
|
||||
return true;
|
||||
}
|
||||
Enabled = m_PrevEnabled;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the ability.
|
||||
/// </summary>
|
||||
public override void Update()
|
||||
{
|
||||
m_InputVector = Vector2.zero;
|
||||
var lookRotation = Quaternion.LookRotation(m_Transform.forward, m_CharacterLocomotion.Up);
|
||||
if (m_NavMeshAgent.isOnOffMeshLink) {
|
||||
UpdateOffMeshLink();
|
||||
} else {
|
||||
// When the path is pending the desired velocity isn't correct. Add a small buffer to ensure the path is valid.
|
||||
if (m_NavMeshAgent.pathPending) {
|
||||
m_LastPathPendingFrame = Time.frameCount;
|
||||
}
|
||||
// Only move if a path exists.
|
||||
if (m_NavMeshAgent.velocity.sqrMagnitude > 0.01f && m_NavMeshAgent.remainingDistance > 0.01f && m_LastPathPendingFrame + 2 < Time.frameCount) {
|
||||
Vector3 velocity;
|
||||
if (m_NavMeshAgent.updateRotation) {
|
||||
lookRotation = Quaternion.LookRotation(m_NavMeshAgent.velocity, m_CharacterLocomotion.Up);
|
||||
// The normalized velocity should be relative to the target rotation.
|
||||
velocity = Quaternion.Inverse(lookRotation) * m_NavMeshAgent.velocity;
|
||||
} else {
|
||||
velocity = m_Transform.InverseTransformDirection(m_NavMeshAgent.velocity);
|
||||
}
|
||||
// Only normalize if the magnitude is greater than 1. This will allow the character to walk.
|
||||
if (velocity.sqrMagnitude > 1) {
|
||||
velocity.Normalize();
|
||||
}
|
||||
m_InputVector.x = velocity.x;
|
||||
m_InputVector.y = velocity.z;
|
||||
}
|
||||
}
|
||||
var rotation = lookRotation * Quaternion.Inverse(m_Transform.rotation);
|
||||
m_DeltaRotation.y = Utility.MathUtility.ClampInnerAngle(rotation.eulerAngles.y);
|
||||
|
||||
base.Update();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the move direction is valid.
|
||||
/// </summary>
|
||||
public override void ApplyPosition()
|
||||
{
|
||||
if (m_NavMeshAgent.remainingDistance < m_NavMeshAgent.stoppingDistance) {
|
||||
// Prevent the character from jittering back and forth to land precisely on the target.
|
||||
var direction = m_Transform.InverseTransformPoint(m_NavMeshAgent.destination);
|
||||
var moveDirection = m_Transform.InverseTransformDirection(m_CharacterLocomotion.MoveDirection);
|
||||
if (Mathf.Abs(moveDirection.x) > Mathf.Abs(direction.x)) {
|
||||
moveDirection.x = direction.x;
|
||||
}
|
||||
if (Mathf.Abs(moveDirection.z) > Mathf.Abs(direction.z)) {
|
||||
moveDirection.z = direction.z;
|
||||
}
|
||||
m_CharacterLocomotion.MoveDirection = m_Transform.TransformDirection(moveDirection);
|
||||
}
|
||||
m_NavMeshAgent.nextPosition = m_Transform.position + m_CharacterLocomotion.MoveDirection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the velocity and look rotation using the off mesh link.
|
||||
/// </summary>
|
||||
protected virtual void UpdateOffMeshLink()
|
||||
{
|
||||
if (m_NavMeshAgent.currentOffMeshLinkData.linkType == OffMeshLinkType.LinkTypeDropDown || m_NavMeshAgent.currentOffMeshLinkData.linkType == OffMeshLinkType.LinkTypeJumpAcross ||
|
||||
(m_NavMeshAgent.currentOffMeshLinkData.linkType == OffMeshLinkType.LinkTypeManual && m_NavMeshAgent.currentOffMeshLinkData.offMeshLink.area == m_NavMeshJumpArea)) {
|
||||
// Ignore the y difference when determining a look direction and velocity.
|
||||
// This will give XZ distances a greater impact when normalized.
|
||||
var direction = m_NavMeshAgent.currentOffMeshLinkData.endPos - m_Transform.position;
|
||||
direction.y = 0;
|
||||
if (direction.sqrMagnitude > 0.1f || m_CharacterLocomotion.Grounded) {
|
||||
var nextPositionDirection = m_Transform.InverseTransformPoint(m_NavMeshAgent.currentOffMeshLinkData.endPos);
|
||||
nextPositionDirection.y = 0;
|
||||
nextPositionDirection.Normalize();
|
||||
|
||||
m_InputVector.x = nextPositionDirection.x;
|
||||
m_InputVector.y = nextPositionDirection.z;
|
||||
}
|
||||
|
||||
// Jump if the agent hasn't jumped yet.
|
||||
if (m_JumpAbility != null && (m_NavMeshAgent.currentOffMeshLinkData.linkType == OffMeshLinkType.LinkTypeJumpAcross ||
|
||||
(m_NavMeshAgent.currentOffMeshLinkData.linkType == OffMeshLinkType.LinkTypeManual && m_NavMeshAgent.currentOffMeshLinkData.offMeshLink.area == m_NavMeshJumpArea))) {
|
||||
if (!m_JumpAbility.IsActive && (m_FallAbility == null || !m_FallAbility.IsActive)) {
|
||||
m_CharacterLocomotion.TryStartAbility(m_JumpAbility);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
if (!base.CanStopAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_NavMeshAgent.hasPath && m_NavMeshAgent.remainingDistance <= m_ArrivedDistance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
if (!m_PrevEnabled) {
|
||||
Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has changed grounded state.
|
||||
/// </summary>
|
||||
/// <param name="grounded">Is the character on the ground?</param>
|
||||
protected virtual void OnGrounded(bool grounded)
|
||||
{
|
||||
if (grounded && m_NavMeshAgent.enabled) {
|
||||
// The agent is no longer on an off mesh link if they just landed.
|
||||
if (m_NavMeshAgent.isOnOffMeshLink && (m_NavMeshAgent.currentOffMeshLinkData.linkType == OffMeshLinkType.LinkTypeDropDown ||
|
||||
m_NavMeshAgent.currentOffMeshLinkData.linkType == OffMeshLinkType.LinkTypeJumpAcross)) {
|
||||
m_NavMeshAgent.CompleteOffMeshLink();
|
||||
}
|
||||
// Warp the NavMeshAgent just in case the navmesh position doesn't match the transform position.
|
||||
var destination = m_NavMeshAgent.destination;
|
||||
m_NavMeshAgent.Warp(m_Transform.position);
|
||||
// Warp can change the destination so make sure that doesn't happen.
|
||||
if (m_NavMeshAgent.destination != destination) {
|
||||
m_NavMeshAgent.SetDestination(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has died.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the force.</param>
|
||||
/// <param name="force">The amount of force which killed the character.</param>
|
||||
/// <param name="attacker">The GameObject that killed the character.</param>
|
||||
private void OnDeath(Vector3 position, Vector3 force, GameObject attacker)
|
||||
{
|
||||
m_UpdateRotation = m_NavMeshAgent.updateRotation;
|
||||
m_NavMeshAgent.updateRotation = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has respawned. Start moving again.
|
||||
/// </summary>
|
||||
private void OnRespawn()
|
||||
{
|
||||
// Reset the NavMeshAgent to the new position.
|
||||
m_NavMeshAgent.Warp(m_Transform.position);
|
||||
if (m_NavMeshAgent.isOnOffMeshLink) {
|
||||
m_NavMeshAgent.ActivateCurrentOffMeshLink(false);
|
||||
}
|
||||
m_NavMeshAgent.updateRotation = m_UpdateRotation;
|
||||
m_LastPathPendingFrame = int.MinValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has been destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<bool>(m_GameObject, "OnCharacterGrounded", OnGrounded);
|
||||
EventHandler.UnregisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnRespawn", OnRespawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43db3a16f3af31c43a1cc4888803ba39
|
||||
timeCreated: 1528032643
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
50
Assets/Scripts/Character/Abilities/AI/PathfindingMovement.cs
Normal file
50
Assets/Scripts/Character/Abilities/AI/PathfindingMovement.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.AI
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for moving the character with a pathfinding implementation.
|
||||
/// </summary>
|
||||
public abstract class PathfindingMovement : Ability
|
||||
{
|
||||
public override bool IsConcurrent { get { return true; } }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the desired input vector value. This will be used by the Ultimate Character Locomotion componnet.
|
||||
/// </summary>
|
||||
public abstract Vector2 InputVector { get; }
|
||||
/// <summary>
|
||||
/// Returns the desired rotation value. This will be used by the Ultimate Character Locomotion component.
|
||||
/// </summary>
|
||||
public abstract Vector3 DeltaRotation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the destination of the pathfinding agent.
|
||||
/// </summary>
|
||||
/// <param name="target">The position to move towards.</param>
|
||||
/// <returns>True if the destination was set.</returns>
|
||||
public abstract bool SetDestination(Vector3 target);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the character's input values.
|
||||
/// </summary>
|
||||
public override void Update()
|
||||
{
|
||||
m_CharacterLocomotion.InputVector = InputVector;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the character's rotation values.
|
||||
/// </summary>
|
||||
public override void UpdateRotation()
|
||||
{
|
||||
m_CharacterLocomotion.DeltaRotation = DeltaRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d286bd3adc4970a40bb47844643ea00e
|
||||
timeCreated: 1527971258
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1062
Assets/Scripts/Character/Abilities/Ability.cs
Normal file
1062
Assets/Scripts/Character/Abilities/Ability.cs
Normal file
File diff suppressed because it is too large
Load Diff
12
Assets/Scripts/Character/Abilities/Ability.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Ability.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f615e1b04c7fee44b2dc7e3e2d879b0
|
||||
timeCreated: 1486968668
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
223
Assets/Scripts/Character/Abilities/AbilityAttributes.cs
Normal file
223
Assets/Scripts/Character/Abilities/AbilityAttributes.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default input name for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public class DefaultInputName : Attribute
|
||||
{
|
||||
private string m_InputName;
|
||||
private int m_Index;
|
||||
public string InputName { get { return m_InputName; } }
|
||||
public int Index { get { return m_Index; } }
|
||||
public DefaultInputName(string inputName) { m_InputName = inputName; }
|
||||
public DefaultInputName(string inputName, int index) { m_InputName = inputName; m_Index = index; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default start type for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultStartType : Attribute
|
||||
{
|
||||
private Ability.AbilityStartType m_StartType;
|
||||
public Ability.AbilityStartType StartType { get { return m_StartType; } }
|
||||
public DefaultStartType(Ability.AbilityStartType startType) { m_StartType = startType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default stop type for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultStopType : Attribute
|
||||
{
|
||||
private Ability.AbilityStopType m_StopType;
|
||||
public Ability.AbilityStopType StopType { get { return m_StopType; } }
|
||||
public DefaultStopType(Ability.AbilityStopType stopType) { m_StopType = stopType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Ability Index for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultAbilityIndex : Attribute
|
||||
{
|
||||
private int m_Value;
|
||||
public int Value { get { return m_Value; } }
|
||||
public DefaultAbilityIndex(int value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Ability Int Data for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultAbilityIntData : Attribute
|
||||
{
|
||||
private int m_Value;
|
||||
public int Value { get { return m_Value; } }
|
||||
public DefaultAbilityIntData(int value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default item state index for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultItemStateIndex : Attribute
|
||||
{
|
||||
private int m_Value;
|
||||
public int Value { get { return m_Value; } }
|
||||
public DefaultItemStateIndex(int value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default State value for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultState : Attribute
|
||||
{
|
||||
private string m_Value;
|
||||
public string Value { get { return m_Value; } }
|
||||
public DefaultState(string value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Allow Positional Input for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultAllowPositionalInput : Attribute
|
||||
{
|
||||
private bool m_Value;
|
||||
public bool Value { get { return m_Value; } }
|
||||
public DefaultAllowPositionalInput(bool value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Allow Rotational Input for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultAllowRotationalInput : Attribute
|
||||
{
|
||||
private bool m_Value;
|
||||
public bool Value { get { return m_Value; } }
|
||||
public DefaultAllowRotationalInput(bool value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Use Gravity value for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultUseGravity : Attribute
|
||||
{
|
||||
private Ability.AbilityBoolOverride m_Value;
|
||||
public Ability.AbilityBoolOverride Value { get { return m_Value; } }
|
||||
public DefaultUseGravity(Ability.AbilityBoolOverride value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Use Root Motion Position value for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultUseRootMotionPosition : Attribute
|
||||
{
|
||||
private Ability.AbilityBoolOverride m_Value;
|
||||
public Ability.AbilityBoolOverride Value { get { return m_Value; } }
|
||||
public DefaultUseRootMotionPosition(Ability.AbilityBoolOverride value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Use Root Motion Rotation value for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultUseRootMotionRotation : Attribute
|
||||
{
|
||||
private Ability.AbilityBoolOverride m_Value;
|
||||
public Ability.AbilityBoolOverride Value { get { return m_Value; } }
|
||||
public DefaultUseRootMotionRotation(Ability.AbilityBoolOverride value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Detect Horizontal Collisions for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultDetectHorizontalCollisions : Attribute
|
||||
{
|
||||
private Ability.AbilityBoolOverride m_Value;
|
||||
public Ability.AbilityBoolOverride Value { get { return m_Value; } }
|
||||
public DefaultDetectHorizontalCollisions(Ability.AbilityBoolOverride value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Detect Vertical Collisions for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultDetectVerticalCollisions : Attribute
|
||||
{
|
||||
private Ability.AbilityBoolOverride m_Value;
|
||||
public Ability.AbilityBoolOverride Value { get { return m_Value; } }
|
||||
public DefaultDetectVerticalCollisions(Ability.AbilityBoolOverride value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Object Detection Mode for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultObjectDetection : Attribute
|
||||
{
|
||||
private DetectObjectAbilityBase.ObjectDetectionMode m_Value;
|
||||
public DetectObjectAbilityBase.ObjectDetectionMode Value { get { return m_Value; } }
|
||||
public DefaultObjectDetection(DetectObjectAbilityBase.ObjectDetectionMode value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Use Look Direction for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultUseLookDirection : Attribute
|
||||
{
|
||||
private bool m_Value;
|
||||
public bool Value { get { return m_Value; } }
|
||||
public DefaultUseLookDirection(bool value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Cast Offset for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultCastOffset : Attribute
|
||||
{
|
||||
private Vector3 m_Value;
|
||||
public Vector3 Value { get { return m_Value; } }
|
||||
public DefaultCastOffset(float x, float y, float z) { m_Value = new Vector3(x, y, z); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Equipped Slots for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultEquippedSlots : Attribute
|
||||
{
|
||||
private int m_Value;
|
||||
public int Value { get { return m_Value; } }
|
||||
public DefaultEquippedSlots(int value) { m_Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute which specifies the default Reequip Slots for the ability.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class DefaultReequipSlots : Attribute
|
||||
{
|
||||
private bool m_Value;
|
||||
public bool Value { get { return m_Value; } }
|
||||
public DefaultReequipSlots(bool value) { m_Value = value; }
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/AbilityAttributes.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/AbilityAttributes.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d69b3e5a6cb42f419cfca854f10fbb6
|
||||
timeCreated: 1520469288
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
146
Assets/Scripts/Character/Abilities/AlignToGravity.cs
Normal file
146
Assets/Scripts/Character/Abilities/AlignToGravity.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The AlignToGravity ability provides a base class for any abilities that want to change the character's up rotation.
|
||||
/// </summary>
|
||||
public abstract class AlignToGravity : Ability
|
||||
{
|
||||
[Tooltip("Specifies the speed that the character can rotate to align to the ground.")]
|
||||
[SerializeField] protected float m_RotationSpeed = 10;
|
||||
[Tooltip("The direction of gravit that should be set when the ability stops. Set to Vector3.zero to disable.")]
|
||||
[SerializeField] protected Vector3 m_StopGravityDirection = Vector3.zero;
|
||||
|
||||
public float RotationSpeed { get { return m_RotationSpeed; } set { m_RotationSpeed = value; } }
|
||||
public Vector3 StopGravityDirection { get { return m_StopGravityDirection; } set { m_StopGravityDirection = value; } }
|
||||
|
||||
public override bool Enabled { get { return base.Enabled; } set { m_Enabled = value; if (!m_Enabled && IsActive) { StopAbility(); } } }
|
||||
public override bool IsConcurrent { get { return true; } }
|
||||
public override bool CanStayActivatedOnDeath { get { return true; } }
|
||||
|
||||
protected bool m_Stopping;
|
||||
private bool m_StoppingFromUpdate;
|
||||
private float m_Epsilon = 1f - Mathf.Epsilon;
|
||||
private ScheduledEventBase m_AlignToGravityReset;
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
if (m_AlignToGravityReset != null) {
|
||||
Scheduler.Cancel(m_AlignToGravityReset);
|
||||
}
|
||||
m_CharacterLocomotion.AlignToGravity = true;
|
||||
m_Stopping = false;
|
||||
m_StoppingFromUpdate = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the character to be oriented with the specified normal.
|
||||
/// </summary>
|
||||
/// <param name="targetNormal">The direction that the character should be oriented towards on the vertical axis.</param>
|
||||
protected void Rotate(Vector3 targetNormal)
|
||||
{
|
||||
var deltaRotation = Quaternion.Euler(m_CharacterLocomotion.DeltaRotation);
|
||||
var rotation = m_Transform.rotation * deltaRotation;
|
||||
var proj = (rotation * Vector3.forward) - (Vector3.Dot((rotation * Vector3.forward), targetNormal)) * targetNormal;
|
||||
if (proj.sqrMagnitude > 0.0001f) {
|
||||
Quaternion targetRotation;
|
||||
if (m_CharacterLocomotion.Platform == null && !m_Stopping) {
|
||||
var alignToGroundSpeed = m_RotationSpeed * m_CharacterLocomotion.TimeScale * Time.timeScale * Time.deltaTime;
|
||||
targetRotation = Quaternion.Slerp(rotation, Quaternion.LookRotation(proj, targetNormal), alignToGroundSpeed);
|
||||
} else {
|
||||
targetRotation = Quaternion.LookRotation(proj, targetNormal);
|
||||
}
|
||||
deltaRotation = deltaRotation * (Quaternion.Inverse(rotation) * targetRotation);
|
||||
m_CharacterLocomotion.DeltaRotation = deltaRotation.eulerAngles;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the ability if it needs to be stopped.
|
||||
/// </summary>
|
||||
public override void LateUpdate()
|
||||
{
|
||||
base.LateUpdate();
|
||||
|
||||
// The ability should be stopped within LateUpdate so the character has a chance to be rotated.
|
||||
if (m_Stopping) {
|
||||
m_StoppingFromUpdate = true;
|
||||
StopAbility();
|
||||
m_StoppingFromUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability is trying to stop. Ensure the character ends at the correct orientation.
|
||||
/// </summary>
|
||||
public override void WillTryStopAbility()
|
||||
{
|
||||
base.WillTryStopAbility();
|
||||
|
||||
if (m_StopGravityDirection.sqrMagnitude > 0) {
|
||||
m_CharacterLocomotion.GravityDirection = m_StopGravityDirection.normalized;
|
||||
}
|
||||
m_Stopping = true;
|
||||
m_CharacterLocomotion.SmoothGravityYawDelta = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
// Don't stop until the character is oriented in the correct direction.
|
||||
if (m_StoppingFromUpdate && (m_StopGravityDirection.sqrMagnitude == 0 || Vector3.Dot(m_Transform.rotation * Vector3.up, -m_StopGravityDirection) >= m_Epsilon)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the gravity direction and align to gravity to their stopping values.
|
||||
/// </summary>
|
||||
protected void ResetAlignToGravity()
|
||||
{
|
||||
if (m_StopGravityDirection.sqrMagnitude > 0) {
|
||||
m_CharacterLocomotion.GravityDirection = m_StopGravityDirection.normalized;
|
||||
}
|
||||
// Wait a frame to allow the camera to reset its rotation. This is useful if the ability is stopped in a single frame.
|
||||
m_AlignToGravityReset = Scheduler.Schedule(Time.deltaTime * 2, DoAlignToGravityReset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the AlignToGravity parameter.
|
||||
/// </summary>
|
||||
private void DoAlignToGravityReset()
|
||||
{
|
||||
m_CharacterLocomotion.AlignToGravity = false;
|
||||
m_AlignToGravityReset = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
m_CharacterLocomotion.SmoothGravityYawDelta = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/AlignToGravity.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/AlignToGravity.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 938ea520feb484841a890899ec60c41b
|
||||
timeCreated: 1543956788
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
148
Assets/Scripts/Character/Abilities/AlignToGravityZone.cs
Normal file
148
Assets/Scripts/Character/Abilities/AlignToGravityZone.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Objects.CharacterAssist;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The AlignToGravityZone ability will orient the character to the direction of the gravity zones.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.Manual)]
|
||||
public class AlignToGravityZone : AlignToGravity
|
||||
{
|
||||
private GravityZone[] m_GravityZones;
|
||||
private int m_GravityZoneCount;
|
||||
|
||||
/// <summary>
|
||||
/// Registers a GravityZone with the ability.
|
||||
/// </summary>
|
||||
/// <param name="gravityZone">The GravityZone that should be registered.</param>
|
||||
public void RegisterGravityZone(GravityZone gravityZone)
|
||||
{
|
||||
if (m_GravityZones == null) {
|
||||
m_GravityZones = new GravityZone[1];
|
||||
} else if (m_GravityZones.Length == m_GravityZoneCount) {
|
||||
System.Array.Resize(ref m_GravityZones, m_GravityZoneCount + 1);
|
||||
}
|
||||
|
||||
m_GravityZones[m_GravityZoneCount] = gravityZone;
|
||||
m_GravityZoneCount++;
|
||||
|
||||
if (!IsActive) {
|
||||
StartAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a GravityZone with the ability.
|
||||
/// </summary>
|
||||
/// <param name="gravityZone">The GravityZone that should be unregistered.</param>
|
||||
public void UnregisterGravityZone(GravityZone gravityZone)
|
||||
{
|
||||
for (int i = 0; i < m_GravityZoneCount; ++i) {
|
||||
if (m_GravityZones[i] != gravityZone) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Shift all of the array elements down one.
|
||||
for (int j = i; j < m_GravityZoneCount - 1; ++j) {
|
||||
m_GravityZones[j] = m_GravityZones[j + 1];
|
||||
}
|
||||
m_GravityZoneCount--;
|
||||
m_GravityZones[m_GravityZoneCount] = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_GravityZoneCount == 0) {
|
||||
StopAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the rotation forces.
|
||||
/// </summary>
|
||||
public override void UpdateRotation()
|
||||
{
|
||||
var targetNormal = m_Stopping ? (m_StopGravityDirection.sqrMagnitude > 0 ? -m_StopGravityDirection : -m_CharacterLocomotion.GravityDirection) : Vector3.zero;
|
||||
if (!m_Stopping) {
|
||||
var position = m_Transform.position;
|
||||
for (int i = 0; i < m_GravityZoneCount; ++i) {
|
||||
// If the character is on the ground then only one gravity zone can influence the character. This will prevent the character from orienting to a different direction
|
||||
// while on the ground.
|
||||
if (m_CharacterLocomotion.Grounded) {
|
||||
var normal = m_GravityZones[i].DetermineGravityDirection(position);
|
||||
if (normal.sqrMagnitude > targetNormal.sqrMagnitude) {
|
||||
targetNormal = normal;
|
||||
}
|
||||
} else {
|
||||
// The character is not on the ground - use the average of all of the directions.
|
||||
targetNormal += m_GravityZones[i].DetermineGravityDirection(position);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetNormal.sqrMagnitude == 0) {
|
||||
return;
|
||||
}
|
||||
targetNormal.Normalize();
|
||||
m_CharacterLocomotion.GravityDirection = -targetNormal;
|
||||
}
|
||||
|
||||
Rotate(targetNormal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability is trying to stop. Ensure the character ends at the correct orientation.
|
||||
/// </summary>
|
||||
public override void WillTryStopAbility()
|
||||
{
|
||||
// If the gravity zone count isn't 0 then the ability will not be able to stop.
|
||||
if (m_GravityZoneCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
base.WillTryStopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
if (!base.CanStopAbility()) {
|
||||
return false;
|
||||
}
|
||||
return m_GravityZoneCount == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be force stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be force stopped.</returns>
|
||||
public override bool CanForceStopAbility()
|
||||
{
|
||||
if (!base.CanForceStopAbility()) {
|
||||
return false;
|
||||
}
|
||||
return m_GravityZoneCount == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
if (!m_CharacterLocomotion.IsAbilityTypeActive<AlignToGround>()) {
|
||||
ResetAlignToGravity();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a54bcb9929be4b445a3aceb56670d9af
|
||||
timeCreated: 1543956788
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
108
Assets/Scripts/Character/Abilities/AlignToGround.cs
Normal file
108
Assets/Scripts/Character/Abilities/AlignToGround.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The AlignToGround ability will orient the character to the direction of the ground's normal.
|
||||
/// </summary>
|
||||
public class AlignToGround : AlignToGravity
|
||||
{
|
||||
[Tooltip("The distance from the ground that the character should align itself to.")]
|
||||
[SerializeField] protected float m_Distance = 4;
|
||||
[Tooltip("The depth offset when checking the ground normal.")]
|
||||
[SerializeField] protected float m_DepthOffset = 0.05f;
|
||||
[Tooltip("Should the direction from the align to ground depth offset be normalized? This is useful for generic characters whose length is long.")]
|
||||
[SerializeField] protected bool m_NormalizeDirection = false;
|
||||
|
||||
public float Distance { get { return m_Distance; } set { m_Distance = value; } }
|
||||
public float DepthOffset { get { return m_DepthOffset; } set { m_DepthOffset = value; } }
|
||||
public bool NormalizeDirection { get { return m_NormalizeDirection; } set { m_NormalizeDirection = value; } }
|
||||
|
||||
private RaycastHit[] m_CombinedRaycastHits;
|
||||
private Dictionary<RaycastHit, int> m_ColliderIndexMap;
|
||||
private UnityEngineUtility.RaycastHitComparer m_RaycastHitComparer = new UnityEngineUtility.RaycastHitComparer();
|
||||
|
||||
/// <summary>
|
||||
/// Update the rotation forces.
|
||||
/// </summary>
|
||||
public override void UpdateRotation()
|
||||
{
|
||||
var updateNormalRotation = m_Stopping;
|
||||
var targetNormal = m_Stopping ? (m_StopGravityDirection.sqrMagnitude > 0 ? -m_StopGravityDirection : -m_CharacterLocomotion.GravityDirection) : m_CharacterLocomotion.Up;
|
||||
if (!m_Stopping) {
|
||||
// If the depth offset isn't zero then use two raycasts to determine the ground normal. This will allow a long character (such as a horse) to correctly
|
||||
// adjust to a slope.
|
||||
if (m_DepthOffset != 0) {
|
||||
var frontPoint = m_Transform.position;
|
||||
bool frontHit;
|
||||
RaycastHit raycastHit;
|
||||
if ((frontHit = Physics.Raycast(m_Transform.TransformPoint(0, m_CharacterLocomotion.Radius, m_DepthOffset * Mathf.Sign(m_CharacterLocomotion.InputVector.y)),
|
||||
-m_CharacterLocomotion.Up, out raycastHit, m_Distance + m_CharacterLocomotion.Radius, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore))) {
|
||||
frontPoint = raycastHit.point;
|
||||
targetNormal = raycastHit.normal;
|
||||
}
|
||||
|
||||
if (Physics.Raycast(m_Transform.TransformPoint(0, m_CharacterLocomotion.Radius, m_DepthOffset * -Mathf.Sign(m_CharacterLocomotion.InputVector.y)),
|
||||
-m_CharacterLocomotion.Up, out raycastHit, m_Distance + m_CharacterLocomotion.Radius, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore)) {
|
||||
if (frontHit) {
|
||||
if (m_NormalizeDirection) {
|
||||
var backPoint = raycastHit.point;
|
||||
var direction = (frontPoint - backPoint).normalized;
|
||||
targetNormal = Vector3.Cross(direction, Vector3.Cross(m_CharacterLocomotion.Up, direction)).normalized;
|
||||
} else {
|
||||
targetNormal = (targetNormal + raycastHit.normal).normalized;
|
||||
}
|
||||
} else {
|
||||
targetNormal = raycastHit.normal;
|
||||
}
|
||||
}
|
||||
|
||||
m_CharacterLocomotion.GravityDirection = -targetNormal;
|
||||
updateNormalRotation = true;
|
||||
} else {
|
||||
var hitCount = m_CharacterLocomotion.Cast(-m_CharacterLocomotion.Up * m_Distance,
|
||||
m_CharacterLocomotion.PlatformMovement + m_CharacterLocomotion.Up * m_CharacterLocomotion.ColliderSpacing, ref m_CombinedRaycastHits, ref m_ColliderIndexMap);
|
||||
|
||||
// The character hit the ground if any hit points are below the collider.
|
||||
for (int i = 0; i < hitCount; ++i) {
|
||||
var closestRaycastHit = QuickSelect.SmallestK(m_CombinedRaycastHits, hitCount, i, m_RaycastHitComparer);
|
||||
|
||||
// The hit point has to be under the collider for the character to align to it.
|
||||
var activeCollider = m_CharacterLocomotion.ColliderCount > 1 ? m_CharacterLocomotion.Colliders[m_ColliderIndexMap[closestRaycastHit]] : m_CharacterLocomotion.Colliders[0];
|
||||
if (!MathUtility.IsUnderCollider(m_Transform, activeCollider, closestRaycastHit.point)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
targetNormal = m_CharacterLocomotion.GravityDirection = -closestRaycastHit.normal;
|
||||
updateNormalRotation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The rotation is affected by aligning to the ground or having a different up rotation from gravity.
|
||||
if (updateNormalRotation) {
|
||||
Rotate(targetNormal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
ResetAlignToGravity();
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/AlignToGround.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/AlignToGround.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d98050f077a770459dba042f8c6e6a2
|
||||
timeCreated: 1543956788
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
150
Assets/Scripts/Character/Abilities/DamageVisualization.cs
Normal file
150
Assets/Scripts/Character/Abilities/DamageVisualization.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Plays an animation when the character takes damage.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.Manual)]
|
||||
[DefaultStopType(AbilityStopType.Manual)]
|
||||
[DefaultAbilityIndex(10)]
|
||||
public class DamageVisualization : Ability
|
||||
{
|
||||
[Tooltip("The minimum amount of damage required for the ability to start.")]
|
||||
[SerializeField] protected float m_MinDamageAmount;
|
||||
[Tooltip("Specifies if the ability should wait for the OnAnimatorDamageVisualizationComplete animation event or wait for the specified duration before interacting with the item.")]
|
||||
[SerializeField] protected AnimationEventTrigger m_DamageVisualizationCompleteEvent = new AnimationEventTrigger(false, 0.2f);
|
||||
|
||||
private float MinDamageAmount { get { return m_MinDamageAmount; } set { m_MinDamageAmount = value; } }
|
||||
private AnimationEventTrigger DamageVisualizationCompleteEvent { get { return m_DamageVisualizationCompleteEvent; } set { m_DamageVisualizationCompleteEvent = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// The type of animation that the ability should play.
|
||||
/// </summary>
|
||||
private enum TakeDamageIndex {
|
||||
FrontLeft, // Play an animation based upon a damage position on the front left.
|
||||
FrontRight, // Play an animation based upon a damage position on the front right.
|
||||
BackLeft, // Play an animation based upon a damage position on the back left.
|
||||
BackRight // Play an animation based upon a damage position on the back right.
|
||||
}
|
||||
|
||||
private int m_TakeDamageIndex;
|
||||
private ScheduledEventBase m_CompleteEvent;
|
||||
|
||||
public override int AbilityIntData { get { return m_TakeDamageIndex; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
EventHandler.RegisterEvent<float, Vector3, Vector3, GameObject, Collider>(m_GameObject, "OnHealthDamage", OnDamage);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorDamageVisualizationComplete", OnDamageVisualizationComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has taken damage.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of damage taken.</param>
|
||||
/// <param name="position">The position of the damage.</param>
|
||||
/// <param name="force">The amount of force applied to the object while taking the damage.</param>
|
||||
/// <param name="attacker">The GameObject that did the damage.</param>
|
||||
/// <param name="hitCollider">The Collider that was hit.</param>
|
||||
private void OnDamage(float amount, Vector3 position, Vector3 force, GameObject attacker, Collider hitCollider)
|
||||
{
|
||||
// The ability shouldn't start if the damage amount doesn't meet the minimum amount required.
|
||||
if (amount < m_MinDamageAmount) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The ability shouldn't start if the damage is internal (such as a fall damage).
|
||||
if (attacker == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_TakeDamageIndex = GetDamageTypeIndex(amount, position, force, attacker);
|
||||
if (m_TakeDamageIndex != -1) {
|
||||
StartAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value that the AbilityIntData parameter should be set to.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of damage taken.</param>
|
||||
/// <param name="position">The position of the damage.</param>
|
||||
/// <param name="force">The amount of force applied to the character.</param>
|
||||
/// <param name="attacker">The GameObject that damaged the character.</param>
|
||||
/// <returns>The value that the AbilityIntData parameter should be set to. A value of -1 will prevent the ability from starting.</returns>
|
||||
protected virtual int GetDamageTypeIndex(float amount, Vector3 position, Vector3 force, GameObject attacker)
|
||||
{
|
||||
var direction = m_Transform.InverseTransformPoint(position);
|
||||
if (direction.z > 0) {
|
||||
if (direction.x > 0) {
|
||||
return (int)TakeDamageIndex.FrontRight;
|
||||
}
|
||||
return (int)TakeDamageIndex.FrontLeft;
|
||||
} else if (direction.z < 0) {
|
||||
if (direction.x > 0) {
|
||||
return (int)TakeDamageIndex.BackRight;
|
||||
}
|
||||
return (int)TakeDamageIndex.BackLeft;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
if (!m_DamageVisualizationCompleteEvent.WaitForAnimationEvent) {
|
||||
m_CompleteEvent = Scheduler.Schedule(m_DamageVisualizationCompleteEvent.Duration, OnDamageVisualizationComplete);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animation event callback when the damage visualization animation has completed.
|
||||
/// </summary>
|
||||
private void OnDamageVisualizationComplete()
|
||||
{
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
Scheduler.Cancel(m_CompleteEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<float, Vector3, Vector3, GameObject, Collider>(m_GameObject, "OnHealthDamage", OnDamage);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorDamageVisualizationComplete", OnDamageVisualizationComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d7b2324759214a4fb66abb5f08e40f6
|
||||
timeCreated: 1487015903
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
136
Assets/Scripts/Character/Abilities/DetectGroundAbilityBase.cs
Normal file
136
Assets/Scripts/Character/Abilities/DetectGroundAbilityBase.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Objects;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract class which determines if the ground object is a valid object.
|
||||
/// </summary>
|
||||
public abstract class DetectGroundAbilityBase : Ability
|
||||
{
|
||||
[Tooltip("The unique ID value of the Object Identifier component. A value of -1 indicates that this ID should not be used.")]
|
||||
[SerializeField] protected int m_ObjectID = -1;
|
||||
[Tooltip("The layer mask of the ground object.")]
|
||||
[SerializeField] protected LayerMask m_LayerMask = -1;
|
||||
[Tooltip("The character is no longer over the ground if the dot product between the character's up direction and the ground normal is less than the sensitivity.")]
|
||||
[Range(0, 1)] [SerializeField] protected float m_GroundNormalSensitivity = 0.5f;
|
||||
[Tooltip("The maximum angle that the character can be relative to the forward direction of the object.")]
|
||||
[Range(0, 180)] [SerializeField] protected float m_AngleThreshold = 180;
|
||||
|
||||
public int ObjectID { get { return m_ObjectID; } set { m_ObjectID = value; } }
|
||||
public LayerMask LayerMask { get { return m_LayerMask; } set { m_LayerMask = value; } }
|
||||
public float NormalSensitivity { get { return m_GroundNormalSensitivity; } set { m_GroundNormalSensitivity = value; } }
|
||||
public float AngleThreshold { get { return m_AngleThreshold; } set { m_AngleThreshold = value; } }
|
||||
|
||||
protected Collider m_GroundCollider;
|
||||
protected Transform m_GroundTransform;
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be started?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsOverValidObject();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the character over a valid ground object?
|
||||
/// </summary>
|
||||
/// <returns>True if the character is over a valid ground object.</returns>
|
||||
protected bool IsOverValidObject()
|
||||
{
|
||||
if (!m_CharacterLocomotion.Grounded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The ability may require the character to be directly on top of the ground.
|
||||
if (Vector3.Dot(m_CharacterLocomotion.Up, m_CharacterLocomotion.GroundRaycastHit.normal) < m_GroundNormalSensitivity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var angle = Quaternion.Angle(Quaternion.LookRotation(m_Transform.forward, m_CharacterLocomotion.Up),
|
||||
Quaternion.LookRotation(m_CharacterLocomotion.GroundRaycastHit.transform.forward, m_CharacterLocomotion.Up));
|
||||
var objectFaces = m_CharacterLocomotion.GroundRaycastHit.transform.gameObject.GetCachedParentComponent<ObjectForwardFaces>();
|
||||
// If an object has multiple faces then the ability can start from multiple directions.
|
||||
if (objectFaces != null) {
|
||||
var roundedAngle = 180 / objectFaces.ForwardFaceCount;
|
||||
angle = Mathf.Abs(MathUtility.ClampInnerAngle(angle - (roundedAngle * Mathf.RoundToInt(angle / roundedAngle))));
|
||||
}
|
||||
if (angle > m_AngleThreshold) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine if the ground object is a valid ground object. This check only needs to be run when the grounded object changes.
|
||||
if (m_GroundCollider != m_CharacterLocomotion.GroundRaycastHit.collider) {
|
||||
// The ground object can be detected by using the ObjectIdentifier component.
|
||||
if (m_ObjectID != -1) {
|
||||
var objIdentifiers = m_CharacterLocomotion.GroundRaycastHit.collider.gameObject.GetCachedComponents<ObjectIdentifier>();
|
||||
if (objIdentifiers == null || objIdentifiers.Length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var idMatch = false;
|
||||
for (int i = 0; i < objIdentifiers.Length; ++i) {
|
||||
if (objIdentifiers[i].ID == m_ObjectID) {
|
||||
idMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!idMatch) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The ground object can be detected by using the layer mask.
|
||||
if (!MathUtility.InLayerMask(m_CharacterLocomotion.GroundRaycastHit.collider.gameObject.layer, m_LayerMask)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The ground object is valid.
|
||||
m_GroundCollider = m_CharacterLocomotion.GroundRaycastHit.collider;
|
||||
m_GroundTransform = m_GroundCollider.transform;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the ability if the character is no longer over a valid object.
|
||||
/// </summary>
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!IsOverValidObject()) {
|
||||
StopAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
m_GroundCollider = null;
|
||||
m_GroundTransform = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27c11aee384e7ed4eb24d2d8cc987948
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
360
Assets/Scripts/Character/Abilities/DetectObjectAbilityBase.cs
Normal file
360
Assets/Scripts/Character/Abilities/DetectObjectAbilityBase.cs
Normal file
@@ -0,0 +1,360 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// An abstract class for any ability that needs another object to start (such as picking an object up, vaulting, climbing, interacting, etc).
|
||||
/// </summary>
|
||||
public abstract class DetectObjectAbilityBase : Ability
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies how to detect the object.
|
||||
/// </summary>
|
||||
public enum ObjectDetectionMode
|
||||
{
|
||||
Trigger = 1, // Use a trigger to detect if the character is near an object.
|
||||
Charactercast = 2, // Use the character colliders to do a cast in order to detect if the character is near an object.
|
||||
Raycast = 4, // Use a raycast to detect if the character is near an object.
|
||||
Spherecast = 8, // Use a spherecast to detect if the character is near an object.
|
||||
Customcast = 16 // The ability will perform its own custom cast.
|
||||
}
|
||||
|
||||
[Tooltip("Mask which specifies how the ability should detect other objects.")]
|
||||
[HideInInspector] [SerializeField] protected ObjectDetectionMode m_ObjectDetection = ObjectDetectionMode.Charactercast;
|
||||
[Tooltip("The LayerMask of the object or trigger that should be detected.")]
|
||||
[HideInInspector] [SerializeField] protected LayerMask m_DetectLayers = ~(1 << LayerManager.IgnoreRaycast | 1 << LayerManager.UI | 1 << LayerManager.SubCharacter | 1 << LayerManager.Overlay | 1 << LayerManager.VisualEffect);
|
||||
[Tooltip("Should the detection method use the look source position? If false the character position will be used.")]
|
||||
[HideInInspector] [SerializeField] protected bool m_UseLookPosition;
|
||||
[Tooltip("Should the detection method use the look source direction? If false the character direction will be used.")]
|
||||
[HideInInspector] [SerializeField] protected bool m_UseLookDirection = true;
|
||||
[Tooltip("The maximum angle that the character can be relative to the forward direction of the object.")]
|
||||
[Range(0, 360)] [HideInInspector] [SerializeField] protected float m_AngleThreshold = 360;
|
||||
[Tooltip("The unique ID value of the Object Identifier component. A value of -1 indicates that this ID should not be used.")]
|
||||
[HideInInspector] [SerializeField] protected int m_ObjectID = -1;
|
||||
[Tooltip("The distance of the cast. Used if the Object Detection Mode uses anything other then a trigger detection mode.")]
|
||||
[HideInInspector] [SerializeField] protected float m_CastDistance = 1;
|
||||
[Tooltip("The number of frames that should elapse before another cast is performed. A value of 0 will allow the cast to occur every frame.")]
|
||||
[HideInInspector] [SerializeField] protected int m_CastFrameInterval = 0;
|
||||
[Tooltip("The offset to applied to the cast.")]
|
||||
[HideInInspector] [SerializeField] protected Vector3 m_CastOffset = new Vector3(0, 1, 0);
|
||||
[Tooltip("Specifies if the cast should interact with triggers.")]
|
||||
[HideInInspector] [SerializeField] protected QueryTriggerInteraction m_TriggerInteraction = QueryTriggerInteraction.Ignore;
|
||||
[Tooltip("The radius of the spherecast.")]
|
||||
[HideInInspector] [SerializeField] protected float m_SpherecastRadius = 0.5f;
|
||||
[Tooltip("The maximum number of valid triggers that the ability can detect.")]
|
||||
[HideInInspector] [SerializeField] protected int m_MaxTriggerObjectCount = 1;
|
||||
|
||||
public ObjectDetectionMode ObjectDetection { get { return m_ObjectDetection; }
|
||||
set {
|
||||
m_ObjectDetection = value;
|
||||
if ((m_ObjectDetection & ObjectDetectionMode.Trigger) != 0 && m_DetectedTriggerObjects == null) {
|
||||
m_DetectedTriggerObjects = new GameObject[m_MaxTriggerObjectCount];
|
||||
}
|
||||
}
|
||||
}
|
||||
public LayerMask DetectLayers { get { return m_DetectLayers; } set { m_DetectLayers = value; } }
|
||||
public float DetectAngleThreshold { get { return m_AngleThreshold; } set { m_AngleThreshold = value; } }
|
||||
public int ObjectID { get { return m_ObjectID; } set { m_ObjectID = value; } }
|
||||
public bool UseLookPosition { get { return m_UseLookPosition; } set { m_UseLookPosition = value; } }
|
||||
public bool UseLookDirection { get { return m_UseLookDirection; } set { m_UseLookDirection = value; } }
|
||||
public float CastDistance { get { return m_CastDistance; } set { m_CastDistance = value; } }
|
||||
public Vector3 CastOffset { get { return m_CastOffset; } set { m_CastOffset = value; } }
|
||||
public QueryTriggerInteraction TriggerInteraction { get { return m_TriggerInteraction; } set { m_TriggerInteraction = value; } }
|
||||
public float SpherecastRadius { get { return m_SpherecastRadius; } set { m_SpherecastRadius = value; } }
|
||||
|
||||
protected ILookSource m_LookSource;
|
||||
protected Transform m_LookSourceTransform;
|
||||
protected RaycastHit m_RaycastResult;
|
||||
protected GameObject[] m_DetectedTriggerObjects;
|
||||
protected int m_DetectedTriggerObjectsCount;
|
||||
protected GameObject m_DetectedObject;
|
||||
private int m_LastCastFrame;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
m_LastCastFrame = -m_CastFrameInterval;
|
||||
// The look source may have already been assigned if the ability was added to the character after the look source was assigned.
|
||||
m_LookSource = m_CharacterLocomotion.LookSource;
|
||||
if (m_LookSource != null) {
|
||||
m_LookSourceTransform = m_LookSource.GameObject.transform;
|
||||
}
|
||||
|
||||
if ((m_ObjectDetection & ObjectDetectionMode.Trigger) != 0) {
|
||||
m_DetectedTriggerObjects = new GameObject[m_MaxTriggerObjectCount];
|
||||
}
|
||||
EventHandler.RegisterEvent<ILookSource>(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new ILookSource object has been attached to the character.
|
||||
/// </summary>
|
||||
/// <param name="lookSource">The ILookSource object attached to the character.</param>
|
||||
private void OnAttachLookSource(ILookSource lookSource)
|
||||
{
|
||||
m_LookSource = lookSource;
|
||||
if (m_LookSource != null) {
|
||||
m_LookSourceTransform = m_LookSource.GameObject.transform;
|
||||
} else {
|
||||
m_LookSourceTransform = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The ability may not detect any objects through a trigger or cast.
|
||||
if (m_ObjectDetection == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The ability can start if using a trigger.
|
||||
if ((m_ObjectDetection & ObjectDetectionMode.Trigger) != 0 && m_DetectedTriggerObjectsCount > 0) {
|
||||
for (int i = 0; i < m_DetectedTriggerObjectsCount; ++i) {
|
||||
if (ValidateObject(m_DetectedTriggerObjects[i], null)) {
|
||||
m_DetectedObject = m_DetectedTriggerObjects[i];
|
||||
return true;
|
||||
} else if (!m_DetectedTriggerObjects[i].activeInHierarchy) { // The OnTriggerExit callback doesn't occur when the object is deactivated.
|
||||
if (TriggerExit(m_DetectedTriggerObjects[i])) {
|
||||
i--; // Subtract one so the newly replaced object will be evaluated.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No more work is necessary if no casts are necessary.
|
||||
if (m_ObjectDetection == ObjectDetectionMode.Trigger || (m_UseLookDirection && m_LookSource == null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't perform the cast if the number of casts are being culled.
|
||||
if (m_LastCastFrame + m_CastFrameInterval > Time.frameCount) {
|
||||
return m_DetectedObject != null;
|
||||
}
|
||||
m_LastCastFrame = Time.frameCount;
|
||||
|
||||
// The ability may have its own custom cast that should be performed.
|
||||
if (m_ObjectDetection == ObjectDetectionMode.Customcast) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use the colliders on the character to detect if the character is near the object.
|
||||
var castTransform = m_UseLookPosition ? m_LookSourceTransform : m_Transform;
|
||||
var castDirection = m_UseLookDirection ? m_LookSource.LookDirection(true) : m_Transform.forward;
|
||||
if ((m_ObjectDetection & ObjectDetectionMode.Charactercast) != 0) {
|
||||
if (m_CharacterLocomotion.SingleCast(castDirection * m_CastDistance, castTransform.TransformDirection(m_CastOffset), m_DetectLayers, ref m_RaycastResult)) {
|
||||
var hitObject = m_RaycastResult.collider.gameObject;
|
||||
if (ValidateObject(hitObject, m_RaycastResult)) {
|
||||
m_DetectedObject = hitObject;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a raycast to detect if the character is near the object.
|
||||
if ((m_ObjectDetection & ObjectDetectionMode.Raycast) != 0) {
|
||||
if (Physics.Raycast(castTransform.TransformPoint(m_CastOffset), castDirection, out m_RaycastResult, m_CastDistance, m_DetectLayers, m_TriggerInteraction)) {
|
||||
var hitObject = m_RaycastResult.collider.gameObject;
|
||||
if (ValidateObject(hitObject, m_RaycastResult)) {
|
||||
m_DetectedObject = hitObject;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a spherecast to detect if the character is near the object.
|
||||
if ((m_ObjectDetection & ObjectDetectionMode.Spherecast) != 0) {
|
||||
if (Physics.SphereCast(castTransform.TransformPoint(m_CastOffset) - castTransform.forward * m_SpherecastRadius, m_SpherecastRadius, castDirection, out m_RaycastResult,
|
||||
m_CastDistance, m_DetectLayers, m_TriggerInteraction)) {
|
||||
var hitObject = m_RaycastResult.collider.gameObject;
|
||||
if (ValidateObject(hitObject, m_RaycastResult)) {
|
||||
m_DetectedObject = hitObject;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The cast did not detect an object.
|
||||
m_DetectedObject = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has entered a trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The trigger collider that the character entered.</param>
|
||||
public override void OnTriggerEnter(Collider other)
|
||||
{
|
||||
// The object may not be detected with a trigger.
|
||||
if ((m_ObjectDetection & ObjectDetectionMode.Trigger) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The object has to use the correct mask.
|
||||
if (!MathUtility.InLayerMask(other.gameObject.layer, m_DetectLayers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the detected object isn't duplicated within the list.
|
||||
for (int i = 0; i < m_DetectedTriggerObjectsCount; ++i) {
|
||||
if (m_DetectedTriggerObjects[i] == other.gameObject) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ValidateObject(other.gameObject, null)) {
|
||||
if (m_DetectedTriggerObjects.Length == m_DetectedTriggerObjectsCount) {
|
||||
Debug.LogError($"Error: The maximum number of trigger objects need to be increased on the {GetType().Name} ability.");
|
||||
return;
|
||||
}
|
||||
m_DetectedTriggerObjects[m_DetectedTriggerObjectsCount] = other.gameObject;
|
||||
m_DetectedTriggerObjectsCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has exited a trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The trigger collider that the character exited.</param>
|
||||
public override void OnTriggerExit(Collider other)
|
||||
{
|
||||
// The object may not be detected with a trigger.
|
||||
if ((m_ObjectDetection & ObjectDetectionMode.Trigger) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
TriggerExit(other.gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has exited a trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The GameObject that the character exited.</param>
|
||||
/// <returns>Returns true if the entered object leaves the trigger.</returns>
|
||||
protected virtual bool TriggerExit(GameObject other)
|
||||
{
|
||||
for (int i = 0; i < m_DetectedTriggerObjectsCount; ++i) {
|
||||
if (other == m_DetectedTriggerObjects[i]) {
|
||||
m_DetectedTriggerObjects[i] = null;
|
||||
// Ensure there is not a gap in the trigger object elements.
|
||||
for (int j = i; j < m_DetectedTriggerObjectsCount - 1; ++j) {
|
||||
m_DetectedTriggerObjects[j] = m_DetectedTriggerObjects[j + 1];
|
||||
}
|
||||
m_DetectedTriggerObjectsCount--;
|
||||
// The detected object should be assigned to the oldest trigger object. This value may be null.
|
||||
m_DetectedObject = m_DetectedTriggerObjects[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the object to ensure it is valid for the current ability.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object being validated.</param>
|
||||
/// <param name="raycastHit">The raycast hit of the detected object. Will be null for trigger detections.</param>
|
||||
/// <returns>True if the object is valid. The object may not be valid if it doesn't have an ability-specific component attached.</returns>
|
||||
protected virtual bool ValidateObject(GameObject obj, RaycastHit? raycastHit)
|
||||
{
|
||||
if (obj == null || !obj.activeInHierarchy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If an object id is specified then the object must have the Object Identifier component attached with the specified ID.
|
||||
if (m_ObjectID != -1) {
|
||||
var objectIdentifiers = obj.GetCachedParentComponents<Objects.ObjectIdentifier>();
|
||||
if (objectIdentifiers == null) {
|
||||
return false;
|
||||
}
|
||||
var hasID = false;
|
||||
for (int i = 0; i < objectIdentifiers.Length; ++i) {
|
||||
if (objectIdentifiers[i].ID == m_ObjectID) {
|
||||
hasID = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasID) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The object has to be within the specified angle.
|
||||
if (raycastHit.HasValue) {
|
||||
var castDirection = m_UseLookDirection ? m_LookSource.LookDirection(true) : m_Transform.forward;
|
||||
float angle;
|
||||
var objectFaces = obj.GetCachedParentComponent<Objects.ObjectForwardFaces>();
|
||||
if (objectFaces != null) {
|
||||
// If an object has multiple faces then the ability can start from multiple directions. It should not start from any angle so don't use the raycast normal.
|
||||
var roundedAngle = 360 / objectFaces.ForwardFaceCount;
|
||||
angle = Quaternion.Angle(Quaternion.LookRotation(castDirection, m_CharacterLocomotion.Up), Quaternion.LookRotation(-obj.transform.forward, m_CharacterLocomotion.Up));
|
||||
angle = Mathf.Abs(MathUtility.ClampInnerAngle(angle - (roundedAngle * Mathf.RoundToInt(angle / roundedAngle))));
|
||||
} else {
|
||||
// The object doesn't have the ObjectFaces component. Use the actual angle value.
|
||||
angle = Quaternion.Angle(Quaternion.LookRotation(castDirection, m_CharacterLocomotion.Up), Quaternion.LookRotation(-raycastHit.Value.normal, m_CharacterLocomotion.Up));
|
||||
}
|
||||
|
||||
if (angle <= m_AngleThreshold) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
AbilityStopped(force, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
/// <param name="triggerExit">Should any triggers be exited before the ability is stopped?</param>
|
||||
protected void AbilityStopped(bool force, bool triggerExit)
|
||||
{
|
||||
// Ensure the OnTriggerExit is triggered when the ability stops.
|
||||
if (triggerExit && m_DetectedObject != null && (m_ObjectDetection & ObjectDetectionMode.Trigger) != 0) {
|
||||
TriggerExit(m_DetectedObject);
|
||||
}
|
||||
|
||||
base.AbilityStopped(force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<ILookSource>(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebf5a921c51093546bd19e09f83099da
|
||||
timeCreated: 1487015903
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
118
Assets/Scripts/Character/Abilities/Die.cs
Normal file
118
Assets/Scripts/Character/Abilities/Die.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Plays a death animation when the character dies.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.Manual)]
|
||||
[DefaultState("Death")]
|
||||
[DefaultAbilityIndex(4)]
|
||||
public class Die : Ability
|
||||
{
|
||||
[Tooltip("The amount of force to add to the camera. This value will be multiplied by the death force magnitude.")]
|
||||
[SerializeField] protected Vector3 m_CameraRotationalForce = new Vector3(0, 0, 0.75f);
|
||||
|
||||
public Vector3 CameraRotationalForce { get { return m_CameraRotationalForce; } set { m_CameraRotationalForce = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// The type of animation that the ability should play.
|
||||
/// </summary>
|
||||
private enum DeathType {
|
||||
Forward, // Play a forward death animation.
|
||||
Backward // Play a backward death animation.
|
||||
}
|
||||
|
||||
private int m_DeathTypeIndex;
|
||||
private Vector3 m_Force;
|
||||
private Vector3 m_Position;
|
||||
|
||||
[NonSerialized] public Vector3 Force { get { return m_Force; } set { m_Force = value; } }
|
||||
[NonSerialized] public Vector3 Position { get { return m_Position; } set { m_Position = value; } }
|
||||
|
||||
public override int AbilityIntData { get { return m_DeathTypeIndex; } }
|
||||
public override bool CanStayActivatedOnDeath { get { return true; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
EventHandler.RegisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnRespawn", OnRespawn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has died. Start the ability.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the force.</param>
|
||||
/// <param name="force">The amount of force which killed the character.</param>
|
||||
/// <param name="attacker">The GameObject that killed the character.</param>
|
||||
private void OnDeath(Vector3 position, Vector3 force, GameObject attacker)
|
||||
{
|
||||
if (!Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_Force = force;
|
||||
m_Position = position;
|
||||
m_DeathTypeIndex = GetDeathTypeIndex(position, force, attacker);
|
||||
StartAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value that the AbilityIntData parameter should be set to.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the force.</param>
|
||||
/// <param name="force">The amount of force which killed the character.</param>
|
||||
/// <param name="attacker">The GameObject that killed the character.</param>
|
||||
/// <returns>The value that the AbilityIntData parameter should be set to.</returns>
|
||||
protected virtual int GetDeathTypeIndex(Vector3 position, Vector3 force, GameObject attacker)
|
||||
{
|
||||
return (int)(m_Transform.InverseTransformPoint(position).z > 0 ? DeathType.Forward : DeathType.Backward);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
m_CharacterLocomotion.ResetRotationPosition();
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnCameraRotationalForce", m_CameraRotationalForce * m_Force.magnitude);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has respawned. Stop the die ability.
|
||||
/// </summary>
|
||||
private void OnRespawn()
|
||||
{
|
||||
if (!Enabled) {
|
||||
return;
|
||||
}
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnRespawn", OnRespawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Die.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Die.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24e13ae414933454290c50666c282b6e
|
||||
timeCreated: 1509717777
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
446
Assets/Scripts/Character/Abilities/Drive.cs
Normal file
446
Assets/Scripts/Character/Abilities/Drive.cs
Normal file
@@ -0,0 +1,446 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
using Opsive.UltimateCharacterController.Networking;
|
||||
#endif
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.UltimateCharacterController.Objects.CharacterAssist;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Ability that uses the IDriveSource interface to drive a vehicle.
|
||||
/// </summary>
|
||||
[DefaultInputName("Action")]
|
||||
[DefaultState("Drive")]
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultStopType(AbilityStopType.ButtonToggle)]
|
||||
[DefaultAllowPositionalInput(false)]
|
||||
[DefaultAllowRotationalInput(false)]
|
||||
[DefaultUseRootMotionPosition(AbilityBoolOverride.True)]
|
||||
[DefaultUseRootMotionRotation(AbilityBoolOverride.True)]
|
||||
[DefaultUseGravity(AbilityBoolOverride.False)]
|
||||
[DefaultDetectHorizontalCollisions(AbilityBoolOverride.False)]
|
||||
[DefaultDetectVerticalCollisions(AbilityBoolOverride.False)]
|
||||
[DefaultAbilityIndex(14)]
|
||||
[DefaultEquippedSlots(0)]
|
||||
public class Drive : DetectObjectAbilityBase
|
||||
{
|
||||
[Tooltip("Should the character teleport for the enter and exit animations?")]
|
||||
[SerializeField] protected bool m_TeleportEnterExit;
|
||||
[Tooltip("Can the Drive ability aim?")]
|
||||
[SerializeField] protected bool m_CanAim;
|
||||
[Tooltip("The speed at which the character moves towards the seat location.")]
|
||||
[SerializeField] protected float m_MoveSpeed = 0.2f;
|
||||
[Tooltip("The speed at which the character rotates towards the seat location.")]
|
||||
[SerializeField] protected float m_RotationSpeed = 2f;
|
||||
|
||||
public bool TeleportEnterExit { get => m_TeleportEnterExit; set => m_TeleportEnterExit = value; }
|
||||
public bool CanAim { get => m_CanAim; set => m_CanAim = value; }
|
||||
public float MoveSpeed { get => m_MoveSpeed; set => m_MoveSpeed = value; }
|
||||
public float RotationSpeed { get => m_RotationSpeed; set => m_RotationSpeed = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the current status of the character.
|
||||
/// </summary>
|
||||
private enum DriveState
|
||||
{
|
||||
Enter, // The character is entering the vehicle.
|
||||
Drive, // The character is driving the vehicle.
|
||||
Exit, // The character is exiting the vehicle.
|
||||
ExitComplete // The character has exited the vehicle.
|
||||
}
|
||||
|
||||
private IDriveSource m_DriveSource;
|
||||
private Transform m_OriginalParent;
|
||||
private Collider[] m_VehicleColliders;
|
||||
|
||||
private DriveState m_DriveState;
|
||||
private Collider[] m_OverlapColliders;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
private INetworkInfo m_NetworkInfo;
|
||||
#endif
|
||||
private KinematicObjectManager.UpdateLocation m_StartUpdateLocation;
|
||||
private float m_Epsilon = 0.99999f;
|
||||
|
||||
public override int AbilityIntData { get { return m_DriveSource.AnimatorID + (int)m_DriveState; } }
|
||||
public override float AbilityFloatData { get { return m_CharacterLocomotion.RawInputVector.x; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_OverlapColliders = new Collider[1];
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
m_NetworkInfo = m_GameObject.GetCachedComponent<INetworkInfo>();
|
||||
#endif
|
||||
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorEnteredVehicle", OnEnteredVehicle);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorExitedVehicle", OnExitedVehicle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the object to ensure it is valid for the current ability.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object being validated.</param>
|
||||
/// <param name="raycastHit">The raycast hit of the detected object. Will be null for trigger detections.</param>
|
||||
/// <returns>True if the object is valid. The object may not be valid if it doesn't have an ability-specific component attached.</returns>
|
||||
protected override bool ValidateObject(GameObject obj, RaycastHit? raycastHit)
|
||||
{
|
||||
if (!base.ValidateObject(obj, raycastHit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_DriveSource = obj.GetCachedParentComponent<IDriveSource>();
|
||||
if (m_DriveSource == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return GetValidStartLocation(true) != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a valid start location.
|
||||
/// </summary>
|
||||
/// <param name="groundCheck">Should the ground be checked at the start location?</param>
|
||||
/// <returns>A valid start location (can be null).</returns>
|
||||
private MoveTowardsLocation GetValidStartLocation(bool groundCheck)
|
||||
{
|
||||
// At least one ability start location must be on the ground and not obstructed by any object.
|
||||
var startLocations = m_DriveSource.GameObject.GetComponentsInChildren<MoveTowardsLocation>();
|
||||
for (int i = 0; i < startLocations.Length; ++i) {
|
||||
// The object must be on the ground.
|
||||
if (groundCheck && !Physics.Raycast(startLocations[i].transform.TransformPoint(0, 0.1f, 0), -startLocations[i].transform.up, 0.2f,
|
||||
m_CharacterLayerManager.IgnoreInvisibleCharacterWaterLayers, QueryTriggerInteraction.Ignore)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the start location has a collider then it should be clear of any other objects.
|
||||
var collider = startLocations[i].gameObject.GetCachedComponent<Collider>();
|
||||
if (collider == null || !ColliderOverlap(collider)) {
|
||||
return startLocations[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the collider overlapping with any other objects?
|
||||
/// </summary>
|
||||
/// <param name="dismountCollider">The collider to determine if it is overlapping with another object.</param>
|
||||
/// <returns>True if the collider is overlapping.</returns>
|
||||
private bool ColliderOverlap(Collider collider)
|
||||
{
|
||||
if (collider == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int hitCount;
|
||||
if (collider is CapsuleCollider) {
|
||||
Vector3 startEndCap, endEndCap;
|
||||
var capsuleCollider = collider as CapsuleCollider;
|
||||
MathUtility.CapsuleColliderEndCaps(capsuleCollider, collider.transform.TransformPoint(capsuleCollider.center), collider.transform.rotation, out startEndCap, out endEndCap);
|
||||
hitCount = Physics.OverlapCapsuleNonAlloc(startEndCap, endEndCap, capsuleCollider.radius * MathUtility.ColliderRadiusMultiplier(capsuleCollider), m_OverlapColliders,
|
||||
m_CharacterLayerManager.IgnoreInvisibleCharacterWaterLayers, QueryTriggerInteraction.Ignore);
|
||||
} else if (collider is BoxCollider) {
|
||||
var boxCollider = collider as BoxCollider;
|
||||
hitCount = Physics.OverlapBoxNonAlloc(collider.transform.TransformPoint(boxCollider.center), Vector3.Scale(boxCollider.size, boxCollider.transform.lossyScale) / 2,
|
||||
m_OverlapColliders, collider.transform.rotation, m_CharacterLayerManager.IgnoreInvisibleCharacterWaterLayers, QueryTriggerInteraction.Ignore);
|
||||
} else { // SphereCollider.
|
||||
var sphereCollider = collider as SphereCollider;
|
||||
hitCount = Physics.OverlapSphereNonAlloc(collider.transform.TransformPoint(sphereCollider.center), sphereCollider.radius * MathUtility.ColliderRadiusMultiplier(sphereCollider),
|
||||
m_OverlapColliders, m_CharacterLayerManager.IgnoreInvisibleCharacterWaterLayers, QueryTriggerInteraction.Ignore);
|
||||
}
|
||||
|
||||
// Any overlap occurs anytime there is more one collider intersecting the colliders.
|
||||
return hitCount > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the possible MoveTowardsLocations that the character can move towards.
|
||||
/// </summary>
|
||||
/// <returns>The possible MoveTowardsLocations that the character can move towards.</returns>
|
||||
public override MoveTowardsLocation[] GetMoveTowardsLocations()
|
||||
{
|
||||
if (m_TeleportEnterExit) {
|
||||
return null;
|
||||
}
|
||||
return m_DriveSource.GameObject.GetComponentsInChildren<MoveTowardsLocation>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
m_OriginalParent = m_Transform.parent;
|
||||
m_VehicleColliders = m_DriveSource.GameObject.GetComponentsInChildren<Collider>();
|
||||
for (int i = 0; i < m_VehicleColliders.Length; ++i) {
|
||||
for (int j = 0; j < m_CharacterLocomotion.ColliderCount; ++j) {
|
||||
Physics.IgnoreCollision(m_VehicleColliders[i], m_CharacterLocomotion.Colliders[j], true);
|
||||
}
|
||||
}
|
||||
m_CharacterLocomotion.AddIgnoredColliders(m_VehicleColliders);
|
||||
m_CharacterLocomotion.AlignToGravity = true;
|
||||
m_StartUpdateLocation = m_CharacterLocomotion.UpdateLocation;
|
||||
// Used FixedUpdate so the root motion location is accurate when getting into the vehicle.
|
||||
m_CharacterLocomotion.UpdateLocation = KinematicObjectManager.UpdateLocation.FixedUpdate;
|
||||
|
||||
m_CharacterLocomotion.SetPlatform(m_DriveSource.Transform);
|
||||
m_Transform.parent = m_DriveSource.Transform;
|
||||
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
// The IDriveSource is responsible for notifying the remote players for the changes.
|
||||
if (m_NetworkInfo != null && !m_NetworkInfo.IsLocalPlayer()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
m_DriveState = DriveState.Enter;
|
||||
m_DriveSource.EnterVehicle(m_GameObject);
|
||||
|
||||
// Teleport the character if there are no enter/exit animations.
|
||||
if (m_TeleportEnterExit) {
|
||||
OnEnteredVehicle();
|
||||
m_CharacterLocomotion.InputVector = Vector2.zero;
|
||||
m_CharacterLocomotion.SetPositionAndRotation(m_DriveSource.DriverLocation.position, m_DriveSource.DriverLocation.rotation, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback when the character has entered the vehicle.
|
||||
/// </summary>
|
||||
private void OnEnteredVehicle()
|
||||
{
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
// The IDriveSource is responsible for notifying the remote players for the changes.
|
||||
if (m_NetworkInfo != null && !m_NetworkInfo.IsLocalPlayer()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_DriveSource.EnteredVehicle(m_GameObject);
|
||||
m_DriveState = DriveState.Drive;
|
||||
m_CharacterLocomotion.ForceRootMotionRotation = false;
|
||||
m_CharacterLocomotion.ForceRootMotionPosition = false;
|
||||
m_CharacterLocomotion.AllowRootMotionRotation = false;
|
||||
m_CharacterLocomotion.AllowRootMotionPosition = false;
|
||||
m_CharacterLocomotion.UpdateLocation = m_StartUpdateLocation;
|
||||
m_CharacterLocomotion.UpdateAbilityAnimatorParameters();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
return m_AllowEquippedSlotsMask == 0 && startingAbility is Items.ItemAbility || (!m_CanAim && startingAbility is Items.Aim) || startingAbility is HeightChange;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the current ability is attempting to start and another ability is active.
|
||||
/// Returns true or false depending on if the active ability should be stopped.
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability that is currently active.</param>
|
||||
/// <returns>True if the ability should be stopped.</returns>
|
||||
public override bool ShouldStopActiveAbility(Ability activeAbility)
|
||||
{
|
||||
return m_AllowEquippedSlotsMask == 0 && activeAbility is Items.ItemAbility || (!m_CanAim && activeAbility is Items.Aim);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the ability.
|
||||
/// </summary>
|
||||
public override void Update()
|
||||
{
|
||||
// Try to stop the ability after the character has exited. The ability won't be able to be stopped if the character isn't level with the gravity direction.
|
||||
if (m_DriveState == DriveState.ExitComplete && !m_TeleportEnterExit) {
|
||||
StopAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the ability's Animator parameters.
|
||||
/// </summary>
|
||||
public override void UpdateAnimator()
|
||||
{
|
||||
// The horizontal input value can be used to animate the steering wheel.
|
||||
SetAbilityFloatDataParameter(m_CharacterLocomotion.RawInputVector.x, Time.deltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the controller's rotation values.
|
||||
/// </summary>
|
||||
public override void UpdateRotation()
|
||||
{
|
||||
var deltaRotation = Quaternion.identity;
|
||||
var rotation = m_Transform.rotation;
|
||||
if (m_DriveState != DriveState.Drive) {
|
||||
if (m_TeleportEnterExit) {
|
||||
return;
|
||||
}
|
||||
var upNormal = m_DriveState == DriveState.Enter ? m_DriveSource.Transform.up : -m_CharacterLocomotion.GravityDirection;
|
||||
// When the character is entering the vehicle they should rotate to face the same up direction as the car. This allows the character to enter while on slopes.
|
||||
// Similarly, when the character exits they should rotate to the gravity direction.
|
||||
var proj = (rotation * Vector3.forward) - Vector3.Dot(rotation * Vector3.forward, upNormal) * upNormal;
|
||||
if (proj.sqrMagnitude > 0.0001f) {
|
||||
var speed = m_RotationSpeed * m_CharacterLocomotion.TimeScale * Time.timeScale * Time.deltaTime * (m_DriveState == DriveState.ExitComplete ? 100 : 1);
|
||||
var targetRotation = Quaternion.Slerp(rotation, Quaternion.LookRotation(proj, upNormal), speed);
|
||||
deltaRotation = deltaRotation * (Quaternion.Inverse(rotation) * targetRotation);
|
||||
}
|
||||
} else if (m_DriveSource.DriverLocation != null) {
|
||||
// The character should fully rotate towards the target rotation after they have entered.
|
||||
deltaRotation = MathUtility.InverseTransformQuaternion(m_Transform.rotation, m_DriveSource.DriverLocation.rotation);
|
||||
}
|
||||
m_CharacterLocomotion.DeltaRotation = deltaRotation.eulerAngles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the controller's position values.
|
||||
/// </summary>
|
||||
public override void UpdatePosition()
|
||||
{
|
||||
if (m_DriveState != DriveState.Drive || m_TeleportEnterExit || m_DriveSource.DriverLocation == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_CharacterLocomotion.MotorThrottle = Vector3.zero;
|
||||
var deltaPosition = Vector3.MoveTowards(m_Transform.position, m_DriveSource.DriverLocation.position, m_MoveSpeed) - m_Transform.position;
|
||||
m_CharacterLocomotion.AbilityMotor = deltaPosition / (m_CharacterLocomotion.TimeScaleSquared * Time.timeScale * TimeUtility.FramerateDeltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback when the ability tries to be stopped. Start the dismount.
|
||||
/// </summary>
|
||||
public override void WillTryStopAbility()
|
||||
{
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
// The IDriveSource is responsible for notifying the remote players for the changes.
|
||||
if (m_NetworkInfo != null && !m_NetworkInfo.IsLocalPlayer()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_DriveState != DriveState.Drive) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The ability can't stop if there are no valid exit locations.
|
||||
MoveTowardsLocation startLocation;
|
||||
if ((startLocation = GetValidStartLocation(false)) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_DriveSource.ExitVehicle(m_GameObject);
|
||||
m_DriveState = DriveState.Exit;
|
||||
m_CharacterLocomotion.AbilityMotor = Vector3.zero;
|
||||
m_CharacterLocomotion.ForceRootMotionRotation = true;
|
||||
m_CharacterLocomotion.ForceRootMotionPosition = true;
|
||||
m_CharacterLocomotion.AllowRootMotionRotation = true;
|
||||
m_CharacterLocomotion.AllowRootMotionPosition = true;
|
||||
m_CharacterLocomotion.UpdateAbilityAnimatorParameters();
|
||||
|
||||
// Teleport the character if there are no enter/exit animations.
|
||||
if (m_TeleportEnterExit) {
|
||||
OnExitedVehicle();
|
||||
var forward = Vector3.ProjectOnPlane(startLocation.transform.forward, -m_CharacterLocomotion.GravityDirection);
|
||||
m_CharacterLocomotion.SetPositionAndRotation(startLocation.transform.position, Quaternion.LookRotation(forward, -m_CharacterLocomotion.GravityDirection), true, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback when the character has exited the vehicle.
|
||||
/// </summary>
|
||||
private void OnExitedVehicle()
|
||||
{
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
// The IDriveSource is responsible for notifying the remote players for the changes.
|
||||
if (m_NetworkInfo != null && !m_NetworkInfo.IsLocalPlayer()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_DriveSource.ExitedVehicle(m_GameObject);
|
||||
m_DriveState = DriveState.ExitComplete;
|
||||
m_CharacterLocomotion.UpdateAbilityAnimatorParameters();
|
||||
m_Transform.parent = m_OriginalParent;
|
||||
m_CharacterLocomotion.SetPlatform(null);
|
||||
m_CharacterLocomotion.AlignToGravity = false;
|
||||
m_CharacterLocomotion.ForceRootMotionRotation = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
// The character has to be exited in order to stop.
|
||||
return m_DriveState == DriveState.ExitComplete &&
|
||||
(m_TeleportEnterExit || Vector3.Dot(m_Transform.rotation * Vector3.up, -m_CharacterLocomotion.GravityDirection) >= m_Epsilon);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
// If the drive state isn't exit complete then the ability was force stopped.
|
||||
if (m_DriveState != DriveState.ExitComplete) {
|
||||
m_DriveSource.ExitVehicle(m_GameObject);
|
||||
m_CharacterLocomotion.AbilityMotor = Vector3.zero;
|
||||
m_CharacterLocomotion.UpdateLocation = m_StartUpdateLocation;
|
||||
OnExitedVehicle();
|
||||
}
|
||||
|
||||
m_CharacterLocomotion.RemoveIgnoredColliders(m_VehicleColliders);
|
||||
for (int i = 0; i < m_VehicleColliders.Length; ++i) {
|
||||
for (int j = 0; j < m_CharacterLocomotion.ColliderCount; ++j) {
|
||||
Physics.IgnoreCollision(m_VehicleColliders[i], m_CharacterLocomotion.Colliders[j], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has been destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorEnteredVehicle", OnEnteredVehicle);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorExitedVehicle", OnExitedVehicle);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Assets/Scripts/Character/Abilities/Drive.cs.meta
Normal file
13
Assets/Scripts/Character/Abilities/Drive.cs.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16ef31f89c2877742bea9da24f017c52
|
||||
timeCreated: 1571233418
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
176
Assets/Scripts/Character/Abilities/Fall.cs
Normal file
176
Assets/Scripts/Character/Abilities/Fall.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.Shared.Utility;
|
||||
using Opsive.UltimateCharacterController.SurfaceSystem;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Fall ability allows the character to play a falling animation when the character has a negative y velocity.
|
||||
/// </summary>
|
||||
[DefaultStopType(AbilityStopType.Automatic)]
|
||||
[DefaultAbilityIndex(2)]
|
||||
[DefaultUseRootMotionPosition(AbilityBoolOverride.False)]
|
||||
[DefaultUseRootMotionRotation(AbilityBoolOverride.False)]
|
||||
public class Fall : Ability
|
||||
{
|
||||
[Tooltip("The minimum height between the ground and character that the ability can start. Set to 0 to start at any height.")]
|
||||
[SerializeField] protected float m_MinFallHeight = 0.2f;
|
||||
[Tooltip("A reference to the Surface Impact triggered when the character hits the ground.")]
|
||||
[SerializeField] protected SurfaceImpact m_LandSurfaceImpact;
|
||||
[Tooltip("The minimum velocity required for the Surface Impact to play.")]
|
||||
[SerializeField] protected float m_MinSurfaceImpactVelocity = -4f;
|
||||
[Tooltip("Specifies if the ability should wait for the OnAnimatorFallComplete animation event or wait for the specified duration before ending the fall.")]
|
||||
[SerializeField] protected AnimationEventTrigger m_LandEvent = new AnimationEventTrigger(true, 0f);
|
||||
|
||||
public float MinFallHeight { get { return m_MinFallHeight; } set { m_MinFallHeight = value; } }
|
||||
public SurfaceImpact LandSurfaceImpact { get { return m_LandSurfaceImpact; } set { m_LandSurfaceImpact = value; } }
|
||||
public float MinSurfaceImpactVelocity { get { return m_MinSurfaceImpactVelocity; } set { m_MinSurfaceImpactVelocity = value; } }
|
||||
public AnimationEventTrigger LandEvent { get { return m_LandEvent; } set { m_LandEvent = value; } }
|
||||
|
||||
public override int AbilityIntData { get { return m_StateIndex; } }
|
||||
public override float AbilityFloatData { get { return m_CharacterLocomotion.LocalLocomotionVelocity.y; } }
|
||||
|
||||
private int m_StateIndex;
|
||||
private bool m_Landed;
|
||||
|
||||
[Snapshot] protected bool Landed { get { return m_Landed; } set { m_Landed = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be started?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fall can't be started if the character is on the ground.
|
||||
if (m_CharacterLocomotion.Grounded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The ground distance must be greater then the minimum fall height if a value is set.
|
||||
RaycastHit hit;
|
||||
if (m_MinFallHeight != 0 && Physics.Raycast(m_Transform.position, -m_CharacterLocomotion.Up, out hit, m_MinFallHeight, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
m_StateIndex = 0;
|
||||
m_Landed = false;
|
||||
|
||||
EventHandler.RegisterEvent<bool>(m_GameObject, "OnCharacterGrounded", OnGrounded);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorFallComplete", Land);
|
||||
EventHandler.RegisterEvent<bool>(m_GameObject, "OnCharacterImmediateTransformChange", OnImmediateTransformChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
return startingAbility is HeightChange;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has changed grounded states.
|
||||
/// </summary>
|
||||
/// <param name="grounded">Is the character on the ground?</param>
|
||||
private void OnGrounded(bool grounded)
|
||||
{
|
||||
if (grounded) {
|
||||
// Allow the SurfaceManager to play an effect when the character hits the ground.
|
||||
if (m_LandSurfaceImpact != null && m_CharacterLocomotion.LocalVelocity.y < m_MinSurfaceImpactVelocity) {
|
||||
SurfaceManager.SpawnEffect(m_CharacterLocomotion.GroundRaycastHit, m_LandSurfaceImpact, m_CharacterLocomotion.GravityDirection, m_CharacterLocomotion.TimeScale, m_GameObject, m_Transform.forward, false);
|
||||
}
|
||||
|
||||
// Move to the fall end state when the character lands.
|
||||
m_StateIndex = 1;
|
||||
if (!m_LandEvent.WaitForAnimationEvent) {
|
||||
Scheduler.ScheduleFixed(m_LandEvent.Duration, Land);
|
||||
}
|
||||
} else {
|
||||
m_StateIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the Animator for the ability.
|
||||
/// </summary>
|
||||
public override void UpdateAnimator()
|
||||
{
|
||||
SetAbilityIntDataParameter(m_StateIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has landed.
|
||||
/// </summary>
|
||||
private void Land()
|
||||
{
|
||||
m_Landed = true;
|
||||
// The controller may no longer be grounded during the time that it takes for the land animation to send the OnAnimatorFallComplete event.
|
||||
if (m_CharacterLocomotion.Grounded) {
|
||||
StopAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
return m_Landed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character's position or rotation has been teleported.
|
||||
/// </summary>
|
||||
/// <param name="snapAnimator">Should the animator be snapped?</param>
|
||||
private void OnImmediateTransformChange(bool snapAnimator)
|
||||
{
|
||||
if (!m_CharacterLocomotion.Grounded && !snapAnimator) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The character is on the ground but fall is still active. Stop the fall.
|
||||
m_Landed = true;
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
EventHandler.UnregisterEvent<bool>(m_GameObject, "OnCharacterGrounded", OnGrounded);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorFallComplete", Land);
|
||||
EventHandler.UnregisterEvent<bool>(m_GameObject, "OnCharacterImmediateTransformChange", OnImmediateTransformChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Fall.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Fall.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85929e010426e0840a6b7c567e3d36c7
|
||||
timeCreated: 1487015903
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
69
Assets/Scripts/Character/Abilities/Generic.cs
Normal file
69
Assets/Scripts/Character/Abilities/Generic.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Generic ability allows for new animations without having to explicitly code a new ability. The ability will end after a specified duration or
|
||||
/// the OnAnimatorGenericAbilityComplete event is sent.
|
||||
/// </summary>
|
||||
[AllowDuplicateTypes]
|
||||
[DefaultAbilityIndex(10000)]
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultInputName("Action")]
|
||||
public class Generic : Ability
|
||||
{
|
||||
[Tooltip("Specifies if the ability should stop when the OnAnimatorGenericAbilityComplete event is received or wait the specified amount of time before ending the ability.")]
|
||||
[SerializeField] protected AnimationEventTrigger m_StopEvent = new AnimationEventTrigger(false, 0.5f);
|
||||
|
||||
public AnimationEventTrigger StopEvent { get { return m_StopEvent; } set { m_StopEvent = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorGenericAbilityComplete", OnComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
if (!m_StopEvent.WaitForAnimationEvent) {
|
||||
Scheduler.ScheduleFixed(m_StopEvent.Duration, OnComplete);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animation is done playing - stop the ability.
|
||||
/// </summary>
|
||||
private void OnComplete()
|
||||
{
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorGenericAbilityComplete", OnComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Generic.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Generic.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32da0da2b18697f45b39ec1ec4541e9e
|
||||
timeCreated: 1513724185
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
205
Assets/Scripts/Character/Abilities/HeightChange.cs
Normal file
205
Assets/Scripts/Character/Abilities/HeightChange.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The HeightChange ability allows the character pose to toggle between height changes.
|
||||
/// </summary>
|
||||
[AllowDuplicateTypes]
|
||||
[DefaultInputName("Crouch")]
|
||||
[DefaultState("Crouch")]
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultStopType(AbilityStopType.ButtonToggle)]
|
||||
[DefaultAbilityIndex(3)]
|
||||
[DefaultAbilityIntData(1)]
|
||||
public class HeightChange : Ability
|
||||
{
|
||||
[Tooltip("Is the ability a concurrent ability?")]
|
||||
[SerializeField] protected bool m_ConcurrentAbility = true;
|
||||
[Tooltip("Specifies the value to set the Height Animator parameter value to.")]
|
||||
[SerializeField] protected int m_Height = 1;
|
||||
[Tooltip("The amount to adjust the height of the CapsuleCollider by when active. This is only used if the character does not have an animator.")]
|
||||
[SerializeField] protected float m_CapsuleColliderHeightAdjustment = -0.4f;
|
||||
[Tooltip("Can the SpeedChange ability run while the HeightChange ability is active?")]
|
||||
[SerializeField] protected bool m_AllowSpeedChange;
|
||||
|
||||
public bool ConcurrentAbility { get { return m_ConcurrentAbility; } set { m_ConcurrentAbility = value; } }
|
||||
public float CapsuleColliderHeightAdjustment { get { return m_CapsuleColliderHeightAdjustment; } set { m_CapsuleColliderHeightAdjustment = value; } }
|
||||
public bool AllowSpeedChange { get { return m_AllowSpeedChange; } set { m_AllowSpeedChange = value; } }
|
||||
|
||||
public override bool IsConcurrent { get { return m_ConcurrentAbility; } }
|
||||
|
||||
private Vector3[] m_StartColliderCenter;
|
||||
private float[] m_CapsuleColliderHeight;
|
||||
private Collider[] m_OverlapColliders = new Collider[1];
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the collider storage arrays.
|
||||
/// </summary>
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
// Initialize the arrays which will help determine if the ability can stop.
|
||||
m_StartColliderCenter = new Vector3[m_CharacterLocomotion.Colliders.Length];
|
||||
m_CapsuleColliderHeight = new float[m_CharacterLocomotion.Colliders.Length];
|
||||
var capsuleColliderCount = 0;
|
||||
for (int i = 0; i < m_CharacterLocomotion.Colliders.Length; ++i) {
|
||||
if (m_CharacterLocomotion.Colliders[i] is CapsuleCollider) {
|
||||
capsuleColliderCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// The counts won't be equal if the character has non-CapsuleCollider colliders.
|
||||
if (capsuleColliderCount != m_CapsuleColliderHeight.Length) {
|
||||
System.Array.Resize(ref m_CapsuleColliderHeight, capsuleColliderCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_CharacterLocomotion.Grounded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
// Colliders may have been added since the last time the ability started.
|
||||
if (m_CharacterLocomotion.ColliderCount > m_CapsuleColliderHeight.Length) {
|
||||
System.Array.Resize(ref m_CapsuleColliderHeight, m_CharacterLocomotion.ColliderCount);
|
||||
System.Array.Resize(ref m_StartColliderCenter, m_CharacterLocomotion.ColliderCount);
|
||||
}
|
||||
|
||||
// When the ability stops it'll check to ensure there is room for the colliders. Save the collider center/height so this check can be made.
|
||||
var capsuleColliderCount = 0;
|
||||
for (int i = 0; i < m_CharacterLocomotion.ColliderCount; ++i) {
|
||||
if (m_CharacterLocomotion.Colliders[i] is CapsuleCollider) {
|
||||
var capsuleCollider = (m_CharacterLocomotion.Colliders[i] as CapsuleCollider);
|
||||
m_CapsuleColliderHeight[capsuleColliderCount] = capsuleCollider.height;
|
||||
m_StartColliderCenter[i] = capsuleCollider.center;
|
||||
capsuleColliderCount++;
|
||||
} else { // SphereCollider.
|
||||
m_StartColliderCenter[i] = (m_CharacterLocomotion.Colliders[i] as SphereCollider).center;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Height != -1) {
|
||||
SetHeightParameter(m_Height);
|
||||
}
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnHeightChangeAdjustHeight", m_CapsuleColliderHeightAdjustment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
m_CharacterLocomotion.EnableColliderCollisionLayer(false);
|
||||
var keepActive = false;
|
||||
|
||||
// The ability can't stop if there isn't enough room for the character to occupy their original height.
|
||||
var capsuleColliderCount = 0;
|
||||
for (int i = 0; i < m_CharacterLocomotion.ColliderCount; ++i) {
|
||||
// Determine if the collider would intersect any objects.
|
||||
if (m_CharacterLocomotion.Colliders[i] is CapsuleCollider) {
|
||||
var capsuleCollider = m_CharacterLocomotion.Colliders[i] as CapsuleCollider;
|
||||
var radiusMultiplier = MathUtility.ColliderRadiusMultiplier(capsuleCollider);
|
||||
Vector3 startEndCap, endEndCap;
|
||||
MathUtility.CapsuleColliderEndCaps(m_CapsuleColliderHeight[capsuleColliderCount] * MathUtility.CapsuleColliderHeightMultiplier(capsuleCollider),
|
||||
(capsuleCollider.radius - m_CharacterLocomotion.ColliderSpacing) * radiusMultiplier, Vector3.Scale(m_StartColliderCenter[i], capsuleCollider.transform.lossyScale), MathUtility.CapsuleColliderDirection(capsuleCollider),
|
||||
capsuleCollider.transform.position, capsuleCollider.transform.rotation, out startEndCap, out endEndCap);
|
||||
// If there is overlap then the ability can't stop.
|
||||
if (Physics.OverlapCapsuleNonAlloc(startEndCap, endEndCap, (capsuleCollider.radius - m_CharacterLocomotion.ColliderSpacing) * radiusMultiplier, m_OverlapColliders, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore) > 0) {
|
||||
keepActive = true;
|
||||
break;
|
||||
}
|
||||
capsuleColliderCount++;
|
||||
} else { // SphereCollider.
|
||||
var sphereCollider = m_CharacterLocomotion.Colliders[i] as SphereCollider;
|
||||
// If there is overlap then the ability can't stop.
|
||||
if (Physics.OverlapSphereNonAlloc(sphereCollider.transform.TransformPoint(sphereCollider.center), (sphereCollider.radius - m_CharacterLocomotion.ColliderSpacing) * MathUtility.ColliderRadiusMultiplier(sphereCollider),
|
||||
m_OverlapColliders, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore) > 0) {
|
||||
keepActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_CharacterLocomotion.EnableColliderCollisionLayer(true);
|
||||
if (keepActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CanStopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
return (!m_AllowSpeedChange && startingAbility is SpeedChange) || startingAbility is StoredInputAbilityBase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the current ability is attempting to start and another ability is active.
|
||||
/// Returns true or false depending on if the active ability should be stopped.
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability that is currently active.</param>
|
||||
public override bool ShouldStopActiveAbility(Ability activeAbility)
|
||||
{
|
||||
return (!m_AllowSpeedChange && activeAbility is SpeedChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
AbilityStopped(force, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
/// <param name="resetStoredInputs">Should the capsule collider height be reset?</param>
|
||||
protected void AbilityStopped(bool force, bool resetCapsuleColliderHeight)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
if (m_Height != -1) {
|
||||
SetHeightParameter(0);
|
||||
}
|
||||
if (resetCapsuleColliderHeight) {
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnHeightChangeAdjustHeight", -m_CapsuleColliderHeightAdjustment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/HeightChange.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/HeightChange.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e09b70b5636247245b674109c396e996
|
||||
timeCreated: 1487015903
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
19
Assets/Scripts/Character/Abilities/IItemToggledReceiver.cs
Normal file
19
Assets/Scripts/Character/Abilities/IItemToggledReceiver.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for any ability that should be notified when the item is toggled while the ability is active.
|
||||
/// </summary>
|
||||
public interface IItemToggledReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// The ItemEquipVerifier ability has toggled an item slot.
|
||||
/// </summary>
|
||||
void ItemToggled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8339cdcef812b243bf8020b6c00456b
|
||||
timeCreated: 1533751497
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
115
Assets/Scripts/Character/Abilities/Idle.cs
Normal file
115
Assets/Scripts/Character/Abilities/Idle.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Plays a random idle animation based off of the AbilityFloatData animator parameter.
|
||||
/// </summary>
|
||||
[DefaultStopType(AbilityStopType.Automatic)]
|
||||
public class Idle : Ability
|
||||
{
|
||||
[Tooltip("Specifies how long the ability should wait until it is started.")]
|
||||
[SerializeField] protected float m_StartDelay = 15;
|
||||
[Tooltip("The maximum AbilityFloatData animator parameter.")]
|
||||
[SerializeField] protected float m_MaxAbilityFloatDataValue;
|
||||
[Tooltip("Should a random int between 0 and MaxAbilityFloatDataValue be used? If false AbilityFloatData will be increased sequentially.")]
|
||||
[SerializeField] protected bool m_RandomValue = true;
|
||||
[Tooltip("The minimum amount of time that the current AbilityFloatData value should be set.")]
|
||||
[SerializeField] protected float m_MinDuration = 5;
|
||||
[Tooltip("The maximum amount of time that the current AbilityFloatData value should be set.")]
|
||||
[SerializeField] protected float m_MaxDuration = 10;
|
||||
|
||||
public float MaxAbilityFloatDataValue { get { return m_MaxAbilityFloatDataValue; } set { m_MaxAbilityFloatDataValue = value; } }
|
||||
public bool RandomValue { get { return m_RandomValue; } set { m_RandomValue = value; } }
|
||||
public float MinDuration { get { return m_MinDuration; } set { m_MinDuration = value; } }
|
||||
public float MaxDuration { get { return m_MaxDuration; } set { m_MaxDuration = value; } }
|
||||
|
||||
private float m_CanStartTime = -1;
|
||||
private float m_AbilityFloatDataValue;
|
||||
private ScheduledEventBase m_FloatChangeEvent;
|
||||
|
||||
public override float AbilityFloatData { get { return m_AbilityFloatDataValue; } }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The ability can be started when the character is not moving and is on the ground.
|
||||
if (!m_CharacterLocomotion.Moving && m_CharacterLocomotion.Grounded) {
|
||||
if (m_CanStartTime == -1) {
|
||||
m_CanStartTime = Time.time;
|
||||
return false;
|
||||
}
|
||||
// A delay can be added to prevent the more extreme idle animations from playing immediately.
|
||||
return m_CanStartTime + m_StartDelay < Time.time;
|
||||
}
|
||||
if (m_CanStartTime != -1) {
|
||||
m_CanStartTime = -1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
// Determine a new AbilityFloatData.
|
||||
DetermineAbilityFloatDataValue();
|
||||
}
|
||||
|
||||
private void DetermineAbilityFloatDataValue()
|
||||
{
|
||||
if (m_RandomValue) {
|
||||
m_AbilityFloatDataValue = Random.Range(0, (int)(m_MaxAbilityFloatDataValue + 1));
|
||||
} else { // Sequence.
|
||||
m_AbilityFloatDataValue = (int)(m_AbilityFloatDataValue + 1) % (int)(m_MaxAbilityFloatDataValue + 1);
|
||||
}
|
||||
m_CharacterLocomotion.UpdateAbilityAnimatorParameters();
|
||||
|
||||
// A new value should be chosen between the min and max duration.
|
||||
m_FloatChangeEvent = Scheduler.ScheduleFixed(Random.Range(m_MinDuration, m_MaxDuration), DetermineAbilityFloatDataValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
// The ability should be stopped when the character moves or is no longer grounded.
|
||||
return m_CharacterLocomotion.Moving || !m_CharacterLocomotion.Grounded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
m_CanStartTime = -1;
|
||||
|
||||
// DetermineAbilityFloatDataValue no longer needs to be called.
|
||||
Scheduler.Cancel(m_FloatChangeEvent);
|
||||
m_FloatChangeEvent = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Idle.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Idle.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc8cd1732d854a549ae9615b0a66d912
|
||||
timeCreated: 1513724196
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
326
Assets/Scripts/Character/Abilities/Interact.cs
Normal file
326
Assets/Scripts/Character/Abilities/Interact.cs
Normal file
@@ -0,0 +1,326 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
using Opsive.UltimateCharacterController.Networking;
|
||||
#endif
|
||||
using Opsive.UltimateCharacterController.Traits;
|
||||
using Opsive.UltimateCharacterController.Objects.CharacterAssist;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Interacts with another object within the scene. The object that the ability interacts with must have the Interact component added to it.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultInputName("Action")]
|
||||
[DefaultAbilityIndex(9)]
|
||||
[DefaultAllowPositionalInput(false)]
|
||||
[DefaultAllowRotationalInput(false)]
|
||||
[AllowDuplicateTypes]
|
||||
public class Interact : DetectObjectAbilityBase
|
||||
{
|
||||
[Tooltip("The ID of the Interactable. A value of -1 indicates no ID.")]
|
||||
[SerializeField] protected int m_InteractableID = -1;
|
||||
[Tooltip("Can the Height Change ability stay active while interacting?")]
|
||||
[SerializeField] protected bool m_AllowActiveHeightChange;
|
||||
[Tooltip("The value of the AbilityIntData animator parameter.")]
|
||||
[SerializeField] protected int m_AbilityIntDataValue;
|
||||
[Tooltip("Specifies if the ability should wait for the OnAnimatorInteract animation event or wait for the specified duration before interacting with the item.")]
|
||||
[SerializeField] protected AnimationEventTrigger m_InteractEvent = new AnimationEventTrigger(false, 0.2f);
|
||||
[Tooltip("Specifies if the ability should wait for the OnAnimatorInteractComplete animation event or wait for the specified duration before stopping the ability.")]
|
||||
[SerializeField] protected AnimationEventTrigger m_InteractCompleteEvent = new AnimationEventTrigger(false, 0.2f);
|
||||
|
||||
public int InteractableID { get { return m_InteractableID; } set { m_InteractableID = value; } }
|
||||
public bool AllowActiveHeightChange { get { return m_AllowActiveHeightChange; } set { m_AllowActiveHeightChange = value; } }
|
||||
public int AbilityIntDataValue { get { return m_AbilityIntDataValue; } set { m_AbilityIntDataValue = value; } }
|
||||
public AnimationEventTrigger InteractEvent { get { return m_InteractEvent; } set { m_InteractEvent = value; } }
|
||||
public AnimationEventTrigger InteractCompleteEvent { get { return m_InteractCompleteEvent; } set { m_InteractCompleteEvent = value; } }
|
||||
|
||||
private CharacterIKBase m_CharacterIK;
|
||||
protected Interactable m_Interactable;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
private INetworkInfo m_NetworkInfo;
|
||||
#endif
|
||||
|
||||
private ScheduledEventBase[] m_DisableIKInteractionEvents;
|
||||
private bool m_HasInteracted;
|
||||
private bool m_ExitedTrigger;
|
||||
|
||||
public override int AbilityIntData { get { return m_AbilityIntDataValue; } }
|
||||
public override string AbilityMessageText
|
||||
{
|
||||
get
|
||||
{
|
||||
var message = m_AbilityMessageText;
|
||||
if (m_Interactable != null) {
|
||||
message = string.Format(message, m_Interactable.AbilityMessage());
|
||||
}
|
||||
return message;
|
||||
}
|
||||
set { base.AbilityMessageText = value; }
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public override string AbilityDescription { get { if (m_InteractableID != -1) { return "Interactable " + m_InteractableID; } return string.Empty; } }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_CharacterIK = m_GameObject.GetCachedComponent<CharacterIKBase>();
|
||||
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
m_NetworkInfo = m_GameObject.GetCachedComponent<INetworkInfo>();
|
||||
#endif
|
||||
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorInteract", DoInteract);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorInteractComplete", InteractComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// The base class may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
m_Interactable = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// The ability can't start if the Interactable isn't ready.
|
||||
if (!m_Interactable.CanInteract(m_GameObject)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the possible MoveTowardsLocations that the character can move towards.
|
||||
/// </summary>
|
||||
/// <returns>The possible MoveTowardsLocations that the character can move towards.</returns>
|
||||
public override MoveTowardsLocation[] GetMoveTowardsLocations()
|
||||
{
|
||||
return m_Interactable.gameObject.GetCachedComponents<MoveTowardsLocation>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the object to ensure it is valid for the current ability.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object being validated.</param>
|
||||
/// <param name="raycastHit">The raycast hit of the detected object. Will be null for trigger detections.</param>
|
||||
/// <returns>True if the object is valid. The object may not be valid if it doesn't have an ability-specific component attached.</returns>
|
||||
protected override bool ValidateObject(GameObject obj, RaycastHit? raycastHit)
|
||||
{
|
||||
if (!base.ValidateObject(obj, raycastHit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Interactable != null && raycastHit.HasValue) {
|
||||
return obj == m_Interactable.gameObject || obj.transform.IsChildOf(m_Interactable.transform);
|
||||
}
|
||||
|
||||
// The object must have the Interactable component.
|
||||
var interactable = obj.GetCachedParentComponent<Interactable>();
|
||||
if (interactable != null) {
|
||||
// If the ID is used then the IDs must match.
|
||||
if (m_InteractableID != -1 && interactable.ID != m_InteractableID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Interactable will not be null if coming from a trigger.
|
||||
if (m_Interactable == null) {
|
||||
m_Interactable = interactable;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
return (startingAbility is Items.ItemAbility) || startingAbility.Index > Index || startingAbility is StoredInputAbilityBase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the current ability is attempting to start and another ability is active.
|
||||
/// Returns true or false depending on if the active ability should be stopped.
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability that is currently active.</param>
|
||||
/// <returns>True if the ability should be stopped.</returns>
|
||||
public override bool ShouldStopActiveAbility(Ability activeAbility)
|
||||
{
|
||||
return (!m_AllowActiveHeightChange && activeAbility is HeightChange) || activeAbility is StoredInputAbilityBase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
m_HasInteracted = false;
|
||||
m_ExitedTrigger = false;
|
||||
|
||||
if (!m_InteractEvent.WaitForAnimationEvent) {
|
||||
Scheduler.ScheduleFixed(m_InteractEvent.Duration, DoInteract);
|
||||
}
|
||||
|
||||
// The interactable can move the limbs to a specific location.
|
||||
if (m_CharacterIK != null) {
|
||||
for (int i = 0; i < m_Interactable.IKTargets.Length; ++i) {
|
||||
var ikTarget = m_Interactable.IKTargets[i];
|
||||
if (ikTarget.Goal != CharacterIKBase.IKGoal.Last) {
|
||||
Scheduler.ScheduleFixed<AbilityIKTarget, Transform>(ikTarget.Delay, SetIKTarget, ikTarget, ikTarget.Transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the IK target.
|
||||
/// </summary>
|
||||
/// <param name="ikTarget">The IK target that should be set.</param>
|
||||
/// <param name="targetTransform">The transform target that should be set.</param>
|
||||
private void SetIKTarget(AbilityIKTarget ikTarget, Transform targetTransform)
|
||||
{
|
||||
m_CharacterIK.SetAbilityIKTarget(targetTransform, ikTarget.Goal, ikTarget.InterpolationDuration);
|
||||
// If the transform is not null then the end should be scheduled so it can be set to null.
|
||||
if (targetTransform != null) {
|
||||
if (m_DisableIKInteractionEvents == null) {
|
||||
m_DisableIKInteractionEvents = new ScheduledEventBase[m_Interactable.IKTargets.Length];
|
||||
} else if (m_DisableIKInteractionEvents.Length < m_Interactable.IKTargets.Length) {
|
||||
System.Array.Resize(ref m_DisableIKInteractionEvents, m_Interactable.IKTargets.Length);
|
||||
}
|
||||
for (int i = 0; i < m_DisableIKInteractionEvents.Length; ++i) {
|
||||
if (m_DisableIKInteractionEvents[i] == null) {
|
||||
m_DisableIKInteractionEvents[i] = Scheduler.ScheduleFixed<AbilityIKTarget, Transform>(ikTarget.Duration, (AbilityIKTarget abilityIKTarget, Transform target) =>
|
||||
{
|
||||
SetIKTarget(ikTarget, target);
|
||||
m_DisableIKInteractionEvents[i] = null;
|
||||
}, ikTarget, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interacts with the object.
|
||||
/// </summary>
|
||||
private void DoInteract()
|
||||
{
|
||||
if (!IsActive || m_HasInteracted) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
// The Interact event will be sent through a message. The ability does not need to call the interaction.
|
||||
if (m_NetworkInfo != null && !m_NetworkInfo.IsLocalPlayer()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_Interactable.Interact(m_GameObject);
|
||||
m_HasInteracted = true;
|
||||
|
||||
if (!m_InteractCompleteEvent.WaitForAnimationEvent) {
|
||||
Scheduler.ScheduleFixed(m_InteractCompleteEvent.Duration, InteractComplete);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes the ability.
|
||||
/// </summary>
|
||||
private void InteractComplete()
|
||||
{
|
||||
if (!IsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has exited a trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The GameObject that the character exited.</param>
|
||||
/// <returns>Returns true if the entered object leaves the trigger.</returns>
|
||||
protected override bool TriggerExit(GameObject other)
|
||||
{
|
||||
if (IsActive) {
|
||||
m_ExitedTrigger = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (base.TriggerExit(other)) {
|
||||
// The character may have been in multiple triggers.
|
||||
if (m_DetectedObject == null) {
|
||||
m_Interactable = null;
|
||||
} else {
|
||||
m_Interactable = m_DetectedObject.GetCachedParentComponent<Interactable>();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
if (m_ExitedTrigger) {
|
||||
m_Interactable = null;
|
||||
m_DetectedTriggerObjectsCount = 0;
|
||||
m_DetectedObject = null;
|
||||
m_ExitedTrigger = false;
|
||||
}
|
||||
// The ability may end before the interaction duration has elapsed.
|
||||
if (m_DisableIKInteractionEvents != null) {
|
||||
for (int i = 0; i < m_DisableIKInteractionEvents.Length; ++i) {
|
||||
if (m_DisableIKInteractionEvents[i] == null) {
|
||||
continue;
|
||||
}
|
||||
m_DisableIKInteractionEvents[i].Invoke();
|
||||
Scheduler.Cancel(m_DisableIKInteractionEvents[i]);
|
||||
m_DisableIKInteractionEvents[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorInteract", DoInteract);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorInteractComplete", InteractComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Interact.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Interact.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b983b39e0984f44a8d128e0d282e1f8
|
||||
timeCreated: 1513724196
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
365
Assets/Scripts/Character/Abilities/ItemEquipVerifier.cs
Normal file
365
Assets/Scripts/Character/Abilities/ItemEquipVerifier.cs
Normal file
@@ -0,0 +1,365 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.Shared.Inventory;
|
||||
using Opsive.UltimateCharacterController.Character.Abilities.Items;
|
||||
using Opsive.UltimateCharacterController.Inventory;
|
||||
using Opsive.UltimateCharacterController.Items;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the items are equipped or unequipped according to the AllowEquippedSlotsMask.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.Manual)]
|
||||
public class ItemEquipVerifier : Ability
|
||||
{
|
||||
public override bool IsConcurrent { get { return true; } }
|
||||
|
||||
private EquipUnequip[] m_EquipUnequipAbilities;
|
||||
private bool m_Equip;
|
||||
private Ability m_StartingAbility;
|
||||
private bool m_UnequippedItems;
|
||||
private ItemSetManagerBase m_ItemSetManager;
|
||||
private bool m_CanToggleItem = true;
|
||||
private bool m_CanStopAbility;
|
||||
private HashSet<ItemAbility> m_ActiveEquipUnequipAbilities = new HashSet<ItemAbility>();
|
||||
|
||||
private int m_StartEquippedSlotMask;
|
||||
private bool m_StartAllowPositionalInput;
|
||||
private bool m_StartAllowRotationalInput;
|
||||
private int[] m_StartingItemSetIndex;
|
||||
private int[] m_TargetItemSetIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_EquipUnequipAbilities = m_CharacterLocomotion.GetAbilities<EquipUnequip>();
|
||||
if (m_EquipUnequipAbilities == null || m_EquipUnequipAbilities.Length == 0) {
|
||||
Debug.LogError("Error: At least one EquipUnequip ability must be added to the character in order for the ItemEquipVerifier ability to work.");
|
||||
Enabled = false;
|
||||
return;
|
||||
}
|
||||
m_ItemSetManager = m_GameObject.GetCachedComponent<ItemSetManagerBase>();
|
||||
m_StartingItemSetIndex = new int[m_EquipUnequipAbilities.Length];
|
||||
m_TargetItemSetIndex = new int[m_EquipUnequipAbilities.Length];
|
||||
|
||||
EventHandler.RegisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to toggle an equip or unequip based on the AllowedEquippedSlotsMask. True will be returned if the ability starts.
|
||||
/// </summary>
|
||||
/// <param name="ability">The ability that is trying to be started/stopped.</param>
|
||||
/// <param name="activate">True if the ability is being activated, false if it is being deactivated.</param>
|
||||
/// <returns>True if the ability started.</returns>
|
||||
public bool TryToggleItem(Ability ability, bool activate)
|
||||
{
|
||||
if (!Enabled || !m_CanToggleItem || ability is PickupItem || ability == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The starting ability may stop before the unequip has completed. Stop unequipping and restart the equip.
|
||||
if (ability == m_StartingAbility && IsActive && !activate) {
|
||||
StopAbility();
|
||||
|
||||
for (int i = 0; i < m_EquipUnequipAbilities.Length; ++i) {
|
||||
m_EquipUnequipAbilities[i].StartEquipUnequip(m_StartingItemSetIndex[i]);
|
||||
}
|
||||
m_StartingAbility = null;
|
||||
return true;
|
||||
} else if (m_StartingAbility != null && ((!activate && m_StartingAbility != ability) || (activate && !m_Equip && m_StartingAbility == ability))) {
|
||||
// Do not change the equip status if the ability is stopping and is not the starting ability, or the ability is already active and is unequipping.
|
||||
return false;
|
||||
}
|
||||
|
||||
var startPossible = false;
|
||||
var start = false;
|
||||
var equip = false;
|
||||
var unequippedItems = m_UnequippedItems;
|
||||
var allowEquippedSlotMask = 0;
|
||||
// If the ability is activated then the current set of items may need to be unequipped.
|
||||
if (activate && (ability.AllowEquippedSlotsMask != -1 || (ability.AllowItemDefinitions != null && ability.AllowItemDefinitions.Length > 0))) {
|
||||
startPossible = true;
|
||||
// A mask can specify if the item should be equipped.
|
||||
if (ability.AllowEquippedSlotsMask != -1) {
|
||||
if (IsActive) {
|
||||
// If the ability is already active it should clean up and force the completion of the previous unequip.
|
||||
StartEquipUnequip(true);
|
||||
}
|
||||
// The ability may not need to activate if the not allowed equipped items are already not equipped.
|
||||
var currentEquippedSlots = 0;
|
||||
Item item;
|
||||
for (int i = 0; i < m_Inventory.SlotCount; ++i) {
|
||||
if ((item = m_Inventory.GetActiveItem(i)) != null) {
|
||||
// If the current ItemSet is the defualt ItemSet then the current item can be considered unequipped. This for example will allow the body item
|
||||
// (for puncing/kicking) to be active even if the ability says no item should be active.
|
||||
if (IsDefaultItemIdentifier(item.ItemIdentifier)) {
|
||||
continue;
|
||||
}
|
||||
currentEquippedSlots |= 1 << i;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentEquippedSlots != 0 && !MathUtility.InLayerMask(currentEquippedSlots, 1 << ability.AllowEquippedSlotsMask)) {
|
||||
start = true;
|
||||
unequippedItems = true;
|
||||
allowEquippedSlotMask = ability.AllowEquippedSlotsMask;
|
||||
} else {
|
||||
unequippedItems = false;
|
||||
}
|
||||
}
|
||||
|
||||
// An array can specify if the item can be equipped.
|
||||
if (!unequippedItems && ability.AllowItemDefinitions != null && ability.AllowItemDefinitions.Length > 0) {
|
||||
Item item;
|
||||
for (int i = 0; i < m_Inventory.SlotCount; ++i) {
|
||||
if ((item = m_Inventory.GetActiveItem(i)) != null) {
|
||||
// The equipped item is a defualt item - it can be considered unequipped.
|
||||
if (IsDefaultItemIdentifier(item.ItemIdentifier)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The item should be unequipped if it doesn't match any of the allow item types.
|
||||
var unequip = true;
|
||||
for (int j = 0; j < ability.AllowItemDefinitions.Length; ++j) {
|
||||
if (ability.AllowItemDefinitions[j] == null) {
|
||||
continue;
|
||||
}
|
||||
if (item.ItemIdentifier.GetItemDefinition() == ability.AllowItemDefinitions[j]) {
|
||||
allowEquippedSlotMask |= 1 << i;
|
||||
unequip = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!unequippedItems && unequip) {
|
||||
start = true;
|
||||
unequippedItems = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If slots were unequipped when the ability started then they should be equipped when the ability stops.
|
||||
} else if (!activate && ability.ReequipSlots && unequippedItems) {
|
||||
start = true;
|
||||
equip = true;
|
||||
}
|
||||
|
||||
if (start) {
|
||||
m_Equip = equip;
|
||||
m_UnequippedItems = unequippedItems;
|
||||
if (ability is MoveTowards && (ability as MoveTowards).OnArriveAbility != null) {
|
||||
ability = (ability as MoveTowards).OnArriveAbility;
|
||||
}
|
||||
|
||||
// When the ability is unequipping the items it should inherit the allow field values from the starting ability.
|
||||
// When the ability is complete (equpping the items) it should use the starting field values.
|
||||
if (!m_Equip) {
|
||||
m_StartEquippedSlotMask = m_AllowEquippedSlotsMask;
|
||||
m_StartAllowPositionalInput = m_AllowPositionalInput;
|
||||
m_StartAllowRotationalInput = m_AllowRotationalInput;
|
||||
|
||||
m_AllowEquippedSlotsMask = allowEquippedSlotMask;
|
||||
m_AllowPositionalInput = ability.AllowPositionalInput;
|
||||
m_AllowRotationalInput = ability.AllowRotationalInput;
|
||||
} else {
|
||||
m_AllowEquippedSlotsMask = m_StartEquippedSlotMask;
|
||||
m_AllowPositionalInput = m_StartAllowPositionalInput;
|
||||
m_AllowRotationalInput = m_StartAllowRotationalInput;
|
||||
}
|
||||
|
||||
// Active should only be true if the ability is equipping and the original ability is reequipping the slots. If the ability is not
|
||||
// reequipping slots then the Item Equip Verifier will not be run again after it is complete.
|
||||
m_StartingAbility = ability;
|
||||
|
||||
if (IsActive) {
|
||||
// The ability is not an ability that can start multiple times.
|
||||
StartEquipUnequip(false);
|
||||
} else {
|
||||
StartAbility();
|
||||
}
|
||||
} else if (startPossible && activate && IsActive && m_StartingAbility != null && m_StartingAbility != ability) {
|
||||
// The ability is already active. A new ability is taking over for the start.
|
||||
m_Equip = equip;
|
||||
m_UnequippedItems = true;
|
||||
|
||||
m_AllowEquippedSlotsMask = allowEquippedSlotMask;
|
||||
m_AllowPositionalInput = ability.AllowPositionalInput;
|
||||
m_AllowRotationalInput = ability.AllowRotationalInput;
|
||||
m_StartingAbility = ability;
|
||||
|
||||
// The original item set should be reverted.
|
||||
for (int i = 0; i < m_EquipUnequipAbilities.Length; ++i) {
|
||||
m_EquipUnequipAbilities[i].StartEquipUnequip(m_TargetItemSetIndex[i]);
|
||||
}
|
||||
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the specified ItemIdentifier the default ItemIdentifier within the ItemSetManager?
|
||||
/// </summary>
|
||||
/// <param name="itemIdentifier">The ItemIdentifier to determine if it is the default ItemIdentifier.</param>
|
||||
/// <returns>True if the specified ItemIdentifier is the default ItemIdentifier.</returns>
|
||||
private bool IsDefaultItemIdentifier(IItemIdentifier itemIdentifier)
|
||||
{
|
||||
if (m_ItemSetManager != null) {
|
||||
return m_ItemSetManager.IsDefaultItemCategory(itemIdentifier.GetItemDefinition());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
m_ActiveEquipUnequipAbilities.Clear();
|
||||
EventHandler.RegisterEvent<ItemAbility, bool>(m_GameObject, "OnCharacterItemAbilityActive", OnItemAbilityActive);
|
||||
// The original ability may be null on the network.
|
||||
if (m_StartingAbility != null) {
|
||||
m_CanStopAbility = !m_Equip && m_StartingAbility.ImmediateUnequip;
|
||||
StartEquipUnequip(false);
|
||||
}
|
||||
m_CanStopAbility = true;
|
||||
|
||||
// If the count is still zero then all of the items were unequipped in a single frame.
|
||||
if (m_ActiveEquipUnequipAbilities.Count == 0) {
|
||||
ItemToggled();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the StartEquipUnequip method on the EquipUnequip ability.
|
||||
/// </summary>
|
||||
/// <param name="immediateEquipUnequip">Should the items be equipped or unequipped immediately?</param>
|
||||
private void StartEquipUnequip(bool immediateEquipUnequip)
|
||||
{
|
||||
for (int i = 0; i < m_EquipUnequipAbilities.Length; ++i) {
|
||||
if (m_Equip) {
|
||||
// Don't equip if the character has since changed which item set is equipped.
|
||||
if (m_ItemSetManager.ActiveItemSetIndex[m_EquipUnequipAbilities[i].ItemSetCategoryIndex] != m_TargetItemSetIndex[i]) {
|
||||
continue;
|
||||
}
|
||||
m_EquipUnequipAbilities[i].StartEquipUnequip(m_StartingItemSetIndex[i], true, immediateEquipUnequip);
|
||||
} else {
|
||||
var targetItemSetIndex = m_ItemSetManager.GetTargetItemSetIndex(m_EquipUnequipAbilities[i].ItemSetCategoryIndex, m_AllowEquippedSlotsMask);
|
||||
var activeItemSetIndex = m_ItemSetManager.ActiveItemSetIndex[m_EquipUnequipAbilities[i].ItemSetCategoryIndex];
|
||||
m_StartingItemSetIndex[i] = activeItemSetIndex;
|
||||
if (targetItemSetIndex != activeItemSetIndex) {
|
||||
m_TargetItemSetIndex[i] = targetItemSetIndex;
|
||||
m_EquipUnequipAbilities[i].StartEquipUnequip(targetItemSetIndex, true, m_StartingAbility.ImmediateUnequip || immediateEquipUnequip);
|
||||
} else {
|
||||
m_TargetItemSetIndex[i] = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An ItemAbility has been activated or deactivated.
|
||||
/// </summary>
|
||||
/// <param name="itemAbility">The ItemAbility activated or deactivated.</param>
|
||||
/// <param name="active">Was the ItemAbility activated?</param>
|
||||
private void OnItemAbilityActive(ItemAbility itemAbility, bool active)
|
||||
{
|
||||
if (itemAbility is EquipUnequip) {
|
||||
// Keep a count of the number of EquipUnequip abilities started. This will allow the ability to call ItemToggled when no more
|
||||
// EquipUnequip abilities are active.
|
||||
if (active) {
|
||||
m_ActiveEquipUnequipAbilities.Add(itemAbility);
|
||||
} else {
|
||||
m_ActiveEquipUnequipAbilities.Remove(itemAbility);
|
||||
}
|
||||
if (m_ActiveEquipUnequipAbilities.Count == 0 && m_CanStopAbility) {
|
||||
ItemToggled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The EquipUnequip ability has toggled an item slot.
|
||||
/// </summary>
|
||||
private void ItemToggled()
|
||||
{
|
||||
if (m_StartingAbility == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop the ability before starting the OriginalAbility ability so ItemEquipVerifier doesn't prevent the ability from starting.
|
||||
StopAbility();
|
||||
|
||||
// The ability should only be started if the items were unequipped and the MoveTowards ability isn't active. If the MoveTowards ability is
|
||||
// active then the MoveTowards ability will start the ability.
|
||||
if (!m_Equip && (m_CharacterLocomotion.MoveTowardsAbility == null || m_CharacterLocomotion.MoveTowardsAbility.OnArriveAbility == null)) {
|
||||
m_CanToggleItem = false;
|
||||
if (!m_StartingAbility.IsActive) {
|
||||
m_CharacterLocomotion.TryStartAbility(m_StartingAbility, true, true);
|
||||
} else if (m_StartingAbility is IItemToggledReceiver) {
|
||||
// If the ability is already active then the ability is the one that toggled the item and it should receive the callback.
|
||||
(m_StartingAbility as IItemToggledReceiver).ItemToggled();
|
||||
}
|
||||
m_CanToggleItem = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
if (m_Equip) {
|
||||
Reset();
|
||||
}
|
||||
|
||||
EventHandler.UnregisterEvent<ItemAbility, bool>(m_GameObject, "OnCharacterItemAbilityActive", OnItemAbilityActive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has died.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the force.</param>
|
||||
/// <param name="force">The amount of force which killed the character.</param>
|
||||
/// <param name="attacker">The GameObject that killed the character.</param>
|
||||
private void OnDeath(Vector3 position, Vector3 force, GameObject attacker)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the ability back to the starting state.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
m_StartingAbility = null;
|
||||
m_UnequippedItems = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has been destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/ItemEquipVerifier.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/ItemEquipVerifier.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78770ec8fdaec394bbd8b1886972127d
|
||||
timeCreated: 1513724185
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Character/Abilities/Items.meta
Normal file
8
Assets/Scripts/Character/Abilities/Items.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1cd370961ec22d74898632d2cc53b56e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
369
Assets/Scripts/Character/Abilities/Items/Aim.cs
Normal file
369
Assets/Scripts/Character/Abilities/Items/Aim.cs
Normal file
@@ -0,0 +1,369 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.UltimateCharacterController.Input;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// ItemAbility which will aim the item.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.ButtonDownContinuous)]
|
||||
[DefaultStopType(AbilityStopType.ButtonUp)]
|
||||
[DefaultInputName("Fire2")]
|
||||
[DefaultItemStateIndex(1)]
|
||||
[DefaultState("Aim")]
|
||||
public class Aim : ItemAbility
|
||||
{
|
||||
[Tooltip("When the Aim ability is activated should it stop the speed change ability?")]
|
||||
[SerializeField] protected bool m_StopSpeedChange = true;
|
||||
[Tooltip("Should the ability activate when the first person perspective is enabled?")]
|
||||
[SerializeField] protected bool m_ActivateInFirstPerson = true;
|
||||
[Tooltip("Should the ability rotate the character to face the look source target?")]
|
||||
[SerializeField] protected bool m_RotateTowardsLookSourceTarget = true;
|
||||
|
||||
public bool StopSpeedChange { get { return m_StopSpeedChange; } set { m_StopSpeedChange = value; } }
|
||||
public bool ActivateInFirstPerson { get { return m_ActivateInFirstPerson; } set { m_ActivateInFirstPerson = value; } }
|
||||
public bool RotateTowardsLookSourceTarget { get { return m_RotateTowardsLookSourceTarget; } set { m_RotateTowardsLookSourceTarget = value; } }
|
||||
|
||||
private ILookSource m_LookSource;
|
||||
private bool m_FirstPersonPerspective;
|
||||
private bool m_InputStart;
|
||||
private bool m_PerspectiveSwitch;
|
||||
#if THIRD_PERSON_CONTROLLER
|
||||
private ThirdPersonController.Character.Abilities.Items.ItemPullback m_ItemPullback;
|
||||
#endif
|
||||
|
||||
public override bool CanReceiveMultipleStarts { get { return true; } }
|
||||
public bool InputStart { get { return m_InputStart; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
#if THIRD_PERSON_CONTROLLER
|
||||
m_ItemPullback = m_CharacterLocomotion.GetAbility<ThirdPersonController.Character.Abilities.Items.ItemPullback>();
|
||||
#endif
|
||||
// The look source may have already been assigned if the ability was added to the character after the look source was assigned.
|
||||
m_LookSource = m_CharacterLocomotion.LookSource;
|
||||
|
||||
EventHandler.RegisterEvent<ILookSource>(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
|
||||
EventHandler.RegisterEvent<bool>(m_GameObject, "OnCameraWillChangePerspectives", OnChangePerspectives);
|
||||
EventHandler.RegisterEvent<Ability, bool>(m_GameObject, "OnCharacterAbilityActive", OnAbilityActive);
|
||||
EventHandler.RegisterEvent<ItemAbility, bool>(m_GameObject, "OnCharacterItemAbilityActive", OnItemAbilityActive);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnRespawn", OnRespawn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new ILookSource object has been attached to the character.
|
||||
/// </summary>
|
||||
/// <param name="lookSource">The ILookSource object attached to the character.</param>
|
||||
private void OnAttachLookSource(ILookSource lookSource)
|
||||
{
|
||||
m_LookSource = lookSource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character perspective between first and third person has changed.
|
||||
/// </summary>
|
||||
/// <param name="firstPersonPerspective">Is the character in a first person perspective?</param>
|
||||
private void OnChangePerspectives(bool firstPersonPerspective)
|
||||
{
|
||||
// While in first person mode the character is always aiming so the ability should be started so the character shadow is correct.
|
||||
m_FirstPersonPerspective = firstPersonPerspective;
|
||||
if (m_ActivateInFirstPerson) {
|
||||
if (!IsActive && firstPersonPerspective) {
|
||||
StartAbility();
|
||||
} else if ( IsActive && !firstPersonPerspective && !m_InputStart) {
|
||||
m_PerspectiveSwitch = true;
|
||||
StopAbility(true);
|
||||
m_PerspectiveSwitch = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Item State Index which corresponds to the slot ID.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot that corresponds to the Item State Index.</param>
|
||||
/// <returns>The Item State Index which corresponds to the slot ID.</returns>
|
||||
public override int GetItemStateIndex(int slotID)
|
||||
{
|
||||
#if THIRD_PERSON_CONTROLLER
|
||||
// If the ItemPullback ability is active then an object would be obstructing the item location. Wait to change the state index
|
||||
// until no objects are obstructing the item location.
|
||||
if (m_ItemPullback != null && m_ItemPullback.IsActive) {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return base.GetItemStateIndex(slotID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Item Substate Index which corresponds to the slot ID.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot that corresponds to the Item Substate Index.</param>
|
||||
/// <returns>The Item Substate Index which corresponds to the slot ID.</returns>
|
||||
public override int GetItemSubstateIndex(int slotID)
|
||||
{
|
||||
#if THIRD_PERSON_CONTROLLER
|
||||
// If the ItemPullback ability is active then an object would be obstructing the item location. Wait to change the state index
|
||||
// until no objects are obstructing the item location.
|
||||
if (m_ItemPullback != null && m_ItemPullback.IsActive) {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
// Return the UsableItem's substate index if it isn't 0. This will allow the animator to know if the item is out of ammo.
|
||||
var item = m_Inventory.GetActiveItem(slotID);
|
||||
if (item != null && item.DominantItem) {
|
||||
var itemActions = item.ItemActions;
|
||||
for (int i = 0; i < itemActions.Length; ++i) {
|
||||
var usableItem = itemActions[i] as UltimateCharacterController.Items.Actions.IUsableItem;
|
||||
if (usableItem != null) {
|
||||
var substateIndex = usableItem.GetItemSubstateIndex();
|
||||
if (substateIndex != -1) {
|
||||
return substateIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m_InputStart ? 1 : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
if (m_InputStart) {
|
||||
return false;
|
||||
}
|
||||
#if THIRD_PERSON_CONTROLLER
|
||||
if (m_ItemPullback != null && m_ItemPullback.IsActive) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return base.CanStartAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
if (base.ShouldBlockAbilityStart(startingAbility)) {
|
||||
return true;
|
||||
}
|
||||
return PreventAbility(startingAbility);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the current ability is attempting to start and another ability is active.
|
||||
/// Returns true or false depending on if the active ability should be stopped.
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability that is currently active.</param>
|
||||
/// <returns>True if the ability should be stopped.</returns>
|
||||
public override bool ShouldStopActiveAbility(Ability activeAbility)
|
||||
{
|
||||
if (base.ShouldStopActiveAbility(activeAbility)) {
|
||||
return true;
|
||||
}
|
||||
return PreventAbility(activeAbility);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the specified ability be active while the Aim ability is active?
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability to check if it can be active.</param>
|
||||
/// <returns>True if the specified ability can be active while the Aim ability is active.</returns>
|
||||
private bool PreventAbility(Ability activeAbility)
|
||||
{
|
||||
// InputStart isn't set until AbilityStarted so the InputIndex should be used as well.
|
||||
if (m_StopSpeedChange && (InputIndex != -1 || m_InputStart) && activeAbility is SpeedChange) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
// If the ability started because of the first person perspective change then the state shouldn't be changed. If the state was changed
|
||||
// to first person and the state was changed then the camera would zoom in which should only occur with a button press.
|
||||
if (InputIndex != -1 || m_StartType == AbilityStartType.Automatic) {
|
||||
base.AbilityStarted();
|
||||
m_InputStart = true;
|
||||
}
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnAimAbilityStart", true, m_InputStart);
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnAimAbilityAim", true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the controller's rotation values.
|
||||
/// </summary>
|
||||
public override void UpdateRotation()
|
||||
{
|
||||
// If the character can look independently then the character does not need to rotate to face the look direction.
|
||||
if (m_CharacterLocomotion.ActiveMovementType.UseIndependentLook(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The look source may be null if a remote player is still being initialized.
|
||||
if (m_LookSource == null || !m_RotateTowardsLookSourceTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the direction that the character should be facing.
|
||||
var lookDirection = m_LookSource.LookDirection(m_LookSource.LookPosition(), true, m_CharacterLayerManager.IgnoreInvisibleCharacterLayers, false);
|
||||
var rotation = m_Transform.rotation * Quaternion.Euler(m_CharacterLocomotion.DeltaRotation);
|
||||
var localLookDirection = MathUtility.InverseTransformDirection(lookDirection, rotation);
|
||||
localLookDirection.y = 0;
|
||||
lookDirection = MathUtility.TransformDirection(localLookDirection, rotation);
|
||||
var targetRotation = Quaternion.LookRotation(lookDirection, rotation * Vector3.up);
|
||||
m_CharacterLocomotion.DeltaRotation = (Quaternion.Inverse(m_Transform.rotation) * targetRotation).eulerAngles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the input stop the ability?
|
||||
/// </summary>
|
||||
/// <param name="playerInput">A reference to the input component.</param>
|
||||
/// <returns>True if the input can stop the ability.</returns>
|
||||
public override bool CanInputStopAbility(PlayerInput playerInput)
|
||||
{
|
||||
if (m_ActivateInFirstPerson && !m_InputStart) {
|
||||
return false;
|
||||
}
|
||||
return base.CanInputStopAbility(playerInput);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
if (m_ActivateInFirstPerson && m_FirstPersonPerspective) {
|
||||
// The ability can't stop for as long as the character is in first person mode. If the ability was started in with a button press then
|
||||
// the ability shouldn't stop but it should change the state so the camera will no longer zoom.
|
||||
if (m_InputStart) {
|
||||
base.AbilityStopped(false);
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnAimAbilityStart", false, m_InputStart);
|
||||
m_InputStart = false;
|
||||
m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
// The base AbilityStopped may have already been called within CanStopAbility - don't call it again to prevent duplicate calls.
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnAimAbilityAim", false);
|
||||
if (m_PerspectiveSwitch && !m_InputStart) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnAimAbilityStart", false, m_InputStart);
|
||||
m_InputStart = false;
|
||||
|
||||
base.AbilityStopped(force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should the input be checked to ensure button up is using the correct value?
|
||||
/// </summary>
|
||||
/// <returns>True if the input should be checked.</returns>
|
||||
protected override bool ShouldCheckInput() { return false; }
|
||||
|
||||
/// <summary>
|
||||
/// The ability has been started or stopped.
|
||||
/// </summary>
|
||||
/// <param name="ability">The ability which was started or stopped.</param>
|
||||
/// <param name="active">True if the ability was started, false if it was stopped.</param>
|
||||
private void OnAbilityActive(Ability ability, bool active)
|
||||
{
|
||||
// Another ability may have stopped aiming while in first person. Activate aiming again if necessary.
|
||||
if (!IsActive && !active && m_ActivateInFirstPerson && m_CharacterLocomotion.FirstPersonPerspective) {
|
||||
StartAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The item ability has been started or stopped.
|
||||
/// </summary>
|
||||
/// <param name="itemAbility">The item ability which was started or stopped.</param>
|
||||
/// <param name="active">True if the ability was started, false if it was stopped.</param>
|
||||
private void OnItemAbilityActive(ItemAbility itemAbility, bool active)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (IsActive && itemAbility.Index > Index && (itemAbility is Use
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
|| itemAbility is Reload
|
||||
#endif
|
||||
)) {
|
||||
Debug.Log($"Warning: The ability {itemAbility.GetType().Name} started but it has a lower priority than the aim ability." +
|
||||
"This will prevent that ability from updating the Animator.");
|
||||
}
|
||||
#endif
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
// The Aim ability should start again if the Reload ability stopped and is in a first person view. The Reload ability would have
|
||||
// stopped the Aim ability.
|
||||
if (itemAbility is Reload && !active && m_CharacterLocomotion.FirstPersonPerspective) {
|
||||
OnChangePerspectives(true);
|
||||
}
|
||||
#endif
|
||||
#if THIRD_PERSON_CONTROLLER
|
||||
// If the ItemPullback ability is activated then the aim state should no longer be set. This will prevent the aim animator parameters from updating.
|
||||
if (IsActive && itemAbility == m_ItemPullback) {
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnAimAbilityAim", !active);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has respawned. Determine if the ability should start.
|
||||
/// </summary>
|
||||
private void OnRespawn()
|
||||
{
|
||||
if (m_ActivateInFirstPerson && m_CharacterLocomotion.FirstPersonPerspective) {
|
||||
StartAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<ILookSource>(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
|
||||
EventHandler.UnregisterEvent<bool>(m_GameObject, "OnCameraWillChangePerspectives", OnChangePerspectives);
|
||||
EventHandler.UnregisterEvent<Ability, bool>(m_GameObject, "OnCharacterAbilityActive", OnAbilityActive);
|
||||
EventHandler.UnregisterEvent<ItemAbility, bool>(m_GameObject, "OnCharacterItemAbilityActive", OnItemAbilityActive);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnRespawn", OnRespawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Items/Aim.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Items/Aim.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fc4f36a70968e849b763a43adffd032
|
||||
timeCreated: 1497448172
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
288
Assets/Scripts/Character/Abilities/Items/Block.cs
Normal file
288
Assets/Scripts/Character/Abilities/Items/Block.cs
Normal file
@@ -0,0 +1,288 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Items.Actions;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Block ability will play a blocking animation when another object comes into contact with the Shield ItemAction.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.Manual)]
|
||||
[DefaultStopType(AbilityStopType.Manual)]
|
||||
public class Block : ItemAbility
|
||||
{
|
||||
[Tooltip("The slot that should be used. -1 will block all of the slots.")]
|
||||
[SerializeField] protected int m_SlotID = -1;
|
||||
[Tooltip("The Animator's Item State Index when the character blocks.")]
|
||||
[SerializeField] protected int m_BlockItemStateIndex = 7;
|
||||
[Tooltip("The Animator's Item State Index when the character parries.")]
|
||||
[SerializeField] protected int m_ParryItemStateIndex = 8;
|
||||
|
||||
public int SlotID
|
||||
{
|
||||
get { return m_SlotID; }
|
||||
set
|
||||
{
|
||||
if (m_SlotID != value) {
|
||||
UnregisterSlotEvents(m_SlotID);
|
||||
m_SlotID = value;
|
||||
RegisterSlotEvents(m_SlotID);
|
||||
}
|
||||
}
|
||||
}
|
||||
public int BlockItemStateIndex { get { return m_BlockItemStateIndex; } set { m_BlockItemStateIndex = value; } }
|
||||
public int ParryItemStateIndex { get { return m_ParryItemStateIndex; } set { m_ParryItemStateIndex = value; } }
|
||||
|
||||
private Shield[] m_Shields;
|
||||
private object[] m_ImpactSources;
|
||||
private ScheduledEventBase[] m_BlockEvents;
|
||||
private bool m_Parry;
|
||||
|
||||
public object[] ImpactSources { get { return m_ImpactSources; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
var count = m_SlotID == -1 ? m_Inventory.SlotCount : 1;
|
||||
m_Shields = new Shield[count];
|
||||
m_ImpactSources = new object[count];
|
||||
m_BlockEvents = new ScheduledEventBase[count];
|
||||
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemImpactComplete", OnItemImpactComplete);
|
||||
EventHandler.RegisterEvent<Shield, object>(m_GameObject, "OnShieldImpact", StartBlock);
|
||||
RegisterSlotEvents(m_SlotID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers for the interested events according to the slot id.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The slot id to register for.</param>
|
||||
private void RegisterSlotEvents(int slotID)
|
||||
{
|
||||
if (!Application.isPlaying) {
|
||||
return;
|
||||
}
|
||||
if (slotID == 0) {
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemImpactCompleteFirstSlot", OnItemImpactCompleteFirstSlot);
|
||||
} else if (slotID == 1) {
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemImpactCompleteSecondSlot", OnItemImpactCompleteSecondSlot);
|
||||
} else if (slotID == 2) {
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemImpactCompleteThirdSlot", OnItemImpactCompleteThirdSlot);
|
||||
} else if (slotID != -1) {
|
||||
Debug.LogError("Error: The Block ability does not listen to slot " + m_SlotID);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters from the interested events according to the slot id.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The slot id to unregister from.</param>
|
||||
private void UnregisterSlotEvents(int slotID)
|
||||
{
|
||||
if (!Application.isPlaying) {
|
||||
return;
|
||||
}
|
||||
if (slotID == 0) {
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemImpactCompleteFirstSlot", OnItemImpactCompleteFirstSlot);
|
||||
} else if (slotID == 1) {
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemImpactCompleteSecondSlot", OnItemImpactCompleteSecondSlot);
|
||||
} else if (slotID == 2) {
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemImpactCompleteThirdSlot", OnItemImpactCompleteThirdSlot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Item State Index which corresponds to the slot ID.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot that corresponds to the Item State Index.</param>
|
||||
/// <returns>The Item State Index which corresponds to the slot ID.</returns>
|
||||
public override int GetItemStateIndex(int slotID)
|
||||
{
|
||||
// Return the ItemStateIndex if the SlotID matches the requested slotID.
|
||||
if (m_SlotID == -1) {
|
||||
if (m_Shields[slotID] != null) {
|
||||
return m_Parry ? m_ParryItemStateIndex : m_BlockItemStateIndex;
|
||||
}
|
||||
} else if (m_SlotID == slotID && m_Shields[0] != null) {
|
||||
return m_Parry ? m_ParryItemStateIndex : m_BlockItemStateIndex;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Item Substate Index which corresponds to the slot ID.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot that corresponds to the Item Substate Index.</param>
|
||||
/// <returns>The Item Substate Index which corresponds to the slot ID.</returns>
|
||||
public override int GetItemSubstateIndex(int slotID)
|
||||
{
|
||||
var substateIndex = -1;
|
||||
object impactSource = null;
|
||||
if (m_SlotID == -1) {
|
||||
if (m_Shields[slotID] != null) {
|
||||
substateIndex = m_Shields[slotID].GetItemSubstateIndex();
|
||||
impactSource = m_ImpactSources[slotID];
|
||||
}
|
||||
} else if (m_SlotID == slotID && m_Shields[0] != null) {
|
||||
substateIndex = m_Shields[0].GetItemSubstateIndex();
|
||||
impactSource = m_ImpactSources[0];
|
||||
}
|
||||
if (substateIndex == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If the impact source is a MeleeWeapon determine which attack is being played. This will allow the block integer to depend on
|
||||
if (impactSource != null && impactSource is MeleeWeapon) {
|
||||
var meleeWeapon = impactSource as MeleeWeapon;
|
||||
var meleeUseSubstateIndex = meleeWeapon.UsedSubstateIndex;
|
||||
if (meleeUseSubstateIndex != -1) {
|
||||
return MathUtility.Concatenate(meleeWeapon.Item.AnimatorItemID, meleeUseSubstateIndex, substateIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return substateIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An object has impacted the shield. Start the blocking animation.
|
||||
/// </summary>
|
||||
/// <param name="shield">The shield that was impacted.</param>
|
||||
/// <param name="source">The object that is trying to damage the shield.</param>
|
||||
public void StartBlock(Shield shield, object source)
|
||||
{
|
||||
var slotID = shield.Item.SlotID;
|
||||
if (m_SlotID != -1 && slotID != m_SlotID) {
|
||||
return;
|
||||
} else if (slotID == m_SlotID) {
|
||||
// If the ability only responds to a single slot then the arrays will always have just a single element.
|
||||
slotID = 0;
|
||||
}
|
||||
|
||||
if (m_Shields[slotID] != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The character can't block and use an item at the same time.
|
||||
if (m_CharacterLocomotion.IsAbilityTypeActive<Use>()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_Shields[slotID] = shield;
|
||||
m_ImpactSources[slotID] = source;
|
||||
// The difference between a block and a parry is that a parry will occur if there is a melee weapon.
|
||||
m_Parry = shield.gameObject.GetCachedComponent<MeleeWeapon>() != null;
|
||||
StartAbility();
|
||||
|
||||
if (!shield.ImpactCompleteEvent.WaitForAnimationEvent) {
|
||||
m_BlockEvents[slotID] = Scheduler.ScheduleFixed(shield.ImpactCompleteEvent.Duration, ImpactComplete, slotID);
|
||||
}
|
||||
m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The impact animation has completed for all of the items.
|
||||
/// </summary>
|
||||
private void OnItemImpactComplete()
|
||||
{
|
||||
for (int i = 0; i < m_Shields.Length; ++i) {
|
||||
if (m_Shields[i] != null) {
|
||||
ImpactComplete(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The impact animation has completed for the first item slot.
|
||||
/// </summary>
|
||||
private void OnItemImpactCompleteFirstSlot()
|
||||
{
|
||||
ImpactComplete(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The impact animation has completed for the second item slot.
|
||||
/// </summary>
|
||||
private void OnItemImpactCompleteSecondSlot()
|
||||
{
|
||||
ImpactComplete(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The impact animation has completed for the third item slot.
|
||||
/// </summary>
|
||||
private void OnItemImpactCompleteThirdSlot()
|
||||
{
|
||||
ImpactComplete(2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The impact animation has completed for the specified slot.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The slot that has completed the impact.</param>
|
||||
private void ImpactComplete(int slotID)
|
||||
{
|
||||
if (m_Shields[slotID] == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_Shields[slotID].StopBlockImpact();
|
||||
m_Shields[slotID] = null;
|
||||
m_BlockEvents[slotID] = null;
|
||||
m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters();
|
||||
|
||||
var stopAbility = true;
|
||||
for (int i = 0; i < m_Shields.Length; ++i) {
|
||||
if (m_Shields[i] != null) {
|
||||
stopAbility = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stopAbility) {
|
||||
StopAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
for (int i = 0; i < m_Shields.Length; ++i) {
|
||||
if (m_Shields[i] != null) {
|
||||
m_Shields[i].StopBlockImpact();
|
||||
m_Shields[i] = null;
|
||||
if (m_BlockEvents[i] != null) {
|
||||
Scheduler.Cancel(m_BlockEvents[i]);
|
||||
m_BlockEvents[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemImpactComplete", OnItemImpactComplete);
|
||||
EventHandler.UnregisterEvent<Shield, object>(m_GameObject, "OnShieldImpact", StartBlock);
|
||||
UnregisterSlotEvents(m_SlotID);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Items/Block.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Items/Block.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64c1220531b26354c82a0fc7cda562bb
|
||||
timeCreated: 1497448172
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
258
Assets/Scripts/Character/Abilities/Items/Drop.cs
Normal file
258
Assets/Scripts/Character/Abilities/Items/Drop.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.Shared.Inventory;
|
||||
using Opsive.UltimateCharacterController.Items;
|
||||
using Opsive.UltimateCharacterController.Inventory;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Drop ItemAbility will drop the currently equipped item.
|
||||
/// </summary>
|
||||
[AllowDuplicateTypes]
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultInputName("Drop")]
|
||||
[DefaultItemStateIndex(6)]
|
||||
public class Drop : ItemAbility
|
||||
{
|
||||
[Tooltip("The slot that should be dropped. -1 will drop all of the slots.")]
|
||||
[SerializeField] protected int m_SlotID = -1;
|
||||
[Tooltip("The ItemIdentifiers that cannot be dropped.")]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("m_NoDropItemTypes")]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("m_NoDropItemIdentifiers")]
|
||||
[SerializeField] protected ItemDefinitionBase[] m_NoDropItemDefinitions;
|
||||
[Tooltip("Should the item wait to be dropped until it is unequipped?")]
|
||||
[SerializeField] protected bool m_WaitForUnequip;
|
||||
[Tooltip("Specifies if the item should be dropped when the OnAnimatorDropItem event is received or wait for the specified duration before dropping the item.")]
|
||||
[SerializeField] protected AnimationEventTrigger m_DropEvent;
|
||||
|
||||
public int SlotID { get { return m_SlotID; } set { m_SlotID = value; } }
|
||||
public ItemDefinitionBase[] NoDropItemDefinitions { get { return m_NoDropItemDefinitions; } set { m_NoDropItemDefinitions = value; } }
|
||||
public bool WaitForUnequip { get { return m_WaitForUnequip; } set { m_WaitForUnequip = value; } }
|
||||
public AnimationEventTrigger DropEvent { get { return m_DropEvent; } set { m_DropEvent = value; } }
|
||||
|
||||
private ItemSetManager m_ItemSetManager;
|
||||
private Item[] m_Items;
|
||||
private EquipUnequip[] m_EquipUnequipAbilities;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public override string AbilityDescription { get { if (m_SlotID != -1) { return "Slot " + m_SlotID; } return string.Empty; } }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_ItemSetManager = m_GameObject.GetCachedComponent<ItemSetManager>();
|
||||
m_Items = new Item[m_SlotID == -1 ? m_Inventory.SlotCount : 1];
|
||||
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorDropItem", DropItem);
|
||||
if (m_ItemSetManager != null) {
|
||||
EventHandler.RegisterEvent<Item, int>(m_GameObject, "OnAbilityUnequipItemComplete", OnUnequipItem);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the equip unequip abilities.
|
||||
/// </summary>
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
m_EquipUnequipAbilities = m_CharacterLocomotion.GetAbilities<EquipUnequip>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the item be dropped?
|
||||
/// </summary>
|
||||
/// <returns>True if the item can be dropped.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the SlotID is -1 then the ability should drop every equipped item at the same time. If only one slot has a item then the
|
||||
// ability can start. If the SlotID is not -1 then the ability should drop the item in the specified slot.
|
||||
var canDrop = false;
|
||||
if (m_SlotID == -1) {
|
||||
for (int i = 0; i < m_Items.Length; ++i) {
|
||||
m_Items[i] = m_Inventory.GetActiveItem(i);
|
||||
if (m_Items[i] == null) {
|
||||
continue;
|
||||
}
|
||||
// Certain ItemIdentifiers cannot be dropped.
|
||||
if (m_NoDropItemDefinitions != null) {
|
||||
var skipItemIdentifier = false;
|
||||
for (int j = 0; j < m_NoDropItemDefinitions.Length; ++j) {
|
||||
if (m_Items[i].ItemIdentifier.GetItemDefinition() == m_NoDropItemDefinitions[j]) {
|
||||
skipItemIdentifier = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skipItemIdentifier) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// The item can be droppped.
|
||||
canDrop = true;
|
||||
}
|
||||
} else {
|
||||
m_Items[0] = m_Inventory.GetActiveItem(m_SlotID);
|
||||
// Certain ItemIdentifiers cannot be dropped.
|
||||
var skipItemIdentifier = false;
|
||||
if (m_NoDropItemDefinitions != null) {
|
||||
for (int j = 0; j < m_NoDropItemDefinitions.Length; ++j) {
|
||||
if (ReferenceEquals(m_Items[0].ItemIdentifier.GetItemDefinition(), m_NoDropItemDefinitions[j])) {
|
||||
skipItemIdentifier = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
canDrop = !skipItemIdentifier && m_Items[0] != null;
|
||||
}
|
||||
|
||||
return canDrop;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
if (base.ShouldBlockAbilityStart(startingAbility)) {
|
||||
return true;
|
||||
}
|
||||
if (startingAbility is Use
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
|| startingAbility is Reload
|
||||
#endif
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the current ability is attempting to start and another ability is active.
|
||||
/// Returns true or false depending on if the active ability should be stopped.
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability that is currently active.</param>
|
||||
/// <returns>True if the ability should be stopped.</returns>
|
||||
public override bool ShouldStopActiveAbility(Ability activeAbility)
|
||||
{
|
||||
if (activeAbility is Use
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
|| activeAbility is Reload
|
||||
#endif
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
var waitForUnequip = false;
|
||||
// The ItemSetManager will be null when the items are managed by Slot ID rather than ItemSet (such as for first person VR).
|
||||
if (m_ItemSetManager != null && m_WaitForUnequip) {
|
||||
for (int i = 0; i < m_Items.Length; ++i) {
|
||||
if (m_Items[i] != null) {
|
||||
for (int j = 0; j < m_EquipUnequipAbilities.Length; ++j) {
|
||||
if (m_ItemSetManager.IsCategoryMember(m_Items[i].ItemIdentifier.GetItemDefinition(), m_EquipUnequipAbilities[j].ItemSetCategoryIndex)) {
|
||||
m_EquipUnequipAbilities[j].StartEquipUnequip(m_ItemSetManager.GetDefaultItemSetIndex(m_EquipUnequipAbilities[j].ItemSetCategoryIndex));
|
||||
waitForUnequip = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!waitForUnequip && !m_DropEvent.WaitForAnimationEvent) {
|
||||
Scheduler.ScheduleFixed(m_DropEvent.Duration, DropItem);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drops the actual item and stops the ability.
|
||||
/// </summary>
|
||||
private void DropItem()
|
||||
{
|
||||
// DropItem may be triggered by the animation event even when the ability isn't active.
|
||||
if (!IsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Drop each item. If a drop prefab is specified then the item will be dropped.
|
||||
for (int i = 0; i < m_Items.Length; ++i) {
|
||||
if (m_Items[i] != null) {
|
||||
m_Inventory.RemoveItem(m_Items[i].ItemIdentifier, m_Items[i].SlotID, 1, true);
|
||||
m_Items[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An item has been unequipped.
|
||||
/// </summary>
|
||||
/// <param name="item">The item that was unequipped.</param>
|
||||
/// <param name="slotID">The slot that the item was unequipped from.</param>
|
||||
private void OnUnequipItem(Item item, int slotID)
|
||||
{
|
||||
if (!IsActive || item != m_Items[slotID]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Once the item has been unequipped it can be removed from the inventory. This will trigger the drop.
|
||||
m_Inventory.RemoveItem(m_Items[slotID].ItemIdentifier, m_Items[slotID].SlotID, 1, true);
|
||||
m_Items[slotID] = null;
|
||||
|
||||
// The ability can be stopped as soon as all items are removed.
|
||||
var stopAbility = true;
|
||||
for (int i = 0; i < m_Items.Length; ++i) {
|
||||
if (m_Items[i] != null) {
|
||||
stopAbility = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (stopAbility) {
|
||||
StopAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorDropItem", DropItem);
|
||||
if (m_ItemSetManager != null) {
|
||||
EventHandler.UnregisterEvent<Item, int>(m_GameObject, "OnAbilityUnequipItemComplete", OnUnequipItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Items/Drop.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Items/Drop.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8422e0e705bb22848ace36ec5324c300
|
||||
timeCreated: 1497448172
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
136
Assets/Scripts/Character/Abilities/Items/EquipNext.cs
Normal file
136
Assets/Scripts/Character/Abilities/Items/EquipNext.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.UltimateCharacterController.Items;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The EquipNext ability will equip the Next ItemSet in the specified category.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultInputName("Equip Next Item")]
|
||||
[AllowDuplicateTypes]
|
||||
public class EquipNext : EquipSwitcher
|
||||
{
|
||||
private int m_PrevItemSetIndex;
|
||||
private int m_ItemSetIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
// The EquipUnequip must exist in order for the item to be able to be equip toggled.
|
||||
if (m_EquipUnequipItemAbility == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventHandler.RegisterEvent<Item, bool>(m_GameObject, "OnNextItemSet", OnNextItemSet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The EquipUnequip ability has changed the active ItemSet.
|
||||
/// </summary>
|
||||
/// <param name="itemSetIndex">The updated active ItemSet index value.</param>
|
||||
protected override void OnItemSetIndexChange(int itemSetIndex)
|
||||
{
|
||||
if (itemSetIndex == -1 ||
|
||||
(m_ItemSetIndex != -1 && itemSetIndex == m_ItemSetManager.GetDefaultItemSetIndex(m_ItemSetCategoryIndex) && !m_ItemSetManager.CategoryItemSets[m_ItemSetCategoryIndex].ItemSetList[itemSetIndex].CanSwitchTo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_PrevItemSetIndex = itemSetIndex;
|
||||
if (m_ItemSetIndex == -1) {
|
||||
m_ItemSetIndex = itemSetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ItemSetIndex = m_ItemSetManager.NextActiveItemSetIndex(m_ItemSetCategoryIndex, m_PrevItemSetIndex, true);
|
||||
|
||||
return m_ItemSetIndex != -1 && m_ItemSetIndex != m_EquipUnequipItemAbility.ActiveItemSetIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
m_EquipUnequipItemAbility.StartEquipUnequip(m_ItemSetIndex);
|
||||
|
||||
// It is up to the EquipUnequip ability to do the actual equip - stop the current ability.
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The next item from the item set should be equipped.
|
||||
/// </summary>
|
||||
/// <param name="item">The item that should be changed.</param>
|
||||
/// <param name="unequipOnFailure">Should the current ItemSet be unequipped if the next ItemSet cannot be activated?</param>
|
||||
private void OnNextItemSet(Item item, bool unequipOnFailure)
|
||||
{
|
||||
var itemIdentifier = item.ItemIdentifier;
|
||||
if (!m_ItemSetManager.IsCategoryMember(itemIdentifier.GetItemDefinition(), m_ItemSetCategoryIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't equip the next item if the current ItemSet doesn't contain the ItemIdentifier.
|
||||
var activeItemIdentifier = m_ItemSetManager.GetEquipItemIdentifier(m_ItemSetCategoryIndex, item.SlotID);
|
||||
if (itemIdentifier != activeItemIdentifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tries to equip the next item. If the ability can't be started then the next item cannot be equipped. If unequip on failure is true
|
||||
// and the next item is invalid then no items should be shown.
|
||||
if (!StartAbility() && unequipOnFailure) {
|
||||
m_EquipUnequipItemAbility.StartEquipUnequip(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has died.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the force.</param>
|
||||
/// <param name="force">The amount of force which killed the character.</param>
|
||||
/// <param name="attacker">The GameObject that killed the character.</param>
|
||||
protected override void OnDeath(Vector3 position, Vector3 force, GameObject attacker)
|
||||
{
|
||||
if (m_Inventory.RemoveAllOnDeath) {
|
||||
m_PrevItemSetIndex = m_ItemSetIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
if (m_EquipUnequipItemAbility == null) {
|
||||
EventHandler.UnregisterEvent<Item, bool>(m_GameObject, "OnNextItemSet", OnNextItemSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Items/EquipNext.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Items/EquipNext.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a352dadf9c65c64d8b7899f0a0873dd
|
||||
timeCreated: 1497448172
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
82
Assets/Scripts/Character/Abilities/Items/EquipPrevious.cs
Normal file
82
Assets/Scripts/Character/Abilities/Items/EquipPrevious.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The EquipPrevious ability will equip the previous ItemSet in the specified category.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultInputName("Equip Previous Item")]
|
||||
[AllowDuplicateTypes]
|
||||
public class EquipPrevious : EquipSwitcher
|
||||
{
|
||||
private int m_PrevItemSetIndex;
|
||||
private int m_ItemSetIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// The EquipUnequip ability has changed the active ItemSet.
|
||||
/// </summary>
|
||||
/// <param name="itemSetIndex">The updated active ItemSet index value.</param>
|
||||
protected override void OnItemSetIndexChange(int itemSetIndex)
|
||||
{
|
||||
if (itemSetIndex == -1 ||
|
||||
(m_ItemSetIndex != -1 && itemSetIndex == m_ItemSetManager.GetDefaultItemSetIndex(m_ItemSetCategoryIndex) && !m_ItemSetManager.CategoryItemSets[m_ItemSetCategoryIndex].ItemSetList[itemSetIndex].CanSwitchTo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_PrevItemSetIndex = itemSetIndex;
|
||||
if (m_ItemSetIndex == -1) {
|
||||
m_ItemSetIndex = itemSetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ItemSetIndex = m_ItemSetManager.NextActiveItemSetIndex(m_ItemSetCategoryIndex, m_PrevItemSetIndex, false);
|
||||
|
||||
return m_ItemSetIndex != -1 && m_ItemSetIndex != m_EquipUnequipItemAbility.ActiveItemSetIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
m_EquipUnequipItemAbility.StartEquipUnequip(m_ItemSetIndex);
|
||||
|
||||
// It is up to the EquipUnequip ability to do the actual equip - stop the current ability.
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has died.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the force.</param>
|
||||
/// <param name="force">The amount of force which killed the character.</param>
|
||||
/// <param name="attacker">The GameObject that killed the character.</param>
|
||||
protected override void OnDeath(Vector3 position, Vector3 force, GameObject attacker)
|
||||
{
|
||||
if (m_Inventory.RemoveAllOnDeath) {
|
||||
m_PrevItemSetIndex = m_ItemSetIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43390113922c6aa48b4f484c4407a5a9
|
||||
timeCreated: 1497448172
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
75
Assets/Scripts/Character/Abilities/Items/EquipScroll.cs
Normal file
75
Assets/Scripts/Character/Abilities/Items/EquipScroll.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The EquipScroll ability will scroll between the previous and next abilities with the scroll wheel.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.Axis)]
|
||||
[DefaultInputName("Mouse ScrollWheel")]
|
||||
[AllowDuplicateTypes]
|
||||
public class EquipScroll : ItemSetAbilityBase
|
||||
{
|
||||
[Tooltip("The sensitivity for switching between items. The higher the value the faster the scroll wheel has to scroll in order to switch items.")]
|
||||
[SerializeField] protected float m_ScrollSensitivity = 0.1f;
|
||||
|
||||
public float ScrollSensitivity { get { return m_ScrollSensitivity; } set { m_ScrollSensitivity = value; } }
|
||||
|
||||
private int m_ScrollItemSetIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
// The EquipUnequip must exist in order for the item to be able to be equip toggled.
|
||||
if (m_EquipUnequipItemAbility == null) {
|
||||
Debug.LogError($"Error: The EquipUnequip ItemAbility with the category ID {m_ItemSetCategoryID} must be added to the character.");
|
||||
Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't scroll items if the scroll wheel value isn't large enough.
|
||||
if (Mathf.Abs(InputAxisValue) < m_ScrollSensitivity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ScrollItemSetIndex = m_ItemSetManager.NextActiveItemSetIndex(m_ItemSetCategoryIndex, m_EquipUnequipItemAbility.ActiveItemSetIndex, InputAxisValue > 0);
|
||||
|
||||
return m_ScrollItemSetIndex != -1 && m_ScrollItemSetIndex != m_EquipUnequipItemAbility.ActiveItemSetIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
m_EquipUnequipItemAbility.StartEquipUnequip(m_ScrollItemSetIndex);
|
||||
|
||||
// It is up to the EquipUnequip ability to do the actual equip - stop the current ability.
|
||||
StopAbility();
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Items/EquipScroll.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Items/EquipScroll.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 726748cf722413d4b9867ac33dc6d87b
|
||||
timeCreated: 1497448172
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
77
Assets/Scripts/Character/Abilities/Items/EquipSwitcher.cs
Normal file
77
Assets/Scripts/Character/Abilities/Items/EquipSwitcher.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The EquipSwitcher is an abstract class implemented by the abilities which change the item set.
|
||||
/// </summary>
|
||||
public abstract class EquipSwitcher : ItemSetAbilityBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
// The EquipUnequip must exist in order for the itemset to be able to be changed.
|
||||
if (m_EquipUnequipItemAbility == null) {
|
||||
Debug.LogError($"Error: The EquipUnequip ItemAbility with the category ID {m_ItemSetCategoryID} must be added to the character.");
|
||||
Enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
EventHandler.RegisterEvent<int>(m_EquipUnequipItemAbility, "OnEquipUnequipItemSetIndexChange", OnItemSetIndexChange);
|
||||
EventHandler.RegisterEvent<int, int>(m_GameObject, "OnItemSetIndexChange", OnItemSetIndexChange);
|
||||
EventHandler.RegisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ItemSetManager has changed the active ItemSet.
|
||||
/// </summary>
|
||||
/// <param name="categoryIndex">The index of the category that changed.</param>
|
||||
/// <param name="itemSetIndex">The updated active ItemSet index value.</param>
|
||||
protected void OnItemSetIndexChange(int categoryIndex, int itemSetIndex)
|
||||
{
|
||||
if (m_ItemSetCategoryIndex != categoryIndex) {
|
||||
return;
|
||||
}
|
||||
OnItemSetIndexChange(itemSetIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The EquipUnequip ability has changed the active ItemSet.
|
||||
/// </summary>
|
||||
/// <param name="itemSetIndex">The updated active ItemSet index value.</param>
|
||||
protected virtual void OnItemSetIndexChange(int itemSetIndex) { }
|
||||
|
||||
/// <summary>
|
||||
/// The character has died.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the force.</param>
|
||||
/// <param name="force">The amount of force which killed the character.</param>
|
||||
/// <param name="attacker">The GameObject that killed the character.</param>
|
||||
protected virtual void OnDeath(Vector3 position, Vector3 force, GameObject attacker) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
if (m_EquipUnequipItemAbility != null) {
|
||||
EventHandler.UnregisterEvent<int>(m_EquipUnequipItemAbility, "OnEquipUnequipItemSetIndexChange", OnItemSetIndexChange);
|
||||
EventHandler.UnregisterEvent<int, int>(m_GameObject, "OnItemSetIndexChange", OnItemSetIndexChange);
|
||||
EventHandler.UnregisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d69b1adaf35366a4dae9ef290d2a98f6
|
||||
timeCreated: 1497448172
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1198
Assets/Scripts/Character/Abilities/Items/EquipUnequip.cs
Normal file
1198
Assets/Scripts/Character/Abilities/Items/EquipUnequip.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71a93dbc059085f48b2c6356c89cb07c
|
||||
timeCreated: 1501959395
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
230
Assets/Scripts/Character/Abilities/Items/InAirMeleeUse.cs
Normal file
230
Assets/Scripts/Character/Abilities/Items/InAirMeleeUse.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.UltimateCharacterController.Items.Actions;
|
||||
using Opsive.UltimateCharacterController.SurfaceSystem;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// ItemAbility which will start using the MeleeWeapon while in the air.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultInputName("Fire1")]
|
||||
[DefaultItemStateIndex(2)]
|
||||
[DefaultState("Use")]
|
||||
[AllowDuplicateTypes]
|
||||
public class InAirMeleeUse : Use
|
||||
{
|
||||
[Tooltip("The amount of force that should be applied at the start of the ability.")]
|
||||
[SerializeField] protected Vector3 m_UpwardForce = new Vector3(0, 1, 0);
|
||||
[Tooltip("The number of frames that the upward force is applied in.")]
|
||||
[SerializeField] protected int m_UpwardForceFrames = 4;
|
||||
[Tooltip("The amount of force that should be applied when the character starts to fall.")]
|
||||
[SerializeField] protected Vector3 m_DownwardForce = new Vector3(0, -15, 0);
|
||||
[Tooltip("The number of frames that the downward force is applied in.")]
|
||||
[SerializeField] protected int m_DownwardForceFrames = 4;
|
||||
[Tooltip("Can the ability stop while the character is still in the air?")]
|
||||
[SerializeField] protected bool m_AllowInAirStop = false;
|
||||
[Tooltip("The value of the ItemSubstateIndex parameter when the character has becomes grounded after performing the use.")]
|
||||
[SerializeField] protected int m_OnGroundedSubstateIndex = 11;
|
||||
[Tooltip("The impact effect that should be played when the character is grounded.")]
|
||||
[SerializeField] protected SurfaceImpact m_GroundImpact;
|
||||
[Tooltip("The amount of positional force to add to the camera when the character is grounded.")]
|
||||
[SerializeField] protected MinMaxVector3 m_GroundPositionCameraRecoil;
|
||||
[Tooltip("The amount of rotational recoil to add to the camera when the character is grounded.")]
|
||||
[SerializeField] protected MinMaxVector3 m_GroundRotationCameraRecoil = new MinMaxVector3(new Vector3(-40f, -40f, 0), new Vector3(40f, 40f, 0), new Vector3(20, 20, 0));
|
||||
|
||||
public Vector3 UpwardForce { get { return m_UpwardForce; } set { m_UpwardForce = value; } }
|
||||
public int UpwardForceFrames { get { return m_UpwardForceFrames; } set { m_UpwardForceFrames = value; } }
|
||||
public Vector3 DownwardForce { get { return m_DownwardForce; } set { m_DownwardForce = value; } }
|
||||
public int DownwardForceFrames { get { return m_DownwardForceFrames; } set { m_DownwardForceFrames = value; } }
|
||||
public bool AllowInAirStop { get { return m_AllowInAirStop; } set { m_AllowInAirStop = value; } }
|
||||
public MinMaxVector3 GroundPositionCameraRecoil { get { return m_GroundPositionCameraRecoil; } set { m_GroundPositionCameraRecoil = value; } }
|
||||
public MinMaxVector3 GroundRotationCameraRecoil { get { return m_GroundRotationCameraRecoil; } set { m_GroundRotationCameraRecoil = value; } }
|
||||
|
||||
private Jump m_JumpAbility;
|
||||
private bool m_AppliedDownwardForce;
|
||||
private bool m_UseOnGroundedSubstate;
|
||||
|
||||
public override bool CanReceiveMultipleStarts { get { return false; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_JumpAbility = m_CharacterLocomotion.GetAbility<Jump>();
|
||||
|
||||
EventHandler.RegisterEvent<bool>(m_GameObject, "OnCharacterGrounded", OnGrounded);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the item be used?
|
||||
/// </summary>
|
||||
/// <returns>True if the item can be used.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// The character needs to be in the air for the ability to start.
|
||||
if (m_CharacterLocomotion.Grounded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The IUsableWeapon must be a MeleeWeapon.
|
||||
var usableMeleeWeapon = false;
|
||||
if (m_SlotID == -1) {
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
if (m_UsableItems[i] is MeleeWeapon) {
|
||||
usableMeleeWeapon = true;
|
||||
} else {
|
||||
m_UsableItems[i] = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (m_UsableItems[0] is MeleeWeapon) {
|
||||
usableMeleeWeapon = true;
|
||||
} else {
|
||||
m_UsableItems[0] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return usableMeleeWeapon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the current ability is attempting to start and another ability is active.
|
||||
/// Returns true or false depending on if the active ability should be stopped.
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability that is currently active.</param>
|
||||
/// <returns>True if the ability should be stopped.</returns>
|
||||
public override bool ShouldStopActiveAbility(Ability activeAbility)
|
||||
{
|
||||
if (base.ShouldStopActiveAbility(activeAbility)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return activeAbility is Jump || activeAbility is Use;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
if (base.ShouldBlockAbilityStart(startingAbility)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return startingAbility is Jump || startingAbility is Use;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
m_CharacterLocomotion.AddRelativeForce(m_UpwardForce, m_UpwardForceFrames);
|
||||
m_AppliedDownwardForce = false;
|
||||
m_UseOnGroundedSubstate = false;
|
||||
// If the character is near the ground the use complete event may occur before use. Prevent the character from getting stuck by
|
||||
// allowing the complete event to occur.
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
ScheduleCompleteEvent(i, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Item Substate Index which corresponds to the slot ID.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot that corresponds to the Item Substate Index.</param>
|
||||
/// <returns>The Item Substate Index which corresponds to the slot ID.</returns>
|
||||
public override int GetItemSubstateIndex(int slotID)
|
||||
{
|
||||
// The OnGrounded Substate Index value should be triggered when the character lands with the melee attack. The ability will then stop when the item use event is sent.
|
||||
if (m_UseOnGroundedSubstate) {
|
||||
if ((m_SlotID == -1 && m_UsableItems[slotID] != null) || (m_SlotID != -1 && m_UsableItems[0] != null)) {
|
||||
return m_OnGroundedSubstateIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return base.GetItemSubstateIndex(slotID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the ability after the controller has updated. This will ensure the character is in the most up to date position.
|
||||
/// </summary>
|
||||
public override void LateUpdate()
|
||||
{
|
||||
base.LateUpdate();
|
||||
|
||||
if (!m_AppliedDownwardForce && m_CharacterLocomotion.LocalVelocity.y < 0) {
|
||||
m_CharacterLocomotion.AddRelativeForce(m_DownwardForce, m_DownwardForceFrames);
|
||||
m_AppliedDownwardForce = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
// The ability can stop when the character is grounded or the character can stop while in the air.
|
||||
if ((m_AllowInAirStop || !m_CharacterLocomotion.Grounded) && (m_JumpAbility == null || !m_JumpAbility.IsActive)) {
|
||||
return false;
|
||||
}
|
||||
return base.CanStopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has changed grounded states.
|
||||
/// </summary>
|
||||
/// <param name="grounded">Is the character on the ground?</param>
|
||||
private void OnGrounded(bool grounded)
|
||||
{
|
||||
if (!IsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (grounded) {
|
||||
SurfaceManager.SpawnEffect(m_CharacterLocomotion.GroundRaycastHit, m_GroundImpact, m_CharacterLocomotion.GravityDirection, m_CharacterLocomotion.TimeScale, m_GameObject);
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnAddSecondaryCameraForce", m_GroundPositionCameraRecoil.RandomValue, m_GroundRotationCameraRecoil.RandomValue, 0f);
|
||||
|
||||
// The item may have completed its use before the object is grounded.
|
||||
if (CanStopAbility()) {
|
||||
StopAbility();
|
||||
} else {
|
||||
m_UseOnGroundedSubstate = true;
|
||||
m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<bool>(m_GameObject, "OnCharacterGrounded", OnGrounded);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2136d2695ea33046bb794d48ce11e09
|
||||
timeCreated: 1497448172
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
89
Assets/Scripts/Character/Abilities/Items/ItemAbility.cs
Normal file
89
Assets/Scripts/Character/Abilities/Items/ItemAbility.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Networking.Character;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract ability class which implements item specific logic for the ability system.
|
||||
/// </summary>
|
||||
public abstract class ItemAbility : Ability
|
||||
{
|
||||
[Tooltip("Specifies the index of the item state within the animator.")]
|
||||
[SerializeField] protected int m_ItemStateIndex = -1;
|
||||
|
||||
public virtual int ItemStateIndex { get { return m_ItemStateIndex; } set { m_ItemStateIndex = value; } }
|
||||
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
protected INetworkCharacter m_NetworkCharacter;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_NetworkCharacter = m_GameObject.GetCachedComponent<INetworkCharacter>();
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Item State Index which corresponds to the slot ID.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot that corresponds to the Item State Index.</param>
|
||||
/// <returns>The Item State Index which corresponds to the slot ID.</returns>
|
||||
public virtual int GetItemStateIndex(int slotID)
|
||||
{
|
||||
return m_ItemStateIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Item Substate Index which corresponds to the slot ID.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot that corresponds to the Item Substate Index.</param>
|
||||
/// <returns>The Item Substate Index which corresponds to the slot ID.</returns>
|
||||
public virtual int GetItemSubstateIndex(int slotID)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
// The StartMovement, QuickTurn, and StopMovement abilities should not be started when an ItemAbility is active.
|
||||
if (startingAbility is StoredInputAbilityBase) {
|
||||
return true;
|
||||
}
|
||||
return base.ShouldBlockAbilityStart(startingAbility);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the current ability is attempting to start and another ability is active.
|
||||
/// Returns true or false depending on if the active ability should be stopped.
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability that is currently active.</param>
|
||||
/// <returns>True if the ability should be stopped.</returns>
|
||||
public override bool ShouldStopActiveAbility(Ability activeAbility)
|
||||
{
|
||||
// The StartMovement, QuickTurn, and StopMovement abilities should not be active when an ItemAbility is active.
|
||||
if (activeAbility is StoredInputAbilityBase) {
|
||||
return true;
|
||||
}
|
||||
return base.ShouldStopActiveAbility(activeAbility);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Items/ItemAbility.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Items/ItemAbility.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb488f41f96c76745a5caf587d43a358
|
||||
timeCreated: 1497457303
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,73 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.Shared.Utility;
|
||||
using Opsive.UltimateCharacterController.Inventory;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The ItemSetAbilityBase ability acts as a base class for common ItemSet operations such as equipping the previous or next item.
|
||||
/// </summary>
|
||||
public abstract class ItemSetAbilityBase : ItemAbility
|
||||
{
|
||||
[Tooltip("The category that the ability should respond to.")]
|
||||
[HideInInspector] [SerializeField] protected uint m_ItemSetCategoryID;
|
||||
public uint ItemSetCategoryID { get { return m_ItemSetCategoryID; } }
|
||||
|
||||
protected EquipUnequip m_EquipUnequipItemAbility;
|
||||
protected ItemSetManagerBase m_ItemSetManager;
|
||||
protected int m_ItemSetCategoryIndex;
|
||||
|
||||
public int ItemSetCategoryIndex { get { return m_ItemSetCategoryIndex; } }
|
||||
|
||||
/// <summary>
|
||||
/// Register for any interested events.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_ItemSetManager = m_GameObject.GetCachedComponent<ItemSetManagerBase>();
|
||||
m_ItemSetManager.Initialize(false);
|
||||
// If the CategoryID is empty then the category hasn't been initialized. Use the first category index.
|
||||
if (RandomID.IsIDEmpty(m_ItemSetCategoryID) && m_ItemSetManager.CategoryItemSets.Length > 0) {
|
||||
m_ItemSetCategoryID = m_ItemSetManager.CategoryItemSets[0].CategoryID;
|
||||
}
|
||||
m_ItemSetCategoryIndex = m_ItemSetManager.CategoryIDToIndex(m_ItemSetCategoryID);
|
||||
|
||||
var equipUnequipAbilities = GetAbilities<EquipUnequip>();
|
||||
if (equipUnequipAbilities != null) {
|
||||
// The ItemSet CategoryID must match for the ToggleEquip ability to be able to use the EquipUnequip ability.
|
||||
for (int i = 0; i < equipUnequipAbilities.Length; ++i) {
|
||||
if (equipUnequipAbilities[i].ItemSetCategoryID == m_ItemSetCategoryID) {
|
||||
m_EquipUnequipItemAbility = equipUnequipAbilities[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// Use and Reload can prevent the ability from equipping or unequipping items.
|
||||
if (m_CharacterLocomotion.IsAbilityTypeActive<Use>()
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
|| m_CharacterLocomotion.IsAbilityTypeActive<Reload>()
|
||||
#endif
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return base.CanStartAbility();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b44b53c489db4746bd1dcd6d695024e
|
||||
timeCreated: 1497448172
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
257
Assets/Scripts/Character/Abilities/Items/MeleeCounterAttack.cs
Normal file
257
Assets/Scripts/Character/Abilities/Items/MeleeCounterAttack.cs
Normal file
@@ -0,0 +1,257 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Items.Actions;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Plays a counter attack in response to an opponent's attack. In order for the counter attack ability to start the character
|
||||
/// must first block the opponent's melee attack.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultInputName("Fire1")]
|
||||
[DefaultItemStateIndex(2)]
|
||||
[DefaultState("Use")]
|
||||
[AllowDuplicateTypes]
|
||||
public class MeleeCounterAttack : Use
|
||||
{
|
||||
[Tooltip("The maximum distance away from the opponent that the counter attack can start.")]
|
||||
[SerializeField] protected float m_AttackDistance = 0.6f;
|
||||
[Tooltip("The counter attack can start if the character blocked an attack within the specified amount of time.")]
|
||||
[SerializeField] protected float m_CounterAttackTimeFrame = 1f;
|
||||
|
||||
private RaycastHit m_RaycastHit;
|
||||
|
||||
private float m_ImpactTime = -1;
|
||||
private MeleeWeapon m_OpponentMeleeWeapon;
|
||||
private UltimateCharacterLocomotion m_OpponentLocomotion;
|
||||
private Use m_OpponentUseAbility;
|
||||
|
||||
public override bool CanReceiveMultipleStarts { get { return false; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
EventHandler.RegisterEvent<ItemAbility, bool>(m_GameObject, "OnCharacterItemAbilityActive", OnItemAbilityActive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the item be used?
|
||||
/// </summary>
|
||||
/// <returns>True if the item can be used.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ImpactTime == -1 || m_ImpactTime + m_CounterAttackTimeFrame < Time.time) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The IUsableWeapon must be a MeleeWeapon.
|
||||
var usableMeleeWeapon = false;
|
||||
if (m_SlotID == -1) {
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
if (m_UsableItems[i] is MeleeWeapon) {
|
||||
usableMeleeWeapon = true;
|
||||
} else {
|
||||
m_UsableItems[i] = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (m_UsableItems[0] is MeleeWeapon) {
|
||||
usableMeleeWeapon = true;
|
||||
} else {
|
||||
m_UsableItems[0] = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!usableMeleeWeapon) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The opponent must be in front of the character.
|
||||
if (!m_CharacterLocomotion.SingleCast(m_Transform.forward * m_AttackDistance, Vector3.zero, m_CharacterLayerManager.EnemyLayers, ref m_RaycastHit) ||
|
||||
m_RaycastHit.collider.gameObject.GetCachedParentComponent<UltimateCharacterLocomotion>() != m_OpponentLocomotion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the current ability is attempting to start and another ability is active.
|
||||
/// Returns true or false depending on if the active ability should be stopped.
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability that is currently active.</param>
|
||||
/// <returns>True if the ability should be stopped.</returns>
|
||||
public override bool ShouldStopActiveAbility(Ability activeAbility)
|
||||
{
|
||||
if (base.ShouldStopActiveAbility(activeAbility)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (activeAbility != this && activeAbility is Use);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
if (base.ShouldBlockAbilityStart(startingAbility)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (startingAbility != this && startingAbility is Use);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
// The opponent should play an animation which responds to the counter attack.
|
||||
var opponentResponseAbility = m_OpponentLocomotion.GetAbility<MeleeCounterAttackResponse>();
|
||||
if (opponentResponseAbility == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var substateIndex = -1;
|
||||
for (int i = 0; i < m_Inventory.SlotCount; ++i) {
|
||||
substateIndex = GetItemSubstateIndex(i);
|
||||
if (substateIndex != -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (substateIndex != -1) {
|
||||
opponentResponseAbility.StartResponse(substateIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Item Substate Index which corresponds to the slot ID.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot that corresponds to the Item Substate Index.</param>
|
||||
/// <returns>The Item Substate Index which corresponds to the slot ID.</returns>
|
||||
public override int GetItemSubstateIndex(int slotID)
|
||||
{
|
||||
var substateIndex = base.GetItemSubstateIndex(slotID);
|
||||
if (substateIndex == -1) {
|
||||
return -1;
|
||||
}
|
||||
return MathUtility.Concatenate(m_OpponentMeleeWeapon.Item.AnimatorItemID, m_OpponentMeleeWeapon.UsedSubstateIndex, substateIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
m_OpponentMeleeWeapon = null;
|
||||
m_ImpactTime = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An ItemAbility has been activated or deactivated.
|
||||
/// </summary>
|
||||
/// <param name="itemAbility">The ItemAbility activated or deactivated.</param>
|
||||
/// <param name="active">Was the ItemAbility activated?</param>
|
||||
private void OnItemAbilityActive(ItemAbility itemAbility, bool active)
|
||||
{
|
||||
if (!active || IsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If another use ability is started or a use is active then the character shouldn't be able to counter attack.
|
||||
if (!(itemAbility is Block) || m_CharacterLocomotion.IsAbilityTypeActive<Use>()) {
|
||||
m_ImpactTime = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// The block ability has been activated. The source of the block must be a melee weapon - counter attack doesn't work against non-melee weapons.
|
||||
var blockAbility = itemAbility as Block;
|
||||
m_OpponentMeleeWeapon = null;
|
||||
for (int i = 0; i < blockAbility.ImpactSources.Length; ++i) {
|
||||
if (blockAbility.ImpactSources[i] == null || !(blockAbility.ImpactSources[i] is MeleeWeapon)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_OpponentMeleeWeapon = blockAbility.ImpactSources[i] as MeleeWeapon;
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_OpponentMeleeWeapon == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The opponent must actively be attacking.
|
||||
m_OpponentLocomotion = m_OpponentMeleeWeapon.CharacterLocomotion;
|
||||
m_OpponentUseAbility = null;
|
||||
var useAbilities = m_OpponentLocomotion.GetAbilities<Use>();
|
||||
if (useAbilities == null || useAbilities.Length == 0) {
|
||||
m_ImpactTime = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < useAbilities.Length; ++i) {
|
||||
if (!useAbilities[i].IsActive) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The ability is active. Ensure it is using a melee weapon.
|
||||
for (int j = 0; j < useAbilities[i].UsableItems.Length; ++j) {
|
||||
var meleeWeapon = useAbilities[i].UsableItems[j] as MeleeWeapon;
|
||||
if (meleeWeapon != m_OpponentMeleeWeapon) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_OpponentUseAbility = useAbilities[i];
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_OpponentUseAbility != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_OpponentUseAbility == null) {
|
||||
m_ImpactTime = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
m_ImpactTime = Time.time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<ItemAbility, bool>(m_GameObject, "OnCharacterItemAbilityActive", OnItemAbilityActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cebf72141915574a8ae7231cd35475d
|
||||
timeCreated: 1497448172
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
641
Assets/Scripts/Character/Abilities/Items/Reload.cs
Normal file
641
Assets/Scripts/Character/Abilities/Items/Reload.cs
Normal file
@@ -0,0 +1,641 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.Shared.Inventory;
|
||||
using Opsive.UltimateCharacterController.Items;
|
||||
using Opsive.UltimateCharacterController.Items.Actions;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// ItemAbility which will reload the item. There are two parts to a reload:
|
||||
/// - The first part will take the reload amount from the inventory and add it to the item.
|
||||
/// - The second part can wait for a small amount of time after the first part to ensure the reload animation is complete before ending the ability.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultInputName("Reload")]
|
||||
[DefaultItemStateIndex(3)]
|
||||
[AllowDuplicateTypes]
|
||||
public class Reload : ItemAbility
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies when the item should automatically be reloaded.
|
||||
/// </summary>
|
||||
public enum AutoReloadType
|
||||
{
|
||||
Pickup = 1, // The item should be reloaded upon pickup for the first time.
|
||||
Empty = 2 // Automatically reload when the item is empty.
|
||||
}
|
||||
|
||||
[Tooltip("The slot that should be reloaded. -1 will use all of the slots.")]
|
||||
[SerializeField] protected int m_SlotID = -1;
|
||||
[Tooltip("The ID of the ItemAction component that can be reloaded.")]
|
||||
[SerializeField] protected int m_ActionID;
|
||||
|
||||
public int SlotID { get { return m_SlotID; }
|
||||
set
|
||||
{
|
||||
if (m_SlotID != value) {
|
||||
UnregisterSlotEvents(m_SlotID);
|
||||
m_SlotID = value;
|
||||
RegisterSlotEvents(m_SlotID);
|
||||
}
|
||||
}
|
||||
}
|
||||
public int ActionID { get { return m_ActionID; } set { m_ActionID = value; } }
|
||||
|
||||
private IReloadableItem[] m_ReloadableItems;
|
||||
private ScheduledEventBase[] m_ReloadEvents;
|
||||
private HashSet<Item> m_InventoryItems = new HashSet<Item>();
|
||||
private HashSet<Item> m_EquippedItems = new HashSet<Item>();
|
||||
private IReloadableItem[] m_CanReloadItems;
|
||||
private bool[] m_Reloaded;
|
||||
|
||||
public IReloadableItem[] ReloadableItems { get { return m_ReloadableItems; } }
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public override string AbilityDescription { get { if (m_SlotID != -1) { return "Slot " + m_SlotID; } return string.Empty; } }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_ReloadableItems = new IReloadableItem[m_SlotID == -1 ? m_Inventory.SlotCount : 1];
|
||||
m_ReloadEvents = new ScheduledEventBase[m_ReloadableItems.Length];
|
||||
m_Reloaded = new bool[m_ReloadableItems.Length];
|
||||
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnItemPickupStartPickup", OnStartPickup);
|
||||
EventHandler.RegisterEvent<IItemIdentifier, int, bool, bool>(m_GameObject, "OnInventoryPickupItemIdentifier", OnPickupItemIdentifier);
|
||||
EventHandler.RegisterEvent<int, IItemIdentifier, bool, bool>(m_GameObject, "OnItemTryReload", OnTryReload);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemReload", OnItemReload);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemReloadComplete", OnItemReloadComplete);
|
||||
// Register for the interested slot events.
|
||||
RegisterSlotEvents(m_SlotID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers for the interested events according to the slot id.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The slot id to register for.</param>
|
||||
private void RegisterSlotEvents(int slotID)
|
||||
{
|
||||
if (slotID == 0) {
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemReloadFirstSlot", OnItemReloadFirstSlot);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemReloadCompleteFirstSlot", OnItemReloadCompleteFirstSlot);
|
||||
} else if (slotID == 1) {
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemReloadSecondSlot", OnItemReloadSecondSlot);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemReloadCompleteSecondSlot", OnItemReloadCompleteSecondSlot);
|
||||
} else if (slotID == 2) {
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemReloadThirdSlot", OnItemReloadThirdSlot);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemReloadCompleteThirdSlot", OnItemReloadCompleteThirdSlot);
|
||||
} else if (slotID != -1) {
|
||||
Debug.LogError("Error: The Reload ability does not listen to slot " + m_SlotID);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters from the interested events according to the slot id.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The slot id to unregister from.</param>
|
||||
private void UnregisterSlotEvents(int slotID)
|
||||
{
|
||||
if (slotID == 0) {
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemReloadFirstSlot", OnItemReloadFirstSlot);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemReloadCompleteFirstSlot", OnItemReloadCompleteFirstSlot);
|
||||
} else if (slotID == 1) {
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemReloadSecondSlot", OnItemReloadSecondSlot);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemReloadCompleteSecondSlot", OnItemReloadCompleteSecondSlot);
|
||||
} else if (slotID == 2) {
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemReloadThirdSlot", OnItemReloadThirdSlot);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemReloadCompleteThirdSlot", OnItemReloadCompleteThirdSlot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the item be reloaded?
|
||||
/// </summary>
|
||||
/// <returns>True if the item can be reloaded.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var canReload = false;
|
||||
// If the SlotID is -1 then the ability should reload every equipped item at the same time. If only one slot has a ReloadableItem then the
|
||||
// ability can start. If the SlotID is not -1 then the ability should reload the item in the specified slot.
|
||||
if (m_SlotID == -1) {
|
||||
for (int i = 0; i < m_ReloadableItems.Length; ++i) {
|
||||
var item = m_Inventory.GetActiveItem(i);
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var itemAction = item.GetItemAction(m_ActionID);
|
||||
if (itemAction == null) {
|
||||
Debug.LogWarning("Warning: The item " + item.name + " must have an ItemAction component attached to it in order to be reloaded.");
|
||||
continue;
|
||||
}
|
||||
|
||||
m_ReloadableItems[i] = itemAction as IReloadableItem;
|
||||
// The item can't be reloaded if it isn't a reloadable item.
|
||||
if (m_ReloadableItems[i] != null && m_ReloadableItems[i].CanReloadItem(true)) {
|
||||
canReload = true;
|
||||
} else {
|
||||
// The ability should not attempt to reload the item if IReloadableItem says that it cannot reload.
|
||||
m_ReloadableItems[i] = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var item = m_Inventory.GetActiveItem(m_SlotID);
|
||||
if (item != null) {
|
||||
var itemAction = item.GetItemAction(m_ActionID);
|
||||
if (itemAction == null) {
|
||||
Debug.LogWarning("Warning: The item " + item.name + " must have an ItemAction component attached to it in order to be used.");
|
||||
} else {
|
||||
m_ReloadableItems[0] = itemAction as IReloadableItem;
|
||||
canReload = m_ReloadableItems[0] != null && m_ReloadableItems[0].CanReloadItem(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return canReload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted(false);
|
||||
|
||||
for (int i = 0; i < m_ReloadableItems.Length; ++i) {
|
||||
if (m_ReloadableItems[i] != null) {
|
||||
m_Reloaded[i] = false;
|
||||
m_ReloadableItems[i].StartItemReload();
|
||||
if (!m_ReloadableItems[i].ReloadEvent.WaitForAnimationEvent) {
|
||||
m_ReloadEvents[i] = Scheduler.ScheduleFixed(m_ReloadableItems[i].ReloadEvent.Duration, ReloadItem, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops reloading the item in the specified slot.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot to stop reloading the item at.</param>
|
||||
public void StopItemReload(int slotID)
|
||||
{
|
||||
if (m_ReloadableItems[slotID] == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_ReloadableItems[slotID].ItemReloadComplete(false, false);
|
||||
m_ReloadableItems[slotID] = null;
|
||||
m_Reloaded[slotID] = false;
|
||||
Scheduler.Cancel(m_ReloadEvents[slotID]);
|
||||
m_ReloadEvents[slotID] = null;
|
||||
m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters();
|
||||
|
||||
// The ability won't be active if CanStartAbility filled in the ReloadableItem but the ability hasn't started yet.
|
||||
if (!IsActive) {
|
||||
return;
|
||||
}
|
||||
// The ability should stop if no more items can be reloaded.
|
||||
var canStop = true;
|
||||
for (int i = 0; i < m_ReloadableItems.Length; ++i) {
|
||||
if (m_ReloadableItems[i] != null) {
|
||||
canStop = false;
|
||||
}
|
||||
}
|
||||
if (canStop) {
|
||||
StopAbility(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
if (base.ShouldBlockAbilityStart(startingAbility)) {
|
||||
return true;
|
||||
}
|
||||
if (startingAbility is Use) {
|
||||
// The ability should be able to be used unless the dominant item state doesn't match. This will prevent a secondary grenade throw
|
||||
// from being started when the primary item is being used. It will not prevent two independent items from being used at the same time.
|
||||
var dominantItem = true;
|
||||
for (int i = 0; i < m_ReloadableItems.Length; ++i) {
|
||||
if (m_ReloadableItems[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m_ReloadableItems[i].Item.DominantItem) {
|
||||
dominantItem = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var useAbility = startingAbility as Use;
|
||||
for (int i = 0; i < useAbility.UsableItems.Length; ++i) {
|
||||
if (useAbility.UsableItems[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dominantItem != useAbility.UsableItems[i].Item.DominantItem) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Equip/Unequip cannot be active at the same time as Reload.
|
||||
return startingAbility is EquipUnequip;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Item State Index which corresponds to the slot ID.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot that corresponds to the Item State Index.</param>
|
||||
/// <returns>The Item State Index which corresponds to the slot ID.</returns>
|
||||
public override int GetItemStateIndex(int slotID)
|
||||
{
|
||||
// Return the ItemStateIndex if the SlotID matches the requested slotID.
|
||||
if (m_SlotID == -1) {
|
||||
if (m_ReloadableItems[slotID] != null) {
|
||||
return m_ItemStateIndex;
|
||||
}
|
||||
} else if (m_SlotID == slotID && m_ReloadableItems[0] != null) {
|
||||
return m_ItemStateIndex;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Item Substate Index which corresponds to the slot ID.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot that corresponds to the Item Substate Index.</param>
|
||||
/// <returns>The Item Substate Index which corresponds to the slot ID.</returns>
|
||||
public override int GetItemSubstateIndex(int slotID)
|
||||
{
|
||||
if (m_SlotID == -1) {
|
||||
if (m_ReloadableItems[slotID] != null) {
|
||||
if (m_Reloaded[slotID]) {
|
||||
return 0;
|
||||
}
|
||||
return m_ReloadableItems[slotID].ReloadAnimatorAudioStateSet.GetItemSubstateIndex();
|
||||
}
|
||||
} else if (m_SlotID == slotID && m_ReloadableItems[0] != null) {
|
||||
if (m_Reloaded[0]) {
|
||||
return 0;
|
||||
}
|
||||
return m_ReloadableItems[0].ReloadAnimatorAudioStateSet.GetItemSubstateIndex();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animation has reloaded all of the items.
|
||||
/// </summary>
|
||||
private void OnItemReload()
|
||||
{
|
||||
for (int i = 0; i < m_ReloadableItems.Length; ++i) {
|
||||
if (m_ReloadableItems[i] != null && !m_Reloaded[i]) {
|
||||
ReloadItem(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animation has reloaded the first item slot.
|
||||
/// </summary>
|
||||
private void OnItemReloadFirstSlot()
|
||||
{
|
||||
ReloadItem(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animation has reloaded the second item slot.
|
||||
/// </summary>
|
||||
private void OnItemReloadSecondSlot()
|
||||
{
|
||||
ReloadItem(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animation has reloaded the third item slot.
|
||||
/// </summary>
|
||||
private void OnItemReloadThirdSlot()
|
||||
{
|
||||
ReloadItem(2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animation has reloaded the item.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The slot that is reloading the item.</param>
|
||||
private void ReloadItem(int slotID)
|
||||
{
|
||||
var reloadableItem = m_ReloadableItems[slotID];
|
||||
if (reloadableItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
reloadableItem.ReloadItem(false);
|
||||
|
||||
// Each reload should update the attribute.
|
||||
if (m_AttributeModifier != null) {
|
||||
m_AttributeModifier.EnableModifier(true);
|
||||
}
|
||||
|
||||
var canReload = true;
|
||||
// An attribute may prevent the reload from being able to continue.
|
||||
if (m_AttributeModifier != null && !m_AttributeModifier.IsValid()) {
|
||||
canReload = false;
|
||||
}
|
||||
|
||||
|
||||
// The item may need to be reloaded again if the reload type is single and the inventory still has ammo.
|
||||
if (canReload && reloadableItem.CanReloadItem(true)) {
|
||||
m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters();
|
||||
if (!reloadableItem.ReloadEvent.WaitForAnimationEvent) {
|
||||
m_ReloadEvents[slotID] = Scheduler.ScheduleFixed(reloadableItem.ReloadEvent.Duration, ReloadItem, slotID);
|
||||
}
|
||||
} else {
|
||||
m_Reloaded[slotID] = true;
|
||||
m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters();
|
||||
// The reload ability isn't done until the ReloadItemComplete method is called.
|
||||
if (!reloadableItem.ReloadCompleteEvent.WaitForAnimationEvent) {
|
||||
m_ReloadEvents[slotID] = Scheduler.ScheduleFixed(reloadableItem.ReloadCompleteEvent.Duration, ReloadItemComplete, slotID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The reload animation has completed for all of the items.
|
||||
/// </summary>
|
||||
private void OnItemReloadComplete()
|
||||
{
|
||||
for (int i = 0; i < m_ReloadableItems.Length; ++i) {
|
||||
if (m_ReloadableItems[i] != null) {
|
||||
ReloadItemComplete(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The reload animation has completed for the first item slot.
|
||||
/// </summary>
|
||||
private void OnItemReloadCompleteFirstSlot()
|
||||
{
|
||||
ReloadItemComplete(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The reload animation has completed for the second item slot.
|
||||
/// </summary>
|
||||
private void OnItemReloadCompleteSecondSlot()
|
||||
{
|
||||
ReloadItemComplete(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The reload animation has completed for the third item slot.
|
||||
/// </summary>
|
||||
private void OnItemReloadCompleteThirdSlot()
|
||||
{
|
||||
ReloadItemComplete(2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animator has finished playing the reload animation.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The slot that is reloading the item.</param>
|
||||
private void ReloadItemComplete(int slotID)
|
||||
{
|
||||
var reloadableItem = m_ReloadableItems[slotID];
|
||||
if (reloadableItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_ReloadableItems[slotID].ItemReloadComplete(true, false);
|
||||
m_ReloadableItems[slotID] = null;
|
||||
if (m_ReloadEvents[slotID] != null) {
|
||||
Scheduler.Cancel(m_ReloadEvents[slotID]);
|
||||
m_ReloadEvents[slotID] = null;
|
||||
}
|
||||
|
||||
// Don't stop the ability unless all slots have been reloaded.
|
||||
var stopAbility = true;
|
||||
for (int i = 0; i < m_ReloadableItems.Length; ++i) {
|
||||
if (m_ReloadableItems[i] != null) {
|
||||
stopAbility = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stopAbility) {
|
||||
StopAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ItemPickup component is starting to pick up ItemIdentifier.
|
||||
/// </summary>
|
||||
private void OnStartPickup()
|
||||
{
|
||||
// Remember the initial item inventory list to be able to determine if an item has been added.
|
||||
m_InventoryItems.Clear();
|
||||
var allItems = m_Inventory.GetAllItems();
|
||||
for (int i = 0; i < allItems.Count; ++i) {
|
||||
if (m_Inventory.GetItemIdentifierAmount(allItems[i].ItemIdentifier) == 0) {
|
||||
continue;
|
||||
}
|
||||
m_InventoryItems.Add(allItems[i]);
|
||||
}
|
||||
m_EquippedItems.Clear();
|
||||
Item item;
|
||||
for (int i = 0; i < m_Inventory.SlotCount; ++i) {
|
||||
if ((item = m_Inventory.GetActiveItem(i)) != null) {
|
||||
m_EquippedItems.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An ItemIdentifier has been picked up within the inventory.
|
||||
/// </summary>
|
||||
/// <param name="itemIdentifier">The ItemIdentifier that has been equipped.</param>
|
||||
/// <param name="amount">The amount of ItemIdentifier picked up.</param>
|
||||
/// <param name="immediatePickup">Was the item be picked up immediately?</param>
|
||||
/// <param name="forceEquip">Should the item be force equipped?</param>
|
||||
private void OnPickupItemIdentifier(IItemIdentifier itemIdentifier, int amount, bool immediatePickup, bool forceEquip)
|
||||
{
|
||||
// Determine if the equipped item should be reloaded.
|
||||
OnTryReload(-1, itemIdentifier, immediatePickup, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries the reload the item with the specified ItemIdentifier.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The SlotID of the item trying to reload.</param>
|
||||
/// <param name="itemIdentifier">The ItemIdentifier which should be reloaded.</param>
|
||||
/// <param name="immediateReload">Should the item be reloaded immediately?</param>
|
||||
/// <param name="equipCheck">Should the equipped items be checked.</param>
|
||||
private void OnTryReload(int slotID, IItemIdentifier itemIdentifier, bool immediateReload, bool equipCheck)
|
||||
{
|
||||
if (m_SlotID != -1 && slotID != -1 && m_SlotID != slotID) {
|
||||
return;
|
||||
}
|
||||
|
||||
var allItems = m_Inventory.GetAllItems();
|
||||
var canReloadCount = 0;
|
||||
for (int i = 0; i < allItems.Count; ++i) {
|
||||
var item = allItems[i];
|
||||
if (slotID != -1 && item.SlotID != slotID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IReloadableItem reloadableItem;
|
||||
if ((reloadableItem = ShouldReload(item, itemIdentifier, slotID == -1)) != null) { // -1 indicates that the item is being picked up.
|
||||
if (m_CanReloadItems == null || m_CanReloadItems.Length == canReloadCount) {
|
||||
System.Array.Resize(ref m_CanReloadItems, canReloadCount + 1);
|
||||
}
|
||||
m_CanReloadItems[canReloadCount] = reloadableItem;
|
||||
canReloadCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (canReloadCount > 0) {
|
||||
var startAbility = false;
|
||||
for (int i = 0; i < canReloadCount; ++i) {
|
||||
var reloadableItem = m_CanReloadItems[i];
|
||||
// The item should automatically be reloaded if:
|
||||
// - The item is being reloaded automatically.
|
||||
// - The item isn't currently equipped. Non-equipped items don't need to play an animation.
|
||||
if (immediateReload || (equipCheck && !m_EquippedItems.Contains(reloadableItem.Item))) {
|
||||
reloadableItem.ReloadItem(true);
|
||||
reloadableItem.ItemReloadComplete(true, immediateReload);
|
||||
} else {
|
||||
startAbility = true;
|
||||
if (m_SlotID == -1) {
|
||||
m_ReloadableItems[reloadableItem.Item.SlotID] = reloadableItem;
|
||||
} else {
|
||||
m_ReloadableItems[0] = reloadableItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (startAbility) {
|
||||
StartAbility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should the item be reloaded? An IReloadableItem reference will be returned if the item can be reloaded.
|
||||
/// </summary>
|
||||
/// <param name="item">The item which may need to be reloaded.</param>
|
||||
/// <param name="itemIdentifier">The ItemIdentifier that is being reloaded.</param>
|
||||
/// <param name="fromPickup">Is the item being reloaded from a pickup?</param>
|
||||
/// <returns>A reference to the IReloadableItem if the item can be reloaded. Null if the item cannot be reloaded.</returns>
|
||||
private IReloadableItem ShouldReload(Item item, IItemIdentifier itemIdentifier, bool fromPickup)
|
||||
{
|
||||
var itemAction = item.GetItemAction(m_ActionID);
|
||||
|
||||
// Don't reload if the item isn't a IReloadableItem.
|
||||
var reloadableItem = itemAction as IReloadableItem;
|
||||
if (reloadableItem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Don't reload if the ItemIdentifier doesn't match.
|
||||
if (reloadableItem.GetReloadableItemIdentifier() != itemIdentifier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var autoReload = false;
|
||||
if ((reloadableItem.AutoReload & AutoReloadType.Empty) != 0 && (reloadableItem is IUsableItem && (reloadableItem as IUsableItem).GetConsumableItemIdentifierAmount() == 0)) {
|
||||
// The item is empty.
|
||||
autoReload = true;
|
||||
} else if ((reloadableItem.AutoReload & AutoReloadType.Pickup) != 0 && fromPickup) {
|
||||
// The item was just picked up for the first time.
|
||||
autoReload = true;
|
||||
}
|
||||
|
||||
// Don't automatically reload if the item says that it shouldn't.
|
||||
if (!autoReload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Don't reload if the reloadable item can't reload.
|
||||
if (!reloadableItem.CanReloadItem(true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Reload.
|
||||
return reloadableItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the camera zoom while the ability is active?
|
||||
/// </summary>
|
||||
/// <returns>True if the camera can zoom while the ability is active.</returns>
|
||||
public override bool CanCameraZoom()
|
||||
{
|
||||
for (int i = 0; i < m_ReloadableItems.Length; ++i) {
|
||||
if (m_ReloadableItems[i] != null && !m_ReloadableItems[i].CanCameraZoom) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
// Ensure the arrays are set to null for the next run.
|
||||
for (int i = 0; i < m_ReloadableItems.Length; ++i) {
|
||||
if (m_ReloadableItems[i] != null) {
|
||||
m_ReloadableItems[i].ItemReloadComplete(!force, force);
|
||||
m_ReloadableItems[i] = null;
|
||||
Scheduler.Cancel(m_ReloadEvents[i]);
|
||||
m_ReloadEvents[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnItemPickupStartPickup", OnStartPickup);
|
||||
EventHandler.UnregisterEvent<IItemIdentifier, int, bool, bool>(m_GameObject, "OnInventoryPickupItemIdentifier", OnPickupItemIdentifier);
|
||||
EventHandler.UnregisterEvent<int, IItemIdentifier, bool, bool>(m_GameObject, "OnItemTryReload", OnTryReload);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemReload", OnItemReload);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemReloadComplete", OnItemReloadComplete);
|
||||
UnregisterSlotEvents(m_SlotID);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Items/Reload.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Items/Reload.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dcff85271faa9149b1c91d9c117aba7
|
||||
timeCreated: 1497448172
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
112
Assets/Scripts/Character/Abilities/Items/ToggleEquip.cs
Normal file
112
Assets/Scripts/Character/Abilities/Items/ToggleEquip.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The ToggleEquip ability will equip or unequip the current ItemSet. ToggleEquip just specifies which ItemSet should be equipped/unequipped and then will let
|
||||
/// the EquipUnequip ability to do the actual equip/unequip.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultInputName("Toggle Item Equip")]
|
||||
[AllowDuplicateTypes]
|
||||
public class ToggleEquip : EquipSwitcher
|
||||
{
|
||||
[Tooltip("Should the default ItemSet be toggled upon start?")]
|
||||
[SerializeField] protected bool m_ToggleDefaultItemSetOnStart;
|
||||
|
||||
public bool ToggleDefaultItemSetOnStart { get { return m_ToggleDefaultItemSetOnStart; } set { m_ToggleDefaultItemSetOnStart = value; } }
|
||||
|
||||
private int m_PrevItemSetIndex = -1;
|
||||
private bool m_ShouldEquipItem = true;
|
||||
|
||||
/// <summary>
|
||||
/// Start the ability if the default ItemSet should be equipped.
|
||||
/// </summary>
|
||||
public override void Start()
|
||||
{
|
||||
if (m_ToggleDefaultItemSetOnStart) {
|
||||
var itemSetIndex = m_ItemSetManager.ActiveItemSetIndex[m_ItemSetCategoryIndex];
|
||||
if (itemSetIndex == -1) {
|
||||
m_ShouldEquipItem = false;
|
||||
StartAbility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The EquipUnequip ability has changed the active ItemSet. Store this value so ToggleEquip knows which ItemSet to equip after the unequip.
|
||||
/// </summary>
|
||||
/// <param name="itemSetIndex">The updated active ItemSet index value.</param>
|
||||
protected override void OnItemSetIndexChange(int itemSetIndex)
|
||||
{
|
||||
if (!Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var defaultItemSetIndex = m_ItemSetManager.GetDefaultItemSetIndex(m_ItemSetCategoryIndex);
|
||||
m_ShouldEquipItem = itemSetIndex == defaultItemSetIndex;
|
||||
if (itemSetIndex == defaultItemSetIndex) {
|
||||
// The previous ItemSet may have been removed.
|
||||
if (!m_ItemSetManager.IsItemSetValid(m_ItemSetCategoryIndex, m_PrevItemSetIndex, false)) {
|
||||
m_PrevItemSetIndex = -1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_PrevItemSetIndex = itemSetIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// PrevItemSetIndex will equal -1 if no non-default items have been equipped.
|
||||
if (m_PrevItemSetIndex == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_PrevItemSetIndex != m_ItemSetManager.GetDefaultItemSetIndex(m_ItemSetCategoryIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
// Start the EquipUnequip ability and then stop the ability. The EquipUnequip ability will do the actual work of equipping or unequipping the items.
|
||||
var defaultItemSetIndex = m_ItemSetManager.GetDefaultItemSetIndex(m_ItemSetCategoryIndex);
|
||||
var itemSetIndex = m_ShouldEquipItem ? m_PrevItemSetIndex : defaultItemSetIndex;
|
||||
m_EquipUnequipItemAbility.StartEquipUnequip(itemSetIndex, false, false);
|
||||
m_ShouldEquipItem = itemSetIndex == defaultItemSetIndex;
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has died.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the force.</param>
|
||||
/// <param name="force">The amount of force which killed the character.</param>
|
||||
/// <param name="attacker">The GameObject that killed the character.</param>
|
||||
protected override void OnDeath(Vector3 position, Vector3 force, GameObject attacker)
|
||||
{
|
||||
if (m_Inventory.RemoveAllOnDeath) {
|
||||
m_PrevItemSetIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Items/ToggleEquip.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Items/ToggleEquip.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8c895f823d309f49be49f4ad5698c14
|
||||
timeCreated: 1497448172
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
972
Assets/Scripts/Character/Abilities/Items/Use.cs
Normal file
972
Assets/Scripts/Character/Abilities/Items/Use.cs
Normal file
@@ -0,0 +1,972 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities.Items
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Input;
|
||||
using Opsive.UltimateCharacterController.Items;
|
||||
using Opsive.UltimateCharacterController.Items.Actions;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// ItemAbility which will start using the IUsableItem.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultStopType(AbilityStopType.ButtonUp)]
|
||||
[DefaultInputName("Fire1")]
|
||||
[DefaultItemStateIndex(2)]
|
||||
[DefaultState("Use")]
|
||||
[AllowDuplicateTypes]
|
||||
public class Use : ItemAbility
|
||||
{
|
||||
[Tooltip("The slot that should be used. -1 will use all of the slots.")]
|
||||
[SerializeField] protected int m_SlotID = -1;
|
||||
[Tooltip("The ID of the ItemAction component that can be used.")]
|
||||
[SerializeField] protected int m_ActionID;
|
||||
[Tooltip("Should the ability rotate the character to face the look source target?")]
|
||||
[SerializeField] protected bool m_RotateTowardsLookSourceTarget = true;
|
||||
|
||||
public int SlotID { get { return m_SlotID; }
|
||||
set
|
||||
{
|
||||
if (m_SlotID != value) {
|
||||
UnregisterSlotEvents(m_SlotID);
|
||||
m_SlotID = value;
|
||||
RegisterSlotEvents(m_SlotID);
|
||||
}
|
||||
}
|
||||
}
|
||||
public int ActionID { get { return m_ActionID; } set { m_ActionID = value; } }
|
||||
public bool RotateTowardsLookSourceTarget { get { return m_RotateTowardsLookSourceTarget; } set { m_RotateTowardsLookSourceTarget = value; } }
|
||||
|
||||
private ILookSource m_LookSource;
|
||||
protected IUsableItem[] m_UsableItems;
|
||||
private PlayerInput m_PlayerInput;
|
||||
private bool[] m_WaitForUseEvent;
|
||||
private bool[] m_CanStopAbility;
|
||||
private bool[] m_WaitForUseCompleteEvent;
|
||||
private bool[] m_UseCompleted;
|
||||
private Item m_FaceTargetItem;
|
||||
private ScheduledEventBase[] m_UseEvent;
|
||||
private ScheduledEventBase[] m_CanStopEvent;
|
||||
private bool m_Started;
|
||||
|
||||
public IUsableItem[] UsableItems { get { return m_UsableItems; } }
|
||||
public Item FaceTargetItem { get { return m_FaceTargetItem; } }
|
||||
|
||||
public override bool CanReceiveMultipleStarts { get { return true; } }
|
||||
#if UNITY_EDITOR
|
||||
public override string AbilityDescription {
|
||||
get {
|
||||
var description = string.Empty;
|
||||
if (m_SlotID != -1) {
|
||||
description += "Slot " + m_SlotID;
|
||||
}
|
||||
if (m_ActionID != 0) {
|
||||
if (!string.IsNullOrEmpty(description)) {
|
||||
description += ", ";
|
||||
}
|
||||
description += "Action " + m_ActionID;
|
||||
}
|
||||
return description;
|
||||
} }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_PlayerInput = m_GameObject.GetCachedComponent<PlayerInput>();
|
||||
var count = m_SlotID == -1 ? m_Inventory.SlotCount : 1;
|
||||
m_UsableItems = new IUsableItem[count];
|
||||
m_WaitForUseEvent = new bool[count];
|
||||
m_CanStopAbility = new bool[count];
|
||||
m_WaitForUseCompleteEvent = new bool[count];
|
||||
m_UseCompleted = new bool[count];
|
||||
m_UseEvent = new ScheduledEventBase[count];
|
||||
m_CanStopEvent = new ScheduledEventBase[count];
|
||||
for (int i = 0; i < count; ++i) {
|
||||
m_WaitForUseEvent[i] = false;
|
||||
m_UseCompleted[i] = true;
|
||||
}
|
||||
// The look source may have already been assigned if the ability was added to the character after the look source was assigned.
|
||||
m_LookSource = m_CharacterLocomotion.LookSource;
|
||||
|
||||
EventHandler.RegisterEvent<ILookSource>(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
|
||||
EventHandler.RegisterEvent<bool>(m_GameObject, "OnEnableGameplayInput", OnEnableGameplayInput);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUse", OnItemUse);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseComplete", OnItemUseComplete);
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
EventHandler.RegisterEvent<ItemAbility, bool>(m_GameObject, "OnCharacterItemAbilityActive", OnItemAbilityActive);
|
||||
#endif
|
||||
// Register for the interested slot events.
|
||||
RegisterSlotEvents(m_SlotID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers for the interested events according to the slot id.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The slot id to register for.</param>
|
||||
private void RegisterSlotEvents(int slotID)
|
||||
{
|
||||
if (!Application.isPlaying) {
|
||||
return;
|
||||
}
|
||||
if (slotID == 0) {
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseFirstSlot", OnItemUseFirstSlot);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseCompleteFirstSlot", OnItemUseCompleteFirstSlot);
|
||||
} else if (slotID == 1) {
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseSecondSlot", OnItemUseSecondSlot);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseCompleteSecondSlot", OnItemUseCompleteSecondSlot);
|
||||
} else if (slotID == 2) {
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseThirdSlot", OnItemUseThirdSlot);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseCompleteThirdSlot", OnItemUseCompleteThirdSlot);
|
||||
} else if (slotID != -1) {
|
||||
Debug.LogError("Error: The Use ability does not listen to slot " + m_SlotID);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters from the interested events according to the slot id.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The slot id to unregister from.</param>
|
||||
private void UnregisterSlotEvents(int slotID)
|
||||
{
|
||||
if (!Application.isPlaying) {
|
||||
return;
|
||||
}
|
||||
if (slotID == 0) {
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseFirstSlot", OnItemUseFirstSlot);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseCompleteFirstSlot", OnItemUseCompleteFirstSlot);
|
||||
} else if (slotID == 1) {
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseSecondSlot", OnItemUseSecondSlot);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseCompleteSecondSlot", OnItemUseCompleteSecondSlot);
|
||||
} else if (slotID == 2) {
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseThirdSlot", OnItemUseThirdSlot);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseCompleteThirdSlot", OnItemUseCompleteThirdSlot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new ILookSource object has been attached to the character.
|
||||
/// </summary>
|
||||
/// <param name="lookSource">The ILookSource object attached to the character.</param>
|
||||
private void OnAttachLookSource(ILookSource lookSource)
|
||||
{
|
||||
m_LookSource = lookSource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Item State Index which corresponds to the slot ID.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot that corresponds to the Item State Index.</param>
|
||||
/// <returns>The Item State Index which corresponds to the slot ID.</returns>
|
||||
public override int GetItemStateIndex(int slotID)
|
||||
{
|
||||
// Return the ItemStateIndex if the SlotID matches the requested slotID.
|
||||
if (m_SlotID == -1) {
|
||||
if (m_UsableItems[slotID] != null) {
|
||||
return m_ItemStateIndex;
|
||||
}
|
||||
} else if (m_SlotID == slotID && m_UsableItems[0] != null) {
|
||||
return m_ItemStateIndex;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Item Substate Index which corresponds to the slot ID.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot that corresponds to the Item Substate Index.</param>
|
||||
/// <returns>The Item Substate Index which corresponds to the slot ID.</returns>
|
||||
public override int GetItemSubstateIndex(int slotID)
|
||||
{
|
||||
if (m_SlotID == -1) {
|
||||
if (m_UsableItems[slotID] != null) {
|
||||
return m_UsableItems[slotID].GetItemSubstateIndex();
|
||||
}
|
||||
} else if (m_SlotID == slotID && m_UsableItems[0] != null) {
|
||||
return m_UsableItems[0].GetItemSubstateIndex();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the item be used?
|
||||
/// </summary>
|
||||
/// <returns>True if the item can be used.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't use the item if the cursor is over any UI.
|
||||
if (m_PlayerInput != null && m_PlayerInput.IsPointerOverUI()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// A look source must exist.
|
||||
if (m_LookSource == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the SlotID is -1 then the ability should use every equipped item at the same time. If only one slot has a UsableItem then the
|
||||
// ability can start. If the SlotID is not -1 then the ability should use the item in the specified slot.
|
||||
var canUse = false;
|
||||
if (m_SlotID == -1) {
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
var item = m_Inventory.GetActiveItem(i);
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var itemAction = item.GetItemAction(m_ActionID);
|
||||
if (itemAction == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_UsableItems[i] = itemAction as IUsableItem;
|
||||
|
||||
// The item can't be used if it isn't a usable item.
|
||||
if (m_UsableItems[i] != null) {
|
||||
if (m_UseCompleted[i] && !m_CanStopAbility[i] && m_UsableItems[i].IsItemInUse() && m_UsableItems[i].CanStopItemUse()) {
|
||||
m_UsableItems[i].StopItemUse();
|
||||
}
|
||||
|
||||
if (!m_UsableItems[i].CanUseItem(this, UsableItem.UseAbilityState.Start)) {
|
||||
continue;
|
||||
}
|
||||
canUse = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var item = m_Inventory.GetActiveItem(m_SlotID);
|
||||
if (item != null) {
|
||||
var itemAction = item.GetItemAction(m_ActionID);
|
||||
if (itemAction != null) {
|
||||
m_UsableItems[0] = itemAction as IUsableItem;
|
||||
|
||||
// The item can't be used if it isn't a usable item.
|
||||
if (m_UsableItems[0] != null) {
|
||||
// If the item has completed use and is waiting on the CanStop event then it should reset so it can be used again.
|
||||
if (m_UseCompleted[0] && !m_CanStopAbility[0] && m_UsableItems[0].IsItemInUse() && m_UsableItems[0].CanStopItemUse()) {
|
||||
m_UsableItems[0].StopItemUse();
|
||||
}
|
||||
if (m_UsableItems[0].CanUseItem(this, UsableItem.UseAbilityState.Start)) {
|
||||
canUse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return canUse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the ability use the specified ItemAction type?
|
||||
/// </summary>
|
||||
/// <param name="itemActionType">The ItemAction type to compare against.</param>
|
||||
/// <returns>True if the ability uses the specified ItemAction type.</returns>
|
||||
public bool UsesItemActionType(System.Type itemActionType)
|
||||
{
|
||||
// If the SlotID is -1 then the ability should can every equipped item at the same time. If only one slot has an action which is of the specified type
|
||||
// then the entire method will return true. If the SlotID is not -1 then the ability will only check against the single ItemAction.
|
||||
if (m_SlotID == -1) {
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
var item = m_Inventory.GetActiveItem(i);
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var itemAction = item.GetItemAction(m_ActionID);
|
||||
if (itemAction == null) {
|
||||
return false;
|
||||
}
|
||||
// It only takes one ItemAction for the ability to use the specified ItemAction.
|
||||
if (itemAction.GetType().IsAssignableFrom(itemActionType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var item = m_Inventory.GetActiveItem(m_SlotID);
|
||||
if (item != null) {
|
||||
var itemAction = item.GetItemAction(m_ActionID);
|
||||
if (itemAction == null) {
|
||||
return false;
|
||||
}
|
||||
return itemAction.GetType().IsAssignableFrom(itemActionType);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
if (base.ShouldBlockAbilityStart(startingAbility)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (startingAbility is Use && startingAbility != this) {
|
||||
// The same item should not be able to be used by multiple use abilities at the same time. Different items can be used at the same time, such as
|
||||
// a primary item and a secondary grenade throw or dual pistols.
|
||||
var startingUseAbility = startingAbility as Use;
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
if (m_UsableItems[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < startingUseAbility.UsableItems.Length; ++j) {
|
||||
if (startingUseAbility.UsableItems[j] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_UsableItems[i].Item == startingUseAbility.UsableItems[j].Item) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
if (startingAbility is Reload) {
|
||||
// The Use ability has priority over the Reload ability. Prevent the reload ability from starting if the use ability is active.
|
||||
if (startingAbility.InputIndex != -1) {
|
||||
// If the item isn't actively being used then it shouldn't block reload.
|
||||
var shouldBlock = false;
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
if (m_UsableItems[i] != null && !m_UseCompleted[i]) {
|
||||
shouldBlock = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!shouldBlock) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var reloadAbility = startingAbility as Reload;
|
||||
StopItemReload(reloadAbility);
|
||||
|
||||
// The ability should only be blocked if there aren't any items left to reload. An item may still be reloaded if it's parented to a different
|
||||
// slot from what is being used.
|
||||
shouldBlock = true;
|
||||
for (int i = 0; i < reloadAbility.ReloadableItems.Length; ++i) {
|
||||
if (reloadAbility.ReloadableItems[i] != null) {
|
||||
shouldBlock = false;
|
||||
}
|
||||
}
|
||||
return shouldBlock;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Active items can block starting abilities.
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
if (m_UsableItems[i] == null) {
|
||||
continue;
|
||||
}
|
||||
if (!m_UsableItems[i].CanStartAbility(startingAbility)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the current ability is attempting to start and another ability is active.
|
||||
/// Returns true or false depending on if the active ability should be stopped.
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability that is currently active.</param>
|
||||
/// <returns>True if the ability should be stopped.</returns>
|
||||
public override bool ShouldStopActiveAbility(Ability activeAbility)
|
||||
{
|
||||
// If Use starts while EquipUnequip is active then EquipUnequip should stop.
|
||||
if (activeAbility is EquipUnequip) {
|
||||
return true;
|
||||
}
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
if (activeAbility is Reload) {
|
||||
// The Use ability has priority over the Reload ability. Stop Reload if it is currently reloading the item.
|
||||
StopItemReload(activeAbility as Reload);
|
||||
}
|
||||
#endif
|
||||
return base.ShouldStopActiveAbility(activeAbility);
|
||||
}
|
||||
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
/// <summary>
|
||||
/// Stops any item that is trying to reload while it is being used.
|
||||
/// </summary>
|
||||
/// <param name="reloadAbility">A reference to the reload ability.</param>
|
||||
/// <returns>True if the same item is trying to be used and reloaded.</returns>
|
||||
private void StopItemReload(Reload reloadAbility)
|
||||
{
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
if (m_UsableItems[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < reloadAbility.ReloadableItems.Length; ++j) {
|
||||
if (reloadAbility.ReloadableItems[j] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_UsableItems[i].Item == reloadAbility.ReloadableItems[j].Item) {
|
||||
reloadAbility.StopItemReload(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
// Shootable weapons will deduct the attribute on each use.
|
||||
var enableAttributeModifier = true;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
if (enableAttributeModifier && m_UsableItems[i] != null && m_UsableItems[i] is ShootableWeapon) {
|
||||
enableAttributeModifier = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
base.AbilityStarted(enableAttributeModifier);
|
||||
|
||||
// The item may require root motion to prevent sliding. It may also require the character to face the target before it can actually be used.
|
||||
m_FaceTargetItem = null;
|
||||
var itemStartedUse = false;
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
if (m_UsableItems[i] == null) {
|
||||
m_CanStopAbility[i] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
m_UsableItems[i].StartItemUse(this);
|
||||
// An Animator Audio State Set may prevent the item from being used.
|
||||
if (!m_UsableItems[i].IsItemInUse()) {
|
||||
m_CanStopAbility[i] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
itemStartedUse = true;
|
||||
m_WaitForUseEvent[i] = true;
|
||||
m_WaitForUseCompleteEvent[i] = false;
|
||||
m_UseCompleted[i] = false;
|
||||
ResetCanStopEvent(i);
|
||||
|
||||
if (m_UsableItems[i].ForceRootMotionPosition) {
|
||||
m_CharacterLocomotion.ForceRootMotionPosition = true;
|
||||
}
|
||||
if (m_UsableItems[i].ForceRootMotionRotation) {
|
||||
m_CharacterLocomotion.ForceRootMotionRotation = true;
|
||||
}
|
||||
if (m_UsableItems[i].FaceTarget && !m_CharacterLocomotion.ActiveMovementType.UseIndependentLook(true)) {
|
||||
m_FaceTargetItem = m_UsableItems[i].Item;
|
||||
}
|
||||
ScheduleUseEvent(i);
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnItemStartUse", m_UsableItems[i], true);
|
||||
}
|
||||
|
||||
// The ability can start multiple times. Ensure the events are only subscribed to once.
|
||||
if (itemStartedUse && !m_Started) {
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnUseAbilityStart", true, this);
|
||||
m_Started = true;
|
||||
} else if (!itemStartedUse) {
|
||||
// The ability should be stopped if no items are being used.
|
||||
var stopAbility = true;
|
||||
for (int i = 0; i < m_UseCompleted.Length; ++i) {
|
||||
if (m_UsableItems[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m_UseCompleted[i] || !m_CanStopAbility[i]) {
|
||||
stopAbility = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stopAbility) {
|
||||
StopAbility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the CanStop event back to its default value.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The id of the slot that should be reset.</param>
|
||||
private void ResetCanStopEvent(int slotID)
|
||||
{
|
||||
// Melee weapons will not have a stop use delay so should not reset the event.
|
||||
if (m_UsableItems[slotID] != null && m_UsableItems[slotID].StopUseAbilityDelay == 0) {
|
||||
m_CanStopAbility[slotID] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
m_CanStopAbility[slotID] = m_StopType == AbilityStopType.Manual;
|
||||
if (m_CanStopEvent[slotID] != null) {
|
||||
Scheduler.Cancel(m_CanStopEvent[slotID]);
|
||||
m_CanStopEvent[slotID] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules the use event.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The id of the slot that should be scheduled.</param>
|
||||
private void ScheduleUseEvent(int slotID)
|
||||
{
|
||||
if (m_UseEvent[slotID] != null) {
|
||||
Scheduler.Cancel(m_UseEvent[slotID]);
|
||||
}
|
||||
|
||||
if (!m_UsableItems[slotID].UseEvent.WaitForAnimationEvent) {
|
||||
m_UseEvent[slotID] = Scheduler.ScheduleFixed(m_UsableItems[slotID].UseEvent.Duration, UseItem, slotID);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animation has used all of the items.
|
||||
/// </summary>
|
||||
private void OnItemUse()
|
||||
{
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
if (m_UsableItems[i] != null) {
|
||||
UseItem(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animation has used the first item slot.
|
||||
/// </summary>
|
||||
private void OnItemUseFirstSlot()
|
||||
{
|
||||
UseItem(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animation has used the second item slot.
|
||||
/// </summary>
|
||||
private void OnItemUseSecondSlot()
|
||||
{
|
||||
UseItem(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animation has used the third item slot.
|
||||
/// </summary>
|
||||
private void OnItemUseThirdSlot()
|
||||
{
|
||||
UseItem(2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ItemUse event has been triggered.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The id of the slot that was used.</param>
|
||||
private void UseItem(int slotID)
|
||||
{
|
||||
var usableItem = m_UsableItems[slotID];
|
||||
if (usableItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_WaitForUseEvent[slotID] = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the ability.
|
||||
/// </summary>
|
||||
public override void Update()
|
||||
{
|
||||
// Do not call the base method to prevent an attribute from stopping the use.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the ability after the controller has updated. This will ensure the character is in the most up to date position.
|
||||
/// </summary>
|
||||
public override void LateUpdate()
|
||||
{
|
||||
// Enable the collision layer so the weapons can apply damage the originating character.
|
||||
m_CharacterLocomotion.EnableColliderCollisionLayer(true);
|
||||
|
||||
// Tries to use the item. This is done within Update because the item can be used multiple times when the input button is held down.
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
if (m_UsableItems[i] != null) {
|
||||
// Allow the items currently in use to be updated.
|
||||
m_UsableItems[i].UseItemUpdate();
|
||||
|
||||
// If the InputIndex isn't -1 and the stop event isn't null then the ability is trying to be stopped. The ability must remain active for as long
|
||||
// as the StopAbilityDelay but during this time the item should not be used.
|
||||
if (InputIndex != -1 && m_CanStopEvent[i] != null && m_UsableItems[i].CanStopItemUse()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't use the item if the item is waiting for the ItemUse event or has already been used.
|
||||
if (m_WaitForUseEvent[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_UsableItems[i].CanUseItem(this, UsableItem.UseAbilityState.Update)) {
|
||||
m_UsableItems[i].UseItem();
|
||||
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
// Each use should update the attribute.
|
||||
if (m_UsableItems[i] is ShootableWeapon && m_AttributeModifier != null) {
|
||||
m_AttributeModifier.EnableModifier(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Using the item may have killed the character and stopped the ability.
|
||||
if (!IsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The ability may have been stopped immediately after use. This will happen if for example a shootable weapon automatically reloads when it
|
||||
// is out of ammo.
|
||||
if (m_UsableItems[i] != null) {
|
||||
// A custom use animation should be played.
|
||||
m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters();
|
||||
|
||||
// The ability can be stopped after it has been used to allow hip firing.
|
||||
if (m_UsableItems[i].StopUseAbilityDelay > 0) {
|
||||
ResetCanStopEvent(i);
|
||||
m_CanStopEvent[i] = Scheduler.ScheduleFixed(m_UsableItems[i].StopUseAbilityDelay, AbilityCanStop, i);
|
||||
} else {
|
||||
m_CanStopAbility[i] = true;
|
||||
}
|
||||
|
||||
// The item needs to be used before the complete event can be called.
|
||||
if (!m_UsableItems[i].IsItemUsePending()) {
|
||||
ScheduleCompleteEvent(i, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_CharacterLocomotion.EnableColliderCollisionLayer(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules the complete event.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the complete event to schedule.</param>
|
||||
/// <param name="scheduleEvent">Should the event be scheduled? If false the Use Complete array will only be set.</param>
|
||||
protected void ScheduleCompleteEvent(int index, bool scheduleEvent)
|
||||
{
|
||||
m_WaitForUseCompleteEvent[index] = true;
|
||||
if (scheduleEvent) {
|
||||
if (m_UseEvent[index] != null) {
|
||||
Scheduler.Cancel(m_UseEvent[index]);
|
||||
}
|
||||
if (!m_UsableItems[index].UseCompleteEvent.WaitForAnimationEvent) {
|
||||
m_UseEvent[index] = Scheduler.ScheduleFixed(m_UsableItems[index].UseCompleteEvent.Duration, UseCompleteItem, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The item has been used and the ability can now stop.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The ID of the slot that can stop.</param>
|
||||
private void AbilityCanStop(int slotID)
|
||||
{
|
||||
m_CanStopAbility[slotID] = true;
|
||||
m_CanStopEvent[slotID] = null;
|
||||
|
||||
// The ability should be stopped if all items have finished being used.
|
||||
var stopAbility = true;
|
||||
for (int i = 0; i < m_UseCompleted.Length; ++i) {
|
||||
if (!m_UseCompleted[i]) {
|
||||
stopAbility = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stopAbility) {
|
||||
StopAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the controller's rotation values.
|
||||
/// </summary>
|
||||
public override void UpdateRotation()
|
||||
{
|
||||
// The rotation doesn't need to be updated if the item doesn't need to face the target.
|
||||
if (m_FaceTargetItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The look source may be null if a remote player is still being initialized.
|
||||
if (m_LookSource == null || !m_RotateTowardsLookSourceTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the direction that the character should be facing.
|
||||
var lookDirection = m_LookSource.LookDirection(m_LookSource.LookPosition(), true, m_CharacterLayerManager.IgnoreInvisibleCharacterLayers, false);
|
||||
var rotation = m_Transform.rotation * Quaternion.Euler(m_CharacterLocomotion.DeltaRotation);
|
||||
var localLookDirection = MathUtility.InverseTransformDirection(lookDirection, rotation);
|
||||
localLookDirection.y = 0;
|
||||
lookDirection = MathUtility.TransformDirection(localLookDirection, rotation);
|
||||
var targetRotation = Quaternion.LookRotation(lookDirection, rotation * Vector3.up);
|
||||
m_CharacterLocomotion.DeltaRotation = (Quaternion.Inverse(m_Transform.rotation) * targetRotation).eulerAngles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The use animation has completed for all of the items.
|
||||
/// </summary>
|
||||
private void OnItemUseComplete()
|
||||
{
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
if (m_UsableItems[i] != null) {
|
||||
UseCompleteItem(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The use animation has completed for the first item slot.
|
||||
/// </summary>
|
||||
private void OnItemUseCompleteFirstSlot()
|
||||
{
|
||||
UseCompleteItem(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The use animation has completed for the second item slot.
|
||||
/// </summary>
|
||||
private void OnItemUseCompleteSecondSlot()
|
||||
{
|
||||
UseCompleteItem(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The use animation has completed for the third item slot.
|
||||
/// </summary>
|
||||
private void OnItemUseCompleteThirdSlot()
|
||||
{
|
||||
UseCompleteItem(2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animator has finished playing the use animation.
|
||||
/// </summary>
|
||||
/// <param name="slotID">The id of the slot that was used.</param>
|
||||
protected virtual void UseCompleteItem(int slotID)
|
||||
{
|
||||
var usableItem = m_UsableItems[slotID];
|
||||
if (usableItem == null || !m_WaitForUseCompleteEvent[slotID]) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_WaitForUseCompleteEvent[slotID] = false;
|
||||
m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters();
|
||||
m_UseCompleted[slotID] = true;
|
||||
m_UsableItems[slotID].ItemUseComplete();
|
||||
|
||||
// The ability should stop when all the items have been used.
|
||||
var stopAbility = true;
|
||||
for (int i = 0; i < m_UseCompleted.Length; ++i) {
|
||||
if (m_UsableItems[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m_UseCompleted[i] || !m_CanStopAbility[i]) {
|
||||
stopAbility = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stopAbility) {
|
||||
StopAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
// If the item is currently being used and it cannot be stopped then the ability cannot stop either.
|
||||
if (m_UsableItems[i] != null && m_UsableItems[i].IsItemInUse()) {
|
||||
m_UsableItems[i].TryStopItemUse();
|
||||
// The UsableItem may not be able to be stopped (for example, if a throwable item should be used when the button press is released).
|
||||
if (!m_UsableItems[i].CanStopItemUse()) {
|
||||
return false;
|
||||
}
|
||||
if (!m_UseCompleted[i]) {
|
||||
// The complete event may not have been called if the item use was still pending.
|
||||
if (m_UseEvent[i] == null || !m_UseEvent[i].Active) {
|
||||
ScheduleCompleteEvent(i, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Don't stop if CanStopAbility is false. This will allow hip firing to keep the item held up momentarily after being used. The ability should always
|
||||
// be able to stop during a reload.
|
||||
if (!m_CanStopAbility[i]
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
&& !m_CharacterLocomotion.IsAbilityTypeActive<Reload>()
|
||||
#endif
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
// The item may require root motion to prevent sliding.
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
if (m_UsableItems[i] != null) {
|
||||
if (m_UsableItems[i].ForceRootMotionPosition) {
|
||||
m_CharacterLocomotion.ForceRootMotionPosition = false;
|
||||
}
|
||||
if (m_UsableItems[i].ForceRootMotionRotation) {
|
||||
m_CharacterLocomotion.ForceRootMotionRotation = false;
|
||||
}
|
||||
m_UsableItems[i].StopItemUse();
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnItemStartUse", m_UsableItems[i], false);
|
||||
m_UsableItems[i] = null;
|
||||
m_UseCompleted[i] = true;
|
||||
if (m_UseEvent[i] != null) {
|
||||
Scheduler.Cancel(m_UseEvent[i]);
|
||||
m_UseEvent[i] = null;
|
||||
}
|
||||
ResetCanStopEvent(i);
|
||||
}
|
||||
}
|
||||
|
||||
m_Started = false;
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnUseAbilityStart", false, this);
|
||||
}
|
||||
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
/// <summary>
|
||||
/// The item ability has been started or stopped.
|
||||
/// </summary>
|
||||
/// <param name="itemAbility">The item ability which was started or stopped.</param>
|
||||
/// <param name="active">True if the ability was started, false if it was stopped.</param>
|
||||
private void OnItemAbilityActive(ItemAbility itemAbility, bool active)
|
||||
{
|
||||
if (!(itemAbility is Reload)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use currently is not active, but it may have to start if the Use ability is trying to be started.
|
||||
if (!active && InputIndex != -1 && m_PlayerInput != null) {
|
||||
// Change the start type so the button up won't affect if the ability can start.
|
||||
var startType = m_StartType;
|
||||
if (startType == AbilityStartType.ButtonDown) {
|
||||
m_StartType = AbilityStartType.ButtonDownContinuous;
|
||||
}
|
||||
if (CanInputStartAbility(m_PlayerInput)) {
|
||||
if (IsActive) {
|
||||
// The use state should be reset if the ability is currently active.
|
||||
for (int i = 0; i < m_UsableItems.Length; ++i) {
|
||||
if (m_UsableItems[i] == null) {
|
||||
continue;
|
||||
}
|
||||
m_UsableItems[i].StartItemUse(this);
|
||||
ResetCanStopEvent(i);
|
||||
}
|
||||
InputIndex = -1;
|
||||
} else {
|
||||
// The ability isn't active, but it should be.
|
||||
StartAbility();
|
||||
}
|
||||
}
|
||||
m_StartType = startType;
|
||||
return;
|
||||
}
|
||||
|
||||
var reloadAbility = itemAbility as Reload;
|
||||
for (int i = 0; i < reloadAbility.ReloadableItems.Length; ++i) {
|
||||
if (reloadAbility.ReloadableItems[i] == null) {
|
||||
continue;
|
||||
}
|
||||
var slotID = reloadAbility.ReloadableItems[i].Item.SlotID;
|
||||
if (m_SlotID != -1) {
|
||||
if (m_SlotID != slotID) {
|
||||
continue;
|
||||
}
|
||||
// If a slot ID is specified then there will only be one element.
|
||||
slotID = 0;
|
||||
}
|
||||
|
||||
// If the reload ability is active the CanStop event shouldn't fire so the character can continue to fire after reloading.
|
||||
if (active) {
|
||||
// If the ability index is not -1 then the item is trying to be stopped. Prevent the item from being used again when reload is complete.
|
||||
if (InputIndex != -1) {
|
||||
StopAbility(true);
|
||||
} else {
|
||||
ResetCanStopEvent(slotID);
|
||||
}
|
||||
} else {
|
||||
m_CanStopEvent[slotID] = Scheduler.ScheduleFixed(m_UsableItems[slotID].StopUseAbilityDelay, AbilityCanStop, slotID);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables gameplay input. An example of when it will not be enabled is when there is a fullscreen UI over the main camera.
|
||||
/// </summary>
|
||||
/// <param name="enable">True if the input is enabled.</param>
|
||||
private void OnEnableGameplayInput(bool enable)
|
||||
{
|
||||
// Force stop the ability if the character no longer has input.
|
||||
if (!enable && IsActive) {
|
||||
StopAbility(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<ILookSource>(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
|
||||
EventHandler.UnregisterEvent<bool>(m_GameObject, "OnEnableGameplayInput", OnEnableGameplayInput);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUse", OnItemUse);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseComplete", OnItemUseComplete);
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER
|
||||
EventHandler.UnregisterEvent<ItemAbility, bool>(m_GameObject, "OnCharacterItemAbilityActive", OnItemAbilityActive);
|
||||
#endif
|
||||
UnregisterSlotEvents(m_SlotID);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Items/Use.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Items/Use.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 182e14066f1b4f4479c2d330a127d1f9
|
||||
timeCreated: 1497448172
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
429
Assets/Scripts/Character/Abilities/Jump.cs
Normal file
429
Assets/Scripts/Character/Abilities/Jump.cs
Normal file
@@ -0,0 +1,429 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.Shared.Utility;
|
||||
using Opsive.UltimateCharacterController.Audio;
|
||||
using Opsive.UltimateCharacterController.Input;
|
||||
using Opsive.UltimateCharacterController.SurfaceSystem;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Jump ability allows the character to jump into the air. Jump is only active when the character has a positive y velocity.
|
||||
/// </summary>
|
||||
[DefaultInputName("Jump")]
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultStopType(AbilityStopType.Automatic)]
|
||||
[DefaultAbilityIndex(1)]
|
||||
[DefaultUseRootMotionPosition(AbilityBoolOverride.False)]
|
||||
[DefaultUseRootMotionRotation(AbilityBoolOverride.False)]
|
||||
public class Jump : Ability
|
||||
{
|
||||
[Tooltip("Prevents the jump ability from starting if there is an object above the character within the specified distance. Set to -1 to disable.")]
|
||||
[SerializeField] protected float m_MinCeilingJumpHeight = 0.05f;
|
||||
[Tooltip("The amount of time after the character is airborne that the character can still jump. Also known as coyote time. " +
|
||||
"Set to -1 to allow for the character to jump at any time after being airborne.")]
|
||||
[SerializeField] protected float m_GroundedGracePeriod = 0.05f;
|
||||
[Tooltip("Should the jump be prevented when the character is on a slope greater than the slope limit?")]
|
||||
[SerializeField] protected bool m_PrevntSlopeLimitJump = true;
|
||||
[Tooltip("The amount of force that should be applied when the character jumps.")]
|
||||
[SerializeField] protected float m_Force = 0.22f;
|
||||
[Tooltip("A multiplier applied to the force while moving sideways.")]
|
||||
[SerializeField] protected float m_SidewaysForceMultiplier = 0.8f;
|
||||
[Tooltip("A multiplier applied to the force while moving backwards.")]
|
||||
[SerializeField] protected float m_BackwardsForceMultiplier = 0.7f;
|
||||
[Tooltip("The number of frames that the force is applied in.")]
|
||||
[SerializeField] protected int m_Frames = 1;
|
||||
[Tooltip("Determines how quickly the jump force wears off.")]
|
||||
[SerializeField] protected float m_ForceDamping = 0.08f;
|
||||
[Tooltip("Specifies if the ability should wait for the OnAnimatorJump animation event or wait for the specified duration before applying the jump force.")]
|
||||
[SerializeField] protected AnimationEventTrigger m_JumpEvent = new AnimationEventTrigger(true, 0f);
|
||||
[Tooltip("The Surface Impact triggered when the character jumps.")]
|
||||
[SerializeField] protected SurfaceImpact m_JumpSurfaceImpact;
|
||||
[Tooltip("The amount of force to add per frame if the jump button is being held down continuously. This is a common feature for providing increased jump control in platform games.")]
|
||||
[SerializeField] protected float m_ForceHold = 0.003f;
|
||||
[Tooltip("Determines how quickly the jump hold force wears off.")]
|
||||
[SerializeField] protected float m_ForceDampingHold = 0.5f;
|
||||
[Tooltip("Specifies the number of times the character can perform a airborne jump (double jump, triple jump, etc). Set to -1 to allow an infinite number of airborne jumps.")]
|
||||
[SerializeField] protected int m_MaxAirborneJumpCount;
|
||||
[Tooltip("The amount of force that applied when the character performs an airborne jump.")]
|
||||
[SerializeField] protected float m_AirborneJumpForce = 0.6f;
|
||||
[Tooltip("The number of frames that the repeated jump force is applied in.")]
|
||||
[SerializeField] protected int m_AirborneJumpFrames = 10;
|
||||
[Tooltip("Contains an array of AudioClips toat can be played when a repeated jump occurs.")]
|
||||
[SerializeField] protected AudioClipSet m_AirborneJumpAudioClipSet = new AudioClipSet();
|
||||
[Tooltip("A vertical velocity value below the specified amount will stop the ability.")]
|
||||
[SerializeField] protected float m_VerticalVelocityStopThreshold = -0.001f;
|
||||
[Tooltip("The number of seconds that the jump ability has to wait after it can start again (includes repeated jumps).")]
|
||||
[SerializeField] protected float m_RecurrenceDelay = 0.2f;
|
||||
|
||||
public float MinCeilingJumpHeight { get { return m_MinCeilingJumpHeight; } set { m_MinCeilingJumpHeight = value; } }
|
||||
public float GroundedGracePeriod { get { return m_GroundedGracePeriod; } set { m_GroundedGracePeriod = value; } }
|
||||
public bool PrevntSlopeLimitJump { get { return m_PrevntSlopeLimitJump; } set { m_PrevntSlopeLimitJump = value; } }
|
||||
public float Force { get { return m_Force; } set { m_Force = value; } }
|
||||
public float SidewaysForceMultiplier { get { return m_SidewaysForceMultiplier; } set { m_SidewaysForceMultiplier = value; } }
|
||||
public float BackwardsForceMultiplier { get { return m_BackwardsForceMultiplier; } set { m_BackwardsForceMultiplier = value; } }
|
||||
public int Frames { get { return m_Frames; } set { m_Frames = value; } }
|
||||
public float ForceDamping { get { return m_ForceDamping; } set { m_ForceDamping = value; } }
|
||||
public AnimationEventTrigger JumpEvent { get { return m_JumpEvent; } set { m_JumpEvent = value; } }
|
||||
public SurfaceImpact JumpSurfaceImpact { get { return m_JumpSurfaceImpact; } set { m_JumpSurfaceImpact = value; } }
|
||||
public float ForceHold { get { return m_ForceHold; } set { m_ForceHold = value; } }
|
||||
public float ForceDampingHold { get { return m_ForceDampingHold; } set { m_ForceDampingHold = value; } }
|
||||
public int MaxAirborneJumpCount { get { return m_MaxAirborneJumpCount; } set { m_MaxAirborneJumpCount = value; } }
|
||||
public float AirborneJumpForce { get { return m_AirborneJumpForce; } set { m_AirborneJumpForce = value; } }
|
||||
public int AirborneJumpFrames { get { return m_AirborneJumpFrames; } set { m_AirborneJumpFrames = value; } }
|
||||
public AudioClipSet AirborneJumpAudioClipSet { get { return m_AirborneJumpAudioClipSet; } set { m_AirborneJumpAudioClipSet = value; } }
|
||||
public float VerticalVelocityStopThreshold { get { return m_VerticalVelocityStopThreshold; } set { m_VerticalVelocityStopThreshold = value; } }
|
||||
public float RecurrenceDelay { get { return m_RecurrenceDelay; } set { m_RecurrenceDelay = value; } }
|
||||
|
||||
private UltimateCharacterLocomotionHandler m_Handler;
|
||||
private ActiveInputEvent m_HoldInput;
|
||||
private ActiveInputEvent m_AirborneJumpInput;
|
||||
|
||||
private RaycastHit m_RaycastResult;
|
||||
private bool m_ForceImmediateJump;
|
||||
private bool m_JumpApplied;
|
||||
private bool m_Jumping;
|
||||
private bool m_ApplyHoldForce;
|
||||
private float m_HoldForce;
|
||||
private int m_AirborneJumpCount;
|
||||
private bool m_AirborneJumpApplied;
|
||||
private float m_JumpTime = -1;
|
||||
private float m_LandTime = -1;
|
||||
private float m_InAirTime = -1;
|
||||
private bool m_AirborneJumpRegistered;
|
||||
|
||||
[Snapshot] protected float HoldForce { get { return m_HoldForce; } set { m_HoldForce = value; } }
|
||||
[Snapshot] protected bool JumpApplied { get { return m_JumpApplied; } set { m_JumpApplied = value; } }
|
||||
[Snapshot] protected bool Jumping { get { return m_Jumping; } set { m_Jumping = value; } }
|
||||
public bool ForceImmediateJump { set { m_ForceImmediateJump = value; } }
|
||||
|
||||
public override float AbilityFloatData { get { if (m_Jumping) { return m_CharacterLocomotion.LocalLocomotionVelocity.y; } return -1; } }
|
||||
public override int AbilityIntData { get { return (m_AirborneJumpCount > 0 ? 2 : (m_JumpApplied ? 0 : 1)); } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_Handler = m_GameObject.GetCachedComponent<UltimateCharacterLocomotionHandler>();
|
||||
|
||||
EventHandler.RegisterEvent<bool>(m_GameObject, "OnCharacterGrounded", OnGrounded);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorJump", ApplyJumpForce);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be started?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_MinCeilingJumpHeight != -1) {
|
||||
// Ensure the space above is clear to get off of the ground.
|
||||
if (m_CharacterLocomotion.SingleCast(m_CharacterLocomotion.Up * (m_MinCeilingJumpHeight + m_CharacterLocomotion.SkinWidth + m_CharacterLocomotion.ColliderSpacing),
|
||||
Vector3.zero, m_CharacterLayerManager.SolidObjectLayers, ref m_RaycastResult)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_CharacterLocomotion.Grounded) {
|
||||
// The character can't jump if they aren't on the ground nor if they recently landed.
|
||||
if (m_LandTime + m_RecurrenceDelay > Time.time) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The character can't jump if the slope is too steep.
|
||||
if (m_PrevntSlopeLimitJump) {
|
||||
var slope = Vector3.Angle(m_CharacterLocomotion.Up, m_CharacterLocomotion.GroundRaycastHit.normal);
|
||||
if (slope > m_CharacterLocomotion.SlopeLimit) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The airborne jump should play if the character walks off a ledge and did not initially jump.
|
||||
if (m_AirborneJumpCount< m_MaxAirborneJumpCount) {
|
||||
m_JumpApplied = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allow the ability to start if the character is in the air before the grounded grace period. This allows the character to run off a ledge but still
|
||||
// be able to jump. The character may also be able to do a repeated jump even if the character isn't grounded.
|
||||
if ((!m_JumpApplied && m_GroundedGracePeriod != -1 && m_InAirTime + m_GroundedGracePeriod <= Time.time) ||
|
||||
(m_AirborneJumpCount != -1 && m_AirborneJumpCount > m_MaxAirborneJumpCount) || (m_JumpTime != -1 && m_JumpTime + m_RecurrenceDelay > Time.time)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the current ability is attempting to start and another ability is active.
|
||||
/// Returns true or false depending on if the active ability should be stopped.
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability that is currently active.</param>
|
||||
/// <returns>True if the ability should be stopped.</returns>
|
||||
public override bool ShouldStopActiveAbility(Ability activeAbility)
|
||||
{
|
||||
if (activeAbility is Fall) {
|
||||
return true;
|
||||
}
|
||||
return base.ShouldStopActiveAbility(activeAbility);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
m_ApplyHoldForce = true;
|
||||
m_HoldForce = 0;
|
||||
|
||||
// If the jump has already been applied then it is a repeated jump.
|
||||
if (m_JumpApplied) {
|
||||
OnAirborneJump();
|
||||
} else {
|
||||
if (!m_JumpEvent.WaitForAnimationEvent || m_ForceImmediateJump) {
|
||||
Scheduler.ScheduleFixed(m_ForceImmediateJump ? 0 : m_JumpEvent.Duration, ApplyJumpForce);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_ForceHold > 0) {
|
||||
if (m_Handler != null && InputIndex != -1) {
|
||||
m_HoldInput = GenericObjectPool.Get<ActiveInputEvent>();
|
||||
m_HoldInput.Initialize(ActiveInputEvent.Type.ButtonUp, InputNames[InputIndex], "OnJumpAbilityReleaseHold");
|
||||
m_Handler.RegisterInputEvent(m_HoldInput);
|
||||
}
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnJumpAbilityReleaseHold", OnReleaseHold);
|
||||
}
|
||||
|
||||
// The character can do a repeated jump after the character is already in the air.
|
||||
if (!m_AirborneJumpRegistered) {
|
||||
if (m_Handler != null && InputIndex != -1) {
|
||||
m_AirborneJumpInput = GenericObjectPool.Get<ActiveInputEvent>();
|
||||
m_AirborneJumpInput.Initialize(ActiveInputEvent.Type.ButtonDown, InputNames[InputIndex], "OnJumpAbilityAirborneJump");
|
||||
m_Handler.RegisterInputEvent(m_AirborneJumpInput);
|
||||
}
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnJumpAbilityAirborneJump", OnAirborneJump);
|
||||
m_AirborneJumpRegistered = true;
|
||||
}
|
||||
m_ForceImmediateJump = false;
|
||||
|
||||
base.AbilityStarted();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has either landed or just left the ground.
|
||||
/// </summary>
|
||||
/// <param name="grounded">Is the character on the ground?</param>
|
||||
private void OnGrounded(bool grounded)
|
||||
{
|
||||
if (grounded) {
|
||||
if (IsActive) {
|
||||
StopAbility(true);
|
||||
}
|
||||
m_AirborneJumpCount = 0;
|
||||
m_JumpApplied = false;
|
||||
// Remember the land time to prevent jumping more than the JumpReoccuranceDelay.
|
||||
m_LandTime = Time.time;
|
||||
m_InAirTime = -1;
|
||||
|
||||
// Unregister the jump input within OnGrounded so a repeated jump can be applied when fall is active.
|
||||
if (m_AirborneJumpInput != null) {
|
||||
m_Handler.UnregisterInputEvent(m_AirborneJumpInput);
|
||||
GenericObjectPool.Return(m_AirborneJumpInput);
|
||||
m_AirborneJumpInput = null;
|
||||
}
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnJumpAbilityAirborneJump", OnAirborneJump);
|
||||
m_AirborneJumpRegistered = false;
|
||||
} else if (!IsActive) {
|
||||
m_InAirTime = Time.time;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the Animator for the ability.
|
||||
/// </summary>
|
||||
public override void UpdateAnimator()
|
||||
{
|
||||
// Set the Float Data parameter for the blend tree.
|
||||
if (m_Jumping) {
|
||||
SetAbilityFloatDataParameter(m_CharacterLocomotion.LocalLocomotionVelocity.y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character should start the jump.
|
||||
/// </summary>
|
||||
private void ApplyJumpForce()
|
||||
{
|
||||
if (IsActive && !m_JumpApplied) {
|
||||
// A surface effect can optionally play when the character leaves the ground.
|
||||
if (m_JumpSurfaceImpact != null) {
|
||||
SurfaceManager.SpawnEffect(m_CharacterLocomotion.GroundRaycastHit, m_JumpSurfaceImpact, m_CharacterLocomotion.GravityDirection, m_CharacterLocomotion.TimeScale, m_GameObject);
|
||||
}
|
||||
// Do not set the Jumping variable because the ability should be active for at least one frame. If Jumping was set there is a chance
|
||||
// the ability could stop right away if the character jumps while moving down a slope.
|
||||
m_JumpApplied = true;
|
||||
m_JumpTime = Time.time;
|
||||
var force = m_Force;
|
||||
// Prevent the character from jumping as high when moving backwards or sideways.
|
||||
if (m_CharacterLocomotion.InputVector.y < 0) {
|
||||
force *= Mathf.Lerp(1, m_BackwardsForceMultiplier, Mathf.Abs(m_CharacterLocomotion.InputVector.y));
|
||||
} else {
|
||||
// The character's forward movement will contribute to a full jump force.
|
||||
force *= Mathf.Lerp(1, m_SidewaysForceMultiplier, Mathf.Abs(m_CharacterLocomotion.InputVector.x) - Mathf.Abs(m_CharacterLocomotion.InputVector.y));
|
||||
}
|
||||
AddForce(m_CharacterLocomotion.Up * force, m_Frames, false, true);
|
||||
|
||||
// Ensure the character is in the air after jumping.
|
||||
Scheduler.ScheduleFixed(Time.fixedDeltaTime + .001f, EnsureAirborne);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// After jumping the character should be in the air. If the character is not in the air then another object prevented the character from jumping and the
|
||||
/// jump ability should be stopped.
|
||||
/// </summary>
|
||||
private void EnsureAirborne()
|
||||
{
|
||||
if (!m_CharacterLocomotion.Grounded) {
|
||||
return;
|
||||
}
|
||||
|
||||
StopAbility(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The user is no longer holding the jump button down.
|
||||
/// </summary>
|
||||
private void OnReleaseHold()
|
||||
{
|
||||
m_ApplyHoldForce = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability should perform a airborne jump.
|
||||
/// </summary>
|
||||
private void OnAirborneJump()
|
||||
{
|
||||
if (m_MaxAirborneJumpCount != -1 && m_AirborneJumpCount >= m_MaxAirborneJumpCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the accumulated gravity to allow for a full airborne jump.
|
||||
m_CharacterLocomotion.GravityAmount = 0;
|
||||
AddForce(m_CharacterLocomotion.Up * m_AirborneJumpForce, m_AirborneJumpFrames, false, true);
|
||||
m_AirborneJumpCount++;
|
||||
m_JumpTime = Time.time;
|
||||
// The repeated jump may be applied just as the fall ability is about to start. Prevent the jump ability from stopping immediately after starting a repeated jump.
|
||||
m_AirborneJumpApplied = true;
|
||||
m_AirborneJumpAudioClipSet.PlayAudioClip(m_GameObject);
|
||||
m_CharacterLocomotion.UpdateAbilityAnimatorParameters();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows for the Jump ability to add an extra force.
|
||||
/// </summary>
|
||||
public override void UpdatePosition()
|
||||
{
|
||||
if (!m_Jumping) {
|
||||
if (m_JumpApplied) {
|
||||
m_Jumping = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_AirborneJumpApplied) {
|
||||
m_AirborneJumpApplied = false;
|
||||
}
|
||||
|
||||
var force = 0f;
|
||||
var deltaTime = m_CharacterLocomotion.TimeScaleSquared * Time.timeScale * TimeUtility.FramerateDeltaTime;
|
||||
// Continuously apply a damping force while in the air.
|
||||
if (m_ForceDamping > 0) {
|
||||
var localExternalForce = m_CharacterLocomotion.LocalExternalForce;
|
||||
var targetForce = localExternalForce.y / (1 + m_ForceDamping * deltaTime);
|
||||
force = (targetForce - localExternalForce.y);
|
||||
}
|
||||
|
||||
// Allow a force and damping to be applied when the input button is held down.
|
||||
if (m_ForceHold > 0 && m_ApplyHoldForce) {
|
||||
m_HoldForce += m_ForceHold;
|
||||
m_HoldForce /= (1 + m_ForceDampingHold * deltaTime);
|
||||
force += m_HoldForce;
|
||||
}
|
||||
|
||||
// When the jump force is added it is added to the character's external force. Dampen this external force.
|
||||
if (force != 0) {
|
||||
AddForce(m_CharacterLocomotion.Up * force, 1, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
// The Jump ability is done if the velocity is less than a the specified value.
|
||||
if (m_Jumping && m_CharacterLocomotion.LocalLocomotionVelocity.y <= m_VerticalVelocityStopThreshold && !m_AirborneJumpApplied) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
m_JumpApplied = m_Jumping = false;
|
||||
|
||||
// Unregister for the ability input events.
|
||||
if (m_HoldInput != null) {
|
||||
m_Handler.UnregisterInputEvent(m_HoldInput);
|
||||
GenericObjectPool.Return(m_HoldInput);
|
||||
}
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnJumpAbilityReleaseHold", OnReleaseHold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
return startingAbility is HeightChange;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GameObject has been destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorJump", ApplyJumpForce);
|
||||
EventHandler.UnregisterEvent<bool>(m_GameObject, "OnCharacterGrounded", OnGrounded);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Jump.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Jump.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0a241f23658d71408b6579488345f18
|
||||
timeCreated: 1487015903
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
107
Assets/Scripts/Character/Abilities/MeleeCounterAttackResponse.cs
Normal file
107
Assets/Scripts/Character/Abilities/MeleeCounterAttackResponse.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Plays a full body animation in response to a melee counter attack.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.Manual)]
|
||||
[DefaultStopType(AbilityStopType.Manual)]
|
||||
[DefaultUseRootMotionPosition(AbilityBoolOverride.True)]
|
||||
[DefaultAbilityIndex(13)]
|
||||
public class MeleeCounterAttackResponse : Ability
|
||||
{
|
||||
[Tooltip("Specifies if the ability should stop when the OnAnimatorMeleeCounterAttackResponseComplete event is received or wait the specified amount of time before ending the ability.")]
|
||||
[SerializeField] protected AnimationEventTrigger m_StopEvent = new AnimationEventTrigger(true, 0.2f);
|
||||
|
||||
public AnimationEventTrigger StopEvent { get { return m_StopEvent; } set { m_StopEvent = value; } }
|
||||
|
||||
private int m_ResponseID;
|
||||
|
||||
public override int AbilityIntData { get { return m_ResponseID; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorMeleeCounterAttackResponseComplete", OnComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has been counter attacked. Play a response animation.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the counter attack.</param>
|
||||
public void StartResponse(int id)
|
||||
{
|
||||
m_ResponseID = id;
|
||||
StartAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
if (!m_StopEvent.WaitForAnimationEvent) {
|
||||
Scheduler.ScheduleFixed(m_StopEvent.Duration, OnComplete);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the current ability is attempting to start and another ability is active.
|
||||
/// Returns true or false depending on if the active ability should be stopped.
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability that is currently active.</param>
|
||||
/// <returns>True if the ability should be stopped.</returns>
|
||||
public override bool ShouldStopActiveAbility(Ability activeAbility)
|
||||
{
|
||||
if (activeAbility is Items.Use) {
|
||||
return true;
|
||||
}
|
||||
return base.ShouldStopActiveAbility(activeAbility);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animation is done playing - stop the ability.
|
||||
/// </summary>
|
||||
private void OnComplete()
|
||||
{
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorMeleeCounterAttackResponseComplete", OnComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 183aff55edcab624885e03c4f4b2db78
|
||||
timeCreated: 1513724185
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
389
Assets/Scripts/Character/Abilities/MoveTowards.cs
Normal file
389
Assets/Scripts/Character/Abilities/MoveTowards.cs
Normal file
@@ -0,0 +1,389 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Character.Abilities.AI;
|
||||
using Opsive.UltimateCharacterController.Objects.CharacterAssist;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Moves the character to the specified start location. This ability will be called manually by the controller and should not be started by the user.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.Manual)]
|
||||
[DefaultAllowPositionalInput(false)]
|
||||
[DefaultAllowRotationalInput(false)]
|
||||
public class MoveTowards : Ability
|
||||
{
|
||||
[Tooltip("The multiplier to apply to the input vector. Allows the character to move towards the destination faster.")]
|
||||
[SerializeField] protected float m_InputMultiplier = 1;
|
||||
[Tooltip("The amount of time it takes that the character has to be stuck before teleporting the character to the start location.")]
|
||||
[SerializeField] protected float m_InactiveTimeout = 1;
|
||||
[Tooltip("Should the OnEnableGameplayInpt event be sent to disable the input when the ability is active?")]
|
||||
[SerializeField] protected bool m_DisableGameplayInput;
|
||||
[Tooltip("The location that the Move Towards ability should move towards if the ability is not started by another ability.")]
|
||||
[SerializeField] protected MoveTowardsLocation m_IndependentStartLocation;
|
||||
|
||||
public float InputMultiplier { get { return m_InputMultiplier; } set { m_InputMultiplier = value; } }
|
||||
public float InactiveTimeout { get { return m_InactiveTimeout; } set { m_InactiveTimeout = value; } }
|
||||
public bool DisableGameplayInput { get { return m_DisableGameplayInput; } set { m_DisableGameplayInput = value; } }
|
||||
[Shared.Utility.NonSerialized] public MoveTowardsLocation IndependentStartLocation { get { return m_IndependentStartLocation; } set { m_IndependentStartLocation = value; } }
|
||||
|
||||
public override bool IsConcurrent { get { return true; } }
|
||||
public override bool ImmediateStartItemVerifier { get { return true; } }
|
||||
|
||||
private MoveTowardsLocation m_MoveTowardsLocation;
|
||||
private Ability m_OnArriveAbility;
|
||||
|
||||
private PathfindingMovement m_PathfindingMovement;
|
||||
private SpeedChange[] m_SpeedChangeAbilities;
|
||||
|
||||
private float m_MovementMultiplier;
|
||||
private Vector3 m_TargetDirection;
|
||||
private bool m_Arrived;
|
||||
private bool m_PrecisionStartWait;
|
||||
|
||||
private ScheduledEventBase m_ForceStartEvent;
|
||||
|
||||
public MoveTowardsLocation StartLocation { get { return m_MoveTowardsLocation; } }
|
||||
public Ability OnArriveAbility { get { return m_OnArriveAbility; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
m_PathfindingMovement = m_CharacterLocomotion.GetAbility<PathfindingMovement>();
|
||||
m_SpeedChangeAbilities = m_CharacterLocomotion.GetAbilities<SpeedChange>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts moving to the specified start location.
|
||||
/// </summary>
|
||||
/// <param name="startLocations">The locations the character can move towards. If multiple locations are possible then the closest valid location will be used.</param>
|
||||
/// <param name="onArriveAbility">The ability that should be started as soon as the character arrives at the location.</param>
|
||||
/// <returns>True if the MoveTowards ability is started.</returns>
|
||||
public bool StartMoving(MoveTowardsLocation[] startLocations, Ability onArriveAbility)
|
||||
{
|
||||
// MoveTowards doesn't need to start if there is no start location.
|
||||
if (startLocations == null || startLocations.Length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The arrive ability must exist and be unique. If the ability is already set then StartMoving may have been triggered because the arrive ability
|
||||
// should start.
|
||||
if (onArriveAbility == null || onArriveAbility == m_OnArriveAbility) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No reason to start if the character is already in a valid start location.
|
||||
for (int i = 0; i < startLocations.Length; ++i) {
|
||||
if (startLocations[i].IsPositionValid(m_Transform.position, m_Transform.rotation, m_CharacterLocomotion.Grounded) && startLocations[i].IsRotationValid(m_Transform.rotation)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The character needs to move - start the ability.
|
||||
m_OnArriveAbility = onArriveAbility;
|
||||
if (m_OnArriveAbility.Index < Index) {
|
||||
Debug.LogWarning($"Warning: {m_OnArriveAbility.GetType().Name} has a higher priority then the MoveTowards ability. This will cause unintended behavior.");
|
||||
}
|
||||
|
||||
m_MoveTowardsLocation = GetClosestStartLocation(startLocations);
|
||||
|
||||
StartAbility();
|
||||
|
||||
// MoveTowards may be starting when all of the inputs are being checked. If it has a lower index then the update loop won't run initially
|
||||
// which will prevent the TargetDirection from having a valid value. Run the Update loop immediately so TargetDirection is correct.
|
||||
if (Index < onArriveAbility.Index) {
|
||||
Update();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the closest start location out of the possible MoveTowardsLocations.
|
||||
/// </summary>
|
||||
/// <param name="startLocations">The locations the character can move towards.</param>
|
||||
/// <returns>The best location out of the possible MoveTowardsLocations.</returns>
|
||||
private MoveTowardsLocation GetClosestStartLocation(MoveTowardsLocation[] startLocations)
|
||||
{
|
||||
// If only one location is available then it is the closest.
|
||||
if (startLocations.Length == 1) {
|
||||
return startLocations[0];
|
||||
}
|
||||
|
||||
// Multiple locations are available. Choose the closest location.
|
||||
MoveTowardsLocation startLocation = null;
|
||||
var closestDistance = float.MaxValue;
|
||||
float distance;
|
||||
for (int i = 0; i < startLocations.Length; ++i) {
|
||||
if ((distance = startLocations[i].GetTargetDirection(m_Transform.position, m_Transform.rotation).sqrMagnitude) < closestDistance) {
|
||||
closestDistance = distance;
|
||||
startLocation = startLocations[i];
|
||||
}
|
||||
}
|
||||
|
||||
return startLocation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_MoveTowardsLocation != null || m_IndependentStartLocation != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
if (m_OnArriveAbility != null) {
|
||||
m_AllowEquippedSlotsMask = m_OnArriveAbility.AllowEquippedSlotsMask;
|
||||
m_OnArriveAbility.AbilityMessageCanStart = false;
|
||||
}
|
||||
|
||||
base.AbilityStarted();
|
||||
m_Arrived = false;
|
||||
if (m_DisableGameplayInput) {
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnEnableGameplayInput", false);
|
||||
}
|
||||
|
||||
// The MoveTowardsLocation may already be set by the starting ability within StartMoving.
|
||||
if (m_MoveTowardsLocation == null) {
|
||||
m_MoveTowardsLocation = m_IndependentStartLocation;
|
||||
}
|
||||
// The movement speed will depend on the current speed the character is moving.
|
||||
m_MovementMultiplier = m_MoveTowardsLocation.MovementMultiplier;
|
||||
if (m_SpeedChangeAbilities != null) {
|
||||
for (int i = 0; i < m_SpeedChangeAbilities.Length; ++i) {
|
||||
if (m_SpeedChangeAbilities[i].IsActive) {
|
||||
m_MovementMultiplier = m_SpeedChangeAbilities[i].SpeedChangeMultiplier;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use the pathfinding ability if the destination is a valid pathfinding destination.
|
||||
if (m_PathfindingMovement != null && m_PathfindingMovement.Index < Index) {
|
||||
m_PathfindingMovement.SetDestination(m_MoveTowardsLocation.TargetPosition);
|
||||
}
|
||||
|
||||
// Force independent look so the ability will have complete control over the rotation.
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterForceIndependentLook", true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
// ItemEquipVerifier and EquipUnequip should never be blocked.
|
||||
if (startingAbility is ItemEquipVerifier || startingAbility is Items.EquipUnequip) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Block the ability if it has a lower priority (higher index) then the MoveTowards ability. ItemAbilities have a different priority list.
|
||||
if (startingAbility.Index > Index || startingAbility is StoredInputAbilityBase) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The arrive ability can determine if an ability should be blocked.
|
||||
if (m_OnArriveAbility != null) {
|
||||
return m_OnArriveAbility.ShouldBlockAbilityStart(startingAbility);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the current ability is attempting to start and another ability is active.
|
||||
/// Returns true or false depending on if the active ability should be stopped.
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability that is currently active.</param>
|
||||
/// <returns>True if the ability should be stopped.</returns>
|
||||
public override bool ShouldStopActiveAbility(Ability activeAbility)
|
||||
{
|
||||
if (activeAbility is StoredInputAbilityBase) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The arrive ability can determine if an ability should be stopped.
|
||||
if (m_OnArriveAbility != null) {
|
||||
return m_OnArriveAbility.ShouldStopActiveAbility(activeAbility);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the ability.
|
||||
/// </summary>
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// The input values should move towards the target.
|
||||
var arrived = m_MoveTowardsLocation.IsRotationValid(m_Transform.rotation);
|
||||
m_TargetDirection = m_MoveTowardsLocation.GetTargetDirection(m_Transform.position, m_Transform.rotation);
|
||||
if (!m_MoveTowardsLocation.IsPositionValid(m_Transform.position, m_Transform.rotation, m_CharacterLocomotion.Grounded)) {
|
||||
if (m_PathfindingMovement == null || !m_PathfindingMovement.IsActive) {
|
||||
m_CharacterLocomotion.InputVector = GetInputVector(m_TargetDirection);
|
||||
}
|
||||
arrived = false;
|
||||
} else if (!m_MoveTowardsLocation.PrecisionStart && (m_PathfindingMovement == null || !m_PathfindingMovement.IsActive) &&
|
||||
(m_OnArriveAbility == null || m_OnArriveAbility.AllowPositionalInput)) {
|
||||
m_CharacterLocomotion.InputVector = m_CharacterLocomotion.RawInputVector;
|
||||
}
|
||||
|
||||
if (arrived && !m_Arrived) {
|
||||
m_Arrived = true;
|
||||
// The character should completely stop moving when they have arrived when using a precision start. Return early to allow the animator
|
||||
// to start transitioning to the next frame.
|
||||
if (m_MoveTowardsLocation.PrecisionStart) {
|
||||
m_CharacterLocomotion.ResetRotationPosition();
|
||||
m_PrecisionStartWait = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the character isn't making any progress teleport them to the starting location and start the arrive ability.
|
||||
if (!m_Arrived) {
|
||||
if (m_CharacterLocomotion.Velocity.sqrMagnitude <= 0.0001f && m_CharacterLocomotion.Torque.eulerAngles.sqrMagnitude <= 0.0001f) {
|
||||
if (m_ForceStartEvent == null) {
|
||||
m_ForceStartEvent = Scheduler.Schedule(m_InactiveTimeout, ImmediateMove);
|
||||
}
|
||||
} else if (m_ForceStartEvent != null) {
|
||||
Scheduler.Cancel(m_ForceStartEvent);
|
||||
m_ForceStartEvent = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the MoveTowards ability active until the character has arrived at the destination and the ItemEquipVerifier ability isn't active.
|
||||
// This will prevent the character from sliding when ItemEquipVerifier is active and MoveTowards is not active.
|
||||
if (arrived && (m_CharacterLocomotion.ItemEquipVerifierAbility == null || !m_CharacterLocomotion.ItemEquipVerifierAbility.IsActive)) {
|
||||
if (!m_MoveTowardsLocation.PrecisionStart || !m_PrecisionStartWait) {
|
||||
StopAbility();
|
||||
} else {
|
||||
// After the character is no longer in transition the arrive ability can start. This will ensure the character always starts in the correct location.
|
||||
// For some abilities it doesn't matter if the character is in a precise position and in that case the precision start field can be disabled.
|
||||
if (m_MoveTowardsLocation.PrecisionStart && !m_AnimatorMonitor.IsInTransition(0)) {
|
||||
m_PrecisionStartWait = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the rotation that the character should rotate towards.
|
||||
/// </summary>
|
||||
/// <returns>The rotation that the character should rotate towards.</returns>
|
||||
protected virtual Quaternion GetTargetRotation()
|
||||
{
|
||||
return Quaternion.LookRotation(m_MoveTowardsLocation.TargetRotation * Vector3.forward, m_CharacterLocomotion.Up);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the input vector that the character should move with.
|
||||
/// </summary>
|
||||
/// <param name="direction">The direction that the character should move towards.</param>
|
||||
/// <returns>The input vector that the character should move with.</returns>
|
||||
protected virtual Vector2 GetInputVector(Vector3 direction)
|
||||
{
|
||||
var inputVector = Vector2.zero;
|
||||
inputVector.x = direction.x;
|
||||
inputVector.y = direction.z;
|
||||
return inputVector.normalized * m_InputMultiplier * m_MovementMultiplier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the controller's rotation values.
|
||||
/// </summary>
|
||||
public override void UpdateRotation()
|
||||
{
|
||||
if (m_PathfindingMovement != null && m_PathfindingMovement.IsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
var rotation = GetTargetRotation() * Quaternion.Inverse(m_Transform.rotation);
|
||||
var deltaRotation = m_CharacterLocomotion.DeltaRotation;
|
||||
deltaRotation.y = Mathf.MoveTowards(0, MathUtility.ClampInnerAngle(rotation.eulerAngles.y),
|
||||
m_CharacterLocomotion.MotorRotationSpeed * m_CharacterLocomotion.TimeScale * Time.timeScale * Time.deltaTime);
|
||||
m_CharacterLocomotion.DeltaRotation = deltaRotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the move direction is valid.
|
||||
/// </summary>
|
||||
public override void ApplyPosition()
|
||||
{
|
||||
if (m_PathfindingMovement != null && m_PathfindingMovement.IsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent the character from jittering back and forth to land precisely on the target.
|
||||
var moveDirection = m_Transform.InverseTransformDirection(m_CharacterLocomotion.MoveDirection);
|
||||
if (Mathf.Abs(moveDirection.x) > Mathf.Abs(m_TargetDirection.x)) {
|
||||
moveDirection.x = m_TargetDirection.x;
|
||||
}
|
||||
if (Mathf.Abs(moveDirection.z) > Mathf.Abs(m_TargetDirection.z)) {
|
||||
moveDirection.z = m_TargetDirection.z;
|
||||
}
|
||||
m_CharacterLocomotion.MoveDirection = m_Transform.TransformDirection(moveDirection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately moves to the target position/rotation.
|
||||
/// </summary>
|
||||
private void ImmediateMove()
|
||||
{
|
||||
m_CharacterLocomotion.SetPositionAndRotation(m_MoveTowardsLocation.TargetPosition, m_MoveTowardsLocation.TargetRotation, true, false);
|
||||
m_ForceStartEvent = null;
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
m_MoveTowardsLocation = null;
|
||||
if (force) {
|
||||
m_OnArriveAbility = null;
|
||||
}
|
||||
if (m_ForceStartEvent != null) {
|
||||
Scheduler.Cancel(m_ForceStartEvent);
|
||||
m_ForceStartEvent = null;
|
||||
}
|
||||
if (m_DisableGameplayInput) {
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnEnableGameplayInput", true);
|
||||
}
|
||||
|
||||
// Reset the force independet look parameter set within StartAbility.
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterForceIndependentLook", false);
|
||||
|
||||
// Start the OnArriveAbility after MoveTowards has stopped to prevent MoveTowards from affecting the arrive ability.
|
||||
if (m_OnArriveAbility != null) {
|
||||
m_CharacterLocomotion.TryStartAbility(m_OnArriveAbility, true, true);
|
||||
m_OnArriveAbility = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/MoveTowards.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/MoveTowards.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce7b0252ce062fe43b0eed257fb6d701
|
||||
timeCreated: 1513724185
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
99
Assets/Scripts/Character/Abilities/MoveWithObject.cs
Normal file
99
Assets/Scripts/Character/Abilities/MoveWithObject.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Moves with the specified object. See this page for more info on the setup required:
|
||||
/// https://opsive.com/support/documentation/ultimate-character-controller/character/abilities/included-abilities/move-with-object/
|
||||
/// </summary>
|
||||
[DefaultStopType(AbilityStopType.Automatic)]
|
||||
public class MoveWithObject : Ability
|
||||
{
|
||||
[Tooltip("The object that the character should move with.")]
|
||||
[SerializeField] protected Transform m_Target;
|
||||
|
||||
public Transform Target { get { return m_Target; }
|
||||
set {
|
||||
var prevTarget = m_Target;
|
||||
m_Target = value;
|
||||
|
||||
if (m_Target != null && m_Target.GetComponent<Game.KinematicObject>() == null) {
|
||||
Debug.Log("Error: The target " + Target.name + " does not have the Kinematic Object component. See the Move With Object documentation for more information.");
|
||||
m_Target = null;
|
||||
}
|
||||
|
||||
if (IsActive && prevTarget != null && m_Target != prevTarget) {
|
||||
m_CharacterLocomotion.SetPlatform(m_Target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsConcurrent { get { return true; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initailize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
// Set the property so it goes through the error check.
|
||||
if (m_Target != null) {
|
||||
Target = m_Target;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be started?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
if (m_Target == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CanStartAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
m_CharacterLocomotion.SetPlatform(m_Target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
if (m_Target != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CanStopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
m_CharacterLocomotion.SetPlatform(null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Assets/Scripts/Character/Abilities/MoveWithObject.cs.meta
Normal file
13
Assets/Scripts/Character/Abilities/MoveWithObject.cs.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ce9e1632a4dec94e97dbbdbd9464376
|
||||
timeCreated: 1554206872
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
340
Assets/Scripts/Character/Abilities/PickupItem.cs
Normal file
340
Assets/Scripts/Character/Abilities/PickupItem.cs
Normal file
@@ -0,0 +1,340 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.Shared.Inventory;
|
||||
using Opsive.UltimateCharacterController.Character.Abilities.Items;
|
||||
using Opsive.UltimateCharacterController.Inventory;
|
||||
using Opsive.UltimateCharacterController.Objects.CharacterAssist;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Plays an animation which picks up the item.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultInputName("Action")]
|
||||
[DefaultAbilityIndex(11)]
|
||||
[DefaultObjectDetection(ObjectDetectionMode.Trigger)]
|
||||
[DefaultReequipSlots(false)]
|
||||
public class PickupItem : DetectObjectAbilityBase
|
||||
{
|
||||
[Tooltip("The slot ID to pick up. A value of -1 indicates any slot.")]
|
||||
[SerializeField] protected int m_SlotID = -1;
|
||||
[Tooltip("Specifies a list of ItemIdentifiers that should be picked up. If the list is empty any ItemIdentifier will trigger the animation.")]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("m_PickupItemTypes")]
|
||||
[SerializeField] protected ItemDefinitionBase[] m_PickupItemDefinitions;
|
||||
[Tooltip("Specifies if the ability should wait for the OnAnimatorPickupItem animation event or wait for the specified duration before picking up the item.")]
|
||||
[SerializeField] protected AnimationEventTrigger m_PickupEvent = new AnimationEventTrigger(true, 0.2f);
|
||||
[Tooltip("Specifies if the ability should wait for the OnAnimatorPickupItemComplete animation event or wait for the specified duration before stopping the ability.")]
|
||||
[SerializeField] protected AnimationEventTrigger m_PickupCompleteEvent = new AnimationEventTrigger(false, 0.4f);
|
||||
|
||||
public int SlotID { get { return m_SlotID; } set { m_SlotID = value; } }
|
||||
public ItemDefinitionBase[] PickupItemDefinitions { get { return m_PickupItemDefinitions; } set { m_PickupItemDefinitions = value; } }
|
||||
public AnimationEventTrigger PickupEvent { get { return m_PickupEvent; } set { m_PickupEvent = value; } }
|
||||
public AnimationEventTrigger PickupCompleteEvent { get { return m_PickupCompleteEvent; } set { m_PickupCompleteEvent = value; } }
|
||||
|
||||
public override bool CanReceiveMultipleStarts { get { return true; } }
|
||||
public override bool ImmediateStartItemVerifier { get { return true; } }
|
||||
public override string AbilityMessageText {
|
||||
get {
|
||||
var message = m_AbilityMessageText;
|
||||
if (m_AvailablePickupCount > 0) {
|
||||
message = string.Format(message, m_AvailableItemPickups[0].PickupMessageText);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
set { base.AbilityMessageText = value; }
|
||||
}
|
||||
|
||||
private ItemPickupBase m_ItemPickup;
|
||||
private ItemPickupBase[] m_AvailableItemPickups;
|
||||
private int m_AvailablePickupCount;
|
||||
private EquipUnequip[] m_EquipUnequipAbilities;
|
||||
private ItemSetManagerBase m_ItemSetManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_AvailableItemPickups = new ItemPickupBase[m_MaxTriggerObjectCount];
|
||||
m_ItemSetManager = m_GameObject.GetCachedComponent<ItemSetManagerBase>();
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorPickupItem", DoItemPickup);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorPickupItemComplete", PickupComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cache the abilities.
|
||||
/// </summary>
|
||||
public override void Start()
|
||||
{
|
||||
m_EquipUnequipAbilities = m_CharacterLocomotion.GetAbilities<EquipUnequip>();
|
||||
|
||||
// Assume every ItemIdentifier can be picked up. The local mask should be set so the ability doesn't block the pickup with EquipUnequip.ShouldEquip.
|
||||
m_AllowEquippedSlotsMask = (1 << m_Inventory.SlotCount) - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the ItemPickup component should pickup the item.
|
||||
/// </summary>
|
||||
/// <returns>True if the ItemPickup component should pickup the item.</returns>
|
||||
public bool CanItemPickup()
|
||||
{
|
||||
// CanItemPickup will be called when a trigger is entered. The item can only be picked up if the start type is automatic.
|
||||
if (m_StartType != AbilityStartType.Automatic) {
|
||||
return false;
|
||||
}
|
||||
// The ItemPickup component should always pickup the item if the Ride ability is active.
|
||||
if (m_CharacterLocomotion.IsAbilityTypeActive<Ride>()) {
|
||||
return true;
|
||||
}
|
||||
// The ItemPickup component should always pickup the item if there are any active higher priority abilities active.
|
||||
for (int i = 0; i < m_CharacterLocomotion.ActiveAbilityCount; ++i) {
|
||||
if (m_CharacterLocomotion.ActiveAbilities[i].Index > Index) {
|
||||
break;
|
||||
}
|
||||
if (!m_CharacterLocomotion.ActiveAbilities[i].IsConcurrent) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return !Enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the object to ensure it is valid for the current ability.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object being validated.</param>
|
||||
/// <param name="raycastHit">The raycast hit of the detected object. Will be null for trigger detections.</param>
|
||||
/// <returns>True if the object is valid. The object may not be valid if it doesn't have an ability-specific component attached.</returns>
|
||||
protected override bool ValidateObject(GameObject obj, RaycastHit? raycastHit)
|
||||
{
|
||||
if (!base.ValidateObject(obj, raycastHit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_AvailablePickupCount > 0) {
|
||||
for (int i = 0; i < m_AvailablePickupCount; ++i) {
|
||||
if (obj == m_AvailableItemPickups[i].gameObject) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ItemPickupBase itemPickup;
|
||||
if ((itemPickup = obj.GetCachedComponent<ItemPickupBase>()) != null && !itemPickup.PickupOnTriggerEnter && !itemPickup.IsDepleted) {
|
||||
if (m_AvailableItemPickups.Length == m_AvailablePickupCount) {
|
||||
Debug.LogWarning("Warning: Unable to pickup " + itemPickup.name + ". Ensure the MaxTriggerObject count is at least " +
|
||||
(m_AvailablePickupCount + 1) + " on the PickupItem ability.");
|
||||
return false;
|
||||
}
|
||||
m_AvailableItemPickups[m_AvailablePickupCount] = itemPickup;
|
||||
m_AvailablePickupCount++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
if (m_AvailablePickupCount > 0) {
|
||||
for (int i = 0; i < m_AvailablePickupCount; ++i) {
|
||||
m_AvailableItemPickups[i] = null;
|
||||
}
|
||||
m_AvailablePickupCount = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_AvailablePickupCount == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
// If the item pickup isn't null then the ability is currently working on equipping another item.
|
||||
if (m_ItemPickup != null && m_ItemPickup != m_AvailableItemPickups[0]) {
|
||||
m_ItemPickup.DoItemIdentifierPickup(m_GameObject, m_Inventory, m_SlotID, true, false);
|
||||
}
|
||||
|
||||
m_ItemPickup = m_AvailableItemPickups[0];
|
||||
m_AvailablePickupCount--;
|
||||
for (int i = 0; i < m_AvailablePickupCount; ++i) {
|
||||
m_AvailableItemPickups[i] = m_AvailableItemPickups[i + 1];
|
||||
}
|
||||
|
||||
var itemDefinitionAmounts = m_ItemPickup.GetItemDefinitionAmounts();
|
||||
|
||||
// If the PickupItemIdentifier array contains any ItemIdentifiers then the PickupItem ability should only start if the PickupItem object contains one of the ItemIdentifiers
|
||||
// within the array. If it doesn't contain the ItemIdentifier then that ItemIdentifier should be equipped as if the PickupItem ability doesn't exist.
|
||||
var immediatePickup = false;
|
||||
if (m_PickupItemDefinitions != null && m_PickupItemDefinitions.Length > 0) {
|
||||
immediatePickup = true;
|
||||
for (int i = 0; i < m_PickupItemDefinitions.Length; ++i) {
|
||||
for (int j = 0; j < itemDefinitionAmounts.Length; ++j) {
|
||||
if (m_PickupItemDefinitions[i] == itemDefinitionAmounts[j].ItemIdentifier.GetItemDefinition()) {
|
||||
immediatePickup = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (immediatePickup) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_ItemPickup.DoItemPickup(m_GameObject, m_Inventory, m_SlotID, !immediatePickup, immediatePickup);
|
||||
|
||||
// The ability shouldn't start if the ItemIdentifier has already been picked up.
|
||||
if (immediatePickup) {
|
||||
StopAbility();
|
||||
return;
|
||||
}
|
||||
|
||||
// Before the item can be picked up the currently equipped items need to be unequipped.
|
||||
for (int i = 0; i < m_EquipUnequipAbilities.Length; ++i) {
|
||||
m_EquipUnequipAbilities[i].WillStartPickup();
|
||||
}
|
||||
var allowedEquippedSlotsMask = 0;
|
||||
for (int i = 0; i < itemDefinitionAmounts.Length; ++i) {
|
||||
var itemDefinition = itemDefinitionAmounts[i].ItemDefinition;
|
||||
var itemIdentifier = itemDefinitionAmounts[i].ItemIdentifier;
|
||||
|
||||
for (int j = 0; j < m_Inventory.SlotCount; ++j) {
|
||||
// Determine if the item should be equipped. The current item needs to be unequipped if it doesn't match the item being picked up.
|
||||
var categoryIndex = 0;
|
||||
var shouldEquip = false;
|
||||
for (int k = 0; k < m_EquipUnequipAbilities.Length; ++k) {
|
||||
if (m_ItemSetManager.IsCategoryMember(itemDefinition, m_EquipUnequipAbilities[k].ItemSetCategoryIndex) &&
|
||||
m_EquipUnequipAbilities[k].ShouldEquip(itemIdentifier, j, itemDefinitionAmounts[i].Amount)) {
|
||||
shouldEquip = true;
|
||||
categoryIndex = m_EquipUnequipAbilities[k].ItemSetCategoryIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldEquip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The item should be equipped.
|
||||
var equippedItem = m_Inventory.GetActiveItem(j);
|
||||
if (equippedItem == null || itemIdentifier != equippedItem.ItemIdentifier) {
|
||||
allowedEquippedSlotsMask |= (1 << j);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the item doesn't need to be equipped then it should still be picked up.
|
||||
if (allowedEquippedSlotsMask == 0) {
|
||||
m_ItemPickup.DoItemPickup(m_GameObject, m_Inventory, m_SlotID, false, true);
|
||||
StopAbility();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the ability index is -1 then an animation will not play and the item should be picked up immediately.
|
||||
if (m_AbilityIndexParameter == -1) {
|
||||
DoItemPickup();
|
||||
} else if (!m_PickupEvent.WaitForAnimationEvent) {
|
||||
Scheduler.ScheduleFixed(m_PickupEvent.Duration, DoItemPickup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Picks up the item.
|
||||
/// </summary>
|
||||
private void DoItemPickup()
|
||||
{
|
||||
if (m_ItemPickup == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_ItemPickup.DoItemIdentifierPickup(m_GameObject, m_Inventory, m_SlotID, true, true);
|
||||
|
||||
if (!m_PickupCompleteEvent.WaitForAnimationEvent) {
|
||||
Scheduler.ScheduleFixed(m_PickupCompleteEvent.Duration, PickupComplete);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes the ability.
|
||||
/// </summary>
|
||||
private void PickupComplete()
|
||||
{
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force, true);
|
||||
|
||||
m_ItemPickup = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has exited a trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The GameObject that the character exited.</param>
|
||||
/// <returns>Returns true if the entered object leaves the trigger.</returns>
|
||||
protected override bool TriggerExit(GameObject other)
|
||||
{
|
||||
if (base.TriggerExit(other) && !IsActive) {
|
||||
var index = -1;
|
||||
for (int i = 0; i < m_AvailablePickupCount; ++i) {
|
||||
if (m_AvailableItemPickups[i].gameObject == other) {
|
||||
m_AvailableItemPickups[i] = null;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index != -1) {
|
||||
m_AvailablePickupCount--;
|
||||
for (int i = index; i < m_AvailablePickupCount; ++i) {
|
||||
m_AvailableItemPickups[i] = m_AvailableItemPickups[i + 1];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has been destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorPickupItem", DoItemPickup);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorPickupItemComplete", PickupComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/PickupItem.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/PickupItem.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ff11b25f09f9c744b29a1e9fdded0b7
|
||||
timeCreated: 1513724196
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
157
Assets/Scripts/Character/Abilities/QuickStart.cs
Normal file
157
Assets/Scripts/Character/Abilities/QuickStart.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Start Movement ability allows the character to play a starting animation.
|
||||
/// </summary>
|
||||
[DefaultStopType(AbilityStopType.Automatic)]
|
||||
[DefaultAbilityIndex(6)]
|
||||
public class QuickStart : StoredInputAbilityBase
|
||||
{
|
||||
[Tooltip("The value which differentiates between a walk and a run.")]
|
||||
[SerializeField] protected float m_SpeedChangeThreshold = 1;
|
||||
|
||||
public float SpeedChangeThreshold { get { return m_SpeedChangeThreshold; } set { m_SpeedChangeThreshold = value; } }
|
||||
|
||||
private enum StartIndex { None, WalkForward, WalkForwardTurnLeft, WalkForwardTurnRight, WalkStrafeLeft, WalkStrafeRight, WalkBackward, WalkBackwardTurnLeft, WalkBackwardTurnRight, RunForward, RunForwardTurnLeft, RunForwardTurnRight, RunStrafeLeft, RunStrafeRight, RunBackward, RunBackwardTurnLeft, RunBackwardTurnRight }
|
||||
|
||||
private bool m_CanStart;
|
||||
private int m_StartIndex;
|
||||
private bool m_EventStop;
|
||||
|
||||
protected override bool UseRawInput { get { return true; } }
|
||||
protected override bool RequireInput { get { return false; } }
|
||||
public override int AbilityIntData { get { return m_StartIndex; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorStartMovementComplete", OnStartComplete);
|
||||
EventHandler.RegisterEvent<bool>(m_GameObject, "OnCharacterMoving", OnMoving);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has started to or stopped moving.
|
||||
/// </summary>
|
||||
/// <param name="moving">Is the character moving?</param>
|
||||
private void OnMoving(bool moving)
|
||||
{
|
||||
if (!moving) {
|
||||
// The ability can't start until the character is no longer moving. This will prevent the ability from starting repeatedly
|
||||
// while the character is moving.
|
||||
m_CanStart = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The ability can't start if the character is stopped.
|
||||
if (m_CharacterLocomotion.InputVector.sqrMagnitude == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the input count is greater than zero then the character has been moving already so the ability should not be started.
|
||||
if (m_InputCount > 0) {
|
||||
m_CanStart = false;
|
||||
}
|
||||
|
||||
return m_CanStart;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
// The start index is based on the input value.
|
||||
var inputValue = m_CharacterLocomotion.InputVector;
|
||||
m_StartIndex = (int)StartIndex.None;
|
||||
if (inputValue.x > m_SpeedChangeThreshold && inputValue.y > m_SpeedChangeThreshold) {
|
||||
m_StartIndex = (int)StartIndex.RunForwardTurnRight;
|
||||
} else if (inputValue.x > 0 && inputValue.y > 0) {
|
||||
m_StartIndex = (int)StartIndex.WalkForwardTurnRight;
|
||||
} else if (inputValue.x < -m_SpeedChangeThreshold && inputValue.y > m_SpeedChangeThreshold) {
|
||||
m_StartIndex = (int)StartIndex.RunForwardTurnLeft;
|
||||
} else if (inputValue.x < 0 && inputValue.y > 0) {
|
||||
m_StartIndex = (int)StartIndex.WalkForwardTurnLeft;
|
||||
} else if (inputValue.x < -m_SpeedChangeThreshold && inputValue.y < -m_SpeedChangeThreshold) {
|
||||
m_StartIndex = (int)StartIndex.RunBackwardTurnLeft;
|
||||
} else if (inputValue.x < 0 && inputValue.y < 0) {
|
||||
m_StartIndex = (int)StartIndex.WalkBackwardTurnLeft;
|
||||
} else if (inputValue.x > m_SpeedChangeThreshold && inputValue.y < -m_SpeedChangeThreshold) {
|
||||
m_StartIndex = (int)StartIndex.RunBackwardTurnRight;
|
||||
} else if (inputValue.x > 0 && inputValue.y < 0) {
|
||||
m_StartIndex = (int)StartIndex.WalkBackwardTurnRight;
|
||||
} else if (inputValue.y > m_SpeedChangeThreshold) {
|
||||
m_StartIndex = (int)StartIndex.RunForward;
|
||||
} else if (inputValue.y > 0) {
|
||||
m_StartIndex = (int)StartIndex.WalkForward;
|
||||
} else if (inputValue.y < -m_SpeedChangeThreshold) {
|
||||
m_StartIndex = (int)StartIndex.RunBackward;
|
||||
} else if (inputValue.y < 0) {
|
||||
m_StartIndex = (int)StartIndex.WalkBackward;
|
||||
} else if (inputValue.x > m_SpeedChangeThreshold) {
|
||||
m_StartIndex = (int)StartIndex.RunStrafeRight;
|
||||
} else if (inputValue.x > 0) {
|
||||
m_StartIndex = (int)StartIndex.WalkStrafeRight;
|
||||
} else if (inputValue.x < -m_SpeedChangeThreshold) {
|
||||
m_StartIndex = (int)StartIndex.RunStrafeLeft;
|
||||
} else if (inputValue.x < 0) {
|
||||
m_StartIndex = (int)StartIndex.WalkStrafeLeft;
|
||||
}
|
||||
|
||||
m_EventStop = false;
|
||||
m_CanStart = false;
|
||||
|
||||
base.AbilityStarted();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
return m_EventStop || m_CharacterLocomotion.RawInputVector.sqrMagnitude == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animation event callback when the start animation has completed.
|
||||
/// </summary>
|
||||
private void OnStartComplete()
|
||||
{
|
||||
m_EventStop = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorStartMovementComplete", OnStartComplete);
|
||||
EventHandler.UnregisterEvent<bool>(m_GameObject, "OnCharacterMoving", OnMoving);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/QuickStart.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/QuickStart.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9eef3540201d13c45b4414e76286efc4
|
||||
timeCreated: 1513724205
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
203
Assets/Scripts/Character/Abilities/QuickStop.cs
Normal file
203
Assets/Scripts/Character/Abilities/QuickStop.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Stop Movement ability allows the character to perform a sudden stop animation.
|
||||
/// </summary>
|
||||
[DefaultStopType(AbilityStopType.Automatic)]
|
||||
[DefaultAbilityIndex(7)]
|
||||
public class QuickStop : StoredInputAbilityBase
|
||||
{
|
||||
[Tooltip("The value which differentiates between a walk and a run.")]
|
||||
[SerializeField] protected float m_SpeedChangeThreshold = 1;
|
||||
[Tooltip("The threshold which indicates when the character has stopped.")]
|
||||
[SerializeField] protected float m_StopThreshold = 0.01f;
|
||||
[Tooltip("The number of times CanStartAbility must return true before the ability actually starts. Will prevent the ability from starting too soon.")]
|
||||
[SerializeField] protected int m_RequiredStartSuccessCount = 2;
|
||||
|
||||
public float SpeedChangeThreshold { get { return m_SpeedChangeThreshold; } set { m_SpeedChangeThreshold = value; } }
|
||||
public float StopThreshold { get { return m_StopThreshold; } set { m_StopThreshold = value; } }
|
||||
public int RequiredStartSuccessCount { get { return m_RequiredStartSuccessCount; } set { m_RequiredStartSuccessCount = value; } }
|
||||
|
||||
private enum StopIndex { None, WalkForward, WalkForwardTurnLeft, WalkForwardTurnRight, WalkStrafeLeft, WalkStrafeRight, WalkBackward, WalkBackwardTurnLeft, WalkBackwardTurnRight, RunForward, RunForwardTurnLeft, RunForwardTurnRight, RunStrafeLeft, RunStrafeRight, RunBackward, RunBackwardTurnLeft, RunBackwardTurnRight }
|
||||
|
||||
private int m_StartSuccessCount;
|
||||
private Vector2 m_AverageInput;
|
||||
private int m_StopIndex;
|
||||
private bool m_EventStop;
|
||||
|
||||
protected override bool UseRawInput { get { return false; } }
|
||||
protected override bool RequireInput { get { return true; } }
|
||||
public override int AbilityIntData { get { return m_StopIndex; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_StartSuccessCount = m_RequiredStartSuccessCount;
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorStopMovementComplete", OnStopComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The input must be moving towards 0 in order to stop.
|
||||
var input = m_CharacterLocomotion.RawInputVector;
|
||||
if (Mathf.Abs(input.x) > Mathf.Abs(m_Inputs[m_InputIndex].x) ||
|
||||
Mathf.Abs(input.y) > Mathf.Abs(m_Inputs[m_InputIndex].y)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There should be minimal current input.
|
||||
if (m_CharacterLocomotion.RawInputVector.sqrMagnitude > m_StopThreshold) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The character has to have been moving in order to stop.
|
||||
m_AverageInput = Vector2.zero;
|
||||
for (int i = 0; i < m_InputCount; ++i) {
|
||||
m_AverageInput += m_Inputs[i];
|
||||
}
|
||||
m_AverageInput /= m_InputCount;
|
||||
if (m_AverageInput.sqrMagnitude < 0.01f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
// The stop index is based on the average input value.
|
||||
m_StopIndex = (int)StopIndex.None;
|
||||
if (m_StartSuccessCount == 0) {
|
||||
DeterminStopIndex();
|
||||
}
|
||||
|
||||
m_EventStop = false;
|
||||
|
||||
base.AbilityStarted();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines which stop index should be set based on the current input.
|
||||
/// </summary>
|
||||
private void DeterminStopIndex()
|
||||
{
|
||||
if (m_AverageInput.x > m_SpeedChangeThreshold && m_AverageInput.y > m_SpeedChangeThreshold) {
|
||||
m_StopIndex = (int)StopIndex.RunForwardTurnRight;
|
||||
} else if (m_AverageInput.x > 0 && m_AverageInput.y > 0) {
|
||||
m_StopIndex = (int)StopIndex.WalkForwardTurnRight;
|
||||
} else if (m_AverageInput.x < -m_SpeedChangeThreshold && m_AverageInput.y > m_SpeedChangeThreshold) {
|
||||
m_StopIndex = (int)StopIndex.RunForwardTurnLeft;
|
||||
} else if (m_AverageInput.x < 0 && m_AverageInput.y > 0) {
|
||||
m_StopIndex = (int)StopIndex.WalkForwardTurnLeft;
|
||||
} else if (m_AverageInput.x < -m_SpeedChangeThreshold && m_AverageInput.y < -m_SpeedChangeThreshold) {
|
||||
m_StopIndex = (int)StopIndex.RunBackwardTurnLeft;
|
||||
} else if (m_AverageInput.x < 0 && m_AverageInput.y < 0) {
|
||||
m_StopIndex = (int)StopIndex.WalkBackwardTurnLeft;
|
||||
} else if (m_AverageInput.x > m_SpeedChangeThreshold && m_AverageInput.y < -m_SpeedChangeThreshold) {
|
||||
m_StopIndex = (int)StopIndex.RunBackwardTurnRight;
|
||||
} else if (m_AverageInput.x > 0 && m_AverageInput.y < 0) {
|
||||
m_StopIndex = (int)StopIndex.WalkBackwardTurnRight;
|
||||
} else if (m_AverageInput.y > m_SpeedChangeThreshold) {
|
||||
m_StopIndex = (int)StopIndex.RunForward;
|
||||
} else if (m_AverageInput.y > 0) {
|
||||
m_StopIndex = (int)StopIndex.WalkForward;
|
||||
} else if (m_AverageInput.y < -m_SpeedChangeThreshold) {
|
||||
m_StopIndex = (int)StopIndex.RunBackward;
|
||||
} else if (m_AverageInput.y < 0) {
|
||||
m_StopIndex = (int)StopIndex.WalkBackward;
|
||||
} else if (m_AverageInput.x > m_SpeedChangeThreshold) {
|
||||
m_StopIndex = (int)StopIndex.RunStrafeRight;
|
||||
} else if (m_AverageInput.x > 0) {
|
||||
m_StopIndex = (int)StopIndex.WalkStrafeRight;
|
||||
} else if (m_AverageInput.x < -m_SpeedChangeThreshold) {
|
||||
m_StopIndex = (int)StopIndex.RunStrafeLeft;
|
||||
} else if (m_AverageInput.x < 0) {
|
||||
m_StopIndex = (int)StopIndex.WalkStrafeLeft;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the ability.
|
||||
/// </summary>
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// The ability may start before the character should actually start to play the stop animation. Keep the AbilityIntData set to 0 until the stop animation should actually stop.
|
||||
// There is a delay because the character may be doing a quick turn instead of actually stopping.
|
||||
if (m_StartSuccessCount > 0) {
|
||||
if (!CanStartAbility()) {
|
||||
StopAbility();
|
||||
}
|
||||
m_StartSuccessCount--;
|
||||
if (m_StartSuccessCount == 0) {
|
||||
DeterminStopIndex();
|
||||
m_CharacterLocomotion.UpdateAbilityAnimatorParameters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
return m_EventStop || m_CharacterLocomotion.RawInputVector.sqrMagnitude > 0.001f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animation event callback when the stop animation has completed.
|
||||
/// </summary>
|
||||
private void OnStopComplete()
|
||||
{
|
||||
m_EventStop = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
// If StartSuccessCount isn't 0 then the stop ability was never fully started.
|
||||
AbilityStopped(force, m_StartSuccessCount == 0);
|
||||
|
||||
m_StartSuccessCount = m_RequiredStartSuccessCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorStopMovementComplete", OnStopComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/QuickStop.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/QuickStop.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ecb09828eba65ea4fa028cc28e14fafd
|
||||
timeCreated: 1487015903
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
133
Assets/Scripts/Character/Abilities/QuickTurn.cs
Normal file
133
Assets/Scripts/Character/Abilities/QuickTurn.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Quick Turn ability allows the character to play an explicit animation when the character turns.
|
||||
/// </summary>
|
||||
[DefaultStopType(AbilityStopType.Automatic)]
|
||||
[DefaultAbilityIndex(8)]
|
||||
public class QuickTurn : StoredInputAbilityBase
|
||||
{
|
||||
[Tooltip("The minimum value of the input vector required for the ability to start.")]
|
||||
[SerializeField] protected float m_MinInputSqrMagnitude = 0.1f;
|
||||
[Tooltip("The value which differentiates between a walk and a run.")]
|
||||
[SerializeField] protected float m_SpeedChangeThreshold = 1;
|
||||
|
||||
public float SpeedChangeThreshold { get { return m_SpeedChangeThreshold; } set { m_SpeedChangeThreshold = value; } }
|
||||
public float MinInputSqrMagnitude { get { return m_MinInputSqrMagnitude; } set { m_MinInputSqrMagnitude = value; } }
|
||||
|
||||
private Vector2 m_AverageInput;
|
||||
private float m_StateIndex;
|
||||
private bool m_EventStop;
|
||||
|
||||
protected override bool UseRawInput { get { return true; } }
|
||||
protected override bool RequireInput { get { return true; } }
|
||||
public override float AbilityFloatData { get { return m_StateIndex; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorQuickTurnComplete", OnQuickTurnComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't start the ability if the input magnitude is below the threshold.
|
||||
if (m_CharacterLocomotion.RawInputVector.sqrMagnitude < m_MinInputSqrMagnitude) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_AverageInput = Vector2.zero;
|
||||
for (int i = 0; i < m_InputCount; ++i) {
|
||||
m_AverageInput += m_Inputs[i];
|
||||
}
|
||||
m_AverageInput /= m_InputCount;
|
||||
|
||||
// In order for turn to start the input vector has move in an opposite direction.
|
||||
if ((m_CharacterLocomotion.RawInputVector.x == 0 || m_AverageInput.x == 0 || Mathf.Sign(m_AverageInput.x) == Mathf.Sign(m_CharacterLocomotion.RawInputVector.x)) &&
|
||||
(m_CharacterLocomotion.RawInputVector.y == 0 || m_AverageInput.y == 0 || Mathf.Sign(m_AverageInput.y) == Mathf.Sign(m_CharacterLocomotion.RawInputVector.y))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Diagonal turns are not accepted.
|
||||
if (Vector2.Dot(m_CharacterLocomotion.RawInputVector, m_AverageInput) > -0.5f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
if (Mathf.Abs(m_AverageInput.x) > m_SpeedChangeThreshold || Mathf.Abs(m_AverageInput.y) > m_SpeedChangeThreshold) {
|
||||
m_StateIndex = 1;
|
||||
} else {
|
||||
m_StateIndex = 0;
|
||||
}
|
||||
m_EventStop = false;
|
||||
m_CharacterLocomotion.ForceRootMotionRotation = true;
|
||||
|
||||
base.AbilityStarted();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animation event callback when the quick turn animation has completed.
|
||||
/// </summary>
|
||||
private void OnQuickTurnComplete()
|
||||
{
|
||||
m_EventStop = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
return m_EventStop || !m_CharacterLocomotion.Moving;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
m_CharacterLocomotion.ForceRootMotionRotation = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorQuickTurnComplete", OnQuickTurnComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/QuickTurn.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/QuickTurn.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7edbf74c03861ee4c911cdf5d4723b00
|
||||
timeCreated: 1510015723
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
242
Assets/Scripts/Character/Abilities/Ragdoll.cs
Normal file
242
Assets/Scripts/Character/Abilities/Ragdoll.cs
Normal file
@@ -0,0 +1,242 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.Shared.Utility;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables the ragdoll colliders. Can be started when the character dies.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.Manual)]
|
||||
[DefaultState("Death")]
|
||||
[DefaultAllowPositionalInput(false)]
|
||||
[DefaultAllowRotationalInput(false)]
|
||||
public class Ragdoll : Ability
|
||||
{
|
||||
[Tooltip("Should the ability start when the character dies?")]
|
||||
[HideInInspector] [SerializeField] protected bool m_StartOnDeath = true;
|
||||
[Tooltip("Specifies the delay after the ability starts that the character should turn into a ragdoll.")]
|
||||
[HideInInspector] [SerializeField] protected float m_StartDelay;
|
||||
[Tooltip("The layer that the colliders should switch to when the ragdoll is active. This should be set to VisualEffect if other characters should not step over the current" +
|
||||
"character when the ability is active.")]
|
||||
[HideInInspector] [SerializeField] protected int m_RagdollLayer = LayerManager.Character;
|
||||
[Tooltip("The layer that the colliders should switch to when the ragdoll is inactive.")]
|
||||
[HideInInspector] [SerializeField] protected int m_InactiveRagdollLayer = LayerManager.SubCharacter;
|
||||
[Tooltip("The amount of force to add to the camera. This value will be multiplied by the death force magnitude.")]
|
||||
[HideInInspector] [SerializeField] protected Vector3 m_CameraRotationalForce = new Vector3(0, 0, 0.75f);
|
||||
|
||||
public bool StartOnDeath { get { return m_StartOnDeath; } set { m_StartOnDeath = value; } }
|
||||
public float StartDelay { get { return m_StartDelay; } set { m_StartDelay = value; } }
|
||||
public int RagdollLayer { get { return m_RagdollLayer; } set { m_RagdollLayer = value; } }
|
||||
public int InactiveRagdollLayer { get { return m_InactiveRagdollLayer; } set { m_InactiveRagdollLayer = value; } }
|
||||
public Vector3 CameraRotationalForce { get { return m_CameraRotationalForce; } set { m_CameraRotationalForce = value; } }
|
||||
|
||||
private Rigidbody[] m_Rigidbodies;
|
||||
private GameObject[] m_RigidbodyGameObjects;
|
||||
|
||||
private Vector3 m_Force;
|
||||
private Vector3 m_Position;
|
||||
private bool m_FromDeath;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
private object[] m_StartData;
|
||||
#endif
|
||||
|
||||
[NonSerialized] public Vector3 Force { get { return m_Force; } set { m_Force = value; } }
|
||||
[NonSerialized] public Vector3 Position { get { return m_Position; } set { m_Position = value; } }
|
||||
public override bool CanStayActivatedOnDeath { get { return true; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
var characterRigidbody = m_GameObject.GetCachedComponent<Rigidbody>();
|
||||
var rigidbodies = m_GameObject.GetComponentsInChildren<Rigidbody>();
|
||||
// The character's Rigidbody should be ignored.
|
||||
var index = 0;
|
||||
m_Rigidbodies = new Rigidbody[rigidbodies.Length - 1];
|
||||
m_RigidbodyGameObjects = new GameObject[m_Rigidbodies.Length];
|
||||
for (int i = 0; i < rigidbodies.Length; ++i) {
|
||||
if (rigidbodies[i] == characterRigidbody) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_Rigidbodies[index] = rigidbodies[i];
|
||||
m_RigidbodyGameObjects[index] = rigidbodies[i].gameObject;
|
||||
index++;
|
||||
}
|
||||
|
||||
EventHandler.RegisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnWillRespawn", OnRespawn);
|
||||
|
||||
EnableRagdoll(false, Vector3.zero, Vector3.zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnCameraRotationalForce", m_CameraRotationalForce * (m_FromDeath ? m_Force.magnitude : 1));
|
||||
|
||||
Scheduler.ScheduleFixed(m_StartDelay, EnableRagdoll, true, m_Force, m_Position);
|
||||
|
||||
m_FromDeath = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables the ragdoll.
|
||||
/// </summary>
|
||||
/// <param name="enable">Should the ragdoll be enabled?</param>
|
||||
/// <param name="force">The amount of force which killed the character.</param>
|
||||
/// <param name="position">The position of the force.</param>
|
||||
private void EnableRagdoll(bool enable, Vector3 force, Vector3 position)
|
||||
{
|
||||
// When the ragdoll is active the animator should be disabled. An active animator will prevent the ragdoll from playing.
|
||||
if (m_AnimatorMonitor != null) {
|
||||
m_AnimatorMonitor.EnableAnimator(!enable);
|
||||
}
|
||||
if (enable) {
|
||||
// When the ragdoll is enabled the character should no longer have any forces applied - it's up to the ragdoll to apply the forces now.
|
||||
m_CharacterLocomotion.ResetRotationPosition();
|
||||
}
|
||||
|
||||
// The GameObject layer is going to change - enable the collision layer so it can be disabled again after the layer has been set. This will allow the controller
|
||||
// to cache the correct layers.
|
||||
m_CharacterLocomotion.EnableColliderCollisionLayer(true);
|
||||
|
||||
// Add the ragdoll force.
|
||||
for (int i = 0; i < m_Rigidbodies.Length; ++i) {
|
||||
m_Rigidbodies[i].useGravity = enable;
|
||||
m_Rigidbodies[i].collisionDetectionMode = enable ? CollisionDetectionMode.ContinuousSpeculative : CollisionDetectionMode.Discrete;
|
||||
m_Rigidbodies[i].isKinematic = !enable;
|
||||
m_Rigidbodies[i].constraints = (enable ? RigidbodyConstraints.None : RigidbodyConstraints.FreezeAll);
|
||||
m_RigidbodyGameObjects[i].layer = enable ? m_RagdollLayer : m_InactiveRagdollLayer;
|
||||
if (enable) {
|
||||
m_Rigidbodies[i].AddForceAtPosition(force, position, ForceMode.Force);
|
||||
}
|
||||
}
|
||||
m_CharacterLocomotion.EnableColliderCollisionLayer(false);
|
||||
|
||||
// The main character colliders do not contribute to the ragdoll.
|
||||
for (int i = 0; i < m_CharacterLocomotion.ColliderCount; ++i) {
|
||||
m_CharacterLocomotion.Colliders[i].enabled = !enable;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has died. Start the ability if requested.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the force.</param>
|
||||
/// <param name="force">The amount of force which killed the character.</param>
|
||||
/// <param name="attacker">The GameObject that killed the character.</param>
|
||||
private void OnDeath(Vector3 position, Vector3 force, GameObject attacker)
|
||||
{
|
||||
// The ability may not need to start from the death event.
|
||||
if (!m_StartOnDeath || !Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_Force = force * MathUtility.RigidbodyForceMultiplier;
|
||||
m_Position = position;
|
||||
m_FromDeath = true;
|
||||
|
||||
StartAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has respawned. Stop the ability if necessary.
|
||||
/// </summary>
|
||||
private void OnRespawn()
|
||||
{
|
||||
// The ability may not have been started from the death event.
|
||||
if (!m_StartOnDeath || !Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
EnableRagdoll(false, Vector3.zero, Vector3.zero);
|
||||
|
||||
base.AbilityStopped(force);
|
||||
|
||||
// Snap the animator back into position.
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterSnapAnimator");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnWillRespawn", OnRespawn);
|
||||
}
|
||||
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
/// <summary>
|
||||
/// Returns any data required to start the ability.
|
||||
/// </summary>
|
||||
/// <returns>Any data required to start the ability.</returns>
|
||||
public override object[] GetNetworkStartData()
|
||||
{
|
||||
if (!IsActive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (m_StartData == null) {
|
||||
m_StartData = new object[m_Rigidbodies.Length * 4];
|
||||
}
|
||||
// Save the rigidbody values so they can be sent across the network.
|
||||
for (int i = 0; i < m_Rigidbodies.Length; ++i) {
|
||||
m_StartData[(i * 3)] = m_Rigidbodies[i].position;
|
||||
m_StartData[(i * 3) + 1] = m_Rigidbodies[i].rotation;
|
||||
m_StartData[(i * 3) + 2] = m_Rigidbodies[i].linearVelocity;
|
||||
m_StartData[(i * 3) + 3] = m_Rigidbodies[i].angularVelocity;
|
||||
}
|
||||
return m_StartData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the start data from the network.
|
||||
/// </summary>
|
||||
/// <param name="startData">The data required to start the ability.</param>
|
||||
public override void SetNetworkStartData(object[] startData)
|
||||
{
|
||||
m_Force = Vector3.zero;
|
||||
m_Position = Vector3.zero;
|
||||
m_CharacterLocomotion.TryStartAbility(this, true, true);
|
||||
|
||||
// Restore the rigidbody momentum.
|
||||
for (int i = 0; i < m_Rigidbodies.Length; ++i) {
|
||||
m_Rigidbodies[i].position = (Vector3)m_StartData[(i * 3)];
|
||||
m_Rigidbodies[i].rotation = (Quaternion)m_StartData[(i * 3) + 1];
|
||||
m_Rigidbodies[i].linearVelocity = (Vector3)m_StartData[(i * 3) + 2];
|
||||
m_Rigidbodies[i].angularVelocity = (Vector3)m_StartData[(i * 3) + 3];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Ragdoll.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Ragdoll.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cdd696adcd872184287e637a7b75cb32
|
||||
timeCreated: 1510094644
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
90
Assets/Scripts/Character/Abilities/RestrictPosition.cs
Normal file
90
Assets/Scripts/Character/Abilities/RestrictPosition.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The RestrictPosition ability restricts the character to the specified position.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.Automatic)]
|
||||
[DefaultStopType(AbilityStopType.Manual)]
|
||||
public class RestrictPosition : Ability
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies how to restrict the character's position.
|
||||
/// </summary>
|
||||
public enum RestrictionType
|
||||
{
|
||||
RestrictX, // Restricts the local X position.
|
||||
RestrictZ, // Restricts the local Z position.
|
||||
RestrictXZ // Restricts the local X and Z position.
|
||||
}
|
||||
|
||||
[Tooltip("Specifies how to restrict the character's position.")]
|
||||
[HideInInspector] [SerializeField] protected RestrictionType m_Restriction = RestrictionType.RestrictXZ;
|
||||
[Tooltip("If restricting the X axis, specifies the minimum local X position the character can move to.")]
|
||||
[HideInInspector] [SerializeField] protected float m_MinXPosition;
|
||||
[Tooltip("If restricting the X axis, specifies the maximum local X position the character can move to.")]
|
||||
[HideInInspector] [SerializeField] protected float m_MaxXPosition;
|
||||
[Tooltip("If restricting the Z axis, specifies the minimum local Z position the character can move to.")]
|
||||
[HideInInspector] [SerializeField] protected float m_MinZPosition;
|
||||
[Tooltip("If restricting the Z axis, specifies the maximum local Z position the character can move to.")]
|
||||
[HideInInspector] [SerializeField] protected float m_MaxZPosition;
|
||||
|
||||
public RestrictionType Restiction { get { return m_Restriction; } set { m_Restriction = value; } }
|
||||
public float MinXPosition { get { return m_MinXPosition; } set { m_MinXPosition = value; } }
|
||||
public float MaxXPosition { get { return m_MaxXPosition; } set { m_MaxXPosition = value; } }
|
||||
public float MinZPosition { get { return m_MinZPosition; } set { m_MinZPosition = value; } }
|
||||
public float MaxZPosition { get { return m_MaxZPosition; } set { m_MaxZPosition = value; } }
|
||||
|
||||
public override bool IsConcurrent { get { return true; } }
|
||||
|
||||
/// <summary>
|
||||
/// Restrict the move direction if the character would be outside the valid position.
|
||||
/// </summary>
|
||||
public override void ApplyPosition()
|
||||
{
|
||||
var targetPosition = m_Transform.position + m_CharacterLocomotion.MoveDirection;
|
||||
if (RestrictedPosition(ref targetPosition)) {
|
||||
m_CharacterLocomotion.MoveDirection = targetPosition - m_Transform.position;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the target position to the restricted position. Will return true if the position is restricted.
|
||||
/// </summary>
|
||||
/// <param name="targetPosition">The target position that should be restricted.</param>
|
||||
/// <returns>True if the position is restricted.</returns>
|
||||
public bool RestrictedPosition(ref Vector3 targetPosition)
|
||||
{
|
||||
var restricted = false;
|
||||
// Restrict the x axis if the constraint is set to anything but RestrictZ.
|
||||
if (m_Restriction != RestrictionType.RestrictZ) {
|
||||
if (targetPosition.x < m_MinXPosition) {
|
||||
targetPosition.x = m_MinXPosition;
|
||||
restricted = true;
|
||||
} else if (targetPosition.x > m_MaxXPosition) {
|
||||
targetPosition.x = m_MaxXPosition;
|
||||
restricted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Restrict the z axis if the constraint is set to anything but RestrictX.
|
||||
if (m_Restriction != RestrictionType.RestrictX) {
|
||||
if (targetPosition.z < m_MinZPosition) {
|
||||
targetPosition.z = m_MinZPosition;
|
||||
restricted = true;
|
||||
} else if (targetPosition.z > m_MaxZPosition) {
|
||||
targetPosition.z = m_MaxZPosition;
|
||||
restricted = true;
|
||||
}
|
||||
}
|
||||
return restricted;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/RestrictPosition.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/RestrictPosition.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e30f187474bc5444bbb7bf73696da90
|
||||
timeCreated: 1506284992
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
96
Assets/Scripts/Character/Abilities/RestrictRotation.cs
Normal file
96
Assets/Scripts/Character/Abilities/RestrictRotation.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The RestrictPosition ability restricts the character to the specified rotation.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.Automatic)]
|
||||
[DefaultStopType(AbilityStopType.Manual)]
|
||||
public class RestrictRotation : Ability
|
||||
{
|
||||
[Tooltip("The number of degrees that the character can rotate between.")]
|
||||
[SerializeField] protected float m_Restriction = 45f;
|
||||
[Tooltip("Any offset that should be applied to the local y rotation.")]
|
||||
[SerializeField] protected float m_Offset;
|
||||
[Tooltip("Should the local y rotation of the look source be applied to the rotation?")]
|
||||
[SerializeField] protected bool m_RelativeLookSourceRotation;
|
||||
[Tooltip("Any offset that should be applied to the look source y rotation.")]
|
||||
[SerializeField] protected float m_LookSourceOffset;
|
||||
|
||||
public float Restriction { get { return m_Restriction; } set { m_Restriction = value; } }
|
||||
public float Offset { get { return m_Offset; } set { m_Offset = value; } }
|
||||
public bool RelativeLookSourceRotation { get { return m_RelativeLookSourceRotation; } set { m_RelativeLookSourceRotation = value; } }
|
||||
public float LookSourceOffset { get { return m_LookSourceOffset; } set { m_LookSourceOffset = value; } }
|
||||
|
||||
private ILookSource m_LookSource;
|
||||
|
||||
public override bool IsConcurrent { get { return true; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
// The look source may have already been assigned if the ability was added to the character after the look source was assigned.
|
||||
m_LookSource = m_CharacterLocomotion.LookSource;
|
||||
EventHandler.RegisterEvent<ILookSource>(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new ILookSource object has been attached to the character.
|
||||
/// </summary>
|
||||
/// <param name="lookSource">The ILookSource object attached to the character.</param>
|
||||
private void OnAttachLookSource(ILookSource lookSource)
|
||||
{
|
||||
m_LookSource = lookSource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify the rotation values. Called immediately before the rotation is applied.
|
||||
/// </summary>
|
||||
public override void ApplyRotation()
|
||||
{
|
||||
var targetRotation = m_Transform.rotation * Quaternion.Euler(m_CharacterLocomotion.DeltaRotation);
|
||||
var localTargetRotation = MathUtility.InverseTransformQuaternion(Quaternion.LookRotation(Vector3.forward, m_CharacterLocomotion.Up), targetRotation);
|
||||
|
||||
// Find the closest angle to the degree restriction.
|
||||
var localEulerRotation = localTargetRotation.eulerAngles;
|
||||
var offset = m_Offset;
|
||||
|
||||
// The rotation can be applied based on the look source angle.
|
||||
if (m_RelativeLookSourceRotation) {
|
||||
var localLookSourceRotation = MathUtility.InverseTransformQuaternion(Quaternion.LookRotation(Vector3.forward, m_CharacterLocomotion.Up), m_LookSource.Transform.rotation);
|
||||
offset += (localLookSourceRotation.eulerAngles.y + m_LookSourceOffset);
|
||||
}
|
||||
|
||||
// Set the restricted rotation.
|
||||
localEulerRotation.y = MathUtility.ClampAngle(Mathf.Round((localEulerRotation.y - offset) / m_Restriction) * m_Restriction) + offset;
|
||||
|
||||
// Rotate towards the restricted angle.
|
||||
targetRotation = MathUtility.TransformQuaternion(Quaternion.LookRotation(Vector3.forward, m_CharacterLocomotion.Up), Quaternion.Euler(localEulerRotation));
|
||||
targetRotation = Quaternion.Slerp(m_Transform.rotation, targetRotation, m_CharacterLocomotion.MotorRotationSpeed * Time.deltaTime);
|
||||
m_CharacterLocomotion.Torque = targetRotation * Quaternion.Inverse(m_Transform.rotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<ILookSource>(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/RestrictRotation.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/RestrictRotation.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 193250b83fbbde146aec43a2cfc012f5
|
||||
timeCreated: 1506284992
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
131
Assets/Scripts/Character/Abilities/Revive.cs
Normal file
131
Assets/Scripts/Character/Abilities/Revive.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Traits;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Revive ability will play a standing animation from laying on the ground. The OnRespawn event will be executed when the animation is complete.
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.Manual)]
|
||||
[DefaultState("Death")]
|
||||
[DefaultAbilityIndex(5)]
|
||||
public class Revive : Ability
|
||||
{
|
||||
[Tooltip("Should the ability start when the character dies?")]
|
||||
[SerializeField] protected bool m_StartOnDeath;
|
||||
[Tooltip("Specifies the number of seconds after the character dies that the ability should start.")]
|
||||
[SerializeField] protected float m_DeathStartDelay = 3;
|
||||
|
||||
public bool StartOnDeath { get { return m_StartOnDeath; } set { m_StartOnDeath = value; } }
|
||||
public float DeathStartDelay { get { return m_DeathStartDelay; } set { m_DeathStartDelay = value; } }
|
||||
|
||||
private Respawner m_Respawner;
|
||||
|
||||
public override bool CanStayActivatedOnDeath { get { return true; } }
|
||||
|
||||
/// <summary>
|
||||
/// The type of animation that the ability should play.
|
||||
/// </summary>
|
||||
private enum ReviveType {
|
||||
Forward, // Play a forward revive animation.
|
||||
Backward // Play a backward revive animation.
|
||||
}
|
||||
|
||||
private int m_ReviveTypeIndex;
|
||||
|
||||
public override int AbilityIntData { get { return m_ReviveTypeIndex; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_Respawner = m_GameObject.GetCachedComponent<Respawner>();
|
||||
if (!m_Respawner) {
|
||||
Debug.LogError("Error: The Revive ability requires the Respawner component to be added to the character.");
|
||||
return;
|
||||
}
|
||||
|
||||
// If the ragdoll is starting when the character dies then the respawner will be called manually and should not reposition the character.
|
||||
if (m_StartOnDeath && Enabled) {
|
||||
m_Respawner.PositioningMode = Respawner.SpawnPositioningMode.None;
|
||||
m_Respawner.ScheduleRespawnOnDeath = false;
|
||||
m_Respawner.ScheduleRespawnOnDisable = false;
|
||||
}
|
||||
|
||||
EventHandler.RegisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorReviveComplete", OnReviveComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has died. Start the ability if requested.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the force.</param>
|
||||
/// <param name="force">The amount of force which killed the character.</param>
|
||||
/// <param name="attacker">The GameObject that killed the character.</param>
|
||||
private void OnDeath(Vector3 position, Vector3 force, GameObject attacker)
|
||||
{
|
||||
// The ability may not need to start from the death event.
|
||||
if (!m_StartOnDeath) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_ReviveTypeIndex = GetReviveTypeIndex(position, force, attacker);
|
||||
Scheduler.ScheduleFixed(m_DeathStartDelay, StartRevive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value that the AbilityIntData parameter should be set to.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the force.</param>
|
||||
/// <param name="force">The amount of force which killed the character.</param>
|
||||
/// <param name="attacker">The GameObject that killed the character.</param>
|
||||
/// <returns>The value that the AbilityIntData parameter should be set to.</returns>
|
||||
protected virtual int GetReviveTypeIndex(Vector3 position, Vector3 force, GameObject attacker)
|
||||
{
|
||||
return (int)(m_Transform.InverseTransformPoint(position).z > 0 ? ReviveType.Forward : ReviveType.Backward);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the ability.
|
||||
/// </summary>
|
||||
private void StartRevive()
|
||||
{
|
||||
StartAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The revive animation has completed.
|
||||
/// </summary>
|
||||
private void OnReviveComplete()
|
||||
{
|
||||
StopAbility();
|
||||
|
||||
// The respawn component will perform the necessary cleanup after the character has died.
|
||||
if (m_StartOnDeath) {
|
||||
m_Respawner.Respawn();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the character is destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorReviveComplete", OnReviveComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Revive.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Revive.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c0268d189a5d6f498c5ec9f68527be3
|
||||
timeCreated: 1510015723
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
402
Assets/Scripts/Character/Abilities/Ride.cs
Normal file
402
Assets/Scripts/Character/Abilities/Ride.cs
Normal file
@@ -0,0 +1,402 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.UltimateCharacterController.Objects.CharacterAssist;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Ride ability allows the character to ride another Ultimate Character Locomotion character (such as a horse).
|
||||
/// </summary>
|
||||
[DefaultStartType(AbilityStartType.ButtonDown)]
|
||||
[DefaultStopType(AbilityStopType.ButtonToggle)]
|
||||
[DefaultInputName("Action")]
|
||||
[DefaultAbilityIndex(12)]
|
||||
[DefaultAllowRotationalInput(false)]
|
||||
[DefaultUseGravity(AbilityBoolOverride.False)]
|
||||
[DefaultDetectHorizontalCollisions(AbilityBoolOverride.False)]
|
||||
[DefaultDetectVerticalCollisions(AbilityBoolOverride.False)]
|
||||
[DefaultUseRootMotionPosition(AbilityBoolOverride.True)]
|
||||
[DefaultUseRootMotionRotation(AbilityBoolOverride.True)]
|
||||
[DefaultEquippedSlots(0)]
|
||||
[AllowDuplicateTypes]
|
||||
public class Ride : DetectObjectAbilityBase, Items.IItemToggledReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the current status of the character.
|
||||
/// </summary>
|
||||
private enum RideState
|
||||
{
|
||||
Mount, // The character is mounting the object.
|
||||
Ride, // The character is riding the object.
|
||||
WaitForItemUnequip, // The character is waiting for the item to be unequipped so it can then start to dismount.
|
||||
Dismount, // The character is dismounting from the object.
|
||||
DismountComplete // The character is no longer on the rideable object.
|
||||
}
|
||||
|
||||
[Tooltip("Specifies if the ability should wait for the OnAnimatorMount animation event or wait for the specified duration before mounting to the rideable object.")]
|
||||
[SerializeField] protected AnimationEventTrigger m_MountEvent;
|
||||
[Tooltip("The speed at which the character moves towards the ride location.")]
|
||||
[SerializeField] protected float m_MoveSpeed = 0.2f;
|
||||
[Tooltip("The speed at which the character rotates towards ride location.")]
|
||||
[SerializeField] protected float m_RotationSpeed = 2f;
|
||||
[Tooltip("Specifies if the ability should wait for the OnAnimatorDismount animation event or wait for the specified duration before dismounting from the rideable object.")]
|
||||
[SerializeField] protected AnimationEventTrigger m_DismountEvent;
|
||||
[Tooltip("After the character mounts should the ability reequip the item that the character had before mounting?")]
|
||||
[SerializeField] protected bool m_ReequipItemAfterMount = true;
|
||||
|
||||
public AnimationEventTrigger MountEvent { get { return m_MountEvent; } set { m_MountEvent = value; } }
|
||||
public float MoveSpeed { get { return m_MoveSpeed; } set { m_MoveSpeed = value; } }
|
||||
public float RotationSpeed { get { return m_RotationSpeed; } set { m_RotationSpeed = value; } }
|
||||
public AnimationEventTrigger DismountEvent { get { return m_DismountEvent; } set { m_DismountEvent = value; } }
|
||||
public bool ReequipItemAfterMount { get { return m_ReequipItemAfterMount; } set { m_ReequipItemAfterMount = value; } }
|
||||
|
||||
private Rideable m_Rideable;
|
||||
|
||||
private bool m_LeftMount;
|
||||
private KinematicObjectManager.UpdateLocation m_StartUpdateLocation;
|
||||
private ScheduledEventBase m_MountDismountEvent;
|
||||
private RideState m_RideState = RideState.DismountComplete;
|
||||
private float m_Epsilon = 0.99999f;
|
||||
|
||||
public override int AbilityIntData
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_RideState == RideState.Mount) {
|
||||
return m_LeftMount ? 1 : 2;
|
||||
} else if (m_RideState == RideState.Ride) {
|
||||
return 3;
|
||||
} else if (m_RideState == RideState.Dismount) {
|
||||
return m_LeftMount ? 4 : 5;
|
||||
}
|
||||
|
||||
return base.AbilityIntData;
|
||||
}
|
||||
}
|
||||
public UltimateCharacterLocomotion CharacterLocomotion { get { return m_CharacterLocomotion; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorRideMount", OnMount);
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorRideDismount", OnDismount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the object to ensure it is valid for the current ability.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object being validated.</param>
|
||||
/// <param name="raycastHit">The raycast hit of the detected object. Will be null for trigger detections.</param>
|
||||
/// <returns>True if the object is valid. The object may not be valid if it doesn't have an ability-specific component attached.</returns>
|
||||
protected override bool ValidateObject(GameObject obj, RaycastHit? raycastHit)
|
||||
{
|
||||
if (!base.ValidateObject(obj, raycastHit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var characterLocomotion = obj.GetCachedParentComponent<UltimateCharacterLocomotion>();
|
||||
if (characterLocomotion == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// A rideable object must be added to the character.
|
||||
var rideable = characterLocomotion.GetAbility<Rideable>();
|
||||
if (rideable == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Rideable = rideable;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the possible MoveTowardsLocations that the character can move towards.
|
||||
/// </summary>
|
||||
/// <returns>The possible MoveTowardsLocations that the character can move towards.</returns>
|
||||
public override MoveTowardsLocation[] GetMoveTowardsLocations()
|
||||
{
|
||||
return m_Rideable.GameObject.GetComponentsInChildren<MoveTowardsLocation>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
m_StartUpdateLocation = m_CharacterLocomotion.UpdateLocation;
|
||||
// Used FixedUpdate so the root motion location is accurate when getting on the Rideable object.
|
||||
m_CharacterLocomotion.UpdateLocation = KinematicObjectManager.UpdateLocation.FixedUpdate;
|
||||
|
||||
m_LeftMount = m_Rideable.Transform.InverseTransformPoint(m_Transform.position).x < 0;
|
||||
m_Rideable.Mount(this);
|
||||
m_CharacterLocomotion.SetPlatform(m_Rideable.Transform);
|
||||
|
||||
// The character will look independently of the rotation.
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterForceIndependentLook", true);
|
||||
m_RideState = RideState.Mount;
|
||||
m_CharacterLocomotion.UpdateAbilityAnimatorParameters();
|
||||
|
||||
// Update the rideable object's parameters as well so it can stay synchronized to the ride obejct.
|
||||
m_Rideable.CharacterLocomotion.UpdateAbilityAnimatorParameters();
|
||||
if (!m_MountEvent.WaitForAnimationEvent) {
|
||||
m_MountDismountEvent = Scheduler.Schedule(m_MountEvent.Duration, OnMount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when another ability is attempting to start and the current ability is active.
|
||||
/// Returns true or false depending on if the new ability should be blocked from starting.
|
||||
/// </summary>
|
||||
/// <param name="startingAbility">The ability that is starting.</param>
|
||||
/// <returns>True if the ability should be blocked.</returns>
|
||||
public override bool ShouldBlockAbilityStart(Ability startingAbility)
|
||||
{
|
||||
#if THIRD_PERSON_CONTROLLER
|
||||
if (startingAbility is ThirdPersonController.Character.Abilities.Items.ItemPullback) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MELEE
|
||||
if (startingAbility is Items.InAirMeleeUse) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
// The character cannot interact with any items while mounting/dismounting.
|
||||
if (m_RideState != RideState.Ride && startingAbility is Items.ItemAbility) {
|
||||
return true;
|
||||
}
|
||||
return startingAbility is HeightChange || base.ShouldBlockAbilityStart(startingAbility);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the current ability is attempting to start and another ability is active.
|
||||
/// Returns true or false depending on if the active ability should be stopped.
|
||||
/// </summary>
|
||||
/// <param name="activeAbility">The ability that is currently active.</param>
|
||||
/// <returns>True if the ability should be stopped.</returns>
|
||||
public override bool ShouldStopActiveAbility(Ability activeAbility)
|
||||
{
|
||||
#if THIRD_PERSON_CONTROLLER
|
||||
if (activeAbility is ThirdPersonController.Character.Abilities.Items.ItemPullback) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return base.ShouldStopActiveAbility(activeAbility);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mounts the character on the object.
|
||||
/// </summary>
|
||||
private void OnMount()
|
||||
{
|
||||
m_RideState = RideState.Ride;
|
||||
m_CharacterLocomotion.ForceRootMotionPosition = false;
|
||||
m_CharacterLocomotion.AllowRootMotionPosition = false;
|
||||
m_CharacterLocomotion.UpdateLocation = m_StartUpdateLocation;
|
||||
m_CharacterLocomotion.UpdateAbilityAnimatorParameters();
|
||||
m_Rideable.OnCharacterMount();
|
||||
|
||||
// The item was unequipped when mounting - it may need to be reequiped again.
|
||||
if (m_CharacterLocomotion.ItemEquipVerifierAbility != null) {
|
||||
if (m_ReequipItemAfterMount) {
|
||||
m_CharacterLocomotion.ItemEquipVerifierAbility.TryToggleItem(this, false);
|
||||
} else {
|
||||
m_CharacterLocomotion.ItemEquipVerifierAbility.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the input vector.
|
||||
/// </summary>
|
||||
public override void Update()
|
||||
{
|
||||
if (m_RideState == RideState.Ride || m_RideState == RideState.WaitForItemUnequip) {
|
||||
// The input parameters should match the rideable object's input parameters.
|
||||
m_CharacterLocomotion.InputVector = m_Rideable.CharacterLocomotion.InputVector;
|
||||
m_CharacterLocomotion.DeltaRotation = m_Rideable.CharacterLocomotion.DeltaRotation;
|
||||
} else if (m_RideState == RideState.DismountComplete) {
|
||||
StopAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the controller's rotation values.
|
||||
/// </summary>
|
||||
public override void UpdateRotation()
|
||||
{
|
||||
var deltaRotation = Quaternion.identity;
|
||||
var rotation = m_Transform.rotation;
|
||||
if (m_RideState != RideState.Ride && m_RideState != RideState.WaitForItemUnequip) {
|
||||
var upNormal = m_RideState == RideState.Mount ? m_Rideable.Transform.up : -m_CharacterLocomotion.GravityDirection;
|
||||
// When the character is starting to mount they should rotate to face the same up direction as the rideable object. This allows the character to enter while on slopes.
|
||||
// Similarly, when the character exits they should rotate to the gravity direction.
|
||||
var proj = (rotation * Vector3.forward) - Vector3.Dot(rotation * Vector3.forward, upNormal) * upNormal;
|
||||
if (proj.sqrMagnitude > 0.0001f) {
|
||||
var speed = m_RotationSpeed * m_CharacterLocomotion.TimeScale * Time.timeScale * Time.deltaTime * (m_RideState == RideState.DismountComplete ? 100 : 1);
|
||||
var targetRotation = Quaternion.Slerp(rotation, Quaternion.LookRotation(proj, upNormal), speed);
|
||||
deltaRotation = deltaRotation * (Quaternion.Inverse(rotation) * targetRotation);
|
||||
}
|
||||
} else if (m_Rideable.RideLocation != null) {
|
||||
// The character should fully rotate towards the target rotation after they have mounted.
|
||||
deltaRotation = MathUtility.InverseTransformQuaternion(m_Transform.rotation, m_Rideable.RideLocation.rotation);
|
||||
}
|
||||
m_CharacterLocomotion.DeltaRotation = deltaRotation.eulerAngles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the controller's position values.
|
||||
/// </summary>
|
||||
public override void UpdatePosition()
|
||||
{
|
||||
if (m_RideState != RideState.Ride && m_RideState != RideState.WaitForItemUnequip) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_CharacterLocomotion.MotorThrottle = Vector3.zero;
|
||||
var deltaPosition = Vector3.MoveTowards(m_Transform.position, m_Rideable.RideLocation.position, m_MoveSpeed) - m_Transform.position;
|
||||
m_CharacterLocomotion.AbilityMotor = deltaPosition / (m_CharacterLocomotion.TimeScaleSquared * Time.timeScale * TimeUtility.FramerateDeltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback when the ability tries to be stopped. Start the dismount.
|
||||
/// </summary>
|
||||
public override void WillTryStopAbility()
|
||||
{
|
||||
if (m_RideState != RideState.Ride) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The character may not have space to dismount.
|
||||
if (!m_Rideable.CanDismount(ref m_LeftMount)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_CharacterLocomotion.ItemEquipVerifierAbility != null) {
|
||||
// Don't allow a dismount if the character is equipping an item just after mounting.
|
||||
if (m_CharacterLocomotion.ItemEquipVerifierAbility.IsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If an item is equipped then it should first be unequipped before dismounting.
|
||||
if (m_CharacterLocomotion.ItemEquipVerifierAbility.TryToggleItem(this, true)) {
|
||||
m_RideState = RideState.WaitForItemUnequip;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
StartDismount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts to dismount from the RideableObject.
|
||||
/// </summary>
|
||||
private void StartDismount()
|
||||
{
|
||||
m_RideState = RideState.Dismount;
|
||||
m_CharacterLocomotion.AbilityMotor = Vector3.zero;
|
||||
m_CharacterLocomotion.ForceRootMotionPosition = true;
|
||||
m_CharacterLocomotion.AllowRootMotionPosition = true;
|
||||
m_CharacterLocomotion.UpdateAbilityAnimatorParameters();
|
||||
// Update the rideable object's parameters as well so it can stay synchronized to the ride obejct.
|
||||
m_Rideable.CharacterLocomotion.UpdateAbilityAnimatorParameters();
|
||||
m_Rideable.StartDismount();
|
||||
|
||||
// If the ability is active then it should also be stopped.
|
||||
var aimAbility = m_CharacterLocomotion.GetAbility<Items.Aim>();
|
||||
if (aimAbility != null) {
|
||||
aimAbility.StopAbility();
|
||||
}
|
||||
|
||||
if (!m_DismountEvent.WaitForAnimationEvent) {
|
||||
m_MountDismountEvent = Scheduler.Schedule(m_DismountEvent.Duration, OnDismount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has dismounted - stop the ability.
|
||||
/// </summary>
|
||||
private void OnDismount()
|
||||
{
|
||||
m_RideState = RideState.DismountComplete;
|
||||
m_Rideable.Dismounted();
|
||||
m_Rideable = null;
|
||||
m_CharacterLocomotion.SetPlatform(null);
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterForceIndependentLook", false);
|
||||
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the ability be stopped?
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
// The character has to be dismounted in order to stop.
|
||||
return m_RideState == RideState.DismountComplete && Vector3.Dot(m_Transform.rotation * Vector3.up, -m_CharacterLocomotion.GravityDirection) >= m_Epsilon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
if (m_MountDismountEvent != null) {
|
||||
Scheduler.Cancel(m_MountDismountEvent);
|
||||
m_MountDismountEvent = null;
|
||||
}
|
||||
|
||||
// If the state isn't complete then the ability was force stopped.
|
||||
if (m_RideState != RideState.DismountComplete) {
|
||||
m_Rideable.StartDismount();
|
||||
m_Rideable.Dismounted();
|
||||
m_Rideable = null;
|
||||
m_CharacterLocomotion.SetPlatform(null);
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterForceIndependentLook", false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ItemEquipVerifier ability has toggled an item slot.
|
||||
/// </summary>
|
||||
public void ItemToggled()
|
||||
{
|
||||
if (m_RideState != RideState.WaitForItemUnequip) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The character can dismount as soon as the item is no longer equipped.
|
||||
StartDismount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorRideMount", OnMount);
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorRideDismount", OnDismount);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Ride.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Ride.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f2d7e39c7463c14d80074e7edacbafe
|
||||
timeCreated: 1527188573
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
238
Assets/Scripts/Character/Abilities/Rideable.cs
Normal file
238
Assets/Scripts/Character/Abilities/Rideable.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using UnityEngine;
|
||||
using Opsive.Shared.Utility;
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Added to a Ultimate Character Controller character that can be ridden.
|
||||
/// </summary>
|
||||
[DefaultAbilityIndex(12)]
|
||||
[DefaultStartType(AbilityStartType.Manual)]
|
||||
[DefaultStopType(AbilityStopType.Manual)]
|
||||
public class Rideable : Ability
|
||||
{
|
||||
[Tooltip("The Transform that the character should be located to after mounting on the object.")]
|
||||
[SerializeField] protected Transform m_RideLocation;
|
||||
[Tooltip("The collider area that should be checked to determine if the character can dismount on the left side.")]
|
||||
[SerializeField] protected Collider m_LeftDismountCollider;
|
||||
[Tooltip("The collider area that should be checked to determine if the character can dismount on the right side.")]
|
||||
[SerializeField] protected Collider m_RightDismountCollider;
|
||||
|
||||
public Transform RideLocation { get { return m_RideLocation; } }
|
||||
[NonSerialized] public Collider LeftDismountCollider { get { return m_LeftDismountCollider; } set { m_LeftDismountCollider = value; } }
|
||||
[NonSerialized] public Collider RightDismountCollider { get { return m_RightDismountCollider; } set { m_RightDismountCollider = value; } }
|
||||
|
||||
private Collider[] m_OriginalColliders;
|
||||
private Transform m_PreviousParent;
|
||||
private Ride m_Ride;
|
||||
private Collider[] m_OverlapColliders;
|
||||
|
||||
public override int AbilityIntData
|
||||
{
|
||||
get
|
||||
{
|
||||
// The rideable character should stay synchronized with the ride character.
|
||||
return m_Ride.AbilityIntData;
|
||||
}
|
||||
}
|
||||
public override bool IsConcurrent { get { return true; } }
|
||||
|
||||
public UltimateCharacterLocomotion CharacterLocomotion { get { return m_CharacterLocomotion; } }
|
||||
public GameObject GameObject { get { return m_GameObject; } }
|
||||
public Transform Transform { get { return m_Transform; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
if (m_RideLocation == null) {
|
||||
Debug.LogWarning("Warning: The RidePLocation is null. This should be set to the Transform that the Ride character is parented to.");
|
||||
m_RideLocation = m_Transform;
|
||||
}
|
||||
m_OverlapColliders = new Collider[1];
|
||||
|
||||
// The colliders do not need to be enabled for the physics check and they should not interfere with any other objects.
|
||||
if (m_LeftDismountCollider != null) {
|
||||
m_LeftDismountCollider.enabled = false;
|
||||
}
|
||||
if (m_RightDismountCollider != null) {
|
||||
m_RightDismountCollider.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The RideableObject should start off not responding to input.
|
||||
/// </summary>
|
||||
public override void Start()
|
||||
{
|
||||
EventHandler.ExecuteEvent<bool>(m_GameObject, "OnEnableGameplayInput", false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has mounted on the RideableObject.
|
||||
/// </summary>
|
||||
/// <param name="ride">The character's Ride ability that mounted on the RideableObject.</param>
|
||||
public void Mount(Ride ride)
|
||||
{
|
||||
m_Ride = ride;
|
||||
// The Ride character should update after the current character. This ensures the Ride
|
||||
// character moves the same distance during the same frame as the Rideable character.
|
||||
m_Ride.CharacterLocomotion.ManualMove = true;
|
||||
|
||||
StartAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has started.
|
||||
/// </summary>
|
||||
protected override void AbilityStarted()
|
||||
{
|
||||
base.AbilityStarted();
|
||||
|
||||
// Set the parent of the character so it moves with the rideable object.
|
||||
var characterLocomotion = m_Ride.CharacterLocomotion;
|
||||
m_PreviousParent = characterLocomotion.transform.parent;
|
||||
characterLocomotion.transform.parent = m_RideLocation;
|
||||
|
||||
if (m_OriginalColliders == null) {
|
||||
m_OriginalColliders = new Collider[m_CharacterLocomotion.ColliderCount];
|
||||
} else if (m_OriginalColliders.Length < m_CharacterLocomotion.ColliderCount) {
|
||||
System.Array.Resize(ref m_OriginalColliders, m_CharacterLocomotion.ColliderCount);
|
||||
}
|
||||
for (int i = 0; i < m_CharacterLocomotion.ColliderCount; ++i) {
|
||||
m_OriginalColliders[i] = m_CharacterLocomotion.Colliders[i];
|
||||
}
|
||||
|
||||
// The rideable object should ignore the character's colliders.
|
||||
m_CharacterLocomotion.AddColliders(characterLocomotion.Colliders);
|
||||
m_CharacterLocomotion.AddIgnoredColliders(characterLocomotion.IgnoredColliders);
|
||||
|
||||
// The character should ignore the rideable object's layers. This will prevent the character from detecting the rideable colliders.
|
||||
characterLocomotion.AddIgnoredColliders(m_OriginalColliders);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has mounted on the Rideable character.
|
||||
/// </summary>
|
||||
public void OnCharacterMount()
|
||||
{
|
||||
// Enable input so the RideableObject can move.
|
||||
EventHandler.ExecuteEvent<bool>(m_GameObject, "OnEnableGameplayInput", true);
|
||||
m_CharacterLocomotion.UpdateAbilityAnimatorParameters();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the ride character after the current character has moved.
|
||||
/// </summary>
|
||||
public override void LateUpdate()
|
||||
{
|
||||
KinematicObjectManager.CharacterMove(m_Ride.CharacterLocomotion.KinematicObjectIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the character dismount? The MoveTowardsLocation dismount colliders must be clear for the charcter to be able to dismount.
|
||||
/// </summary>
|
||||
/// <param name="leftDismount">Should the character dismount from the left side?</param>
|
||||
/// <returns>True if the character can dismount.</returns>
|
||||
public bool CanDismount(ref bool leftDismount)
|
||||
{
|
||||
var dismountCollider = leftDismount ? m_LeftDismountCollider : m_RightDismountCollider;
|
||||
if (!DismountColliderOverlap(dismountCollider)) {
|
||||
return true;
|
||||
}
|
||||
// If the original direction can't be used then try the other side.
|
||||
dismountCollider = leftDismount ? m_RightDismountCollider : m_LeftDismountCollider;
|
||||
if (!DismountColliderOverlap(dismountCollider)) {
|
||||
leftDismount = !leftDismount;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the collider overlapping with any other objects?
|
||||
/// </summary>
|
||||
/// <param name="dismountCollider">The collider to determine if it is overlapping with another object.</param>
|
||||
/// <returns>True if the collider is overlapping.</returns>
|
||||
private bool DismountColliderOverlap(Collider dismountCollider)
|
||||
{
|
||||
if (dismountCollider == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int hitCount;
|
||||
if (dismountCollider is CapsuleCollider) {
|
||||
Vector3 startEndCap, endEndCap;
|
||||
var capsuleCollider = dismountCollider as CapsuleCollider;
|
||||
MathUtility.CapsuleColliderEndCaps(capsuleCollider, dismountCollider.transform.TransformPoint(capsuleCollider.center), dismountCollider.transform.rotation, out startEndCap, out endEndCap);
|
||||
hitCount = Physics.OverlapCapsuleNonAlloc(startEndCap, endEndCap, capsuleCollider.radius * MathUtility.ColliderRadiusMultiplier(capsuleCollider), m_OverlapColliders,
|
||||
m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore);
|
||||
} else if (dismountCollider is BoxCollider) {
|
||||
var boxCollider = dismountCollider as BoxCollider;
|
||||
hitCount = Physics.OverlapBoxNonAlloc(dismountCollider.transform.TransformPoint(boxCollider.center), Vector3.Scale(boxCollider.size, boxCollider.transform.lossyScale) / 2,
|
||||
m_OverlapColliders, dismountCollider.transform.rotation, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore);
|
||||
} else { // SphereCollider.
|
||||
var sphereCollider = dismountCollider as SphereCollider;
|
||||
hitCount = Physics.OverlapSphereNonAlloc(dismountCollider.transform.TransformPoint(sphereCollider.center), sphereCollider.radius * MathUtility.ColliderRadiusMultiplier(sphereCollider),
|
||||
m_OverlapColliders, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore);
|
||||
}
|
||||
|
||||
// Any overlap occurs anytime there is more one collider intersecting the dismount colliders.
|
||||
return hitCount > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has started to dismount from the Rideable object.
|
||||
/// </summary>
|
||||
public void StartDismount()
|
||||
{
|
||||
EventHandler.ExecuteEvent<bool>(m_GameObject, "OnEnableGameplayInput", false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has dismounted from the Rideable object.
|
||||
/// </summary>
|
||||
public void Dismounted()
|
||||
{
|
||||
StopAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ability has stopped running.
|
||||
/// </summary>
|
||||
/// <param name="force">Was the ability force stopped?</param>
|
||||
protected override void AbilityStopped(bool force)
|
||||
{
|
||||
base.AbilityStopped(force);
|
||||
|
||||
// Restore the character parent transform.
|
||||
var characterLocomotion = m_Ride.CharacterLocomotion;
|
||||
characterLocomotion.transform.parent = m_PreviousParent;
|
||||
|
||||
// Revert the rideable colliders on the ride character.
|
||||
characterLocomotion.RemoveIgnoredColliders(m_OriginalColliders);
|
||||
|
||||
// Revert the added colliders.
|
||||
m_CharacterLocomotion.RemoveColliders(characterLocomotion.Colliders);
|
||||
m_CharacterLocomotion.RemoveIgnoredColliders(characterLocomotion.IgnoredColliders);
|
||||
|
||||
// The Ride character can update normally again.
|
||||
m_Ride.CharacterLocomotion.ManualMove = false;
|
||||
|
||||
// The RideableObject is no longer in control of the input.
|
||||
EventHandler.ExecuteEvent<bool>(m_GameObject, "OnEnableGameplayInput", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Rideable.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Rideable.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4225eaa2ec2518448bbf1359de96f64b
|
||||
timeCreated: 1527204647
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
175
Assets/Scripts/Character/Abilities/Slide.cs
Normal file
175
Assets/Scripts/Character/Abilities/Slide.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Character.Abilities
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Slide ability will apply a force to the character if the character is on a steep slope.
|
||||
/// </summary>
|
||||
[DefaultStopType(AbilityStopType.Automatic)]
|
||||
public class Slide : Ability
|
||||
{
|
||||
[Tooltip("Steepness (in degrees) above which the character can slide.")]
|
||||
[SerializeField] protected float m_MinSlideLimit = 40;
|
||||
[Tooltip("Steepness (in degrees) below which the character can slide.")]
|
||||
[SerializeField] protected float m_MaxSlideLimit = 89f;
|
||||
[Tooltip("Multiplier of the ground's slide value. The slide value is determined by (1 - dynamicFriction) of the ground's physic material.")]
|
||||
[SerializeField] protected float m_Multiplier = 0.4f;
|
||||
[Tooltip("The maximum speed that the character can slide.")]
|
||||
[SerializeField] protected float m_MaxSlideSpeed = 1;
|
||||
[Tooltip("Optionally specifies the up direction that should override the character's up direction.")]
|
||||
[SerializeField] protected Vector3 m_OverrideUpDirection;
|
||||
|
||||
public float MinSlideLimit { get { return m_MinSlideLimit; } set { m_MinSlideLimit = value; } }
|
||||
public float MaxSlideLimit { get { return m_MaxSlideLimit; } set { m_MaxSlideLimit = value; } }
|
||||
public float Multiplier { get { return m_Multiplier; } set { m_Multiplier = value; } }
|
||||
public float MaxSlideSpeed { get { return m_MaxSlideSpeed; } set { m_MaxSlideSpeed = value; } }
|
||||
public Vector3 OverrideUpDirection { get { return m_OverrideUpDirection; } set { m_OverrideUpDirection = value; } }
|
||||
|
||||
private float m_SlideSpeed;
|
||||
|
||||
public override bool IsConcurrent { get { return true; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
EventHandler.RegisterEvent<bool>(m_GameObject, "OnCharacterGrounded", OnGrounded);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability can be started.</returns>
|
||||
public override bool CanStartAbility()
|
||||
{
|
||||
// An attribute may prevent the ability from starting.
|
||||
if (!base.CanStartAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CanSlide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the character can slide on the ground.
|
||||
/// </summary>
|
||||
/// <returns>True if the character can slide on the ground.</returns>
|
||||
private bool CanSlide()
|
||||
{
|
||||
// The character cannot slide in the air.
|
||||
if (!m_CharacterLocomotion.Grounded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The character cannot slide if the slope isn't steep enough or is too steep.
|
||||
var slope = Vector3.Angle(m_CharacterLocomotion.Up, m_CharacterLocomotion.GroundRaycastHit.normal);
|
||||
if (slope < m_MinSlideLimit + m_CharacterLocomotion.SlopeLimitSpacing || slope > m_MaxSlideLimit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't slide if the character is moving and can step over the object.
|
||||
if (m_CharacterLocomotion.Moving) {
|
||||
var groundPoint = m_Transform.InverseTransformPoint(m_CharacterLocomotion.GroundRaycastHit.point);
|
||||
groundPoint.y = 0;
|
||||
groundPoint = m_Transform.TransformPoint(groundPoint);
|
||||
var direction = groundPoint - m_Transform.position;
|
||||
if (m_CharacterLocomotion.OverlapCount((direction.normalized * (direction.magnitude + m_CharacterLocomotion.Radius)) +
|
||||
m_CharacterLocomotion.PlatformMovement + m_CharacterLocomotion.Up * (m_CharacterLocomotion.MaxStepHeight - m_CharacterLocomotion.ColliderSpacing)) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The character can slide.
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the controller's position values.
|
||||
/// </summary>
|
||||
public override void UpdatePosition()
|
||||
{
|
||||
base.UpdatePosition();
|
||||
|
||||
// The character's motor throttle may be greater than the slide force. Negate any motor throttle force in the opposite direction.
|
||||
var groundRaycastHitNormal = m_CharacterLocomotion.GroundRaycastHit.normal;
|
||||
var upDirection = m_OverrideUpDirection.sqrMagnitude > 0 ? m_OverrideUpDirection : m_CharacterLocomotion.Up;
|
||||
var direction = Vector3.Cross(Vector3.Cross(groundRaycastHitNormal, -upDirection), groundRaycastHitNormal);
|
||||
var horizontalMotorThrottle = Vector3.ProjectOnPlane(m_CharacterLocomotion.MotorThrottle, upDirection);
|
||||
var horizontalDirection = Vector3.ProjectOnPlane(direction, upDirection);
|
||||
// Only update the motor throttle if the character is moving into the slope.
|
||||
var dot = Vector3.Dot(horizontalMotorThrottle.normalized, horizontalDirection.normalized);
|
||||
if (dot < 0) {
|
||||
m_CharacterLocomotion.MotorThrottle *= (1 + dot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the ability after the character movements have been applied.
|
||||
/// </summary>
|
||||
public override void LateUpdate()
|
||||
{
|
||||
var groundRaycastHit = m_CharacterLocomotion.GroundRaycastHit;
|
||||
// The slide value uses the ground's physic material to get the amount of friction of the material.
|
||||
var slide = (1 - groundRaycastHit.collider.material.dynamicFriction) * m_Multiplier;
|
||||
var upDirection = m_OverrideUpDirection.sqrMagnitude > 0 ? m_OverrideUpDirection : m_CharacterLocomotion.Up;
|
||||
// Slide at a constant speed if the slope is within the slope limit.
|
||||
var slope = Vector3.Angle(groundRaycastHit.normal, upDirection);
|
||||
if (slope < m_CharacterLocomotion.SlopeLimit) {
|
||||
m_SlideSpeed = Mathf.Max(m_SlideSpeed, slide);
|
||||
} else { // The slope is steeper then the slope limit. Slide with an accelerating slide speed.
|
||||
m_SlideSpeed += slide * (slope / m_MinSlideLimit);
|
||||
}
|
||||
m_SlideSpeed = Mathf.Min(m_SlideSpeed, m_MaxSlideSpeed);
|
||||
|
||||
// Add a force if the character should slide.
|
||||
if (m_SlideSpeed > 0) {
|
||||
var direction = Vector3.Cross(Vector3.Cross(groundRaycastHit.normal, -upDirection), groundRaycastHit.normal);
|
||||
AddForce(direction.normalized * m_SlideSpeed * m_CharacterLocomotion.TimeScale * Time.timeScale * Time.deltaTime, 1, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the ability from running.
|
||||
/// </summary>
|
||||
/// <returns>True if the ability was stopped.</returns>
|
||||
public override bool CanStopAbility()
|
||||
{
|
||||
return !CanSlide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has changed grounded state.
|
||||
/// </summary>
|
||||
/// <param name="grounded">Is the character on the ground?</param>
|
||||
private void OnGrounded(bool grounded)
|
||||
{
|
||||
if (grounded) {
|
||||
if (!CanSlide()) {
|
||||
m_SlideSpeed = 0;
|
||||
}
|
||||
} else {
|
||||
StopAbility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has been destroyed.
|
||||
/// </summary>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<bool>(m_GameObject, "OnCharacterGrounded", OnGrounded);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Scripts/Character/Abilities/Slide.cs.meta
Normal file
12
Assets/Scripts/Character/Abilities/Slide.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc409a59c9641514d8ea0bd83ff77ad2
|
||||
timeCreated: 1487015903
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user