initial upload

This commit is contained in:
tom.hempel
2025-09-21 22:42:26 +02:00
commit d03bcd4ba5
6231 changed files with 351582 additions and 0 deletions

View File

@ -0,0 +1,534 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using RootMotion;
namespace RootMotion.FinalIK {
/// <summary>
/// Managing Interactions for a single FBBIK effector.
/// </summary>
[System.Serializable]
public class InteractionEffector {
// The type of the effector
public FullBodyBipedEffector effectorType { get; private set; }
// Has the interaction been paused?
public bool isPaused { get; private set; }
// The current InteractionObject (null if there is no interaction going on)
public InteractionObject interactionObject { get; private set; }
// Is this InteractionEffector currently in the middle of an interaction?
public bool inInteraction { get { return interactionObject != null; }}
// Internal values
private Poser poser;
private IKEffector effector;
private float timer, length, weight, fadeInSpeed, defaultPositionWeight, defaultRotationWeight, defaultPull, defaultReach, defaultPush, defaultPushParent, defaultBendGoalWeight, defaultPoserWeight, resetTimer;
private float prevPositionWeight, prevRotationWeight, prevRotateBoneWeight, prevPull, prevReach, prevPush, prevPushParent, prevBendGoalWeight, prevPoserWeight, switchTimer;
private bool positionWeightUsed, rotationWeightUsed, pullUsed, reachUsed, pushUsed, pushParentUsed, bendGoalWeightUsed, poserUsed;
private bool prevPositionWeightUsed, prevRotationWeightUsed, prevRotateBoneWeightUsed, prevPullUsed, prevReachUsed, prevPushUsed, prevPushParentUsed, prevBendGoalWeightUsed, prevPoserUsed;
private bool pickedUp, defaults, pickUpOnPostFBBIK;
private Vector3 pickUpPosition, pausePositionRelative, prevTargetPosition;
private Quaternion pickUpRotation, pauseRotationRelative, prevTargetRotation = Quaternion.identity, prevRotateBoneValue = Quaternion.identity;
private InteractionTarget interactionTarget;
private Transform target;
private List<bool> triggered = new List<bool>();
private InteractionSystem interactionSystem;
private bool started;
private bool isSwitching;
// The custom constructor
public InteractionEffector (FullBodyBipedEffector effectorType) {
this.effectorType = effectorType;
}
// Initiate this, get the default values
public void Initiate(InteractionSystem interactionSystem) {
this.interactionSystem = interactionSystem;
// Find the effector if we haven't already
effector = interactionSystem.ik.solver.GetEffector(effectorType);
poser = effector.bone.GetComponent<Poser>();
StoreDefaults();
}
/// <summary>
/// Store the default values to which the effector will be reset to after an interaction has ended.
/// </summary>
public void StoreDefaults() {
if (interactionSystem == null) return;
defaultPositionWeight = interactionSystem.ik.solver.GetEffector(effectorType).positionWeight;
defaultRotationWeight = interactionSystem.ik.solver.GetEffector(effectorType).rotationWeight;
defaultPoserWeight = poser != null? poser.weight: 0f;
defaultPull = interactionSystem.ik.solver.GetChain(effectorType).pull;
defaultReach = interactionSystem.ik.solver.GetChain(effectorType).reach;
defaultPush = interactionSystem.ik.solver.GetChain(effectorType).push;
defaultPushParent = interactionSystem.ik.solver.GetChain(effectorType).pushParent;
defaultBendGoalWeight = interactionSystem.ik.solver.GetChain(effectorType).bendConstraint.weight;
}
/// <summary>
/// Store the current interaction values from which to interpolate to the next interaction
/// </summary>
public void StorePrevious(InteractionObject interactionObject)
{
if (interactionSystem == null) return;
// See which InteractionObject.WeightCurve.Types are used
prevTargetPosition = effector.position;
prevTargetRotation = effector.rotation;
prevPositionWeightUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.PositionWeight);
prevRotationWeightUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.RotationWeight);
prevRotateBoneWeightUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.RotateBoneWeight);
prevPullUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.Pull);
prevReachUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.Reach);
prevPushUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.Push);
prevPushParentUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.PushParent);
prevBendGoalWeightUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.BendGoalWeight);
prevPoserUsed = poser != null && interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.PoserWeight);
prevPositionWeight = interactionSystem.ik.solver.GetEffector(effectorType).positionWeight;
prevRotationWeight = interactionSystem.ik.solver.GetEffector(effectorType).rotationWeight;
prevPoserWeight = poser != null ? poser.weight : 0f;
prevPull = interactionSystem.ik.solver.GetChain(effectorType).pull;
prevReach = interactionSystem.ik.solver.GetChain(effectorType).reach;
prevPush = interactionSystem.ik.solver.GetChain(effectorType).push;
prevPushParent = interactionSystem.ik.solver.GetChain(effectorType).pushParent;
prevBendGoalWeight = interactionSystem.ik.solver.GetChain(effectorType).bendConstraint.weight;
switchTimer = 0f;
isSwitching = true;
}
// Interpolate from previous to current interaction
public bool Switch(float speed, float deltaTime)
{
//if (!inInteraction) return false;
//if (isPaused) return false;
if (!isSwitching) return false;
switchTimer = Mathf.MoveTowards(switchTimer, 1f, deltaTime * speed);
// Pull and Reach
if (effector.isEndEffector)
{
if (prevPullUsed) interactionSystem.ik.solver.GetChain(effectorType).pull = Mathf.Lerp(prevPull, interactionSystem.ik.solver.GetChain(effectorType).pull, switchTimer);
if (prevReachUsed) interactionSystem.ik.solver.GetChain(effectorType).reach = Mathf.Lerp(prevReach, interactionSystem.ik.solver.GetChain(effectorType).reach, switchTimer);
if (prevPushUsed) interactionSystem.ik.solver.GetChain(effectorType).push = Mathf.Lerp(prevPush, interactionSystem.ik.solver.GetChain(effectorType).push, switchTimer);
if (prevPushParentUsed) interactionSystem.ik.solver.GetChain(effectorType).pushParent = Mathf.Lerp(prevPushParent, interactionSystem.ik.solver.GetChain(effectorType).pushParent, switchTimer);
if (prevBendGoalWeightUsed) interactionSystem.ik.solver.GetChain(effectorType).bendConstraint.weight = Mathf.Lerp(prevBendGoalWeight, interactionSystem.ik.solver.GetChain(effectorType).bendConstraint.weight, switchTimer);
}
// Effector weights
if (prevPositionWeightUsed)
{
effector.positionWeight = Mathf.Lerp(prevPositionWeight, effector.positionWeight, switchTimer);
effector.position = Vector3.Lerp(prevTargetPosition, effector.position, switchTimer);
}
if (prevRotationWeightUsed)
{
effector.rotationWeight = Mathf.Lerp(prevRotationWeight, effector.rotationWeight, switchTimer);
effector.rotation = Quaternion.Lerp(prevTargetRotation, effector.rotation, switchTimer);
}
if (switchTimer >= 1f)
{
prevPullUsed = false;
prevReachUsed = false;
prevPushUsed = false;
prevPushParentUsed = false;
prevPositionWeightUsed = false;
prevRotationWeightUsed = false;
prevBendGoalWeightUsed = false;
prevPoserUsed = false;
isSwitching = false;
}
return true;
}
// Interpolate to default values when currently not in interaction
public bool ResetToDefaults(float speed, float deltaTime) {
if (inInteraction) return false;
if (isPaused) return false;
if (defaults) return false;
resetTimer = Mathf.MoveTowards(resetTimer, 0f, deltaTime * speed);
// Pull and Reach
if (effector.isEndEffector) {
if (pullUsed) interactionSystem.ik.solver.GetChain(effectorType).pull = Mathf.Lerp(defaultPull, interactionSystem.ik.solver.GetChain(effectorType).pull, resetTimer);
if (reachUsed) interactionSystem.ik.solver.GetChain(effectorType).reach = Mathf.Lerp(defaultReach, interactionSystem.ik.solver.GetChain(effectorType).reach, resetTimer);
if (pushUsed) interactionSystem.ik.solver.GetChain(effectorType).push = Mathf.Lerp(defaultPush, interactionSystem.ik.solver.GetChain(effectorType).push, resetTimer);
if (pushParentUsed) interactionSystem.ik.solver.GetChain(effectorType).pushParent = Mathf.Lerp(defaultPushParent, interactionSystem.ik.solver.GetChain(effectorType).pushParent, resetTimer);
if (bendGoalWeightUsed) interactionSystem.ik.solver.GetChain(effectorType).bendConstraint.weight = Mathf.Lerp(defaultBendGoalWeight, interactionSystem.ik.solver.GetChain(effectorType).bendConstraint.weight, resetTimer);
}
// Effector weights
if (positionWeightUsed) effector.positionWeight = Mathf.Lerp(defaultPositionWeight, effector.positionWeight, resetTimer);
if (rotationWeightUsed) effector.rotationWeight = Mathf.Lerp(defaultRotationWeight, effector.rotationWeight, resetTimer);
if (resetTimer <= 0f) {
pullUsed = false;
reachUsed = false;
pushUsed = false;
pushParentUsed = false;
positionWeightUsed = false;
rotationWeightUsed = false;
bendGoalWeightUsed = false;
poserUsed = false;
defaults = true;
}
return true;
}
// Pause this interaction
public bool Pause() {
if (!inInteraction) return false;
isPaused = true;
pausePositionRelative = target.InverseTransformPoint(effector.position);
pauseRotationRelative = Quaternion.Inverse(target.rotation) * effector.rotation;
if (interactionSystem.OnInteractionPause != null) {
interactionSystem.OnInteractionPause(effectorType, interactionObject);
}
return true;
}
// Resume a paused interaction
public bool Resume() {
if (!inInteraction) return false;
isPaused = false;
if (interactionSystem.OnInteractionResume != null) interactionSystem.OnInteractionResume(effectorType, interactionObject);
return true;
}
// Start interaction
public bool Start(InteractionObject interactionObject, string tag, float fadeInTime, bool interrupt)
{
if (inInteraction && !interrupt) return false;
// Get the InteractionTarget
InteractionTarget interactionTarget = null;
target = interactionObject.GetTarget(effectorType, tag);
if (target != null) interactionTarget = target.GetComponent<InteractionTarget>();
return Start(interactionObject, interactionTarget, fadeInTime, interrupt);
}
public bool Start(InteractionObject interactionObject, InteractionTarget interactionTarget, float fadeInTime, bool interrupt)
{
// If not in interaction, set effector positions to their bones
if (!inInteraction)
{
effector.position = effector.bone.position;
effector.rotation = effector.bone.rotation;
}
else
{
if (!interrupt)
{
return false;
}
else
{
defaults = false;
StorePrevious(interactionObject);
}
}
this.interactionTarget = interactionTarget;
target = interactionTarget != null? interactionTarget.transform: interactionObject.transform;
// Start the interaction
this.interactionObject = interactionObject;
if (interactionSystem.OnInteractionStart != null) interactionSystem.OnInteractionStart(effectorType, interactionObject);
interactionObject.OnStartInteraction(interactionSystem);
// Cleared triggered events
triggered.Clear();
for (int i = 0; i < interactionObject.events.Length; i++)
{
triggered.Add(false);
}
// See which InteractionObject.WeightCurve.Types are used
positionWeightUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.PositionWeight);
rotationWeightUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.RotationWeight);
pullUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.Pull);
reachUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.Reach);
pushUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.Push);
pushParentUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.PushParent);
bendGoalWeightUsed = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.BendGoalWeight);
poserUsed = poser != null && interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.PoserWeight);
// Posing the hand/foot
if (poser != null && poserUsed)
{
if (poser.poseRoot == null) poser.weight = 0f;
if (interactionTarget != null)
{
if (interactionTarget.usePoser)
{
poser.SetPoseRoot(target.transform, interactionTarget.bones, interactionSystem.switchInteractionSpeed);
}
} else
{
poser.SetPoseRoot(null, null, interactionSystem.switchInteractionSpeed);
}
poser.AutoMapping();
}
if (defaults) StoreDefaults();
// Reset internal values
timer = 0f;
weight = 0f;
fadeInSpeed = fadeInTime > 0f ? 1f / fadeInTime : 1000f;
length = interactionObject.length;
isPaused = false;
pickedUp = false;
pickUpPosition = Vector3.zero;
pickUpRotation = Quaternion.identity;
if (interactionTarget != null) interactionTarget.RotateTo(effector.bone);
started = true;
return true;
}
// Update the (possibly) ongoing interaction
public void Update(Transform root, float speed, float deltaTime) {
if (!inInteraction) {
// If the InteractionObject has been destroyed, reset to defaults
if (started) {
isPaused = false;
pickedUp = false;
defaults = false;
resetTimer = 1f;
started = false;
}
return;
}
// Rotate target
if (interactionTarget != null && !interactionTarget.rotateOnce) interactionTarget.RotateTo(effector.bone);
if (isPaused) {
if (!pickedUp)
{
effector.position = target.TransformPoint(pausePositionRelative);
effector.rotation = target.rotation * pauseRotationRelative;
}
// Apply the current interaction state to the solver
interactionObject.Apply(interactionSystem.ik.solver, effectorType, interactionTarget, timer, weight, true);
return;
}
// Advance the interaction timer and weight
timer += deltaTime * speed * (interactionTarget != null? interactionTarget.interactionSpeedMlp: 1f);
weight = Mathf.Clamp(weight + deltaTime * fadeInSpeed * speed, 0f, 1f);
// Interaction events
bool pickUp = false;
bool pause = false;
TriggerUntriggeredEvents(true, out pickUp, out pause);
// Effector target positions and rotations
Vector3 targetPosition = pickedUp? interactionSystem.transform.TransformPoint(pickUpPosition): target.position;
Quaternion targetRotation = pickedUp? interactionSystem.transform.rotation * pickUpRotation: target.rotation;
// Interpolate effector position and rotation
effector.position = Vector3.Lerp(effector.bone.position, targetPosition, weight);
effector.rotation = Quaternion.Lerp(effector.bone.rotation, targetRotation, weight);
// Apply the current interaction state to the solver
interactionObject.Apply(interactionSystem.ik.solver, effectorType, interactionTarget, timer, weight, false);
if (pickUp) PickUp(root);
if (pause) Pause();
// Hand poser weight
float poserWeight = interactionObject.GetValue (InteractionObject.WeightCurve.Type.PoserWeight, interactionTarget, timer);
if (poser != null && poserUsed) {
poser.weight = Mathf.Lerp (poser.weight, poserWeight, weight);
} else {
if (poserWeight > 0f) {
Warning.Log("InteractionObject " + interactionObject.name + " has a curve/multipler for Poser Weight, but the bone of effector " + effectorType.ToString() + " has no HandPoser/GenericPoser attached.", effector.bone);
}
}
if (timer >= length) Stop();
}
// Get the normalized progress of the interaction
public float progress {
get {
if (!inInteraction) return 0f;
if (length == 0f) return 0f;
return timer / length;
}
}
// Go through all the InteractionObject events to trigger the ones that have not yet triggered
private void TriggerUntriggeredEvents(bool checkTime, out bool pickUp, out bool pause) {
pickUp = false;
pause = false;
for (int i = 0; i < triggered.Count; i++) {
// If this event has not been triggered by this effector
if (!triggered[i]) {
// If time has passed...
if (!checkTime || interactionObject.events[i].time < timer) {
// Activate the event
interactionObject.events[i].Activate(effector.bone);
// Picking up
if (interactionObject.events[i].pickUp) {
if (timer >= interactionObject.events[i].time) timer = interactionObject.events[i].time;
pickUp = true;
}
// Pausing
if (interactionObject.events[i].pause) {
if (timer >= interactionObject.events[i].time) timer = interactionObject.events[i].time;
pause = true;
}
if (interactionSystem.OnInteractionEvent != null) interactionSystem.OnInteractionEvent(effectorType, interactionObject, interactionObject.events[i]);
triggered[i] = true;
}
}
}
}
// Trigger the interaction object
private void PickUp(Transform root) {
// Picking up the object
pickUpPosition = root.InverseTransformPoint(effector.position);
pickUpRotation = Quaternion.Inverse(interactionSystem.transform.rotation) * effector.rotation;
pickUpOnPostFBBIK = true;
pickedUp = true;
var rigidbody = interactionObject.targetsRoot.GetComponent<Rigidbody>();
if (rigidbody != null) {
if (!rigidbody.isKinematic) {
rigidbody.isKinematic = true;
}
// Ignore collisions between the character and the colliders of the interaction object
var rootCollider = root.GetComponent<Collider>();
if (rootCollider != null) {
var colliders = interactionObject.targetsRoot.GetComponentsInChildren<Collider>();
foreach (Collider collider in colliders) {
if (!collider.isTrigger && collider.enabled) Physics.IgnoreCollision(rootCollider, collider);
}
}
}
if (interactionSystem.OnInteractionPickUp != null) interactionSystem.OnInteractionPickUp(effectorType, interactionObject);
}
// Stop the interaction
public bool Stop() {
if (!inInteraction) return false;
bool pickUp = false;
bool pause = false;
TriggerUntriggeredEvents(false, out pickUp, out pause);
if (interactionSystem.OnInteractionStop != null) interactionSystem.OnInteractionStop(effectorType, interactionObject);
// Reset the interaction target
if (interactionTarget != null) interactionTarget.ResetRotation();
// Reset the internal values
interactionObject = null;
weight = 0f;
timer = 0f;
isPaused = false;
target = null;
defaults = false;
resetTimer = 1f;
//if (poser != null && !pickedUp) poser.weight = 0f;
pickedUp = false;
started = false;
return true;
}
// Called after FBBIK update
public void OnPostFBBIK() {
if (!inInteraction) return;
// Rotate the hands/feet to the RotateBoneWeight curve
float rotateBoneWeight = interactionObject.GetValue(InteractionObject.WeightCurve.Type.RotateBoneWeight, interactionTarget, timer) * weight;
if (isSwitching)
{
rotateBoneWeight = Mathf.Lerp(prevRotateBoneWeight, rotateBoneWeight, switchTimer);
} else
{
prevRotateBoneWeight = rotateBoneWeight;
}
if (rotateBoneWeight > 0f) {
Quaternion r = pickedUp? interactionSystem.transform.rotation * pickUpRotation: effector.rotation;
Quaternion targetRotation = Quaternion.Slerp(effector.bone.rotation, r, rotateBoneWeight * rotateBoneWeight);
effector.bone.localRotation = Quaternion.Inverse(effector.bone.parent.rotation) * targetRotation;
}
if (isSwitching)
{
effector.bone.localRotation = Quaternion.Slerp(prevRotateBoneValue, effector.bone.localRotation, switchTimer);
}
else
{
prevRotateBoneValue = effector.bone.localRotation;
}
// Positioning the interaction object to the effector (not the bone, because it is still at its animated translation)
if (pickUpOnPostFBBIK) {
Vector3 bonePosition = effector.bone.position;
effector.bone.position = interactionSystem.transform.TransformPoint(pickUpPosition);
interactionObject.targetsRoot.parent = effector.bone;
effector.bone.position = bonePosition;
pickUpOnPostFBBIK = false;
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 4046d96b4054144c0b0fe262b112932c
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,100 @@
using UnityEngine;
using System.Collections;
using RootMotion;
namespace RootMotion.FinalIK {
/// <summary>
/// Controls LookAtIK for the InteractionSystem
/// </summary>
[System.Serializable]
public class InteractionLookAt {
// (Optional) reference to the LookAtIK component that will be used to make the character look at the objects that it is interacting with.
[Tooltip("(Optional) reference to the LookAtIK component that will be used to make the character look at the objects that it is interacting with.")]
public LookAtIK ik;
/// <summary>
/// Interpolation speed of the LookAtIK target.
/// </summary>
[Tooltip("Interpolation speed of the LookAtIK target.")]
public float lerpSpeed = 5f;
/// <summary>
/// Interpolation speed of the LookAtIK weight.
/// </summary>
[Tooltip("Interpolation speed of the LookAtIK weight.")]
public float weightSpeed = 1f;
/// <summary>
/// Look the specified target for the specified time.
/// </summary>
public void Look(Transform target, float time) {
if (ik == null) return;
if (ik.solver.IKPositionWeight <= 0f) ik.solver.IKPosition = ik.solver.GetRoot().position + ik.solver.GetRoot().forward * 3f;
lookAtTarget = target;
stopLookTime = time;
}
[HideInInspector] public bool isPaused;
private Transform lookAtTarget; // The target Transform to look at
private float stopLookTime; // Time to start fading out the LookAtIK
private float weight; // Current weight
private bool firstFBBIKSolve; // Has the FBBIK already solved for this frame? In case it is solved more than once, for example when using the ShoulderRotator
public void OnFixTransforms() {
if (ik == null) return;
if (ik.fixTransforms) ik.solver.FixTransforms();
}
public void Update() {
if (ik == null) return;
if (ik.enabled) ik.enabled = false;
if (lookAtTarget == null) return;
if (isPaused) stopLookTime += Time.deltaTime;
// Interpolate the weight
float add = Time.time < stopLookTime? weightSpeed: -weightSpeed;
weight = Mathf.Clamp(weight + add * Time.deltaTime, 0f, 1f);
// Set LookAtIK weight
ik.solver.IKPositionWeight = Interp.Float(weight, InterpolationMode.InOutQuintic);
// Set LookAtIK position
ik.solver.IKPosition = Vector3.Lerp(ik.solver.IKPosition, lookAtTarget.position, lerpSpeed * Time.deltaTime);
// Release the LookAtIK for other tasks once we're weighed out
if (weight <= 0f) lookAtTarget = null;
firstFBBIKSolve = true;
}
public void SolveSpine() {
if (ik == null) return;
if (!firstFBBIKSolve) return;
float headWeight = ik.solver.headWeight;
float eyesWeight = ik.solver.eyesWeight;
ik.solver.headWeight = 0f;
ik.solver.eyesWeight = 0f;
ik.solver.Update();
ik.solver.headWeight = headWeight;
ik.solver.eyesWeight = eyesWeight;
}
public void SolveHead() {
if (ik == null) return;
if (!firstFBBIKSolve) return;
float bodyWeight = ik.solver.bodyWeight;
ik.solver.bodyWeight = 0f;
ik.solver.Update();
ik.solver.bodyWeight = bodyWeight;
firstFBBIKSolve = false;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 277df98b5c4b44c71bf8c728da72f815
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,548 @@
using UnityEngine;
using System.Collections;
using UnityEngine.Events;
namespace RootMotion.FinalIK {
/// <summary>
/// Object than the InteractionSystem can interact with.
/// </summary>
[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
/// <summary>
/// Predefined interaction events for pausing, picking up, triggering animations and sending messages.
/// </summary>
[System.Serializable]
public class InteractionEvent {
/// <summary>
/// The time of the event since interaction start.
/// </summary>
[Tooltip("The time of the event since interaction start.")]
public float time;
/// <summary>
/// If true, the interaction will be paused on this event. The interaction can be resumed by InteractionSystem.ResumeInteraction() or InteractionSystem.ResumeAll;
/// </summary>
[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;
/// <summary>
/// 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.
/// </summary>
[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;
/// <summary>
/// The animations called on this event.
/// </summary>
[Tooltip("The animations called on this event.")]
public AnimatorEvent[] animations;
/// <summary>
/// The messages sent on this event using GameObject.SendMessage().
/// </summary>
[Tooltip("The messages sent on this event using GameObject.SendMessage().")]
public Message[] messages;
[TooltipAttribute("The UnityEvent to invoke on this event.")]
/// <summary>
/// The UnityEvent to invoke on this event.
/// </summary>
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);
}
}
/// <summary>
/// Definition of a message sent by an InteractionEvent.
/// </summary>
[System.Serializable]
public class Message {
/// <summary>
/// The name of the function called.
/// </summary>
[Tooltip("The name of the function called.")]
public string function;
/// <summary>
/// The recipient game object.
/// </summary>
[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);
}
}
/// <summary>
/// Calls an animation on an interaction event.
/// </summary>
[System.Serializable]
public class AnimatorEvent {
/// <summary>
/// The Animator component that will receive the AnimatorEvents.
/// </summary>
[Tooltip("The Animator component that will receive the AnimatorEvents.")]
public Animator animator;
/// <summary>
/// The Animation component that will receive the AnimatorEvents (Legacy).
/// </summary>
[Tooltip("The Animation component that will receive the AnimatorEvents (Legacy).")]
public Animation animation;
/// <summary>
/// The name of the animation state.
/// </summary>
[Tooltip("The name of the animation state.")]
public string animationState;
/// <summary>
/// The crossfading time.
/// </summary>
[Tooltip("The crossfading time.")]
public float crossfadeTime = 0.3f;
/// <summary>
/// The layer of the animation state (if using Legacy, the animation state will be forced to this layer).
/// </summary>
[Tooltip("The layer of the animation state (if using Legacy, the animation state will be forced to this layer).")]
public int layer;
/// <summary>
/// Should the animation always start from 0 normalized time?
/// </summary>
[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);
}
}
/// <summary>
/// A Weight curve for various FBBIK channels.
/// </summary>
[System.Serializable]
public class WeightCurve {
/// <summary>
/// The type of the weight curve
/// </summary>
[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
}
/// <summary>
/// The type of the curve (InteractionObject.WeightCurve.Type).
/// </summary>
[Tooltip("The type of the curve (InteractionObject.WeightCurve.Type).")]
public Type type;
/// <summary>
/// The weight curve.
/// </summary>
[Tooltip("The weight curve.")]
public AnimationCurve curve;
// Evaluate the curve at the specified time
public float GetValue(float timer) {
return curve.Evaluate(timer);
}
}
/// <summary>
/// Multiplies a weight curve and uses the result for another FBBIK channel. (to reduce the amount of work with AnimationCurves)
/// </summary>
[System.Serializable]
public class Multiplier {
/// <summary>
/// The curve type to multiply.
/// </summary>
[Tooltip("The curve type to multiply.")]
public WeightCurve.Type curve;
/// <summary>
/// The multiplier of the curve's value.
/// </summary>
[Tooltip("The multiplier of the curve's value.")]
public float multiplier = 1f;
/// <summary>
/// The resulting value will be applied to this channel.
/// </summary>
[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;
}
}
/// <summary>
/// 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.
/// </summary>
[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;
/// <summary>
/// The root Transform of the InteractionTargets. If null, will use this GameObject. GetComponentsInChildren<InteractionTarget>() will be used at initiation to find all InteractionTargets associated with this InteractionObject.
/// </summary>
[Tooltip("The root Transform of the InteractionTargets. If null, will use this GameObject. GetComponentsInChildren<InteractionTarget>() will be used at initiation to find all InteractionTargets associated with this InteractionObject.")]
public Transform otherTargetsRoot;
/// <summary>
/// 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.
/// </summary>
[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;
/// <summary>
/// The weight curves for the interaction.
/// </summary>
public WeightCurve[] weightCurves;
/// <summary>
/// The weight curve multipliers for the interaction.
/// </summary>
public Multiplier[] multipliers;
/// <summary>
/// The interaction events.
/// </summary>
public InteractionEvent[] events;
/// <summary>
/// Gets the length of the interaction (the longest curve).
/// </summary>
public float length { get; private set; }
/// <summary>
/// The last InteractionSystem that started an interaction with this InteractionObject.
/// </summary>
/// <value>The last used interaction system.</value>
public InteractionSystem lastUsedInteractionSystem { get; private set; }
/// <summary>
/// Call if you have changed the curves in play mode or added/removed InteractionTargets.
/// </summary>
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<InteractionTarget>();
}
/// <summary>
/// Gets the look at target (returns otherLookAtTarget if not null).
/// </summary>
public Transform lookAtTarget {
get {
if (otherLookAtTarget != null) return otherLookAtTarget;
return transform;
}
}
/// <summary>
/// Gets the InteractionTarget of the specified effector type and InteractionSystem tag.
/// </summary>
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;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 41f425a4a83914b04af3f1642c000f5d
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 9e5cf919254ee4d6bb9ccd7dc1b4bebd, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,926 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Serialization;
namespace RootMotion.FinalIK {
/// <summary>
/// Handles FBBIK interactions for a character.
/// </summary>
[HelpURL("https://www.youtube.com/watch?v=r5jiZnsDH3M")]
[AddComponentMenu("Scripts/RootMotion.FinalIK/Interaction System/Interaction System")]
public class InteractionSystem : 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_system.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")]
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
/// <summary>
/// If not empty, only the targets with the specified tag will be used by this Interaction System.
/// </summary>
[Tooltip("If not empty, only the targets with the specified tag will be used by this Interaction System.")]
public string targetTag = "";
/// <summary>
/// The fade in time of the interaction.
/// </summary>
[Tooltip("The fade in time of the interaction.")]
public float fadeInTime = 0.3f;
/// <summary>
/// The master speed for all interactions.
/// </summary>
[Tooltip("The master speed for all interactions.")]
public float speed = 1f;
/// <summary>
/// The speed of blending from previous interaction to current interaction if an ongoing interaction has been interrupted by another interaction.
/// </summary>
[Tooltip("The speed of blending from previous interaction to current interaction if an ongoing interaction has been interrupted by another interaction.")]
public float switchInteractionSpeed = 3f;
/// <summary>
/// If > 0, lerps all the FBBIK channels used by the Interaction System back to their default or initial values when not in interaction.
/// </summary>
[Tooltip("If > 0, lerps all the FBBIK channels used by the Interaction System back to their default or initial values when not in interaction.")]
public float resetToDefaultsSpeed = 1f;
[Header("Triggering")]
/// <summary>
/// The collider that registers OnTriggerEnter and OnTriggerExit events with InteractionTriggers.
/// </summary>
[Tooltip("The collider that registers OnTriggerEnter and OnTriggerExit events with InteractionTriggers.")]
[FormerlySerializedAs("collider")]
public Collider characterCollider;
/// <summary>
/// Will be used by Interaction Triggers that need the camera's position. Assign the first person view character camera.
/// </summary>
[Tooltip("Will be used by Interaction Triggers that need the camera's position. Assign the first person view character camera.")]
[FormerlySerializedAs("camera")]
public Transform FPSCamera;
/// <summary>
/// The layers that will be raycasted from the camera (along camera.forward). All InteractionTrigger look at target colliders should be included.
/// </summary>
[Tooltip("The layers that will be raycasted from the camera (along camera.forward). All InteractionTrigger look at target colliders should be included.")]
public LayerMask camRaycastLayers;
/// <summary>
/// Max distance of raycasting from the camera.
/// </summary>
[Tooltip("Max distance of raycasting from the camera.")]
public float camRaycastDistance = 1f;
/// <summary>
/// Returns true if any of the effectors is in interaction and not paused.
/// </summary>
public bool inInteraction {
get {
if (!IsValid(true)) return false;
for (int i = 0; i < interactionEffectors.Length; i++) {
if (interactionEffectors[i].inInteraction && !interactionEffectors[i].isPaused) return true;
}
return false;
}
}
/// <summary>
/// Determines whether this effector is interaction and not paused
/// </summary>
public bool IsInInteraction(FullBodyBipedEffector effectorType) {
if (!IsValid(true)) return false;
for (int i = 0; i < interactionEffectors.Length; i++) {
if (interactionEffectors[i].effectorType == effectorType) {
return interactionEffectors[i].inInteraction && !interactionEffectors[i].isPaused;
}
}
return false;
}
/// <summary>
/// Determines whether this effector is paused
/// </summary>
public bool IsPaused(FullBodyBipedEffector effectorType) {
if (!IsValid(true)) return false;
for (int i = 0; i < interactionEffectors.Length; i++) {
if (interactionEffectors[i].effectorType == effectorType) {
return interactionEffectors[i].inInteraction && interactionEffectors[i].isPaused;
}
}
return false;
}
/// <summary>
/// Returns true if any of the effectors is paused
/// </summary>
public bool IsPaused() {
if (!IsValid(true)) return false;
for (int i = 0; i < interactionEffectors.Length; i++) {
if (interactionEffectors[i].inInteraction && interactionEffectors[i].isPaused) return true;
}
return false;
}
/// <summary>
/// Returns true if either all effectors in interaction are paused or none is.
/// </summary>
public bool IsInSync() {
if (!IsValid(true)) return false;
for (int i = 0; i < interactionEffectors.Length; i++) {
if (interactionEffectors[i].isPaused) {
for (int n = 0; n < interactionEffectors.Length; n++) {
if (n != i && interactionEffectors[n].inInteraction && !interactionEffectors[n].isPaused) return false;
}
}
}
return true;
}
/// <summary>
/// Starts the interaction between an effector and an interaction object.
/// </summary>
public bool StartInteraction(FullBodyBipedEffector effectorType, InteractionObject interactionObject, bool interrupt) {
if (!IsValid(true)) return false;
if (interactionObject == null) return false;
for (int i = 0; i < interactionEffectors.Length; i++) {
if (interactionEffectors[i].effectorType == effectorType) {
return interactionEffectors[i].Start(interactionObject, targetTag, fadeInTime, interrupt);
}
}
return false;
}
/// <summary>
/// Starts the interaction between an effector and an interaction object, choosing InteractionTarget that is closest to current rotation of the effector.
/// </summary>
public bool StartInteractionWithClosestTarget(FullBodyBipedEffector effectorType, InteractionObject interactionObject, bool interrupt)
{
if (!IsValid(true)) return false;
if (interactionObject == null) return false;
for (int i = 0; i < interactionEffectors.Length; i++)
{
if (interactionEffectors[i].effectorType == effectorType)
{
int closestTargetIndex = GetClosestTargetIndex(effectorType, interactionObject);
if (closestTargetIndex == -1) continue;
return interactionEffectors[i].Start(interactionObject, interactionObject.GetTargets()[i], fadeInTime, interrupt);
}
}
return false;
}
private int GetClosestTargetIndex(FullBodyBipedEffector effectorType, InteractionObject obj)
{
int index = -1;
float closestAngle = Mathf.Infinity;
Quaternion handRot = ik.solver.GetEffector(effectorType).bone.rotation;
for (int i = 0; i < obj.GetTargets().Length; i++)
{
float angle = Quaternion.Angle(handRot, obj.GetTargets()[i].transform.rotation);
if (angle < closestAngle)
{
closestAngle = angle;
index = i;
}
}
return index;
}
/// <summary>
/// Starts the interaction between an effector and a specific interaction target.
/// </summary>
public bool StartInteraction(FullBodyBipedEffector effectorType, InteractionObject interactionObject, InteractionTarget target, bool interrupt)
{
if (!IsValid(true)) return false;
if (interactionObject == null) return false;
for (int i = 0; i < interactionEffectors.Length; i++)
{
if (interactionEffectors[i].effectorType == effectorType)
{
return interactionEffectors[i].Start(interactionObject, target, fadeInTime, interrupt);
}
}
return false;
}
/// <summary>
/// Pauses the interaction of an effector.
/// </summary>
public bool PauseInteraction(FullBodyBipedEffector effectorType) {
if (!IsValid(true)) return false;
for (int i = 0; i < interactionEffectors.Length; i++) {
if (interactionEffectors[i].effectorType == effectorType) {
return interactionEffectors[i].Pause();
}
}
return false;
}
/// <summary>
/// Resumes the paused interaction of an effector.
/// </summary>
public bool ResumeInteraction(FullBodyBipedEffector effectorType) {
if (!IsValid(true)) return false;
for (int i = 0; i < interactionEffectors.Length; i++) {
if (interactionEffectors[i].effectorType == effectorType) {
return interactionEffectors[i].Resume();
}
}
return false;
}
/// <summary>
/// Stops the interaction of an effector.
/// </summary>
public bool StopInteraction(FullBodyBipedEffector effectorType) {
if (!IsValid(true)) return false;
for (int i = 0; i < interactionEffectors.Length; i++) {
if (interactionEffectors[i].effectorType == effectorType) {
return interactionEffectors[i].Stop();
}
}
return false;
}
/// <summary>
/// Pauses all the interaction effectors.
/// </summary>
public void PauseAll() {
if (!IsValid(true)) return;
for (int i = 0; i < interactionEffectors.Length; i++) interactionEffectors[i].Pause();
}
/// <summary>
/// Resumes all the paused interaction effectors.
/// </summary>
public void ResumeAll() {
if (!IsValid(true)) return;
for (int i = 0; i < interactionEffectors.Length; i++) interactionEffectors[i].Resume();
}
/// <summary>
/// Stops all interactions.
/// </summary>
public void StopAll() {
for (int i = 0; i < interactionEffectors.Length; i++) interactionEffectors[i].Stop();
}
/// <summary>
/// Gets the current interaction object of an effector.
/// </summary>
public InteractionObject GetInteractionObject(FullBodyBipedEffector effectorType) {
if (!IsValid(true)) return null;
for (int i = 0; i < interactionEffectors.Length; i++) {
if (interactionEffectors[i].effectorType == effectorType) {
return interactionEffectors[i].interactionObject;
}
}
return null;
}
/// <summary>
/// Gets the progress of any interaction with the specified effector.
/// </summary>
public float GetProgress(FullBodyBipedEffector effectorType) {
if (!IsValid(true)) return 0f;
for (int i = 0; i < interactionEffectors.Length; i++) {
if (interactionEffectors[i].effectorType == effectorType) {
return interactionEffectors[i].progress;
}
}
return 0f;
}
/// <summary>
/// Gets the minimum progress of any active interaction
/// </summary>
public float GetMinActiveProgress() {
if (!IsValid(true)) return 0f;
float min = 1f;
for (int i = 0; i < interactionEffectors.Length; i++) {
if (interactionEffectors[i].inInteraction) {
float p = interactionEffectors[i].progress;
if (p > 0f && p < min) min = p;
}
}
return min;
}
/// <summary>
/// Triggers all interactions of an InteractionTrigger. Returns false if unsuccessful (maybe out of range).
/// </summary>
public bool TriggerInteraction(int index, bool interrupt) {
if (!IsValid(true)) return false;
if (!TriggerIndexIsValid(index)) return false;
bool all = true;
var range = triggersInRange[index].ranges[bestRangeIndexes[index]];
for (int i = 0; i < range.interactions.Length; i++) {
for (int e = 0; e < range.interactions[i].effectors.Length; e++) {
bool s = StartInteraction(range.interactions[i].effectors[e], range.interactions[i].interactionObject, interrupt);
if (!s) all = false;
}
}
return all;
}
/// <summary>
/// Triggers all interactions of an InteractionTrigger. Returns false if unsuccessful (maybe out of range).
/// </summary>
public bool TriggerInteraction(int index, bool interrupt, out InteractionObject interactionObject) {
interactionObject = null;
if (!IsValid(true)) return false;
if (!TriggerIndexIsValid(index)) return false;
bool all = true;
var range = triggersInRange[index].ranges[bestRangeIndexes[index]];
for (int i = 0; i < range.interactions.Length; i++) {
for (int e = 0; e < range.interactions[i].effectors.Length; e++) {
interactionObject = range.interactions[i].interactionObject;
bool s = StartInteraction(range.interactions[i].effectors[e], interactionObject, interrupt);
if (!s) all = false;
}
}
return all;
}
/// <summary>
/// Triggers all interactions of an InteractionTrigger. Returns false if unsuccessful (maybe out of range).
/// </summary>
public bool TriggerInteraction(int index, bool interrupt, out InteractionTarget interactionTarget) {
interactionTarget = null;
if (!IsValid(true)) return false;
if (!TriggerIndexIsValid(index)) return false;
bool all = true;
var range = triggersInRange[index].ranges[bestRangeIndexes[index]];
for (int i = 0; i < range.interactions.Length; i++) {
for (int e = 0; e < range.interactions[i].effectors.Length; e++) {
var interactionObject = range.interactions[i].interactionObject;
var t = interactionObject.GetTarget(range.interactions[i].effectors[e], tag);
if (t != null) interactionTarget = t.GetComponent<InteractionTarget>();
bool s = StartInteraction(range.interactions[i].effectors[e], interactionObject, interrupt);
if (!s) all = false;
}
}
return all;
}
/// <summary>
/// Gets the closest InteractionTrigger Range.
/// </summary>
public InteractionTrigger.Range GetClosestInteractionRange() {
if (!IsValid(true)) return null;
int index = GetClosestTriggerIndex();
if (index < 0 || index >= triggersInRange.Count) return null;
return triggersInRange[index].ranges[bestRangeIndexes[index]];
}
/// <summary>
/// Gets the closest InteractionObject in range.
/// </summary>
public InteractionObject GetClosestInteractionObjectInRange() {
var range = GetClosestInteractionRange();
if (range == null) return null;
return range.interactions[0].interactionObject;
}
/// <summary>
/// Gets the closest InteractionTarget in range.
/// </summary>
public InteractionTarget GetClosestInteractionTargetInRange() {
var range = GetClosestInteractionRange();
if (range == null) return null;
return range.interactions[0].interactionObject.GetTarget(range.interactions[0].effectors[0], this);
}
/// <summary>
/// Gets the closest InteractionObjects in range.
/// </summary>
public InteractionObject[] GetClosestInteractionObjectsInRange() {
var range = GetClosestInteractionRange();
if (range == null) return new InteractionObject[0];
InteractionObject[] objects = new InteractionObject[range.interactions.Length];
for (int i = 0; i < range.interactions.Length; i++) {
objects[i] = range.interactions[i].interactionObject;
}
return objects;
}
/// <summary>
/// Gets the closest InteractionTargets in range.
/// </summary>
public InteractionTarget[] GetClosestInteractionTargetsInRange() {
var range = GetClosestInteractionRange();
if (range == null) return new InteractionTarget[0];
List<InteractionTarget> targets = new List<InteractionTarget>();
foreach (InteractionTrigger.Range.Interaction interaction in range.interactions) {
foreach (FullBodyBipedEffector effectorType in interaction.effectors) {
targets.Add (interaction.interactionObject.GetTarget(effectorType, this));
}
}
return (InteractionTarget[])targets.ToArray();
}
/// <summary>
/// Returns true if all effectors of a trigger are either not in interaction or paused
/// </summary>
public bool TriggerEffectorsReady(int index) {
if (!IsValid(true)) return false;
if (!TriggerIndexIsValid(index)) return false;
for (int r = 0; r < triggersInRange[index].ranges.Length; r++) {
var range = triggersInRange[index].ranges[r];
for (int i = 0; i < range.interactions.Length; i++) {
for (int e = 0; e < range.interactions[i].effectors.Length; e++) {
if (IsInInteraction(range.interactions[i].effectors[e])) return false;
}
}
for (int i = 0; i < range.interactions.Length; i++) {
for (int e = 0; e < range.interactions[i].effectors.Length; e++) {
if (IsPaused(range.interactions[i].effectors[e])) {
for (int n = 0; n < range.interactions[i].effectors.Length; n++) {
if (n != e && !IsPaused(range.interactions[i].effectors[n])) return false;
}
}
}
}
}
return true;
}
/// <summary>
/// Return the current most appropriate range of an InteractionTrigger listed in triggersInRange.
/// </summary>
public InteractionTrigger.Range GetTriggerRange(int index) {
if (!IsValid(true)) return null;
if (index < 0 || index >= bestRangeIndexes.Count) {
Warning.Log("Index out of range.", transform);
return null;
}
return triggersInRange[index].ranges[bestRangeIndexes[index]];
}
/// <summary>
/// Returns the InteractionTrigger that is in range and closest to the character.
/// </summary>
public int GetClosestTriggerIndex() {
if (!IsValid(true)) return -1;
if (triggersInRange.Count == 0) return -1;
if (triggersInRange.Count == 1) return 0;
int closest = -1;
float closestSqrMag = Mathf.Infinity;
for (int i = 0; i < triggersInRange.Count; i++) {
if (triggersInRange[i] != null) {
float sqrMag = Vector3.SqrMagnitude(triggersInRange[i].transform.position - transform.position);
if (sqrMag < closestSqrMag) {
closest = i;
closestSqrMag = sqrMag;
}
}
}
return closest;
}
/// <summary>
/// Store the default values to which the interaction effectors will be reset to after interactions have ended.
/// </summary>
public void StoreDefaults()
{
for (int i = 0; i < interactionEffectors.Length; i++)
{
interactionEffectors[i].StoreDefaults();
}
}
/// <summary>
/// Gets the FullBodyBipedIK component.
/// </summary>
public FullBodyBipedIK ik {
get {
return fullBody;
}
set {
fullBody = value;
}
}
/// <summary>
/// Gets the in contact.
/// </summary>
/// <value>The in contact.</value>
public List<InteractionTrigger> triggersInRange { get; private set; }
private List<InteractionTrigger> inContact = new List<InteractionTrigger>();
private List<int> bestRangeIndexes = new List<int>();
/// <summary>
/// Interaction delegate
/// </summary>
public delegate void InteractionDelegate(FullBodyBipedEffector effectorType, InteractionObject interactionObject);
/// <summary>
/// Interaction event delegate
/// </summary>
public delegate void InteractionEventDelegate(FullBodyBipedEffector effectorType, InteractionObject interactionObject, InteractionObject.InteractionEvent interactionEvent);
/// <summary>
/// Called when an InteractionEvent has been started
/// </summary>
public InteractionDelegate OnInteractionStart;
/// <summary>
/// Called when an Interaction has been paused
/// </summary>
public InteractionDelegate OnInteractionPause;
/// <summary>
/// Called when an InteractionObject has been picked up.
/// </summary>
public InteractionDelegate OnInteractionPickUp;
/// <summary>
/// Called when a paused Interaction has been resumed
/// </summary>
public InteractionDelegate OnInteractionResume;
/// <summary>
/// Called when an Interaction has been stopped
/// </summary>
public InteractionDelegate OnInteractionStop;
/// <summary>
/// Called when an interaction event occurs.
/// </summary>
public InteractionEventDelegate OnInteractionEvent;
/// <summary>
/// Gets the RaycastHit from trigger seeking.
/// </summary>
/// <value>The hit.</value>
public RaycastHit raycastHit;
#endregion Main Interface
[Space(10)]
[Tooltip("Reference to the FBBIK component.")]
[SerializeField] FullBodyBipedIK fullBody; // Reference to the FBBIK component.
/// <summary>
/// Handles looking at the interactions.
/// </summary>
[Tooltip("Handles looking at the interactions.")]
public InteractionLookAt lookAt = new InteractionLookAt();
// The array of Interaction Effectors
private InteractionEffector[] interactionEffectors = new InteractionEffector[9] {
new InteractionEffector(FullBodyBipedEffector.Body),
new InteractionEffector(FullBodyBipedEffector.LeftFoot),
new InteractionEffector(FullBodyBipedEffector.LeftHand),
new InteractionEffector(FullBodyBipedEffector.LeftShoulder),
new InteractionEffector(FullBodyBipedEffector.LeftThigh),
new InteractionEffector(FullBodyBipedEffector.RightFoot),
new InteractionEffector(FullBodyBipedEffector.RightHand),
new InteractionEffector(FullBodyBipedEffector.RightShoulder),
new InteractionEffector(FullBodyBipedEffector.RightThigh)
};
public bool initiated { get; private set; }
private Collider lastCollider, c;
private float lastTime;
// Initiate
public void Start() {
if (fullBody == null) fullBody = GetComponent<FullBodyBipedIK>();
//Debug.Log(fullBody);
if (fullBody == null) {
Warning.Log("InteractionSystem can not find a FullBodyBipedIK component", transform);
return;
}
// Add to the FBBIK OnPostUpdate delegate to get a call when it has finished updating
fullBody.solver.OnPreUpdate += OnPreFBBIK;
fullBody.solver.OnPostUpdate += OnPostFBBIK;
fullBody.solver.OnFixTransforms += OnFixTransforms;
OnInteractionStart += LookAtInteraction;
OnInteractionPause += InteractionPause;
OnInteractionResume += InteractionResume;
OnInteractionStop += InteractionStop;
foreach (InteractionEffector e in interactionEffectors) e.Initiate(this);
triggersInRange = new List<InteractionTrigger>();
c = GetComponent<Collider>();
UpdateTriggerEventBroadcasting();
initiated = true;
}
private void InteractionPause(FullBodyBipedEffector effector, InteractionObject interactionObject) {
lookAt.isPaused = true;
}
private void InteractionResume(FullBodyBipedEffector effector, InteractionObject interactionObject) {
lookAt.isPaused = false;
}
private void InteractionStop(FullBodyBipedEffector effector, InteractionObject interactionObject) {
lookAt.isPaused = false;
}
// Called by the delegate
private void LookAtInteraction(FullBodyBipedEffector effector, InteractionObject interactionObject) {
lookAt.Look(interactionObject.lookAtTarget, Time.time + (interactionObject.length * 0.5f));
}
public void OnTriggerEnter(Collider c) {
if (fullBody == null) return;
var trigger = c.GetComponent<InteractionTrigger>();
if (trigger == null) return;
if (inContact.Contains(trigger)) return;
inContact.Add(trigger);
}
public void OnTriggerExit(Collider c) {
if (fullBody == null) return;
var trigger = c.GetComponent<InteractionTrigger>();
if (trigger == null) return;
inContact.Remove(trigger);
}
// Is the InteractionObject trigger in range of any effectors? If the trigger collider is bigger than any effector ranges, then the object in contact is still unreachable.
private bool ContactIsInRange(int index, out int bestRangeIndex) {
bestRangeIndex = -1;
if (!IsValid(true)) return false;
if (index < 0 || index >= inContact.Count) {
Warning.Log("Index out of range.", transform);
return false;
}
if (inContact[index] == null) {
Warning.Log("The InteractionTrigger in the list 'inContact' has been destroyed", transform);
return false;
}
bestRangeIndex = inContact[index].GetBestRangeIndex(transform, FPSCamera, raycastHit);
if (bestRangeIndex == -1) return false;
return true;
}
// Using this to assign some default values in Editor
void OnDrawGizmosSelected() {
if (Application.isPlaying) return;
if (fullBody == null) fullBody = GetComponent<FullBodyBipedIK>();
if (characterCollider == null) characterCollider = GetComponent<Collider>();
}
public void Update() {
if (fullBody == null) return;
UpdateTriggerEventBroadcasting();
Raycasting();
// Finding the triggers in contact and in range
triggersInRange.Clear();
bestRangeIndexes.Clear();
for (int i = 0; i < inContact.Count; i++) {
int bestRangeIndex = -1;
if (inContact[i] != null && inContact[i].gameObject.activeInHierarchy && ContactIsInRange(i, out bestRangeIndex)) {
triggersInRange.Add(inContact[i]);
bestRangeIndexes.Add(bestRangeIndex);
}
}
// Update LookAt
lookAt.Update();
}
// Finds triggers that need camera position and rotation
private void Raycasting() {
if (camRaycastLayers == -1) return;
if (FPSCamera == null) return;
Physics.Raycast(FPSCamera.position, FPSCamera.forward, out raycastHit, camRaycastDistance, camRaycastLayers);
}
// Update collider and TriggerEventBroadcaster
private void UpdateTriggerEventBroadcasting() {
if (characterCollider == null) characterCollider = c;
if (characterCollider != null && characterCollider != c) {
if (characterCollider.GetComponent<TriggerEventBroadcaster>() == null) {
var t = characterCollider.gameObject.AddComponent<TriggerEventBroadcaster>();
t.target = gameObject;
}
if (lastCollider != null && lastCollider != c && lastCollider != characterCollider) {
var t = lastCollider.GetComponent<TriggerEventBroadcaster>();
if (t != null) Destroy(t);
}
}
lastCollider = characterCollider;
}
private void OnEnable()
{
lastTime = Time.time;
}
// Update the interaction
void UpdateEffectors() {
if (fullBody == null) return;
float deltaTime = Time.time - lastTime; // When AnimatePhysics is used, Time.deltaTime is unusable
lastTime = Time.time;
for (int i = 0; i < interactionEffectors.Length; i++) interactionEffectors[i].Update(transform, speed, deltaTime);
// Switch from interaction to interaction
for (int i = 0; i < interactionEffectors.Length; i++) interactionEffectors[i].Switch(switchInteractionSpeed * speed, deltaTime);
// Interpolate to default pull, reach values
for (int i = 0; i < interactionEffectors.Length; i++) interactionEffectors[i].ResetToDefaults(resetToDefaultsSpeed * speed, deltaTime);
}
// Used for using LookAtIK to rotate the spine
private void OnPreFBBIK() {
//if (!enabled) return;
if (fullBody == null) return;
lookAt.SolveSpine();
UpdateEffectors();
}
// Used for rotating the hands after FBBIK has finished
private void OnPostFBBIK() {
//if (!enabled) return;
if (fullBody == null) return;
for (int i = 0; i < interactionEffectors.Length; i++) interactionEffectors[i].OnPostFBBIK();
// Update LookAtIK head
lookAt.SolveHead();
}
void OnFixTransforms() {
lookAt.OnFixTransforms();
}
// Remove the delegates
void OnDestroy() {
if (fullBody == null) return;
fullBody.solver.OnPreUpdate -= OnPreFBBIK;
fullBody.solver.OnPostUpdate -= OnPostFBBIK;
fullBody.solver.OnFixTransforms -= OnFixTransforms;
OnInteractionStart -= LookAtInteraction;
OnInteractionPause -= InteractionPause;
OnInteractionResume -= InteractionResume;
OnInteractionStop -= InteractionStop;
}
// Is this InteractionSystem valid and initiated
private bool IsValid(bool log) {
if (fullBody == null) {
if (log) Warning.Log("FBBIK is null. Will not update the InteractionSystem", transform);
return false;
}
if (!initiated) {
if (log) Warning.Log("The InteractionSystem has not been initiated yet.", transform);
return false;
}
return true;
}
// Is the index of triggersInRange valid?
private bool TriggerIndexIsValid(int index) {
if (index < 0 || index >= triggersInRange.Count) {
Warning.Log("Index out of range.", transform);
return false;
}
if (triggersInRange[index] == null) {
Warning.Log("The InteractionTrigger in the list 'inContact' has been destroyed", transform);
return false;
}
return true;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 537b81dc57e504256a6c74c4bcf6bca9
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 5e7cd1ffcb3304ceba342d37416ceb2c, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,216 @@
using UnityEngine;
using System.Collections;
using RootMotion;
namespace RootMotion.FinalIK {
/// <summary>
/// The target of an effector in the InteractionSystem.
/// </summary>
[HelpURL("https://www.youtube.com/watch?v=r5jiZnsDH3M")]
[AddComponentMenu("Scripts/RootMotion.FinalIK/Interaction System/Interaction Target")]
public class InteractionTarget : MonoBehaviour {
[System.Serializable]
public enum RotationMode
{
TwoDOF = 0,
ThreeDOF = 1
}
// 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_target.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/");
}
/// <summary>
/// Multiplies the value of a weight curve for this effector target.
/// </summary>
[System.Serializable]
public class Multiplier {
/// <summary>
/// The curve type (InteractionObject.WeightCurve.Type).
/// </summary>
[Tooltip("The curve type (InteractionObject.WeightCurve.Type).")]
public InteractionObject.WeightCurve.Type curve;
/// <summary>
/// Multiplier of the curve's value.
/// </summary>
[Tooltip("Multiplier of the curve's value.")]
public float multiplier;
}
/// <summary>
/// The type of the FBBIK effector.
/// </summary>
[Tooltip("The type of the FBBIK effector.")]
public FullBodyBipedEffector effectorType;
/// <summary>
/// InteractionObject weight curve multipliers for this effector target.
/// </summary>
[Tooltip("InteractionObject weight curve multipliers for this effector target.")]
public Multiplier[] multipliers;
/// <summary>
/// The interaction speed multiplier for this effector. This can be used to make interactions faster/slower for specific effectors.
/// </summary>
[Tooltip("The interaction speed multiplier for this effector. This can be used to make interactions faster/slower for specific effectors.")]
public float interactionSpeedMlp = 1f;
/// <summary>
/// The pivot to twist/swing this interaction target about. For symmetric objects that can be interacted with from a certain angular range.
/// </summary>
[Tooltip("The pivot to twist/swing this interaction target about. For symmetric objects that can be interacted with from a certain angular range.")]
public Transform pivot;
/// <summary>
/// 2 or 3 degrees of freedom to match this InteractionTarget's rotation to the effector bone rotation.
/// </summary>
[Tooltip("2 or 3 degrees of freedom to match this InteractionTarget's rotation to the effector bone rotation.")]
public RotationMode rotationMode;
/// <summary>
/// The axis of twisting the interaction target.
/// </summary>
[Tooltip("The axis of twisting the interaction target (blue line).")]
public Vector3 twistAxis = Vector3.up;
/// <summary>
/// The weight of twisting the interaction target towards the effector bone in the start of the interaction.
/// </summary>
[Tooltip("The weight of twisting the interaction target towards the effector bone in the start of the interaction.")]
public float twistWeight = 1f;
/// <summary>
/// The weight of swinging the interaction target towards the effector bone in the start of the interaction. Swing is defined as a 3-DOF rotation around any axis, while twist is only around the twist axis.
/// </summary>
[Tooltip("The weight of swinging the interaction target towards the effector bone in the start of the interaction. Swing is defined as a 3-DOF rotation around any axis, while twist is only around the twist axis.")]
public float swingWeight;
/// <summary>
/// The weight of rotating this InteractionTarget to the effector bone in the start of the interaction (and during if 'Rotate Once' is disabled
/// </summary>
[Tooltip("The weight of rotating this InteractionTarget to the effector bone in the start of the interaction (and during if 'Rotate Once' is disabled")]
[Range(0f, 1f)] public float threeDOFWeight = 1f;
/// <summary>
/// If true, will twist/swing around the pivot only once at the start of the interaction. If false, will continue rotating throuout the whole interaction.
/// </summary>
[Tooltip("If true, will twist/swing around the pivot only once at the start of the interaction. If false, will continue rotating throuout the whole interaction.")]
public bool rotateOnce = true;
/// <summary>
/// Will not set HandPoser's pose target and allows you to use a pose target from a previous interaction if disabled.
/// </summary>
[Tooltip("Will not set HandPoser's pose target and allows you to use a pose target from a previous interaction if disabled.")]
public bool usePoser = true;
/// <summary>
/// Used only together with UniversalPoser. List of bones must match UniversalPoser's list of bones in both array size and hierarchy.
/// </summary>
[Tooltip("Used only together with UniversalPoser. List of bones must match UniversalPoser's list of bones in both array size and hierarchy.")]
public Transform[] bones = new Transform[0];
private Quaternion defaultLocalRotation;
private Transform lastPivot;
// Should a curve of the Type be ignored for this effector?
public float GetValue(InteractionObject.WeightCurve.Type curveType) {
for (int i = 0; i < multipliers.Length; i++) if (multipliers[i].curve == curveType) return multipliers[i].multiplier;
return 1f;
}
// Reset the twist and swing rotation of the target
public void ResetRotation() {
if (pivot != null) pivot.localRotation = defaultLocalRotation;
}
// Rotate this target towards a position
public void RotateTo(Transform bone) {
if (pivot == null) return;
if (pivot != lastPivot) {
defaultLocalRotation = pivot.localRotation;
lastPivot = pivot;
}
// Rotate to the default local rotation
pivot.localRotation = defaultLocalRotation;
switch (rotationMode)
{
case RotationMode.TwoDOF:
// Twisting around the twist axis
if (twistWeight > 0f)
{
Vector3 targetTangent = transform.position - pivot.position;
Vector3 n = pivot.rotation * twistAxis;
Vector3 normal = n;
Vector3.OrthoNormalize(ref normal, ref targetTangent);
normal = n;
Vector3 direction = bone.position - pivot.position;
Vector3.OrthoNormalize(ref normal, ref direction);
Quaternion q = QuaTools.FromToAroundAxis(targetTangent, direction, n);
pivot.rotation = Quaternion.Lerp(Quaternion.identity, q, twistWeight) * pivot.rotation;
}
// Swinging freely
if (swingWeight > 0f)
{
Quaternion s = Quaternion.FromToRotation(transform.position - pivot.position, bone.position - pivot.position);
pivot.rotation = Quaternion.Lerp(Quaternion.identity, s, swingWeight) * pivot.rotation;
}
break;
case RotationMode.ThreeDOF:
// Free rotation around all axes
if (threeDOFWeight <= 0f) break;
Quaternion fromTo = QuaTools.FromToRotation(transform.rotation, bone.rotation);
if (threeDOFWeight >= 1f)
{
pivot.rotation = fromTo * pivot.rotation;
} else
{
pivot.rotation = Quaternion.Slerp(Quaternion.identity, fromTo, threeDOFWeight) * pivot.rotation;
}
break;
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 4c15924752853459490d29011ba18d1d
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 706b88054f06c4e83ad5f34217bedea3, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,312 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using RootMotion.FinalIK;
namespace RootMotion.FinalIK {
/// <summary>
/// When a character with an InteractionSystem component enters the trigger collider of this game object, this component will register itself to the InteractionSystem.
/// The InteractionSystem can then use it to find the most appropriate InteractionObject and effectors to interact with.
/// Use InteractionSystem.GetClosestTriggerIndex() and InteractionSystem.TriggerInteration() to trigger the interactions that the character is in contact with.
/// </summary>
[HelpURL("https://www.youtube.com/watch?v=-TDZpNjt2mk&index=15&list=PLVxSIA1OaTOu8Nos3CalXbJ2DrKnntMv6")]
[AddComponentMenu("Scripts/RootMotion.FinalIK/Interaction System/Interaction Trigger")]
public class InteractionTrigger: 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_trigger.html");
}
// Open a video tutorial video
[ContextMenu("TUTORIAL VIDEO")]
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/");
}
/// <summary>
/// Defines the valid range of the character's position and rotation relative to this trigger.
/// </summary>
[System.Serializable]
public class CharacterPosition {
/// <summary>
/// If false, will not care where the character stands, as long as it is in contact with the trigger collider.
/// </summary>
[Tooltip("If false, will not care where the character stands, as long as it is in contact with the trigger collider.")]
public bool use;
/// <summary>
/// The offset of the character's position relative to the trigger in XZ plane. Y position of the character is unlimited as long as it is contact with the collider.
/// </summary>
[Tooltip("The offset of the character's position relative to the trigger in XZ plane. Y position of the character is unlimited as long as it is contact with the collider.")]
public Vector2 offset;
/// <summary>
/// Angle offset from the default forward direction..
/// </summary>
[Tooltip("Angle offset from the default forward direction.")]
[Range(-180f, 180f)] public float angleOffset;
/// <summary>
/// Max angular offset of the character's forward from the direction of this trigger.
/// </summary>
[Tooltip("Max angular offset of the character's forward from the direction of this trigger.")]
[Range(0f, 180f)] public float maxAngle = 45f;
/// <summary>
/// Max offset of the character's position from this range's center.
/// </summary>
[Tooltip("Max offset of the character's position from this range's center.")]
public float radius = 0.5f;
/// <summary>
/// If true, will rotate the trigger around its Y axis relative to the position of the character, so the object can be interacted with from all sides.
/// </summary>
[Tooltip("If true, will rotate the trigger around its Y axis relative to the position of the character, so the object can be interacted with from all sides.")]
public bool orbit;
/// <summary>
/// Fixes the Y axis of the trigger to Vector3.up. This makes the trigger symmetrical relative to the object.
/// For example a gun will be able to be picked up from the same direction relative to the barrel no matter which side the gun is resting on.
/// </summary>
[Tooltip("Fixes the Y axis of the trigger to Vector3.up. This makes the trigger symmetrical relative to the object. For example a gun will be able to be picked up from the same direction relative to the barrel no matter which side the gun is resting on.")]
public bool fixYAxis;
// Returns the 2D offset as 3D vector.
public Vector3 offset3D { get { return new Vector3(offset.x, 0f, offset.y); }}
// Returns the default direction of this character position in world space.
public Vector3 direction3D {
get {
return Quaternion.AngleAxis(angleOffset, Vector3.up) * Vector3.forward;
}
}
// Is the character in range with this character position?
public bool IsInRange(Transform character, Transform trigger, out float error) {
// Do not use this character position, trigger is still valid
error = 0f;
if (!use) return true;
// Invalid character position conditions
error = 180f;
if (radius <= 0f) return false;
if (maxAngle <= 0f) return false;
Vector3 forward = trigger.forward;
if (fixYAxis) forward.y = 0f;
if (forward == Vector3.zero) return false; // Singularity
Vector3 up = (fixYAxis? Vector3.up: trigger.up);
Quaternion triggerRotation = Quaternion.LookRotation(forward, up);
Vector3 position = trigger.position + triggerRotation * offset3D;
Vector3 origin = orbit? trigger.position: position;
Vector3 toCharacter = character.position - origin;
Vector3.OrthoNormalize(ref up, ref toCharacter);
toCharacter *= Vector3.Project(character.position - origin, toCharacter).magnitude;
if (orbit) {
float mag = offset.magnitude;
float dist = toCharacter.magnitude;
if (dist < mag - radius || dist > mag + radius) return false;
} else {
if (toCharacter.magnitude > radius) return false;
}
Vector3 d = triggerRotation * direction3D;
Vector3.OrthoNormalize(ref up, ref d);
if (orbit) {
Vector3 toPosition = position - trigger.position;
if (toPosition == Vector3.zero) toPosition = Vector3.forward;
Quaternion r = Quaternion.LookRotation(toPosition, up);
toCharacter = Quaternion.Inverse(r) * toCharacter;
float a = Mathf.Atan2(toCharacter.x, toCharacter.z) * Mathf.Rad2Deg;
d = Quaternion.AngleAxis(a, up) * d;
}
float angle = Vector3.Angle(d, character.forward);
if (angle > maxAngle) return false;
error = (angle / maxAngle) * 180f;
return true;
}
}
/// <summary>
/// Defines the valid range of the camera's position relative to this trigger.
/// </summary>
[System.Serializable]
public class CameraPosition {
/// <summary>
/// What the camera should be looking at to trigger the interaction?
/// </summary>
[Tooltip("What the camera should be looking at to trigger the interaction? If null, this camera position will not be used.")]
public Collider lookAtTarget;
/// <summary>
/// The direction from the lookAtTarget towards the camera (in lookAtTarget's space).
/// </summary>
[Tooltip("The direction from the lookAtTarget towards the camera (in lookAtTarget's space).")]
public Vector3 direction = -Vector3.forward;
/// <summary>
/// Max distance from the lookAtTarget to the camera.
/// </summary>
[Tooltip("Max distance from the lookAtTarget to the camera.")]
public float maxDistance = 0.5f;
/// <summary>
/// Max angle between the direction and the direction towards the camera.
/// </summary>
[Tooltip("Max angle between the direction and the direction towards the camera.")]
[Range(0f, 180f)] public float maxAngle = 45f;
/// <summary>
/// Fixes the Y axis of the trigger to Vector3.up. This makes the trigger symmetrical relative to the object.
/// </summary>
[Tooltip("Fixes the Y axis of the trigger to Vector3.up. This makes the trigger symmetrical relative to the object.")]
public bool fixYAxis;
// Returns the rotation space of this CameraPosition.
public Quaternion GetRotation() {
Vector3 forward = lookAtTarget.transform.forward;
if (fixYAxis) forward.y = 0f;
if (forward == Vector3.zero) return Quaternion.identity; // Singularity
Vector3 up = (fixYAxis? Vector3.up: lookAtTarget.transform.up);
return Quaternion.LookRotation(forward, up);
}
// Is the camera raycast hit in range of this CameraPosition?
public bool IsInRange(Transform raycastFrom, RaycastHit hit, Transform trigger, out float error) {
// Not using the CameraPosition
error = 0f;
if (lookAtTarget == null) return true;
// Not in range conditions
error = 180f;
if (raycastFrom == null) return false;
if (hit.collider != lookAtTarget) return false;
if (hit.distance > maxDistance) return false;
if (direction == Vector3.zero) return false;
if (maxDistance <= 0f) return false;
if (maxAngle <= 0f) return false;
Vector3 d = GetRotation() * direction;
float a = Vector3.Angle(raycastFrom.position - hit.point, d);
if (a > maxAngle) return false;
error = (a / maxAngle) * 180f;
return true;
}
}
/// <summary>
/// Defines the valid range of the character's and/or its camera's position for one or multiple interactions.
/// </summary>
[System.Serializable]
public class Range {
[HideInInspector] public string name; // Name is composed automatically by InteractionTriggerInspector.cs. Editor only.
[HideInInspector] public bool show = true; // Show this range in the Scene view? Editor only.
/// <summary>
/// Defines the interaction object and effectors that will be triggered when calling InteractionSystem.TriggerInteraction().
/// </summary>
[System.Serializable]
public class Interaction {
/// <summary>
/// The InteractionObject to interact with.
/// </summary>
[Tooltip("The InteractionObject to interact with.")]
public InteractionObject interactionObject;
/// <summary>
/// The effectors to interact with.
/// </summary>
[Tooltip("The effectors to interact with.")]
public FullBodyBipedEffector[] effectors;
}
/// <summary>
/// The range for the character's position and rotation.
/// </summary>
[Tooltip("The range for the character's position and rotation.")]
public CharacterPosition characterPosition;
/// <summary>
/// The range for the character camera's position and rotation.
/// </summary>
[Tooltip("The range for the character camera's position and rotation.")]
public CameraPosition cameraPosition;
/// <summary>
/// Definitions of the interactions associated with this range.
/// </summary>
[Tooltip("Definitions of the interactions associated with this range.")]
public Interaction[] interactions;
public bool IsInRange(Transform character, Transform raycastFrom, RaycastHit raycastHit, Transform trigger, out float maxError) {
maxError = 0f;
float characterError = 0f;
float cameraError = 0f;
if (!characterPosition.IsInRange(character, trigger, out characterError)) return false;
if (!cameraPosition.IsInRange(raycastFrom, raycastHit, trigger, out cameraError)) return false;
maxError = Mathf.Max(characterError, cameraError);
return true;
}
}
/// <summary>
/// The valid ranges of the character's and/or its camera's position for triggering interaction when the character is in contact with the collider of this trigger.
/// </summary>
[Tooltip("The valid ranges of the character's and/or its camera's position for triggering interaction when the character is in contact with the collider of this trigger.")]
public Range[] ranges = new Range[0];
// Returns the index of the ranges that is best fit for the current position/rotation of the character and its camera.
public int GetBestRangeIndex(Transform character, Transform raycastFrom, RaycastHit raycastHit) {
if (GetComponent<Collider>() == null) {
Warning.Log("Using the InteractionTrigger requires a Collider component.", transform);
return -1;
}
int bestRangeIndex = -1;
float smallestError = 180f;
float error = 0f;
for (int i = 0; i < ranges.Length; i++) {
if (ranges[i].IsInRange(character, raycastFrom, raycastHit, transform, out error)) {
if (error <= smallestError) {
smallestError = error;
bestRangeIndex = i;
}
}
}
return bestRangeIndex;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 271d39b80226e4699829647c96a98758
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 2a1323dcf174944a69736d167afe96e0, type: 3}
userData:
assetBundleName:
assetBundleVariant: