Update
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Animator component at a fixed delta time.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Animator))]
|
||||
public class AnimatorUpdate : MonoBehaviour, IKinematicObject
|
||||
{
|
||||
private Animator m_Animator;
|
||||
|
||||
private int m_KinematicObjectIndex;
|
||||
|
||||
public int KinematicObjectIndex { set { m_KinematicObjectIndex = value; } }
|
||||
public KinematicObjectManager.UpdateLocation UpdateLocation {
|
||||
get {
|
||||
return m_Animator.updateMode == AnimatorUpdateMode.Fixed ? KinematicObjectManager.UpdateLocation.FixedUpdate : KinematicObjectManager.UpdateLocation.Update;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cache the componetn references.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_Animator = GetComponent<Animator>();
|
||||
m_Animator.enabled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the object with the KinematicObjectManager.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
m_KinematicObjectIndex = KinematicObjectManager.RegisterKinematicObject(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Animator at a fixed delta time.
|
||||
/// </summary>
|
||||
public void Move()
|
||||
{
|
||||
m_Animator.Update(Time.fixedDeltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters the object with the KinematicObjectManager.
|
||||
/// </summary>
|
||||
private void OnDisable()
|
||||
{
|
||||
KinematicObjectManager.UnregisterKinematicObject(m_KinematicObjectIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4b24e158aa0be24589b80251cfe9b7e
|
||||
timeCreated: 1554812130
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,412 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.Shared.Utility;
|
||||
using Opsive.UltimateCharacterController.StateSystem;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// An Attribute can be used to describe a set of values which change over time. Examples include health, shield, stamina, hunger, etc.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Attribute : StateObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes how the attribute should update the value.
|
||||
/// </summary>
|
||||
public enum AutoUpdateValue
|
||||
{
|
||||
None, // Do not automatically update the value.
|
||||
Decrease, // Decreases the value to the min value.
|
||||
Increase // Increases the value to the max value.
|
||||
}
|
||||
|
||||
[Tooltip("The name of the attribute.")]
|
||||
[SerializeField] protected string m_Name;
|
||||
[Tooltip("The minimum value of the attribute.")]
|
||||
[SerializeField] protected float m_MinValue;
|
||||
[Tooltip("The maximum value of the attribute.")]
|
||||
[SerializeField] protected float m_MaxValue = 100;
|
||||
[Tooltip("The current value of the attribute.")]
|
||||
[SerializeField] protected float m_Value = 100;
|
||||
[Tooltip("Describes how the attribute should update the value.")]
|
||||
[SerializeField] protected AutoUpdateValue m_AutoUpdateValueType;
|
||||
[Tooltip("The amount of time between a value change and when the auto updater should start.")]
|
||||
[SerializeField] protected float m_AutoUpdateStartDelay = 1f;
|
||||
[Tooltip("The amount of time to wait in between auto update loops.")]
|
||||
[SerializeField] protected float m_AutoUpdateInterval = 0.1f;
|
||||
[Tooltip("The amount to change the value with each auto update.")]
|
||||
[SerializeField] protected float m_AutoUpdateAmount = 0.2f;
|
||||
|
||||
[NonSerialized] public string Name { get { return m_Name; } set { m_Name = value; } }
|
||||
public float MinValue { get { return m_MinValue; } set { m_MinValue = value; } }
|
||||
public float MaxValue { get { return m_MaxValue; } set { m_MaxValue = value; } }
|
||||
[NonSerialized] public float Value { get { return m_Value; }
|
||||
set
|
||||
{
|
||||
m_Value = Mathf.Clamp(value, m_MinValue, m_MaxValue);
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnAttributeUpdateValue", this);
|
||||
|
||||
ScheduleAutoUpdate(m_AutoUpdateStartDelay);
|
||||
}
|
||||
}
|
||||
public AutoUpdateValue AutoUpdateValueType { get { return m_AutoUpdateValueType; } set { m_AutoUpdateValueType = value; ScheduleAutoUpdate(0.01f); } }
|
||||
public float AutoUpdateStartDelay { get { return m_AutoUpdateStartDelay; } set { m_AutoUpdateStartDelay = value; ScheduleAutoUpdate(0.01f); } }
|
||||
public float AutoUpdateInterval { get { return m_AutoUpdateInterval; } set { m_AutoUpdateInterval = value; } }
|
||||
public float AutoUpdateAmount { get { return m_AutoUpdateAmount; } set { m_AutoUpdateAmount = value; } }
|
||||
|
||||
private GameObject m_GameObject;
|
||||
private float m_StartValue;
|
||||
private ScheduledEventBase m_AutoUpdateEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public Attribute() { }
|
||||
|
||||
/// <summary>
|
||||
/// Two parameter constructor.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the attribute.</param>
|
||||
/// <param name="value">The value of the attribute.</param>
|
||||
public Attribute(string name, float value)
|
||||
{
|
||||
m_Name = name;
|
||||
m_Value = m_MaxValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the default values.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The GameObject this object is attached to.</param>
|
||||
public override void Initialize(GameObject gameObject)
|
||||
{
|
||||
base.Initialize(gameObject);
|
||||
|
||||
m_GameObject = gameObject;
|
||||
m_StartValue = m_Value;
|
||||
|
||||
ScheduleAutoUpdate(m_AutoUpdateStartDelay);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules an auto update if the auto update value type is not set to none.
|
||||
/// </summary>
|
||||
/// <param name="delay">The amount to delay the attribute update event by.</param>
|
||||
private void ScheduleAutoUpdate(float delay)
|
||||
{
|
||||
Scheduler.Cancel(m_AutoUpdateEvent);
|
||||
if ((m_AutoUpdateValueType == AutoUpdateValue.Increase && m_Value != m_MaxValue) ||
|
||||
(m_AutoUpdateValueType == AutoUpdateValue.Decrease && m_Value != m_MinValue)) {
|
||||
m_AutoUpdateEvent = Scheduler.Schedule(delay, UpdateValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback when the auto update event is executed.
|
||||
/// </summary>
|
||||
private void UpdateValue()
|
||||
{
|
||||
if (m_AutoUpdateValueType == AutoUpdateValue.Increase) {
|
||||
m_Value = Mathf.Min(m_Value + m_AutoUpdateAmount, m_MaxValue);
|
||||
if (m_Value < m_MaxValue) {
|
||||
m_AutoUpdateEvent = Scheduler.ScheduleFixed(m_AutoUpdateInterval, UpdateValue);
|
||||
} else {
|
||||
EventHandler.ExecuteEvent(this, "OnAttributeReachedDestinationValue");
|
||||
}
|
||||
} else { // Decrease.
|
||||
m_Value = Mathf.Max(m_Value - m_AutoUpdateAmount, m_MinValue);
|
||||
if (m_Value > m_MinValue) {
|
||||
m_AutoUpdateEvent = Scheduler.ScheduleFixed(m_AutoUpdateInterval, UpdateValue);
|
||||
} else {
|
||||
EventHandler.ExecuteEvent(this, "OnAttributeReachedDestinationValue");
|
||||
}
|
||||
}
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnAttributeUpdateValue", this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the attribute currently valid? A valid attribute will not be at the minimum value.
|
||||
/// </summary>
|
||||
/// <param name="valueChange">The amount that the attribute is going to change values by.</param>
|
||||
/// <returns>True if the attribute value is currently valid.</returns>
|
||||
public bool IsValid(float valueChange)
|
||||
{
|
||||
return m_Value + valueChange > m_MinValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the auto update value.
|
||||
/// </summary>
|
||||
public void CancelAutoUpdate()
|
||||
{
|
||||
Scheduler.Cancel(m_AutoUpdateEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the value to the starting value.
|
||||
/// </summary>
|
||||
public void ResetValue()
|
||||
{
|
||||
Scheduler.Cancel(m_AutoUpdateEvent);
|
||||
m_Value = m_StartValue;
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnAttributeUpdateValue", this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback when the StateManager has changed the active state on the current object.
|
||||
/// </summary>
|
||||
public override void StateChange()
|
||||
{
|
||||
ScheduleAutoUpdate(m_AutoUpdateStartDelay);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute destructor - will cancel the update event.
|
||||
/// </summary>
|
||||
~Attribute()
|
||||
{
|
||||
Scheduler.Cancel(m_AutoUpdateEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Attribute that can have its values changed.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class AttributeModifier
|
||||
{
|
||||
[Tooltip("The name of the attribute.")]
|
||||
[SerializeField] protected string m_AttributeName;
|
||||
[Tooltip("Specifies the amount to change the attribute by when the modifier is enabled.")]
|
||||
[SerializeField] protected float m_ValueChange;
|
||||
[Tooltip("Should a new update value be set?")]
|
||||
[SerializeField] protected bool m_ChangeUpdateValue;
|
||||
[Tooltip("Describes how the attribute should update the value.")]
|
||||
[SerializeField] protected Attribute.AutoUpdateValue m_AutoUpdateValueType;
|
||||
[Tooltip("The amount of time between a value change and when the auto updater should start.")]
|
||||
[SerializeField] protected float m_AutoUpdateStartDelay = 1f;
|
||||
[Tooltip("The amount of time to wait in between auto update loops.")]
|
||||
[SerializeField] protected float m_AutoUpdateInterval = 0.1f;
|
||||
[Tooltip("The amount to change the value with each auto update.")]
|
||||
[SerializeField] protected float m_AutoUpdateAmount = 0.2f;
|
||||
[Tooltip("The duration that the auto update lasts for. Set to a positive value to enable.")]
|
||||
[SerializeField] protected float m_AutoUpdateDuration = -1;
|
||||
|
||||
[NonSerialized] public string AttributeName { get { return m_AttributeName; } set { m_AttributeName = value; } }
|
||||
[NonSerialized] public float ValueChange { get { return m_ValueChange; } set { m_ValueChange = value; } }
|
||||
[NonSerialized] public bool ChangeUpdateValue { get { return m_ChangeUpdateValue; } set { m_ChangeUpdateValue = value; } }
|
||||
[NonSerialized] public Attribute.AutoUpdateValue AutoUpdateValueType { get { return m_AutoUpdateValueType; } set { m_AutoUpdateValueType = value; } }
|
||||
[NonSerialized] public float AutoUpdateStartDelay { get { return m_AutoUpdateStartDelay; } set { m_AutoUpdateStartDelay = value; } }
|
||||
[NonSerialized] public float AutoUpdateInterval { get { return m_AutoUpdateInterval; } set { m_AutoUpdateInterval = value; } }
|
||||
[NonSerialized] public float AutoUpdateAmount { get { return m_AutoUpdateAmount; } set { m_AutoUpdateAmount = value; } }
|
||||
[NonSerialized] public float AutoUpdateDuration { get { return m_AutoUpdateDuration; } set { m_AutoUpdateDuration = value; } }
|
||||
|
||||
private Attribute m_Attribute;
|
||||
|
||||
private bool m_AutoUpdating;
|
||||
private Attribute.AutoUpdateValue m_StoredAutoUpdateValueType;
|
||||
private float m_StoredAutoUpdateStartDelay;
|
||||
private float m_StoredAutoUpdateInterval;
|
||||
private float m_StoredAutoUpdateAmount;
|
||||
private ScheduledEventBase m_DisableAutoUpdateEvent;
|
||||
|
||||
public Attribute Attribute { get { return m_Attribute; } }
|
||||
public bool AutoUpdating { get { return m_AutoUpdating; } }
|
||||
|
||||
/// <summary>
|
||||
/// Default AttributeModifier constructor.
|
||||
/// </summary>
|
||||
public AttributeModifier() { }
|
||||
|
||||
/// <summary>
|
||||
/// AttributeModifier constructor.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the attribute.</param>
|
||||
/// <param name="valueChange">Specifies the amount to change the attribute by when the modifier is enabled.</param>
|
||||
/// <param name="autoUpdateValueType">Describes how the attribute should update the value.</param>
|
||||
public AttributeModifier(string name, float valueChange, Attribute.AutoUpdateValue autoUpdateValueType)
|
||||
{
|
||||
m_AttributeName = name;
|
||||
m_ValueChange = valueChange;
|
||||
m_AutoUpdateValueType = autoUpdateValueType;
|
||||
if (m_AutoUpdateValueType != Attribute.AutoUpdateValue.None) {
|
||||
m_ChangeUpdateValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the AttributeModifier.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The GameObject that has the AttributeManager attached to it.</param>
|
||||
/// <returns>True if the AttributeModifier was initialized.</returns>
|
||||
public bool Initialize(GameObject gameObject)
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_AttributeName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var attributeManager = gameObject.GetCachedComponent<AttributeManager>();
|
||||
if (attributeManager == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Attribute = attributeManager.GetAttribute(m_AttributeName);
|
||||
return m_Attribute != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the AttributeModifier from another AttributeModifier.
|
||||
/// </summary>
|
||||
/// <param name="other">The AttributeModifier to copy.</param>
|
||||
/// <param name="attributeManager">The AttributeManager that the modifier is attached to.</param>
|
||||
/// <returns>True if the AttributeModifier was initialized.</returns>
|
||||
public bool Initialize(AttributeModifier other, AttributeManager attributeManager)
|
||||
{
|
||||
if (string.IsNullOrEmpty(other.AttributeName) || attributeManager == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_AttributeName = other.AttributeName;
|
||||
m_ValueChange = other.ValueChange;
|
||||
m_ChangeUpdateValue = other.ChangeUpdateValue;
|
||||
m_AutoUpdateValueType = other.AutoUpdateValueType;
|
||||
m_AutoUpdateStartDelay = other.AutoUpdateStartDelay;
|
||||
m_AutoUpdateInterval = other.m_AutoUpdateInterval;
|
||||
m_AutoUpdateAmount = other.AutoUpdateAmount;
|
||||
m_AutoUpdateDuration = other.AutoUpdateDuration;
|
||||
|
||||
m_Attribute = attributeManager.GetAttribute(m_AttributeName);
|
||||
return m_Attribute != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the attribute currently valid? A valid attribute will not be at the minimum value.
|
||||
/// </summary>
|
||||
/// <returns>True if the attribute value is currently valid.</returns>
|
||||
public bool IsValid()
|
||||
{
|
||||
if (m_Attribute == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return m_Attribute.IsValid(m_ValueChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables the modifier.
|
||||
/// </summary>
|
||||
/// <param name="enable"></param>
|
||||
public void EnableModifier(bool enable)
|
||||
{
|
||||
if (m_Attribute == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The attribute can be changed by a single value...
|
||||
if (enable && m_ValueChange != 0) {
|
||||
m_Attribute.Value += m_ValueChange;
|
||||
}
|
||||
|
||||
if (!m_ChangeUpdateValue || m_AutoUpdating == enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ...Or a change with a longer duration.
|
||||
m_AutoUpdating = enable;
|
||||
if (enable) {
|
||||
m_StoredAutoUpdateAmount = m_Attribute.AutoUpdateAmount;
|
||||
m_StoredAutoUpdateInterval = m_Attribute.AutoUpdateInterval;
|
||||
m_StoredAutoUpdateStartDelay = m_Attribute.AutoUpdateStartDelay;
|
||||
m_StoredAutoUpdateValueType = m_Attribute.AutoUpdateValueType;
|
||||
|
||||
m_Attribute.AutoUpdateAmount = m_AutoUpdateAmount;
|
||||
m_Attribute.AutoUpdateInterval = m_AutoUpdateInterval;
|
||||
m_Attribute.AutoUpdateStartDelay = m_AutoUpdateStartDelay;
|
||||
m_Attribute.AutoUpdateValueType = m_AutoUpdateValueType;
|
||||
|
||||
if (m_AutoUpdateDuration > 0) {
|
||||
m_DisableAutoUpdateEvent = Scheduler.Schedule(m_AutoUpdateDuration, EnableModifier, false);
|
||||
}
|
||||
} else {
|
||||
m_Attribute.AutoUpdateAmount = m_StoredAutoUpdateAmount;
|
||||
m_Attribute.AutoUpdateInterval = m_StoredAutoUpdateInterval;
|
||||
m_Attribute.AutoUpdateStartDelay = m_StoredAutoUpdateStartDelay;
|
||||
m_Attribute.AutoUpdateValueType = m_StoredAutoUpdateValueType;
|
||||
|
||||
if (m_DisableAutoUpdateEvent != null) {
|
||||
Scheduler.Cancel(m_DisableAutoUpdateEvent);
|
||||
m_DisableAutoUpdateEvent = null;
|
||||
}
|
||||
}
|
||||
|
||||
EventHandler.ExecuteEvent(this, "OnAttributeModifierAutoUpdateEnabled", this, enable);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The AttributeManager will manage the array of Attributes.
|
||||
/// </summary>
|
||||
public class AttributeManager : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The array of Attributes on the object.")]
|
||||
[SerializeField] protected Attribute[] m_Attributes = new Attribute[] { new Attribute("Health", 100) };
|
||||
|
||||
public Attribute[] Attributes { get { return m_Attributes; } set {
|
||||
if (m_Attributes != value) {
|
||||
m_Attributes = value;
|
||||
if (Application.isPlaying) {
|
||||
Initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, Attribute> m_NameAttributeMap = new Dictionary<string, Attribute>();
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attributes.
|
||||
/// </summary>
|
||||
private void Initialize()
|
||||
{
|
||||
m_NameAttributeMap.Clear();
|
||||
for (int i = 0; i < m_Attributes.Length; ++i) {
|
||||
m_NameAttributeMap.Add(m_Attributes[i].Name, m_Attributes[i]);
|
||||
m_Attributes[i].Initialize(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the attribute with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the attribute.</param>
|
||||
/// <returns>The attribute with the specified name. Will return null if no attributes with the specified name exist.</returns>
|
||||
public Attribute GetAttribute(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
return null;
|
||||
}
|
||||
Attribute attribute;
|
||||
if (m_NameAttributeMap.TryGetValue(name, out attribute)) {
|
||||
return attribute;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e30da4fa96ed67145a2c5310eca67bb4
|
||||
timeCreated: 1518722169
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: -180
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,173 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Character;
|
||||
using Opsive.UltimateCharacterController.Character.Effects;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Extends the health component by allowing the character to take fall damage. The amount of damage is specified by a curve.
|
||||
/// </summary>
|
||||
public class CharacterHealth : Health
|
||||
{
|
||||
[Tooltip("Should fall damage be applied?")]
|
||||
[SerializeField] protected bool m_ApplyFallDamage;
|
||||
[Tooltip("The minimum height that the character has to fall in order for any damage to be applied.")]
|
||||
[SerializeField] protected float m_MinFallDamageHeight = 3;
|
||||
[Tooltip("The amount of damage to apply when the player falls by the minimum fall damage height.")]
|
||||
[SerializeField] protected float m_MinFallDamage = 1;
|
||||
[Tooltip("The amount of damage to apply when the player falls just less than the death height.")]
|
||||
[SerializeField] protected float m_MaxFallDamage = 50;
|
||||
[Tooltip("A fall greater than this value is an instant death.")]
|
||||
[SerializeField] protected float m_DeathHeight = 20;
|
||||
[Tooltip("A curve specifying the amount of damage to apply if the character falls between the min and max fall damage values.")]
|
||||
[SerializeField] protected AnimationCurve m_DamageCurve = AnimationCurve.Linear(0, 0, 1, 1);
|
||||
[Tooltip("The effect that should be started when the character takes damage.")]
|
||||
[HideInInspector] [SerializeField] protected string m_DamagedEffectName;
|
||||
[Tooltip("The index of the effect that should be started when the character takes damage.")]
|
||||
[HideInInspector] [SerializeField] protected int m_DamagedEffectIndex = -1;
|
||||
|
||||
public bool ApplyFallDamage { get { return m_ApplyFallDamage; } set { m_ApplyFallDamage = value; } }
|
||||
public float MinFallDamageHeight { get { return m_MinFallDamageHeight; } set { m_MinFallDamageHeight = value; } }
|
||||
public float MinFallDamage { get { return m_MinFallDamage; } set { m_MinFallDamage = value; } }
|
||||
public float MaxFallDamage { get { return m_MaxFallDamage; } set { m_MaxFallDamage = value; } }
|
||||
public float DeathHeight { get { return m_DeathHeight; } set { m_DeathHeight = value; } }
|
||||
public AnimationCurve DamageCurve { get { return m_DamageCurve; } set { m_DamageCurve = value; } }
|
||||
public string StartDamagedName { get { return m_DamagedEffectName; } set { m_DamagedEffectName = value; } }
|
||||
public int DamagedEffectIndex { get { return m_DamagedEffectIndex; } set { m_DamagedEffectIndex = value; } }
|
||||
|
||||
private UltimateCharacterLocomotion m_CharacterLocomotion;
|
||||
private GameObject[] m_ColliderGameObjects;
|
||||
private int[] m_ColliderLayers;
|
||||
private Effect m_DamagedEffect;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_CharacterLocomotion = m_GameObject.GetCachedComponent<UltimateCharacterLocomotion>();
|
||||
if (m_ColliderGameObjects != null) {
|
||||
for (int i = 0; i < m_ColliderGameObjects.Length; ++i) {
|
||||
m_ColliderLayers[i] = m_ColliderGameObjects[i].layer;
|
||||
}
|
||||
}
|
||||
|
||||
EventHandler.RegisterEvent<float>(m_GameObject, "OnCharacterLand", OnCharacterLand);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the collider layers after the UltimateCharacterLocomotion has been initialized.
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
m_ColliderGameObjects = new GameObject[m_CharacterLocomotion.ColliderCount];
|
||||
for (int i = 0; i < m_ColliderGameObjects.Length; ++i) {
|
||||
m_ColliderGameObjects[i] = m_CharacterLocomotion.Colliders[i].gameObject;
|
||||
}
|
||||
m_ColliderLayers = new int[m_CharacterLocomotion.ColliderCount];
|
||||
|
||||
if (!string.IsNullOrEmpty(m_DamagedEffectName)) {
|
||||
m_DamagedEffect = m_CharacterLocomotion.GetEffect(UnityEngineUtility.GetType(m_DamagedEffectName), m_DamagedEffectIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has taken been damaged.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of damage taken.</param>
|
||||
/// <param name="position">The position of the damage.</param>
|
||||
/// <param name="direction">The direction that the object took damage from.</param>
|
||||
/// <param name="forceMagnitude">The magnitude of the force that is applied to the object.</param>
|
||||
/// <param name="frames">The number of frames to add the force to.</param>
|
||||
/// <param name="radius">The radius of the explosive damage. If 0 then a non-explosive force will be used.</param>
|
||||
/// <param name="attacker">The GameObject that did the damage.</param>
|
||||
/// <param name="attackerObject">The object that did the damage.</param>
|
||||
/// <param name="hitCollider">The Collider that was hit.</param>
|
||||
public override void OnDamage(float amount, Vector3 position, Vector3 direction, float forceMagnitude, int frames, float radius, GameObject attacker, object attackerObject, Collider hitCollider)
|
||||
{
|
||||
base.OnDamage(amount, position, direction, forceMagnitude, frames, radius, attacker, attackerObject, hitCollider);
|
||||
|
||||
if (m_DamagedEffect != null) {
|
||||
m_CharacterLocomotion.TryStartEffect(m_DamagedEffect);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object is no longer alive
|
||||
/// </summary>
|
||||
/// <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 killed the character.</param>
|
||||
public override void Die(Vector3 position, Vector3 force, GameObject attacker)
|
||||
{
|
||||
base.Die(position, force, attacker);
|
||||
|
||||
if (m_DeathLayer != 0) {
|
||||
for (int i = 0; i < m_ColliderGameObjects.Length; ++i) {
|
||||
m_ColliderLayers[i] = m_ColliderGameObjects[i].layer;
|
||||
m_ColliderGameObjects[i].layer = m_DeathLayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has spawned again. Set the health and shield back to their starting values.
|
||||
/// </summary>
|
||||
protected override void OnRespawn()
|
||||
{
|
||||
base.OnRespawn();
|
||||
|
||||
if (m_DeathLayer != 0) {
|
||||
for (int i = 0; i < m_ColliderGameObjects.Length; ++i) {
|
||||
m_ColliderGameObjects[i].layer = m_ColliderLayers[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has landed after falling a spcified amount. Determine if any damage should be taken.
|
||||
/// </summary>
|
||||
/// <param name="fallHeight"></param>
|
||||
private void OnCharacterLand(float fallHeight)
|
||||
{
|
||||
// Return immediately if the character isn't being damaged by a fall or the fall height is less than the minimum height
|
||||
// that the character has to fall in order to start taking damage.
|
||||
if (!m_ApplyFallDamage || fallHeight < m_MinFallDamageHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
var damageAmount = 0f;
|
||||
// The fall was too great, specify an infinite amount of damage.
|
||||
if (fallHeight >= m_DeathHeight) {
|
||||
damageAmount = Mathf.Infinity;
|
||||
} else {
|
||||
// The fall was somewhere in between the min and max fall height. Use the damage curve to determine how much damage to apply.
|
||||
var normalizedHeight = (fallHeight - m_MinFallDamageHeight) / (m_DeathHeight - m_MinFallDamageHeight);
|
||||
var damageAmountMultiplier = m_DamageCurve.Evaluate(normalizedHeight);
|
||||
damageAmount = m_MinFallDamage + damageAmountMultiplier * (m_MaxFallDamage - m_MinFallDamage);
|
||||
}
|
||||
|
||||
// Apply the damage.
|
||||
Damage(damageAmount, m_Transform.position, Vector3.zero, 0, m_GameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GameObject has been destroyed.
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
EventHandler.UnregisterEvent<float>(m_GameObject, "OnCharacterLand", OnCharacterLand);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d6607657a78e5445a5e47d0ad348e2d
|
||||
timeCreated: 1507907779
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,77 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Extends the Respawner by listening/executing character related events.
|
||||
/// </summary>
|
||||
public class CharacterRespawner : Respawner
|
||||
{
|
||||
private bool m_Active;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
EventHandler.RegisterEvent<bool>(m_GameObject, "OnCharacterActivate", OnActivate);
|
||||
m_Active = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the respawn by setting the position and rotation to the specified values.
|
||||
/// Enable the GameObject and let all of the listening objects know that the object has been respawned.
|
||||
/// </summary>
|
||||
/// <param name="position">The respawn position.</param>
|
||||
/// <param name="rotation">The respawn rotation.</param>
|
||||
/// <param name="transformChange">Was the position or rotation changed?</param>
|
||||
public override void Respawn(Vector3 position, Quaternion rotation, bool transformChange)
|
||||
{
|
||||
base.Respawn(position, rotation, transformChange);
|
||||
|
||||
// Execute OnCharacterImmediateTransformChange after OnRespawn to ensure all of the interested components are using the new position/rotation.
|
||||
if (transformChange) {
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnCharacterImmediateTransformChange", true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been disabled.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
// If the GameObject was deactivated then the respawner shouldn't respawn.
|
||||
if (m_Active) {
|
||||
base.OnDisable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has been activated or deactivated.
|
||||
/// </summary>
|
||||
/// <param name="activate">Was the character activated?</param>
|
||||
private void OnActivate(bool activate)
|
||||
{
|
||||
m_Active = activate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GameObject has been destroyed.
|
||||
/// </summary>
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
EventHandler.UnregisterEvent<bool>(m_GameObject, "OnCharacterActivate", OnActivate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2473f4d1200d65d4e9388687553497ab
|
||||
timeCreated: 1505263435
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the ConstantForce component to the specified gravity direction.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(ConstantForce))]
|
||||
public class DirectionalConstantForce : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The normalized direction of the constant force.")]
|
||||
[SerializeField] protected Vector3 m_Direction = new Vector3(0, -1, 0);
|
||||
[Tooltip("The magnitude of the starting constant force.")]
|
||||
[SerializeField] protected float m_StartMagnitude = 1;
|
||||
[Tooltip("The amount of magnitude to add each frame.")]
|
||||
[SerializeField] protected float m_FrameMagnitudeAddition = 0.2f;
|
||||
|
||||
private float m_Magnitude;
|
||||
|
||||
public Vector3 Direction { get { return m_Direction; } set { m_Direction = value; m_ConstantForce.force = m_Direction * m_StartMagnitude; } }
|
||||
public float Magnitude { get { return m_StartMagnitude; } set { m_StartMagnitude = value; m_ConstantForce.force = m_Direction * m_StartMagnitude; } }
|
||||
|
||||
private ConstantForce m_ConstantForce;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_ConstantForce = GetComponent<ConstantForce>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the magnitude.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
m_Magnitude = m_StartMagnitude;
|
||||
// The component doesn't need to update if there is no per frame force.
|
||||
if (m_FrameMagnitudeAddition == 0) {
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the per frame magnitude to the constant force.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
m_ConstantForce.force = m_Direction * m_Magnitude;
|
||||
m_Magnitude += m_FrameMagnitudeAddition;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cad37c6a6906d5e4f9edb7802f824feb
|
||||
timeCreated: 1508354041
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,572 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.Shared.Utility;
|
||||
using Opsive.UltimateCharacterController.Audio;
|
||||
using Opsive.UltimateCharacterController.Events;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.UltimateCharacterController.Objects;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
using Opsive.UltimateCharacterController.Networking;
|
||||
using Opsive.UltimateCharacterController.Networking.Traits;
|
||||
#endif
|
||||
using Opsive.UltimateCharacterController.StateSystem;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Adds health and a shield to the object.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(AttributeManager))]
|
||||
public class Health : StateBehavior
|
||||
{
|
||||
[Tooltip("Is the object invincible?")]
|
||||
[SerializeField] protected bool m_Invincible;
|
||||
[Tooltip("The amount of time that the object is invincible after respawning.")]
|
||||
[SerializeField] protected float m_TimeInvincibleAfterSpawn;
|
||||
[Tooltip("The name of the health attribute.")]
|
||||
[SerializeField] protected string m_HealthAttributeName = "Health";
|
||||
[Tooltip("The name of the shield attribute.")]
|
||||
[SerializeField] protected string m_ShieldAttributeName;
|
||||
[Tooltip("The list of Colldiers that should apply a multiplier when damaged.")]
|
||||
[SerializeField] protected Hitbox[] m_Hitboxes;
|
||||
[Tooltip("The maximum number of colliders that can be detected when determining if a hitbox was damaged.")]
|
||||
[SerializeField] protected int m_MaxHitboxCollisionCount = 10;
|
||||
[Tooltip("Any object that should spawn when the object dies.")]
|
||||
[SerializeField] protected GameObject[] m_SpawnedObjectsOnDeath;
|
||||
[Tooltip("Any object that should be destroyed when the object dies.")]
|
||||
[SerializeField] protected GameObject[] m_DestroyedObjectsOnDeath;
|
||||
[Tooltip("Should the object be deactivated on death?")]
|
||||
[SerializeField] protected bool m_DeactivateOnDeath;
|
||||
[Tooltip("If DeactivateOnDeath is enabled, specify a delay for the object to be deactivated.")]
|
||||
[SerializeField] protected float m_DeactivateOnDeathDelay;
|
||||
[Tooltip("The layer that the GameObject should switch to upon death.")]
|
||||
[SerializeField] protected LayerMask m_DeathLayer;
|
||||
[Tooltip("A set of AudioClips that can be played when the object takes damage.")]
|
||||
[HideInInspector] [SerializeField] protected AudioClipSet m_TakeDamageAudioClipSet = new AudioClipSet();
|
||||
[Tooltip("A set of AudioClips that can be played when the object is healed.")]
|
||||
[HideInInspector] [SerializeField] protected AudioClipSet m_HealAudioClipSet = new AudioClipSet();
|
||||
[Tooltip("A set of AudioClips that can be played when the object dies.")]
|
||||
[HideInInspector] [SerializeField] protected AudioClipSet m_DeathAudioClipSet = new AudioClipSet();
|
||||
[Tooltip("Unity event invoked when taking damage.")]
|
||||
[SerializeField] protected UnityFloatVector3Vector3GameObjectEvent m_OnDamageEvent;
|
||||
[Tooltip("Unity event invoked when healing.")]
|
||||
[SerializeField] protected UnityFloatEvent m_OnHealEvent;
|
||||
[Tooltip("Unity event invoked when the object dies.")]
|
||||
[SerializeField] protected UnityVector3Vector3GameObjectEvent m_OnDeathEvent;
|
||||
|
||||
public bool Invincible { get { return m_Invincible; } set { m_Invincible = value; } }
|
||||
public float TimeInvincibleAfterSpawn { get { return m_TimeInvincibleAfterSpawn; } set { m_TimeInvincibleAfterSpawn = value; } }
|
||||
public string HealthAttributeName { get { return m_HealthAttributeName; }
|
||||
set
|
||||
{
|
||||
m_HealthAttributeName = value;
|
||||
if (Application.isPlaying) {
|
||||
if (!string.IsNullOrEmpty(m_HealthAttributeName)) {
|
||||
m_HealthAttribute = m_AttributeManager.GetAttribute(m_HealthAttributeName);
|
||||
} else {
|
||||
m_HealthAttribute = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public string ShieldAttributeName
|
||||
{
|
||||
get { return m_ShieldAttributeName; }
|
||||
set
|
||||
{
|
||||
m_ShieldAttributeName = value;
|
||||
if (Application.isPlaying) {
|
||||
if (!string.IsNullOrEmpty(m_ShieldAttributeName)) {
|
||||
m_ShieldAttribute = m_AttributeManager.GetAttribute(m_ShieldAttributeName);
|
||||
} else {
|
||||
m_ShieldAttribute = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[NonSerialized] public Hitbox[] Hitboxes { get { return m_Hitboxes; } set { m_Hitboxes = value; } }
|
||||
public int MaxHitboxCollisionCount { get { return m_MaxHitboxCollisionCount; } set { m_MaxHitboxCollisionCount = value; } }
|
||||
public GameObject[] SpawnedObjectsOnDeath { get { return m_SpawnedObjectsOnDeath; } set { m_SpawnedObjectsOnDeath = value; } }
|
||||
public GameObject[] DestroyedObjectsOnDeath { get { return m_DestroyedObjectsOnDeath; } set { m_DestroyedObjectsOnDeath = value; } }
|
||||
public bool DeactivateOnDeath { get { return m_DeactivateOnDeath; } set { m_DeactivateOnDeath = value; } }
|
||||
public float DeactivateOnDeathDelay { get { return m_DeactivateOnDeathDelay; } set { m_DeactivateOnDeathDelay = value; } }
|
||||
public LayerMask DeathLayer { get { return m_DeathLayer; } set { m_DeathLayer = value; } }
|
||||
public AudioClipSet TakeDamageAudioClipSet { get { return m_TakeDamageAudioClipSet; } set { m_TakeDamageAudioClipSet = value; } }
|
||||
public AudioClipSet HealAudioClipSet { get { return m_HealAudioClipSet; } set { m_HealAudioClipSet = value; } }
|
||||
public AudioClipSet DeathAudioClipSet { get { return m_DeathAudioClipSet; } set { m_DeathAudioClipSet = value; } }
|
||||
public UnityFloatVector3Vector3GameObjectEvent OnDamageEvent { get { return m_OnDamageEvent; } set { m_OnDamageEvent = value; } }
|
||||
public UnityFloatEvent OnHealEvent { get { return m_OnHealEvent; } set { m_OnHealEvent = value; } }
|
||||
public UnityVector3Vector3GameObjectEvent OnDeathEvent { get { return m_OnDeathEvent; } set { m_OnDeathEvent = value; } }
|
||||
|
||||
protected GameObject m_GameObject;
|
||||
protected Transform m_Transform;
|
||||
private IForceObject m_ForceObject;
|
||||
private Rigidbody m_Rigidbody;
|
||||
private AttributeManager m_AttributeManager;
|
||||
private Attribute m_HealthAttribute;
|
||||
private Attribute m_ShieldAttribute;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
private INetworkInfo m_NetworkInfo;
|
||||
private INetworkHealthMonitor m_NetworkHealthMonitor;
|
||||
#endif
|
||||
|
||||
private float m_SpawnTime;
|
||||
private int m_AliveLayer;
|
||||
private Dictionary<Collider, Hitbox> m_ColliderHitboxMap;
|
||||
private RaycastHit[] m_RaycastHits;
|
||||
private UnityEngineUtility.RaycastHitComparer m_RaycastHitComparer;
|
||||
|
||||
public float HealthValue { get { return (m_HealthAttribute != null ? m_HealthAttribute.Value : 0); } }
|
||||
public float ShieldValue { get { return (m_ShieldAttribute != null ? m_ShieldAttribute.Value : 0); } }
|
||||
public float Value { get { return HealthValue + ShieldValue; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_GameObject = gameObject;
|
||||
m_Transform = transform;
|
||||
m_ForceObject = m_GameObject.GetCachedComponent<IForceObject>();
|
||||
m_Rigidbody = m_GameObject.GetCachedComponent<Rigidbody>();
|
||||
m_AttributeManager = GetComponent<AttributeManager>();
|
||||
if (!string.IsNullOrEmpty(m_HealthAttributeName)) {
|
||||
m_HealthAttribute = m_AttributeManager.GetAttribute(m_HealthAttributeName);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(m_ShieldAttributeName)) {
|
||||
m_ShieldAttribute = m_AttributeManager.GetAttribute(m_ShieldAttributeName);
|
||||
}
|
||||
m_AliveLayer = m_GameObject.layer;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
m_NetworkInfo = m_GameObject.GetCachedComponent<INetworkInfo>();
|
||||
m_NetworkHealthMonitor = m_GameObject.GetCachedComponent<INetworkHealthMonitor>();
|
||||
if (m_NetworkInfo != null && m_NetworkHealthMonitor == null) {
|
||||
Debug.LogError("Error: The object " + m_GameObject.name + " must have a NetworkHealthMonitor component.");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_Hitboxes != null && m_Hitboxes.Length > 0) {
|
||||
m_ColliderHitboxMap = new Dictionary<Collider, Hitbox>();
|
||||
for (int i = 0; i < m_Hitboxes.Length; ++i) {
|
||||
m_ColliderHitboxMap.Add(m_Hitboxes[i].Collider, m_Hitboxes[i]);
|
||||
}
|
||||
m_RaycastHits = new RaycastHit[m_MaxHitboxCollisionCount];
|
||||
m_RaycastHitComparer = new UnityEngineUtility.RaycastHitComparer();
|
||||
}
|
||||
|
||||
EventHandler.RegisterEvent(m_GameObject, "OnRespawn", OnRespawn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been damaged.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of damage taken.</param>
|
||||
public void Damage(float amount)
|
||||
{
|
||||
Damage(amount, m_Transform.position, Vector3.zero, 1, 0, 0, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been damaged.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of damage taken.</param>
|
||||
/// <param name="position">The position of the damage.</param>
|
||||
/// <param name="direction">The direction that the object took damage from.</param>
|
||||
/// <param name="forceMagnitude">The magnitude of the force that is applied to the object.</param>
|
||||
public void Damage(float amount, Vector3 position, Vector3 direction, float forceMagnitude)
|
||||
{
|
||||
Damage(amount, position, direction, forceMagnitude, 1, 0, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been damaged.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of damage taken.</param>
|
||||
/// <param name="position">The position of the damage.</param>
|
||||
/// <param name="direction">The direction that the object took damage from.</param>
|
||||
/// <param name="forceMagnitude">The magnitude of the force that is applied to the object.</param>
|
||||
/// <param name="radius">The radius of the explosive damage. If 0 then a non-exposive force will be used.</param>
|
||||
public void Damage(float amount, Vector3 position, Vector3 direction, float forceMagnitude, float radius)
|
||||
{
|
||||
Damage(amount, position, direction, forceMagnitude, 1, radius, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been damaged.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of damage taken.</param>
|
||||
/// <param name="position">The position of the damage.</param>
|
||||
/// <param name="direction">The direction that the object took damage from.</param>
|
||||
/// <param name="forceMagnitude">The magnitude of the force that is applied to the object.</param>
|
||||
/// <param name="attacker">The GameObject that did the damage.</param>
|
||||
public void Damage(float amount, Vector3 position, Vector3 direction, float forceMagnitude, GameObject attacker)
|
||||
{
|
||||
Damage(amount, position, direction, forceMagnitude, 1, 0, attacker, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been damaged.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of damage taken.</param>
|
||||
/// <param name="position">The position of the damage.</param>
|
||||
/// <param name="direction">The direction that the object took damage from.</param>
|
||||
/// <param name="forceMagnitude">The magnitude of the force that is applied to the object.</param>
|
||||
/// <param name="frames">The number of frames to add the force to.</param>
|
||||
/// <param name="radius">The radius of the explosive damage. If 0 then a non-exposive force will be used.</param>
|
||||
/// <param name="attacker">The GameObject that did the damage.</param>
|
||||
public void Damage(float amount, Vector3 position, Vector3 direction, float forceMagnitude, int frames, float radius, GameObject attacker)
|
||||
{
|
||||
Damage(amount, position, direction, forceMagnitude, frames, radius, attacker, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been damaged.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of damage taken.</param>
|
||||
/// <param name="position">The position of the damage.</param>
|
||||
/// <param name="direction">The direction that the object took damage from.</param>
|
||||
/// <param name="forceMagnitude">The magnitude of the force that is applied to the object.</param>
|
||||
/// <param name="frames">The number of frames to add the force to.</param>
|
||||
/// <param name="radius">The radius of the explosive damage. If 0 then a non-explosive force will be used.</param>
|
||||
/// <param name="attacker">The GameObject that did the damage.</param>
|
||||
/// <param name="hitCollider">The Collider that was hit.</param>
|
||||
public void Damage(float amount, Vector3 position, Vector3 direction, float forceMagnitude, int frames, float radius, GameObject attacker, Collider hitCollider)
|
||||
{
|
||||
Damage(amount, position, direction, forceMagnitude, frames, radius, attacker, null, hitCollider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been damaged.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of damage taken.</param>
|
||||
/// <param name="position">The position of the damage.</param>
|
||||
/// <param name="direction">The direction that the object took damage from.</param>
|
||||
/// <param name="forceMagnitude">The magnitude of the force that is applied to the object.</param>
|
||||
/// <param name="frames">The number of frames to add the force to.</param>
|
||||
/// <param name="radius">The radius of the explosive damage. If 0 then a non-explosive force will be used.</param>
|
||||
/// <param name="attacker">The GameObject that did the damage.</param>
|
||||
/// <param name="attackerObject">The object that did the damage.</param>
|
||||
/// <param name="hitCollider">The Collider that was hit.</param>
|
||||
public void Damage(float amount, Vector3 position, Vector3 direction, float forceMagnitude, int frames, float radius, GameObject attacker, object attackerObject, Collider hitCollider)
|
||||
{
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
if (m_NetworkInfo != null && !m_NetworkInfo.IsLocalPlayer()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Don't take any damage if the object is invincible, already dead, or just spawned and is invincible for a small amount of time.
|
||||
if (m_Invincible || !IsAlive() || m_SpawnTime + m_TimeInvincibleAfterSpawn > Time.time || amount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
OnDamage(amount, position, direction, forceMagnitude, frames, radius, attacker, attackerObject, hitCollider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has taken been damaged.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of damage taken.</param>
|
||||
/// <param name="position">The position of the damage.</param>
|
||||
/// <param name="direction">The direction that the object took damage from.</param>
|
||||
/// <param name="forceMagnitude">The magnitude of the force that is applied to the object.</param>
|
||||
/// <param name="frames">The number of frames to add the force to.</param>
|
||||
/// <param name="radius">The radius of the explosive damage. If 0 then a non-explosive force will be used.</param>
|
||||
/// <param name="attacker">The GameObject that did the damage.</param>
|
||||
/// <param name="attackerObject">The object that did the damage.</param>
|
||||
/// <param name="hitCollider">The Collider that was hit.</param>
|
||||
public virtual void OnDamage(float amount, Vector3 position, Vector3 direction, float forceMagnitude, int frames, float radius, GameObject attacker, object attackerObject, Collider hitCollider)
|
||||
{
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
|
||||
m_NetworkHealthMonitor.OnDamage(amount, position, direction, forceMagnitude, frames, radius, attacker, hitCollider);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Add a multiplier if a particular collider was hit. Do not apply a multiplier if the damage is applied through a radius because multiple
|
||||
// collider are hit.
|
||||
if (radius == 0 && direction != Vector3.zero && hitCollider != null) {
|
||||
Hitbox hitbox;
|
||||
if (m_ColliderHitboxMap != null && m_ColliderHitboxMap.Count > 0) {
|
||||
if (m_ColliderHitboxMap.TryGetValue(hitCollider, out hitbox)) {
|
||||
amount *= hitbox.DamageMultiplier;
|
||||
} else {
|
||||
// The main collider may be overlapping child hitbox colliders. Perform one more raycast to ensure a hitbox collider shouldn't be hit.
|
||||
float distance = 0.2f;
|
||||
if (hitCollider is CapsuleCollider) {
|
||||
distance = (hitCollider as CapsuleCollider).radius;
|
||||
} else if (hitCollider is SphereCollider) {
|
||||
distance = (hitCollider as SphereCollider).radius;
|
||||
}
|
||||
|
||||
// The hitbox collider may be underneath the base collider. Fire a raycast to detemine if there are any colliders underneath the hit collider
|
||||
// that should apply a multiplier.
|
||||
var hitCount = Physics.RaycastNonAlloc(position, direction, m_RaycastHits, distance,
|
||||
~(1 << LayerManager.IgnoreRaycast | 1 << LayerManager.Overlay | 1 << LayerManager.VisualEffect), QueryTriggerInteraction.Ignore);
|
||||
for (int i = 0; i < hitCount; ++i) {
|
||||
var closestRaycastHit = QuickSelect.SmallestK(m_RaycastHits, hitCount, i, m_RaycastHitComparer);
|
||||
if (closestRaycastHit.collider == hitCollider) {
|
||||
continue;
|
||||
}
|
||||
// A new collider has been found - stop iterating if the hitbox map exists and use the hitbox multiplier.
|
||||
if (m_ColliderHitboxMap.TryGetValue(closestRaycastHit.collider, out hitbox)) {
|
||||
amount *= hitbox.DamageMultiplier;
|
||||
hitCollider = hitbox.Collider;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the damage to the shield first because the shield can regenrate.
|
||||
if (m_ShieldAttribute != null && m_ShieldAttribute.Value > m_ShieldAttribute.MinValue) {
|
||||
var shieldAmount = Mathf.Min(amount, m_ShieldAttribute.Value - m_ShieldAttribute.MinValue);
|
||||
amount -= shieldAmount;
|
||||
m_ShieldAttribute.Value -= shieldAmount;
|
||||
}
|
||||
|
||||
// Decrement the health by remaining amount after the shield has taken damage.
|
||||
if (m_HealthAttribute != null && m_HealthAttribute.Value > m_HealthAttribute.MinValue) {
|
||||
m_HealthAttribute.Value -= Mathf.Min(amount, m_HealthAttribute.Value - m_HealthAttribute.MinValue);
|
||||
}
|
||||
|
||||
var force = direction * forceMagnitude;
|
||||
if (forceMagnitude > 0) {
|
||||
// Apply a force to the object.
|
||||
if (m_ForceObject != null) {
|
||||
m_ForceObject.AddForce(force, frames);
|
||||
} else {
|
||||
// Apply a force to the rigidbody if the object isn't a character.
|
||||
if (m_Rigidbody != null && !m_Rigidbody.isKinematic) {
|
||||
if (radius == 0) {
|
||||
m_Rigidbody.AddForceAtPosition(force * MathUtility.RigidbodyForceMultiplier, position);
|
||||
} else {
|
||||
m_Rigidbody.AddExplosionForce(force.magnitude * MathUtility.RigidbodyForceMultiplier, position, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let other interested objects know that the object took damage.
|
||||
EventHandler.ExecuteEvent<float, Vector3, Vector3, GameObject, Collider>(m_GameObject, "OnHealthDamage", amount, position, force, attacker, hitCollider);
|
||||
if (m_OnDamageEvent != null) {
|
||||
m_OnDamageEvent.Invoke(amount, position, force, attacker);
|
||||
}
|
||||
|
||||
// The object is dead when there is no more health or shield.
|
||||
if (!IsAlive()) {
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
if (m_NetworkInfo == null || m_NetworkInfo.IsLocalPlayer()) {
|
||||
#endif
|
||||
Die(position, force, attacker);
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// Play any take damage audio if the object did not die. If the object died then the death audio will play.
|
||||
m_TakeDamageAudioClipSet.PlayAudioClip(m_GameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the object currently alive?
|
||||
/// </summary>
|
||||
/// <returns>True if the object is currently alive.</returns>
|
||||
public bool IsAlive()
|
||||
{
|
||||
return (m_HealthAttribute != null && m_HealthAttribute.Value > m_HealthAttribute.MinValue) ||
|
||||
(m_ShieldAttribute != null && m_ShieldAttribute.Value > m_ShieldAttribute.MinValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object is no longer alive.
|
||||
/// </summary>
|
||||
/// <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 killed the character.</param>
|
||||
public virtual void Die(Vector3 position, Vector3 force, GameObject attacker)
|
||||
{
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
|
||||
m_NetworkHealthMonitor.Die(position, force, attacker);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Spawn any objects on death, such as an explosion if the object is an explosive barrel.
|
||||
if (m_SpawnedObjectsOnDeath != null) {
|
||||
for (int i = 0; i < m_SpawnedObjectsOnDeath.Length; ++i) {
|
||||
var spawnedObject = ObjectPool.Instantiate(m_SpawnedObjectsOnDeath[i], transform.position, transform.rotation);
|
||||
Explosion explosion;
|
||||
if ((explosion = spawnedObject.GetCachedComponent<Explosion>()) != null) {
|
||||
explosion.Explode(gameObject);
|
||||
}
|
||||
var rigidbodies = spawnedObject.GetComponentsInChildren<Rigidbody>();
|
||||
for (int j = 0; j < rigidbodies.Length; ++j) {
|
||||
rigidbodies[j].AddForceAtPosition(force, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy any objects on death. The objects will be placed back in the object pool if they were created within it otherwise the object will be destroyed.
|
||||
if (m_DestroyedObjectsOnDeath != null) {
|
||||
for (int i = 0; i < m_DestroyedObjectsOnDeath.Length; ++i) {
|
||||
if (ObjectPool.InstantiatedWithPool(m_DestroyedObjectsOnDeath[i])) {
|
||||
ObjectPool.Destroy(m_DestroyedObjectsOnDeath[i]);
|
||||
} else {
|
||||
Object.Destroy(m_DestroyedObjectsOnDeath[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Change the layer to a death layer.
|
||||
if (m_DeathLayer.value != 0) {
|
||||
m_AliveLayer = m_GameObject.layer;
|
||||
m_GameObject.layer = m_DeathLayer;
|
||||
}
|
||||
|
||||
// Play any take death audio. Use PlayAtPosition because the audio won't play if the GameObject is inactive.
|
||||
m_DeathAudioClipSet.PlayAtPosition(m_Transform.position);
|
||||
|
||||
// Deactivate the object if requested.
|
||||
if (m_DeactivateOnDeath) {
|
||||
Scheduler.Schedule(m_DeactivateOnDeathDelay, Deactivate);
|
||||
}
|
||||
|
||||
// The attributes shouldn't regenerate.
|
||||
if (m_ShieldAttribute != null) {
|
||||
m_ShieldAttribute.CancelAutoUpdate();
|
||||
}
|
||||
if (m_HealthAttribute != null) {
|
||||
m_HealthAttribute.CancelAutoUpdate();
|
||||
}
|
||||
|
||||
// Notify those interested.
|
||||
EventHandler.ExecuteEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", position, force, attacker);
|
||||
if (m_OnDeathEvent != null) {
|
||||
m_OnDeathEvent.Invoke(position, force, attacker);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kills the object immediately.
|
||||
/// </summary>
|
||||
public void ImmediateDeath()
|
||||
{
|
||||
ImmediateDeath(m_Transform.position, Vector3.zero, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kills the object immediately.
|
||||
/// </summary>
|
||||
/// <param name="position">The position the character died.</param>
|
||||
/// <param name="direction">The direction that the object took damage from.</param>
|
||||
/// <param name="forceMagnitude">The magnitude of the force that is applied to the object.</param>
|
||||
public void ImmediateDeath(Vector3 position, Vector3 direction, float forceMagnitude)
|
||||
{
|
||||
var amount = 0f;
|
||||
if (m_HealthAttribute != null) {
|
||||
amount += m_HealthAttribute.Value;
|
||||
}
|
||||
if (m_ShieldAttribute != null) {
|
||||
amount += m_ShieldAttribute.Value;
|
||||
}
|
||||
// If ImmediateDeath is called then the object should die even if it is invincible.
|
||||
var invincible = m_Invincible;
|
||||
m_Invincible = false;
|
||||
Damage(amount, position, direction, forceMagnitude);
|
||||
m_Invincible = invincible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds amount to health and then to the shield if there is still an amount remaining. Will not go over the maximum health or shield value.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of health or shield to add.</param>
|
||||
/// <returns>True if the object was healed.</returns>
|
||||
public virtual bool Heal(float amount)
|
||||
{
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
|
||||
m_NetworkHealthMonitor.Heal(amount);
|
||||
}
|
||||
#endif
|
||||
|
||||
var healAmount = 0f;
|
||||
|
||||
// Contribute the amount of the health first.
|
||||
if (m_HealthAttribute != null && m_HealthAttribute.Value < m_HealthAttribute.MaxValue) {
|
||||
var healthAmount = Mathf.Min(amount, m_HealthAttribute.MaxValue - m_HealthAttribute.Value);
|
||||
amount -= healthAmount;
|
||||
m_HealthAttribute.Value += healthAmount;
|
||||
healAmount += healthAmount;
|
||||
}
|
||||
|
||||
// Add any remaining amount to the shield.
|
||||
if (m_ShieldAttribute != null && amount > 0 && m_ShieldAttribute.Value < m_ShieldAttribute.MaxValue) {
|
||||
var shieldAmount = Mathf.Min(amount, m_ShieldAttribute.MaxValue - m_ShieldAttribute.Value);
|
||||
m_ShieldAttribute.Value += shieldAmount;
|
||||
healAmount += shieldAmount;
|
||||
}
|
||||
|
||||
// Don't play any effects if the object wasn't healed.
|
||||
if (healAmount == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Play any heal audio.
|
||||
m_HealAudioClipSet.PlayAudioClip(m_GameObject);
|
||||
|
||||
EventHandler.ExecuteEvent<float>(m_GameObject, "OnHealthHeal", healAmount);
|
||||
if (m_OnHealEvent != null) {
|
||||
m_OnHealEvent.Invoke(healAmount);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object doesn't have any health or shield left and should be deactivated.
|
||||
/// </summary>
|
||||
private void Deactivate()
|
||||
{
|
||||
m_GameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has spawned again. Set the health and shield back to their starting values.
|
||||
/// </summary>
|
||||
protected virtual void OnRespawn()
|
||||
{
|
||||
if (m_HealthAttribute != null) {
|
||||
m_HealthAttribute.ResetValue();
|
||||
}
|
||||
if (m_ShieldAttribute != null) {
|
||||
m_ShieldAttribute.ResetValue();
|
||||
}
|
||||
// Change the layer back to the alive layer.
|
||||
if (m_DeathLayer.value != 0) {
|
||||
m_GameObject.layer = m_AliveLayer;
|
||||
}
|
||||
m_SpawnTime = Time.time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GameObject has been destroyed.
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
EventHandler.UnregisterEvent(m_GameObject, "OnRespawn", OnRespawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c1eefff1d3d6844f88ad19752c306e0
|
||||
timeCreated: 1507812009
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,39 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// A hitbox maps a collider to a multiplier. It is also used for collision detection by the MeleeWeapon.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Hitbox
|
||||
{
|
||||
[Tooltip("The collider used for collisions in the hitbox.")]
|
||||
[SerializeField] protected Collider m_Collider;
|
||||
[Tooltip("The amount to multiply the damage amount by when the hitbox collides with an object.")]
|
||||
[SerializeField] protected float m_DamageMultiplier = 1;
|
||||
|
||||
public Collider Collider { get { return m_Collider; } }
|
||||
public float DamageMultiplier { get { return m_DamageMultiplier; } }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public Hitbox() { }
|
||||
|
||||
/// <summary>
|
||||
/// Single parameter constructor.
|
||||
/// </summary>
|
||||
/// <param name="collider">The collider that represents the hitbox.</param>
|
||||
public Hitbox(Collider collider)
|
||||
{
|
||||
m_Collider = collider;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7df6b4ef6d21484f993c5407bdbd387
|
||||
timeCreated: 1521540308
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for an interactable target that can display a message.
|
||||
/// </summary>
|
||||
public interface IInteractableMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the message that should be displayed when the object can be interacted with.
|
||||
/// </summary>
|
||||
/// <returns>The message that should be displayed when the object can be interacted with.</returns>
|
||||
string AbilityMessage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62f580e76045155429366e4da1ce3276
|
||||
timeCreated: 1520372319
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for an object that can be interacted with (such as a platform or door).
|
||||
/// </summary>
|
||||
public interface IInteractableTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// Can the target be interacted with?
|
||||
/// </summary>
|
||||
/// <param name="character">The character that wants to interact with the object.</param>
|
||||
/// <returns>True if the target can be interacted with.</returns>
|
||||
bool CanInteract(GameObject character);
|
||||
|
||||
/// <summary>
|
||||
/// Interact with the target.
|
||||
/// </summary>
|
||||
/// <param name="character">The character that wants to interact with the object.</param>
|
||||
void Interact(GameObject character);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa5e407a0ef309742bb607957001417f
|
||||
timeCreated: 1520372319
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,122 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
using Opsive.Shared.Game;
|
||||
#endif
|
||||
using Opsive.UltimateCharacterController.Objects.CharacterAssist;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
using Opsive.UltimateCharacterController.Networking;
|
||||
using Opsive.UltimateCharacterController.Networking.Traits;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Represents any object that can be interacted with by the character. Acts as a link between the character and IInteractableTarget.
|
||||
/// </summary>
|
||||
public class Interactable : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The ID of the Interactable, used by the Interact ability for filtering. A value of -1 indicates no ID.")]
|
||||
[SerializeField] protected int m_ID = -1;
|
||||
[Tooltip("The object(s) that the interaction is performend on. This component must implement the IInteractableTarget.")]
|
||||
[SerializeField] protected MonoBehaviour[] m_Targets;
|
||||
|
||||
public int ID { get { return m_ID; } set { m_ID = value; } }
|
||||
public MonoBehaviour[] Targets { get { return m_Targets; } set { m_Targets = value; } }
|
||||
|
||||
private IInteractableTarget[] m_InteractableTargets;
|
||||
private AbilityIKTarget[] m_IKTargets;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
private INetworkInteractableMonitor m_NetworkInteractable;
|
||||
#endif
|
||||
|
||||
public AbilityIKTarget[] IKTargets { get { return m_IKTargets; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
if (m_Targets == null || m_Targets.Length == 0) {
|
||||
Debug.LogError("Error: An IInteractableTarget must be specified in the Targets field.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_InteractableTargets = new IInteractableTarget[m_Targets.Length];
|
||||
for (int i = 0; i < m_Targets.Length; ++i) {
|
||||
if (m_Targets[i] == null || !(m_Targets[i] is IInteractableTarget)) {
|
||||
Debug.LogError("Error: element " + i + " of the Targets array is null or does not subscribe to the IInteractableTarget iterface.");
|
||||
} else {
|
||||
m_InteractableTargets[i] = m_Targets[i] as IInteractableTarget;
|
||||
}
|
||||
}
|
||||
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
m_NetworkInteractable = gameObject.GetCachedComponent<INetworkInteractableMonitor>();
|
||||
#endif
|
||||
|
||||
m_IKTargets = GetComponentsInChildren<AbilityIKTarget>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the character can interact with the InteractableTarget.
|
||||
/// </summary>
|
||||
/// <param name="character">The character that wants to interactact with the target.</param>
|
||||
/// <returns>True if the character can interact with the InteractableTarget</returns>
|
||||
public virtual bool CanInteract(GameObject character)
|
||||
{
|
||||
for (int i = 0; i < m_InteractableTargets.Length; ++i) {
|
||||
if (m_InteractableTargets[i] == null || !m_InteractableTargets[i].CanInteract(character)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the interaction.
|
||||
/// </summary>
|
||||
/// <param name="character">The character that wants to interactact with the target.</param>
|
||||
public virtual void Interact(GameObject character)
|
||||
{
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
var characterNetworkInfo = character.GetCachedComponent<INetworkInfo>();
|
||||
if (characterNetworkInfo != null && characterNetworkInfo.IsLocalPlayer()) {
|
||||
#if UNITY_EDITOR
|
||||
if (m_NetworkInteractable == null) {
|
||||
Debug.LogError("Error: The object " + gameObject.name + " must have a NetworkInteractable component.");
|
||||
}
|
||||
#endif
|
||||
m_NetworkInteractable.Interact(character);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < m_InteractableTargets.Length; ++i) {
|
||||
m_InteractableTargets[i].Interact(character);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the message that should be displayed when the object can be interacted with.
|
||||
/// </summary>
|
||||
/// <returns>The message that should be displayed when the object can be interacted with.</returns>
|
||||
public string AbilityMessage()
|
||||
{
|
||||
if (m_InteractableTargets != null) {
|
||||
for (int i = 0; i < m_InteractableTargets.Length; ++i) {
|
||||
// Returns the message from the first IInteractableMessage object.
|
||||
if (m_InteractableTargets[i] is IInteractableMessage) {
|
||||
return (m_InteractableTargets[i] as IInteractableMessage).AbilityMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f6e504a50ae5db4aa10a9f5957eb4bd
|
||||
timeCreated: 1520372319
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,73 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
using UnityEngine;
|
||||
using Opsive.Shared.Game;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
using Opsive.UltimateCharacterController.Networking.Game;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Pools the ParticleSystem after it is done playing.
|
||||
/// </summary>
|
||||
public class ParticlePooler : MonoBehaviour
|
||||
{
|
||||
private GameObject m_GameObject;
|
||||
private ParticleSystem m_ParticleSystem;
|
||||
|
||||
private ScheduledEventBase m_PoolEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default variables.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_GameObject = gameObject;
|
||||
m_ParticleSystem = GetComponent<ParticleSystem>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules the object to be pooled after the particle system has stopped playing.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
m_PoolEvent = Scheduler.Schedule(m_ParticleSystem.main.duration, PoolGameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the pool event if the object is disabled early.
|
||||
/// </summary>
|
||||
private void OnDisable()
|
||||
{
|
||||
Scheduler.Cancel(m_PoolEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the GameObject back to the ObjectPool.
|
||||
/// </summary>
|
||||
private void PoolGameObject()
|
||||
{
|
||||
// The particle may be looping so it shouldn't be stopped yet.
|
||||
if (m_ParticleSystem.IsAlive(true)) {
|
||||
m_PoolEvent = Scheduler.Schedule(m_ParticleSystem.main.duration, PoolGameObject);
|
||||
return;
|
||||
}
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
if (NetworkObjectPool.IsNetworkActive()) {
|
||||
// The object may have already been destroyed over the network.
|
||||
if (!m_GameObject.activeSelf) {
|
||||
return;
|
||||
}
|
||||
NetworkObjectPool.Destroy(m_GameObject);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
ObjectPool.Destroy(m_GameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d2db2c07ddd0834c993424e657516ce
|
||||
timeCreated: 1508276457
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,67 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Places the object back in the ObjectPool after the specified number of seconds.
|
||||
/// </summary>
|
||||
public class Remover : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The number of seconds until the object should be placed back in the pool.")]
|
||||
[SerializeField] protected float m_Lifetime = 5;
|
||||
|
||||
private GameObject m_GameObject;
|
||||
private ScheduledEventBase m_RemoveEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_GameObject = gameObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedule the object for removal.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
m_RemoveEvent = Scheduler.Schedule(m_Lifetime, Remove);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the remove event.
|
||||
/// </summary>
|
||||
public void CancelRemoveEvent()
|
||||
{
|
||||
if (m_RemoveEvent != null) {
|
||||
Scheduler.Cancel(m_RemoveEvent);
|
||||
m_RemoveEvent = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been destroyed - no need for removal if it hasn't already been removed.
|
||||
/// </summary>
|
||||
private void OnDisable()
|
||||
{
|
||||
CancelRemoveEvent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the object.
|
||||
/// </summary>
|
||||
private void Remove()
|
||||
{
|
||||
ObjectPool.Destroy(m_GameObject);
|
||||
m_RemoveEvent = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8c9270770f16444e9f3ab8656e2ad72
|
||||
timeCreated: 1508335927
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,221 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Audio;
|
||||
using Opsive.UltimateCharacterController.Character;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.Shared.Events;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
using Opsive.UltimateCharacterController.Networking;
|
||||
using Opsive.UltimateCharacterController.Networking.Traits;
|
||||
#endif
|
||||
using Opsive.UltimateCharacterController.StateSystem;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the location the object should spawn.
|
||||
/// </summary>
|
||||
public class Respawner : StateBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies if the object should spawn in the starting position or use a spawn point.
|
||||
/// </summary>
|
||||
public enum SpawnPositioningMode
|
||||
{
|
||||
None, // The object will not change locations when spawning.
|
||||
StartLocation, // The object will spawn in the same location as the object started in.
|
||||
SpawnPoint // The object will use the Spawn Point system to determine where to spawn.
|
||||
}
|
||||
|
||||
[Tooltip("Specifies the location the object should spawn.")]
|
||||
[SerializeField] protected SpawnPositioningMode m_PositioningMode = SpawnPositioningMode.StartLocation;
|
||||
[Tooltip("The grouping index to use when spawning to a spawn point. A value of -1 will ignore the grouping value.")]
|
||||
[SerializeField] protected int m_Grouping = -1;
|
||||
[Tooltip("The minimum amount of time before the object respawns after death or after being disabled.")]
|
||||
[SerializeField] protected float m_MinRespawnTime = 2;
|
||||
[Tooltip("The maximum amount of time before the object respawns after death or after being disabled.")]
|
||||
[SerializeField] protected float m_MaxRespawnTime = 3;
|
||||
[Tooltip("Should a respawn be scheduled when the object dies?")]
|
||||
[SerializeField] protected bool m_ScheduleRespawnOnDeath = true;
|
||||
[Tooltip("Should a respawn be scheduled when the component is disabled?")]
|
||||
[SerializeField] protected bool m_ScheduleRespawnOnDisable = true;
|
||||
[Tooltip("A set of AudioClips that can be played when the object is respawned.")]
|
||||
[HideInInspector] [SerializeField] protected AudioClipSet m_RespawnAudioClipSet = new AudioClipSet();
|
||||
[Tooltip("Unity event invoked when the object respawns.")]
|
||||
[SerializeField] protected UnityEvent m_OnRespawnEvent;
|
||||
|
||||
public SpawnPositioningMode PositioningMode { get { return m_PositioningMode; } set { m_PositioningMode = value; } }
|
||||
public int Grouping { get { return m_Grouping; } set { m_Grouping = value; } }
|
||||
public float MinRespawnTime { get { return m_MinRespawnTime; } set { m_MinRespawnTime = value; } }
|
||||
public float MaxRespawnTime { get { return m_MaxRespawnTime; } set { m_MaxRespawnTime = value; } }
|
||||
public bool ScheduleRespawnOnDeath { get { return m_ScheduleRespawnOnDeath; } set { m_ScheduleRespawnOnDeath = value; } }
|
||||
public bool ScheduleRespawnOnDisable { get { return m_ScheduleRespawnOnDisable; } set { m_ScheduleRespawnOnDisable = value; } }
|
||||
public AudioClipSet RespawnAudioClipSet { get { return m_RespawnAudioClipSet; } set { m_RespawnAudioClipSet = value; } }
|
||||
public UnityEvent OnRespawnEvent { get { return m_OnRespawnEvent; } set { m_OnRespawnEvent = value; } }
|
||||
|
||||
protected GameObject m_GameObject;
|
||||
private Transform m_Transform;
|
||||
private UltimateCharacterLocomotion m_CharacterLocomotion;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
private INetworkInfo m_NetworkInfo;
|
||||
private INetworkRespawnerMonitor m_NetworkRespawnerMonitor;
|
||||
#endif
|
||||
|
||||
private Vector3 m_StartPosition;
|
||||
private Quaternion m_StartRotation;
|
||||
private ScheduledEventBase m_ScheduledRespawnEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_GameObject = gameObject;
|
||||
m_Transform = transform;
|
||||
m_CharacterLocomotion = m_GameObject.GetCachedComponent<UltimateCharacterLocomotion>();
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
m_NetworkInfo = m_GameObject.GetCachedComponent<INetworkInfo>();
|
||||
m_NetworkRespawnerMonitor = m_GameObject.GetCachedComponent<INetworkRespawnerMonitor>();
|
||||
if (m_NetworkInfo != null && m_NetworkRespawnerMonitor == null) {
|
||||
Debug.LogError("Error: The object " + m_GameObject.name + " must have a NetworkRespawnerMonitor component.");
|
||||
}
|
||||
#endif
|
||||
|
||||
m_StartPosition = m_Transform.position;
|
||||
m_StartRotation = m_Transform.rotation;
|
||||
|
||||
EventHandler.RegisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character has died. Prepare for a respawn.
|
||||
/// </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 (!m_ScheduleRespawnOnDeath) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
// The local player will control when the object respawns.
|
||||
if (m_NetworkInfo != null && !m_NetworkInfo.IsLocalPlayer()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_ScheduledRespawnEvent != null) {
|
||||
Scheduler.Cancel(m_ScheduledRespawnEvent);
|
||||
m_ScheduledRespawnEvent = null;
|
||||
}
|
||||
m_ScheduledRespawnEvent = Scheduler.Schedule(Random.Range(m_MinRespawnTime, m_MaxRespawnTime), Respawn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been disabled.
|
||||
/// </summary>
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
if (m_NetworkInfo != null && !m_NetworkInfo.IsLocalPlayer()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ScheduleRespawnOnDisable && m_ScheduledRespawnEvent == null) {
|
||||
m_ScheduledRespawnEvent = Scheduler.Schedule(Random.Range(m_MinRespawnTime, m_MaxRespawnTime), Respawn);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the location to respawn the object to and then does the respawn.
|
||||
/// </summary>
|
||||
public void Respawn()
|
||||
{
|
||||
m_ScheduledRespawnEvent = null;
|
||||
|
||||
if (m_PositioningMode != SpawnPositioningMode.None) {
|
||||
Vector3 position;
|
||||
Quaternion rotation;
|
||||
if (m_PositioningMode == SpawnPositioningMode.SpawnPoint) {
|
||||
position = m_Transform.position;
|
||||
rotation = m_Transform.rotation;
|
||||
// If the object can't be spawned then try again in the future.
|
||||
if (!SpawnPointManager.GetPlacement(m_GameObject, m_Grouping, ref position, ref rotation)) {
|
||||
m_ScheduledRespawnEvent = Scheduler.Schedule(Random.Range(m_MinRespawnTime, m_MaxRespawnTime), Respawn);
|
||||
return;
|
||||
}
|
||||
} else { // Spawn Location.
|
||||
position = m_StartPosition;
|
||||
rotation = m_StartRotation;
|
||||
}
|
||||
|
||||
Respawn(position, rotation, true);
|
||||
} else {
|
||||
Respawn(m_Transform.position, m_Transform.rotation, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the respawn by setting the position and rotation to the specified values.
|
||||
/// Enable the GameObject and let all of the listening objects know that the object has been respawned.
|
||||
/// </summary>
|
||||
/// <param name="position">The respawn position.</param>
|
||||
/// <param name="rotation">The respawn rotation.</param>
|
||||
/// <param name="transformChange">Was the position or rotation changed?</param>
|
||||
public virtual void Respawn(Vector3 position, Quaternion rotation, bool transformChange)
|
||||
{
|
||||
// Send a pre-respawn event so abilities can stop if they should no longer be active.
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnWillRespawn");
|
||||
|
||||
if (transformChange) {
|
||||
// Characters require a specific setter for the position and rotation.
|
||||
if (m_CharacterLocomotion != null) {
|
||||
m_CharacterLocomotion.SetPositionAndRotation(position, rotation);
|
||||
} else {
|
||||
m_Transform.position = position;
|
||||
m_Transform.rotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
m_GameObject.SetActive(true);
|
||||
|
||||
// Play any respawn audio.
|
||||
m_RespawnAudioClipSet.PlayAudioClip(m_GameObject);
|
||||
|
||||
EventHandler.ExecuteEvent(m_GameObject, "OnRespawn");
|
||||
if (m_OnRespawnEvent != null) {
|
||||
m_OnRespawnEvent.Invoke();
|
||||
}
|
||||
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) {
|
||||
m_NetworkRespawnerMonitor.Respawn(position, rotation, transformChange);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GameObject has been destroyed.
|
||||
/// </summary>
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
if (m_ScheduledRespawnEvent != null) {
|
||||
Scheduler.Cancel(m_ScheduledRespawnEvent);
|
||||
m_ScheduledRespawnEvent = null;
|
||||
}
|
||||
EventHandler.UnregisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69bf5fc13e796d141b5f806bad87f704
|
||||
timeCreated: 1505263435
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Traits
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Applies an impulse to the Rigidbody when enabled.
|
||||
/// </summary>
|
||||
public class RigidbodyImpulse : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Should the force be applied in the local space?")]
|
||||
[SerializeField] protected bool m_LocalForce = true;
|
||||
[Tooltip("The force to apply to the Rigidbody.")]
|
||||
[SerializeField] protected MinMaxVector3 m_Force = new MinMaxVector3(new Vector3(0, 5, 0), new Vector3(0, 5, 0));
|
||||
[Tooltip("Should the torque be applied in the local space?")]
|
||||
[SerializeField] protected bool m_LocalTorque = true;
|
||||
[Tooltip("The torque to apply to the Rigidbody.")]
|
||||
[SerializeField] protected MinMaxVector3 m_Torque;
|
||||
|
||||
private Rigidbody m_Rigidbody;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the impulse force.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
if (m_LocalForce) {
|
||||
m_Rigidbody.AddRelativeForce(m_Force.RandomValue, ForceMode.Impulse);
|
||||
} else {
|
||||
m_Rigidbody.AddForce(m_Force.RandomValue, ForceMode.Impulse);
|
||||
}
|
||||
|
||||
if (m_LocalTorque) {
|
||||
m_Rigidbody.AddRelativeTorque(m_Torque.RandomValue);
|
||||
} else {
|
||||
m_Rigidbody.AddTorque(m_Torque.RandomValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd12990f08235f44f955675d2d959ec8
|
||||
timeCreated: 1508344821
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user