update
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0cdad2dd192fa54981045527ed0b234
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.CharacterAssist
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Character;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the location that the ability should use when determining where to move the limb to. This component should be attached to the target limb location.
|
||||
/// </summary>
|
||||
public class AbilityIKTarget : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The IK limb that should be positioned.")]
|
||||
[SerializeField] protected CharacterIKBase.IKGoal m_Goal;
|
||||
[Tooltip("The amount of time that the ability should wait before setting the IK goal.")]
|
||||
[SerializeField] protected float m_Delay = 0;
|
||||
[Tooltip("The time it takes for the limb to reach the target. A positive value is required.")]
|
||||
[SerializeField] protected float m_InterpolationDuration = 0.2f;
|
||||
[Tooltip("The amount of time after the IK goal is set that the limb should be in the IK location. This value should be greater than the interpolation duration.")]
|
||||
[SerializeField] protected float m_Duration = 1f;
|
||||
|
||||
public CharacterIKBase.IKGoal Goal { get { return m_Goal; } }
|
||||
public float Delay { get { return m_Delay; } }
|
||||
public float InterpolationDuration { get { return m_InterpolationDuration; } }
|
||||
public float Duration { get { return m_Duration; } }
|
||||
|
||||
private Transform m_Transform;
|
||||
public Transform Transform { get { return m_Transform; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_Transform = transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 627fb43e464c809458459585500fad4d
|
||||
timeCreated: 1520365325
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,152 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.CharacterAssist
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Audio;
|
||||
using Opsive.UltimateCharacterController.Traits;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Sets a bool parameter value when interacted with (implements IInteractableTarget).
|
||||
/// </summary>
|
||||
public class AnimatedInteractable : MonoBehaviour, IInteractableTarget, IInteractableMessage
|
||||
{
|
||||
[Tooltip("Can the interactable be interacted with only once?")]
|
||||
[SerializeField] protected bool m_SingleInteract;
|
||||
[Tooltip("The bool parameter name that should be changed when interacted with. Can be empty.")]
|
||||
[SerializeField] protected string m_BoolParameter;
|
||||
[Tooltip("The value to set the bool to when interacted with. Only used if the bool parameter is not empty.")]
|
||||
[SerializeField] protected bool m_BoolInteractValue = true;
|
||||
[Tooltip("The UI message that should be displayed when the bool is enabled.")]
|
||||
[SerializeField] protected string m_BoolEnabledMessage;
|
||||
[Tooltip("The UI message that should be displayed when the bool is disabled.")]
|
||||
[SerializeField] protected string m_BoolDisabledMessage;
|
||||
[Tooltip("Should the bool interact value be toggled after an interact?")]
|
||||
[SerializeField] protected bool m_ToggleBoolInteractValue = true;
|
||||
[Tooltip("The trigger parameter name that should be set when interacted with. Can be empty.")]
|
||||
[SerializeField] protected string m_TriggerParameter;
|
||||
[Tooltip("An array of audio clips that can be played when the interaction starts.")]
|
||||
[SerializeField] protected AudioClip[] m_InteractAudioClips;
|
||||
|
||||
private GameObject m_GameObject;
|
||||
private Animator m_Animator;
|
||||
private AnimatedInteractable[] m_AnimatedInteractables;
|
||||
protected bool m_HasInteracted;
|
||||
private int m_BoolParameterHash;
|
||||
private int m_TriggerParameterHash;
|
||||
private int m_AudioClipIndex = -1;
|
||||
private bool m_ActiveBoolInteractable;
|
||||
|
||||
public bool ActiveBoolInteractable { get { return m_ActiveBoolInteractable; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
protected virtual void Awake()
|
||||
{
|
||||
m_GameObject = gameObject;
|
||||
m_Animator = GetComponent<Animator>();
|
||||
if (!string.IsNullOrEmpty(m_BoolParameter)) {
|
||||
m_BoolParameterHash = Animator.StringToHash(m_BoolParameter);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(m_TriggerParameter)) {
|
||||
m_TriggerParameterHash = Animator.StringToHash(m_TriggerParameter);
|
||||
}
|
||||
var animatedInteractables = GetComponents<AnimatedInteractable>();
|
||||
if (animatedInteractables.Length > 1) {
|
||||
m_AnimatedInteractables = new AnimatedInteractable[animatedInteractables.Length - 1];
|
||||
var count = 0;
|
||||
for (int i = 0; i < animatedInteractables.Length; ++i) {
|
||||
if (animatedInteractables[i] == this) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_AnimatedInteractables[count] = animatedInteractables[i];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the target be interacted with?
|
||||
/// </summary>
|
||||
/// <param name="character">The character that wants to interactact with the target.</param>
|
||||
/// <returns>True if the target can be interacted with.</returns>
|
||||
public bool CanInteract(GameObject character)
|
||||
{
|
||||
return !m_HasInteracted || !m_SingleInteract;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interact with the target.
|
||||
/// </summary>
|
||||
/// <param name="character">The character that wants to interactact with the target.</param>
|
||||
public void Interact(GameObject character)
|
||||
{
|
||||
if (m_BoolParameterHash != 0) {
|
||||
// If the bool value can be toggled then there's a chance that another AnimatedInteractable is currently active. In that case the original
|
||||
// AnimatedInteractable should respond to the interact event.
|
||||
if (m_AnimatedInteractables != null) {
|
||||
for (int i = 0; i < m_AnimatedInteractables.Length; ++i) {
|
||||
if (m_AnimatedInteractables[i].ActiveBoolInteractable) {
|
||||
m_AnimatedInteractables[i].Interact(character);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_Animator.SetBool(m_BoolParameterHash, m_BoolInteractValue);
|
||||
if (m_ToggleBoolInteractValue) {
|
||||
m_BoolInteractValue = !m_BoolInteractValue;
|
||||
m_ActiveBoolInteractable = !m_ActiveBoolInteractable;
|
||||
}
|
||||
}
|
||||
if (m_TriggerParameterHash != 0) {
|
||||
m_Animator.SetTrigger(m_TriggerParameterHash);
|
||||
}
|
||||
|
||||
if (m_InteractAudioClips.Length > 0) {
|
||||
// Sequentually switch between audio clips.
|
||||
m_AudioClipIndex = (m_AudioClipIndex + 1) % m_InteractAudioClips.Length;
|
||||
AudioManager.Play(m_GameObject, m_InteractAudioClips[m_AudioClipIndex]);
|
||||
}
|
||||
m_HasInteracted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the interact variables.
|
||||
/// </summary>
|
||||
public void ResetInteract()
|
||||
{
|
||||
m_HasInteracted = false;
|
||||
m_AudioClipIndex = -1;
|
||||
m_Animator.Rebind();
|
||||
}
|
||||
|
||||
/// <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_BoolParameterHash != 0) {
|
||||
// If the bool value can be toggled then there's a chance that another AnimatedInteractable is currently active. In that case the original
|
||||
// AnimatedInteractable should respond to the message.
|
||||
if (m_AnimatedInteractables != null) {
|
||||
for (int i = 0; i < m_AnimatedInteractables.Length; ++i) {
|
||||
if (m_AnimatedInteractables[i].ActiveBoolInteractable) {
|
||||
return m_AnimatedInteractables[i].AbilityMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m_BoolInteractValue ? m_BoolEnabledMessage : m_BoolDisabledMessage;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f54cf0812fcc9d6458c6102e4df6be2b
|
||||
timeCreated: 1520793857
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,80 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.CharacterAssist
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Character;
|
||||
using Opsive.UltimateCharacterController.Character.Abilities;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// A Gravity Zone represents a trigger area that adjusts the character's gravity direction when the character is within the trigger.
|
||||
/// </summary>
|
||||
public abstract class GravityZone : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines the direction of gravity that should be applied.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the character.</param>
|
||||
/// <returns>The direction of gravity that should be applied.</returns>
|
||||
public abstract Vector3 DetermineGravityDirection(Vector3 position);
|
||||
|
||||
/// <summary>
|
||||
/// An object has entered the trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The object that entered the trigger.</param>
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
// A main character collider is required.
|
||||
if (!MathUtility.InLayerMask(other.gameObject.layer, 1 << LayerManager.Character)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The object must be a character.
|
||||
var characterLocomotion = other.GetComponentInParent<UltimateCharacterLocomotion>();
|
||||
if (characterLocomotion == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// With the Align To Gravity Zone ability.
|
||||
var alignToGravity = characterLocomotion.GetAbility<AlignToGravityZone>();
|
||||
if (alignToGravity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
alignToGravity.RegisterGravityZone(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An object has exited the trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The collider that exited the trigger.</param>
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
// A main character collider is required.
|
||||
if (!MathUtility.InLayerMask(other.gameObject.layer, 1 << LayerManager.Character)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// The object must be a character.
|
||||
var characterLocomotion = other.GetComponentInParent<UltimateCharacterLocomotion>();
|
||||
if (characterLocomotion == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// With the Align To Gravity Zone ability.
|
||||
var alignToGravity = characterLocomotion.GetAbility<AlignToGravityZone>();
|
||||
if (alignToGravity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
alignToGravity.UnregisterGravityZone(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82bf2d12f02310144aabbb3f7f06e6ec
|
||||
timeCreated: 1543955724
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.CharacterAssist
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Traits;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Heals the object that has the Health component.
|
||||
/// </summary>
|
||||
public class HealthPickup : ObjectPickup
|
||||
{
|
||||
[Tooltip("The amount of health to replenish.")]
|
||||
[SerializeField] private float m_HealthAmount = 40;
|
||||
[Tooltip("Should the object be picked up even if the object has full health?")]
|
||||
[SerializeField] private bool m_AlwaysPickup;
|
||||
|
||||
public float HealthAmount { get { return m_HealthAmount; } set { m_HealthAmount = value; } }
|
||||
public bool AlwaysPickup { get { return m_AlwaysPickup; } set { m_AlwaysPickup = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// A GameObject has entered the trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The GameObject that entered the trigger.</param>
|
||||
public override void TriggerEnter(GameObject other)
|
||||
{
|
||||
var health = other.GetCachedParentComponent<Health>();
|
||||
if (health != null && health.IsAlive()) {
|
||||
if (health.Heal(m_HealthAmount) || m_AlwaysPickup) {
|
||||
ObjectPickedUp(health.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50af1f3344f80c64dbd40bdbc4d9df5e
|
||||
timeCreated: 1500999042
|
||||
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.Objects.CharacterAssist
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for any object that can be driven.
|
||||
/// </summary>
|
||||
public interface IDriveSource
|
||||
{
|
||||
/// <summary>
|
||||
/// The GameObject of the vehicle.
|
||||
/// </summary>
|
||||
GameObject GameObject { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Transform of the vehicle.
|
||||
/// </summary>
|
||||
Transform Transform { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The location that the character drives the vehicle from.
|
||||
/// </summary>
|
||||
Transform DriverLocation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique identifier of the object. This value is used within the AbilityIntData parameter of the character's animator.
|
||||
/// </summary>
|
||||
int AnimatorID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The character has started to enter the vehicle.
|
||||
/// </summary>
|
||||
/// <param name="character">The character that is entering the vehicle.</param>
|
||||
void EnterVehicle(GameObject character);
|
||||
|
||||
/// <summary>
|
||||
/// The character has entered the vehicle.
|
||||
/// </summary>
|
||||
/// <param name="character">The character that entered the vehicle.</param>
|
||||
void EnteredVehicle(GameObject character);
|
||||
|
||||
/// <summary>
|
||||
/// The character has started to exit the vehicle.
|
||||
/// </summary>
|
||||
/// <param name="character">The character that is exiting the vehicle.</param>
|
||||
void ExitVehicle(GameObject character);
|
||||
|
||||
/// <summary>
|
||||
/// The character has exited the vehicle.
|
||||
/// </summary>
|
||||
/// <param name="character">The character that exited the vehicle.</param>
|
||||
void ExitedVehicle(GameObject character);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b364593e2dde534a93176a932934cf6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.CharacterAssist
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Inventory;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Extends ItemPickupBase to allow for ItemIdentifier pickups.
|
||||
/// </summary>
|
||||
public class ItemPickup : ItemPickupBase
|
||||
{
|
||||
[Tooltip("An array of ItemIdentifiers to be picked up.")]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("m_ItemTypeCounts")]
|
||||
[SerializeField] protected ItemDefinitionAmount[] m_ItemDefinitionAmounts;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the ItemDefinitionAmount that the ItemPickup contains.
|
||||
/// </summary>
|
||||
/// <returns>The ItemDefinitionAmount that the ItemPickup contains.</returns>
|
||||
public override ItemDefinitionAmount[] GetItemDefinitionAmounts()
|
||||
{
|
||||
return m_ItemDefinitionAmounts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the ItemPickup ItemDefinitionAmounts value.
|
||||
/// </summary>
|
||||
/// <param name="itemDefinitionAmounts">The ItemDefinitionAmount that should be set.</param>
|
||||
public override void SetItemDefinitionAmounts(ItemDefinitionAmount[] itemDefinitionAmounts)
|
||||
{
|
||||
m_ItemDefinitionAmounts = itemDefinitionAmounts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal method which picks up the ItemIdentifier.
|
||||
/// </summary>
|
||||
/// <param name="character">The character that should pick up the ItemIdentifier.</param>
|
||||
/// <param name="inventory">The inventory belonging to the character.</param>
|
||||
/// <param name="slotID">The slot ID that picked up the item. A -1 value will indicate no specified slot.</param>
|
||||
/// <param name="immediatePickup">Should the item be picked up immediately?</param>
|
||||
/// <param name="forceEquip">Should the item be force equipped?</param>
|
||||
/// <returns>True if an ItemIdentifier was picked up.</returns>
|
||||
protected override bool DoItemIdentifierPickupInternal(GameObject character, InventoryBase inventory, int slotID, bool immediatePickup, bool forceEquip)
|
||||
{
|
||||
// Add the ItemIdentifiers to the Inventory. This allows the character to pick up the actual item and any consumable ItemIdentifier (such as ammo).
|
||||
var pickedUp = false;
|
||||
if (m_ItemDefinitionAmounts != null) {
|
||||
for (int i = 0; i < m_ItemDefinitionAmounts.Length; ++i) {
|
||||
if (inventory.Pickup(m_ItemDefinitionAmounts[i].ItemIdentifier, m_ItemDefinitionAmounts[i].Amount, slotID, immediatePickup, forceEquip)) {
|
||||
pickedUp = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pickedUp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e704e820a055af41b5a98fa3a79f767
|
||||
timeCreated: 1500999042
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,280 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.CharacterAssist
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.Shared.Inventory;
|
||||
using Opsive.UltimateCharacterController.Character;
|
||||
using Opsive.UltimateCharacterController.Inventory;
|
||||
using Opsive.UltimateCharacterController.Items;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Base class which allows an object with the Inventory component to pickup items when a character enters the trigger.
|
||||
/// </summary>
|
||||
public abstract class ItemPickupBase : ObjectPickup
|
||||
{
|
||||
/// <summary>
|
||||
/// Class which allows the specified item to be collected by the character at runtime.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class PickupSet
|
||||
{
|
||||
[Tooltip("The item prefab that can be picked up.")]
|
||||
[SerializeField] protected GameObject m_Item;
|
||||
[Tooltip("The ID of the category that the ItemSet should be added to.")]
|
||||
[SerializeField] protected uint m_CategoryID;
|
||||
[Tooltip("The ItemSet to load when the item is picked up.")]
|
||||
[SerializeField] protected ItemSet m_ItemSet;
|
||||
[Tooltip("Is the ItemSet the default ItemSet within the category?")]
|
||||
[SerializeField] protected bool m_Default;
|
||||
|
||||
public GameObject Item { get { return m_Item; } set { m_Item = value; } }
|
||||
public uint CategoryID { get { return m_CategoryID; } set { m_CategoryID = value; } }
|
||||
public ItemSet ItemSet { get { return m_ItemSet; } set { m_ItemSet = value; } }
|
||||
public bool Default { get { return m_Default; } set { m_Default = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Default PickupSet constructor.
|
||||
/// </summary>
|
||||
public PickupSet() { m_ItemSet = new ItemSet(); }
|
||||
}
|
||||
|
||||
[Tooltip("An array of items and ItemSets to pick up.")]
|
||||
[SerializeField] protected PickupSet[] m_ItemPickupSet;
|
||||
[Tooltip("Should the object be picked up even if the inventory cannot hold any more of the ItemIdentifier?")]
|
||||
[SerializeField] protected bool m_AlwaysPickup;
|
||||
|
||||
public PickupSet[] ItemPickupSet { get { return m_ItemPickupSet; } set { m_ItemPickupSet = value; } }
|
||||
public bool AlwaysPickup { get { return m_AlwaysPickup; } set { m_AlwaysPickup = value; } }
|
||||
|
||||
private bool m_PickedUp;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
if (m_ItemPickupSet != null) {
|
||||
for (int i = 0; i < m_ItemPickupSet.Length; ++i) {
|
||||
// The item GameObject must contain the Item component.
|
||||
if (m_ItemPickupSet[i].Item != null && m_ItemPickupSet[i].Item.GetComponent<Item>() == null) {
|
||||
Debug.LogError($"Error: {m_ItemPickupSet[i].Item.name} doesn't contain the Item component.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A GameObject has entered the trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The GameObject that entered the trigger.</param>
|
||||
public override void TriggerEnter(GameObject other)
|
||||
{
|
||||
TriggerEnter(other, -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A GameObject has entered the trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The other GameObject which is trying to do the pickup.</param>
|
||||
/// <param name="slotID">The slot ID that picked up the item. A -1 value will indicate no specified slot.</param>
|
||||
public void TriggerEnter(GameObject other, int slotID)
|
||||
{
|
||||
// The object must have an enabled inventory in order for the item to be picked up.
|
||||
var inventory = other.GetCachedParentComponent<InventoryBase>();
|
||||
if (inventory == null || !inventory.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The collider must be a main character collider. Items or ragdoll colliders don't count.
|
||||
var layerManager = inventory.gameObject.GetCachedComponent<CharacterLayerManager>();
|
||||
if (layerManager == null || !MathUtility.InLayerMask(other.gameObject.layer, layerManager.CharacterLayer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TryItemPickup(inventory, slotID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to pickup the item.
|
||||
/// </summary>
|
||||
/// <param name="inventory">The inventory belonging to the character.</param>
|
||||
/// <param name="slotID">The slot ID that picked up the item. A -1 value will indicate no specified slot.</param>
|
||||
private void TryItemPickup(InventoryBase inventory, int slotID)
|
||||
{
|
||||
if (m_PickupOnTriggerEnter) {
|
||||
DoItemPickup(inventory.gameObject, inventory, slotID, false, true);
|
||||
} else {
|
||||
// If the object is a character that has a disabled pickup item ability then the item should be picked up immediately,
|
||||
// even if the pickup on trigger enter is disabled.
|
||||
var character = inventory.gameObject.GetCachedComponent<Character.UltimateCharacterLocomotion>();
|
||||
if (character != null) {
|
||||
var pickupItem = character.GetAbility<Character.Abilities.PickupItem>();
|
||||
if (pickupItem != null && pickupItem.CanItemPickup()) {
|
||||
DoItemPickup(inventory.gameObject, inventory, slotID, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Picks up the item.
|
||||
/// </summary>
|
||||
/// <param name="character">The character that should pick up the item.</param>
|
||||
/// <param name="inventory">The inventory belonging to the character.</param>
|
||||
/// <param name="slotID">The slot ID that picked up the item. A -1 value will indicate no specified slot.</param>
|
||||
/// <param name="immediatePickup">Should the item be picked up immediately?</param>
|
||||
/// <param name="pickupItemIdentifier">Should the ItemIdentifier be picked up? This should be false if the ItemIdentifier will later be picked up.</param>
|
||||
public void DoItemPickup(GameObject character, InventoryBase inventory, int slotID, bool immediatePickup, bool pickupItemIdentifier)
|
||||
{
|
||||
// Add any items to the character.
|
||||
if (m_ItemPickupSet != null && m_ItemPickupSet.Length > 0) {
|
||||
// Spawn the item under the character's ItemPlacement GameObject.
|
||||
var itemPlacement = character.GetComponentInChildren<ItemPlacement>(true);
|
||||
if (itemPlacement == null) {
|
||||
Debug.LogError($"Error: ItemPlacement doesn't exist under the character {character.name}.");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < m_ItemPickupSet.Length; ++i) {
|
||||
// If the Item is null then only the ItemSet should be added.
|
||||
if (m_ItemPickupSet[i].Item == null) {
|
||||
var itemSetManager = character.GetCachedComponent<ItemSetManagerBase>();
|
||||
if (itemSetManager == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IItemCategoryIdentifier category = null;
|
||||
var addItemSetParents = true;
|
||||
// If no item is specified then the category should be retrieved from the Item Definition.
|
||||
if (m_ItemPickupSet[i].CategoryID == 0) {
|
||||
for (int j = 0; j < m_ItemPickupSet[i].ItemSet.Slots.Length; ++j) {
|
||||
var itemDefinition = m_ItemPickupSet[i].ItemSet.Slots[j];
|
||||
if (itemDefinition != null) {
|
||||
category = itemDefinition.GetItemCategory();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// A specific category was specified.
|
||||
if (itemSetManager.CategoryItemSets != null) {
|
||||
for (int j = 0; j < itemSetManager.CategoryItemSets.Length; ++j) {
|
||||
if (itemSetManager.CategoryItemSets[j].CategoryID == m_ItemPickupSet[i].CategoryID) {
|
||||
category = itemSetManager.CategoryItemSets[j].ItemCategory;
|
||||
addItemSetParents = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (category != null) {
|
||||
itemSetManager.AddItemSet(m_ItemPickupSet[i].ItemSet, m_ItemPickupSet[i].Default, category, addItemSetParents);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (slotID != -1 && (slotID >= m_ItemPickupSet[i].ItemSet.Slots.Length || m_ItemPickupSet[i].ItemSet.Slots[slotID] == null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var item = m_ItemPickupSet[i].Item.GetCachedComponent<Item>();
|
||||
if (inventory.HasItem(item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Instantiate the item that will be added to the character.
|
||||
item = Item.SpawnItem(character, item);
|
||||
|
||||
// Add the ItemSet before the item so the item can use the added ItemSet.
|
||||
if (m_ItemPickupSet[i].ItemSet != null) {
|
||||
var itemSetManager = character.GetCachedComponent<ItemSetManager>();
|
||||
if (itemSetManager != null) {
|
||||
m_ItemPickupSet[i].ItemSet.ItemIdentifiers = new Shared.Inventory.IItemIdentifier[m_ItemPickupSet[i].ItemSet.Slots.Length];
|
||||
m_ItemPickupSet[i].ItemSet.ItemIdentifiers[item.SlotID] = item.ItemIdentifier;
|
||||
itemSetManager.AddItemSet(item, m_ItemPickupSet[i].ItemSet, m_ItemPickupSet[i].Default);
|
||||
}
|
||||
}
|
||||
|
||||
// All of the setup is complete - add the item to the inventory.
|
||||
inventory.AddItem(item, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
m_PickedUp = m_AlwaysPickup;
|
||||
if (pickupItemIdentifier) {
|
||||
// Even if the ItemIdentifier doesn't have space it may be equipped by the inventory. The object should be considered as picked up in this situation.
|
||||
EventHandler.RegisterEvent<Item, int>(character, "OnAbilityWillEquipItem", OnWillEquipItem);
|
||||
if (DoItemIdentifierPickup(character, inventory, slotID, immediatePickup, true)) {
|
||||
m_PickedUp = true;
|
||||
}
|
||||
EventHandler.UnregisterEvent<Item, int>(character, "OnAbilityWillEquipItem", OnWillEquipItem);
|
||||
} else {
|
||||
// If pickup ItemIdentifier is false then the PickupItem ability will pick up the ItemIdentifier.
|
||||
m_PickedUp = true;
|
||||
}
|
||||
|
||||
if (m_PickedUp) {
|
||||
ObjectPickedUp(character);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the ItemDefinitionAmount that the ItemPickup contains.
|
||||
/// </summary>
|
||||
/// <returns>The ItemDefinitionAmount that the ItemPickup contains.</returns>
|
||||
public abstract ItemDefinitionAmount[] GetItemDefinitionAmounts();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the ItemPickup ItemDefinitionAmounts value.
|
||||
/// </summary>
|
||||
/// <param name="itemDefinitionAmounts">The ItemDefinitionAmount that should be set.</param>
|
||||
public abstract void SetItemDefinitionAmounts(ItemDefinitionAmount[] itemDefinitionAmounts);
|
||||
|
||||
/// <summary>
|
||||
/// Picks up the ItemIdentifier.
|
||||
/// </summary>
|
||||
/// <param name="character">The character that should pick up the ItemIdentifier.</param>
|
||||
/// <param name="inventory">The inventory belonging to the character.</param>
|
||||
/// <param name="slotID">The slot ID that picked up the item. A -1 value will indicate no specified slot.</param>
|
||||
/// <param name="immediatePickup">Should the item be picked up immediately?</param>
|
||||
/// <param name="forceEquip">Should the item be force equipped?</param>
|
||||
/// <returns>True if an ItemIdentifier was picked up.</returns>
|
||||
public bool DoItemIdentifierPickup(GameObject character, InventoryBase inventory, int slotID, bool immediatePickup, bool forceEquip)
|
||||
{
|
||||
EventHandler.ExecuteEvent(character, "OnItemPickupStartPickup");
|
||||
var result = DoItemIdentifierPickupInternal(character, inventory, slotID, immediatePickup, forceEquip);
|
||||
EventHandler.ExecuteEvent(character, "OnItemPickupStopPickup");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal method which picks up the ItemIdentifier.
|
||||
/// </summary>
|
||||
/// <param name="character">The character that should pick up the ItemIdentifier.</param>
|
||||
/// <param name="inventory">The inventory belonging to the character.</param>
|
||||
/// <param name="slotID">The slot ID that picked up the item. A -1 value will indicate no specified slot.</param>
|
||||
/// <param name="immediatePickup">Should the item be picked up immediately?</param>
|
||||
/// <param name="forceEquip">Should the item be force equipped?</param>
|
||||
/// <returns>True if an ItemIdentifier was picked up.</returns>
|
||||
protected abstract bool DoItemIdentifierPickupInternal(GameObject character, InventoryBase inventory, int slotID, bool immediatePickup, bool forceEquip);
|
||||
|
||||
/// <summary>
|
||||
/// The specified item will be equipped.
|
||||
/// </summary>
|
||||
/// <param name="item">The item that will be equipped.</param>
|
||||
/// <param name="slotID">The slot that the item will occupy.</param>
|
||||
private void OnWillEquipItem(Item item, int slotID)
|
||||
{
|
||||
m_PickedUp = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75b4a0fd8e4a0b14bb9cac502a77aa37
|
||||
timeCreated: 1500999042
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,116 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.CharacterAssist
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the location that the character should move to when the Move Towards ability is started.
|
||||
/// </summary>
|
||||
public class MoveTowardsLocation : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The offset relative to the transform that the character should move towards.")]
|
||||
[SerializeField] protected Vector3 m_Offset = new Vector3(0, 0, 1);
|
||||
[Tooltip("The yaw offset relative to the transform that the character should rotate towards.")]
|
||||
[SerializeField] protected float m_YawOffset = 180;
|
||||
[Tooltip("The size of the area that the character can start the ability at. A zero value indicates that the character must land on the exact offset.")]
|
||||
[SerializeField] protected Vector3 m_Size;
|
||||
[Tooltip("The ability can start when the distance between the start location and character is less than the specified value.")]
|
||||
[Range(0.0001f, 100)] [SerializeField] protected float m_Distance = 0.01f;
|
||||
[Tooltip("The ability can start when the angle threshold between the start location and character is less than the specified value.")]
|
||||
[Range(0, 360)] [SerializeField] protected float m_Angle = 0.5f;
|
||||
[Tooltip("Is the character required to be on the ground?")]
|
||||
[SerializeField] protected bool m_RequireGrounded = true;
|
||||
[Tooltip("Should the ability wait to start until all transitions are complete?")]
|
||||
[SerializeField] protected bool m_PrecisionStart = true;
|
||||
[Tooltip("The multiplier to apply to the character's speed when moving to the start location.")]
|
||||
[SerializeField] protected float m_MovementMultiplier = 1;
|
||||
|
||||
public Vector3 Offset { get { return m_Offset; } set { m_Offset = value; } }
|
||||
public float YawOffset { get { return m_YawOffset; } set { m_YawOffset = value; } }
|
||||
public Vector3 Size { get { return m_Size; } set { m_Size = value; } }
|
||||
public float Distance { get { return m_Distance; } set { m_Distance = value; } }
|
||||
public float Angle { get { return m_Angle; } set { m_Angle = value; } }
|
||||
public bool RequireGrounded { get { return m_RequireGrounded; } set { m_RequireGrounded = value; } }
|
||||
public bool PrecisionStart { get { return m_PrecisionStart; } set { m_PrecisionStart = value; } }
|
||||
public float MovementMultiplier { get { return m_MovementMultiplier; } set { m_MovementMultiplier = value; } }
|
||||
|
||||
private Transform m_Transform;
|
||||
private float m_StartYawOffset;
|
||||
private Vector3 m_StartOffset;
|
||||
|
||||
public Vector3 TargetPosition { get { return m_Transform.TransformPoint(m_Offset); } }
|
||||
public Quaternion TargetRotation { get { return m_Transform.rotation * Quaternion.Euler(0, m_YawOffset, 0); } }
|
||||
public float StartYawOffset { get { return m_StartYawOffset; } }
|
||||
public Vector3 StartOffset { get { return m_StartOffset; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_Transform = transform;
|
||||
m_StartYawOffset = m_YawOffset;
|
||||
m_StartOffset = m_Offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the direction that the character should move towards.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the character.</param>
|
||||
/// <param name="rotation">The rotation of the character.</param>
|
||||
/// <returns>The direction that the character should move towards.</returns>
|
||||
public Vector3 GetTargetDirection(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
var direction = m_Transform.TransformPoint(m_Offset) - position;
|
||||
if (m_Size.sqrMagnitude == 0) {
|
||||
return MathUtility.InverseTransformDirection(direction, rotation);
|
||||
}
|
||||
|
||||
var size = m_Transform.TransformDirection(m_Size);
|
||||
if (Mathf.Abs(direction.x) < Mathf.Abs(size.x / 2)) { direction.x = 0; }
|
||||
if (Mathf.Abs(direction.y) < Mathf.Abs(size.y / 2)) { direction.y = 0; }
|
||||
if (Mathf.Abs(direction.z) < Mathf.Abs(size.z / 2)) { direction.z = 0; }
|
||||
return MathUtility.InverseTransformDirection(direction, rotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the character in a valid position?
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the character.</param>
|
||||
/// <param name="rotation">The rotation of the character.</param>
|
||||
/// <param name="grounded">Is the character grounded?</param>
|
||||
/// <returns>True if the position is valid.</returns>
|
||||
public bool IsPositionValid(Vector3 position, Quaternion rotation, bool grounded)
|
||||
{
|
||||
var direction = GetTargetDirection(position, rotation);
|
||||
if (Mathf.Abs(direction.x) <= m_Distance &&
|
||||
((m_RequireGrounded && grounded) || (!m_RequireGrounded && (Mathf.Abs(direction.y) <= m_Distance))) &&
|
||||
Mathf.Abs(direction.z) <= m_Distance) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the character in a valid rotation?
|
||||
/// </summary>
|
||||
/// <param name="rotation">The rotation of the character.</param>
|
||||
/// <returns>True if the rotation is valid.</returns>
|
||||
public bool IsRotationValid(Quaternion rotation)
|
||||
{
|
||||
Vector3 forwardDirection;
|
||||
if (m_RequireGrounded) {
|
||||
forwardDirection = Vector3.ProjectOnPlane(m_Transform.forward, rotation * Vector3.up);
|
||||
} else {
|
||||
forwardDirection = m_Transform.forward;
|
||||
}
|
||||
return Vector3.Angle(Quaternion.Euler(0, m_YawOffset, 0) * forwardDirection, rotation * Vector3.forward) <= m_Angle / 2 + 0.001f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e1740b57cc4f204399681ca7a2edd2f
|
||||
timeCreated: 1520365325
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects
|
||||
{
|
||||
using Opsive.UltimateCharacterController.StateSystem;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Activates or deactivates the GameObject based on the state.
|
||||
/// </summary>
|
||||
public class ObjectActivator : StateBehavior
|
||||
{
|
||||
[Tooltip("Should the GameObject be activated?")]
|
||||
[SerializeField] protected bool m_Active = true;
|
||||
|
||||
public bool Active { get { return m_Active; } set { m_Active = value; } }
|
||||
|
||||
private GameObject m_GameObject;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
m_GameObject = gameObject;
|
||||
|
||||
base.Awake();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The StateManager has changed the active state on the current object.
|
||||
/// </summary>
|
||||
public override void StateChange()
|
||||
{
|
||||
base.StateChange();
|
||||
|
||||
m_GameObject.SetActive(m_Active);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0b2f510676cc504cacc3fdbce05b906
|
||||
timeCreated: 1512934167
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the number of forward faces the object has, used by the Detect Object Ability Base ability.
|
||||
/// </summary>
|
||||
public class ObjectForwardFaces : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The number of forward facing sides the object has.")]
|
||||
[SerializeField] protected int m_ForwardFaceCount = 1;
|
||||
|
||||
public int ForwardFaceCount { get { return m_ForwardFaceCount; } set { m_ForwardFaceCount = value; } }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 330bb3e76868731429949dcb8df426b3
|
||||
timeCreated: 1527000257
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a unique identifier for the object that this component is attached to, used by the Detect Object Ability Base ability.
|
||||
/// </summary>
|
||||
public class ObjectIdentifier : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The value of the identifier.")]
|
||||
[SerializeField] protected uint m_ID;
|
||||
|
||||
public uint ID { get { return m_ID; } set { m_ID = value; } }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3ebcf42a9d8f9145a73c60c46c60bc2
|
||||
timeCreated: 1527000257
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,211 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.CharacterAssist
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Audio;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
using Opsive.UltimateCharacterController.Networking.Game;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for any object that can be picked up.
|
||||
/// </summary>
|
||||
public abstract class ObjectPickup : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The amount of time to enable the trigger after the object has been enabled and the rigidbody has stopped moving.")]
|
||||
[SerializeField] protected float m_TriggerEnableDelay = 4;
|
||||
[Tooltip("Should the item be picked up when the character enters the trigger?")]
|
||||
[SerializeField] protected bool m_PickupOnTriggerEnter = true;
|
||||
[Tooltip("The amount that the object should rotate while waiting to be picked up.")]
|
||||
[SerializeField] protected Vector3 m_RotationSpeed;
|
||||
[Tooltip("A set of AudioClips that can be played when the object is picked up.")]
|
||||
[SerializeField] protected AudioClipSet m_PickupAudioClipSet = new AudioClipSet();
|
||||
[Tooltip("The text that should be shown by the message monitor when the object is picked up.")]
|
||||
[SerializeField] protected string m_PickupMessageText;
|
||||
[Tooltip("The sprite that should be drawn by the message monitor when the object is picked up.")]
|
||||
[SerializeField] protected Sprite m_PickupMessageIcon;
|
||||
|
||||
public float TriggerEnableDelay { get { return m_TriggerEnableDelay; } set { m_TriggerEnableDelay = value; } }
|
||||
public bool PickupOnTriggerEnter { get { return m_PickupOnTriggerEnter; } set { m_PickupOnTriggerEnter = value; } }
|
||||
public Vector3 RotationSpeed { get { return m_RotationSpeed; } set { m_RotationSpeed = value; enabled = m_RotationSpeed.sqrMagnitude > 0; } }
|
||||
public AudioClipSet PickupAudioClipSet { get { return m_PickupAudioClipSet; } set { m_PickupAudioClipSet = value; } }
|
||||
public string PickupMessageText { get { return m_PickupMessageText; } set { m_PickupMessageText = value; } }
|
||||
public Sprite PickupMessageIcon { get { return m_PickupMessageIcon; } set { m_PickupMessageIcon = value; } }
|
||||
|
||||
private GameObject m_GameObject;
|
||||
private Transform m_Transform;
|
||||
private Rigidbody m_Rigidbody;
|
||||
private Collider m_Trigger;
|
||||
|
||||
private int m_StartLayer;
|
||||
private bool m_IsDepleted = true;
|
||||
private ScheduledEventBase m_TriggerEnableEvent;
|
||||
private bool m_Initialized;
|
||||
|
||||
public bool IsDepleted { get { return m_IsDepleted; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
protected virtual void Awake()
|
||||
{
|
||||
m_GameObject = gameObject;
|
||||
m_Transform = transform;
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
m_StartLayer = m_GameObject.layer;
|
||||
|
||||
// Get a reference to the trigger and non-trigger collider. The collider will be disabled when the Rigidbody has stopped moving. The trigger will be enabled
|
||||
// when the Rigidbody has stopped moving.
|
||||
var colliders = GetComponents<Collider>();
|
||||
for (int i = 0; i < colliders.Length; ++i) {
|
||||
if (colliders[i].isTrigger) {
|
||||
m_Trigger = colliders[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Trigger == null) {
|
||||
Debug.LogError("Error: A trigger must exist on the ObjectPickup component on the GameObject " + name + ".");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been enabled.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
Initialize(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the object.
|
||||
/// </summary>
|
||||
/// <param name="forceInitialization">Should the object be initialized even if it isn't depleted?</param>
|
||||
public void Initialize(bool forceInitialization)
|
||||
{
|
||||
// If the item isn't depleted then it has already been initialized.
|
||||
if (!m_IsDepleted && !forceInitialization) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_IsDepleted = false;
|
||||
if (m_TriggerEnableDelay > 0) {
|
||||
m_Trigger.enabled = false;
|
||||
m_GameObject.layer = LayerManager.IgnoreRaycast;
|
||||
if (m_Initialized || forceInitialization) {
|
||||
// If a rigidbody exists then the trigger event should be scheduled after the rigidbody has settled. If a rigidbody does not exist then
|
||||
// the event should be scheduled immediately.
|
||||
if (m_Rigidbody != null) {
|
||||
if (m_TriggerEnableEvent != null) {
|
||||
Scheduler.Cancel(m_TriggerEnableEvent);
|
||||
m_TriggerEnableEvent = null;
|
||||
}
|
||||
Scheduler.Schedule(0.2f, CheckVelocity);
|
||||
} else {
|
||||
m_TriggerEnableEvent = Scheduler.Schedule(m_TriggerEnableDelay, EnableTrigger);
|
||||
}
|
||||
} else {
|
||||
// If the object isn't initialized yet then this is the first time the object has spawned.
|
||||
EnableTrigger();
|
||||
}
|
||||
}
|
||||
m_Initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable the component when the Rigidbody has settled.
|
||||
/// </summary>
|
||||
private void CheckVelocity()
|
||||
{
|
||||
if (m_Rigidbody.linearVelocity.sqrMagnitude < 0.01f) {
|
||||
m_TriggerEnableEvent = Scheduler.Schedule(m_TriggerEnableDelay, EnableTrigger);
|
||||
return;
|
||||
}
|
||||
// The Rigidbody hasn't settled yet - check the velocity again in the future.
|
||||
Scheduler.Schedule(0.2f, CheckVelocity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the trigger.
|
||||
/// </summary>
|
||||
private void EnableTrigger()
|
||||
{
|
||||
m_Trigger.enabled = true;
|
||||
m_TriggerEnableEvent = null;
|
||||
m_GameObject.layer = m_StartLayer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optionally rotates the object.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
m_Transform.rotation *= Quaternion.Euler(m_RotationSpeed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An object has entered the trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The object which entered the trigger.</param>
|
||||
public virtual void OnTriggerEnter(Collider other)
|
||||
{
|
||||
// The object can't be picked up if it is depleted.
|
||||
if (m_IsDepleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
TriggerEnter(other.gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A GameObject has entered the trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The GameObject that entered the trigger.</param>
|
||||
public abstract void TriggerEnter(GameObject other);
|
||||
|
||||
/// <summary>
|
||||
/// The object has been picked up.
|
||||
/// </summary>
|
||||
/// <param name="pickedUpBy">A reference to the object that picked up the object.</param>
|
||||
protected virtual void ObjectPickedUp(GameObject pickedUpBy)
|
||||
{
|
||||
// The object may not have been instantiated within the scene.
|
||||
if (m_GameObject == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_IsDepleted = true;
|
||||
|
||||
// Send an event notifying of the pickup.
|
||||
EventHandler.ExecuteEvent(pickedUpBy, "OnObjectPickedUp", this);
|
||||
|
||||
// Optionally play a pickup sound if the object picking up the item is attached to a camera.
|
||||
// A null GameObject indicates that the clip will play from the AudioManager.
|
||||
var camera = Utility.UnityEngineUtility.FindCamera(pickedUpBy);
|
||||
if (camera != null) {
|
||||
m_PickupAudioClipSet.PlayAudioClip(null);
|
||||
}
|
||||
|
||||
if (ObjectPool.InstantiatedWithPool(m_GameObject)) {
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
if (NetworkObjectPool.IsNetworkActive()) {
|
||||
NetworkObjectPool.Destroy(m_GameObject);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
ObjectPool.Destroy(m_GameObject);
|
||||
} else {
|
||||
// Deactivate the pickup for now. It can appear again if a Respawner component is attached to the GameObject.
|
||||
m_GameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2ab038298fafd84cb7c13303f9fe91c
|
||||
timeCreated: 1505262312
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,47 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.CharacterAssist
|
||||
{
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Spherical Gravity Zone extends the Gravity Zone component by implementing a gravity force that is affected by spherical directions.
|
||||
/// </summary>
|
||||
public class SphericalGravityZone : GravityZone
|
||||
{
|
||||
[Tooltip("The amount of influence that the gravity force has. An x value of 0 represents the further point away from the sphere (at the distance of the radius), " +
|
||||
"while an x value of 1 represents the cloest point (the center of the sphere). The y value represents the amount of force that should be applied at that distance (0-1).")]
|
||||
[SerializeField] protected AnimationCurve m_Influence = AnimationCurve.EaseInOut(0, 0, 1, 1);
|
||||
[Tooltip("The value to multiply the influence by. A larger value can be used for larger spheres.")]
|
||||
[SerializeField] protected float m_InfluenceMultiplier = 1;
|
||||
|
||||
private Transform m_Transform;
|
||||
private SphereCollider m_SphereCollider;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_Transform = transform;
|
||||
m_SphereCollider = GetComponent<SphereCollider>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the direction of gravity that should be applied.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the character.</param>
|
||||
/// <returns>The direction of gravity that should be applied.</returns>
|
||||
public override Vector3 DetermineGravityDirection(Vector3 position)
|
||||
{
|
||||
var direction = (position - m_Transform.position);
|
||||
var influenceFactor = m_Influence.Evaluate(1 - (direction.magnitude / (m_SphereCollider.radius * MathUtility.ColliderRadiusMultiplier(m_SphereCollider)))) * m_InfluenceMultiplier;
|
||||
return direction.normalized * influenceFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33f9f41519105e741b84fe36d514ef71
|
||||
timeCreated: 1543955762
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,349 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Character;
|
||||
using Opsive.UltimateCharacterController.Events;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
using Opsive.UltimateCharacterController.Networking;
|
||||
using Opsive.UltimateCharacterController.Networking.Game;
|
||||
using Opsive.UltimateCharacterController.Networking.Objects;
|
||||
#endif
|
||||
using Opsive.UltimateCharacterController.Objects.ItemAssist;
|
||||
using Opsive.UltimateCharacterController.StateSystem;
|
||||
using Opsive.UltimateCharacterController.SurfaceSystem;
|
||||
using Opsive.UltimateCharacterController.Traits;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Destructible class is an abstract class which acts as the base class for any object that destroys itself and applies a damange.
|
||||
/// Primary uses include projectiles and grenades.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public abstract class Destructible : TrajectoryObject
|
||||
{
|
||||
[Tooltip("The layers that the object can stick to.")]
|
||||
[SerializeField] protected LayerMask m_StickyLayers = ~((1 << LayerManager.IgnoreRaycast) | (1 << LayerManager.Water) | (1 << LayerManager.UI) | (1 << LayerManager.VisualEffect) |
|
||||
(1 << LayerManager.Overlay) | (1 << LayerManager.Character) | (1 << LayerManager.SubCharacter));
|
||||
[Tooltip("Should the projectile be destroyed when it collides with another object?")]
|
||||
[SerializeField] protected bool m_DestroyOnCollision = true;
|
||||
[Tooltip("The amount of time after a collision that the object should be destroyed.")]
|
||||
[SerializeField] protected float m_DestructionDelay;
|
||||
[Tooltip("The objects which should spawn when the object is destroyed.")]
|
||||
[SerializeField] protected ObjectSpawnInfo[] m_SpawnedObjectsOnDestruction;
|
||||
[Tooltip("Unity event invoked when the destructable hits another object.")]
|
||||
[SerializeField] protected UnityFloatVector3Vector3GameObjectEvent m_OnImpactEvent;
|
||||
|
||||
public LayerMask StickyLayers { get { return m_StickyLayers; } set { m_StickyLayers = value; } }
|
||||
public bool DestroyOnCollision { get { return m_DestroyOnCollision; } set { m_DestroyOnCollision = value; } }
|
||||
public float DestructionDelay { get { return m_DestructionDelay; } set { m_DestructionDelay = value; } }
|
||||
public ObjectSpawnInfo[] SpawnedObjectsOnDestruction { get { return m_SpawnedObjectsOnDestruction; } set { m_SpawnedObjectsOnDestruction = value; } }
|
||||
public UnityFloatVector3Vector3GameObjectEvent OnImpactEvent { get { return m_OnImpactEvent; } set { m_OnImpactEvent = value; } }
|
||||
|
||||
protected float m_DamageAmount;
|
||||
protected float m_ImpactForce;
|
||||
protected int m_ImpactForceFrames;
|
||||
protected string m_ImpactStateName;
|
||||
protected float m_ImpactStateDisableTimer;
|
||||
private TrailRenderer m_TrailRenderer;
|
||||
private ParticleSystem m_ParticleSystem;
|
||||
private bool m_Destroyed;
|
||||
|
||||
private UltimateCharacterLocomotion m_StickyCharacterLocomotion;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
private INetworkInfo m_NetworkInfo;
|
||||
private IDestructibleMonitor m_DestructibleMonitor;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the defualt values.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_TrailRenderer = GetComponent<TrailRenderer>();
|
||||
if (m_TrailRenderer != null) {
|
||||
m_TrailRenderer.enabled = false;
|
||||
}
|
||||
m_ParticleSystem = GetComponent<ParticleSystem>();
|
||||
if (m_ParticleSystem != null) {
|
||||
m_ParticleSystem.Stop();
|
||||
}
|
||||
|
||||
// The Rigidbody is only used to notify Unity that the object isn't static. The Rigidbody doesn't control any movement.
|
||||
var rigidbody = GetComponent<Rigidbody>();
|
||||
rigidbody.mass = m_Mass;
|
||||
rigidbody.isKinematic = true;
|
||||
rigidbody.constraints = RigidbodyConstraints.FreezeAll;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
m_NetworkInfo = GetComponent<INetworkInfo>();
|
||||
m_DestructibleMonitor = GetComponent<IDestructibleMonitor>();
|
||||
#endif
|
||||
|
||||
if (m_DestroyOnCollision && m_CollisionMode != CollisionMode.Collide) {
|
||||
Debug.LogWarning($"Warning: The Destructible {name} will be destroyed on collision but does not have a Collision Mode set to Collide.");
|
||||
m_CollisionMode = CollisionMode.Collide;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the object. This will be called from an object creating the projectile (such as a weapon).
|
||||
/// </summary>
|
||||
/// <param name="velocity">The velocity to apply.</param>
|
||||
/// <param name="torque">The torque to apply.</param>
|
||||
/// <param name="damageAmount">The amount of damage to apply to the hit object.</param>
|
||||
/// <param name="impactForce">The amount of force to apply to the hit object.</param>
|
||||
/// <param name="impactForceFrames">The number of frames to add the force to.</param>
|
||||
/// <param name="impactLayers">The layers that the projectile can impact with.</param>
|
||||
/// <param name="impactStateName">The name of the state to activate upon impact.</param>
|
||||
/// <param name="impactStateDisableTimer">The number of seconds until the impact state is disabled.</param>
|
||||
/// <param name="surfaceImpact">A reference to the Surface Impact triggered when the object hits an object.</param>
|
||||
/// <param name="originator">The object that instantiated the trajectory object.</param>
|
||||
public virtual void Initialize(Vector3 velocity, Vector3 torque, float damageAmount, float impactForce, int impactForceFrames, LayerMask impactLayers,
|
||||
string impactStateName, float impactStateDisableTimer, SurfaceImpact surfaceImpact, GameObject originator)
|
||||
{
|
||||
InitializeDestructibleProperties(damageAmount, impactForce, impactForceFrames, impactLayers, impactStateName, impactStateDisableTimer, surfaceImpact);
|
||||
|
||||
base.Initialize(velocity, torque, originator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the destructible properties.
|
||||
/// </summary>
|
||||
/// <param name="damageAmount">The amount of damage to apply to the hit object.</param>
|
||||
/// <param name="impactForce">The amount of force to apply to the hit object.</param>
|
||||
/// <param name="impactForceFrames">The number of frames to add the force to.</param>
|
||||
/// <param name="impactLayers">The layers that the projectile can impact with.</param>
|
||||
/// <param name="impactStateName">The name of the state to activate upon impact.</param>
|
||||
/// <param name="impactStateDisableTimer">The number of seconds until the impact state is disabled.</param>
|
||||
/// <param name="surfaceImpact">A reference to the Surface Impact triggered when the object hits an object.</param>
|
||||
public void InitializeDestructibleProperties(float damageAmount, float impactForce, int impactForceFrames, LayerMask impactLayers, string impactStateName, float impactStateDisableTimer, SurfaceImpact surfaceImpact)
|
||||
{
|
||||
m_Destroyed = false;
|
||||
m_DamageAmount = damageAmount;
|
||||
m_ImpactForce = impactForce;
|
||||
m_ImpactForceFrames = impactForceFrames;
|
||||
m_ImpactLayers = impactLayers;
|
||||
m_ImpactStateName = impactStateName;
|
||||
m_ImpactStateDisableTimer = impactStateDisableTimer;
|
||||
// The SurfaceImpact may be set directly on the destructible prefab.
|
||||
if (m_SurfaceImpact == null) {
|
||||
m_SurfaceImpact = surfaceImpact;
|
||||
}
|
||||
if (m_TrailRenderer != null) {
|
||||
m_TrailRenderer.Clear();
|
||||
m_TrailRenderer.enabled = true;
|
||||
}
|
||||
if (m_ParticleSystem != null) {
|
||||
m_ParticleSystem.Play();
|
||||
}
|
||||
if (m_Collider != null) {
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
// The object may be reused and was previously stuck to a character.
|
||||
if (m_StickyCharacterLocomotion != null) {
|
||||
m_StickyCharacterLocomotion.RemoveIgnoredCollider(m_Collider);
|
||||
m_StickyCharacterLocomotion = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has collided with another object.
|
||||
/// </summary>
|
||||
/// <param name="hit">The RaycastHit of the object. Can be null.</param>
|
||||
protected override void OnCollision(RaycastHit? hit)
|
||||
{
|
||||
base.OnCollision(hit);
|
||||
|
||||
var forceDestruct = false;
|
||||
if (m_CollisionMode == CollisionMode.Collide) {
|
||||
// When there is a collision the object should move to the position that was hit so if it's not destroyed then it looks like it
|
||||
// is penetrating the hit object.
|
||||
if (hit != null && hit.HasValue && m_Collider != null) {
|
||||
var closestPoint = m_Collider.ClosestPoint(hit.Value.point);
|
||||
m_Transform.position += (hit.Value.point - closestPoint);
|
||||
// Only set the parent to the hit transform on uniform objects to prevent stretching.
|
||||
if (MathUtility.IsUniform(hit.Value.transform.localScale)) {
|
||||
// The parent layer must be within the sticky layer mask.
|
||||
if (MathUtility.InLayerMask(hit.Value.transform.gameObject.layer, m_StickyLayers)) {
|
||||
m_Transform.parent = hit.Value.transform;
|
||||
|
||||
// If the destructible sticks to a character then the object should be added as a sub collider so collisions will be ignored.
|
||||
m_StickyCharacterLocomotion = hit.Value.transform.gameObject.GetCachedComponent<UltimateCharacterLocomotion>();
|
||||
if (m_StickyCharacterLocomotion != null) {
|
||||
m_StickyCharacterLocomotion.AddIgnoredCollider(m_Collider);
|
||||
}
|
||||
} else {
|
||||
forceDestruct = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_TrailRenderer != null) {
|
||||
m_TrailRenderer.enabled = false;
|
||||
}
|
||||
if (m_ParticleSystem != null) {
|
||||
Scheduler.ScheduleFixed(Time.fixedDeltaTime - 0.01f, StopParticleSystem);
|
||||
}
|
||||
|
||||
// The object may not have been initialized before it collides.
|
||||
if (m_GameObject == null) {
|
||||
InitializeComponentReferences();
|
||||
}
|
||||
|
||||
if (hit != null && hit.HasValue) {
|
||||
var hitValue = hit.Value;
|
||||
var hitGameObject = hitValue.collider.gameObject;
|
||||
// The shield can absorb some (or none) of the damage from the destructible.
|
||||
var damageAmount = m_DamageAmount;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MELEE
|
||||
ShieldCollider shieldCollider;
|
||||
if ((shieldCollider = hitGameObject.GetCachedComponent<ShieldCollider>()) != null) {
|
||||
damageAmount = shieldCollider.Shield.Damage(this, damageAmount);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Allow a custom event to be received.
|
||||
EventHandler.ExecuteEvent<float, Vector3, Vector3, GameObject, object, Collider>(hitGameObject, "OnObjectImpact", damageAmount, hitValue.point, m_Velocity.normalized * m_ImpactForce, m_Originator, this, hitValue.collider);
|
||||
if (m_OnImpactEvent != null) {
|
||||
m_OnImpactEvent.Invoke(damageAmount, hitValue.point, m_Velocity.normalized * m_ImpactForce, m_Originator);
|
||||
}
|
||||
|
||||
// If the shield didn't absorb all of the damage then it should be applied to the character.
|
||||
if (damageAmount > 0) {
|
||||
// If the Health component exists it will apply a force to the rigidbody in addition to deducting the health. Otherwise just apply the force to the rigidbody.
|
||||
Health hitHealth;
|
||||
if ((hitHealth = hitGameObject.GetCachedParentComponent<Health>()) != null) {
|
||||
hitHealth.Damage(damageAmount, hitValue.point, -hitValue.normal, m_ImpactForce, m_ImpactForceFrames, 0, m_Originator, this, hitValue.collider);
|
||||
} else if (m_ImpactForce > 0) {
|
||||
var collisionRigidbody = hitGameObject.GetCachedParentComponent<Rigidbody>();
|
||||
if (collisionRigidbody != null && !collisionRigidbody.isKinematic) {
|
||||
collisionRigidbody.AddForceAtPosition(-hitValue.normal * m_ImpactForce * MathUtility.RigidbodyForceMultiplier, hitValue.point);
|
||||
} else {
|
||||
var forceObject = hitGameObject.GetCachedParentComponent<IForceObject>();
|
||||
if (forceObject != null) {
|
||||
forceObject.AddForce(m_Transform.forward * m_ImpactForce);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An optional state can be activated on the hit object.
|
||||
if (!string.IsNullOrEmpty(m_ImpactStateName)) {
|
||||
StateManager.SetState(hitGameObject, m_ImpactStateName, true);
|
||||
// If the timer isn't -1 then the state should be disabled after a specified amount of time. If it is -1 then the state
|
||||
// will have to be disabled manually.
|
||||
if (m_ImpactStateDisableTimer != -1) {
|
||||
StateManager.DeactivateStateTimer(hitGameObject, m_ImpactStateName, m_ImpactStateDisableTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The object can destroy itself after a small delay.
|
||||
if (m_DestroyOnCollision || forceDestruct) {
|
||||
Scheduler.ScheduleFixed(m_DestructionDelay, Destruct, hit);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the object.
|
||||
/// </summary>
|
||||
/// <param name="hit">The RaycastHit of the object. Can be null.</param>
|
||||
protected void Destruct(RaycastHit? hit)
|
||||
{
|
||||
if (m_Destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
// The object can only explode on the server.
|
||||
if (m_NetworkInfo != null && !m_NetworkInfo.IsServer()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// The RaycastHit will be null if the destruction happens with no collision.
|
||||
var hitPosition = (hit != null && hit.HasValue) ? hit.Value.point : m_Transform.position;
|
||||
var hitNormal = (hit != null && hit.HasValue) ? hit.Value.normal : m_Transform.up;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
if (m_NetworkInfo != null && m_NetworkInfo.IsServer()) {
|
||||
m_DestructibleMonitor.Destruct(hitPosition, hitNormal);
|
||||
}
|
||||
#endif
|
||||
Destruct(hitPosition, hitNormal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the object.
|
||||
/// </summary>
|
||||
/// <param name="hitPosition">The position of the destruction.</param>
|
||||
/// <param name="hitNormal">The normal direction of the destruction</param>
|
||||
public void Destruct(Vector3 hitPosition, Vector3 hitNormal)
|
||||
{
|
||||
for (int i = 0; i < m_SpawnedObjectsOnDestruction.Length; ++i) {
|
||||
if (m_SpawnedObjectsOnDestruction[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var spawnedObject = m_SpawnedObjectsOnDestruction[i].Instantiate(hitPosition, hitNormal, m_NormalizedGravity);
|
||||
if (spawnedObject == null) {
|
||||
continue;
|
||||
}
|
||||
var explosion = spawnedObject.GetCachedComponent<Explosion>();
|
||||
if (explosion != null) {
|
||||
explosion.Explode(m_DamageAmount, m_ImpactForce, m_ImpactForceFrames, m_Originator);
|
||||
}
|
||||
}
|
||||
|
||||
// The component and collider no longer need to be enabled after the object has been destroyed.
|
||||
if (m_Collider != null) {
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
m_Destroyed = true;
|
||||
|
||||
// The destructible should be destroyed.
|
||||
#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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the particle system.
|
||||
/// </summary>
|
||||
private void StopParticleSystem()
|
||||
{
|
||||
m_ParticleSystem.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The component has been disabled.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
if (m_DestroyOnCollision && m_StickyCharacterLocomotion != null) {
|
||||
m_StickyCharacterLocomotion.RemoveIgnoredCollider(m_Collider);
|
||||
m_StickyCharacterLocomotion = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d61e4087f9721894eb2b1db6d7cb14bf
|
||||
timeCreated: 1507917189
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,226 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Audio;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.UltimateCharacterController.Events;
|
||||
using Opsive.UltimateCharacterController.Objects.ItemAssist;
|
||||
using Opsive.UltimateCharacterController.Traits;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
using EventHandler = Opsive.Shared.Events.EventHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an explosion which applies a force and damage to any object that is within the specified radius.
|
||||
/// </summary>
|
||||
public class Explosion : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Should the object explode when the object is enabled?")]
|
||||
[SerializeField] protected bool m_ExplodeOnEnable;
|
||||
[Tooltip("Determines how far out the explosion affects other objects.")]
|
||||
[SerializeField] protected float m_Radius = 5;
|
||||
[Tooltip("The maximum amount of damage the explosion applies to objects with the Health component.")]
|
||||
[SerializeField] protected float m_DamageAmount = 10;
|
||||
[Tooltip("The maximum amount of force the explosion applies to nearby Rigidbody/IForceObject objects.")]
|
||||
[SerializeField] protected float m_ImpactForce = 2;
|
||||
[Tooltip("The number of frames to add the impact force to.")]
|
||||
[SerializeField] protected int m_ImpactForceFrames = 1;
|
||||
[Tooltip("The layers that the explosion can affect.")]
|
||||
[SerializeField] protected LayerMask m_ImpactLayers = ~(1 << LayerManager.IgnoreRaycast | 1 << LayerManager.Water | 1 << LayerManager.SubCharacter | 1 << LayerManager.Overlay |
|
||||
1 << LayerManager.VisualEffect);
|
||||
[Tooltip("Does the explosion require line of sight in order to damage the hit object?")]
|
||||
[SerializeField] protected bool m_LineOfSight;
|
||||
[Tooltip("The duration of the explosion.")]
|
||||
[SerializeField] protected float m_Lifespan = 3;
|
||||
[Tooltip("The maximum number of objects that the explosions can detect.")]
|
||||
[SerializeField] protected int m_MaxCollisionCount = 100;
|
||||
[Tooltip("A set of AudioClips that can be played when the explosion occurs.")]
|
||||
[SerializeField] protected AudioClipSet m_ExplosionAudioClipSet = new AudioClipSet();
|
||||
[Tooltip("Unity event invoked when the explosion hits another object.")]
|
||||
[SerializeField] protected UnityFloatVector3Vector3GameObjectEvent m_OnImpactEvent;
|
||||
|
||||
public bool ExplodeOnEnable { get { return m_ExplodeOnEnable; } set { m_ExplodeOnEnable = value; } }
|
||||
public float Radius { get { return m_Radius; } set { m_Radius = value; } }
|
||||
public float DamageAmount { get { return m_DamageAmount; } set { m_DamageAmount = value; } }
|
||||
public float ImpactForce { get { return m_ImpactForce; } set { m_ImpactForce = value; } }
|
||||
public int ImpactForceFrames { get { return m_ImpactForceFrames; } set { m_ImpactForceFrames = value; } }
|
||||
public LayerMask ImpactLayers { get { return m_ImpactLayers; } set { m_ImpactLayers = value; } }
|
||||
public bool LineOfSight { get { return m_LineOfSight; } set { m_LineOfSight = value; } }
|
||||
public float Lifespan { get { return m_Lifespan; } set { m_Lifespan = value; } }
|
||||
public AudioClipSet ExplosionAudioClipSet { get { return m_ExplosionAudioClipSet; } set { m_ExplosionAudioClipSet = value; } }
|
||||
public UnityFloatVector3Vector3GameObjectEvent OnImpactEvent { get { return m_OnImpactEvent; } set { m_OnImpactEvent = value; } }
|
||||
|
||||
private GameObject m_GameObject;
|
||||
private Transform m_Transform;
|
||||
private HashSet<object> m_ObjectExplosions = new HashSet<object>();
|
||||
private Collider[] m_CollidersHit;
|
||||
private RaycastHit m_RaycastHit;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_GameObject = gameObject;
|
||||
m_Transform = transform;
|
||||
m_CollidersHit = new Collider[m_MaxCollisionCount];
|
||||
AudioManager.Register(m_GameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explode if requested when the component is enabled.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
if (m_ExplodeOnEnable) {
|
||||
Explode(m_DamageAmount, m_ImpactForce, m_ImpactForceFrames, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do the explosion.
|
||||
/// </summary>
|
||||
public void Explode()
|
||||
{
|
||||
Explode(m_DamageAmount, m_ImpactForce, m_ImpactForceFrames, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do the explosion.
|
||||
/// </summary>
|
||||
/// <param name="damageAmount">The amount of damage to apply to the hit objects.</param>
|
||||
public void Explode(GameObject originator)
|
||||
{
|
||||
Explode(m_DamageAmount, m_ImpactForce, m_ImpactForceFrames, originator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do the explosion.
|
||||
/// </summary>
|
||||
/// <param name="damageAmount">The amount of damage to apply to the hit objects.</param>
|
||||
/// <param name="impactForce">The amount of force to apply to the hit object.</param>
|
||||
/// <param name="impactForceFrames">The number of frames to add the force to.</param>
|
||||
/// <param name="originator">The originator of the object.</param>
|
||||
public void Explode(float damageAmount, float impactForce, int impactForceFrames, GameObject originator)
|
||||
{
|
||||
Health health = null;
|
||||
Rigidbody colliderRigidbody = null;
|
||||
IForceObject forceObject = null;
|
||||
var hitCount = Physics.OverlapSphereNonAlloc(m_Transform.position, m_Radius, m_CollidersHit, m_ImpactLayers, QueryTriggerInteraction.Ignore);
|
||||
#if UNITY_EDITOR
|
||||
if (hitCount == m_MaxCollisionCount) {
|
||||
Debug.LogWarning("Warning: The maximum number of colliders have been hit by " + m_GameObject.name + ". Consider increasing the Max Collision Count value.");
|
||||
}
|
||||
#endif
|
||||
for (int i = 0; i < hitCount; ++i) {
|
||||
// A GameObject can contain multiple colliders. Prevent the explosion from occurring on the same GameObject multiple times.
|
||||
if (m_ObjectExplosions.Contains(m_CollidersHit[i].gameObject)) {
|
||||
continue;
|
||||
}
|
||||
m_ObjectExplosions.Add(m_CollidersHit[i].gameObject);
|
||||
// The base character GameObject should only be checked once.
|
||||
if ((forceObject = m_CollidersHit[i].gameObject.GetCachedParentComponent<IForceObject>()) != null) {
|
||||
if (m_ObjectExplosions.Contains(forceObject)) {
|
||||
continue;
|
||||
}
|
||||
m_ObjectExplosions.Add(forceObject);
|
||||
}
|
||||
|
||||
// OverlapSphere can return objects that are in a different room. Perform a cast to ensure the object is within the explosion range.
|
||||
if (m_LineOfSight) {
|
||||
// Add a slight vertical offset to prevent a floor collider from getting in the way of the cast.
|
||||
var position = m_Transform.TransformPoint(0, 0.1f, 0);
|
||||
var direction = m_CollidersHit[i].transform.position - position;
|
||||
if (Physics.Raycast(position - direction.normalized * 0.1f, direction, out m_RaycastHit, direction.magnitude, m_ImpactLayers, QueryTriggerInteraction.Ignore) &&
|
||||
!(m_RaycastHit.transform.IsChildOf(m_CollidersHit[i].transform)
|
||||
#if FIRST_PERSON_CONTROLLER
|
||||
// The cast should not hit any colliders who are a child of the camera.
|
||||
|| m_RaycastHit.transform.gameObject.GetCachedParentComponent<FirstPersonController.Character.FirstPersonObjects>() != null
|
||||
#endif
|
||||
)) {
|
||||
// If the collider is part of a character then ensure the head can't be hit.
|
||||
var parentAnimator = m_CollidersHit[i].transform.gameObject.GetCachedParentComponent<Animator>();
|
||||
if (parentAnimator != null && parentAnimator.isHuman) {
|
||||
var head = parentAnimator.GetBoneTransform(HumanBodyBones.Head);
|
||||
direction = head.position - position;
|
||||
if (Physics.Raycast(position, direction, out m_RaycastHit, direction.magnitude, m_ImpactLayers, QueryTriggerInteraction.Ignore) &&
|
||||
!m_RaycastHit.transform.IsChildOf(m_CollidersHit[i].transform) && !m_CollidersHit[i].transform.IsChildOf(m_RaycastHit.transform) &&
|
||||
m_RaycastHit.transform.IsChildOf(m_Transform)
|
||||
#if FIRST_PERSON_CONTROLLER
|
||||
// The cast should not hit any colliders who are a child of the camera.
|
||||
&& m_RaycastHit.transform.gameObject.GetCachedParentComponent<FirstPersonController.Character.FirstPersonObjects>() == null
|
||||
#endif
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The shield can absorb some (or none) of the damage from the explosion.
|
||||
var hitDamageAmount = damageAmount;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MELEE
|
||||
ShieldCollider shieldCollider;
|
||||
if ((shieldCollider = m_CollidersHit[i].transform.gameObject.GetCachedComponent<ShieldCollider>()) != null) {
|
||||
hitDamageAmount = shieldCollider.Shield.Damage(this, hitDamageAmount);
|
||||
}
|
||||
#endif
|
||||
|
||||
// ClosestPoint only works with a subset of collider types.
|
||||
Vector3 closestPoint;
|
||||
if (m_CollidersHit[i] is BoxCollider || m_CollidersHit[i] is SphereCollider || m_CollidersHit[i] is CapsuleCollider || (m_CollidersHit[i] is MeshCollider && (m_CollidersHit[i] as MeshCollider).convex)) {
|
||||
closestPoint = m_CollidersHit[i].ClosestPoint(m_Transform.position);
|
||||
} else {
|
||||
closestPoint = m_CollidersHit[i].ClosestPointOnBounds(m_Transform.position);
|
||||
}
|
||||
var hitDirection = closestPoint - m_Transform.position;
|
||||
|
||||
// Allow a custom event to be received.
|
||||
EventHandler.ExecuteEvent<float, Vector3, Vector3, GameObject, object, Collider>(m_CollidersHit[i].transform.gameObject, "OnObjectImpact", hitDamageAmount, closestPoint, hitDirection * m_ImpactForce, originator, this, m_CollidersHit[i]);
|
||||
if (m_OnImpactEvent != null) {
|
||||
m_OnImpactEvent.Invoke(hitDamageAmount, closestPoint, hitDirection * m_ImpactForce, originator);
|
||||
}
|
||||
|
||||
// If the shield didn't absorb all of the damage then it should be applied to the character.
|
||||
if (hitDamageAmount > 0) {
|
||||
// If the Health component exists it will apply an explosive force to the character/character in addition to deducting the health.
|
||||
// Otherwise just apply the force to the character/rigidbody.
|
||||
if ((health = m_CollidersHit[i].gameObject.GetCachedParentComponent<Health>()) != null) {
|
||||
// The further out the collider is, the less it is damaged.
|
||||
var damageModifier = Mathf.Max(1 - (hitDirection.magnitude / m_Radius), 0.01f);
|
||||
health.Damage(hitDamageAmount * damageModifier, m_Transform.position, hitDirection.normalized, impactForce * damageModifier, impactForceFrames, m_Radius, originator, this, null);
|
||||
} else if (forceObject != null) {
|
||||
var damageModifier = Mathf.Max(1 - (hitDirection.magnitude / m_Radius), 0.01f);
|
||||
forceObject.AddForce(hitDirection.normalized * impactForce * damageModifier);
|
||||
} else if ((colliderRigidbody = m_CollidersHit[i].gameObject.GetCachedComponent<Rigidbody>()) != null) {
|
||||
colliderRigidbody.AddExplosionForce(impactForce * MathUtility.RigidbodyForceMultiplier, m_Transform.position, m_Radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_ObjectExplosions.Clear();
|
||||
|
||||
// An audio clip can play when the object explodes.
|
||||
m_ExplosionAudioClipSet.PlayAudioClip(m_GameObject);
|
||||
|
||||
Scheduler.Schedule(m_Lifespan, Destroy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Place the object back in the ObjectPool.
|
||||
/// </summary>
|
||||
private void Destroy()
|
||||
{
|
||||
ObjectPool.Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0fb51aa5f0f40649a1da8aa84aa1c25
|
||||
timeCreated: 1507817577
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,126 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.UltimateCharacterController.SurfaceSystem;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Projectile component moves a Destructible object along the specified path. Can apply damage at the collision point.
|
||||
/// </summary>
|
||||
public class Grenade : Destructible
|
||||
{
|
||||
[Tooltip("The length of time before the grenade destructs.")]
|
||||
[SerializeField] protected float m_Lifespan = 5;
|
||||
[Tooltip("A reference to the pin that is removed.")]
|
||||
[SerializeField] protected Transform m_Pin;
|
||||
|
||||
public float Lifespan { get { return m_Lifespan; } set { m_Lifespan = value; } }
|
||||
public Transform Pin { get { return m_Pin; } set { m_Pin = value; } }
|
||||
|
||||
protected ScheduledEventBase m_ScheduledDeactivation;
|
||||
private Transform m_PinParent;
|
||||
private Vector3 m_PinLocalPosition;
|
||||
private Quaternion m_PinLocalRotation;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
// Remember the pin location so it can be reattached.
|
||||
if (m_Pin != null) {
|
||||
m_PinParent = m_Pin.parent;
|
||||
m_PinLocalPosition = m_Pin.localPosition;
|
||||
m_PinLocalRotation = m_Pin.localRotation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The grenade has been enabled.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
DetachAttachPin(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the object.
|
||||
/// </summary>
|
||||
/// <param name="velocity">The velocity to apply.</param>
|
||||
/// <param name="torque">The torque to apply.</param>
|
||||
/// <param name="damageAmount">The amount of damage to apply to the hit object.</param>
|
||||
/// <param name="impactForce">The amount of force to apply to the hit object.</param>
|
||||
/// <param name="impactForceFrames">The number of frames to add the force to.</param>
|
||||
/// <param name="impactLayers">The layers that the projectile can impact with.</param>
|
||||
/// <param name="impactStateName">The name of the state to activate upon impact.</param>
|
||||
/// <param name="impactStateDisableTimer">The number of seconds until the impact state is disabled.</param>
|
||||
/// <param name="surfaceImpact">A reference to the Surface Impact triggered when the object hits an object.</param>
|
||||
/// <param name="originator">The object that instantiated the trajectory object.</param>
|
||||
/// <param name="originatorCollisionCheck">Should a collision check against the originator be performed?</param>
|
||||
public virtual void Initialize(Vector3 velocity, Vector3 torque, float damageAmount, float impactForce, int impactForceFrames, LayerMask impactLayers,
|
||||
string impactStateName, float impactStateDisableTimer, SurfaceImpact surfaceImpact, GameObject originator, bool originatorCollisionCheck)
|
||||
{
|
||||
InitializeDestructibleProperties(damageAmount, impactForce, impactForceFrames, impactLayers, impactStateName, impactStateDisableTimer, surfaceImpact);
|
||||
|
||||
base.Initialize(velocity, torque, originator, originatorCollisionCheck);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The grenade should start to cook.
|
||||
/// </summary>
|
||||
/// <param name="originator">The object that instantiated the trajectory object.</param>
|
||||
public void StartCooking(GameObject originator)
|
||||
{
|
||||
SetOriginator(originator, Vector3.up);
|
||||
|
||||
// The grenade should destruct after a specified amount of time.
|
||||
m_ScheduledDeactivation = Scheduler.Schedule(m_Lifespan, Deactivate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detaches or attach the pin.
|
||||
/// </summary>
|
||||
/// <param name="attachTransform">The transform that the pin should be attached to. If null the pin will move back to the starting location.</param>
|
||||
public void DetachAttachPin(Transform attachTransform)
|
||||
{
|
||||
if (m_Pin == null || m_PinParent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (attachTransform != null) {
|
||||
m_Pin.parent = attachTransform;
|
||||
} else { // Attach the pin back to the original transform.
|
||||
m_Pin.parent = m_PinParent;
|
||||
m_Pin.localPosition = m_PinLocalPosition;
|
||||
m_Pin.localRotation = m_PinLocalRotation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The grenade has reached its lifespan.
|
||||
/// </summary>
|
||||
protected void Deactivate()
|
||||
{
|
||||
Scheduler.Cancel(m_ScheduledDeactivation);
|
||||
|
||||
InitializeComponentReferences(); // The grenade may explode before Awake is called.
|
||||
|
||||
// Change the layer of the GameObject so the explosion doesn't detect the grenade when performing its overlap check.
|
||||
var prevLayer = m_GameObject.layer;
|
||||
m_GameObject.layer = LayerManager.IgnoreRaycast;
|
||||
Destruct(null);
|
||||
m_GameObject.layer = prevLayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d861d79cd49579546bd2ffb8a1fa5246
|
||||
timeCreated: 1498505827
|
||||
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.Objects
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for any object that is kinematic and can have forces applied to it.
|
||||
/// </summary>
|
||||
public interface IForceObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a force to the object.
|
||||
/// </summary>
|
||||
/// <param name="force">The force to add to the object.</param>
|
||||
void AddForce(Vector3 force);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a force to the object.
|
||||
/// </summary>
|
||||
/// <param name="force">The force to add to the object.</param>
|
||||
/// <param name="frames">The number of frames to add the force to.</param>
|
||||
void AddForce(Vector3 force, int frames);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f81f6d409e5a3e34397c149607e413bf
|
||||
timeCreated: 1518644530
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a2321a18d6775e47a07a8d9f32af8ea
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,119 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.ItemAssist
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Items.Actions;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The MagicParticle will perform a MagicItem impact when it collides with an object. In order for this to work correctly the ParticleSystem must
|
||||
/// have collisions enabled and the "Send Collision Event" parameter enabled. See this page for more information:
|
||||
/// https://docs.unity3d.com/Manual/PartSysCollisionModule.html.
|
||||
/// </summary>
|
||||
public class MagicParticle : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Can the particle collide with the originator?")]
|
||||
[SerializeField] protected bool m_CanCollideWithOriginator = false;
|
||||
|
||||
private GameObject m_GameObject;
|
||||
private Transform m_Transform;
|
||||
private MagicItem m_MagicItem;
|
||||
private uint m_CastID;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_GameObject = gameObject;
|
||||
m_Transform = transform;
|
||||
|
||||
var particleSystem = GetComponent<ParticleSystem>();
|
||||
if (particleSystem == null) {
|
||||
Debug.LogError($"Error: The MagicProjectile {m_GameObject.name} does not have a ParticleSystem attached.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!particleSystem.collision.enabled) {
|
||||
Debug.LogError($"Error: The collision module on the MagicProjectile {m_GameObject.name} is disabled. This should be enabled in order to receive collision events.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!particleSystem.collision.sendCollisionMessages) {
|
||||
Debug.LogError($"Error: Send Collision Messages on the the MagicProjectile {m_GameObject.name} is disabled. This should be enabled in order to receive collision events.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the particle to the specified MagicItem.
|
||||
/// </summary>
|
||||
/// <param name="magicItem">The MagicItem that casted the particle.</param>
|
||||
/// <param name="castID">The ID of the MagicItem cast.</param>
|
||||
public void Initialize(MagicItem magicItem, uint castID)
|
||||
{
|
||||
m_MagicItem = magicItem;
|
||||
m_CastID = castID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A particle has collided with another object.
|
||||
/// </summary>
|
||||
/// <param name="other">The object that the particle collided with.</param>
|
||||
public void OnParticleCollision(GameObject other)
|
||||
{
|
||||
// If the transform is null the particle hasn't been initialized yet.
|
||||
if (m_Transform == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent the particle from colliding with the originator.
|
||||
if (!m_CanCollideWithOriginator) {
|
||||
var characterLocomotion = other.GetCachedComponent<Character.UltimateCharacterLocomotion>();
|
||||
if (characterLocomotion != null && m_MagicItem.Character == characterLocomotion.gameObject) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// PerformImpact requires a RaycastHit.
|
||||
var colliders = other.GetCachedComponents<Collider>();
|
||||
if (colliders == null) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < colliders.Length; ++i) {
|
||||
if (colliders[i].isTrigger) {
|
||||
continue;
|
||||
}
|
||||
Vector3 closestPoint;
|
||||
if (colliders[i] is BoxCollider || colliders[i] is SphereCollider || colliders[i] is CapsuleCollider || (colliders[i] is MeshCollider && (colliders[i] as MeshCollider).convex)) {
|
||||
closestPoint = colliders[i].ClosestPoint(m_Transform.position);
|
||||
} else {
|
||||
closestPoint = m_Transform.position;
|
||||
}
|
||||
var direction = other.transform.position - closestPoint;
|
||||
if (Physics.Raycast(closestPoint - direction.normalized * 0.1f, direction.normalized, out var hit, direction.magnitude + 0.1f, 1 << other.layer)) {
|
||||
m_MagicItem.PerformImpact(m_CastID, m_GameObject, other, hit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The particle has been disabled.
|
||||
/// </summary>
|
||||
private void OnDisable()
|
||||
{
|
||||
// All of the impact actions should be reset for the particle spawn id.
|
||||
if (m_MagicItem != null && m_MagicItem.ImpactActions != null) {
|
||||
for (int i = 0; i < m_MagicItem.ImpactActions.Length; ++i) {
|
||||
m_MagicItem.ImpactActions[i].Reset(m_CastID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6db731486d10934cbfa28dc65e38c1a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,106 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.ItemAssist
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Items.Actions;
|
||||
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
|
||||
using Opsive.UltimateCharacterController.Networking.Game;
|
||||
#endif
|
||||
using Opsive.UltimateCharacterController.Objects;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The ParticleProjectile extends TrajectoryObject and notifies the MagicItem when the object has collided with another object.
|
||||
/// TrajectoryObject.CollisionMode should be set to Ignore for the projectile to pass through the object.
|
||||
/// </summary>
|
||||
public class MagicProjectile : TrajectoryObject
|
||||
{
|
||||
[Tooltip("Should the projectile be destroyed when there's a collision?")]
|
||||
[SerializeField] protected bool m_DestroyOnCollision;
|
||||
[Tooltip("Should the projectile be destroyed after the particle has stopped emitting?")]
|
||||
[SerializeField] protected bool m_WaitForParticleStop;
|
||||
|
||||
public bool DestroyOnCollision { get { return m_DestroyOnCollision; } set { m_DestroyOnCollision = value; } }
|
||||
public bool WaitForParticleStop { get { return m_WaitForParticleStop; } set { m_WaitForParticleStop = value; } }
|
||||
|
||||
protected MagicItem m_MagicItem;
|
||||
protected uint m_CastID;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the object with the specified velocity and torque.
|
||||
/// </summary>
|
||||
/// <param name="velocity">The starting velocity.</param>
|
||||
/// <param name="torque">The starting torque.</param>
|
||||
/// <param name="originator">The object that instantiated the trajectory object.</param>
|
||||
/// <param name="magicItem">The MagicItem that created the projectile.</param>
|
||||
/// <param name="castID">The ID of the cast.</param>
|
||||
public void Initialize(Vector3 velocity, Vector3 torque, GameObject originator, MagicItem magicItem, uint castID)
|
||||
{
|
||||
m_MagicItem = magicItem;
|
||||
m_CastID = castID;
|
||||
|
||||
Initialize(velocity, torque, originator);
|
||||
if (m_Collider != null) {
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has collided with another object.
|
||||
/// </summary>
|
||||
/// <param name="hit">The RaycastHit of the object. Can be null.</param>
|
||||
protected override void OnCollision(RaycastHit? hit)
|
||||
{
|
||||
base.OnCollision(hit);
|
||||
|
||||
if (!hit.HasValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_MagicItem.PerformImpact(m_CastID, m_GameObject, hit.Value.transform.gameObject, hit.Value);
|
||||
|
||||
// Destroys the projectile when it has collided with an object.
|
||||
if (m_DestroyOnCollision) {
|
||||
// The projectile can wait for any particles to stop emitting.
|
||||
var immediateDestroy = !m_WaitForParticleStop;
|
||||
if (!immediateDestroy) {
|
||||
var particleSystem = m_GameObject.GetCachedComponent<ParticleSystem>();
|
||||
if (particleSystem != null) {
|
||||
particleSystem.Stop(true, ParticleSystemStopBehavior.StopEmitting);
|
||||
Scheduler.Schedule(particleSystem.main.duration, ReturnToObjectPool);
|
||||
immediateDestroy = false;
|
||||
}
|
||||
}
|
||||
if (immediateDestroy) {
|
||||
ReturnToObjectPool();
|
||||
} else {
|
||||
// The projectile is waiting on the particles to be destroyed. Stop moving.
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the projectile back to the object pool.
|
||||
/// </summary>
|
||||
private void ReturnToObjectPool()
|
||||
{
|
||||
#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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a93ab405fbc6e54db16c521de2c6cc5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.ItemAssist
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies an offset for the pivot position.
|
||||
/// </summary>
|
||||
public class PivotOffset : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The pivot offset.")]
|
||||
[SerializeField] protected Vector3 m_Offset;
|
||||
|
||||
public Vector3 Offset { get { return m_Offset; } set { m_Offset = value; } }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a16c458321536b43b2e57119a8cb244
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.ItemAssist
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that the attached object should cause recoil when hit with a MeleeWeapon.
|
||||
/// </summary>
|
||||
public class RecoilObject : MonoBehaviour
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa25a7ec2cbec8a40988d4328115db41
|
||||
timeCreated: 1532978512
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,89 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.ItemAssist
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a shell casing which uses the trajectory object for kinematic shell movement.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class Shell : TrajectoryObject
|
||||
{
|
||||
[Tooltip("Time to live in seconds before the shell is removed.")]
|
||||
[SerializeField] protected float m_Lifespan = 10;
|
||||
[Tooltip("Chance of shell not being removed after settling on the ground.")]
|
||||
[Range(0, 1)] [SerializeField] protected float m_Persistence = 1;
|
||||
|
||||
private float m_RemoveTime;
|
||||
private Vector3 m_StartScale;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_StartScale = transform.localScale;
|
||||
|
||||
// The Rigidbody is only used to notify Unity that the object isn't static. The Rigidbody doesn't control any movement.
|
||||
var rigidbody = GetComponent<Rigidbody>();
|
||||
rigidbody.mass = m_Mass;
|
||||
rigidbody.isKinematic = true;
|
||||
rigidbody.constraints = RigidbodyConstraints.FreezeAll;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The shell has been spawned - reset the timing and component values.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
m_RemoveTime = Time.time + m_Lifespan;
|
||||
m_Transform.localScale = m_StartScale;
|
||||
|
||||
if (m_Collider != null) {
|
||||
m_Collider.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move and rotate the object according to a parabolic trajectory.
|
||||
/// </summary>
|
||||
protected override void FixedUpdate()
|
||||
{
|
||||
base.FixedUpdate();
|
||||
|
||||
if (Time.time > m_RemoveTime) { // The shell should be removed.
|
||||
m_Transform.localScale = Vector3.Lerp(m_Transform.localScale, Vector3.zero, Utility.TimeUtility.FramerateDeltaTime * 0.2f);
|
||||
if (Time.time > m_RemoveTime + 0.5f) {
|
||||
ObjectPool.Destroy(m_GameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has collided with another object.
|
||||
/// </summary>
|
||||
/// <param name="hit">The RaycastHit of the object. Can be null.</param>
|
||||
protected override void OnCollision(RaycastHit? hit)
|
||||
{
|
||||
base.OnCollision(hit);
|
||||
|
||||
if (m_Velocity.sqrMagnitude > 4) { // Hard bounce.
|
||||
// Apply more random rotation velocity to make the shell behave a bit unpredictably on a hard bounce (similar to real brass shell behavior).
|
||||
AddTorque(Random.rotation.eulerAngles * 0.15f * (Random.value > 0.5f ? 1 : -1));
|
||||
} else if (Random.value > m_Persistence) { // Soft bounce.
|
||||
// Remove the shell after half a second on a soft bounce.
|
||||
m_RemoveTime = Time.time + 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec2d29bda368ada499ad37e3d0781339
|
||||
timeCreated: 1505688289
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,63 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.ItemAssist
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.UltimateCharacterController.Items.Actions;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The ShieldCollider component specifies the object that acts as a collider for the shield.
|
||||
/// </summary>
|
||||
public class ShieldCollider : MonoBehaviour
|
||||
{
|
||||
[Tooltip("A reference to the Shield item action.")]
|
||||
[SerializeField] protected Shield m_Shield;
|
||||
[Tooltip("Is the collider attached to a Shield used for the first person perspective?")]
|
||||
[HideInInspector] [SerializeField] protected bool m_FirstPersonPerspective;
|
||||
|
||||
[Shared.Utility.NonSerialized] public Shield Shield { get { return m_Shield; } set { m_Shield = value; } }
|
||||
[Shared.Utility.NonSerialized] public bool FirstPersonPerspective { set { m_FirstPersonPerspective = value; } }
|
||||
|
||||
private Collider m_Collider;
|
||||
private GameObject m_Character;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
if (m_Shield == null) {
|
||||
Debug.LogError("Error: The shield is not assigned. Ensure the shield is created from the Item Manager.", this);
|
||||
return;
|
||||
}
|
||||
m_Collider = GetComponent<Collider>();
|
||||
m_Collider.enabled = false;
|
||||
|
||||
m_Character = m_Shield.gameObject.GetComponentInParent<Character.UltimateCharacterLocomotion>().gameObject;
|
||||
EventHandler.RegisterEvent<bool>(m_Character, "OnCharacterChangePerspectives", OnChangePerspectives);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The camera perspective between first and third person has changed.
|
||||
/// </summary>
|
||||
/// <param name="inFirstPerson">Is the camera in a first person view?</param>
|
||||
private void OnChangePerspectives(bool firstPersonPerspective)
|
||||
{
|
||||
// The collider should only be enabled for the corresponding perspective.
|
||||
m_Collider.enabled = m_FirstPersonPerspective == firstPersonPerspective;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been destroyed.
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
EventHandler.UnregisterEvent<bool>(m_Character, "OnCharacterChangePerspectives", OnChangePerspectives);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16cb3bccd7b264c46ba4804f977c18b9
|
||||
timeCreated: 1534209342
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,124 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.ItemAssist
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Character;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.UltimateCharacterController.Items;
|
||||
using Opsive.UltimateCharacterController.Items.Actions.PerspectiveProperties;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Smoke component is attached to a GameObject with the a ParticleSystem attached representing smoke.
|
||||
/// </summary>
|
||||
public class Smoke : MonoBehaviour
|
||||
{
|
||||
private GameObject m_GameObject;
|
||||
private Transform m_Transform;
|
||||
private Item m_Item;
|
||||
private int m_ItemActionID;
|
||||
private ParticleSystem[] m_Particles;
|
||||
private ParticleSystemSimulationSpace[] m_SimulationSpace;
|
||||
|
||||
private GameObject m_Character;
|
||||
private int m_StartLayer;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_GameObject = gameObject;
|
||||
m_Transform = transform;
|
||||
m_Particles = GetComponentsInChildren<ParticleSystem>();
|
||||
m_SimulationSpace = new ParticleSystemSimulationSpace[m_Particles.Length];
|
||||
m_StartLayer = m_GameObject.layer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A weapon has been fired and the smoke needs to show.
|
||||
/// </summary>
|
||||
/// <param name="item">The item that the muzzle flash is attached to.</param>
|
||||
/// <param name="itemActionID">The ID which corresponds to the ItemAction that spawned the smoke.</param>
|
||||
/// <param name="characterLocomotion">The character that the smoke is attached to.</param>
|
||||
public void Show(Item item, int itemActionID, UltimateCharacterLocomotion characterLocomotion)
|
||||
{
|
||||
m_Character = characterLocomotion.gameObject;
|
||||
EventHandler.RegisterEvent<bool>(m_Character, "OnCharacterChangePerspectives", OnChangePerspectives);
|
||||
|
||||
m_Item = item;
|
||||
m_ItemActionID = itemActionID;
|
||||
m_GameObject.layer = characterLocomotion.FirstPersonPerspective ? LayerManager.Overlay : m_StartLayer;
|
||||
|
||||
// Disable the object after the particles are done playing.
|
||||
float maxLifeTime = 0;
|
||||
for (int i = 0; i < m_Particles.Length; ++i) {
|
||||
m_Particles[i].Play();
|
||||
var lifeTime = 0f;
|
||||
if ((lifeTime = m_Particles[i].main.startLifetime.Evaluate(0)) > maxLifeTime) {
|
||||
maxLifeTime = lifeTime;
|
||||
}
|
||||
}
|
||||
|
||||
Scheduler.Schedule(maxLifeTime, DestroySelf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Place itself back in the ObjectPool.
|
||||
/// </summary>
|
||||
public void DestroySelf()
|
||||
{
|
||||
ObjectPool.Destroy(m_GameObject);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// All of the particles should be set to local space so they'll change correctly when switching perspective.
|
||||
for (int i = 0; i < m_Particles.Length; ++i) {
|
||||
m_SimulationSpace[i] = m_Particles[i].main.simulationSpace;
|
||||
var mainParticle = m_Particles[i].main;
|
||||
mainParticle.simulationSpace = ParticleSystemSimulationSpace.Local;
|
||||
}
|
||||
|
||||
// When switching locations the local position and rotation should remain the same.
|
||||
var localPosition = m_Transform.localPosition;
|
||||
var localRotation = m_Transform.rotation;
|
||||
|
||||
var itemAction = m_Item.ItemActions[m_ItemActionID];
|
||||
var perspectiveProperties = (firstPersonPerspective ? itemAction.FirstPersonPerspectiveProperties : itemAction.ThirdPersonPerspectiveProperties);
|
||||
var smokeLocation = (perspectiveProperties as IShootableWeaponPerspectiveProperties).SmokeLocation;
|
||||
m_Transform.parent = smokeLocation;
|
||||
m_Transform.localPosition = localPosition;
|
||||
m_Transform.rotation = localRotation;
|
||||
|
||||
// Switch the particle simulation space back to the previous value.
|
||||
for (int i = 0; i < m_Particles.Length; ++i) {
|
||||
var mainParticle = m_Particles[i].main;
|
||||
mainParticle.simulationSpace = m_SimulationSpace[i];
|
||||
}
|
||||
|
||||
m_GameObject.layer = firstPersonPerspective ? LayerManager.Overlay : m_StartLayer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has been disabled.
|
||||
/// </summary>
|
||||
private void OnDisable()
|
||||
{
|
||||
if (m_Character != null) {
|
||||
EventHandler.UnregisterEvent<bool>(m_Character, "OnCharacterChangePerspectives", OnChangePerspectives);
|
||||
}
|
||||
m_Character = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c09be7c342cf2642a12738bb38baaf5
|
||||
timeCreated: 1505602291
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.ItemAssist
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The tracer will show a Line Renderer from the hitscan fire point to the hit point.
|
||||
/// </summary>
|
||||
public class Tracer : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The amount of time that the tracer is visible for")]
|
||||
[SerializeField] protected float m_VisibleTime = 0.05f;
|
||||
|
||||
// Component references
|
||||
private Transform m_Transform;
|
||||
private LineRenderer m_LineRenderer;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_Transform = transform;
|
||||
m_LineRenderer = GetComponent<LineRenderer>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the hit point that the tracer should move to.
|
||||
/// </summary>
|
||||
/// <param name="hitPoint">The hit point position.</param>
|
||||
public virtual void Initialize(Vector3 hitPoint)
|
||||
{
|
||||
m_LineRenderer.SetPosition(0, m_Transform.position);
|
||||
m_LineRenderer.SetPosition(1, hitPoint);
|
||||
|
||||
Scheduler.Schedule(m_VisibleTime, DestroyObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Places the object back in the ObjectPool.
|
||||
/// </summary>
|
||||
private void DestroyObject()
|
||||
{
|
||||
ObjectPool.Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27f97a858bca96045a845d9af5c6dba3
|
||||
timeCreated: 1511455046
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,396 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects.ItemAssist
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Builds a mesh which can show a trail following a melee weapon. This is typically used when the melee weapon is slashed.
|
||||
/// </summary>
|
||||
public class Trail : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The minimum distance between the position of the last slice and the current position.")]
|
||||
[SerializeField] protected float m_MinDistance = 0.01f;
|
||||
[Tooltip("The vertical length of the trail.")]
|
||||
[SerializeField] protected float m_Length = 1f;
|
||||
[Tooltip("The maximum number of slices within the trail. A larger value will cause the trail to be longer.")]
|
||||
[SerializeField] protected int m_MaxSliceCount = 50;
|
||||
[Tooltip("The smoothing value of the curve. A larger value will have a smoother curve compared to a smaller value.")]
|
||||
[SerializeField] protected int m_CurveSmoothness = 10;
|
||||
[Tooltip("The steepness of the curve. A value closer to 1 will increase the steepness.")]
|
||||
[Range(0, 1)] [SerializeField] protected float m_CurveSteepness = 0.5f;
|
||||
[Tooltip("The start color of the trail, near the melee weapon object.")]
|
||||
[SerializeField] protected Color m_StartColor = Color.white;
|
||||
[Tooltip("The end color of the trail.")]
|
||||
[SerializeField] protected Color m_EndColor = Color.white;
|
||||
[Tooltip("The amount of time the slice should be visible.")]
|
||||
[SerializeField] protected float m_VisibilityTime = 1;
|
||||
|
||||
private GameObject m_GameObject;
|
||||
private Transform m_Transform;
|
||||
private Mesh m_Mesh;
|
||||
|
||||
private float m_MinDistanceSquared;
|
||||
private TrailSlice[] m_TrailSlices = new TrailSlice[4];
|
||||
private int m_TrailSlicesIndex = -1;
|
||||
private int m_TrailSlicesCount;
|
||||
|
||||
private TrailSlice[] m_SmoothedTrailSlices;
|
||||
private int m_SmoothedTrailSlicesIndex = -1;
|
||||
private int m_SmoothedTrailSlicesCount;
|
||||
|
||||
private int m_SmoothedTrailSlicesPrevCount;
|
||||
private int m_SmoothedTrailSlicesPrevIndex;
|
||||
|
||||
private List<Vector3> m_Vertices;
|
||||
private List<Vector2> m_UVs;
|
||||
private List<Color> m_Colors;
|
||||
private List<int> m_Triangles;
|
||||
|
||||
private bool m_GenerateSlices;
|
||||
|
||||
/// <summary>
|
||||
/// A small container class for each object that represents a slice from the melee trail.
|
||||
/// </summary>
|
||||
private struct TrailSlice
|
||||
{
|
||||
private Vector3 m_Point;
|
||||
private Vector3 m_Up;
|
||||
private float m_Time;
|
||||
|
||||
public Vector3 Point { get { return m_Point; } }
|
||||
public Vector3 Up { get { return m_Up; } }
|
||||
public float Time { get { return m_Time; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the slice.
|
||||
/// </summary>
|
||||
/// <param name="point">The position of the slice.</param>
|
||||
/// <param name="up">The up direction of the slice.</param>
|
||||
public void Initialize(Vector3 point, Vector3 up)
|
||||
{
|
||||
m_Point = point;
|
||||
m_Up = up;
|
||||
m_Time = UnityEngine.Time.time;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the trail.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_GameObject = gameObject;
|
||||
m_Transform = transform;
|
||||
|
||||
var meshFilter = GetComponent<MeshFilter>();
|
||||
if (meshFilter == null) {
|
||||
Debug.LogError("Error: Unable to find the MeshFilter component. Enssure the Trail object has been created through the Object Manager.");
|
||||
return;
|
||||
}
|
||||
m_Mesh = meshFilter.mesh;
|
||||
|
||||
m_MinDistanceSquared = m_MinDistance * m_MinDistance;
|
||||
var count = m_MaxSliceCount * m_CurveSmoothness;
|
||||
m_SmoothedTrailSlices = new TrailSlice[count];
|
||||
|
||||
m_Vertices = new List<Vector3>(count * 2);
|
||||
m_UVs = new List<Vector2>(count * 2);
|
||||
m_Colors = new List<Color>(count * 2);
|
||||
m_Triangles = new List<int>((count - 1) * 6); // 3 indices per triangle, 2 triangles per slice.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start to generate the trail slices.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
m_GenerateSlices = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Samples the position of the melee object.
|
||||
/// </summary>
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (m_GenerateSlices) {
|
||||
SampleTrail();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays the trail.
|
||||
/// </summary>
|
||||
private void LateUpdate()
|
||||
{
|
||||
RemoveOldSlices();
|
||||
BuildMesh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores a new sample of the trail slice.
|
||||
/// </summary>
|
||||
private void SampleTrail()
|
||||
{
|
||||
// Add a new slice if the last sample position is too far away.
|
||||
if (m_TrailSlicesCount == 0 || (m_TrailSlices[m_TrailSlicesIndex].Point - m_Transform.position).sqrMagnitude > m_MinDistanceSquared) {
|
||||
// Revert the trail slice index and smoothed index/count values so the extra slice can be removed. A more accurate slice value will replace the prediction.
|
||||
if (m_TrailSlicesCount > 3) {
|
||||
m_TrailSlicesIndex = m_TrailSlicesIndex - 1;
|
||||
if (m_TrailSlicesIndex < 0) {
|
||||
m_TrailSlicesIndex += m_TrailSlicesCount;
|
||||
}
|
||||
|
||||
m_SmoothedTrailSlicesIndex = m_SmoothedTrailSlicesPrevIndex;
|
||||
m_SmoothedTrailSlicesCount = m_SmoothedTrailSlicesPrevCount;
|
||||
}
|
||||
|
||||
// Add the new slice at the current position.
|
||||
AddTrailSlice(m_Transform.position, m_Transform.up);
|
||||
|
||||
// A catmull-rom curve smooths the middle two verticies rather then all four vertices. Add one more slice near the beginning of the trail so the start of the
|
||||
// curve will intersect with the melee object.
|
||||
if (m_TrailSlicesCount > 3) {
|
||||
var prevIndex = m_TrailSlicesIndex - 1;
|
||||
if (prevIndex < 0) {
|
||||
prevIndex = m_TrailSlicesCount - 1;
|
||||
}
|
||||
var prevTrailSlice = m_TrailSlices[prevIndex];
|
||||
var trailSlice = m_TrailSlices[m_TrailSlicesIndex];
|
||||
|
||||
// Remember the previous smoothed values so the extra slice can be removed.
|
||||
m_SmoothedTrailSlicesPrevIndex = m_SmoothedTrailSlicesIndex;
|
||||
m_SmoothedTrailSlicesPrevCount = m_SmoothedTrailSlicesCount;
|
||||
// The new slice should be in the previous to the current slice position. This value probably won't be correct the next frame unless
|
||||
// the object is moving in a linear path but it is a good prediction.
|
||||
AddTrailSlice(trailSlice.Point + (trailSlice.Point - prevTrailSlice.Point).normalized, (trailSlice.Up + prevTrailSlice.Up) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the point and up vertex to the trail slices array. The values will also be smoothed.
|
||||
/// </summary>
|
||||
/// <param name="point">The point of the slice.</param>
|
||||
/// <param name="up">The up direction of the slice.</param>
|
||||
private void AddTrailSlice(Vector3 point, Vector3 up)
|
||||
{
|
||||
// Catmull-rom curves do not like repeated points.
|
||||
if (m_TrailSlicesCount > 0 && m_TrailSlices[m_TrailSlicesIndex].Point == point) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_TrailSlicesIndex = (m_TrailSlicesIndex + 1) % m_TrailSlices.Length;
|
||||
m_TrailSlices[m_TrailSlicesIndex].Initialize(point, up);
|
||||
if (m_TrailSlicesIndex + 1 > m_TrailSlicesCount) {
|
||||
m_TrailSlicesCount++;
|
||||
}
|
||||
|
||||
SmoothTrailSlice();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smooths the trail slices with a catmull-rom curve.
|
||||
/// </summary>
|
||||
private void SmoothTrailSlice()
|
||||
{
|
||||
// A catmull-rom curve requires at least four points.
|
||||
if (m_TrailSlicesCount < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A fixed size array is used to store the vertex values. The starting index may not be at the beginning of the array.
|
||||
var startIndex = m_TrailSlicesIndex - m_TrailSlicesCount + 1;
|
||||
if (startIndex < 0) {
|
||||
startIndex = m_TrailSlices.Length + startIndex;
|
||||
}
|
||||
|
||||
// Determine a smoothed value for both the point and up vertex.
|
||||
var p0 = m_TrailSlices[startIndex].Point;
|
||||
var p1 = m_TrailSlices[(startIndex + 1) % 4].Point;
|
||||
var p2 = m_TrailSlices[(startIndex + 2) % 4].Point;
|
||||
var p3 = m_TrailSlices[(startIndex + 3) % 4].Point;
|
||||
|
||||
var u0 = m_TrailSlices[startIndex].Up;
|
||||
var u1 = m_TrailSlices[(startIndex + 1) % 4].Up;
|
||||
var u2 = m_TrailSlices[(startIndex + 2) % 4].Up;
|
||||
var u3 = m_TrailSlices[(startIndex + 3) % 4].Up;
|
||||
|
||||
var t1 = CentripetralCatmullRomTime(0, p0, p1);
|
||||
var t2 = CentripetralCatmullRomTime(t1, p1, p2);
|
||||
var t3 = CentripetralCatmullRomTime(t2, p2, p3);
|
||||
|
||||
// Iterate based on the number of sample values.
|
||||
var iterAmount = ((t2 - t1) / m_CurveSmoothness);
|
||||
for (float t = t1; t < t2; t += iterAmount) {
|
||||
var point = CentripetralCatmullRomValue(p0, p1, p2, p3, 0, t1, t2, t3, t);
|
||||
var up = CentripetralCatmullRomValue(u0, u1, u2, u3, 0, t1, t2, t3, t);
|
||||
|
||||
// The value has been determined. Add it to the smoothed array.
|
||||
m_SmoothedTrailSlicesIndex = (m_SmoothedTrailSlicesIndex + 1) % m_SmoothedTrailSlices.Length;
|
||||
m_SmoothedTrailSlices[m_SmoothedTrailSlicesIndex].Initialize(point, up);
|
||||
if (m_SmoothedTrailSlicesIndex + 1 > m_SmoothedTrailSlicesCount) {
|
||||
m_SmoothedTrailSlicesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the time of the centripetral catmull-rom curve, defined in https://en.wikipedia.org/wiki/Centripetal_Catmull–Rom_spline.
|
||||
/// </summary>
|
||||
/// <param name="t">The sample time.</param>
|
||||
/// <param name="v0">The first vertex.</param>
|
||||
/// <param name="v1">The second vertex.</param>
|
||||
/// <returns>The time of the centripetral catmull-rom curve.</returns>
|
||||
private float CentripetralCatmullRomTime(float t, Vector3 v0, Vector3 v1)
|
||||
{
|
||||
var a = Mathf.Pow((v1.x - v0.x), 2f) + Mathf.Pow((v1.y - v0.y), 2f) + Mathf.Pow((v1.z - v0.z), 2f);
|
||||
var b = Mathf.Pow(a, 0.5f);
|
||||
var c = Mathf.Pow(b, m_CurveSteepness);
|
||||
|
||||
return c + t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the vertex of the centripetral catmull-rom curve, defined in https://en.wikipedia.org/wiki/Centripetal_Catmull–Rom_spline.
|
||||
/// </summary>
|
||||
/// <returns>The vertex of the centripetral catmull-rom curve.</returns>
|
||||
private Vector3 CentripetralCatmullRomValue(Vector3 v0, Vector3 v1, Vector3 v2, Vector3 v3, float t0, float t1, float t2, float t3, float t)
|
||||
{
|
||||
var a1 = (t1 - t) / (t1 - t0) * v0 + (t - t0) / (t1 - t0) * v1;
|
||||
var a2 = (t2 - t) / (t2 - t1) * v1 + (t - t1) / (t2 - t1) * v2;
|
||||
var a3 = (t3 - t) / (t3 - t2) * v2 + (t - t2) / (t3 - t2) * v3;
|
||||
var b1 = (t2 - t) / (t2 - t0) * a1 + (t - t0) / (t2 - t0) * a2;
|
||||
var b2 = (t3 - t) / (t3 - t1) * a2 + (t - t1) / (t3 - t1) * a3;
|
||||
return (t2 - t) / (t2 - t1) * b1 + (t - t1) / (t2 - t1) * b2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes any slices which have existed for more than the visible time.
|
||||
/// </summary>
|
||||
private void RemoveOldSlices()
|
||||
{
|
||||
if (m_TrailSlicesCount == 0) {
|
||||
if (!m_GenerateSlices) {
|
||||
if (ObjectPool.InstantiatedWithPool(m_GameObject)) {
|
||||
ObjectPool.Destroy(m_GameObject);
|
||||
} else {
|
||||
m_GameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var startIndex = m_TrailSlicesIndex - m_TrailSlicesCount + 1;
|
||||
if (startIndex < 0) {
|
||||
startIndex = m_TrailSlices.Length + startIndex;
|
||||
}
|
||||
var count = m_TrailSlicesCount;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
var trailSlice = m_TrailSlices[(startIndex + i) % m_TrailSlices.Length];
|
||||
if (trailSlice.Time + m_VisibilityTime > Time.time) {
|
||||
break;
|
||||
}
|
||||
|
||||
// The slice has existed for more than the visiblity time - remove it by decreasing the count.
|
||||
m_TrailSlicesCount--;
|
||||
}
|
||||
|
||||
if (m_SmoothedTrailSlicesCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
startIndex = m_SmoothedTrailSlicesIndex - m_SmoothedTrailSlicesCount + 1;
|
||||
if (startIndex < 0) {
|
||||
startIndex = m_SmoothedTrailSlices.Length + startIndex;
|
||||
}
|
||||
count = m_SmoothedTrailSlicesCount;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
var trailSlice = m_SmoothedTrailSlices[(startIndex + i) % m_SmoothedTrailSlices.Length];
|
||||
if (trailSlice.Time + m_VisibilityTime > Time.time) {
|
||||
break;
|
||||
}
|
||||
|
||||
// The slice has existed for more than the visiblity time - remove it by decreasing the count.
|
||||
m_SmoothedTrailSlicesCount--;
|
||||
m_SmoothedTrailSlicesPrevCount--;
|
||||
}
|
||||
|
||||
if (m_TrailSlicesCount == 0 && m_SmoothedTrailSlicesCount == 0) {
|
||||
m_GenerateSlices = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the mesh from the catmull rom verticies.
|
||||
/// </summary>
|
||||
private void BuildMesh()
|
||||
{
|
||||
m_Mesh.Clear();
|
||||
|
||||
if (m_TrailSlicesCount < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A fixed size array is used to store the vertex values. The starting index may not be at the beginning of the array.
|
||||
var startIndex = m_SmoothedTrailSlicesIndex - m_SmoothedTrailSlicesCount + 1;
|
||||
if (startIndex < 0) {
|
||||
startIndex = m_SmoothedTrailSlices.Length + startIndex;
|
||||
}
|
||||
|
||||
m_Vertices.Clear();
|
||||
m_UVs.Clear();
|
||||
m_Colors.Clear();
|
||||
m_Triangles.Clear();
|
||||
|
||||
for (int i = 0; i < m_SmoothedTrailSlicesCount; ++i) {
|
||||
var trailSlice = m_SmoothedTrailSlices[(startIndex + i) % m_SmoothedTrailSlices.Length];
|
||||
// The vertex position is the local position of the slice point. This will allow the trail to stay in the same position while the melee object is moving.
|
||||
m_Vertices.Add(m_Transform.InverseTransformPoint(trailSlice.Point));
|
||||
m_Vertices.Add(m_Transform.InverseTransformPoint(trailSlice.Point + trailSlice.Up * m_Length));
|
||||
// Set the UV value so a texture can be applied to the material.
|
||||
var u = Mathf.Max(i / (float)(m_SmoothedTrailSlicesCount - 1), 0.01f);
|
||||
m_UVs.Add(new Vector2(u, 0));
|
||||
m_UVs.Add(new Vector2(u, 1));
|
||||
// Optionally lerp between the start and end color.
|
||||
m_Colors.Add(Color.Lerp(m_EndColor, m_StartColor, u));
|
||||
// A clear color will fade the trail at the bottom.
|
||||
m_Colors.Add(Color.clear);
|
||||
|
||||
// Map the triangle indices to the vertex element.
|
||||
if (i < m_SmoothedTrailSlicesCount - 1) {
|
||||
// First triangle.
|
||||
m_Triangles.Add((i * 2));
|
||||
m_Triangles.Add((i * 2) + 1);
|
||||
m_Triangles.Add((i * 2) + 2);
|
||||
|
||||
// Second triangle.
|
||||
m_Triangles.Add((i * 2) + 2);
|
||||
m_Triangles.Add((i * 2) + 1);
|
||||
m_Triangles.Add((i * 2) + 3);
|
||||
}
|
||||
}
|
||||
|
||||
// Assign the values so the mesh will be displayed on the screen. The list version is used to prevent allocations when the mesh changes size.
|
||||
m_Mesh.SetVertices(m_Vertices);
|
||||
m_Mesh.SetUVs(0, m_UVs);
|
||||
m_Mesh.SetColors(m_Colors);
|
||||
m_Mesh.SetTriangles(m_Triangles, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops generating the trail.
|
||||
/// </summary>
|
||||
public void StopGeneration()
|
||||
{
|
||||
m_Transform.parent = null;
|
||||
m_GenerateSlices = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f4224c12740b644f82a0f901a35061e
|
||||
timeCreated: 1531917952
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 370
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,533 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.Shared.Utility;
|
||||
using Opsive.UltimateCharacterController.Character;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.UltimateCharacterController.StateSystem;
|
||||
using Opsive.UltimateCharacterController.Traits;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The MovingPlatform component will move an object from one point to another. GameObjects with the Moving Platform component should be on the MovingPlatform layer.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class MovingPlatform : StateBehavior, IKinematicObject, IInteractableTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// Represets a point that the platform can traverse.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct Waypoint
|
||||
{
|
||||
[Tooltip("The transform the waypoint that the platform should traverse.")]
|
||||
[SerializeField] private Transform m_Transform;
|
||||
[Tooltip("The amount of time that the platform should stay at the current waypoint before moving to the next waypoint")]
|
||||
[SerializeField] private float m_Delay;
|
||||
[Tooltip("The state that should be triggered when the platform is moving towards it.")]
|
||||
[SerializeField] private string m_State;
|
||||
|
||||
private int m_StateHash;
|
||||
|
||||
public Transform Transform { get { return m_Transform; } set { m_Transform = value; } }
|
||||
public float Delay { get { return m_Delay; } set { m_Delay = value; } }
|
||||
public string State { get { return m_State; } set { m_State = value; } }
|
||||
public int StateHash { get { return m_StateHash; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the state hash.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
m_StateHash = Serialization.StringHash(m_State);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the direction that the platform should traverse.
|
||||
/// </summary>
|
||||
public enum PathDirection
|
||||
{
|
||||
Forward, // Move waypoints from least to greatest.
|
||||
Backwards // Move waypoints from greatest to least.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies how the platform traverses through waypoints.
|
||||
/// </summary>
|
||||
public enum PathMovementType
|
||||
{
|
||||
PingPong, // Moves to the last waypoint and then back the way it came from.
|
||||
Loop, // Moves to the last waypoint and then directly to the first waypoint.
|
||||
Target // Moves to the specified waypoint index.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies how the platform should interpolate the movement speed.
|
||||
/// </summary>
|
||||
public enum MovementInterpolationMode
|
||||
{
|
||||
EaseInOut, // Gently moves into full movement and then gently moves out of it at each waypoint.
|
||||
EaseIn, // Gently moves into full movement.
|
||||
EaseOut, // Moves into full movement immediately and gently moves out of full movement at each waypoint.
|
||||
EaseOut2, // Moves into full movement immediately and moves out of full movement according to the movement speed.
|
||||
Slerp, // Uses Vector3.Slerp to move in and out of movement according to the movement speed.
|
||||
Lerp // Uses Vector3.Lerp to move in and out of movement according to the movement speed.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies how the platform should interpolate the rotation speed.
|
||||
/// </summary>
|
||||
public enum RotateInterpolationMode
|
||||
{
|
||||
SyncToMovement, // Rotates according to the movement speed.
|
||||
EaseOut, // Uses Quaternion.Lerp to lerp the rotation based on a linear curve.
|
||||
CustomEaseOut, // Uses Quaternion.Lerp to lerp the rotation based on the RotationEaseAmount.
|
||||
CustomRotate // Rotates according to the rotation speed.
|
||||
}
|
||||
|
||||
[Tooltip("Specifies the location that the object should be updated.")]
|
||||
[SerializeField] protected KinematicObjectManager.UpdateLocation m_UpdateLocation = KinematicObjectManager.UpdateLocation.FixedUpdate;
|
||||
[Tooltip("The waypoints to traverse.")]
|
||||
[SerializeField] protected Waypoint[] m_Waypoints;
|
||||
[Tooltip("Specifies the direction that the platform should traverse.")]
|
||||
[SerializeField] protected PathDirection m_Direction;
|
||||
[Tooltip("Specifies how the platform traverses through waypoints.")]
|
||||
[SerializeField] protected PathMovementType m_MovementType;
|
||||
[Tooltip("If using the Target PathMovementType, specifies the waypoint index to move towards.")]
|
||||
[SerializeField] protected int m_TargetWaypoint;
|
||||
[Tooltip("The speed at which the platform should move.")]
|
||||
[SerializeField] protected float m_MovementSpeed = 0.1f;
|
||||
[Tooltip("Specifies how the platform should interpolate the movement speed.")]
|
||||
[SerializeField] protected MovementInterpolationMode m_MovementInterpolation = MovementInterpolationMode.EaseInOut;
|
||||
[Tooltip("Specifies how the platform should interpolate the rotation speed.")]
|
||||
[SerializeField] protected RotateInterpolationMode m_RotationInterpolation;
|
||||
[Tooltip("If using the CustomEaseOut RotationInterpolationMode, specifies the amount to ease into the target rotation.")]
|
||||
[SerializeField] protected float m_RotationEaseAmount = 0.1f;
|
||||
[Tooltip("If using the CustomRotate RotationInterpolationMode, specifies the rotation speed.")]
|
||||
[SerializeField] protected Vector3 m_CustomRotationSpeed;
|
||||
[Tooltip("The maximum angle that the platform can rotate. Set to -1 to have no max angle.")]
|
||||
[SerializeField] protected float m_MaxRotationDeltaAngle = -1;
|
||||
[Tooltip("The state name that should activate when the character enters the platform trigger.")]
|
||||
[SerializeField] protected string m_CharacterTriggerState;
|
||||
[Tooltip("Should the platform be enabled when interacted with?")]
|
||||
[SerializeField] protected bool m_EnableOnInteract;
|
||||
[Tooltip("Should the directions be changed if the character interacts with the platform while it is moving?")]
|
||||
[SerializeField] protected bool m_ChangeDirectionsOnInteract = false;
|
||||
#if UNITY_EDITOR
|
||||
[Tooltip("The color to draw the editor gizmo in (editor only).")]
|
||||
[SerializeField] protected Color m_GizmoColor = new Color(0, 0, 1, 0.3f);
|
||||
[Tooltip("Should the delay and distance labels be drawh t0 tye scene view (editor only)?")]
|
||||
[SerializeField] protected bool m_DrawDebugLabels;
|
||||
#endif
|
||||
|
||||
public KinematicObjectManager.UpdateLocation UpdateLocation { get { return m_UpdateLocation; } }
|
||||
[NonSerialized] public Waypoint[] Waypoints { get { return m_Waypoints; } set { m_Waypoints = value; } }
|
||||
[NonSerialized] public PathDirection Direction { get { return m_Direction; } set { m_Direction = value; } }
|
||||
public PathMovementType MovementType { get { return m_MovementType; } set { m_MovementType = value; } }
|
||||
public int TargetWaypoint { get { return m_TargetWaypoint; } set { m_TargetWaypoint = value; } }
|
||||
public float MovementSpeed { get { return m_MovementSpeed; } set { m_MovementSpeed = value; } }
|
||||
public MovementInterpolationMode MovementInterpolation { get { return m_MovementInterpolation; } set { m_MovementInterpolation = value; } }
|
||||
public RotateInterpolationMode RotationInterpolation { get { return m_RotationInterpolation; } set { m_RotationInterpolation = value; } }
|
||||
public float RotationEaseAmount { get { return m_RotationEaseAmount; } set { m_RotationEaseAmount = value; } }
|
||||
public Vector3 CustomRotationSpeed { get { return m_CustomRotationSpeed; } set { m_CustomRotationSpeed = value; } }
|
||||
public string CharacterTriggerState { get { return m_CharacterTriggerState; } set { m_CharacterTriggerState = value; } }
|
||||
public bool EnableOnInteract { get { return m_EnableOnInteract; } set { m_EnableOnInteract = value; } }
|
||||
public bool ChangeDirectionsOnInteract { get { return m_ChangeDirectionsOnInteract; } set { m_ChangeDirectionsOnInteract = value; } }
|
||||
#if UNITY_EDITOR
|
||||
[NonSerialized] public Color GizmoColor { get { return m_GizmoColor; } set { m_GizmoColor = value; } }
|
||||
[NonSerialized] public bool DrawDebugLabels { get { return m_DrawDebugLabels; } set { m_DrawDebugLabels = value; } }
|
||||
#endif
|
||||
|
||||
protected GameObject m_GameObject;
|
||||
protected Transform m_Transform;
|
||||
private Rigidbody m_Rigidbody;
|
||||
|
||||
private int m_KinematicObjectIndex = -1;
|
||||
protected int m_NextWaypoint;
|
||||
protected int m_PreviousWaypoint;
|
||||
protected float m_NextWaypointDistance;
|
||||
protected Quaternion m_OriginalRotation;
|
||||
protected float m_MoveTime;
|
||||
protected Vector3 m_TargetPosition;
|
||||
protected Quaternion m_TargetRotation;
|
||||
private Vector3 m_MovePosition;
|
||||
private Quaternion m_MoveRotation;
|
||||
private AnimationCurve m_EaseInOutCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
|
||||
private AnimationCurve m_LinearCurve = AnimationCurve.Linear(0, 0, 1, 1);
|
||||
protected ScheduledEventBase m_NextWaypointEvent;
|
||||
protected int m_ActiveCharacterCount;
|
||||
|
||||
public int NextWaypoint { get { return m_NextWaypoint; } }
|
||||
public int KinematicObjectIndex { get { return m_KinematicObjectIndex; } set { m_KinematicObjectIndex = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Cache the component references and initialize the default values.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Sanity check in the editor:
|
||||
for (int i = 0; i < m_Waypoints.Length; ++i) {
|
||||
if (m_Waypoints[i].Transform == null) {
|
||||
Debug.LogError("Error: Moving Platform " + gameObject.name + " has a null waypoint. This platform will be disabled.");
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
m_GameObject = gameObject;
|
||||
m_Transform = transform;
|
||||
m_MovePosition = m_Transform.position;
|
||||
m_MoveRotation = m_Transform.rotation;
|
||||
|
||||
// The Rigidbody is only used to notify Unity that the character isn't static and for collision events. The Rigidbody doesn't control any movement.
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
m_Rigidbody.collisionDetectionMode = CollisionDetectionMode.Discrete;
|
||||
m_Rigidbody.isKinematic = true;
|
||||
m_Rigidbody.constraints = RigidbodyConstraints.FreezeAll;
|
||||
|
||||
// The GameObject must be on the MovingPlatform layer.
|
||||
if (m_GameObject.layer != LayerManager.MovingPlatform) {
|
||||
Debug.LogWarning("Warning: " + m_GameObject.name + " is a moving platform not using the MovingPlatform layer. Please change this layer.");
|
||||
m_GameObject.layer = LayerManager.MovingPlatform;
|
||||
}
|
||||
|
||||
// The platform can rotate without any waypoints.
|
||||
if (m_Waypoints.Length > 0) {
|
||||
for (int i = 0; i < m_Waypoints.Length; ++i) {
|
||||
m_Waypoints[i].Initialize();
|
||||
}
|
||||
m_TargetRotation = m_OriginalRotation = m_Waypoints[m_NextWaypoint].Transform.rotation;
|
||||
m_TargetPosition = m_Waypoints[m_NextWaypoint].Transform.position;
|
||||
|
||||
if (!string.IsNullOrEmpty(m_Waypoints[m_NextWaypoint].State)) {
|
||||
StateManager.SetState(m_GameObject, m_Waypoints[m_NextWaypoint].State, true);
|
||||
}
|
||||
m_PreviousWaypoint = m_NextWaypoint;
|
||||
}
|
||||
|
||||
// Start disabled until the platform is interacted with.
|
||||
if (m_EnableOnInteract) {
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the object with the KinematicObjectManager.
|
||||
/// </summary>
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
m_KinematicObjectIndex = KinematicObjectManager.RegisterKinematicObject(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the platform movement and rotation.
|
||||
/// </summary>
|
||||
public void Move()
|
||||
{
|
||||
// Updates the path to the next waypoint if necessary.
|
||||
UpdatePath();
|
||||
|
||||
// Rotate along the path.
|
||||
UpdateRotation();
|
||||
|
||||
// Applies the rotation.
|
||||
ApplyRotation();
|
||||
|
||||
// No more updates are necessary if the platform is waiting at the current waypoint.
|
||||
if (m_NextWaypointEvent != null || m_Waypoints.Length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Progress towards the waypoint.
|
||||
UpdatePosition();
|
||||
|
||||
// Applies the position.
|
||||
ApplyPosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the platform has arrived at the current waypoint then the next waypoint should be determined.
|
||||
/// </summary>
|
||||
private void UpdatePath()
|
||||
{
|
||||
if (GetRemainingDistance() < 0.01f && m_NextWaypointEvent == null && (m_MovementType != PathMovementType.Target || m_NextWaypoint != m_TargetWaypoint)) {
|
||||
m_NextWaypointEvent = Scheduler.ScheduleFixed(m_Waypoints[m_NextWaypoint].Delay, UpdateWaypoint);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the moving platform to move to the next waypoint.
|
||||
/// </summary>
|
||||
protected void UpdateWaypoint()
|
||||
{
|
||||
// The state should always reflect the state of the next waypoint. If moving in reverse then the state has to be updated before the index changes.
|
||||
if (m_Direction == PathDirection.Backwards) {
|
||||
UpdateState();
|
||||
}
|
||||
m_PreviousWaypoint = m_NextWaypoint;
|
||||
|
||||
switch (m_MovementType) {
|
||||
case PathMovementType.Target:
|
||||
if (m_NextWaypoint != m_TargetWaypoint) {
|
||||
GoToNextWaypoint();
|
||||
}
|
||||
break;
|
||||
case PathMovementType.Loop:
|
||||
GoToNextWaypoint();
|
||||
break;
|
||||
case PathMovementType.PingPong:
|
||||
if (m_Direction == PathDirection.Backwards) {
|
||||
if (m_NextWaypoint == 0) {
|
||||
m_Direction = PathDirection.Forward;
|
||||
}
|
||||
} else {
|
||||
if (m_NextWaypoint == (m_Waypoints.Length - 1)) {
|
||||
m_Direction = PathDirection.Backwards;
|
||||
}
|
||||
}
|
||||
GoToNextWaypoint();
|
||||
break;
|
||||
}
|
||||
// The state should always reflect the state of the next waypoint. If moving in reverse then the state has to be updated before the index changes.
|
||||
if (m_Direction == PathDirection.Forward) {
|
||||
UpdateState();
|
||||
}
|
||||
m_NextWaypointEvent = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables the state at the old index and enables the state at the new index.
|
||||
/// </summary>
|
||||
private void UpdateState()
|
||||
{
|
||||
if (m_Waypoints[m_PreviousWaypoint].StateHash != m_Waypoints[m_NextWaypoint].StateHash) {
|
||||
// The previous state should be disabled.
|
||||
if (m_Waypoints[m_PreviousWaypoint].StateHash != 0) {
|
||||
StateManager.SetState(m_GameObject, m_Waypoints[m_PreviousWaypoint].State, false);
|
||||
}
|
||||
if (m_Waypoints[m_NextWaypoint].StateHash != 0) {
|
||||
StateManager.SetState(m_GameObject, m_Waypoints[m_NextWaypoint].State, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the distance to the next waypoint.
|
||||
/// </summary>
|
||||
/// <returns>The distance to the next waypoint.</returns>
|
||||
protected float GetRemainingDistance()
|
||||
{
|
||||
if (m_Waypoints.Length == 0) {
|
||||
return float.MaxValue;
|
||||
}
|
||||
return Vector3.Distance(m_Transform.position, m_Waypoints[m_NextWaypoint].Transform.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the next waypoint.
|
||||
/// </summary>
|
||||
private void GoToNextWaypoint()
|
||||
{
|
||||
// The next waypoint is based on the path direction.
|
||||
switch (m_Direction) {
|
||||
case PathDirection.Forward:
|
||||
m_NextWaypoint = GetNextWaypoint(true);
|
||||
break;
|
||||
case PathDirection.Backwards:
|
||||
m_NextWaypoint = GetNextWaypoint(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the path related variables.
|
||||
m_MoveTime = 0;
|
||||
m_OriginalRotation = m_TargetRotation;
|
||||
m_TargetPosition = m_Waypoints[m_NextWaypoint].Transform.position;
|
||||
m_TargetRotation = m_Waypoints[m_NextWaypoint].Transform.rotation;
|
||||
m_NextWaypointDistance = GetRemainingDistance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next waypoint index.
|
||||
/// </summary>
|
||||
/// <param name="increase">Should the waypoint index be inceased? If false it'll be decreased.</param>
|
||||
/// <returns>The next waypoint index.</returns>
|
||||
private int GetNextWaypoint(bool increase)
|
||||
{
|
||||
m_NextWaypoint = (m_NextWaypoint + (increase ? 1 : -1)) % m_Waypoints.Length;
|
||||
if (m_NextWaypoint < 0) {
|
||||
m_NextWaypoint = 0;
|
||||
}
|
||||
return m_NextWaypoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates platform angle according to the current rotation interpolation mode.
|
||||
/// </summary>
|
||||
private void UpdateRotation()
|
||||
{
|
||||
switch (m_RotationInterpolation) {
|
||||
case RotateInterpolationMode.SyncToMovement:
|
||||
if (m_NextWaypointEvent == null) {
|
||||
m_MoveRotation = Quaternion.Lerp(m_OriginalRotation, m_TargetRotation, 1.0f - (GetRemainingDistance() / m_NextWaypointDistance));
|
||||
}
|
||||
break;
|
||||
case RotateInterpolationMode.EaseOut:
|
||||
m_MoveRotation = Quaternion.Lerp(m_Transform.rotation, m_TargetRotation, m_LinearCurve.Evaluate(m_MoveTime));
|
||||
break;
|
||||
case RotateInterpolationMode.CustomEaseOut:
|
||||
m_MoveRotation = Quaternion.Lerp(m_Transform.rotation, m_TargetRotation, m_RotationEaseAmount);
|
||||
break;
|
||||
case RotateInterpolationMode.CustomRotate:
|
||||
m_MoveRotation = m_Transform.rotation * Quaternion.Euler(m_CustomRotationSpeed);
|
||||
break;
|
||||
}
|
||||
if (m_MaxRotationDeltaAngle != -1) {
|
||||
m_MoveRotation = Quaternion.RotateTowards(m_Transform.rotation, m_MoveRotation, m_MaxRotationDeltaAngle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the rotational movement to the Transform.
|
||||
/// </summary>
|
||||
private void ApplyRotation()
|
||||
{
|
||||
m_Transform.rotation = m_MoveRotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates platform position according to the current movement interpolation mode.
|
||||
/// </summary>
|
||||
private void UpdatePosition()
|
||||
{
|
||||
switch (m_MovementInterpolation)
|
||||
{
|
||||
case MovementInterpolationMode.EaseInOut:
|
||||
m_MovePosition = Vector3.Lerp(m_Transform.position, m_TargetPosition, m_EaseInOutCurve.Evaluate(m_MoveTime));
|
||||
break;
|
||||
case MovementInterpolationMode.EaseIn:
|
||||
m_MovePosition = Vector3.MoveTowards(m_Transform.position, m_TargetPosition, m_MoveTime);
|
||||
break;
|
||||
case MovementInterpolationMode.EaseOut:
|
||||
m_MovePosition = Vector3.Lerp(m_Transform.position, m_TargetPosition, m_LinearCurve.Evaluate(m_MoveTime));
|
||||
break;
|
||||
case MovementInterpolationMode.EaseOut2:
|
||||
m_MovePosition = Vector3.Lerp(m_Transform.position, m_TargetPosition, m_MovementSpeed * 0.25f);
|
||||
break;
|
||||
case MovementInterpolationMode.Slerp:
|
||||
m_MovePosition = Vector3.Slerp(m_Transform.position, m_TargetPosition, m_LinearCurve.Evaluate(m_MoveTime));
|
||||
break;
|
||||
case MovementInterpolationMode.Lerp:
|
||||
m_MovePosition = Vector3.MoveTowards(m_Transform.position, m_TargetPosition, m_MovementSpeed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the positional movement to the Transform.
|
||||
/// </summary>
|
||||
private void ApplyPosition()
|
||||
{
|
||||
m_Transform.position = m_MovePosition;
|
||||
|
||||
// Progress the move time and also store the updated metrics.
|
||||
m_MoveTime += m_MovementSpeed * 0.01f * Time.deltaTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the target be interacted with?
|
||||
/// </summary>
|
||||
/// <param name="character">The character that wants to interactact with the target.</param>
|
||||
/// <returns>True if the target can be interacted with.</returns>
|
||||
public bool CanInteract(GameObject character)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interact with the target.
|
||||
/// </summary>
|
||||
/// <param name="character">The character that wants to interactact with the target.</param>
|
||||
public void Interact(GameObject character)
|
||||
{
|
||||
if (m_EnableOnInteract) {
|
||||
enabled = true;
|
||||
} else if (m_ChangeDirectionsOnInteract) {
|
||||
// If the platform is already moving and is interacted with then it should change directions.
|
||||
m_Direction = m_Direction == PathDirection.Forward ? PathDirection.Backwards : PathDirection.Forward;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An object has entered the trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The object that entered the trigger.</param>
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
// Characters will have a CharacterLayerManager.
|
||||
var layerManager = other.gameObject.GetCachedParentComponent<CharacterLayerManager>();
|
||||
if (layerManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MathUtility.InLayerMask(other.gameObject.layer, layerManager.CharacterLayer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_ActiveCharacterCount++;
|
||||
|
||||
// The platform can activate a state based on the character trigger state. Only the first character should activate the state
|
||||
// if multiple characters land on the platform.
|
||||
if (m_ActiveCharacterCount == 1 && !string.IsNullOrEmpty(m_CharacterTriggerState)) {
|
||||
StateManager.SetState(m_GameObject, m_CharacterTriggerState, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An object has exited the trigger.
|
||||
/// </summary>
|
||||
/// <param name="other">The collider that exited the trigger.</param>
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
// No further checks need to be done if a character isn't on the platform.
|
||||
if (m_ActiveCharacterCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Characters will have a CharacterLayerManager.
|
||||
var layerManager = other.gameObject.GetCachedParentComponent<CharacterLayerManager>();
|
||||
if (layerManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MathUtility.InLayerMask(other.gameObject.layer, layerManager.CharacterLayer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_ActiveCharacterCount--;
|
||||
|
||||
if (m_ActiveCharacterCount == 0 && !string.IsNullOrEmpty(m_CharacterTriggerState)) {
|
||||
StateManager.SetState(m_GameObject, m_CharacterTriggerState, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters the object with the KinematicObjectManager.
|
||||
/// </summary>
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
KinematicObjectManager.UnregisterKinematicObject(m_KinematicObjectIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 362155b3b81744a4aa28b315efd334a2
|
||||
timeCreated: 1554812130
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,218 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Character;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.UltimateCharacterController.Items;
|
||||
using Opsive.UltimateCharacterController.Items.Actions.PerspectiveProperties;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Shows an object which slowly fades out with time. Can optionally attach a light to the GameObject and that light will be faded as well.
|
||||
/// </summary>
|
||||
public class MuzzleFlash : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The name of the shader tint color property.")]
|
||||
[SerializeField] protected string m_TintColorPropertyName = "_TintColor";
|
||||
|
||||
[Tooltip("The alpha value to initialize the muzzle flash material to.")]
|
||||
[Range(0, 1)] [SerializeField] protected float m_StartAlpha = 0.5f;
|
||||
[Tooltip("The minimum fade speed - the larger the value the quicker the muzzle flash will fade.")]
|
||||
[SerializeField] protected float m_MinFadeSpeed = 3;
|
||||
[Tooltip("The maximum fade speed - the larger the value the quicker the muzzle flash will fade.")]
|
||||
[SerializeField] protected float m_MaxFadeSpeed = 4;
|
||||
|
||||
private GameObject m_GameObject;
|
||||
#if FIRST_PERSON_CONTROLLER && THIRD_PERSON_CONTROLLER
|
||||
private Transform m_Transform;
|
||||
#endif
|
||||
private Material m_Material;
|
||||
private Light m_Light;
|
||||
private ParticleSystem m_Particles;
|
||||
|
||||
private int m_TintColorPropertyID;
|
||||
private Color m_Color;
|
||||
private float m_StartLightIntensity;
|
||||
private float m_FadeSpeed;
|
||||
private float m_TimeScale = 1;
|
||||
|
||||
private GameObject m_Character;
|
||||
#if FIRST_PERSON_CONTROLLER && THIRD_PERSON_CONTROLLER
|
||||
private Item m_Item;
|
||||
private int m_ItemActionID;
|
||||
#endif
|
||||
private bool m_Pooled;
|
||||
private int m_StartLayer;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default values.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
m_GameObject = gameObject;
|
||||
#if FIRST_PERSON_CONTROLLER && THIRD_PERSON_CONTROLLER
|
||||
m_Transform = transform;
|
||||
#endif
|
||||
m_TintColorPropertyID = Shader.PropertyToID(m_TintColorPropertyName);
|
||||
|
||||
var renderer = GetComponent<Renderer>();
|
||||
if (renderer != null) {
|
||||
m_Material = renderer.sharedMaterial;
|
||||
}
|
||||
m_Light = GetComponent<Light>();
|
||||
m_Particles = GetComponent<ParticleSystem>();
|
||||
// If a light exists set the start light intensity. Every time the muzzle flash is enabed the light intensity will be reset to its starting value.
|
||||
if (m_Light != null) {
|
||||
m_StartLightIntensity = m_Light.intensity;
|
||||
}
|
||||
m_StartLayer = m_GameObject.layer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The muzzle flash has been enabled.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
m_Color.a = 0;
|
||||
if (m_Material != null) {
|
||||
m_Material.SetColor(m_TintColorPropertyID, m_Color);
|
||||
}
|
||||
if (m_Light != null) {
|
||||
m_Light.intensity = 0;
|
||||
}
|
||||
if (m_Particles != null) {
|
||||
m_Particles.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A weapon has been fired and the muzzle flash needs to show. Set the starting alpha value and light intensity if the light exists.
|
||||
/// </summary>
|
||||
/// <param name="item">The item that the muzzle flash is attached to.</param>
|
||||
/// <param name="itemActionID">The ID which corresponds to the ItemAction that spawned the muzzle flash.</param>
|
||||
/// <param name="pooled">Is the muzzle flash pooled?</param>
|
||||
/// <param name="characterLocomotion">The character that the muzzle flash is attached to.</param>
|
||||
public void Show(Item item, int itemActionID, bool pooled, UltimateCharacterLocomotion characterLocomotion)
|
||||
{
|
||||
// The muzzle flash may be inactive if the object isn't pooled.
|
||||
if (!m_Pooled) {
|
||||
m_GameObject.SetActive(true);
|
||||
}
|
||||
|
||||
if (m_Character == null && characterLocomotion != null) {
|
||||
m_Character = characterLocomotion.gameObject;
|
||||
|
||||
EventHandler.RegisterEvent<float>(m_Character, "OnCharacterChangeTimeScale", OnChangeTimeScale);
|
||||
#if FIRST_PERSON_CONTROLLER && THIRD_PERSON_CONTROLLER
|
||||
EventHandler.RegisterEvent<bool>(m_Character, "OnCharacterChangePerspectives", OnChangePerspectives);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if FIRST_PERSON_CONTROLLER && THIRD_PERSON_CONTROLLER
|
||||
m_Item = item;
|
||||
m_ItemActionID = itemActionID;
|
||||
#endif
|
||||
m_Pooled = pooled;
|
||||
if (characterLocomotion != null) {
|
||||
m_TimeScale = characterLocomotion.TimeScale;
|
||||
m_GameObject.layer = characterLocomotion.FirstPersonPerspective ? LayerManager.Overlay : m_StartLayer;
|
||||
} else {
|
||||
m_TimeScale = 1;
|
||||
m_GameObject.layer = m_StartLayer;
|
||||
}
|
||||
|
||||
m_Color = Color.white;
|
||||
m_Color.a = m_StartAlpha;
|
||||
if (m_Material != null) {
|
||||
m_Material.SetColor(m_TintColorPropertyID, m_Color);
|
||||
}
|
||||
m_FadeSpeed = Random.Range(m_MinFadeSpeed, m_MaxFadeSpeed);
|
||||
if (m_Light != null) {
|
||||
m_Light.intensity = m_StartLightIntensity;
|
||||
}
|
||||
if (m_Particles != null) {
|
||||
m_Particles.Play(true);
|
||||
}
|
||||
// The muzzle flash may be inactive if the object isn't pooled.
|
||||
if (!m_Pooled) {
|
||||
m_GameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrease the alpha value of the muzzle flash to give it a fading effect. As soon as the alpha value reaches zero place the muzzle flash back in
|
||||
/// the object pool. If a light exists decrease the intensity of the light as well.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (m_Color.a > 0) {
|
||||
m_Color.a = Mathf.Max(m_Color.a - (m_FadeSpeed * Time.deltaTime * m_TimeScale), 0);
|
||||
if (m_Material != null) {
|
||||
m_Material.SetColor(m_TintColorPropertyID, m_Color);
|
||||
}
|
||||
// Keep the light intensity synchronized with the alpha channel's value.
|
||||
if (m_Light != null) {
|
||||
m_Light.intensity = m_StartLightIntensity * (m_Color.a / m_StartAlpha);
|
||||
}
|
||||
} else {
|
||||
if (m_Pooled) {
|
||||
ObjectPool.Destroy(m_GameObject);
|
||||
} else {
|
||||
m_GameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character's local timescale has changed.
|
||||
/// </summary>
|
||||
/// <param name="timeScale">The new timescale.</param>
|
||||
private void OnChangeTimeScale(float timeScale)
|
||||
{
|
||||
m_TimeScale = timeScale;
|
||||
}
|
||||
|
||||
#if FIRST_PERSON_CONTROLLER && THIRD_PERSON_CONTROLLER
|
||||
/// <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)
|
||||
{
|
||||
// When switching locations the local position and rotation should remain the same.
|
||||
var localPosition = m_Transform.localPosition;
|
||||
var localRotation = m_Transform.rotation;
|
||||
|
||||
var itemAction = m_Item.ItemActions[m_ItemActionID];
|
||||
var perspectiveProperties = (firstPersonPerspective ? itemAction.FirstPersonPerspectiveProperties : itemAction.ThirdPersonPerspectiveProperties);
|
||||
var muzzleFlashLocation = (perspectiveProperties as IShootableWeaponPerspectiveProperties).MuzzleFlashLocation;
|
||||
m_Transform.parent = muzzleFlashLocation;
|
||||
m_Transform.localPosition = localPosition;
|
||||
m_Transform.rotation = localRotation;
|
||||
|
||||
m_GameObject.layer = firstPersonPerspective ? LayerManager.Overlay : m_StartLayer;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The object has been disabled.
|
||||
/// </summary>
|
||||
private void OnDisable()
|
||||
{
|
||||
if (m_Pooled && m_Character != null) {
|
||||
EventHandler.UnregisterEvent<float>(m_Character, "OnCharacterChangeTimeScale", OnChangeTimeScale);
|
||||
#if FIRST_PERSON_CONTROLLER && THIRD_PERSON_CONTROLLER
|
||||
EventHandler.UnregisterEvent<bool>(m_Character, "OnCharacterChangePerspectives", OnChangePerspectives);
|
||||
#endif
|
||||
m_Character = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 533c81f655f906f46b89d75f31829bcd
|
||||
timeCreated: 1505602291
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,80 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects
|
||||
{
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.SurfaceSystem;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// The Projectile component moves a Destructible object along the specified path. Can apply damage at the collision point.
|
||||
/// </summary>
|
||||
public class Projectile : Destructible
|
||||
{
|
||||
[Tooltip("The length of time the projectile should exist before it deactivates if no collision occurs.")]
|
||||
[SerializeField] protected float m_Lifespan = 10;
|
||||
|
||||
private ScheduledEventBase m_ScheduledDeactivation;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the object. This will be called from an object creating the projectile (such as a weapon).
|
||||
/// </summary>
|
||||
/// <param name="velocity">The velocity to apply.</param>
|
||||
/// <param name="torque">The torque to apply.</param>
|
||||
/// <param name="damageAmount">The amount of damage to apply to the hit object.</param>
|
||||
/// <param name="impactForce">The amount of force to apply to the hit object.</param>
|
||||
/// <param name="impactForceFrames">The number of frames to add the force to.</param>
|
||||
/// <param name="impactLayers">The layers that the projectile can impact with.</param>
|
||||
/// <param name="impactStateName">The name of the state to activate upon impact.</param>
|
||||
/// <param name="impactStateDisableTimer">The number of seconds until the impact state is disabled.</param>
|
||||
/// <param name="surfaceImpact">A reference to the Surface Impact triggered when the object hits an object.</param>
|
||||
/// <param name="originator">The object that instantiated the trajectory object.</param>
|
||||
public override void Initialize(Vector3 velocity, Vector3 torque, float damageAmount, float impactForce, int impactForceFrames, LayerMask impactLayers,
|
||||
string impactStateName, float impactStateDisableTimer, SurfaceImpact surfaceImpact, GameObject originator)
|
||||
{
|
||||
// The projectile can deactivate after it comes in contact with another object or after a specified amount of time. Do the scheduling here to allow
|
||||
// it to activate after a set amount of time.
|
||||
m_ScheduledDeactivation = Scheduler.Schedule(m_Lifespan, Deactivate);
|
||||
|
||||
base.Initialize(velocity, torque, damageAmount, impactForce, impactForceFrames, impactLayers, impactStateName, impactStateDisableTimer, surfaceImpact, originator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The projectile has reached its lifespan.
|
||||
/// </summary>
|
||||
private void Deactivate()
|
||||
{
|
||||
OnCollision(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has collided with another object.
|
||||
/// </summary>
|
||||
/// <param name="hit">The RaycastHit of the object. Can be null.</param>
|
||||
protected override void OnCollision(RaycastHit? hit)
|
||||
{
|
||||
if (m_ScheduledDeactivation != null) {
|
||||
Scheduler.Cancel(m_ScheduledDeactivation);
|
||||
m_ScheduledDeactivation = null;
|
||||
}
|
||||
base.OnCollision(hit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The component has been disabled.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
if (m_ScheduledDeactivation != null) {
|
||||
Scheduler.Cancel(m_ScheduledDeactivation);
|
||||
m_ScheduledDeactivation = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4e5fbf96af1f2e4d9c6f57d702b7de2
|
||||
timeCreated: 1498505827
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,820 @@
|
||||
/// ---------------------------------------------
|
||||
/// Ultimate Character Controller
|
||||
/// Copyright (c) Opsive. All Rights Reserved.
|
||||
/// https://www.opsive.com
|
||||
/// ---------------------------------------------
|
||||
|
||||
namespace Opsive.UltimateCharacterController.Objects
|
||||
{
|
||||
using Opsive.Shared.Events;
|
||||
using Opsive.Shared.Game;
|
||||
using Opsive.UltimateCharacterController.Audio;
|
||||
using Opsive.UltimateCharacterController.Character;
|
||||
using Opsive.UltimateCharacterController.Game;
|
||||
using Opsive.UltimateCharacterController.SurfaceSystem;
|
||||
using Opsive.UltimateCharacterController.Utility;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// A trajectory object follows a kinematic parabolic curve and can be simuated using the SimulateTrajectory method.
|
||||
/// </summary>
|
||||
public class TrajectoryObject : MonoBehaviour, IForceObject
|
||||
{
|
||||
// Padding value used to prevent the collider from overlapping the environment collider. Overlapped colliders don't work well with ray casts.
|
||||
private const float c_ColliderSpacing = 0.01f;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies how the object should behave after hitting another collider.
|
||||
/// </summary>
|
||||
public enum CollisionMode
|
||||
{
|
||||
Collide, // Collides with the object. Does not bounce.
|
||||
Reflect, // Reflect according to the velocity.
|
||||
RandomReflect, // Reflect in a random direction. This mode will make the object nonkinematic but for visual only objects such as shells this is preferred.
|
||||
Ignore // Passes through the object. A collision is reported.
|
||||
}
|
||||
|
||||
[Tooltip("Should the component initialize when enabled?")]
|
||||
[SerializeField] protected bool m_InitializeOnEnable = false;
|
||||
[Tooltip("The mass of the object.")]
|
||||
[SerializeField] protected float m_Mass = 1;
|
||||
[Tooltip("Multiplies the starting velocity by the specified value.")]
|
||||
[SerializeField] protected float m_StartVelocityMultiplier = 1;
|
||||
[Tooltip("The amount of gravity to apply to the object.")]
|
||||
[Range(0, 40)] [SerializeField] protected float m_GravityMagnitude = 9.8f;
|
||||
[Tooltip("The movement speed.")]
|
||||
[SerializeField] protected float m_Speed = 1;
|
||||
[Tooltip("The rotation speed.")]
|
||||
[SerializeField] protected float m_RotationSpeed = 5;
|
||||
[Tooltip("The amount of damping to apply to the movement.")]
|
||||
[Range(0, 1)] [SerializeField] protected float m_Damping = 0.1f;
|
||||
[Tooltip("Amount of damping to apply to the torque.")]
|
||||
[Range(0, 1)] [SerializeField] protected float m_RotationDamping = 0.1f;
|
||||
[Tooltip("Should the object rotate in the direction that it is moving?")]
|
||||
[SerializeField] protected bool m_RotateInMoveDirection;
|
||||
[Tooltip("When the velocity and torque have a square magnitude value less than the specified value the object will be considered settled.")]
|
||||
[SerializeField] protected float m_SettleThreshold;
|
||||
[Tooltip("Specifies if the collider should settle on its side or upright. The higher the value the more likely the collider will settle on its side. " +
|
||||
"This is only used for CapsuleColliders and BoxColliders.")]
|
||||
[Range(0, 1)] [SerializeField] protected float m_SidewaysSettleThreshold = 0.75f;
|
||||
[Tooltip("Starts to rotate to the settle rotation when the velocity magnitude is less than the specified values.")]
|
||||
[SerializeField] protected float m_StartSidewaysVelocityMagnitude = 3f;
|
||||
[Tooltip("The layers that the object can collide with.")]
|
||||
[SerializeField] protected LayerMask m_ImpactLayers = ~(1 << LayerManager.IgnoreRaycast | 1 << LayerManager.Water | 1 << LayerManager.SubCharacter | 1 << LayerManager.Overlay |
|
||||
1 << LayerManager.VisualEffect);
|
||||
[Tooltip("The identifier that is used when the object collides with another object.")]
|
||||
[SerializeField] protected SurfaceImpact m_SurfaceImpact;
|
||||
[Tooltip("When a force is applied the multiplier will modify the magnitude of the force.")]
|
||||
[SerializeField] protected float m_ForceMultiplier = 40;
|
||||
[Tooltip("Specifies how the object should behave after hitting another collider.")]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("m_BounceMode")] // 2.2.
|
||||
[SerializeField] protected CollisionMode m_CollisionMode = CollisionMode.Reflect;
|
||||
[Tooltip("If the object can reflect, specifies the multiplier to apply to the reflect velocity.")]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("m_BounceMultiplier")]
|
||||
[Range(0, 4)] [SerializeField] protected float m_ReflectMultiplier = 1;
|
||||
[Tooltip("The maximum number of objects the projectile can collide with at a time.")]
|
||||
[SerializeField] protected int m_MaxCollisionCount = 5;
|
||||
[Tooltip("The maximum number of positions any single curve amplitude can contain.")]
|
||||
[SerializeField] protected int m_MaxPositionCount = 150;
|
||||
[Tooltip("The audio that should be looped while the object is active.")]
|
||||
[SerializeField] protected AudioClipSet m_ActiveAudioClipSet = new AudioClipSet();
|
||||
|
||||
public float Mass { get { return m_Mass; } set { m_Mass = value; } }
|
||||
public float StartVelocityMultiplier { get { return m_StartVelocityMultiplier; } set { m_StartVelocityMultiplier = value; } }
|
||||
public float GravityMagnitude { get { return m_GravityMagnitude; } set { m_GravityMagnitude = value; } }
|
||||
public float Speed { get { return m_Speed; } set { m_Speed = value; } }
|
||||
public float RotationSpeed { get { return m_RotationSpeed; } set { m_RotationSpeed = value; } }
|
||||
public float Damping { get { return m_Damping; } set { m_Damping = value; } }
|
||||
public float RotationDamping { get { return m_RotationDamping; } set { m_RotationDamping = value; } }
|
||||
public bool RotateInMoveDirection { get { return m_RotateInMoveDirection; } set { m_RotateInMoveDirection = value; } }
|
||||
public float SettleThreshold { get { return m_SettleThreshold; } set { m_SettleThreshold = value; } }
|
||||
public float SidewaysSettleThreshold { get { return m_SidewaysSettleThreshold; } set { m_SidewaysSettleThreshold = value; } }
|
||||
public float StartSidewaysVelocityMagnitude { get { return m_StartSidewaysVelocityMagnitude; } set { m_StartSidewaysVelocityMagnitude = value; } }
|
||||
public LayerMask ImpactLayers { get { return m_ImpactLayers; } set { m_ImpactLayers = value; } }
|
||||
public SurfaceImpact SurfaceImpact { get { return m_SurfaceImpact; } set { m_SurfaceImpact = value; } }
|
||||
public float ForceMultiplier { get { return m_ForceMultiplier; } set { m_ForceMultiplier = value; } }
|
||||
public CollisionMode Collision { get { return m_CollisionMode; } set { m_CollisionMode = value; } }
|
||||
public float ReflectMultiplier { get { return m_ReflectMultiplier; } set { m_ReflectMultiplier = value; } }
|
||||
public AudioClipSet ActiveAudioClipSet { get { return m_ActiveAudioClipSet; } set { m_ActiveAudioClipSet = value; } }
|
||||
|
||||
protected GameObject m_GameObject;
|
||||
protected Transform m_Transform;
|
||||
protected Collider m_Collider;
|
||||
protected GameObject m_Originator;
|
||||
protected Transform m_OriginatorTransform;
|
||||
protected UltimateCharacterLocomotion m_OriginatorCharacterLocomotion;
|
||||
private LineRenderer m_LineRenderer;
|
||||
|
||||
protected RaycastHit m_RaycastHit;
|
||||
protected Collider[] m_ColliderHit;
|
||||
private List<Vector3> m_Positions;
|
||||
|
||||
private Vector3 m_Gravity;
|
||||
protected Vector3 m_NormalizedGravity;
|
||||
protected Vector3 m_Velocity;
|
||||
protected Vector3 m_Torque;
|
||||
private bool m_DeterminedRotation;
|
||||
private bool m_SettleSideways;
|
||||
private bool m_OriginatorCollisionCheck;
|
||||
|
||||
private float m_TimeScale;
|
||||
private bool m_AutoDisable;
|
||||
private bool m_MovementSettled;
|
||||
private bool m_RotationSettled;
|
||||
private bool m_InCollision;
|
||||
private bool m_Collided;
|
||||
|
||||
private Transform m_Platform;
|
||||
private Vector3 m_PlatformRelativePosition;
|
||||
private Quaternion m_PrevPlatformRotation;
|
||||
|
||||
public GameObject Originator { get { return m_Originator; } }
|
||||
public Vector3 Velocity { get { return m_Velocity; } }
|
||||
public Vector3 Torque { get { return m_Torque; } }
|
||||
public LineRenderer LineRenderer { get { return m_LineRenderer; } }
|
||||
protected bool AutoDisable { set { m_AutoDisable = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the defualt values.
|
||||
/// </summary>
|
||||
protected virtual void Awake()
|
||||
{
|
||||
// The movement will be controlled by the TrajectoryObject.
|
||||
var rigidbody = GetComponent<Rigidbody>();
|
||||
if (rigidbody != null) {
|
||||
rigidbody.useGravity = false;
|
||||
rigidbody.isKinematic = true;
|
||||
}
|
||||
|
||||
// The object may want to play audio.
|
||||
var hasActiveAudioClipSet = false;
|
||||
if ((hasActiveAudioClipSet = (m_ActiveAudioClipSet.AudioClips != null && m_ActiveAudioClipSet.AudioClips.Length > 0 && m_ActiveAudioClipSet.AudioClips[0] != null)) ||
|
||||
m_SurfaceImpact != null) {
|
||||
AudioManager.Register(gameObject);
|
||||
// The looping audio should have a reserved index of 0.
|
||||
if (hasActiveAudioClipSet) {
|
||||
AudioManager.SetReserveCount(gameObject, 1);
|
||||
}
|
||||
}
|
||||
|
||||
enabled = m_InitializeOnEnable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The component has been enabled.
|
||||
/// </summary>
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (m_InitializeOnEnable) {
|
||||
InitializeComponentReferences();
|
||||
Initialize(Vector3.zero, Vector3.zero, null, false, -m_Transform.up);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a simulation of the parabolic trajectory with the given start and end position. The trajectory will then be displayed with the attached LineRenderer.
|
||||
/// </summary>
|
||||
/// <param name="originator">The object that instantiated the trajectory object.</param>
|
||||
/// <param name="startPosition">The starting position.</param>
|
||||
/// <param name="endPosition">The ending position.</param>
|
||||
public void SimulateTrajectory(GameObject originator, Vector3 startPosition, Vector3 endPosition)
|
||||
{
|
||||
var velocity = CalculateVelocity(startPosition, endPosition);
|
||||
Initialize(velocity, Vector3.zero, originator);
|
||||
|
||||
if (m_LineRenderer == null) {
|
||||
Debug.LogError($"Error: A LineRenderer must be added to the Trajectory Object {name}.", this);
|
||||
return;
|
||||
}
|
||||
if (m_Positions == null) {
|
||||
m_Positions = new List<Vector3>();
|
||||
} else {
|
||||
m_Positions.Clear();
|
||||
}
|
||||
m_Positions.Add(startPosition);
|
||||
SimulateTrajectory(startPosition, m_Transform.rotation, m_Positions, 0);
|
||||
// Insert the end position into the list to ensure the complete curve is shown.
|
||||
m_Positions[m_Positions.Count - 1] = endPosition;
|
||||
|
||||
// Show the curve.
|
||||
m_LineRenderer.positionCount = m_Positions.Count;
|
||||
m_LineRenderer.SetPositions(m_Positions.ToArray());
|
||||
|
||||
m_MovementSettled = m_RotationSettled = false;
|
||||
m_Collided = false;
|
||||
if (m_Collider != null) {
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a simulation of the parabolic trajectory. The trajectory will then be displayed with the attached LineRenderer.
|
||||
/// </summary>
|
||||
/// <param name="originator">The object that instantiated the trajectory object.</param>
|
||||
/// <param name="position">The starting position.</param>
|
||||
/// <param name="rotation">The starting rotation.</param>
|
||||
/// <param name="velocity">The starting velocity.</param>
|
||||
/// <param name="torque">The starting torque.</param>
|
||||
public void SimulateTrajectory(GameObject originator, Vector3 position, Quaternion rotation, Vector3 velocity, Vector3 torque)
|
||||
{
|
||||
Initialize(velocity, torque, originator, false);
|
||||
|
||||
if (m_LineRenderer == null) {
|
||||
Debug.LogError($"Error: A LineRenderer must be added to the Trajectory Object {name}.", this);
|
||||
return;
|
||||
}
|
||||
if (m_Positions == null) {
|
||||
m_Positions = new List<Vector3>();
|
||||
} else {
|
||||
m_Positions.Clear();
|
||||
}
|
||||
m_Positions.Add(position);
|
||||
SimulateTrajectory(position, rotation, m_Positions, 0);
|
||||
|
||||
// Show the curve.
|
||||
m_LineRenderer.positionCount = m_Positions.Count;
|
||||
m_LineRenderer.SetPositions(m_Positions.ToArray());
|
||||
|
||||
m_MovementSettled = m_RotationSettled = false;
|
||||
m_InCollision = false;
|
||||
m_Collided = false;
|
||||
if (m_Collider != null) {
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the trajectory from the LineRenderer.
|
||||
/// </summary>
|
||||
public void ClearTrajectory()
|
||||
{
|
||||
if (m_LineRenderer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_LineRenderer.positionCount = 0;
|
||||
if (m_Originator != null) {
|
||||
EventHandler.UnregisterEvent<float>(m_Originator, "OnCharacterChangeTimeScale", OnChangeTimeScale);
|
||||
m_Originator = null;
|
||||
m_OriginatorCharacterLocomotion = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the velocity to move from startPosition to endPosition.
|
||||
/// </summary>
|
||||
/// <param name="startPosition">The starting position.</param>
|
||||
/// <param name="endPosition">The ending position.</param>
|
||||
/// <returns>The velocity required to move from startPosition to endPosition.</returns>
|
||||
private Vector3 CalculateVelocity(Vector3 startPosition, Vector3 endPosition)
|
||||
{
|
||||
var direction = endPosition - startPosition;
|
||||
return direction - m_Gravity * 0.5f + direction * 0.02f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a simulation of the parabolic trajectory. Will save off the positions in the positions list.
|
||||
/// </summary>
|
||||
/// <param name="originator">The object that instantiated the trajectory object.</param>
|
||||
/// <param name="velocity">The starting velocity.</param>
|
||||
/// <param name="torque">The starting torque.</param>
|
||||
/// <param name="positions">The list of positions that the object will move through.</param>
|
||||
/// <param name="positionsSkip">Reduce the number of saved positions by skipping a specified number of positions.</param>
|
||||
public void SimulateTrajectory(GameObject originator, Vector3 velocity, Vector3 torque, List<Vector3> positions, int positionsSkip)
|
||||
{
|
||||
Initialize(velocity, torque, originator, false);
|
||||
|
||||
SimulateTrajectory(m_Transform.position, m_Transform.rotation, positions, positionsSkip);
|
||||
if (m_Collider != null) {
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a simulation of the parabolic trajectory. Will save off the positions in the positions list.
|
||||
/// </summary>
|
||||
/// <param name="position">The current position of the object.</param>
|
||||
/// <param name="rotation">The current rotation of the object.</param>
|
||||
/// <param name="positions">The list of positions that the object will move through.</param>
|
||||
/// <param name="positionsSkip">Reduce the number of saved positions by skipping a specified number of positions.</param>
|
||||
public void SimulateTrajectory(Vector3 position, Quaternion rotation, List<Vector3> positions, int positionsSkip)
|
||||
{
|
||||
for (int i = 0; i < m_MaxPositionCount; i++) {
|
||||
// Saving every position may be too high of a resolution than what is necessary - allow every x number of positions be skipped.
|
||||
for (int j = 0; j < positionsSkip + 1; ++j) {
|
||||
if (!Move(ref position, rotation)) {
|
||||
// If the object hit a collider then SimulateTrajectory should be recused and run again with the updated position and rotation value.
|
||||
SimulateTrajectory(position, rotation, positions, positionsSkip);
|
||||
return;
|
||||
}
|
||||
|
||||
Rotate(position, ref rotation);
|
||||
}
|
||||
positions.Add(position);
|
||||
|
||||
// The loop can stop when both the position and rotation have settled.
|
||||
if (m_MovementSettled && m_RotationSettled) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the object with the specified velocity and torque.
|
||||
/// </summary>
|
||||
/// <param name="velocity">The starting velocity.</param>
|
||||
/// <param name="torque">The starting torque.</param>
|
||||
/// <param name="originator">The object that instantiated the trajectory object.</param>
|
||||
public virtual void Initialize(Vector3 velocity, Vector3 torque, GameObject originator)
|
||||
{
|
||||
Initialize(velocity, torque, originator, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the object with the specified velocity and torque.
|
||||
/// </summary>
|
||||
/// <param name="velocity">The starting velocity.</param>
|
||||
/// <param name="torque">The starting torque.</param>
|
||||
/// <param name="originator">The object that instantiated the trajectory object.</param>
|
||||
/// <param name="originatorCollisionCheck">Should a collision check against the originator be performed?</param>
|
||||
public virtual void Initialize(Vector3 velocity, Vector3 torque, GameObject originator, bool originatorCollisionCheck)
|
||||
{
|
||||
Initialize(velocity, torque, originator, originatorCollisionCheck, Vector3.down);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the object with the specified velocity and torque.
|
||||
/// </summary>
|
||||
/// <param name="velocity">The starting velocity.</param>
|
||||
/// <param name="torque">The starting torque.</param>
|
||||
/// <param name="originator">The object that instantiated the trajectory object.</param>
|
||||
/// <param name="originatorCollisionCheck">Should a collision check against the originator be performed?</param>
|
||||
/// <param name="defaultNormalizedGravity">The normalized gravity direction if a character isn't specified for the originator.</param>
|
||||
public virtual void Initialize(Vector3 velocity, Vector3 torque, GameObject originator, bool originatorCollisionCheck, Vector3 defaultNormalizedGravity)
|
||||
{
|
||||
InitializeComponentReferences();
|
||||
|
||||
m_Velocity = velocity / m_Mass * m_StartVelocityMultiplier;
|
||||
m_Torque = torque;
|
||||
SetOriginator(originator, defaultNormalizedGravity);
|
||||
m_Gravity = m_NormalizedGravity * m_GravityMagnitude;
|
||||
m_OriginatorCollisionCheck = m_Originator != null;
|
||||
|
||||
m_Platform = null;
|
||||
m_MovementSettled = m_RotationSettled = false;
|
||||
m_InCollision = false;
|
||||
m_Collided = false;
|
||||
if (m_Collider != null) {
|
||||
m_Collider.enabled = true;
|
||||
}
|
||||
m_ActiveAudioClipSet.PlayAudioClip(m_GameObject, 0, true);
|
||||
enabled = true;
|
||||
|
||||
// Set the layer to prevent the current object from getting in the way of the casts.
|
||||
var previousLayer = m_GameObject.layer;
|
||||
m_GameObject.layer = LayerManager.IgnoreRaycast;
|
||||
|
||||
// The object could start in a collision state.
|
||||
if (originatorCollisionCheck && OverlapCast(m_Transform.position, m_Transform.rotation)) {
|
||||
OnCollision(null);
|
||||
if (m_CollisionMode == CollisionMode.Collide) {
|
||||
m_MovementSettled = m_RotationSettled = true;
|
||||
} else if (m_CollisionMode != CollisionMode.Ignore) { // Reflect and Random Reflection.
|
||||
// Update the velocity to the reflection direction. Use the originator's forward direction as the normal because the actual collision point is not determined.
|
||||
m_Velocity = Vector3.Reflect(m_Velocity, -m_OriginatorTransform.forward) * m_ReflectMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
m_GameObject.layer = previousLayer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the originator of the TrajectoryObject.
|
||||
/// </summary>
|
||||
/// <param name="originator">The originator that should be set.</param>
|
||||
/// <param name="defaultNormalizedGravity">The default gravity direction.</param>
|
||||
protected void SetOriginator(GameObject originator, Vector3 defaultNormalizedGravity)
|
||||
{
|
||||
if (m_Originator == originator) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (originator != null) {
|
||||
m_Originator = originator;
|
||||
m_OriginatorTransform = m_Originator.transform;
|
||||
m_OriginatorCharacterLocomotion = m_Originator.GetCachedComponent<UltimateCharacterLocomotion>();
|
||||
if (m_OriginatorCharacterLocomotion != null) {
|
||||
m_NormalizedGravity = m_OriginatorCharacterLocomotion.GravityDirection;
|
||||
m_TimeScale = m_OriginatorCharacterLocomotion.TimeScale;
|
||||
EventHandler.RegisterEvent<float>(m_Originator, "OnCharacterChangeTimeScale", OnChangeTimeScale);
|
||||
} else {
|
||||
m_NormalizedGravity = defaultNormalizedGravity;
|
||||
m_TimeScale = 1;
|
||||
}
|
||||
} else {
|
||||
m_NormalizedGravity = defaultNormalizedGravity;
|
||||
m_TimeScale = 1;
|
||||
m_OriginatorTransform = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retruns true if any objects are overlapping with the Trajectory Object.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the cast.</param>
|
||||
/// <param name="rotation">The rotation of the cast.</param>
|
||||
/// <returns>True if any objects are overlapping with the Trajectory Object.</returns>
|
||||
private bool OverlapCast(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
// No need to do a cast if the originator is null.
|
||||
if (m_OriginatorTransform == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int hit = 0;
|
||||
if (m_Collider is SphereCollider) {
|
||||
var sphereCollider = m_Collider as SphereCollider;
|
||||
hit = Physics.OverlapSphereNonAlloc(MathUtility.TransformPoint(position, m_Transform.rotation, sphereCollider.center),
|
||||
sphereCollider.radius * MathUtility.ColliderRadiusMultiplier(sphereCollider), m_ColliderHit, m_ImpactLayers, QueryTriggerInteraction.Ignore);
|
||||
} else if (m_Collider is CapsuleCollider) {
|
||||
var capsuleCollider = m_Collider as CapsuleCollider;
|
||||
Vector3 startEndCap, endEndCap;
|
||||
MathUtility.CapsuleColliderEndCaps(capsuleCollider, position, rotation, out startEndCap, out endEndCap);
|
||||
hit = Physics.OverlapCapsuleNonAlloc(startEndCap, endEndCap, capsuleCollider.radius * MathUtility.ColliderRadiusMultiplier(capsuleCollider), m_ColliderHit, m_ImpactLayers, QueryTriggerInteraction.Ignore);
|
||||
} else if (m_Collider is BoxCollider) {
|
||||
var boxCollider = m_Collider as BoxCollider;
|
||||
hit = Physics.OverlapBoxNonAlloc(MathUtility.TransformPoint(position, m_Transform.rotation, boxCollider.center), Vector3.Scale(boxCollider.size, boxCollider.transform.lossyScale) / 2, m_ColliderHit, m_Transform.rotation, m_ImpactLayers, QueryTriggerInteraction.Ignore);
|
||||
}
|
||||
|
||||
if (hit > 0) {
|
||||
// The TrajectoryObject is only in an overlap state if the object is overlapping a non-character or camera collider.
|
||||
for (int i = 0; i < hit; ++i) {
|
||||
if (!m_ColliderHit[i].transform.IsChildOf(m_OriginatorTransform)
|
||||
#if FIRST_PERSON_CONTROLLER
|
||||
// The object should not hit any colliders who are a child of the camera.
|
||||
&& m_ColliderHit[i].transform.gameObject.GetCachedParentComponent<FirstPersonController.Character.FirstPersonObjects>() == null
|
||||
#endif
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the local component references.
|
||||
/// </summary>
|
||||
protected void InitializeComponentReferences()
|
||||
{
|
||||
if (m_GameObject != null) {
|
||||
return;
|
||||
}
|
||||
m_GameObject = gameObject;
|
||||
m_Transform = transform;
|
||||
var colliders = GetComponents<Collider>();
|
||||
for (int i = 0; i < colliders.Length; ++i) {
|
||||
// The collider cannot be a triger.
|
||||
if (colliders[i].isTrigger) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The collider has to be of the correct type.
|
||||
if (!(colliders[i] is SphereCollider) && !(colliders[i] is CapsuleCollider) && !(colliders[i] is BoxCollider)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_Collider = colliders[i];
|
||||
break;
|
||||
}
|
||||
m_LineRenderer = GetComponent<LineRenderer>();
|
||||
m_ColliderHit = new Collider[m_MaxCollisionCount];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move and rotate the object according to a parabolic trajectory.
|
||||
/// </summary>
|
||||
protected virtual void FixedUpdate()
|
||||
{
|
||||
// Update the position.
|
||||
var position = m_Transform.position;
|
||||
var rotation = m_Transform.rotation;
|
||||
|
||||
// Set the layer to prevent the current object from getting in the way of the casts.
|
||||
var previousLayer = m_GameObject.layer;
|
||||
m_GameObject.layer = LayerManager.IgnoreRaycast;
|
||||
|
||||
if (!Move(ref position, rotation)) {
|
||||
// If the object collided with another object then Move should be called one more time so the reflected velocity is used.
|
||||
// If the second Move method is not called then the object would wait a tick before it is moved.
|
||||
Move(ref position, rotation);
|
||||
}
|
||||
|
||||
// The object may have been disabed within OnCollision. Do not do any more updates for a disabled object.
|
||||
if (enabled) {
|
||||
if (m_Platform != null) {
|
||||
position += (m_Platform.TransformPoint(m_PlatformRelativePosition) - position);
|
||||
m_PlatformRelativePosition = m_Platform.InverseTransformPoint(position);
|
||||
}
|
||||
m_Transform.position = position;
|
||||
|
||||
// Update the rotation.
|
||||
Rotate(position, ref rotation);
|
||||
if (m_Platform != null) {
|
||||
rotation *= (m_Platform.rotation * Quaternion.Inverse(m_PrevPlatformRotation));
|
||||
m_PrevPlatformRotation = m_Platform.rotation;
|
||||
}
|
||||
m_Transform.rotation = rotation;
|
||||
}
|
||||
|
||||
m_GameObject.layer = previousLayer;
|
||||
|
||||
// If both the position and rotation are done making changes then the component can be disabled.
|
||||
if (m_AutoDisable && m_MovementSettled && m_RotationSettled) {
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the object based on the current velocity.
|
||||
/// </summary>
|
||||
/// <param name="position">The current position of the object. Passed by reference so the updated position can be set.</param>
|
||||
/// <param name="rotation">The current rotation of the object.</param>
|
||||
/// <returns>True if the position was updated or the movement has settled.</returns>
|
||||
private bool Move(ref Vector3 position, Quaternion rotation)
|
||||
{
|
||||
// The object can't move if the movement and rotation has settled.
|
||||
if (m_MovementSettled && m_RotationSettled && m_SettleThreshold > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stop moving if the velocity is less than a minimum threshold and the object is on the ground.
|
||||
if (m_Velocity.sqrMagnitude < m_SettleThreshold && m_RotationSettled) {
|
||||
// The object should be on the ground before the object has settled.
|
||||
if (SingleCast(position, rotation, m_NormalizedGravity * c_ColliderSpacing)) {
|
||||
m_MovementSettled = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var deltaTime = m_TimeScale * Time.fixedDeltaTime * Time.timeScale;
|
||||
|
||||
// The object hasn't settled yet - move based on the velocity.
|
||||
m_Velocity += m_Gravity * deltaTime;
|
||||
m_Velocity *= Mathf.Clamp01(1 - m_Damping * deltaTime);
|
||||
|
||||
// If the object hits an object then it should either reflect off of that object or stop moving.
|
||||
var targetPosition = position + m_Velocity * m_Speed * deltaTime;
|
||||
var direction = targetPosition - position;
|
||||
if (SingleCast(position, rotation, direction)) {
|
||||
if (m_RaycastHit.transform.gameObject.layer == LayerManager.MovingPlatform) {
|
||||
if (m_RaycastHit.transform != m_Platform) {
|
||||
m_Platform = m_RaycastHit.transform;
|
||||
m_PlatformRelativePosition = m_Platform.InverseTransformPoint(position);
|
||||
m_PrevPlatformRotation = m_Platform.rotation;
|
||||
}
|
||||
} else {
|
||||
m_Platform = null;
|
||||
}
|
||||
|
||||
// If the object has settled but not disabled a collision will occur every frame. Prevent the effects from playing because of this.
|
||||
if (!m_InCollision) {
|
||||
m_InCollision = true;
|
||||
OnCollision(m_RaycastHit);
|
||||
}
|
||||
|
||||
if (m_CollisionMode == CollisionMode.Collide) {
|
||||
m_Velocity = Vector3.zero;
|
||||
m_Torque = Vector3.zero;
|
||||
m_MovementSettled = true;
|
||||
enabled = false;
|
||||
return true;
|
||||
} else if (m_CollisionMode != CollisionMode.Ignore) { // Reflect and Random Reflect.
|
||||
Vector3 velocity;
|
||||
if (m_CollisionMode == CollisionMode.RandomReflect) {
|
||||
// Add ramdomness to the bounce.
|
||||
// This mode should not be used over the network unless it doesn't matter if the object is synchronized (such as a shell).
|
||||
velocity = Quaternion.AngleAxis(Random.Range(-70, 70), m_RaycastHit.normal) * m_Velocity;
|
||||
} else { // Reflect.
|
||||
velocity = m_Velocity;
|
||||
}
|
||||
|
||||
// The bounce strenght is dependent on the physic material.
|
||||
var dynamicFrictionValue = m_Collider != null ? Mathf.Clamp01(1 - MathUtility.FrictionValue(m_Collider.material, m_RaycastHit.collider.material, true)) : 0;
|
||||
// Update the velocity to the reflection direction.
|
||||
m_Velocity = Vector3.Reflect(velocity, m_RaycastHit.normal) * dynamicFrictionValue * m_ReflectMultiplier;
|
||||
if (m_Velocity.magnitude < m_StartSidewaysVelocityMagnitude) {
|
||||
m_MovementSettled = true;
|
||||
}
|
||||
m_Collided = true;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
m_Platform = null;
|
||||
m_InCollision = false;
|
||||
}
|
||||
position = targetPosition;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object has collided with another object.
|
||||
/// </summary>
|
||||
/// <param name="hit">The RaycastHit of the object. Can be null.</param>
|
||||
protected virtual void OnCollision(RaycastHit? hit)
|
||||
{
|
||||
if (hit != null && hit.HasValue) {
|
||||
m_ActiveAudioClipSet.Stop(m_GameObject, 0);
|
||||
|
||||
// A Rigidbody should be affected by the impact.
|
||||
if (hit.Value.rigidbody != null) {
|
||||
hit.Value.rigidbody.AddForceAtPosition(m_Velocity, hit.Value.point);
|
||||
}
|
||||
|
||||
// An impact has occurred.
|
||||
if (m_SurfaceImpact != null) {
|
||||
SurfaceManager.SpawnEffect(hit.Value, m_SurfaceImpact, m_NormalizedGravity, m_TimeScale, m_GameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does a cast in in the specified direction.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the cast.</param>
|
||||
/// <param name="rotation">The rotation of the cast.</param>
|
||||
/// <param name="direction">The direction of the cast.</param>
|
||||
/// <returns>The number of hit results.</returns>
|
||||
protected virtual bool SingleCast(Vector3 position, Quaternion rotation, Vector3 direction)
|
||||
{
|
||||
var hit = false;
|
||||
if (m_Collider is SphereCollider) {
|
||||
var sphereCollider = m_Collider as SphereCollider;
|
||||
hit = Physics.SphereCast(position, sphereCollider.radius * MathUtility.ColliderRadiusMultiplier(sphereCollider), direction.normalized, out m_RaycastHit, direction.magnitude + c_ColliderSpacing, m_ImpactLayers, QueryTriggerInteraction.Ignore);
|
||||
} else if (m_Collider is CapsuleCollider) {
|
||||
var capsuleCollider = m_Collider as CapsuleCollider;
|
||||
Vector3 startEndCap, endEndCap;
|
||||
MathUtility.CapsuleColliderEndCaps(capsuleCollider, position, rotation, out startEndCap, out endEndCap);
|
||||
var radius = capsuleCollider.radius * MathUtility.ColliderRadiusMultiplier(capsuleCollider);
|
||||
hit = Physics.CapsuleCast(startEndCap, endEndCap, radius, direction.normalized, out m_RaycastHit, direction.magnitude + c_ColliderSpacing, m_ImpactLayers, QueryTriggerInteraction.Ignore);
|
||||
} else if (m_Collider is BoxCollider) {
|
||||
var boxCollider = m_Collider as BoxCollider;
|
||||
hit = Physics.BoxCast(m_Transform.TransformPoint(boxCollider.center), boxCollider.size / 4, direction.normalized, out m_RaycastHit, m_Transform.rotation, direction.magnitude + c_ColliderSpacing, m_ImpactLayers, QueryTriggerInteraction.Ignore);
|
||||
} else { // No collider attached.
|
||||
hit = Physics.Raycast(position, direction.normalized, out m_RaycastHit, direction.magnitude + c_ColliderSpacing, m_ImpactLayers, QueryTriggerInteraction.Ignore);
|
||||
}
|
||||
|
||||
// The object should not collide with the originator to prevent the character from hitting themself.
|
||||
if (m_OriginatorCollisionCheck && m_OriginatorTransform != null) {
|
||||
if (hit && (m_RaycastHit.transform.IsChildOf(m_OriginatorTransform)
|
||||
#if FIRST_PERSON_CONTROLLER
|
||||
// The object should not hit any colliders who are a child of the camera.
|
||||
|| m_RaycastHit.transform.gameObject.GetCachedParentComponent<FirstPersonController.Character.FirstPersonObjects>() != null
|
||||
#endif
|
||||
)) {
|
||||
hit = false;
|
||||
} else {
|
||||
m_OriginatorCollisionCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotate the object based on the current torque.
|
||||
/// </summary>
|
||||
/// <param name="position">The current position of the object.</param>
|
||||
/// <param name="rotation">The current rotation of the object. Passed by reference so the updated rotation can be set.</param>
|
||||
private void Rotate(Vector3 position, ref Quaternion rotation)
|
||||
{
|
||||
// The object should rotate to the desired direction after it has bounced and the rotation has settled.
|
||||
if ((m_CollisionMode == CollisionMode.Collide || m_Collided) && (m_Torque.sqrMagnitude < m_SettleThreshold || m_MovementSettled)) {
|
||||
if (m_Collider is CapsuleCollider || m_Collider is BoxCollider) {
|
||||
if (!m_RotationSettled) {
|
||||
var up = -m_NormalizedGravity;
|
||||
var normal = up;
|
||||
if (SingleCast(position, rotation, m_NormalizedGravity * c_ColliderSpacing)) {
|
||||
normal = m_RaycastHit.normal;
|
||||
}
|
||||
var dot = Mathf.Abs(Vector3.Dot(normal, rotation * Vector3.up));
|
||||
if (dot > 0.0001 && dot < 0.9999) {
|
||||
// Allow the object to be force rotated to a rotation based on the sideways settle threshold. This works well with bullet
|
||||
// shells to allow them to settle upright instead of always settling on their side.
|
||||
var localRotation = MathUtility.InverseTransformQuaternion(Quaternion.LookRotation(Vector3.forward, up), rotation).eulerAngles;
|
||||
if (!m_DeterminedRotation) {
|
||||
m_SettleSideways = dot < m_SidewaysSettleThreshold;
|
||||
m_DeterminedRotation = true;
|
||||
}
|
||||
localRotation.x = 0;
|
||||
if (m_SettleSideways) { // The collider should settle on its side.
|
||||
localRotation.z = Mathf.Abs(MathUtility.ClampInnerAngle(localRotation.z)) < 90 ? 0 : 180;
|
||||
} else { // The collider should settle upright.
|
||||
localRotation.z = MathUtility.ClampInnerAngle(localRotation.z) < 0 ? 270 : 90;
|
||||
}
|
||||
var target = MathUtility.TransformQuaternion(Quaternion.LookRotation(Vector3.forward, up), Quaternion.Euler(localRotation));
|
||||
var deltaTime = m_TimeScale * Time.fixedDeltaTime * Time.timeScale;
|
||||
rotation = Quaternion.Slerp(rotation, target, m_RotationSpeed * deltaTime);
|
||||
} else {
|
||||
// The object has finished rotating.
|
||||
m_Torque = Vector3.zero;
|
||||
m_RotationSettled = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_Torque = Vector3.zero;
|
||||
m_RotationSettled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the new rotation.
|
||||
if (m_RotateInMoveDirection && m_Velocity.sqrMagnitude > 0) {
|
||||
rotation = Quaternion.LookRotation(m_Velocity.normalized, -m_Gravity);
|
||||
}
|
||||
m_Torque *= Mathf.Clamp01(1 - m_RotationDamping);
|
||||
var targetRotation = rotation * Quaternion.Euler(m_Torque);
|
||||
|
||||
// Do not rotate if the collider would intersect with another object. A SphereCollider does not need this check.
|
||||
var hitCount = 0;
|
||||
if (m_Collider is CapsuleCollider) {
|
||||
Vector3 startEndCap, endEndCap;
|
||||
var capsuleCollider = m_Collider as CapsuleCollider;
|
||||
MathUtility.CapsuleColliderEndCaps(capsuleCollider, position, targetRotation, out startEndCap, out endEndCap);
|
||||
hitCount = Physics.OverlapCapsuleNonAlloc(startEndCap, endEndCap, capsuleCollider.radius * MathUtility.CapsuleColliderHeightMultiplier(capsuleCollider), m_ColliderHit, m_ImpactLayers, QueryTriggerInteraction.Ignore);
|
||||
} else if (m_Collider is BoxCollider) {
|
||||
var boxCollider = m_Collider as BoxCollider;
|
||||
hitCount = Physics.OverlapBoxNonAlloc(MathUtility.TransformPoint(position, m_Transform.rotation, boxCollider.center), Vector3.Scale(boxCollider.size, boxCollider.transform.lossyScale) / 2, m_ColliderHit, m_Transform.rotation, m_ImpactLayers, QueryTriggerInteraction.Ignore);
|
||||
}
|
||||
|
||||
// Apply the rotation if the rotation doesnt intersect any object.
|
||||
if (hitCount == 0) {
|
||||
rotation = targetRotation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the projectile from moving.
|
||||
/// </summary>
|
||||
protected void Stop()
|
||||
{
|
||||
m_Velocity = m_Torque = Vector3.zero;
|
||||
m_MovementSettled = m_RotationSettled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the torque value to the object.
|
||||
/// </summary>
|
||||
/// <param name="torque">The amount of torque to add.</param>
|
||||
protected void AddTorque(Vector3 torque)
|
||||
{
|
||||
m_Torque += torque;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a force to the object.
|
||||
/// </summary>
|
||||
/// <param name="force">The force to add to the object.</param>
|
||||
/// <param name="frames">The number of frames to add the force to. This is not used by the TrajectoryObject.</param>
|
||||
public void AddForce(Vector3 force, int frames)
|
||||
{
|
||||
AddForce(force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a force to the object.
|
||||
/// </summary>
|
||||
/// <param name="force">The force to add to the object.</param>
|
||||
public void AddForce(Vector3 force)
|
||||
{
|
||||
m_Velocity += (force / m_Mass) * m_ForceMultiplier;
|
||||
if (m_MovementSettled) {
|
||||
m_MovementSettled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The character's local timescale has changed.
|
||||
/// </summary>
|
||||
/// <param name="timeScale">The new timescale.</param>
|
||||
private void OnChangeTimeScale(float timeScale)
|
||||
{
|
||||
m_TimeScale = timeScale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The component has been disabled.
|
||||
/// </summary>
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
if (m_Originator != null) {
|
||||
EventHandler.UnregisterEvent<float>(m_Originator, "OnCharacterChangeTimeScale", OnChangeTimeScale);
|
||||
m_Originator = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf61090e160b840499607b4e4942727b
|
||||
timeCreated: 1505735668
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user