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:
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:
Gravity
A floating platform or a top-down game simply doesn't need gravity; it can be turned off per body:
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:
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 .