This commit is contained in:
2026-06-14 23:57:44 +07:00
parent 20f9010787
commit 78d7b2f5a7
5775 changed files with 4796241 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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