gale.physics package

gale.physics: 2D physics for gale games, backed by Box2D (never exposed in the public API — everything here works in plain pixel units and gale’s own vocabulary) plus a lightweight scene graph for organizing physics entities.

See docs/examples/physics.rst for a walkthrough.

Author: Alejandro Mujica (aledrums@gmail.com)

class gale.physics.Body(b2_body, body_type, pixels_per_meter)[source]

Bases: object

A physics body, in pixel units. Wraps a single Box2D body (never exposed) plus its fixtures. Created through World.create_static_body/ create_dynamic_body/create_kinematic_body, never directly.

Usage example:

player = world.create_dynamic_body(100, 50, CircleShape(radius=10)) player.apply_impulse(0, -400) # jump

# Every frame, after world.update(dt): pygame.draw.circle(surface, “white”, player.position, 10)

Parameters:
  • b2_body (Any)

  • body_type (int)

  • pixels_per_meter (float)

property position: Vector2
property angle: float
property velocity: Vector2
property angular_velocity: float
set_velocity(vx, vy)[source]
Parameters:
  • vx (float) – Horizontal velocity, in pixels per second.

  • vy (float) – Vertical velocity, in pixels per second.

Return type:

None

apply_force(fx, fy)[source]

Apply a continuous force at this body’s center of mass (call every frame while the force should be acting, such as a thruster).

Parameters:
  • fx (float) – Horizontal force.

  • fy (float) – Vertical force.

Return type:

None

apply_torque(torque)[source]
Parameters:

torque (float) – The torque to apply this step.

Return type:

None

apply_impulse(ix, iy)[source]

Apply an instantaneous change in momentum at this body’s center of mass (a one-off “kick”, such as a jump).

Parameters:
  • ix (float) – Horizontal impulse.

  • iy (float) – Vertical impulse.

Return type:

None

add_circle(shape)[source]
Parameters:

shape (CircleShape) – The circle fixture to attach to this body.

Return type:

None

add_box(shape)[source]
Parameters:

shape (BoxShape) – The box fixture to attach to this body.

Return type:

None

add_polygon(shape)[source]
Parameters:

shape (PolygonShape) – The polygon fixture to attach to this body.

Return type:

None

property touching_bodies: List[Body]

Every other Body currently in contact with this one (a cheap way to answer “is this body resting on something,” with no event bookkeeping needed).

Type:

returns

destroy()[source]

Remove this body (and its fixtures) from its World. Do not use this Body afterwards.

Return type:

None

class gale.physics.BodyType[source]

Bases: object

Identifies how a Body is simulated.

  • STATIC: never moves, infinite mass. Use it for the ground, walls, and any other fixed level geometry.

  • DYNAMIC: fully simulated, affected by gravity, forces, and collisions. Use it for anything that should move and react realistically, such as a player or a projectile.

  • KINEMATIC: moves exactly according to the velocity you set on it, is not affected by gravity/forces/collisions with other bodies, but does push dynamic bodies resting on it. Use it for moving platforms, elevators, or anything else whose motion is scripted rather than simulated.

STATIC: int = 0
DYNAMIC: int = 1
KINEMATIC: int = 2
class gale.physics.BoxShape(width, height, density=1.0, friction=0.3, restitution=0.0, is_sensor=False, offset=(0, 0))[source]

Bases: object

A rectangular fixture.

Usage example:

body.add_box(BoxShape(width=32, height=32))

Parameters:
  • width (float)

  • height (float)

  • density (float)

  • friction (float)

  • restitution (float)

  • is_sensor (bool)

  • offset (Tuple[float, float])

class gale.physics.CircleShape(radius, density=1.0, friction=0.3, restitution=0.0, is_sensor=False, offset=(0, 0))[source]

Bases: object

A circular fixture.

Usage example:

body.add_circle(CircleShape(radius=8, friction=0.4, restitution=0.6))

Parameters:
  • radius (float)

  • density (float)

  • friction (float)

  • restitution (float)

  • is_sensor (bool)

  • offset (Tuple[float, float])

class gale.physics.Joint(b2_joint)[source]

Bases: object

