Skip to content

Instantly share code, notes, and snippets.

@jerrygreen
Last active September 21, 2021 12:19
Show Gist options
  • Save jerrygreen/ddf66ef2c1e5e42550367fcb4d4c3d23 to your computer and use it in GitHub Desktop.
Save jerrygreen/ddf66ef2c1e5e42550367fcb4d4c3d23 to your computer and use it in GitHub Desktop.

Beginning Game Development with TypeScript and ECS

I recently started developing a game, while not using any huge game engines like Unity of Unreal Engine, nor even smaller ones like Heaps or PhaserJS or Cocos2D game frameworks. Here's what I came up with…

DISCLAIMER: Should not be considered as a "tutorial", though it might give you inspiration to do your own thing. Read this article for your entertainment!

I decided to use some of the knowledge I already had, so I ended up with this list of tools to experiment with, for a project start:

  • Electron. It can let me experiment with rendering and see frame drops, and even throttle my cpu to see if my game performant or not, with many other development tools, with ability to pack a game into executable.
  • React. A convenient way to render user interfaces: menus, buttons, and that's pretty much it.
  • TypeScript. It is an extension/superset of JavaScript and has cool typing system that helps during development to write reliable code.
  • AssemblyScript. It is a strict subset of TypeScript that compiles into performant WebAssembly, which can be useful for some hard tasks like procedural world generation and such, to decrease loading times.
  • ECSY. Entity-Component-System (ECS) pattern looks real cool for a game architecture. ECSY is a library that might help me sort out my game stuff into distinguishable parts for readability and maintainability. It is made by team from Mozilla, so I liked ECSY as soon as I found it.

This is how it looks right now:

game@x2

For now it's barely a game, and more like fundament for a game. It has game loop, some additional development infrastructure like unit tests, some build scripts to make executables for different platforms, etc.

My root React component looks very minimalistic right now:

<>
  <Head>
    <title>Project X</title>
  </Head>
  <div id="overlay">
    <p>
      <Link href="/next">
        <a>Go to next page</a>
      </Link>
    </p>
    <p>Execute function from wasm module:</p>
    <p>
      1 + 2 = <AsmComponent />
    </p>
  </div>
  <Game /> {/* <---- this is where circles and rectangles live */}
</>

You might wonder what is this "Go to next page", - but it's just a link to a blank screen, not currently used. Later on, I can add some main menu screen, settings screen, and some other. React is still neat to build such interfaces. For the main Game part, though, ECSY is used.

AsmComponent is currently just a little WebAssembly component so I can check that everything works as intended which uses this AssemblyScript code:

export function add(a: i32, b: i32): i32 {
  return a + b
}

Looks very much like TypeScript, though it has some limitations and cannot use all the TypeScript features. Full feature comparison can be found here.

The good side of AssemblyScript that it can run certain programs much faster. A friend of mine made a benchmark by implementing "Conway's Game of Life" in JS and AS, and he figured out AssemblyScript version is more than 2x performant than JavaScript! Might be quite useful for game development in JS/TS… So I might use some AssemblyScript for world generation or something like that, later.

I won't paste whole engine.js which I shamelessly stolen from official ECSY examples, but here's the part responsible for game loop:

export async function run(lastTime) {
  const time = performance.now()
  var delta = time - lastTime
  const fps = 1000 / delta
  world.fps = fps
  world.execute(delta, time)
  requestAnimationFrame(async () => run(time))
}

world.execute is a tricky fella here… execute function makes all the changes to the world: updating positions, where re-rendering and other stuff is happening. It is the most heavy part here, and it has budget of 16.67 milliseconds to do all that, to maintain 60 fps in the game. Needless to say, it should be implemented in most cool optimized way.

Entity Component System

It is the blood of my game. Again, I use little ECSY library that helps me arrange all that. It has three main elements, and a couple other:

  • Entity. It is basically any thing in the game: Human, or some Sheep, or Plant, Tree, whatever… Each entity has an unique ID and consists of many Components. In programming words, entities are classes and instances.
  • Component. It is basically data: an object, hash, associative array, you name it. Some property that an entity can have, or multiple. Something can be EdibleComponent, or HealthComponent so an entity can have hp and die, some other components may be related purely to rendering like ShadowComponent. Components can be composed and applied to an entity. Weirdly, in ECSY it's all stored in class instances, but anyway…
  • System. It is code. It runs perpetually in game loop, and has all the logic regarding different components. In many cases components have their corresponding system, for example for HealthComponent there likely to be HealthSystem which may iterate through mortal creatures to check if they're out of health to actually kill them and trigger some death animation, etc. You can guess what does ShadowingSystem, CollisionSystem, and other.
  • World. A container to store all that. Basically one is enough, but it is possible to have many worlds simultaneously.
  • Query. Queries are used by Systems to filter those particular entities that they're interested in, based on some property/component entities own

Detailed ECS architecture with pictures can be found here. It's quite neat.

3D vs 2D

In my particular example I don't have any 3D yet, but I have no limits on that, and can potentially add 3D models. It will be harder ofc, not only because 3D models are harder to do than 2D images, but it will also require some dynamic lightning and maybe some other stuff, which might be not easy to do. I'm hoping to embrace the power of 3D, actually, at some point.

What to improve

Currently I have my own RenderableSystem which re-draws entire screen from scratch on each frame. Even though it runs smoothly with this simple rectangles-circles example at 60fps, it might not work as well in real world examples. Again, to draw everything, the budget is very limited: 16.67 milliseconds. So RenderableSystem might come out to be the bottleneck for making a good performant game. I'll keep an eye on that. But for now…

That's it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment