This is a port of rs-breakout "A Breakout clone written in Rust using a strict ECS architecture" to Nim. It was done for learning purposes. It also incorporates improvements done by me. These are explained below.
The original codebase when updating a system or creating a new entity, it iterates up
to MAX_ENTITIES
. This was eliminated by using a specialized data structure.
For entity management (creation, deletion) a SlotMap
is used. It also holds
a dense sequence of set[HasComponent]
which is the "signature" for each entity.
A signature is a bit-set describing the component composition of an entity.
This is used for iterating over all entities, skipping the ones that don't match a system's "registered" components.
These are encoded as Query
, another bit-set and the check performed is: signature * Query == Query
.
Alpha value is used to interpolate between next and previous transforms. Interpolation function
for angles
was implemented.
As explained by the original authors in their documentation for backcountry
Transforms can have child transforms attached to them. We use this to group entities into larger wholes (e.g. a character is a hierarchy of body parts, the hat and the gun).
I changed the implementation of children: [Option<usize>; MAX_CHILDREN]
with the design described at
skypjack's blog.
Now it is a seperate Hierarchy
component following the unconstrained model.
Immediate updates are implemented by traversing this hierarchy using dfs traversal.
A type safe vector math library was created for use in the game. distinct
types are
used to prohibit operations that have no physical meaning, such as adding two points.
type
Rad* = distinct float32
func lerp*(a, b: Rad, t: float32): Rad =
# interpolates angles
type
Vec2* = object
x*, y*: float32
Point2* {.borrow: `.`.} = distinct Vec2
func `+`*(a, b: Vec2): Vec2
func `-`*(a, b: Point2): Vec2
func `+`*(p: Point2, v: Vec2): Point2
func `-`*(p: Point2, v: Vec2): Point2
func `+`*(a, b: Point2): Point2 {.
error: "Adding 2 Point2s doesn't make physical sense".}
build
is a macro that allows you to declaratively specify an entity and its components.
It produces mixin
proc calls that register the components for the entity with the arguments specified.
The macro also supports nested entities (children in the hierarchical scene graph) and composes perfectly
with user-made procedures. These must have signature proc (w: World, e: Entity, ...): Entity
and tagged with entity
.
- Creates a new entity, with these components, returns the entity handle.
let ent1 = game.build(blueprint(with Transform2d(), Fade(step: 0.5),
Collide(size: vec2(100.0, 20.0)), Move(speed: 600.0)))
- Specifies a hierarchy of entities, the children (explosion particles) are built inside a loop.
The
build
macro composes with all of Nim's control flow constructs.
proc createExplosion*(world: var World, parent: Entity, x, y: float32): Entity =
let explosions = 32
let step = Tau / explosions.float
let fadeStep = 0.05
result = world.build(blueprint(id = explosion)):
with(Transform2d(translation: Vec2(x: x, y: y), parent: parent))
children:
for i in 0 ..< explosions:
blueprint:
with:
Transform2d(parent: explosion)
Draw2d(width: 20, height: 20, color: [255'u8, 255, 255, 255])
Fade(step: fadeStep)
Move(direction: Vec2(x: sin(step * i.float), y: cos(step * i.float)), speed: 20.0)
It expands to:
let explosion = createEntity(world)
mixTransform2d(world, explosion, mat2d(), Vec2(x: x, y: y), Rad(0), vec2(1, 1),
parent)
for i in 0 ..< explosions:
let :tmp_1493172298 = createEntity(world)
mixTransform2d(world, :tmp_1493172298, mat2d(), vec2(0, 0), Rad(0),
vec2(1, 1), explosion)
mixDraw2d(world, :tmp_1493172298, 20, 20, [255'u8, 255, 255, 255])
mixFade(world, :tmp_1493172298, fadeStep)
mixMove(world, :tmp_1493172298,
Vec2(x: sin(step * float(i)), y: cos(step * float(i))), 20.0)
explosion
- Fixed-Time-Step Implementation
- bitquid
- Goodluck A hackable template for creating small and fast browser games.
- rs-breakout
- Breakout Tutorial
- Backcountry Architecture lessons learned when using ECS in a game
- ECS Back and Forth excellent series that describe ECS designs
- ECS with sparse array notes
- Trace of Radiance the idea of using distinct types in a math lib
- #nim-gamedev, a friendly community interested in making games with nim.
This library is distributed under the MIT license.