Skip to content

Instantly share code, notes, and snippets.

@samhains
Created April 15, 2019 18:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samhains/fecaf25018403e406168e5f52d0313c6 to your computer and use it in GitHub Desktop.
Save samhains/fecaf25018403e406168e5f52d0313c6 to your computer and use it in GitHub Desktop.
/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
using UnityEngine;
using Opsive.UltimateCharacterController.Game;
using Opsive.UltimateCharacterController.Events;
using Opsive.UltimateCharacterController.Traits;
using Opsive.UltimateCharacterController.Objects.CharacterAssist;
using Opsive.UltimateCharacterController.Utility;
namespace Opsive.UltimateCharacterController.Character.Abilities
{
/// <summary>
/// Interacts with another object within the scene. The object that the ability interacts with must have the Interact component added to it.
/// </summary>
[DefaultStartType(AbilityStartType.ButtonDown)]
[DefaultInputName("Action")]
[DefaultAbilityIndex(9)]
[DefaultAllowPositionalInput(false)]
[DefaultAllowRotationalInput(false)]
[AllowMultipleAbilityTypes]
public class Interact : DetectObjectAbilityBase
{
[Tooltip("The ID of the Interactable. A value of -1 indicates no ID.")]
[SerializeField] protected int m_InteractableID = -1;
[Tooltip("The value of the AbilityIntData animator parameter.")]
[SerializeField] protected int m_AbilityIntDataValue;
[Tooltip("Specifies if the ability should wait for the OnAnimatorInteract animation event or wait for the specified duration before interacting with the item.")]
[SerializeField] protected AnimationEventTrigger m_InteractEvent = new AnimationEventTrigger(false, 0.2f);
[Tooltip("Specifies if the ability should wait for the OnAnimatorInteractComplete animation event or wait for the specified duration before stopping the ability.")]
[SerializeField] protected AnimationEventTrigger m_InteractCompleteEvent = new AnimationEventTrigger(false, 0.2f);
public int InteractableID { get { return m_InteractableID; } set { m_InteractableID = value; } }
public int AbilityIntDataValue { get { return m_AbilityIntDataValue; } set { m_AbilityIntDataValue = value; } }
public AnimationEventTrigger InteractEvent { get { return m_InteractEvent; } set { m_InteractEvent = value; } }
public AnimationEventTrigger InteractCompleteEvent { get { return m_InteractCompleteEvent; } set { m_InteractCompleteEvent = value; } }
private CharacterIKBase m_CharacterIK;
private Interactable m_Interactable;
private ScheduledEventBase[] m_DisableIKInteractionEvents;
private bool m_HasInteracted;
private bool m_ExitedTrigger;
public override int AbilityIntData { get { return m_AbilityIntDataValue; } }
public override string AbilityMessageText
{
get
{
var message = m_AbilityMessageText;
if (m_Interactable != null) {
message = string.Format(message, m_Interactable.AbilityMessage());
}
return message;
}
set { base.AbilityMessageText = value; }
}
#if UNITY_EDITOR
public override string AbilityDescription { get { if (m_InteractableID != -1) { return "Interactable " + m_InteractableID; } return string.Empty; } }
#endif
/// <summary>
/// Initialize the default values.
/// </summary>
public override void Awake()
{
base.Awake();
m_CharacterIK = m_GameObject.GetCachedComponent<CharacterIKBase>();
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorInteract", DoInteract);
if (!m_InteractEvent.WaitForAnimationEvent)
{
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorInteractComplete", InteractComplete);
}
}
/// <summary>
/// Called when the ablity is tried to be started. If false is returned then the ability will not be started.
/// </summary>
/// <returns>True if the ability can be started.</returns>
public override bool CanStartAbility()
{
// The base class may prevent the ability from starting.
if (!base.CanStartAbility()) {
m_Interactable = null;
return false;
}
// The ability can't start if the Interactable isn't ready.
if (!m_Interactable.CanInteract(m_GameObject)) {
return false;
}
return true;
}
/// <summary>
/// Returns the possible AbilityStartLocations that the character can move towards.
/// </summary>
/// <returns>The possible AbilityStartLocations that the character can move towards.</returns>
public override AbilityStartLocation[] GetStartLocations()
{
return m_Interactable.gameObject.GetCachedComponents<AbilityStartLocation>();
}
/// <summary>
/// Validates the object to ensure it is valid for the current ability.
/// </summary>
/// <param name="obj">The object being validated.</param>
/// <param name="fromTrigger">Is the object being validated from a trigger?</param>
/// <returns>True if the object is valid. The object may not be valid if it doesn't have an ability-specific component attached.</returns>
protected override bool ValidateObject(GameObject obj, bool fromTrigger)
{
if (!base.ValidateObject(obj, fromTrigger)) {
return false;
}
if (m_Interactable != null) {
return obj == m_Interactable.gameObject;
}
// The object must have the Interactable component.
if ((m_Interactable = obj.GetCachedComponent<Interactable>()) != null) {
// If the ID is used then the IDs must match.
if (m_InteractableID != -1 && m_Interactable.ID != m_InteractableID) {
m_Interactable = null;
return false;
}
return true;
}
return false;
}
/// <summary>
/// Called when another ability is attempting to start and the current ability is active.
/// Returns true or false depending on if the new ability should be blocked from starting.
/// </summary>
/// <param name="startingAbility">The ability that is starting.</param>
/// <returns>True if the ability should be blocked.</returns>
public override bool ShouldBlockAbilityStart(Ability startingAbility)
{
return (startingAbility is Items.ItemAbility) || startingAbility.Index > Index || startingAbility is StoredInputAbilityBase;
}
/// <summary>
/// Called when the current ability is attempting to start and another ability is active.
/// Returns true or false depending on if the active ability should be stopped.
/// </summary>
/// <param name="activeAbility">The ability that is currently active.</param>
/// <returns>True if the ability should be stopped.</returns>
public override bool ShouldStopActiveAbility(Ability activeAbility)
{
return activeAbility is HeightChange || activeAbility is StoredInputAbilityBase;
}
/// <summary>
/// The ability has started.
/// </summary>
protected override void AbilityStarted()
{
base.AbilityStarted();
m_HasInteracted = false;
m_ExitedTrigger = false;
if (!m_InteractEvent.WaitForAnimationEvent) {
Scheduler.ScheduleFixed(m_InteractEvent.Duration, DoInteract);
}
// The interactable can move the limbs to a specific location.
if (m_CharacterIK != null) {
for (int i = 0; i < m_Interactable.IKTargets.Length; ++i) {
var ikTarget = m_Interactable.IKTargets[i];
if (ikTarget.Goal != CharacterIKBase.IKGoal.Last) {
Scheduler.ScheduleFixed<AbilityIKTarget, Transform>(ikTarget.Delay, SetIKTarget, ikTarget, ikTarget.Transform);
}
}
}
}
/// <summary>
/// Sets the IK target.
/// </summary>
/// <param name="ikTarget">The IK target that should be set.</param>
/// <param name="targetTransform">The transform target that should be set.</param>
private void SetIKTarget(AbilityIKTarget ikTarget, Transform targetTransform)
{
m_CharacterIK.SetAbilityIKTarget(targetTransform, ikTarget.Goal, ikTarget.InterpolationDuration);
// If the transform is not null then the end should be scheduled so it can be set to null.
if (targetTransform != null) {
if (m_DisableIKInteractionEvents == null) {
m_DisableIKInteractionEvents = new ScheduledEventBase[m_Interactable.IKTargets.Length];
} else if (m_DisableIKInteractionEvents.Length < m_Interactable.IKTargets.Length) {
System.Array.Resize(ref m_DisableIKInteractionEvents, m_Interactable.IKTargets.Length);
}
for (int i = 0; i < m_DisableIKInteractionEvents.Length; ++i) {
if (m_DisableIKInteractionEvents[i] == null) {
m_DisableIKInteractionEvents[i] = Scheduler.ScheduleFixed<AbilityIKTarget, Transform>(ikTarget.Duration, (AbilityIKTarget abilityIKTarget, Transform target) =>
{
SetIKTarget(ikTarget, target);
m_DisableIKInteractionEvents[i] = null;
}, ikTarget, null);
break;
}
}
}
}
/// <summary>
/// Interacts with the object.
/// </summary>
private void DoInteract()
{
if (!IsActive || m_HasInteracted) {
return;
}
m_Interactable.Interact(m_GameObject);
m_HasInteracted = true;
if (!m_InteractCompleteEvent.WaitForAnimationEvent) {
Debug.Log("Fixed");
Scheduler.ScheduleFixed(m_InteractCompleteEvent.Duration, InteractComplete);
}
}
/// <summary>
/// Completes the ability.
/// </summary>
private void InteractComplete()
{
Debug.Log("Interact complete event");
if (!IsActive) {
return;
}
StopAbility();
}
/// <summary>
/// The character has exited a trigger.
/// </summary>
/// <param name="other">The trigger collider that the character exited.</param>
/// <returns>Returns true if the entered object leaves the trigger.</returns>
protected override bool TriggerExit(Collider other)
{
if (IsActive) {
m_ExitedTrigger = true;
return false;
}
if (base.TriggerExit(other)) {
m_Interactable = null;
return true;
}
return false;
}
/// <summary>
/// The ability has stopped running.
/// </summary>
/// <param name="force">Was the ability force stopped?</param>
protected override void AbilityStopped(bool force)
{
base.AbilityStopped(force);
if (m_ExitedTrigger) {
m_Interactable = null;
m_DetectedTriggerObjectsCount = 0;
m_DetectedObject = null;
m_ExitedTrigger = false;
}
// The ability may end before the interaction duration has elapsed.
if (m_DisableIKInteractionEvents != null) {
for (int i = 0; i < m_DisableIKInteractionEvents.Length; ++i) {
if (m_DisableIKInteractionEvents[i] == null) {
continue;
}
m_DisableIKInteractionEvents[i].Invoke();
Scheduler.Cancel(m_DisableIKInteractionEvents[i]);
m_DisableIKInteractionEvents[i] = null;
}
}
}
/// <summary>
/// The object has been destroyed.
/// </summary>
public override void OnDestroy()
{
base.OnDestroy();
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorInteract", DoInteract);
if (!m_InteractEvent.WaitForAnimationEvent)
{
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorInteractComplete", InteractComplete);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment