DEV Community

Cover image for I'm Building a Game Engine in Node.js (Yes, Really)
Sk
Sk

Posted on

I'm Building a Game Engine in Node.js (Yes, Really)

As crazy as the title sounds… it’s happening.
And it’s looking really good.

The results are already interesting:

Godot like scene tree

scene tree

Dual screen + culling

dual screen

Physics

physics engine intergration

Character controller with a procedurally generated map

Character Controller 2D

Once you read the Node.js source code, or any runtime, really,you realize how novel and powerful this stuff is. And at that point, you can’t stop yourself. You have to build.

So that’s how Nexus came to be. Also based on another project a C++ N-API renderer for Node.js. More on that here:
How I built a Renderer for Node.js

But every project has its problems. I’d say it’s about 70% boilerplate and 30% real programming, and that 30% is the novel, painful, decision-heavy part.

A big chunk of that 30% was physics and camera.
Spoiler: very difficult.

To be fair, I’ve built a physics engine from scratch in C before. But JavaScript is a different story. It’s less expressive, garbage-collected, single-threaded, an entirely new frontier of problems.

Still… it’s looking good.
Also I have a plan to aggressively optimize see I Tried to Beat WebAssembly With Node.js.


Camera

The problem: your screen is static.
It doesn’t move. It doesn’t resize.

So what happens when the player is at screen size + 1 pixel?

The world is infinite.
The screen is finite.

I can’t move the real screen, but what if I create a pseudo screen?
A simulated one. Not real. One that can move freely.

World (large area)
+-------------------------------------------------------------+
|                                                             |
|   . . . . . . . . . . . . . . . . . . . . . . . . . . .     |
|   |----------------------- Screen ---------------------|    |
|   |                                                   |    |
|   |                                                   |    |
|   |                                                   |    |
|   |                                                   |    |
|   |                                                   |    |
|   |---------------- (real screen) -------------------|    |
|                ^                                            |
|                |                                            |
|          (player P)                                         |
|               P                                             |
|                                                            C|
|         [Camera viewport]  ------------------               |
|         .----------------------------------.                |
|         |                                |                 |
|         |            CAMERA (C)           |                 |
|         |                                |                 |
|         '----------------------------------'                |
|                                                             |
+-------------------------------------------------------------+
Legend:
- Big box = World
- Inner rectangle labeled "Screen" = the real screen (inside the world)
- "P" = Player point placed outside the real screen
- Dashed/smaller rectangle near P labeled Camera = the pseudo screen / camera viewport that can move freely
Enter fullscreen mode Exit fullscreen mode

Whatever this pseudo screen sees gets translated onto the real screen.

That’s your frustum → viewport → screen translation.

Sounds simple, until you realize you now have two coordinate systems for every object and component:

  • Top-left ← renderer
  • Free-flowing, center-based ← camera

The real pain is translating to and from these systems.

And that’s not even the end of it.

Once the camera exists, it has to be useful:
a controller that actually follows the player smoothly,
culls pixels outside the frustum/viewport,
translates every pixel into screen space (and back, if needed).

On top of translation madness, now you add follow behavior, shake, culling, multiple cameras…

Still, it’s looking decent, as you saw from Character controller with a procedurally generated map,


Physics Engine

Like I said, I’ve built a physics engine in C before.

It’s hard, not just because it’s real-time, but because it’s simulated real-time. Things move fast. Things collide fast. You can’t console.log your way out of that.

So this time I went with Matter.js. Pure JS. Solid library. It works.

Until I realized it doesn’t support kinematic bodies, the centerpiece of almost every 2D game: your sensors, your AI, moving platforms, and a lot more.

An engine usually has three body types:

  • Static - immovable; used for terrain/scenery, not affected by forces
  • Dynamic - fully simulated; affected by forces and collisions
  • Kinematic - moves by script/control; collides but isn’t driven by physics forces

Kinematic bodies are the core of gameplay.

But I’m a developer, so I thought:
“Easy. A kinematic body is just a static body that moves.”

Huge mistake.

Static bodies don’t collide with other static bodies (for example, a bullet won’t collide with a kinematic enemy or moving platform in this case), because the engine assumes static means never moves. Now I’m trying to bend Matter.js and manually handle collisions myself.

It did not go well.

matter-js manual query

I’m simulating outside the simulator, feeding tainted results back in, hoping the physics engine plays nice.

It’s possible.
It’s also incredibly annoying.

So instead of burning four more days on this, I did the sane thing and switched engines, to planck.js, which actually supports everything I need.

Of course… it wouldn’t be programming without more problems.

Planck’s coordinate system is bottom-left.

So now I’m translating between three systems:

  • Top-left ← renderer
  • Middle-center ← camera
  • Bottom-left ← planck.js

And as if that wasn’t enough, Planck speaks meters, while the renderer and camera speak pixels.

That’s yet another translation layer.

Yep.
So that’s what I’m dealing with right now: migrating to a new physics engine, building a whole new translation layer.

And somehow… still having fun.

Top comments (1)

Collapse
 
sfundomhlungu profile image
Sk

Docs and References:

matter-js

Matter.js - a 2D rigid body JavaScript physics engine · code by @liabru

Matter.js is 2D rigid body physics engine for the web, using JavaScript and HTML5

favicon brm.io

planck

rapier - rust-wasm physics engine(another candidate I am looking at, if I need speed):

Rapier physics engine | Rapier

Fast and cross-platform physics engine

favicon rapier.rs