You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			288 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
			
		
		
	
	
			288 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
using UnityEngine;
 | 
						|
using System.Collections;
 | 
						|
 | 
						|
namespace RootMotion.FinalIK
 | 
						|
{
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Procedural recoil using FBBIK.
 | 
						|
    /// </summary>
 | 
						|
    public class Recoil : OffsetModifier
 | 
						|
    {
 | 
						|
 | 
						|
        [System.Serializable]
 | 
						|
        public class RecoilOffset
 | 
						|
        {
 | 
						|
 | 
						|
            [Tooltip("Offset vector for the associated effector when doing recoil.")]
 | 
						|
            public Vector3 offset;
 | 
						|
            [Tooltip("When firing before the last recoil has faded, how much of the current recoil offset will be maintained?")]
 | 
						|
            [Range(0f, 1f)] public float additivity = 1f;
 | 
						|
            [Tooltip("Max additive recoil for automatic fire.")]
 | 
						|
            public float maxAdditiveOffsetMag = 0.2f;
 | 
						|
 | 
						|
            // Linking this to an effector
 | 
						|
            [System.Serializable]
 | 
						|
            public class EffectorLink
 | 
						|
            {
 | 
						|
                [Tooltip("Type of the FBBIK effector to use")]
 | 
						|
                public FullBodyBipedEffector effector;
 | 
						|
                [Tooltip("Weight of using this effector")]
 | 
						|
                public float weight;
 | 
						|
            }
 | 
						|
 | 
						|
            [Tooltip("Linking this recoil offset to FBBIK effectors.")]
 | 
						|
            public EffectorLink[] effectorLinks;
 | 
						|
 | 
						|
            private Vector3 additiveOffset;
 | 
						|
            private Vector3 lastOffset;
 | 
						|
 | 
						|
            // Start recoil
 | 
						|
            public void Start()
 | 
						|
            {
 | 
						|
                if (additivity <= 0f) return;
 | 
						|
 | 
						|
                additiveOffset = Vector3.ClampMagnitude(lastOffset * additivity, maxAdditiveOffsetMag);
 | 
						|
            }
 | 
						|
 | 
						|
            // Apply offset to FBBIK effectors
 | 
						|
            public void Apply(IKSolverFullBodyBiped solver, Quaternion rotation, float masterWeight, float length, float timeLeft)
 | 
						|
            {
 | 
						|
                additiveOffset = Vector3.Lerp(Vector3.zero, additiveOffset, timeLeft / length);
 | 
						|
                lastOffset = (rotation * (offset * masterWeight)) + (rotation * additiveOffset);
 | 
						|
 | 
						|
                foreach (EffectorLink e in effectorLinks)
 | 
						|
                {
 | 
						|
                    solver.GetEffector(e.effector).positionOffset += lastOffset * e.weight;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        [System.Serializable]
 | 
						|
        public enum Handedness
 | 
						|
        {
 | 
						|
            Right,
 | 
						|
            Left
 | 
						|
        }
 | 
						|
 | 
						|
        [Tooltip("Reference to the AimIK component. Optional, only used to getting the aiming direction.")]
 | 
						|
        public AimIK aimIK;
 | 
						|
        [Tooltip("Optional head AimIK solver. This solver should only use neck and head bones and have the head as Aim Transform")]
 | 
						|
        public AimIK headIK;
 | 
						|
        [Tooltip("Set this true if you are using IKExecutionOrder.cs or a custom script to force AimIK solve after FBBIK.")]
 | 
						|
        public bool aimIKSolvedLast;
 | 
						|
        [Tooltip("Which hand is holding the weapon?")]
 | 
						|
        public Handedness handedness;
 | 
						|
        [Tooltip("Check for 2-handed weapons.")]
 | 
						|
        public bool twoHanded = true;
 | 
						|
        [Tooltip("Weight curve for the recoil offsets. Recoil procedure is as long as this curve.")]
 | 
						|
        public AnimationCurve recoilWeight;
 | 
						|
        [Tooltip("How much is the magnitude randomized each time Recoil is called?")]
 | 
						|
        public float magnitudeRandom = 0.1f;
 | 
						|
        [Tooltip("How much is the rotation randomized each time Recoil is called?")]
 | 
						|
        public Vector3 rotationRandom;
 | 
						|
        [Tooltip("Rotating the primary hand bone for the recoil (in local space).")]
 | 
						|
        public Vector3 handRotationOffset;
 | 
						|
        [Tooltip("Time of blending in another recoil when doing automatic fire.")]
 | 
						|
        public float blendTime;
 | 
						|
 | 
						|
        [Space(10)]
 | 
						|
 | 
						|
        [Tooltip("FBBIK effector position offsets for the recoil (in aiming direction space).")]
 | 
						|
        public RecoilOffset[] offsets;
 | 
						|
 | 
						|
        [HideInInspector] public Quaternion rotationOffset = Quaternion.identity;
 | 
						|
 | 
						|
        private float magnitudeMlp = 1f;
 | 
						|
        private float endTime = -1f;
 | 
						|
        private Quaternion handRotation, secondaryHandRelativeRotation, randomRotation;
 | 
						|
        private float length = 1f;
 | 
						|
        private bool initiated;
 | 
						|
        private float blendWeight;
 | 
						|
        private float w;
 | 
						|
        private Quaternion primaryHandRotation = Quaternion.identity;
 | 
						|
        //private Quaternion secondaryHandRotation = Quaternion.identity;
 | 
						|
        private bool handRotationsSet;
 | 
						|
        private Vector3 aimIKAxis;
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Returns true if recoil has finished or has not been called at all.
 | 
						|
        /// </summary>
 | 
						|
        public bool isFinished
 | 
						|
        {
 | 
						|
            get
 | 
						|
            {
 | 
						|
                return Time.time > endTime;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Sets the starting rotations for the hands for 1 frame. Use this if the final rotation of the hands will not be the same as before FBBIK solves.
 | 
						|
        /// </summary>
 | 
						|
        public void SetHandRotations(Quaternion leftHandRotation, Quaternion rightHandRotation)
 | 
						|
        {
 | 
						|
            if (handedness == Handedness.Left)
 | 
						|
            {
 | 
						|
                primaryHandRotation = leftHandRotation;
 | 
						|
                //secondaryHandRotation = rightHandRotation;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                primaryHandRotation = rightHandRotation;
 | 
						|
                //secondaryHandRotation = leftHandRotation;
 | 
						|
            }
 | 
						|
 | 
						|
            handRotationsSet = true;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Starts the recoil procedure.
 | 
						|
        /// </summary>
 | 
						|
        public void Fire(float magnitude)
 | 
						|
        {
 | 
						|
            float rnd = magnitude * UnityEngine.Random.value * magnitudeRandom;
 | 
						|
            magnitudeMlp = magnitude + rnd;
 | 
						|
 | 
						|
            randomRotation = Quaternion.Euler(rotationRandom * UnityEngine.Random.value);
 | 
						|
 | 
						|
            foreach (RecoilOffset offset in offsets)
 | 
						|
            {
 | 
						|
                offset.Start();
 | 
						|
            }
 | 
						|
 | 
						|
            if (Time.time < endTime) blendWeight = 0f;
 | 
						|
            else blendWeight = 1f;
 | 
						|
 | 
						|
            Keyframe[] keys = recoilWeight.keys;
 | 
						|
            length = keys[keys.Length - 1].time;
 | 
						|
            endTime = Time.time + length;
 | 
						|
        }
 | 
						|
 | 
						|
        protected override void OnModifyOffset()
 | 
						|
        {
 | 
						|
            if (aimIK != null) aimIKAxis = aimIK.solver.axis;
 | 
						|
 | 
						|
            if (!initiated && ik != null)
 | 
						|
            {
 | 
						|
                initiated = true;
 | 
						|
                if (headIK != null) headIK.enabled = false;
 | 
						|
                ik.solver.OnPostUpdate += AfterFBBIK;
 | 
						|
                if (aimIK != null) aimIK.solver.OnPostUpdate += AfterAimIK;
 | 
						|
            }
 | 
						|
 | 
						|
            if (Time.time >= endTime)
 | 
						|
            {
 | 
						|
                rotationOffset = Quaternion.identity;
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            blendTime = Mathf.Max(blendTime, 0f);
 | 
						|
            if (blendTime > 0f) blendWeight = Mathf.Min(blendWeight + Time.deltaTime * (1f / blendTime), 1f);
 | 
						|
            else blendWeight = 1f;
 | 
						|
 | 
						|
            // Current weight of offset
 | 
						|
            float wTarget = recoilWeight.Evaluate(length - (endTime - Time.time)) * magnitudeMlp;
 | 
						|
            w = Mathf.Lerp(w, wTarget, blendWeight);
 | 
						|
 | 
						|
            // Find the rotation space of the recoil
 | 
						|
            Quaternion lookRotation = aimIK != null && aimIK.solver.transform != null && !aimIKSolvedLast ? Quaternion.LookRotation(aimIK.solver.IKPosition - aimIK.solver.transform.position, ik.references.root.up) : ik.references.root.rotation;
 | 
						|
            lookRotation = randomRotation * lookRotation;
 | 
						|
 | 
						|
            // Apply FBBIK effector positionOffsets
 | 
						|
            foreach (RecoilOffset offset in offsets)
 | 
						|
            {
 | 
						|
                offset.Apply(ik.solver, lookRotation, w, length, endTime - Time.time);
 | 
						|
            }
 | 
						|
 | 
						|
            if (!handRotationsSet)
 | 
						|
            {
 | 
						|
                primaryHandRotation = primaryHand.rotation;
 | 
						|
                //if (twoHanded) secondaryHandRotation = secondaryHand.rotation;
 | 
						|
            }
 | 
						|
            handRotationsSet = false;
 | 
						|
 | 
						|
            // Rotation offset of the primary hand
 | 
						|
            rotationOffset = Quaternion.Lerp(Quaternion.identity, Quaternion.Euler(randomRotation * primaryHandRotation * handRotationOffset), w);
 | 
						|
            handRotation = rotationOffset * primaryHandRotation;
 | 
						|
 | 
						|
            // Fix the secondary hand relative to the primary hand
 | 
						|
            if (twoHanded)
 | 
						|
            {
 | 
						|
                Vector3 secondaryHandRelativePosition = Quaternion.Inverse(primaryHand.rotation) * (secondaryHand.position - primaryHand.position);
 | 
						|
                secondaryHandRelativeRotation = Quaternion.Inverse(primaryHand.rotation) * secondaryHand.rotation;
 | 
						|
 | 
						|
                Vector3 primaryHandPosition = primaryHand.position + primaryHandEffector.positionOffset;
 | 
						|
                Vector3 secondaryHandPosition = primaryHandPosition + handRotation * secondaryHandRelativePosition;
 | 
						|
 | 
						|
                secondaryHandEffector.positionOffset += secondaryHandPosition - (secondaryHand.position + secondaryHandEffector.positionOffset);
 | 
						|
            }
 | 
						|
 | 
						|
            if (aimIK != null && aimIKSolvedLast) aimIK.solver.axis = Quaternion.Inverse(ik.references.root.rotation) * Quaternion.Inverse(rotationOffset) * aimIKAxis;
 | 
						|
        }
 | 
						|
 | 
						|
        private void AfterFBBIK()
 | 
						|
        {
 | 
						|
            if (Time.time < endTime)
 | 
						|
            {
 | 
						|
                // Rotate the hand bones
 | 
						|
                primaryHand.rotation = handRotation;
 | 
						|
                if (twoHanded) secondaryHand.rotation = primaryHand.rotation * secondaryHandRelativeRotation;
 | 
						|
            }
 | 
						|
 | 
						|
            if (!aimIKSolvedLast && headIK != null) headIK.solver.Update();
 | 
						|
        }
 | 
						|
 | 
						|
        private void AfterAimIK()
 | 
						|
        {
 | 
						|
            if (aimIKSolvedLast) aimIK.solver.axis = aimIKAxis;
 | 
						|
            if (aimIKSolvedLast && headIK != null) headIK.solver.Update();
 | 
						|
        }
 | 
						|
 | 
						|
        // Shortcuts
 | 
						|
        private IKEffector primaryHandEffector
 | 
						|
        {
 | 
						|
            get
 | 
						|
            {
 | 
						|
                if (handedness == Handedness.Right) return ik.solver.rightHandEffector;
 | 
						|
                return ik.solver.leftHandEffector;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        private IKEffector secondaryHandEffector
 | 
						|
        {
 | 
						|
            get
 | 
						|
            {
 | 
						|
                if (handedness == Handedness.Right) return ik.solver.leftHandEffector;
 | 
						|
                return ik.solver.rightHandEffector;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        private Transform primaryHand
 | 
						|
        {
 | 
						|
            get
 | 
						|
            {
 | 
						|
                return primaryHandEffector.bone;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        private Transform secondaryHand
 | 
						|
        {
 | 
						|
            get
 | 
						|
            {
 | 
						|
                return secondaryHandEffector.bone;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        protected override void OnDestroy()
 | 
						|
        {
 | 
						|
            base.OnDestroy();
 | 
						|
            if (ik != null && initiated)
 | 
						|
            {
 | 
						|
                ik.solver.OnPostUpdate -= AfterFBBIK;
 | 
						|
                if (aimIK != null) aimIK.solver.OnPostUpdate -= AfterAimIK;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
    }
 | 
						|
}
 |