initial upload
This commit is contained in:
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4046d96b4054144c0b0fe262b112932c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 277df98b5c4b44c71bf8c728da72f815
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41f425a4a83914b04af3f1642c000f5d
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 9e5cf919254ee4d6bb9ccd7dc1b4bebd, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 537b81dc57e504256a6c74c4bcf6bca9
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 5e7cd1ffcb3304ceba342d37416ceb2c, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c15924752853459490d29011ba18d1d
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 706b88054f06c4e83ad5f34217bedea3, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 271d39b80226e4699829647c96a98758
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 2a1323dcf174944a69736d167afe96e0, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user