GithubHelp home page GithubHelp logo

planetis-m / breakout Goto Github PK

View Code? Open in Web Editor NEW
19.0 2.0 1.0 238 KB

Breakout game implemented using strict ECS architecture. Used as a testbed.

License: Other

Nim 100.00%
game nim gamedev breakout breakout-game ecs entity-component-system

breakout's Introduction

breakout-ecs

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.

Entity management was redesigned

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.

Fixed timestep with interpolation

Alpha value is used to interpolate between next and previous transforms. Interpolation function for angles was implemented.

Improvements to the hierarchical scene graph

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.

Custom vector math library

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".}

Blueprints DSL

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.

Examples

  1. 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)))
  1. 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

Acknowledgments

License

This library is distributed under the MIT license.

breakout's People

Contributors

planetis-m avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

dseeni

breakout's Issues

Todo in the future

Since I need to switch tasks, I will document here the directions/experiments
I want to follow in the future, so I'm not tempted to spend more time on it, right now.

Refactor Storage

[] and []= procs should raise KeyError when the entity doesn't exist in the Storage.
A distinct Storage type must be created solely for Signatures that keeps the old behaviour,
by adding overloads for [] and []=.

hasEntity proc needs to be created. Also proc sort[T](a: var Storage[T]; cmp: proc (x, y: T): int, start: Natural = 0)

Benchmark Storage internals

Modify the benchmark in experiments/ to use array/seqs in internal lists of Storage.
Can be done with when statements inside type section.

Switch every component store to Storage / Remove Signatures

If the benchmarks are favourable, change the design of the ecs to use only sparse sets instead
of the current combination of a sparse set and seqs. Then Signatures can be ommitted completely.

Every system will iterate upon a chosen component. sysTransform will work over (part of) Transform2d (where dirty components start), for sysFade that would be Fade, etc. Then use if checks to fetch the other required components, example:

if game.collide.hasEntity(entity):
   let collide = game.collide[entity]
else: ...

See also: https://wickedengine.net/2019/09/29/entity-component-system/

(Don't really like this design but it may worth implementing it for benchmarking)

Decomposed Trasform2d

Copy the design of cgmath math library and refactor vmath to use Decomposed instead of Mat2d. Every operation (concat, transformPoint, invert, etc) may work directly on top of it, avoiding matrix baking and decomposing on every update. A more elegant/sophisticated design could also be more performant but needs benchmarking first.

Note: Start Here

Design of Deferred updates

Another route to discover before, giving in to immediate updates is to implement bfs graph
traversal on top of Hiearachy. Wing Engine seems to do that, so it is entirely possible.

Note: Modify pathfinding gist.

Switch to immediate updates / seperate scene graph to global / model

Immediate updates (sysTransform) is touted as simpler design. Implementation similar to the bitsquid blog.

See also: piesku/goodluck#51

Note: Benchmarking is necessary before this change.

Blueprint DSL: not all entities need Transform2d

Currently creating an entity through addBlueprint always attaches Transform2d and Hiearchy components to that entity. That's not always necessary.

Note: Low Priority

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.