Clusters: using Three for a 2D game

Clusters: https://harmlessfun.xyz/clusters

I’ve used Three to remake the old 1990s freeware game Stardust (which I also wrote, back in the day) as a web game Clusters. Items of possible interest to this forum:

  • I don’t use Sprite anywhere. I found it more flexible to use OrthogonalCamera and simple ‘tile sprites’ with a square Geometry and MeshBasicMaterial, letting me easily adjust the UVs, transparencies, color effects and such.
  • Did the starry background with a rotating PerspectiveCamera and a CubeTexture background, some fun zoom tricks by animating the camera’s FOV to ridiculous values like 1 or 179
  • Responsive design to work well on cell phones and iPads (which is why I added a ‘joystick’ to an otherwise keyboard-driven game)
  • Lot of interaction between HTML elements and multiple canvases, orchestrating key, mouse, and touch events
  • Sound using THREE.Audio

clusters-top

clusters-level

https://harmlessfun.xyz/clusters

6 Likes

so cool, good job

Awesome! Really love it!

are you using cubes for the platforms or the player sprite?, cause i would like to make a 2d game using three.js but i am unsure if i can use planes with pixel art as textures or i would be better to use cubes showing one of its faces.

do you have the repository of the project to look at.

I started with cubes, but of course that’s a bit wasteful as you’re never showing 5 of the 6 sides. Instead I found it easiest to make my own THREE.Geometry() with four vertices and two triangular faces – that is, a square. Here’s the code excerpt (in Typescript):

  private uvTopLeft = new THREE.Vector2();
  private uvTopRight = new THREE.Vector2();
  private uvBottomLeft = new THREE.Vector2();
  private uvBottomRight = new THREE.Vector2();
...
    this.geo = new THREE.Geometry();
    this.geo.vertices.push(new THREE.Vector3(0, 0, 0)); // bottom left
    this.geo.vertices.push(new THREE.Vector3(0, 1, 0)); // top left
    this.geo.vertices.push(new THREE.Vector3(1, 1, 0)); // top right
    this.geo.vertices.push(new THREE.Vector3(1, 0, 0)); // bottom right
    this.geo.faces.push(new THREE.Face3(2, 1, 0));
    this.geo.faceVertexUvs[0].push([this.uvTopRight, this.uvTopLeft, this.uvBottomLeft]);
    this.geo.faces.push(new THREE.Face3(0, 3, 2));
    this.geo.faceVertexUvs[0].push([this.uvBottomLeft, this.uvBottomRight, this.uvTopRight]);
    this.geo.computeFlatVertexNormals();

The point of those “uv” Vector2s is that they point back into the source image which is a THREE.MeshBasicMaterial. I can switch between the “tiles” of the image by changing the vectors.

and still the collisions and gravity you have in the game are for 2d right?

Honestly, this feels like a lot of effort just to force Three.js into doing something it’s not really meant for. If the end result is basically a flat 2D game, then all the camera tricks, textured planes, and extra layers feel like over-engineering for what a simple 2D canvas or a lightweight 2D engine could handle much more cleanly. It works, but it doesn’t really show off what Three.js is good at.

If you want to see a more sensible approach to using Three.js in a 2D context, take a look at something like Play Spin It!. It still uses planes and textures, but the setup leans into Three.js strengths instead of fighting them, and it feels like a more practical example of 2D in a 3D framework.

2 Likes