Organize custom scripts under Assets/Baba_yaga and merge Opsive folders to Assets root

This commit is contained in:
2026-07-01 20:32:28 +07:00
parent 83d4157ac6
commit befc19bf37
5901 changed files with 243 additions and 141 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ceccbd50b42dc3448a66733b390f24ef
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 43db3a16f3af31c43a1cc4888803ba39
timeCreated: 1528032643
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d286bd3adc4970a40bb47844643ea00e
timeCreated: 1527971258
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View 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:

View 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; }
}
}

View 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:

View 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;
}
}
}

View 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:

View 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();
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a54bcb9929be4b445a3aceb56670d9af
timeCreated: 1543956788
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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();
}
}
}

View 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:

View 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);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 3d7b2324759214a4fb66abb5f08e40f6
timeCreated: 1487015903
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 27c11aee384e7ed4eb24d2d8cc987948
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: ebf5a921c51093546bd19e09f83099da
timeCreated: 1487015903
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}
}

View 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:

View 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();
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c8339cdcef812b243bf8020b6c00456b
timeCreated: 1533751497
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1cd370961ec22d74898632d2cc53b56e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}
}

View 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:

View 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);
}
}
}
}

View 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:

View 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;
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 43390113922c6aa48b4f484c4407a5a9
timeCreated: 1497448172
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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();
}
}
}

View 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:

View 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);
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d69b1adaf35366a4dae9ef290d2a98f6
timeCreated: 1497448172
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 71a93dbc059085f48b2c6356c89cb07c
timeCreated: 1501959395
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f2136d2695ea33046bb794d48ce11e09
timeCreated: 1497448172
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View 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:

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1b44b53c489db4746bd1dcd6d695024e
timeCreated: 1497448172
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8cebf72141915574a8ae7231cd35475d
timeCreated: 1497448172
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View 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:

View 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;
}
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 183aff55edcab624885e03c4f4b2db78
timeCreated: 1513724185
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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
}
}

View 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:

View 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;
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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:

View 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);
}
}
}

View 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