DEV Community

Robert Mendola
Robert Mendola

Posted on

How I Built a Browser-Based Isometric RPG with Three.js

I just shipped alpha v0.17 of Eidolon, a browser-based isometric action RPG built entirely with JavaScript and Three.js. No game engine, no Unity WebGL export—just vanilla JS and a lot of math.

It's still early, but it's playable. Here's what I've learned building it so far.

Why Three.js for a 2.5D Game?

Most isometric games use 2D sprite engines. I went with Three.js because:

  1. Real lighting — Dynamic shadows and ambient occlusion without faking it
  2. Camera flexibility — Easy to add zoom, rotation, and cinematic effects
  3. Future-proofing — Can add 3D elements without rewriting everything

The tradeoff? You're managing a full 3D scene for what looks like a 2D game.

The Architecture

The game runs on a custom Entity Component System (ECS):

class Entity {
  constructor() {
    this.id = crypto.randomUUID();
    this.components = new Map();
  }

  addComponent(component) {
    this.components.set(component.constructor.name, component);
    return this;
  }

  getComponent(type) {
    return this.components.get(type.name);
  }
}
Enter fullscreen mode Exit fullscreen mode

Systems iterate over entities each frame:

class MovementSystem {
  update(entities, delta) {
    for (const entity of entities) {
      const transform = entity.getComponent(Transform);
      const velocity = entity.getComponent(Velocity);

      if (transform && velocity) {
        transform.position.x += velocity.x * delta;
        transform.position.z += velocity.z * delta;
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This keeps game logic decoupled from rendering and makes it easy to add new behaviors.

Performance: Hitting 60 FPS on Mobile

The biggest challenge was performance. Here's what worked:

1. Aggressive frustum culling

Only render what the camera can see:

const frustum = new THREE.Frustum();
frustum.setFromProjectionMatrix(
  camera.projectionMatrix.clone().multiply(camera.matrixWorldInverse)
);

for (const entity of entities) {
  entity.mesh.visible = frustum.containsPoint(entity.position);
}
Enter fullscreen mode Exit fullscreen mode

2. Instanced meshes for repeated objects

Trees, rocks, and enemies use InstancedMesh to batch draw calls:

const mesh = new THREE.InstancedMesh(geometry, material, COUNT);
Enter fullscreen mode Exit fullscreen mode

3. Object pooling

Never allocate during gameplay. Projectiles, particles, and effects all come from pre-allocated pools.

GLTF Asset Pipeline

All 3D models are loaded as GLTF:

const loader = new GLTFLoader();

async function loadModel(url) {
  const gltf = await loader.loadAsync(url);
  return gltf.scene;
}
Enter fullscreen mode Exit fullscreen mode

I use Blender for modeling, export to .glb, and compress with gltf-transform to keep file sizes small.

What's Next for v1.0

The alpha is playable but there's a lot left to build:

  • More enemy types and boss encounters
  • Inventory and loot system
  • Procedural dungeon generation
  • Sound design and music

Play the Alpha

You can play Eidolon v0.17 here: eidolon.mendola.tech

Source code: github.com/aeml/eidolon

It's rough around the edges, but I'd love feedback. Let me know what breaks.


Building games in the browser is more viable than ever. Three.js handles the heavy lifting, and modern JavaScript is fast enough for real-time games. If you're thinking about it—just start.

Got questions? Find me at mendola.tech.

Top comments (0)