void ResetCan(TinCan can) { can.position = new Vector3(Random.Range(0f, ArmManager.armRowWidth + 10f), Random.Range(3f, 8f), 15f); can.rotation = Quaternion.identity; can.scale = 0f; matrices[can.index] = Matrix4x4.TRS(can.position, can.rotation, canSize); }
public static void HitCan(TinCan can, Vector3 velocity) { instance.sittingCans.Remove(can); instance.fallingCans.Add(can); can.velocity = velocity; can.angularVelocity = Random.onUnitSphere * velocity.magnitude * 40f; }
void Update() { for (int i = 0; i < rockCount; i++) { Rock rock = allRocks[i]; // cheesy and fps-dependent rock.size += (rock.targetSize - rock.size) * 3f * Time.deltaTime; if (rock.state == Rock.State.Conveyor) { rock.position.x += conveyorSpeed * Time.deltaTime; if (rock.position.x > maxConveyorX) { rock.position.x -= ArmManager.armRowWidth + conveyorMargin * 2f; rock.size = 0f; } } else if (rock.state == Rock.State.Thrown) { rock.position += rock.velocity * Time.deltaTime; rock.velocity += Vector3.up * -gravityStrength * Time.deltaTime; TinCan nearestCan = TinCanManager.GetNearestCan(rock.position, false); if (nearestCan != null) { if ((nearestCan.position - rock.position).sqrMagnitude < .5f * .5f) { TinCanManager.HitCan(nearestCan, rock.velocity); rock.velocity = Random.insideUnitSphere * 3f; } } if (rock.position.y < -5f) { rock.state = Rock.State.Conveyor; conveyorRocks.Add(rock); rock.position = new Vector3(Random.Range(minConveyorX, maxConveyorX), 0f, 1.5f); rock.size = 0f; } } Matrix4x4 matrix = matrices[i]; matrix.m03 = rock.position.x; matrix.m13 = rock.position.y; matrix.m23 = rock.position.z; matrix.m00 = rock.size; matrix.m11 = rock.size; matrix.m22 = rock.size; matrices[i] = matrix; } Graphics.DrawMeshInstanced(rockMesh, 0, rockMaterial, matrices); }
Vector3 AimAtCan(TinCan can, Vector3 startPos) { // predictive aiming based on this article by Kain Shin: // https://www.gamasutra.com/blogs/KainShin/20090515/83954/Predictive_Aim_Mathematics_for_AI_Targeting.php float targetSpeed = can.velocity.magnitude; float cosTheta = Vector3.Dot((startPos - can.position).normalized, can.velocity.normalized); float D = (can.position - startPos).magnitude; // quadratic equation terms float A = baseThrowSpeed * baseThrowSpeed - targetSpeed * targetSpeed; float B = (2f * D * targetSpeed * cosTheta); float C = -D * D; if (B * B < 4f * A * C) { // it's impossible to hit the target return(Vector3.forward * 10f + Vector3.up * 8f); } // quadratic equation has two possible outputs float t1 = (-B + Mathf.Sqrt(B * B - 4f * A * C)) / (2f * A); float t2 = (-B - Mathf.Sqrt(B * B - 4f * A * C)) / (2f * A); // our two t values represent two possible trajectory durations. // pick the best one - whichever is lower, as long as it's positive float t; if (t1 < 0f && t2 < 0f) { // both potential collisions take place in the past! return(Vector3.forward * 10f + Vector3.up * 8f); } else if (t1 < 0f && t2 > 0f) { t = t2; } else if (t1 > 0f && t2 < 0f) { t = t1; } else { t = Mathf.Min(t1, t2); } Vector3 output = can.velocity - .5f * new Vector3(0f, -RockManager.instance.gravityStrength, 0f) * t + (can.position - startPos) / t; if (output.magnitude > baseThrowSpeed * 2f) { // the required throw is too serious for us to handle return(Vector3.forward * 10f + Vector3.up * 8f); } return(output); }
void Start() { instance = this; sittingCans = new List <TinCan>(canCount); fallingCans = new List <TinCan>(canCount); matrices = new Matrix4x4[canCount]; for (int i = 0; i < canCount; i++) { TinCan can = new TinCan(); can.index = i; ResetCan(can); sittingCans.Add(can); } }
void Update() { for (int i = 0; i < fallingCans.Count; i++) { TinCan can = fallingCans[i]; can.position += can.velocity * Time.deltaTime; can.velocity += new Vector3(0f, -gravityStrength * Time.deltaTime, 0f); can.rotation = Quaternion.AngleAxis(can.angularVelocity.magnitude * Time.deltaTime, can.angularVelocity) * can.rotation; matrices[can.index] = Matrix4x4.TRS(can.position, can.rotation, canSize); if (can.position.y < -5f) { fallingCans.RemoveAt(i); i--; sittingCans.Add(can); ResetCan(can); } } for (int i = 0; i < sittingCans.Count; i++) { TinCan can = sittingCans[i]; // cheesy and fps-dependent can.scale += (1f - can.scale) * 3f * Time.deltaTime; can.velocity = -Vector3.right * 3f; can.position += can.velocity * Time.deltaTime; if (can.position.x < 0f && can.reserved == false) { can.position.x = ArmManager.armRowWidth + 10f; can.scale = 0f; } matrices[can.index].m03 = can.position.x; matrices[can.index].m13 = can.position.y; matrices[can.index].m23 = can.position.z; matrices[can.index].m00 = canSize.x * can.scale; matrices[can.index].m11 = canSize.y * can.scale; matrices[can.index].m22 = canSize.z * can.scale; } Graphics.DrawMeshInstanced(canMesh, 0, canMaterial, matrices); }
public static TinCan GetNearestCan(Vector3 pos, bool skipReservedCans = true, float xRange = float.MaxValue) { float minDist = float.MaxValue; TinCan output = null; for (int i = 0; i < instance.sittingCans.Count; i++) { float xDelta = Mathf.Abs(pos.x - instance.sittingCans[i].position.x); if (xDelta < xRange) { if (instance.sittingCans[i].reserved == false || skipReservedCans == false) { Vector3 delta = instance.sittingCans[i].position - pos; float sqrDist = delta.sqrMagnitude; if (sqrDist < minDist) { minDist = sqrDist; output = instance.sittingCans[i]; } } } } return(output); }
IEnumerator UnreserveCoroutine(TinCan can, float delay) { yield return(new WaitForSeconds(delay)); can.reserved = false; }
public static void UnreserveCanAfterDelay(TinCan can, float delay) { instance.StartCoroutine(instance.UnreserveCoroutine(can, delay)); }
void Update() { float time = Time.time + timeOffset; // resting position Vector3 idleHandTarget = transform.position + new Vector3(Mathf.Sin(time) * .35f, 1f + Mathf.Cos(time * 1.618f) * .5f, 1.5f); if (heldRock == null && windupTimer <= 0f) { if (intendedRock == null && reachTimer == 0f) { // we're idle - see if we can grab a rock Rock nearestRock = RockManager.NearestConveyorRock(transform.position - Vector3.right * .5f); if (nearestRock != null) { if ((nearestRock.position - transform.position).sqrMagnitude < maxReachLength * maxReachLength) { // found a rock to grab! // mark it as reserved so other hands don't reach for it intendedRock = nearestRock; intendedRock.reserved = true; lastIntendedRockSize = intendedRock.size; } } } else if (intendedRock == null) { // stop reaching if we've lost our target reachTimer -= Time.deltaTime / reachDuration; } if (intendedRock != null) { // we're reaching for a rock (but we haven't grabbed it yet) Vector3 delta = intendedRock.position - transform.position; if (delta.sqrMagnitude < maxReachLength * maxReachLength) { // figure out where we want to put our wrist // in order to grab the rock Vector3 flatDelta = delta; flatDelta.y = 0f; flatDelta.Normalize(); grabHandTarget = intendedRock.position + Vector3.up * intendedRock.size * .5f - flatDelta * intendedRock.size * .5f; lastIntendedRockPos = intendedRock.position; reachTimer += Time.deltaTime / reachDuration; if (reachTimer >= 1f) { // we've arrived at the rock - pick it up heldRock = intendedRock; RockManager.RemoveFromConveyor(heldRock); heldRock.state = Rock.State.Held; // remember the rock's position in "hand space" // (so we can position the rock while holding it) heldRockOffset = handMatrix.inverse.MultiplyPoint3x4(heldRock.position); intendedRock = null; // random minimum delay before starting the windup windupTimer = Random.Range(-1f, 0f); throwTimer = 0f; } } else { // we didn't grab the rock in time - forget it intendedRock.reserved = false; intendedRock = null; } } } if (heldRock != null) { // stop reaching after we've successfully grabbed a rock reachTimer -= Time.deltaTime / reachDuration; if (targetCan == null) { // find a target targetCan = TinCanManager.GetNearestCan(transform.position, true, targetXRange); } if (targetCan != null) { // found a target - prepare to throw targetCan.reserved = true; windupTimer += Time.deltaTime / windupDuration; } } reachTimer = Mathf.Clamp01(reachTimer); // smoothed reach timer float grabT = reachTimer; grabT = 3f * grabT * grabT - 2f * grabT * grabT * grabT; // reaching overrides our idle hand position handTarget = Vector3.Lerp(idleHandTarget, grabHandTarget, grabT); if (targetCan != null) { // we've got a target, which means we're currently throwing if (windupTimer < 1f) { // still winding up... float windupT = Mathf.Clamp01(windupTimer) - Mathf.Clamp01(throwTimer * 2f); windupT = 3f * windupT * windupT - 2f * windupT * windupT * windupT; handTarget = Vector3.Lerp(handTarget, windupHandTarget, windupT); Vector3 flatTargetDelta = targetCan.position - transform.position; flatTargetDelta.y = 0f; flatTargetDelta.Normalize(); // windup position is "behind us," relative to the target position windupHandTarget = transform.position - flatTargetDelta * 2f + Vector3.up * (3f - windupT * 2.5f); } else { // done winding up - actual throw, plus resetting to idle throwTimer += Time.deltaTime / throwDuration; // update our aim until we release the rock if (heldRock != null) { aimVector = AimAtCan(targetCan, lastIntendedRockPos); } // we start this animation in our windup position, // and end it by returning to our default idle pose Vector3 restingPos = Vector3.Lerp(windupHandTarget, handTarget, throwTimer); // find the hand's target position to perform the throw // (somewhere forward and upward from the windup position) Vector3 throwHandTarget = windupHandTarget + aimVector.normalized * 2.5f; handTarget = Vector3.LerpUnclamped(restingPos, throwHandTarget, throwCurve.Evaluate(throwTimer)); if (throwTimer > .15f && heldRock != null) { // release the rock heldRock.reserved = false; heldRock.state = Rock.State.Thrown; heldRock.velocity = aimVector; heldRock = null; } if (throwTimer >= 1f) { // we've completed the animation - return to idle windupTimer = 0f; throwTimer = 0f; TinCanManager.UnreserveCanAfterDelay(targetCan, 3f); targetCan = null; } } } // solve the arm IK chain first FABRIK.Solve(armChain, armBoneLength, transform.position, handTarget, handUp * armBendStrength); // figure out our current "hand vectors" from our arm orientation handForward = (armChain.Last(0) - armChain.Last(1)).normalized; handUp = Vector3.Cross(handForward, transform.right).normalized; handRight = Vector3.Cross(handUp, handForward); // create handspace-to-worldspace matrix handMatrix = Matrix4x4.TRS(armChain.Last(), Quaternion.LookRotation(handForward, handUp), Vector3.one); // how much are our fingers gripping? // (during a reach, this is based on the reach timer) float fingerGrabT = grabT; if (heldRock != null) { // move our held rock to match our new hand position heldRock.position = handMatrix.MultiplyPoint3x4(heldRockOffset); lastIntendedRockPos = heldRock.position; // if we're holding a rock, we're always gripping fingerGrabT = 1f; } // create rendering matrices for arm bones UpdateMatrices(armChain, 0, armBoneThickness, handUp); int matrixIndex = armChain.Length - 1; // next: fingers Vector3 handPos = armChain.Last(); // fingers spread out during a throw float openPalm = throwCurve.Evaluate(throwTimer); for (int i = 0; i < fingerChains.Length; i++) { // find knuckle position for this finger Vector3 fingerPos = handPos + handRight * (fingerXOffset + i * fingerSpacing); // find resting position for this fingertip Vector3 fingerTarget = fingerPos + handForward * (.5f - .1f * fingerGrabT); // spooky finger wiggling while we're idle fingerTarget += handUp * Mathf.Sin((time + i * .2f) * 3f) * .2f * (1f - fingerGrabT); // if we're gripping, move this fingertip onto the surface of our rock Vector3 rockFingerDelta = fingerTarget - lastIntendedRockPos; Vector3 rockFingerPos = lastIntendedRockPos + rockFingerDelta.normalized * (lastIntendedRockSize * .5f + fingerThicknesses[i]); fingerTarget = Vector3.Lerp(fingerTarget, rockFingerPos, fingerGrabT); // apply finger-spreading during throw animation fingerTarget += (handUp * .3f + handForward * .1f + handRight * (i - 1.5f) * .1f) * openPalm; // solve this finger's IK chain FABRIK.Solve(fingerChains[i], fingerBoneLengths[i], fingerPos, fingerTarget, handUp * fingerBendStrength); // update this finger's rendering matrices UpdateMatrices(fingerChains[i], matrixIndex, fingerThicknesses[i], handUp); matrixIndex += fingerChains[i].Length - 1; } // the thumb is pretty much the same as the fingers // (but pointing in a strange direction) Vector3 thumbPos = handPos + handRight * thumbXOffset; Vector3 thumbTarget = thumbPos - handRight * .15f + handForward * (.2f + .1f * fingerGrabT) - handUp * .1f; thumbTarget += handRight * Mathf.Sin(time * 3f + .5f) * .1f * (1f - fingerGrabT); // thumb bends away from the palm, instead of "upward" like the fingers Vector3 thumbBendHint = (-handRight - handForward * .5f); Vector3 rockThumbDelta = thumbTarget - lastIntendedRockPos; Vector3 rockThumbPos = lastIntendedRockPos + rockThumbDelta.normalized * (lastIntendedRockSize * .5f); thumbTarget = Vector3.Lerp(thumbTarget, rockThumbPos, fingerGrabT); FABRIK.Solve(thumbChain, thumbBoneLength, thumbPos, thumbTarget, thumbBendHint * thumbBendStrength); UpdateMatrices(thumbChain, matrixIndex, thumbThickness, thumbBendHint); // draw all of our bones Graphics.DrawMeshInstanced(boneMesh, 0, material, matrices); }