Base class for a constraint between two bodies. Created through World.create_revolute_joint/create_wheel_joint, never directly.

Parameters:

b2_joint (Any)

destroy(b2_world)[source]

Internal — use World.destroy_joint instead.

Parameters:

b2_world (Any)

Return type:

None

class gale.physics.Node(x=0, y=0, body=None, children=None)[source]

Bases: object

A lightweight scene graph node for organizing physics entities: every node has an absolute position (matching how everything else in gale already works), optionally owns a Body (in which case its position/angle are pulled from the body every update, since physics is the source of truth for anything simulated), and can have children for grouping/attachment — a decorative or dependent node that should follow its parent’s body around, or just a way to destroy a group of related nodes together.

This mirrors gale.ui.Container’s parent/children/add_order-render pattern, applied to physics entities instead of widgets.

Usage example:

chassis_node = Node(body=chassis_body) exhaust_node = Node(offset=(-20, 0)) # a decoration, no body of its own chassis_node.add_child(exhaust_node)

# Every frame, after world.update(dt): chassis_node.update(dt) chassis_node.render(surface)

Parameters:
  • x (float)

  • y (float)

  • body (Body | None)

  • children (Sequence[Node] | None)

add_child(node)[source]
Parameters:

node (Node) – The child to add. Rendered/updated after every previously added child (add-order is z-order, as in gale.ui.Container).

Return type:

None

remove_child(node)[source]
Parameters:

node (Node) – The child to remove.

Return type:

None

property world_position: Vector2

This node’s absolute position. A node with a body already has an absolute position (Box2D bodies are always in world coordinates); otherwise, this composes every ancestor’s position with this node’s own offset.

Type:

returns

update(dt)[source]
Parameters:

dt (float) – Time elapsed, in seconds, since the last call.

Return type:

None

render(surface)[source]
Parameters:

surface (Surface) – The surface to draw this node (and its children) on. The base Node itself draws nothing — subclasses/games draw whatever they want at self.world_position.

Return type:

None

class gale.physics.PolygonShape(points, density=1.0, friction=0.3, restitution=0.0, is_sensor=False)[source]

Bases: object

An arbitrary convex polygon fixture (at most 8 vertices, Box2D’s own limit).

Usage example:

body.add_polygon(PolygonShape([(0, -10), (10, 10), (-10, 10)]))

Parameters:
  • points (List[Tuple[float, float]])

  • density (float)

  • friction (float)

  • restitution (float)

  • is_sensor (bool)

class gale.physics.RevoluteJoint(b2_joint)[source]

Bases: Joint

A pin joint: constrains two bodies to rotate around a shared point, optionally motorized and/or limited to an angle range.

Usage example:

joint = world.create_revolute_joint(anchor_body, arm, (100, 50)) joint.enable_motor = True joint.motor_speed = 2.0 joint.max_motor_torque = 500

Parameters:

b2_joint (Any)

property angle: float
property motor_speed: float
property max_motor_torque: float
property enable_motor: bool
class gale.physics.WheelJoint(b2_joint)[source]

Bases: Joint

A wheel with suspension: constrains a body to move along an axis relative to another (the suspension), plus an unconstrained rotation (the wheel spinning), optionally motorized.

Usage example:

joint = world.create_wheel_joint(

chassis, wheel, wheel.position, frequencyHz=4, dampingRatio=0.7

) joint.enable_motor = True joint.motor_speed = -10 # drive forward joint.max_motor_torque = 800

Parameters:

b2_joint (Any)

property motor_speed: float
property max_motor_torque: float
property enable_motor: bool
property frequency: float
property damping_ratio: float
class gale.physics.World(gravity=(0, 900), pixels_per_meter=30.0, fixed_timestep=0.016666666666666666)[source]

Bases: object

A physics simulation, in pixel units. Wraps a Box2D world (never exposed) and every Body/Joint created in it.

Follows the same update/fixed_update split Unity uses to decouple the game loop’s real, variable dt from the fixed timestep Box2D’s solver needs for stable results: call update(dt) once a frame, same as everything else in gale; it accumulates dt and calls fixed_update() (a real Box2D step) as many times as needed to consume it. fixed_update() is itself a public method, useful for tests (or the rare game) that want to drive the simulation one fixed step at a time directly.

