Skip to content

Instantly share code, notes, and snippets.

@maxattack
Created April 18, 2016 05:16
Show Gist options
  • Save maxattack/48396825a644e1bc2e1b647e5af4fc13 to your computer and use it in GitHub Desktop.
Save maxattack/48396825a644e1bc2e1b647e5af4fc13 to your computer and use it in GitHub Desktop.
Scrapped LD35 Compo Idea // Robot Pants
using InControl;
using System.Collections;
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class RobotPants : MonoBehaviour {
public Transform hips;
public TwoBoneIK legLeft;
public TwoBoneIK legRight;
public float maxSpeed = 4f;
public float crouchTime = 1.333f;
public enum Status {
Idle,
Crouching,
Jumping
}
internal Status status = Status.Idle;
// root motion
internal CharacterController controller;
internal Quaternion targetRotation;
internal Quaternion rotSpeed = new Quaternion(0f, 0f, 0f, 0f);
internal float yspeed = 0f;
// hip damping
public float hipDampTime = 0.333f;
internal float hipOff, hipHeight, hipSpeed, hipSwayAngle;
// stepping
public float stepDuration = 1f;
internal TwoBoneIK leg = null;
internal bool wantStep = false;
internal bool stepping = false;
internal bool didLastStep = false;
internal float lastStepTime;
internal float landingTime = -999f;
void Start() {
// init root motion
controller = GetComponent<CharacterController>();
targetRotation = transform.rotation;
// init hips
hips.transform.localPosition = 0.88f * hips.transform.localPosition;
hipHeight = hips.transform.position.y;
hipOff = hipHeight - transform.position.y;
status = Status.Idle;
StartCoroutine(StateMachine());
}
IEnumerator StateMachine() {
for(;;) {
status = Status.Idle;
do {
yield return null;
} while(!(controller.isGrounded && JumpButton.IsPressed));
status = Status.Crouching;
yield return new WaitForSeconds(crouchTime);
status = Status.Jumping;
legLeft.StartJump(this);
legRight.StartJump(this);
yspeed = 15f;
yield return null;
yield return null;
yield return null;
while(!controller.isGrounded) {
yield return null;
}
landingTime = Time.time;
}
}
void Update() {
// update rotation
var input = CalcCameraRelativeInput();
if (status != Status.Idle) {
input = Vector3.zero;
}
var rot = transform.rotation;
if (input.sqrMagnitude > 0.85f * 0.85f) {
targetRotation = Quaternion.LookRotation(input);
} else {
targetRotation = Geom.IntegrateRotation(rot, rotSpeed, 0.0666f);
}
rot = Geom.SmoothDamp(rot, targetRotation, ref rotSpeed, 0.233f);
transform.rotation = rot;
// update location
var fwd = rot * Vector3.forward;
var speedMulti = Mathf.Abs(Vector3.Dot(fwd, input));
var targetSpeed = maxSpeed * speedMulti;
yspeed += Physics.gravity.y * Time.deltaTime;
var vel = new Vector3(0, yspeed, 0);
if (status != Status.Jumping) {
vel += targetSpeed * fwd;
} else {
vel += 1.666f * maxSpeed * transform.forward;
}
controller.Move(vel * Time.deltaTime);
if (yspeed < 0f && controller.isGrounded) {
yspeed = 0f;
}
UpdateSteps();
UpdateHips();
}
void UpdateSteps() {
var rot = transform.rotation;
var speed = 1.0f * controller.velocity.magnitude + 0.5f * Geom.DerivToAngVel(rot, rotSpeed).magnitude;
wantStep = status == Status.Idle && (wantStep ? speed > 0.2f : speed > 0.35f);
if (!stepping) {
if (wantStep) {
didLastStep = false;
stepping = true;
leg = leg == legRight ? legLeft : legRight;
leg.StartStep(false, stepDuration);
}
} else if (!leg.stepping) {
var startNext = wantStep;
if (wantStep) {
didLastStep = false;
} else if (!didLastStep) {
didLastStep = true;
startNext = true;
}
if (startNext) {
leg = leg == legRight ? legLeft : legRight;
leg.StartStep(didLastStep, stepDuration);
} else {
stepping = false;
}
}
}
void UpdateHips() {
switch(status) {
case Status.Crouching: {
var targetHipHeight = transform.position.y + 0.25f * hipOff;
hipHeight = Mathf.SmoothDamp(hipHeight, targetHipHeight, ref hipSpeed, 0.1f);
break;
}
default: {
hipSwayAngle = 8f * (legRight.stepAmount - legLeft.stepAmount);
hips.localRotation = Quaternion.AngleAxis(hipSwayAngle, Vector3.forward);
var targetHipHeight = transform.position.y + hipOff;
var timeSinceLanding = Util.TimeSince(landingTime) / 0.333f;
if (timeSinceLanding < 1f) {
var landingRecover = Geom.EaseInOut(timeSinceLanding);
targetHipHeight -= (1f - landingRecover) * 0.22f;
}
if (targetHipHeight < hipHeight) {
hipSpeed = (targetHipHeight - hipHeight) / Time.deltaTime;
hipHeight = targetHipHeight;
} else {
hipHeight = Mathf.SmoothDamp(hipHeight, targetHipHeight, ref hipSpeed, 0.25f);
}
break;
}
}
var idleOscillation = 0.0333f * Mathf.Sin(Time.time);
var hipPos = hips.transform.position;
hipPos.y = hipHeight + idleOscillation;
hips.transform.position = hipPos;
}
// helpers
static Vector3 CalcCameraRelativeInput() {
var ls = InputManager.ActiveDevice.LeftStick.Value;
var sz = ls.magnitude;
if (sz > 1.0f) {
ls /= sz;
}
return Util.GetCameraRelative(ls);
}
static InputControl JumpButton {
get { return InputManager.ActiveDevice.Action1; }
}
}
using UnityEngine;
using System.Collections;
public class TwoBoneIK : MonoBehaviour {
public Transform joint1;
public Transform joint2;
public Transform joint3;
float upperLength = 1f;
float lowerLength = 1f;
internal Vector3 effPos;
internal Quaternion effRot;
internal bool effOutOfRange;
internal bool stepping;
internal bool jumping;
internal Vector3 prevPos, rawVelocity;
internal float prevTime;
static Quaternion MakeFromYZ(Vector3 up, Vector3 forward)
{
var right = Vector3.Cross(up, forward);
var fwd = Vector3.Cross(right, up);
return Quaternion.LookRotation(fwd, up);
}
void Awake() {
upperLength = Vector3.Distance(joint1.position, joint2.position);
lowerLength = Vector3.Distance(joint2.position, joint3.position);
effPos = joint3.position;
effRot = joint3.rotation;
prevPos = transform.position;
prevTime = Time.time;
rawVelocity = Vector3.zero;
}
float MaxLength { get { return upperLength + lowerLength - 0.001f; } }
void LateUpdate() {
var hipPos = transform.position;
var off = effPos - hipPos;
var len = off.magnitude;
var dir = off.normalized;
var maxLength = MaxLength;
var footPos = effPos;
var effDir = effRot * Vector3.forward;
var footDir = effDir;
effOutOfRange = len > maxLength;
if (effOutOfRange) {
footPos = hipPos + maxLength * dir;
var slop = len - maxLength;
var u = Mathf.Clamp01(slop);
u = 1f - (1f - u) * (1f - u);
footDir = Vector3.Slerp(footDir, (effPos - footPos).normalized, 0.65f * u);
}
var alpha = upperLength / (upperLength + lowerLength);
var midpoint = hipPos + alpha * (footPos - hipPos);
var dist = Vector3.Distance(hipPos, midpoint);
var angle = Mathf.Acos(dist / upperLength);
var kneeDist = upperLength * Mathf.Sin(angle);
var rotation = MakeFromYZ(-dir, effDir);
var kneeOff = rotation * new Vector3(0f, 0f, kneeDist);
var kneePos = midpoint + kneeOff;
//Debug.DrawLine(hipPos, kneePos, Color.white);
//Debug.DrawLine(kneePos, footPos);
joint1.rotation = MakeFromYZ(hipPos - kneePos, effDir);
joint2.position = kneePos;
joint2.rotation = MakeFromYZ(kneePos - footPos, effDir);
joint3.position = footPos;
var footUp = effRot * Vector3.up;
if (Mathf.Abs(Vector3.Dot(footUp, footDir)) > 0.99f) {
footUp = transform.forward;
}
joint3.rotation = Quaternion.LookRotation(footDir, footUp); //-dir);
var dt = Time.time - prevTime;
if (dt > Mathf.Epsilon) {
rawVelocity = (hipPos - prevPos) / dt;
}
prevPos = hipPos;
prevTime = Time.time;
}
public void StartStep(bool littleOne, float duration) {
StartCoroutine(DoStep(littleOne, duration));
}
internal float stepAmount = 0f;
bool TraceDown(out RaycastHit hit, float distance = 100f) {
//return Physics.Raycast(new Ray(transform.position, Vector3.down), out hit, distance, 1, QueryTriggerInteraction.Ignore);
return Physics.SphereCast(new Ray(transform.position, Vector3.down), 0.35f, out hit, distance, 1, QueryTriggerInteraction.Ignore);
}
IEnumerator DoStep(bool littleOne, float duration) {
while(stepping) {
yield return null;
}
stepping = true;
while(jumping) {
yield return null;
}
var savedPos = effPos;
var savedRot = effRot;
var ratio = littleOne ? 0.1333f : 0.4f;
foreach(var alpha in Util.Interpolate(duration)) {
var hipPos = transform.position + 0.075f * rawVelocity;
var maxLength = MaxLength;
RaycastHit hit;
var didHit = TraceDown(out hit);
var targetPos = didHit ? hit.point : hipPos + maxLength * Vector3.down;
var targetRot = didHit ? MakeFromYZ(hit.normal, transform.forward) : transform.rotation;
var lerpPos = Vector3.LerpUnclamped(savedPos, targetPos, alpha);
var lerpRot = Quaternion.SlerpUnclamped(savedRot, targetRot, alpha);
stepAmount = Geom.UnitParabola(Geom.EaseInOut(alpha));
effPos = Vector3.LerpUnclamped(lerpPos, hipPos, ratio * stepAmount);
effRot = lerpRot;
yield return null;
}
stepping = false;
}
void SnapToGround() {
RaycastHit hit;
var didHit = TraceDown(out hit);
effPos = didHit ? hit.point : transform.position + MaxLength * Vector3.down;
effRot = didHit ? MakeFromYZ(hit.normal, transform.forward) : transform.rotation;
}
public void StartJump(RobotPants pants) {
StartCoroutine(DoJump(pants));
}
IEnumerator DoJump(RobotPants pants) {
jumping = true;
// wait during takeoff
do {
yield return null;
} while(pants.yspeed > 5f);
// wait until we're close enough to ground
var savedTime = Time.time;
var savedPos = effPos;
var savedRot = effRot;
var maxLength = MaxLength;
var curlDist = 0.5f * maxLength;
var probeDist = maxLength;
RaycastHit hit;
while(!TraceDown(out hit, probeDist)) {
var alpha = Geom.EaseInOut(Mathf.Clamp01((Time.time - savedTime) / 0.35f));
var targetPos = transform.position + new Vector3(0, -alpha * curlDist, 0);
var targetRot = transform.rotation;
effPos = Vector3.LerpUnclamped(savedPos, targetPos, alpha);
effRot = Quaternion.Slerp(savedRot, targetRot, alpha);
yield return null;
if (pants.status != RobotPants.Status.Jumping)
{
goto Finished;
}
}
// put yer foot down
savedTime = Time.time;
savedPos = effPos;
savedRot = effRot;
while(pants.status == RobotPants.Status.Jumping) {
var alpha = Geom.EaseInOut(Mathf.Clamp01((Time.time - savedTime) / 0.15f));
var didHit = TraceDown(out hit);
var targetPos = didHit ? hit.point : transform.position + maxLength * Vector3.down;
var targetRot = didHit ? MakeFromYZ(hit.normal, transform.forward) : transform.rotation;
effPos = Vector3.LerpUnclamped(savedPos, targetPos, alpha);
effRot = Quaternion.Slerp(savedRot, targetRot, alpha);
yield return null;
}
Finished:
jumping = false;
SnapToGround();
}
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public static class Util {
public static float TimeSince(float t) {
return Time.time - t;
}
public static Vector3 GetCameraRelative(Vector2 input) {
var cam = Camera.main.transform;
var camFwd = cam.forward;
camFwd.y = 0f;
camFwd.Normalize();
var camRt = cam.right;
camRt.y = 0f;
camRt.Normalize();
return input.x * camRt + input.y * camFwd;
}
public static IEnumerable<float> Interpolate(float time) {
var inv = 1f / time;
for(var t=0f; t<time; t+=Time.deltaTime) {
yield return t * inv;
}
yield return 1f;
}
}
public static class Geom {
public static float UnitParabola(float x) {
x = x + x - 1f;
return 1f - x * x;
}
public static float EaseOutQuad(float x) {
x = 1f - x;
return 1f - x * x;
}
public static float EaseInOut(float x) {
return 3f * x * x - 2f * x * x * x;
}
public static Quaternion EnsureShortArc(Quaternion current, Quaternion target) {
// account for double-cover
var Dot = Quaternion.Dot(current, target);
var Multi = Dot > 0f ? 1f : -1f;
target.x *= Multi;
target.y *= Multi;
target.z *= Multi;
target.w *= Multi;
return target;
}
public static Quaternion SmoothDamp(Quaternion current, Quaternion target, ref Quaternion deriv, float dampTime) {
target = EnsureShortArc(current, target);
var q = new Vector4(
Mathf.SmoothDamp(current.x, target.x, ref deriv.x, dampTime),
Mathf.SmoothDamp(current.y, target.y, ref deriv.y, dampTime),
Mathf.SmoothDamp(current.z, target.z, ref deriv.z, dampTime),
Mathf.SmoothDamp(current.w, target.w, ref deriv.w, dampTime)
).normalized;
var dtInv = 1f / Time.deltaTime;
deriv.x = (q.x - current.x) * dtInv;
deriv.y = (q.y - current.y) * dtInv;
deriv.z = (q.z - current.z) * dtInv;
deriv.w = (q.w - current.w) * dtInv;
return new Quaternion(q.x, q.y, q.z, q.w);
}
public static Quaternion AngVelToDeriv(Quaternion Current, Vector3 AngVel) {
var Spin = new Quaternion(AngVel.x, AngVel.y, AngVel.z, 0f);
var Result = Spin * Current;
return new Quaternion(0.5f * Result.x, 0.5f * Result.y, 0.5f * Result.z, 0.5f * Result.w);
}
public static Vector3 DerivToAngVel(Quaternion Current, Quaternion Deriv) {
var Result = Deriv * Quaternion.Inverse(Current);
return new Vector3(2f * Result.x, 2f * Result.y, 2f * Result.z);
}
public static Quaternion IntegrateRotation(Quaternion Rotation, Quaternion Deriv, float DeltaTime) {
var Pred = new Vector4(
Rotation.x + Deriv.x * DeltaTime,
Rotation.y + Deriv.y * DeltaTime,
Rotation.z + Deriv.z * DeltaTime,
Rotation.w + Deriv.w * DeltaTime
).normalized;
return new Quaternion(Pred.x, Pred.y, Pred.z, Pred.w);
}
public static Quaternion IntegrateRotation(Quaternion Rotation, Vector3 AngularVelocity, float DeltaTime) {
var Deriv = AngVelToDeriv(Rotation, AngularVelocity);
return IntegrateRotation(Rotation, Deriv, DeltaTime);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment