using UnityEngine; using System.Collections; using UnityEngine.Events; namespace RootMotion.FinalIK { /// /// Object than the InteractionSystem can interact with. /// [HelpURL("https://www.youtube.com/watch?v=r5jiZnsDH3M")] [AddComponentMenu("Scripts/RootMotion.FinalIK/Interaction System/Interaction Object")] public class InteractionObject : MonoBehaviour { // Open the User Manual URL [ContextMenu("User Manual")] void OpenUserManual() { Application.OpenURL("http://www.root-motion.com/finalikdox/html/page10.html"); } // Open the Script Reference URL [ContextMenu("Scrpt Reference")] void OpenScriptReference() { Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_interaction_object.html"); } // Open a video tutorial video [ContextMenu("TUTORIAL VIDEO (PART 1: BASICS)")] void OpenTutorial1() { Application.OpenURL("https://www.youtube.com/watch?v=r5jiZnsDH3M"); } // Open a video tutorial video [ContextMenu("TUTORIAL VIDEO (PART 2: PICKING UP...)")] void OpenTutorial2() { Application.OpenURL("https://www.youtube.com/watch?v=eP9-zycoHLk"); } // Open a video tutorial video [ContextMenu("TUTORIAL VIDEO (PART 3: ANIMATION)")] void OpenTutorial3() { Application.OpenURL("https://www.youtube.com/watch?v=sQfB2RcT1T4&index=14&list=PLVxSIA1OaTOu8Nos3CalXbJ2DrKnntMv6"); } // Open a video tutorial video [ContextMenu("TUTORIAL VIDEO (PART 4: TRIGGERS)")] void OpenTutorial4() { Application.OpenURL("https://www.youtube.com/watch?v=-TDZpNjt2mk&index=15&list=PLVxSIA1OaTOu8Nos3CalXbJ2DrKnntMv6"); } // Link to the Final IK Google Group [ContextMenu("Support Group")] void SupportGroup() { Application.OpenURL("https://groups.google.com/forum/#!forum/final-ik"); } // Link to the Final IK Asset Store thread in the Unity Community [ContextMenu("Asset Store Thread")] void ASThread() { Application.OpenURL("http://forum.unity3d.com/threads/final-ik-full-body-ik-aim-look-at-fabrik-ccd-ik-1-0-released.222685/"); } #region Main Interface /// /// Predefined interaction events for pausing, picking up, triggering animations and sending messages. /// [System.Serializable] public class InteractionEvent { /// /// The time of the event since interaction start. /// [Tooltip("The time of the event since interaction start.")] public float time; /// /// If true, the interaction will be paused on this event. The interaction can be resumed by InteractionSystem.ResumeInteraction() or InteractionSystem.ResumeAll; /// [Tooltip("If true, the interaction will be paused on this event. The interaction can be resumed by InteractionSystem.ResumeInteraction() or InteractionSystem.ResumeAll;")] public bool pause; /// /// If true, the object will be parented to the effector bone on this event. Note that picking up like this can be done by only a single effector at a time. /// If you wish to pick up an object with both hands, see the Interaction PickUp2Handed demo scene. /// [Tooltip("If true, the object will be parented to the effector bone on this event. Note that picking up like this can be done by only a single effector at a time. If you wish to pick up an object with both hands, see the Interaction PickUp2Handed demo scene.")] public bool pickUp; /// /// The animations called on this event. /// [Tooltip("The animations called on this event.")] public AnimatorEvent[] animations; /// /// The messages sent on this event using GameObject.SendMessage(). /// [Tooltip("The messages sent on this event using GameObject.SendMessage().")] public Message[] messages; [TooltipAttribute("The UnityEvent to invoke on this event.")] /// /// The UnityEvent to invoke on this event. /// public UnityEvent unityEvent; // Activates this event public void Activate(Transform t) { unityEvent.Invoke(); foreach (AnimatorEvent e in animations) e.Activate(pickUp); foreach (Message m in messages) m.Send(t); } } /// /// Definition of a message sent by an InteractionEvent. /// [System.Serializable] public class Message { /// /// The name of the function called. /// [Tooltip("The name of the function called.")] public string function; /// /// The recipient game object. /// [Tooltip("The recipient game object.")] public GameObject recipient; private const string empty = ""; // Sends the message to the recipient public void Send(Transform t) { if (recipient == null) return; if (function == string.Empty || function == empty) return; recipient.SendMessage(function, t, SendMessageOptions.RequireReceiver); } } /// /// Calls an animation on an interaction event. /// [System.Serializable] public class AnimatorEvent { /// /// The Animator component that will receive the AnimatorEvents. /// [Tooltip("The Animator component that will receive the AnimatorEvents.")] public Animator animator; /// /// The Animation component that will receive the AnimatorEvents (Legacy). /// [Tooltip("The Animation component that will receive the AnimatorEvents (Legacy).")] public Animation animation; /// /// The name of the animation state. /// [Tooltip("The name of the animation state.")] public string animationState; /// /// The crossfading time. /// [Tooltip("The crossfading time.")] public float crossfadeTime = 0.3f; /// /// The layer of the animation state (if using Legacy, the animation state will be forced to this layer). /// [Tooltip("The layer of the animation state (if using Legacy, the animation state will be forced to this layer).")] public int layer; /// /// Should the animation always start from 0 normalized time? /// [Tooltip("Should the animation always start from 0 normalized time?")] public bool resetNormalizedTime; private const string empty = ""; // Activate the animation public void Activate(bool pickUp) { if (animator != null) { // disable root motion because it may become a child of another Animator. Workaround for a Unity bug with an error message: "Transform.rotation on 'gameobject name' is no longer valid..." if (pickUp) animator.applyRootMotion = false; Activate(animator); } if (animation != null) Activate(animation); } // Activate a Mecanim animation private void Activate(Animator animator) { if (animationState == empty) return; if (resetNormalizedTime) animator.CrossFade(animationState, crossfadeTime, layer, 0f); else animator.CrossFade(animationState, crossfadeTime, layer); } // Activate a Legacy animation private void Activate(Animation animation) { if (animationState == empty) return; if (resetNormalizedTime) animation[animationState].normalizedTime = 0f; animation[animationState].layer = layer; animation.CrossFade(animationState, crossfadeTime); } } /// /// A Weight curve for various FBBIK channels. /// [System.Serializable] public class WeightCurve { /// /// The type of the weight curve /// [System.Serializable] public enum Type { PositionWeight, // IKEffector.positionWeight RotationWeight, // IKEffector.rotationWeight PositionOffsetX, // X offset from the interpolation direction relative to the character rotation PositionOffsetY, // Y offse from the interpolation direction relative to the character rotation PositionOffsetZ, // Z offset from the interpolation direction relative to the character rotation Pull, // FBIKChain.pull Reach, // FBIKChain.reach RotateBoneWeight, // Rotating the bone after FBBIK is finished Push, // FBIKChain.push PushParent, // FBIKChain.pushParent PoserWeight, // Weight of hand/generic Poser BendGoalWeight // Weight of the bend goal } /// /// The type of the curve (InteractionObject.WeightCurve.Type). /// [Tooltip("The type of the curve (InteractionObject.WeightCurve.Type).")] public Type type; /// /// The weight curve. /// [Tooltip("The weight curve.")] public AnimationCurve curve; // Evaluate the curve at the specified time public float GetValue(float timer) { return curve.Evaluate(timer); } } /// /// Multiplies a weight curve and uses the result for another FBBIK channel. (to reduce the amount of work with AnimationCurves) /// [System.Serializable] public class Multiplier { /// /// The curve type to multiply. /// [Tooltip("The curve type to multiply.")] public WeightCurve.Type curve; /// /// The multiplier of the curve's value. /// [Tooltip("The multiplier of the curve's value.")] public float multiplier = 1f; /// /// The resulting value will be applied to this channel. /// [Tooltip("The resulting value will be applied to this channel.")] public WeightCurve.Type result; // Get the multiplied value of the curve at the specified time public float GetValue(WeightCurve weightCurve, float timer) { return weightCurve.GetValue(timer) * multiplier; } } /// /// If the Interaction System has a 'Look At' LookAtIK component assigned, will use it to make the character look at the specified Transform. If unassigned, will look at this GameObject. /// [Tooltip("If the Interaction System has a 'Look At' LookAtIK component assigned, will use it to make the character look at the specified Transform. If unassigned, will look at this GameObject.")] public Transform otherLookAtTarget; /// /// The root Transform of the InteractionTargets. If null, will use this GameObject. GetComponentsInChildren() will be used at initiation to find all InteractionTargets associated with this InteractionObject. /// [Tooltip("The root Transform of the InteractionTargets. If null, will use this GameObject. GetComponentsInChildren() will be used at initiation to find all InteractionTargets associated with this InteractionObject.")] public Transform otherTargetsRoot; /// /// If assigned, all PositionOffset channels will be applied in the rotation space of this Transform. If not, they will be in the rotation space of the character. /// [Tooltip("If assigned, all PositionOffset channels will be applied in the rotation space of this Transform. If not, they will be in the rotation space of the character.")] public Transform positionOffsetSpace; /// /// The weight curves for the interaction. /// public WeightCurve[] weightCurves; /// /// The weight curve multipliers for the interaction. /// public Multiplier[] multipliers; /// /// The interaction events. /// public InteractionEvent[] events; /// /// Gets the length of the interaction (the longest curve). /// public float length { get; private set; } /// /// The last InteractionSystem that started an interaction with this InteractionObject. /// /// The last used interaction system. public InteractionSystem lastUsedInteractionSystem { get; private set; } /// /// Call if you have changed the curves in play mode or added/removed InteractionTargets. /// public void Initiate() { // Push length to the last weight curve key for (int i = 0; i < weightCurves.Length; i++) { if (weightCurves[i].curve.length > 0) { float l = weightCurves[i].curve.keys[weightCurves[i].curve.length - 1].time; length = Mathf.Clamp(length, l, length); } } // Push length to the last event time for (int i = 0; i < events.Length; i++) { length = Mathf.Clamp(length, events[i].time, length); } targets = targetsRoot.GetComponentsInChildren(); } /// /// Gets the look at target (returns otherLookAtTarget if not null). /// public Transform lookAtTarget { get { if (otherLookAtTarget != null) return otherLookAtTarget; return transform; } } /// /// Gets the InteractionTarget of the specified effector type and InteractionSystem tag. /// public InteractionTarget GetTarget(FullBodyBipedEffector effectorType, InteractionSystem interactionSystem) { if (string.IsNullOrEmpty(interactionSystem.tag)) { foreach (InteractionTarget target in targets) { if (target.effectorType == effectorType) return target; } return null; } foreach (InteractionTarget target in targets) { if (target.effectorType == effectorType && target.CompareTag(interactionSystem.tag)) return target; } return null; } #endregion Main Interface // Returns true if the specified WeightCurve.Type is used by this InteractionObject public bool CurveUsed(WeightCurve.Type type) { foreach (WeightCurve curve in weightCurves) { if (curve.type == type) return true; } foreach (Multiplier multiplier in multipliers) { if (multiplier.result == type) return true; } return false; } // Returns all the InteractionTargets of this object public InteractionTarget[] GetTargets() { return targets; } // Returns the InteractionTarget of effector type and tag public Transform GetTarget(FullBodyBipedEffector effectorType, string tag) { if (tag == string.Empty || tag == "") return GetTarget(effectorType); for (int i = 0; i < targets.Length; i++) { if (targets[i].effectorType == effectorType && targets[i].CompareTag(tag)) return targets[i].transform; } return transform; } // Called when interaction is started with this InteractionObject public void OnStartInteraction(InteractionSystem interactionSystem) { this.lastUsedInteractionSystem = interactionSystem; } // Applies the weight curves and multipliers to the FBBIK solver public void Apply(IKSolverFullBodyBiped solver, FullBodyBipedEffector effector, InteractionTarget target, float timer, float weight, bool isPaused) { for (int i = 0; i < weightCurves.Length; i++) { if (isPaused) { if (weightCurves[i].type == WeightCurve.Type.PositionOffsetX) continue; if (weightCurves[i].type == WeightCurve.Type.PositionOffsetY) continue; if (weightCurves[i].type == WeightCurve.Type.PositionOffsetZ) continue; } float mlp = target == null? 1f: target.GetValue(weightCurves[i].type); Apply(solver, effector, weightCurves[i].type, weightCurves[i].GetValue(timer), weight * mlp); } for (int i = 0; i < multipliers.Length; i++) { if (isPaused) { if (multipliers[i].result == WeightCurve.Type.PositionOffsetX) continue; if (multipliers[i].result == WeightCurve.Type.PositionOffsetY) continue; if (multipliers[i].result == WeightCurve.Type.PositionOffsetZ) continue; } if (multipliers[i].curve == multipliers[i].result) { if (!Warning.logged) Warning.Log("InteractionObject Multiplier 'Curve' " + multipliers[i].curve.ToString() + "and 'Result' are the same.", transform); } int curveIndex = GetWeightCurveIndex(multipliers[i].curve); if (curveIndex != -1) { float mlp = target == null? 1f: target.GetValue(multipliers[i].result); Apply(solver, effector, multipliers[i].result, multipliers[i].GetValue(weightCurves[curveIndex], timer), weight * mlp); } else { if (!Warning.logged) Warning.Log("InteractionObject Multiplier curve " + multipliers[i].curve.ToString() + "does not exist.", transform); } } } // Gets the value of a weight curve/multiplier public float GetValue(WeightCurve.Type weightCurveType, InteractionTarget target, float timer) { int index = GetWeightCurveIndex(weightCurveType); if (index != -1) { float mlp = target == null? 1f: target.GetValue(weightCurveType); return weightCurves[index].GetValue(timer) * mlp; } for (int i = 0; i < multipliers.Length; i++) { if (multipliers[i].result == weightCurveType) { int wIndex = GetWeightCurveIndex(multipliers[i].curve); if (wIndex != -1) { float mlp = target == null? 1f: target.GetValue(multipliers[i].result); return multipliers[i].GetValue(weightCurves[wIndex], timer) * mlp; } } } return 0f; } // Get the root Transform of the targets public Transform targetsRoot { get { if (otherTargetsRoot != null) return otherTargetsRoot; return transform; } } private InteractionTarget[] targets = new InteractionTarget[0]; // Initiate this Interaction Object void Start() { Initiate(); } // Apply the curve to the specified solver, effector, with the value and weight. private void Apply(IKSolverFullBodyBiped solver, FullBodyBipedEffector effector, WeightCurve.Type type, float value, float weight) { switch (type) { case WeightCurve.Type.PositionWeight: solver.GetEffector(effector).positionWeight = Mathf.Lerp(solver.GetEffector(effector).positionWeight, value, weight); return; case WeightCurve.Type.RotationWeight: solver.GetEffector(effector).rotationWeight = Mathf.Lerp(solver.GetEffector(effector).rotationWeight, value, weight); return; case WeightCurve.Type.PositionOffsetX: Vector3 xOffset = (positionOffsetSpace != null ? positionOffsetSpace.rotation : solver.GetRoot().rotation) * Vector3.right * value; solver.GetEffector(effector).position += xOffset * weight; //solver.GetEffector(effector).positionOffset += xOffset; return; case WeightCurve.Type.PositionOffsetY: Vector3 yOffset = (positionOffsetSpace != null ? positionOffsetSpace.rotation : solver.GetRoot().rotation) * Vector3.up * value; solver.GetEffector(effector).position += yOffset * weight; //solver.GetEffector(effector).positionOffset += yOffset; return; case WeightCurve.Type.PositionOffsetZ: Vector3 zOffset = (positionOffsetSpace != null ? positionOffsetSpace.rotation : solver.GetRoot().rotation) * Vector3.forward * value; solver.GetEffector(effector).position += zOffset * weight; //solver.GetEffector(effector).positionOffset += zOffset; return; case WeightCurve.Type.Pull: solver.GetChain(effector).pull = Mathf.Lerp(solver.GetChain(effector).pull, value, weight); return; case WeightCurve.Type.Reach: solver.GetChain(effector).reach = Mathf.Lerp(solver.GetChain(effector).reach, value, weight); return; case WeightCurve.Type.Push: solver.GetChain(effector).push = Mathf.Lerp(solver.GetChain(effector).push, value, weight); return; case WeightCurve.Type.PushParent: solver.GetChain(effector).pushParent = Mathf.Lerp(solver.GetChain(effector).pushParent, value, weight); return; case WeightCurve.Type.BendGoalWeight: solver.GetChain(effector).bendConstraint.weight = Mathf.Lerp(solver.GetChain(effector).bendConstraint.weight, value, weight); return; } } // Gets the interaction target Transform private Transform GetTarget(FullBodyBipedEffector effectorType) { for (int i = 0; i < targets.Length; i++) { if (targets[i].effectorType == effectorType) return targets[i].transform; } return transform; } // Get the index of a weight curve of type private int GetWeightCurveIndex(WeightCurve.Type weightCurveType) { for (int i = 0; i < weightCurves.Length; i++) { if (weightCurves[i].type == weightCurveType) return i; } return -1; } // Get the index of a multiplayer of type private int GetMultiplierIndex(WeightCurve.Type weightCurveType) { for (int i = 0; i < multipliers.Length; i++) { if (multipliers[i].result == weightCurveType) return i; } return -1; } } }