using UnityEngine; using System.Collections; using System.Collections.Generic; using RootMotion; namespace RootMotion.FinalIK { /// /// Managing Interactions for a single FBBIK effector. /// [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, resetTimer; private bool positionWeightUsed, rotationWeightUsed, pullUsed, reachUsed, pushUsed, pushParentUsed, bendGoalWeightUsed; private bool pickedUp, defaults, pickUpOnPostFBBIK; private Vector3 pickUpPosition, pausePositionRelative; private Quaternion pickUpRotation, pauseRotationRelative; private InteractionTarget interactionTarget; private Transform target; private List triggered = new List(); private InteractionSystem interactionSystem; private bool started; // 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 if (effector == null) { effector = interactionSystem.ik.solver.GetEffector(effectorType); poser = effector.bone.GetComponent(); } StoreDefaults(); } private void StoreDefaults() { 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; } // Interpolate to default values when currently not in interaction public bool ResetToDefaults(float speed) { if (inInteraction) return false; if (isPaused) return false; if (defaults) return false; resetTimer = Mathf.MoveTowards(resetTimer, 0f, Time.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 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; } // Get the InteractionTarget target = interactionObject.GetTarget(effectorType, tag); if (target == null) return false; interactionTarget = target.GetComponent(); // 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); } // Posing the hand/foot if (poser != null) { if (poser.poseRoot == null) poser.weight = 0f; if (interactionTarget != null) poser.poseRoot = target.transform; else poser.poseRoot = null; poser.AutoMapping(); } // 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 = interactionObject.CurveUsed(InteractionObject.WeightCurve.Type.PoserWeight); 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.position); started = true; return true; } // Update the (possibly) ongoing interaction public void Update(Transform root, float speed) { 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.position); if (isPaused) { 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); return; } // Advance the interaction timer and weight timer += Time.deltaTime * speed * (interactionTarget != null? interactionTarget.interactionSpeedMlp: 1f); weight = Mathf.Clamp(weight + Time.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? pickUpPosition: target.position; Quaternion targetRotation = pickedUp? 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); if (pickUp) PickUp(root); if (pause) Pause(); // Hand poser weight float poserWeight = interactionObject.GetValue (InteractionObject.WeightCurve.Type.PoserWeight, interactionTarget, timer); if (poser != null) { 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 = effector.position; pickUpRotation = effector.rotation; pickUpOnPostFBBIK = true; pickedUp = true; var rigidbody = interactionObject.targetsRoot.GetComponent(); 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(); if (rootCollider != null) { var colliders = interactionObject.targetsRoot.GetComponentsInChildren(); 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 (rotateBoneWeight > 0f) { Quaternion r = pickedUp? pickUpRotation: effector.rotation; Quaternion targetRotation = Quaternion.Slerp(effector.bone.rotation, r, rotateBoneWeight * rotateBoneWeight); effector.bone.localRotation = Quaternion.Inverse(effector.bone.parent.rotation) * targetRotation; } // Positioning the interaction object to the effector (not the bone, because it is still at it's animated translation) if (pickUpOnPostFBBIK) { Vector3 bonePosition = effector.bone.position; effector.bone.position = pickUpPosition; interactionObject.targetsRoot.parent = effector.bone; effector.bone.position = bonePosition; pickUpOnPostFBBIK = false; } } } }