Skip to content

Physic Entity

A shape drawn on screen doesn't react to anything by default; attaching a physics body to it is what lets it collide, fall, bounce, or get pushed.


Overview

Physics entities are represented by PhysicBody, contained in the header "remake2d/physic.hpp". PhysicBody is an abstract base class carrying the shared logic, while StaticBody and DynamicBody implement the two concrete kinds of bodies the engine offers. Every body is built from an existing Geometry, whose points are used to compute the underlying physical shape.


Methods

The methods shared by every PhysicBody are as follows:

void               tag(std::string_view) noexcept;         // set identifying tag
const std::string& tag(void) const noexcept;               // get tag
u64                ID(void) const noexcept;                // get unique body ID
void               isSolid(bool) noexcept;                 // set solid or sensor
bool               isSolid(void) const noexcept;           // check if solid
void               move(const Vec2d&)     noexcept;
void               rotate(f32)            noexcept;
void               scale(const Fact2d&)   noexcept;
void               resize(const Dim2d&)   noexcept;
Vec2d              center(void)     const noexcept;
Dim2d              size(void)       const noexcept;

void        focusAnimation(std::string_view);        // focus an animation
Animation&  animation(std::string_view);             // get animation by name (use focused animation by default)
void        linkAnimation(std::string_view, const Animation&); // attach an animation (first animation is automatically focused

Signal<PhysicBody*, PhysicBody*> onContact;                 // emitted on contact persist
Signal<PhysicBody*, PhysicBody*> onContactStart;            // emitted on contact begin
Signal<PhysicBody*, PhysicBody*> onContactEnd;              // emitted on contact end

DynamicBody additionally exposes:

void mass(f32) noexcept;                    // set mass (kg)
f32  mass(void) const noexcept;             // get mass (kg)
void density(f32) noexcept;                 // set density (kg/m², independent of size)
f32  density(void) const noexcept;          // get density (kg/m²)
void bounce(f32) noexcept;                  // set restitution (0.0–1.0)
f32  bounce(void) const noexcept;           // get restitution
void bounceThreshold(f32) noexcept;         // set minimum velocity for bounce
f32  bounceThreshold(void) const noexcept;  // get bounce threshold
void infiniteBounce(bool) noexcept;         // enable or disable perfect bouncing
bool infiniteBounce(void) const noexcept;   // check whether perfect bouncing is enabled
void friction(f32) noexcept;                // set friction coefficient
f32  friction(void) const noexcept;         // get friction coefficient
void gravity(bool) noexcept;                // enable or disable gravity
bool gravity(void) const noexcept;          // check whether gravity is enabled
void isBullet(bool) noexcept;               // enable bullet (CCD) mode for fast objects
bool isBullet(void) const noexcept;         // check whether bullet mode is enabled
void warp(const Area&) noexcept;            // set screen-wrapping area
Area warp(void) const noexcept;             // get screen-wrapping area
void limit(const Area&) noexcept;           // set movement boundaries
Area limit(void) const noexcept;            // get movement boundaries
void jump(f32) noexcept;                    // apply an upward impulse
void push(const Vec2d&) noexcept;           // apply a continuous force
void velocity(const Vec2d&) noexcept;       // set linear velocity
Vec2d velocity(void) const noexcept;        // get linear velocity

// Parameter: DynamicBody* self
_PhysicSignal<DynamicBody*> onMove;         // emitted while moving
_PhysicSignal<DynamicBody*> onMoveUp;       // emitted while moving upward
_PhysicSignal<DynamicBody*> onMoveDown;     // emitted while moving downward
_PhysicSignal<DynamicBody*> onMoveLeft;     // emitted while moving left
_PhysicSignal<DynamicBody*> onMoveRight;    // emitted while moving right

Usage

Static vs dynamic

A wall doesn't need to react to anything; that's exactly what StaticBody does, never moving and ignoring forces and gravity. DynamicBody, on the other hand, is affected by gravity, forces, and collisions — the player, a projectile, a falling crate.

Both are built from a Geometry:

rmk::Rectangle floorShape({400, 550}, {800, 50});
rmk::StaticBody floor(floorShape);

rmk::Rectangle playerShape({400, 100}, {32, 48});
rmk::DynamicBody player(playerShape);

Tagging a body

Knowing what was just touched inside a contact callback without keeping a separate reference comes down to a tag:

player.tag("player");
floor.tag("floor");

Solid vs sensor

By default, a body physically blocks others. Making it non-solid turns it into a sensor: it still reports contacts through onContact*, but lets everything pass through it, useful for a trigger or detection zone:

rmk::Rectangle zoneShape({600, 300}, {100, 100});
rmk::StaticBody trigger(zoneShape);

trigger.isSolid(false);
trigger.tag("checkpoint");

Contacts

onContact fires every frame two bodies are still overlapping, while onContactStart/onContactEnd fire once, on the frame contact begins or ends:

player.onContactStart.join([](rmk::PhysicBody* self, rmk::PhysicBody* other) {
    if (other->tag() == "checkpoint") std::cout << "checkpoint reached\n";
});

Mass, friction and bounce

These only apply to DynamicBody, since a StaticBody never moves:

player.mass(10.0f);       // kg
player.friction(0.3f);    // 0.0 - 1.0
player.bounce(0.5f);      // restitution, 0.0 - 1.0

For a ball that should always bounce at full energy, infiniteBounce ignores the usual impact-speed threshold:

ball.infiniteBounce(true);

Gravity

A floating platform or a top-down game simply doesn't need gravity; it can be turned off per body:

player.gravity(false);

Warp and limit

Warp makes a body reappear on the opposite side of a zone once it exits it, for a wraparound screen effect. Limit does the opposite: it keeps the body from leaving a zone, stopping it right at the edges:

player.warp({0, 0, 800, 600});   // wraps around screen edges
enemy.limit({0, 0, 2000, 1200}); // stays confined to the level bounds

Jump and velocity

Jump applies an instant upward impulse, while velocity reads or sets the body's current speed directly:

player.jump(400.0f);

if (rmk::event.onPressSpace.isActive()) player.jump(400.0f);

rmk::Vec2d v = player.velocity();
player.velocity({v.x, 0}); // cancel vertical speed

Movement signals

Triggering a walk animation based on direction doesn't require checking velocity manually every frame; onMove fires as soon as the body is moving, and onMoveUp/onMoveDown/onMoveLeft/onMoveRight fire based on the exact direction:

player.onMoveLeft.join([&](rmk::DynamicBody*) {
    playerAnim.play(-1, 8); // walk-left animation
});

Attaching an animation

they attach directly to the body, and are retrieved by name, which makes it easy to manage several on the same character . so its position and rotation automatically follow the physics simulation, with no need to sync them by hand:

rmk::Animation walk("player_walk.png", {{0, 0}, {32, 48}}, 6, {32, 48});
player.linkAnimation({"walk", walk});
player.focusAnimation("walk");

player.animation().play(-1, 10);

win.draw(player); // print animation

when a animation is focused, Window::draw draw it automatically . if you want a simple sprite, you can use animation too . just don't play it .


Previous chapter Next chapter