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

Published: (January 8, 2026 at 08:10 AM EST)
4 min read
Source: Dev.to

Source: Dev.to

As crazy as the title sounds… it’s happening.

And it’s looking really good.

Results

Godot‑like scene tree

scene tree

Dual screen + culling

dual screen

Physics

physics engine integration

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. 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, and an entirely new frontier of problems.

Still… it’s looking good.

I also 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—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 modeExit 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:

SystemOrigin
Top‑leftrenderer
Free‑flowing, center‑basedcamera

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,
  • culling pixels outside the frustum/viewport,
  • translating every pixel into screen space (and back, if needed).

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

Still, it’s looking decent, as you saw from the character controller demo.

Physics Engine

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 JavaScript, a 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.

Body types an engine usually provides

TypeDescription
StaticImmovable; used for terrain/scenery, not affected by forces
DynamicFully simulated; affected by forces and collisions
KinematicMoves 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.

New problems with Planck.js

  • Planck’s coordinate system is bottom‑left.

  • I now have to translate between three systems:

    1. Top‑left ← renderer
    2. Middle‑center ← camera
    3. Bottom‑left ← planck.js
  • Planck works in meters, while the renderer and camera work in pixels.

That’s yet another translation layer.

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.

Back to Blog

Related posts

Read more »

Cigarette smoke effect using shaders

Article URL: https://garden.bradwoods.io/notes/javascript/three-js/shaders/shaders-103-smoke Comments URL: https://news.ycombinator.com/item?id=46497589 Points:...

Building a CLI Adapter for Hono

Overview hono-cli-adapter lets you call Hono apps directly from the CLI. Your business logic stays in Hono, so you can debug with Postman or Insomnia, ship the...