Created
April 18, 2016 05:16
-
-
Save maxattack/48396825a644e1bc2e1b647e5af4fc13 to your computer and use it in GitHub Desktop.
Scrapped LD35 Compo Idea // Robot Pants
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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