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;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|