Krotox Engine › Docs › Scripting API › Physics & Input

Rigidbody2D, colliders & collision callbacks

Controlling 2D physics bodies from C#: velocity, forces, impulses and torque, teleporting, mass and sleep, changing colliders at run time, and every collision and trigger callback with the Collision2D data they carry.

Rigidbody2DComponent

var rb = GetComponent<Rigidbody2DComponent>();

rb.Type = Rigidbody2DComponent.BodyType.Dynamic;   // Static, Dynamic, Kinematic
rb.LinearVelocity = new Vector2(5, 0);             // units per second
rb.AngularVelocity = 1.5f;                         // radians per second
rb.GravityScale = 2.0f;
rb.Mass = 1.5f;
rb.FixedRotation = true;                           // keep characters upright
rb.Bullet = true;                                  // continuous collision for fast bodies

Velocity, forces and impulses

MemberUse
LinearVelocity (Vector2), AngularVelocity (float, rad/s)Read or set the speed directly. Best for responsive character control.
ApplyLinearImpulse(Vector2 impulse) / (impulse, Vector2 worldPoint)An instant kick: jumps, explosions, hits.
ApplyForce(Vector2 force) / (force, Vector2 worldPoint), ApplyForceToCenter(force)A continuous push; apply it every fixed step.
ApplyTorque(float torque)A continuous spin.
AddForce, AddImpulse, AddTorque, AddForceAtPositionUnity-style aliases of the above.
GetVelocityAtPoint(Vector2 worldPoint)The velocity of a point on the body.
LinearDamping, AngularDampingDrag. Run-time only; resets on the next Play.
// Jump only from the ground, with a fixed jump height regardless of current fall speed
if (Input.IsKeyPressed(KeyCode.Space) && m_Grounded)
{
    Vector2 v = rb.LinearVelocity;
    v.Y = 0.0f;
    rb.LinearVelocity = v;
    rb.ApplyLinearImpulse(new Vector2(0, JumpImpulse));
}

// Thrusters: apply continuous force in OnFixedUpdate
public override void OnFixedUpdate()
{
    if (Input.IsKeyDown(KeyCode.W))
        rb.ApplyForce(rb.TransformDirection(Vector2.Up) * Thrust);
}

Position, teleporting and kinematic movement

rb.Position = new Vector2(0, 5);    // teleport now (no interpolation trail); velocity is kept
rb.Rotation = 0.0f;                 // radians; teleport the angle
Vector2 pos = rb.Position;          // world position
Vector2 com = rb.WorldCenter;       // centre of mass in world space

rb.MovePosition(target);            // kinematic platforms: move over the next step, pushing what is in the way
rb.MoveRotation(angleRadians);
For moving platforms, use a Kinematic body and MovePosition (or set its velocity). Riders on top are carried properly, which does not happen if you write the Transform.

Mass, sleep and enabling

MemberMeaning
Mass, CenterOfMass, InertiaMass properties. Inertia is zero before Play.
RecalculateMass()Recompute after changing a collider's size or density. A Mass override stays.
IsAwake, WakeUp(), Sleep()Sleep state. A sleeping body ignores its motor.
SleepEnabled, SleepThresholdWhether the body may sleep, and the speed below which it counts as resting.
WakeTouching()Wake whatever this body rests on or pushes.
Enabledfalse takes the body out of the simulation and queries but keeps its shapes and joints.
CollisionDetectionDiscrete (default) or Continuous (same as Bullet).
InterpolationNone, Interpolate (default) or Extrapolate.
CollisionGroupBox2D group index. See collision groups.

Converting between body and world space

Vector2 tip   = rb.TransformPoint(new Vector2(0.5f, 0));   // local point to world
Vector2 local = rb.InverseTransformPoint(worldPoint);      // world point to local
Vector2 dir   = rb.TransformDirection(Vector2.Right);      // direction only

Colliders at run time

Every 2D collider can be changed while the game runs. The shape is rebuilt on its body, which keeps its velocity and joints.