Usage example:

world = World(gravity=(0, 900)) ground = world.create_static_body(200, 280, BoxShape(400, 20)) ball = world.create_dynamic_body(200, 50, CircleShape(radius=10))

# In your state: def update(self, dt: float) -> None:

self.world.update(dt)

Parameters:
  • gravity (Tuple[float, float])

  • pixels_per_meter (float)

  • fixed_timestep (float)

create_static_body(x, y, shape=None)[source]
Parameters:
  • x (float) – Initial x position, in pixels.

  • y (float) – Initial y position, in pixels.

  • shape (Any | None) – A CircleShape/BoxShape/PolygonShape to attach immediately. The default value is None, so fixtures can be added later via Body.add_circle/add_box/add_polygon.

Returns:

The new Body.

Return type:

Body

create_dynamic_body(x, y, shape=None)[source]
Parameters:
  • x (float) – Initial x position, in pixels.

  • y (float) – Initial y position, in pixels.

  • shape (Any | None) – A CircleShape/BoxShape/PolygonShape to attach immediately. The default value is None, so fixtures can be added later via Body.add_circle/add_box/add_polygon.

Returns:

The new Body.

Return type:

Body

create_kinematic_body(x, y, shape=None)[source]
Parameters:
  • x (float) – Initial x position, in pixels.

  • y (float) – Initial y position, in pixels.

  • shape (Any | None) – A CircleShape/BoxShape/PolygonShape to attach immediately. The default value is None, so fixtures can be added later via Body.add_circle/add_box/add_polygon.

Returns:

The new Body.

Return type:

Body

destroy_body(body)[source]
Parameters:

body (Body) – The Body to remove from this World.

Return type:

None

create_revolute_joint(body_a, body_b, anchor, **options)[source]
Parameters:
  • body_a (Body) – The first body.

  • body_b (Body) – The second body.

  • anchor (Tuple[float, float]) – The pivot point, in pixels, in world coordinates.

  • options (Any) – Forwarded to Box2D’s revolute joint definition (e.g. enableLimit, lowerAngle, upperAngle).

Returns:

The new RevoluteJoint.

Return type:

RevoluteJoint

create_wheel_joint(body_a, body_b, anchor, axis=(0, 1), **options)[source]
Parameters:
  • body_a (Body) – The chassis (or other body the wheel is attached to).

  • body_b (Body) – The wheel.

  • anchor (Tuple[float, float]) – The suspension’s attachment point, in pixels, in world coordinates.

  • axis (Tuple[float, float]) – The suspension’s movement axis, in body_a’s local frame. The default value is (0, 1) (straight up/down).

  • options (Any) – Forwarded to Box2D’s wheel joint definition (e.g. frequencyHz, dampingRatio, maxMotorTorque, motorSpeed, enableMotor).

Returns:

The new WheelJoint.

Return type:

WheelJoint

destroy_joint(joint)[source]
Parameters:

joint (Joint) – The Joint to remove from this World.

Return type:

None

on_collision_begin(callback)[source]
Parameters:

callback (Callable[[Body, Body], None]) – Called with (body_a, body_b) when two fixtures start touching. At least one of the two must not be a sensor for physical collision response; either or both may be sensors for overlap-only detection.

Return type:

None

on_collision_end(callback)[source]
Parameters:

callback (Callable[[Body, Body], None]) – Called with (body_a, body_b) when two fixtures that were touching stop touching.

Return type:

None

update(dt)[source]

Call once a frame with the real, variable elapsed time. Steps the simulation forward by calling fixed_update() as many times as the accumulated time covers.

Parameters:

dt (float) – Time elapsed, in seconds, since the last call.

Return type:

None

fixed_update()[source]

Advance the simulation by exactly one fixed_timestep.

Return type:

None

render_debug(surface, color=(0, 255, 0))[source]

Draw every fixture’s outline directly onto surface, with no assets — a debugging aid for building a level.

Parameters:
  • surface (Surface) – The surface to draw on.

  • color – The outline color. The default value is (0, 255, 0).

Return type:

None

Submodules