I programmed a procedurally animated spider by the use of an inverse kinematics (IK) limb alghorithm. With the use of animations curves and variables the movement of the limbs can be tweaked. The spider can climb hills too. This was an experimental project made with Unity.

IK and Spider Controller Code
Basic algorithm for inverse kinematics for a chain or limb.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InverseKinematicsSolver : MonoBehaviour {
[SerializeField] private int chainLength = 2;
[SerializeField] private Transform target;
[SerializeField] private Transform pole;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InverseKinematicsSolver : MonoBehaviour {
[SerializeField] private int chainLength = 2;
[SerializeField] private Transform target;
[SerializeField] private Transform pole;
[SerializeField] private Transform[] bones;
[SerializeField] private Vector3[] positions;
[SerializeField] private float[] bonesLength;
[SerializeField] private float completeLength;
private void Awake() {
Init();
}
private void Init() {
bones = new Transform[chainLength + 1];
positions = new Vector3[chainLength + 1];
bonesLength = new float[chainLength];
completeLength = 0;
var current = this.transform;
for (var i = bones.Length - 1; i >= 0; i--) {
bones[i] = current;
if (i == bones.Length - 1) {
} else {
bonesLength[i] = (bones[i + 1].position - current.position).magnitude;
completeLength += bonesLength[i];
}
current = current.parent;
}
}
private void LateUpdate() {
ResolveIK();
}
private void ResolveIK() {
if (target == null) {
return;
}
if (bonesLength.Length != chainLength) {
Init();
}
for (int i = 0; i < bones.Length; i++) {
positions[i] = bones[i].position;
}
if ((target.position - bones[0].position).sqrMagnitude >= completeLength * completeLength) {
var direction = (target.position - positions[0]).normalized;
for (int i = 1; i < positions.Length; i++) {
positions[i] = positions[i - 1] + direction * bonesLength[i - 1];
}
}
}
}
The procedural controller. It controls where the IK targets and body of the spider will be positioned. It is very tweakable with many variables and animation curves for the way that the spider moves the feet to new target positions.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ProceduralSpiderController : MonoBehaviour {
[Header("Spider joint targets")]
[SerializeField] private Transform[] targets;
[SerializeField] private Transform[] IKtargets;
[SerializeField] private Transform[] hitTargets;
[SerializeField] private Transform[] feet;
[SerializeField] private Transform body;
[Header("Other variables")]
[SerializeField] private float bodyOffset = 3f;
[SerializeField] private float stepDistance;
[SerializeField] private LayerMask rayLayerMask;
[Header("Procedural movement animations")]
[SerializeField] private AnimationCurve limbIKAnimationXZ;
[SerializeField] private AnimationCurve limbIKAnimationY;
[SerializeField] private AnimationCurve footRotationAnimation;
[SerializeField] private float animationLength = 0.5f;
private Vector3[] startMovePositions;
private Vector3[] targetPositions;
private Quaternion[] startFootRotations;
private Quaternion[] targetFootRotations;
private float[] timers;
private float[] distances;
private bool animSwitch;
void Start() {
distances = new float[targets.Length];
timers = new float[targets.Length];
startMovePositions = new Vector3[targets.Length];
targetFootRotations = new Quaternion[targets.Length];
startFootRotations = new Quaternion[targets.Length];
for (int i = 0; i < timers.Length; i++) {
timers[i] = 1;
}
for (int i = 0; i < timers.Length; i++) {
startMovePositions[i] = IKtargets[i].position;
startFootRotations[i] = feet[i].rotation;
}
targetPositions = new Vector3[targets.Length];
for (int i = 0; i < targets.Length; i++) {
targetPositions[i] = targets[i].position;
}
animSwitch = true;
}
void Update() {
Vector3 movement = new Vector3((Input.GetKey(KeyCode.A) ? -1f : 0f) + (Input.GetKey(KeyCode.D) ? 1f : 0f), 0, (Input.GetKey(KeyCode.S) ? -1f : 0f) + (Input.GetKey(KeyCode.W) ? 1f : 0f)) * Time.deltaTime;
transform.position += movement * 10f;
CalculateTargetPositions();
MoveJointTargets();
}
private void CalculateTargetPositions() {
for (int i = 0; i < timers.Length; i++) {
if (timers[i] < animationLength && timers[i] >= 0) {
Vector3 nextPosition = Vector3.Lerp(startMovePositions[i], targetPositions[i] + hitTargets[i].up * 2.3f, limbIKAnimationXZ.Evaluate(timers[i] / animationLength));
Quaternion nextFootRotation = Quaternion.Slerp(startFootRotations[i], targetFootRotations[i], footRotationAnimation.Evaluate(timers[i] / animationLength));
nextPosition += Vector3.up * (limbIKAnimationY.Evaluate(timers[i] / animationLength));
IKtargets[i].position = nextPosition;
feet[i].rotation = nextFootRotation;
RaycastHit footHit;
if (Physics.Raycast(targets[i].position + targets[i].up * 2.3f, -targets[i].up, out footHit, 100f, rayLayerMask)) {
Debug.Log("Hit");
targetFootRotations[i] = Quaternion.FromToRotation(Vector3.right, -footHit.normal);
}
timers[i] += Time.deltaTime;
} else {
timers[i] = animationLength;
}
}
}
private void MoveJointTargets() {
RaycastHit hit;
for (int i = 0; i < IKtargets.Length; i++) {
if (Physics.Raycast(targets[i].position + targets[i].up * 20f, -targets[i].up, out hit, 40f, rayLayerMask)) {
targetPositions[i] = hit.point;
hitTargets[i].position = hit.point;
hitTargets[i].rotation = Quaternion.FromToRotation(Vector3.up, hit.normal);
}
Vector3 targetPos = targetPositions[i];
Vector3 IKPos = IKtargets[i].position;
distances[i] = Vector3.Distance(targetPos, IKPos);
if (distances[i] > stepDistance && timers[i] >= animationLength && AreOppositeLegsGrounded(i)) {
startMovePositions[i] = IKtargets[i].position;
startFootRotations[i] = feet[i].rotation;
timers[i] = 0;
}
}
body.position = new Vector3(body.position.x, AveragePosition(targetPositions).y + bodyOffset, body.position.z);
float x;
float z;
Vector3 a = (targetPositions[0] + targetPositions[1]) / 2;
Vector3 b = (targetPositions[2] + targetPositions[3]) / 2;
Vector3 forward = new Vector3(body.forward.x, (a - b).y, body.forward.z);
float xRotation = Vector3.Angle(a - b, forward);
Quaternion rotation = Quaternion.LookRotation(a - b, Vector3.up);
Vector3 c = (targetPositions[0] + targetPositions[2]) / 2;
Vector3 d = (targetPositions[1] + targetPositions[3]) / 2;
Vector3 right = new Vector3(body.right.x, (c - d).y, body.right.z);
float zRotation = Vector3.Angle(c - d, right);
Quaternion addedZRotation = Quaternion.LookRotation(d - c, Vector3.up);
body.eulerAngles = new Vector3(rotation.eulerAngles.x, 0, addedZRotation.eulerAngles.x);
}
private bool AreOppositeLegsGrounded(int current) {
bool grounded = true;
for (int i = 0; i < timers.Length; i++) {
if (grounded) {
if (i == 1 || i == 2) {
grounded = ((timers[0] >= animationLength) || (timers[3] >= animationLength));
}
if (i == 0 || i == 3) {
grounded = ((timers[1] >= animationLength) || (timers[2] >= animationLength));
}
}
}
return grounded;
}
private Vector3 AveragePosition(Vector3[] targets) {
Vector3 average;
average = Vector3.zero;
foreach (var target in targets) {
average += target;
}
average /= targets.Length;
return average;
}
private Vector3 GetRotation(Transform[] targets) {
Vector3 rotation;
float x;
float z;
Vector3 a = (targets[0].position + targets[1].position) / 2;
Vector3 b = (targets[2].position + targets[3].position) / 2;
x = Vector3.Angle(b - a, new Vector3((b - a).x, 0, (b - a).z));
Vector3 c = (targets[0].position + targets[2].position) / 2;
Vector3 d = (targets[1].position + targets[3].position) / 2;
z = Mathf.DeltaAngle(c.y, d.y);
rotation = new Vector3(x, 0, z);
return rotation;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spawner : MonoBehaviour {
public bool startSpawning = true;
public bool isSpawning;
public float spawnRate = 1f;
public GameObject objectToSpawn;
public Transform parent;
private void OnEnable() {
if (startSpawning)
StartCoroutine("Spawn");
}
private void OnDestroy() {
isSpawning = false;
StopAllCoroutines();
}
private void OnDisable() {
isSpawning = false;
StopAllCoroutines();
}
IEnumerator Spawn() {
//waiting to spawn
Instantiate(objectToSpawn, transform.position, Quaternion.identity, parent);
yield return new WaitForSeconds(spawnRate);
if (gameObject.activeInHierarchy)
StartCoroutine("Spawn");
}
public void StartSpawning() {
isSpawning = true;
StartCoroutine("Spawn");
}
public void StopSpawning() {
//isSpawning = false;
StopAllCoroutines();
}
}