强大的程序腿效果
程序脚完全使用算法完成,没有复杂的物理运算
3D蜘蛛脚算法
public class SpiderProceduralAnimation : MonoBehaviour
{
public Transform[] legTargets;
public float stepSize = 0.15f;
public int smoothness = 8;
public float stepHeight = 0.15f;
public float sphereCastRadius = 0.125f;
public bool bodyOrientation = true;
public float raycastRange = 1.5f;
private Vector3[] defaultLegPositions;
private Vector3[] lastLegPositions;
private Vector3 lastBodyUp;
private bool[] legMoving;
private int nbLegs;
private Vector3 velocity;
private Vector3 lastVelocity;
private Vector3 lastBodyPos;
private float velocityMultiplier = 15f;
Vector3[] MatchToSurfaceFromAbove(Vector3 point, float halfRange, Vector3 up)
{
Vector3[] res = new Vector3[2];
res[1] = Vector3.zero;
RaycastHit hit;
Ray ray = new Ray(point + halfRange * up / 2f, -up);
if (Physics.SphereCast(ray, sphereCastRadius, out hit, 2f * halfRange))
{
res[0] = hit.point;
res[1] = hit.normal;
}
else
{
res[0] = point;
}
return res;
}
void Start()
{
lastBodyUp = transform.up;
nbLegs = legTargets.Length;
defaultLegPositions = new Vector3[nbLegs];
lastLegPositions = new Vector3[nbLegs];
legMoving = new bool[nbLegs];
for (int i = 0; i < nbLegs; ++i)
{
defaultLegPositions[i] = legTargets[i].localPosition;
lastLegPositions[i] = legTargets[i].position;
legMoving[i] = false;
}
lastBodyPos = transform.position;
}
IEnumerator PerformStep(int index, Vector3 targetPoint)
{
Vector3 startPos = lastLegPositions[index];
for (int i = 1; i <= smoothness; ++i)
{
legTargets[index].position = Vector3.Lerp(startPos, targetPoint, i / (float)(smoothness + 1f));
legTargets[index].position += transform.up * Mathf.Sin(i / (float)(smoothness + 1f) * Mathf.PI) * stepHeight;
yield return new WaitForFixedUpdate();
}
legTargets[index].position = targetPoint;
lastLegPositions[index] = legTargets[index].position;
legMoving[0] = false;
}
void FixedUpdate()
{
velocity = transform.position - lastBodyPos;
velocity = (velocity + smoothness * lastVelocity) / (smoothness + 1f);
if (velocity.magnitude < 0.000025f)
velocity = lastVelocity;
else
lastVelocity = velocity;
Vector3[] desiredPositions = new Vector3[nbLegs];
int indexToMove = -1;
float maxDistance = stepSize;
for (int i = 0; i < nbLegs; ++i)
{
desiredPositions[i] = transform.TransformPoint(defaultLegPositions[i]);
float distance = Vector3.ProjectOnPlane(desiredPositions[i] + velocity * velocityMultiplier - lastLegPositions[i], transform.up).magnitude;
if (distance > maxDistance)
{
maxDistance = distance;
indexToMove = i;
}
}
for (int i = 0; i < nbLegs; ++i)
if (i != indexToMove)
legTargets[i].position = lastLegPositions[i];
if (indexToMove != -1 && !legMoving[0])
{
Vector3 targetPoint = desiredPositions[indexToMove] + Mathf.Clamp(velocity.magnitude * velocityMultiplier, 0.0f, 1.5f) * (desiredPositions[indexToMove] - legTargets[indexToMove].position) + velocity * velocityMultiplier;
Vector3[] positionAndNormalFwd = MatchToSurfaceFromAbove(targetPoint + velocity * velocityMultiplier, raycastRange, (transform.parent.up - velocity * 100).normalized);
Vector3[] positionAndNormalBwd = MatchToSurfaceFromAbove(targetPoint + velocity * velocityMultiplier, raycastRange * (1f + velocity.magnitude), (transform.parent.up + velocity * 75).normalized);
legMoving[0] = true;
if (positionAndNormalFwd[1] == Vector3.zero)
{
StartCoroutine(PerformStep(indexToMove, positionAndNormalBwd[0]));
}
else
{
StartCoroutine(PerformStep(indexToMove, positionAndNormalFwd[0]));
}
}
lastBodyPos = transform.position;
if (nbLegs > 3 && bodyOrientation)
{
Vector3 v1 = legTargets[0].position - legTargets[1].position;
Vector3 v2 = legTargets[2].position - legTargets[3].position;
Vector3 normal = Vector3.Cross(v1, v2).normalized;
Vector3 up = Vector3.Lerp(lastBodyUp, normal, 1f / (float)(smoothness + 1));
transform.up = up;
transform.rotation = Quaternion.LookRotation(transform.parent.forward, up);
lastBodyUp = transform.up;
}
}
private void OnDrawGizmosSelected()
{
for (int i = 0; i < nbLegs; ++i)
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(legTargets[i].position, 0.05f);
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(transform.TransformPoint(defaultLegPositions[i]), stepSize);
}
}
}
2D蜘蛛脚算法
public class SpiderProceduralAnimation : MonoBehaviour
{
public Transform[] legTargets;
public float stepSize = 0.15f;
public int smoothness = 8;
public float stepHeight = 0.15f;
public bool bodyOrientation = true;
public float raycastRange = 1.5f;
private Vector2[] defaultLegPositions;
private Vector2[] lastLegPositions;
private Vector2 lastBodyUp;
private bool[] legMoving;
private int nbLegs;
private Vector2 velocity;
private Vector2 lastVelocity;
private Vector2 lastBodyPos;
private float velocityMultiplier = 7f;
Vector2[] MatchToSurfaceFromAbove(Vector2 point, float halfRange, Vector2 up)
{
Vector2[] res = new Vector2[2];
res[1] = Vector3.zero;
RaycastHit2D hit;
hit = Physics2D.Raycast(point + halfRange * up / 2f, -up, 2f * halfRange);
Debug.DrawRay(point + halfRange * up / 2f, -up * 2f * halfRange, Color.red, smoothness * Time.deltaTime);
if (hit.collider)
{
res[0] = hit.point;
res[1] = hit.normal;
}
else
{
res[0] = point;
}
return res;
}
void Start()
{
lastBodyUp = transform.up;
nbLegs = legTargets.Length;
defaultLegPositions = new Vector2[nbLegs];
lastLegPositions = new Vector2[nbLegs];
legMoving = new bool[nbLegs];
for (int i = 0; i < nbLegs; ++i)
{
defaultLegPositions[i] = legTargets[i].localPosition;
lastLegPositions[i] = legTargets[i].position;
legMoving[i] = false;
}
lastBodyPos = transform.position;
}
IEnumerator PerformStep(int index, Vector3 targetPoint)
{
Vector3 startPos = lastLegPositions[index];
for (int i = 1; i <= smoothness; ++i)
{
legTargets[index].position = Vector3.Lerp(startPos, targetPoint, i / (float)(smoothness + 1f));
legTargets[index].position += transform.up * Mathf.Sin(i / (float)(smoothness + 1f) * Mathf.PI) * stepHeight;
yield return new WaitForFixedUpdate();
}
legTargets[index].position = targetPoint;
lastLegPositions[index] = legTargets[index].position;
legMoving[0] = false;
}
void FixedUpdate()
{
velocity = (Vector2)transform.position - lastBodyPos;
velocity = (velocity + smoothness * lastVelocity) / (smoothness + 1f);
if (velocity.magnitude < 0.000025f)
velocity = lastVelocity;
else
lastVelocity = velocity;
Vector2[] desiredPositions = new Vector2[nbLegs];
int indexToMove = -1;
float maxDistance = stepSize;
for (int i = 0; i < nbLegs; ++i)
{
desiredPositions[i] = transform.TransformPoint(defaultLegPositions[i]);
float distance = Vector3.ProjectOnPlane(desiredPositions[i] + velocity * velocityMultiplier - lastLegPositions[i], transform.up).magnitude;
if (distance > maxDistance)
{
maxDistance = distance;
indexToMove = i;
}
}
for (int i = 0; i < nbLegs; ++i)
if (i != indexToMove)
legTargets[i].position = lastLegPositions[i];
if (indexToMove != -1 && !legMoving[0])
{
Vector2 targetPoint = desiredPositions[indexToMove] + Mathf.Clamp(velocity.magnitude * velocityMultiplier, 0.0f, 1.5f) * (desiredPositions[indexToMove] -
(Vector2)legTargets[indexToMove].position) + velocity * velocityMultiplier;
Vector2[] positionAndNormalFwd = MatchToSurfaceFromAbove(targetPoint + velocity * velocityMultiplier, raycastRange,
((Vector2)transform.parent.up - velocity * 10).normalized);
Vector2[] positionAndNormalBwd = MatchToSurfaceFromAbove(targetPoint + velocity * velocityMultiplier, raycastRange * (1f + velocity.magnitude),
((Vector2)transform.parent.up + velocity * 10).normalized);
legMoving[0] = true;
if (positionAndNormalFwd[1] == Vector2.zero)
{
StartCoroutine(PerformStep(indexToMove, positionAndNormalBwd[0]));
}
else
{
StartCoroutine(PerformStep(indexToMove, positionAndNormalFwd[0]));
}
}
lastBodyPos = transform.position;
if (nbLegs > 1 && bodyOrientation)
{
Vector2 v1 = (legTargets[1].position - legTargets[0].position).normalized;
Vector3 v2 = Vector3.back;
Vector3 normal = Vector3.Cross(v1, v2).normalized;
Vector3 up = Vector3.Lerp(lastBodyUp, normal, 1f / (float)(smoothness + 1));
transform.up = up;
transform.rotation = Quaternion.LookRotation(transform.parent.forward, up);
lastBodyUp = transform.up;
}
}
private void OnDrawGizmosSelected()
{
for (int i = 0; i < nbLegs; ++i)
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(legTargets[i].position, 0.05f);
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(transform.TransformPoint(defaultLegPositions[i]), stepSize);
}
}
}
人物程序动画算法
public class ProceduralAnimation : MonoBehaviour
{
/* Some useful functions we may need */
static Vector3[] CastOnSurface(Vector3 point, float halfRange, Vector3 up)
{
Vector3[] res = new Vector3[2];
RaycastHit hit;
Ray ray = new Ray(new Vector3(point.x, point.y + halfRange, point.z), -up);
if (Physics.Raycast(ray, out hit, 2f * halfRange))
{
res[0] = hit.point;
res[1] = hit.normal;
}
else
{
res[0] = point;
}
return res;
}
/*************************************/
public Transform leftFootTarget;
public Transform rightFootTarget;
public Transform leftFootTargetRig;
public Transform rightFootTargetRig;
public Transform pivot;
public Transform scaler;
public float smoothness = 2f;
public float stepHeight = 0.2f;
public float stepLength = 1f;
public float angularSpeed = 0.1f;
public float velocityMultiplier = 80f;
public float bounceAmplitude = 0.05f;
public bool running = false;
private Vector3 initLeftFootPos;
private Vector3 initRightFootPos;
private Vector3 lastLeftFootPos;
private Vector3 lastRightFootPos;
private Vector3 lastBodyPos;
private Vector3 initBodyPos;
private Vector3 velocity;
private Vector3 lastVelocity;
// Start is called before the first frame update
void Start()
{
initLeftFootPos = leftFootTarget.localPosition;
initRightFootPos = rightFootTarget.localPosition;
lastLeftFootPos = leftFootTarget.position;
lastRightFootPos = rightFootTarget.position;
lastBodyPos = transform.position;
initBodyPos = transform.localPosition;
}
// Update is called once per frame
void FixedUpdate()
{
velocity = transform.position - lastBodyPos;
velocity *= velocityMultiplier;
velocity = (velocity + smoothness * lastVelocity) / (smoothness + 1f);
if (velocity.magnitude < 0.000025f * velocityMultiplier)
velocity = lastVelocity;
lastVelocity = velocity;
int sign = (Vector3.Dot(velocity.normalized, transform.forward) < 0 ? -1 : 1);
scaler.localScale = new Vector3(scaler.localScale.x, stepHeight * 2f * velocity.magnitude, stepLength * velocity.magnitude);
scaler.rotation = Quaternion.LookRotation(sign * velocity.normalized, Vector3.up);
pivot.Rotate(Vector3.right, sign * angularSpeed, Space.Self);
Vector3 desiredPositionLeft = leftFootTarget.position;
Vector3 desiredPositionRight = rightFootTarget.position;
Vector3[] posNormLeft = CastOnSurface(desiredPositionLeft, 2f, Vector3.up);
if (posNormLeft[0].y > desiredPositionLeft.y)
{
leftFootTargetRig.position = posNormLeft[0];
}
else
{
leftFootTargetRig.position = desiredPositionLeft;
}
if (posNormLeft[1] != Vector3.zero)
{
leftFootTargetRig.rotation = Quaternion.LookRotation(sign * velocity.normalized, posNormLeft[1]);
}
Vector3[] posNormRight = CastOnSurface(desiredPositionRight, 2f, Vector3.up);
if (posNormRight[0].y > desiredPositionRight.y)
{
rightFootTargetRig.position = posNormRight[0];
}
else
{
rightFootTargetRig.position = desiredPositionRight;
}
if(posNormRight[1] != Vector3.zero)
{
rightFootTargetRig.rotation = Quaternion.LookRotation(sign * velocity.normalized, posNormRight[1]);
}
lastLeftFootPos = leftFootTargetRig.position;
lastRightFootPos = rightFootTargetRig.position;
float feetDistance = Mathf.Clamp01(Mathf.Abs(leftFootTargetRig.localPosition.z - rightFootTargetRig.localPosition.z) / (stepLength / 4f));
float heightReduction = (running ? Mathf.Clamp01(velocity.magnitude) * bounceAmplitude - bounceAmplitude * Mathf.Clamp(velocity.magnitude, 0f, 10f) * feetDistance : bounceAmplitude * Mathf.Clamp(velocity.magnitude, 0f, 10f) * feetDistance);
transform.localPosition = initBodyPos - heightReduction * Vector3.up;
scaler.localPosition = new Vector3(0f, heightReduction, 0f);
lastBodyPos = transform.position;
}
private void OnDrawGizmosSelected()
{
Gizmos.DrawWireSphere(leftFootTarget.position, 0.2f);
Gizmos.DrawWireSphere(rightFootTarget.position, 0.2f);
}
}
哇塞这么多代码,头晕,搞不明白,看不太懂
没事的! demo来啦~
点击👇
程序脚Demo