Skip to content

body

FreeBodyEngine.core.physics.body #

RigidBody2D - the new rigid-body physics node.

Unlike PhysicsBody (the older, simpler "arcade physics" system this package still exports for backward compatibility - push-out collision response with no concept of a real constraint solver), a RigidBody2D is simulated by PhysicsWorld alongside every other body and Joint2D in the scene, all at once, through a proper sequential-impulse velocity solver - which is what makes joints, motors, and physically stable stacking/resting contact possible at all.

BodyType #

Bases: Enum

What drives a RigidBody2D's motion:

  • STATIC: never moves (infinite mass/inertia) - level geometry, walls.
  • KINEMATIC: moves only when you set its position/rotation directly (e.g. animating a moving platform) - unaffected by forces/impulses/ collision response, but still pushes DYNAMIC bodies it touches.
  • DYNAMIC: fully simulated - forces, gravity, collision response, and joints all apply.

DYNAMIC = auto() class-attribute instance-attribute #

KINEMATIC = auto() class-attribute instance-attribute #

STATIC = auto() class-attribute instance-attribute #

RigidBody2D(position=Vector(), rotation=0.0, body_type=BodyType.DYNAMIC, density=1.0, mass=None, inertia=None, linear_velocity=Vector(0, 0), angular_velocity=0.0, linear_damping=0.05, angular_damping=0.05, gravity_scale=1.0, friction=0.3, restitution=0.0, fixed_rotation=False, is_sensor=False, collision_layer=1, collision_mask=4294967295, allow_sleep=True) #

Bases: Node2D

A physically-simulated body: requires a sibling Collider2D child (declared via self.requirements, exactly like PhysicsBody) whose shape drives both collision detection and (unless overridden) this body's auto-computed mass/inertia. Every RigidBody2D in a scene is simulated together by a PhysicsWorld (see world.py) - forces accumulate here between physics steps, but integration, collision response, and joint solving all happen there, not on the body itself.

Records every physics parameter, but doesn't compute mass/ inertia yet - that needs this body's Collider2D child, which isn't available until on_initialize() runs.

mass/inertia, if given, override the auto-derived values from density and the collider's shape entirely (useful for gameplay-driven bodies where "realistic" density-based mass would fight the feel you actually want).

allow_sleep = allow_sleep instance-attribute #

angular_damping = angular_damping instance-attribute #

angular_velocity = angular_velocity instance-attribute #

body_type = body_type instance-attribute #

collider = None instance-attribute #

collision_layer = collision_layer instance-attribute #

collision_mask = collision_mask instance-attribute #

density = density instance-attribute #

fixed_rotation = fixed_rotation instance-attribute #

friction = friction instance-attribute #

gravity_scale = gravity_scale instance-attribute #

inertia = 0.0 instance-attribute #

inv_inertia = 0.0 instance-attribute #

inv_mass = 0.0 instance-attribute #

is_sensor = is_sensor instance-attribute #

is_sleeping = False instance-attribute #

linear_damping = linear_damping instance-attribute #

linear_velocity = linear_velocity.copy() instance-attribute #

mass = 0.0 instance-attribute #

requirements = ['Collider2D'] instance-attribute #

restitution = restitution instance-attribute #

touching = set() instance-attribute #

apply_force(force, world_point=None) #

Accumulates force, applied at world_point if given (otherwise at this body's center of mass, producing no torque). Cleared every physics step after integration - for a constant force (e.g. a thruster), call this every step, not just once.

apply_impulse(impulse, world_point=None) #

Immediately changes velocity by impulse * inv_mass (and angular velocity, if world_point is off-center) - unlike apply_force(), this is a one-shot velocity change applied right away, not accumulated for the next integration step. Used directly by explosions/knockback/etc, and internally by the constraint solver itself.

apply_torque(torque) #

Accumulates a pure rotational force, with no linear component.

on_collision_enter(other, contact) #

Called the first physics step two (non-sensor) bodies start touching - override to react (damage, sound, sticking). contact is the core.physics.contact.Manifold for this pair, in this body's own frame (i.e. contact.normal points from this body toward other).

on_collision_exit(other) #

Called the physics step two bodies stop touching, having touched the step before. No-op by default.

on_collision_stay(other, contact) #

Called every physics step (after the first) two bodies remain touching. No-op by default.

on_initialize() #

Finds this body's collider child and computes mass/inertia from it (unless overridden), then derives the inverse values the solver actually uses - 0 for a STATIC/KINEMATIC body (or, for inertia, a fixed_rotation one), so multiplying by inv_mass/ inv_inertia anywhere naturally applies zero effect instead of needing a body-type check at every use site.

on_physics_process() #

Called once per physics step, before force integration - override to run custom per-step physics logic (e.g. a leg's IK target update).

on_trigger_enter(other) #

Called the first physics step this body (or other) starts overlapping a sensor - fired instead of on_collision_enter for any pair where either body has is_sensor=True, since a sensor detects overlap without a physical collision response. No-op by default.

on_trigger_exit(other) #

Called the physics step this body/other's sensor overlap ends.

velocity_at_point(world_point) #

The linear velocity of the material point on this body currently at world_point - its center-of-mass velocity plus the tangential velocity from rotation about that offset. Needed by the contact solver (relative velocity at the actual contact point, not just the two bodies' center velocities, matters once either body is rotating).

wake() #

Clears sleeping state and resets the sleep timer - called automatically by force/impulse application, and by the solver when an awake body touches a sleeping one.

A no-op if already awake: apply_force()/apply_impulse() call this unconditionally, including from within the contact solver's own routine impulses (a resting body still gets a small corrective impulse every step, to counteract that same step's gravity - it's what "resting" means). If this reset the sleep timer even for an already-awake body, that alone would keep a perfectly settled body from ever accumulating enough idle time to sleep at all.