Krotox Engine › Docs › Scripting API › Physics & Input

Physics2D queries, layers, joints & controllers

Raycasts, shape casts and overlap queries, layer masks and per-pair ignoring, collision groups, tuning the live physics world, scripting joints and driving motors every frame, two-bone IK, effectors and the Character Controller 2D.

The static Physics2D class asks questions of the running physics world and changes its settings. Every query takes an optional uint layerMask as its last argument.

Raycasts

RaycastHit2D hit = Physics2D.Raycast(origin, direction, 10.0f);
if (hit.Hit)
    Log.Info(quot;Hit {hit.Entity.Name} at {hit.Point}, normal {hit.Normal}, distance {hit.Distance}");

RaycastHit2D[] all = Physics2D.RaycastAll(origin, direction, 10.0f);   // nearest first
RaycastHit2D los   = Physics2D.Linecast(eye, target);                   // Hit = something is in between

RaycastHit2D has bool Hit, ulong EntityID, Vector2 Point, Vector2 Normal, float Distance, and an Entity property that is null when nothing was hit.

Ground check

bool IsGrounded()
{
    Vector2 feet = new Vector2(WorldPosition.X, WorldPosition.Y - 0.51f);
    ulong ground = LayerMask.GetMask("Ground");
    return Physics2D.Raycast(feet, Vector2.Down, 0.1f, (uint)ground).Hit;
}

Shape casts

Sweep a shape along a direction and report the first hit (or all hits):

RaycastHit2D ball  = Physics2D.CircleCast(origin, 0.5f, direction, 5.0f);
RaycastHit2D crate = Physics2D.BoxCast(center, new Vector2(1, 1), 0.0f, direction, 5.0f);   // angle in radians
RaycastHit2D body  = Physics2D.CapsuleCast(origin, new Vector2(0.6f, 1.8f), 0.0f, direction, 4.0f);
RaycastHit2D[] sweep = Physics2D.CircleCastAll(origin, 0.5f, Vector2.Right, 8.0f);
RaycastHit2D[] boxes = Physics2D.BoxCastAll(center, new Vector2(1, 2), 0.0f, direction, 5.0f);

Overlap and point queries

// What is under the finger? (tap to select, drop targets)
Vector2 tap = Camera.ScreenToWorldPoint(Input.MousePosition);
Entity picked = Physics2D.OverlapPointEntity(tap);           // null when nothing is there
RaycastHit2D under = Physics2D.OverlapPoint(tap);            // same, as a hit

// Everything in an area (each entity once)
Entity[] nearby  = Physics2D.OverlapCircleAll(new Vector2(WorldPosition.X, WorldPosition.Y), 3.0f);
Entity any       = Physics2D.OverlapCircle(point, 0.2f);
Entity[] inBox   = Physics2D.OverlapBoxAll(center, size, angle);
Entity[] inArea  = Physics2D.OverlapAreaAll(cornerA, cornerB);
Entity first     = Physics2D.OverlapArea(cornerA, cornerB);

// Polled contact state: the asking counterpart of the callbacks
bool onBelt       = Physics2D.IsTouching(player, belt);      // solid contact or sensor overlap
Entity[] touching = Physics2D.GetTouching(player);

Explosion example

void Explode(Vector2 center, float radius, float strength)
{
    foreach (Entity e in Physics2D.OverlapCircleAll(center, radius))
    {
        if (!e.TryGetComponent(out Rigidbody2DComponent rb)) continue;
        Vector2 away = rb.Position - center;
        float falloff = 1.0f - Mathf.Clamp01(away.Length() / radius);
        rb.ApplyLinearImpulse(away.Normalized * strength * falloff);
    }
}

Layers and filtering

int player  = LayerMask.NameToLayer("Player");
int pickups = LayerMask.NameToLayer("Pickups");
Physics2D.IgnoreLayerCollision(player, pickups);           // the whole layer pair

ulong mask = LayerMask.GetMask("Ground", "Walls");         // for queries
Layer = LayerMask.NameToLayer("Projectiles");              // put this entity on a layer

Physics2D.IgnoreCollision(player, shield);                 // just these two (both need a Rigidbody 2D)
bool ignored = Physics2D.GetIgnoreCollision(player, shield);

Collision groups

Rigidbody2DComponent.CollisionGroup is Box2D's group index, applied to every collider on the body. Bodies sharing a negative group never collide with each other, whatever their layers; sharing a positive group they always do; 0 is none. It is saved with the component, and changes apply at once.

// A ragdoll's limbs ignore each other but still hit the world
int ragdollGroup = -(int)(ID % 30000) - 1;          // unique per character
foreach (var limb in GetComponentsInChildren<Rigidbody2DComponent>())
    limb.CollisionGroup = ragdollGroup;

// An arrow ignores its own archer while leaving the bow...
arrowBody.CollisionGroup = ragdollGroup;
// ...then hits everything once it is clear
arrowBody.CollisionGroup = 0;

Tuning the live world

These change the running world only; the project settings apply again on the next Play.

Physics2D.Gravity = new Vector2(0, -20);
Physics2D.SetContactTuning(hertz: 30f, dampingRatio: 10f, contactSpeed: 3f);
Physics2D.RestitutionThreshold = 1.0f;      // slower impacts do not bounce (world-wide)
Physics2D.HitEventThreshold = 1.0f;
Physics2D.MaxLinearSpeed = 400f;
Physics2D.SleepingEnabled = true;
Physics2D.ContinuousEnabled = true;
Physics2D.WarmStartingEnabled = true;       // keep this on; off makes stacks jitter
int awake = Physics2D.AwakeBodyCount;       // 0 when the simulation is not running

Joints

All 2D joint components (DistanceJoint2DComponent, HingeJoint2DComponent, SliderJoint2DComponent, WheelJoint2DComponent, WeldJoint2DComponent, MotorJoint2DComponent, TargetJoint2DComponent, FrictionJoint2DComponent) share:

MemberMeaning
Vector2 ReactionForce, float ReactionTorqueThe live load on the joint.
bool IsBroken, bool IsValidBroken, and whether the Box2D joint exists.
SetBreakThresholds(bool enableForce, float force, bool enableTorque, float torque)Make it breakable.
Break()Snap it now.
var hinge = GetComponent<HingeJoint2DComponent>();
hinge.SetMotor(true, speed: 2.0f, maxTorque: 50.0f);
hinge.SetLimits(true, lowerAngle: -0.5f, upperAngle: 0.5f);
float angle = hinge.Angle;

var rope = GetComponent<DistanceJoint2DComponent>();
rope.Length = 5.0f;
rope.SetSpring(true, hertz: 4.0f, dampingRatio: 0.5f);

var piston = GetComponent<SliderJoint2DComponent>();
piston.SetLimits(true, lowerTranslation: -2.0f, upperTranslation: 5.0f);
piston.SetMotor(true, speed: 3.0f, maxForce: 100.0f);

var wheel = GetComponent<WheelJoint2DComponent>();
wheel.SetSpring(true, hertz: 5.0f, dampingRatio: 0.7f);
wheel.SetMotor(true, speed: 10.0f, maxTorque: 80.0f);

// Drag an object with the finger
var drag = GetComponent<TargetJoint2DComponent>();
drag.Target = Camera.ScreenToWorldPoint(Input.MousePosition);
drag.MaxForce = 500.0f;

Driving joint motors every frame

SetMotor, SetSpring and SetLimits rewrite the whole joint. To steer a motor or spring every frame, set the single property instead. It updates the live joint in place and keeps the solver's warm start, so the motion stays smooth.

ComponentPropertyUnit
HingeJoint2DComponentMotorSpeed, MaxMotorTorque, TargetAnglerad/s, N·m, rad
SliderJoint2DComponentMotorSpeed, MaxMotorForce, TargetTranslationunits/s, N, units
WheelJoint2DComponentMotorSpeed, MaxMotorTorquerad/s, N·m
MotorJoint2DComponentLinearVelocity, AngularVelocity

The motor or spring must already be enabled. Setting a new value wakes both bodies; setting the same value again does nothing, so bodies can still fall asleep.

public class Car : Entity
{
    public float TopSpeed = 30.0f;      // rad/s
    WheelJoint2DComponent m_Rear;

    void OnCreate()
    {
        m_Rear = FindChild("RearWheel").GetComponent<WheelJoint2DComponent>();
        m_Rear.SetMotor(true, 0.0f, 120.0f);     // enable once
    }

    void OnUpdate(float ts)
    {
        float throttle = Input.IsKeyDown(KeyCode.D) ? 1.0f : Input.IsKeyDown(KeyCode.A) ? -1.0f : 0.0f;
        m_Rear.MotorSpeed = -throttle * TopSpeed;   // cheap every frame
    }
}

Two-bone IK

InverseKinematics2D.SolveTwoBone places a two-bone limb (shoulder–elbow–hand, hip–knee–foot) so its end reaches a target. Angles are radians, counter-clockwise from +X.

bool reached = InverseKinematics2D.SolveTwoBone(
    shoulder, hand, upperArmLength, forearmLength, bendPositive: true,
    out float upperAngle,      // absolute angle of the upper arm
    out float elbowAngle,      // forearm relative to the upper arm (0 = straight)
    out Vector2 elbow);        // where the elbow ends up

shoulderHinge.TargetAngle = upperAngle - torsoAngle;
elbowHinge.TargetAngle    = elbowAngle;

bendPositive chooses which way the middle joint bends. Out of reach, the limb points straight at the target and the method returns false.

Character Controller 2D

CharacterController2DComponent drives the entity's Dynamic Rigidbody 2D with platformer movement and ground detection.

MemberMeaning
bool Walk(float direction)Horizontal speed = direction (-1 to 1) × MoveSpeed. Call every fixed update; 0 stops. Will not walk up slopes steeper than MaxSlopeAngle.
bool Jump(float speed = 0, bool allowAirJump = false)Jump at JumpForce (or speed), from the ground only unless allowAirJump. Returns whether it jumped.
void Move(Vector2 displacement)Set the velocity to cover displacement per fixed step.
MoveSpeed, JumpForce, MaxSlopeAngleMovement settings.
GroundCheckDistance, SkinWidth, GroundLayerMaskGround detection.
EnableGravity, GravityScale, StickToGroundGravity and slope handling.
IsGrounded, GroundNormal, IsOnSteepSlopeLive state (read-only).
public class Platformer : Entity
{
    CharacterController2DComponent m_Controller;
    float m_Input;
    bool m_JumpQueued;

    public override void OnAwake() { m_Controller = GetComponent<CharacterController2DComponent>(); }

    void OnUpdate(float ts)
    {
        m_Input = (Input.IsKeyDown(KeyCode.D) ? 1f : 0f) - (Input.IsKeyDown(KeyCode.A) ? 1f : 0f);
        if (Input.IsKeyPressed(KeyCode.Space)) m_JumpQueued = true;   // remember presses between fixed steps
    }

    public override void OnFixedUpdate()
    {
        m_Controller.Walk(m_Input);
        if (m_JumpQueued) { m_Controller.Jump(); m_JumpQueued = false; }
    }
}

Effectors from code

ComponentScriptable members
PlatformEffector2DComponentUseOneWay, SurfaceArc, SurfaceNormal, RotationalOffset
AreaEffector2DComponentForceMagnitude, ForceAngle (degrees), ForceVariation, Mode (Additive, TargetVelocity), Target (Rigidbody, Collider), Drag, AngularDrag
PointEffector2DComponentForceMagnitude (positive attracts), ForceVariation, Falloff (Constant, Linear, InverseSquare), Drag, AngularDrag
BuoyancyEffector2DComponentDensity, SurfaceLevel, LinearDrag, AngularDrag, FlowVelocity, FlowMagnitude
ConstantForce2DComponentForce, RelativeForce, Torque
// A fan that can be switched off
var fan = GetComponent<AreaEffector2DComponent>();
fan.ForceMagnitude = m_On ? 25.0f : 0.0f;

Physics gameplay components

The ready-made gameplay components can be changed while the game runs:

GetComponent<SurfaceConveyorComponent>().Reverse = true;          // flip a belt from a switch
GetComponent<PortalComponent>().LinkedPortal = Entity.Find("Portal_B");
GetComponent<ForceFieldZoneComponent>().Force = new Vector2(-8, 0);

var spawner = GetComponent<SpawnerComponent>();
if (spawner.IsJammed) spawner.ClearJam();

int stars = GetComponent<LevelMetadataComponent>().EvaluateStars(score, elapsedSeconds, collectibles);

Full member lists: SpawnerComponent (IsJammed, ClearJam(), Heat, ChargeRatio, RemainingAmmo, MaxAmmo, AutoSpawn), MotionPathComponent (PointA, PointB, Speed, PivotOffset, Progress, ResetToRest()), HazardZoneComponent (Response, TriggerDelay), PhysicsBumperComponent, ProximityTriggerComponent (including GrazeCount). See the manual for what each does.