Cannibal Starfish is a physics-based platformer made in Unity for the University of Utah's GameCraft September 2022 game jam. The initial game was made in 48 hours with a team of 9. During the jam, my main focus was to implement the physics for the tentacles of the starfish player character, including extension and grappling. After the jam, I added a small quality-of-life update including death volumes (for when the player falls out of bounds), tweaks to level design, respawning interactable objects, and fixing some minor graphical issues.
Broadly speaking, the tentacles of the starfish are rigidbody 2D capsules that change height to extend and contract. They connect to the main body of the starfish (a circle rigidbody) via a relative joint. This gives the tentacles their floppy feel. To control the direction a tentacle is pointing towards - like when a tentacle follows the cursor whilst the left mouse button is being pressed - we adjust the angle of the relative joint. When the tentacle detaches from the body of the starfish - like from taking damage or from completing a grapple - the joint is deactivated.
While the player is extending a tentacle for grappling (holding down the left mouse button), and the tentacle experiences a collision, it creates a hinge joint between itself and the collided surface to create the grapple effect. The code snippet below shows how this works.
/// <summary>
/// Handles the grappling collision logic.
/// </summary>
/// <param name="collision">The collision encountered.</param>
private void GrappleCollisionHandling(Collision2D collision)
{
bool lookingForGrapple = State == TentacleState.EXTENDED_GRAPPLE;
bool grappleable = !collision.gameObject.CompareTag("Non-Grappleable");
if (lookingForGrapple && grappleable)
{
Vector3 forward = transform.up;
Vector2 tipSphereCenter = transform.position + transform.up * (baseColliderHeight - capCollider.size.x);
foreach (ContactPoint2D contact in collision.contacts)
{
// Check that the collision is towards the tip of
// the tentacle instead of at the back. (e.g. filter
// out weird hits from behind).
if (Vector2.Dot(contact.normal, forward) < 0 && Vector2.Distance(contact.point, tipSphereCenter) < capCollider.size.x)
{
if (Vector2.Angle(contact.normal, Vector2.up) < groundAngleThreshold)
break;
if (TryGetComponentInParent(collision.gameObject, out Rigidbody2D otherRigid))
CreateHingeJointRigidBody(contact.point, otherRigid);
else
CreateHingeJointFixedWorld(contact.point);
// Start retracting the arm.
joint.linearOffset = baseExtention;
joint.angularOffset = baseAngularOffset;
State = TentacleState.GRAPPLED;
onGrapple.Invoke(collision, contact);
break;
}
}
}
}