I'm Building a Game Engine in Node.js (Yes, Really)
Source: Dev.to
As crazy as the title sounds… it’s happening.
And it’s looking really good.
Results
Godot‑like scene tree
Dual screen + culling
Physics
Character controller with a procedurally generated map
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 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:
| System | Origin |
|---|---|
| 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,
- 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
| Type | Description |
|---|---|
| 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.
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:
- Top‑left ← renderer
- Middle‑center ← camera
- 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.