ComponentShape members
BoxCollider2DComponentVector2 Size, Vector2 Offset
CircleCollider2DComponentfloat Radius, Vector2 Offset
CapsuleCollider2DComponentfloat Radius, float Height, Vector2 Offset
PolygonCollider2DComponentVector2[] Vertices (convex, 3–8), Vector2 Offset
SegmentCollider2DComponentVector2 Point0, Vector2 Point1
ChainCollider2DComponentVector2[] Vertices (2 or more), bool Loop

All colliders also have Friction, Restitution, IsSensor, UseLayerCollisionMatrix, CategoryBits and MaskBits; all but the chain have Density and RestitutionThreshold.

var box = GetComponent<BoxCollider2DComponent>();
box.Size = new Vector2(0.8f, 0.9f);       // crouching hitbox
box.Offset = new Vector2(0, -0.35f);

var poly = GetComponent<PolygonCollider2DComponent>();
poly.Vertices = new[] { new Vector2(-0.5f, 0), new Vector2(0.5f, 0), new Vector2(0, 1) };
To change Vertices, assign a new array. Editing the array you got back has no effect.

Collision and trigger callbacks

Override these on any script whose entity (or a child collider of its body) takes part in the contact.

// A solid contact started. Collision2D has the details.
public override void OnCollisionEnter2D(Collision2D c)
{
    Log.Info(quot;Hit {c.Entity?.Name} at {c.Point}, normal {c.Normal}, impulse {c.NormalImpulse}");
}

// Still touching: once per physics step until it ends.
public override void OnCollisionStay2D(Collision2D c) { }

// The contact ended. Point, normal and impulses are zero here.
public override void OnCollisionExit2D(Collision2D c) { }

// Lighter versions: just the other entity.
public override void OnCollisionEnter(Entity other) { }
public override void OnCollisionStay(Entity other) { }
public override void OnCollisionExit(Entity other) { }

// Sensor (trigger) overlaps
public override void OnTriggerEnter(Entity other)
{
    if (other.HasTag("Coin"))
    {
        m_Score += 1;
        other.Destroy();
    }
}
public override void OnTriggerStay(Entity other) { }
public override void OnTriggerExit(Entity other) { }

// A joint on this entity exceeded its break threshold
public override void OnJointBreak2D(JointBreak2D info)
{
    Log.Warn(quot;Joint to {info.ConnectedEntity?.Name} broke at {info.BreakForce} N");
}

Collision2D

MemberMeaning
Entity / EntityID / ColliderThe entity holding the other side's collider.
Body / BodyIDThe entity with the other side's Rigidbody 2D (for a ragdoll hit on the head, the ragdoll's root).
SelfCollider / SelfColliderIDThe entity holding our collider that made the contact.
ColliderType, SelfColliderTypeBox, Circle, Capsule, Polygon, Segment, Chain, Tilemap or None.
Point, NormalContact point and normal. Zero on exit.
NormalImpulse, TangentImpulseHow hard the hit was. Zero on exit.
// A headshot check on a ragdoll, from the arrow's script
public override void OnCollisionEnter2D(Collision2D c)
{
    bool fromTip  = c.SelfCollider?.Name == "Tip";
    bool headshot = c.Collider?.Name == "Head";
    Entity enemy  = c.Body;     // the whole character, whichever limb was hit
}

Who receives a contact

The entity holding the collider receives it. If that collider is on a child of the entity with the Rigidbody 2D, the body's entity receives it too, so a script on a character's root hears about contacts on its child colliders. Use c.SelfCollider to tell which one.

Timing

Requirements

Checklist: why don't I get callbacks?

  1. Is the method signature exact, with public override? OnTriggerEnter(Entity other), not OnTriggerEnter2D.
  2. Does at least one of the two entities have a Rigidbody 2D?
  3. For a trigger, is Is Sensor ticked?
  4. Do the two layers collide in the collision matrix?
  5. Is the script on the entity that holds the collider, or on the root of its body?