Recently I made a tiny demo where I implemented a subset of the chess game, made it in 3D and implemented using React exclusively for the job. It will be a quick summary of what happened and how it happened.
Screenshot of the final effect.
I some have experience with Blender so I quickly put together a set of geometric (Bauhaus style) pieces.
Blender models of pieces.
In order to display them I had to go for WebGL as usual with 3D rendering on the web. Normally I like to do it all myself, but this time I decided that maybe instead of spending a week on proving a point that I can do it myself, I might rather finish the project.
So here we go, Three.js to make handling 3D easier than usual and react-three-fiber
because it exists and I really want to check it out.
The first thing that is mind-blowing to me is that I can define Three.js in JSX. How? Custom renderers. There is a great talk from React Conf 2019 that explains how it works.
What's even greater, you can mix JSX targetting this renderer with regular React for React DOM too, so Three.js elements can be nested in regular React. Although, sometimes it might be tricky to remember all caveats that apply there. For example, rendering context is not passed to the inner renderer, so if you have a state library with a state provider outside in the React context, you will have to pass it to the inside one.
This is what it looks like in practice:
<Canvas
concurrent
pixelRatio={[1, 2]}
camera={{ position: [4, 10, 0] }}
>
<ambientLight intensity={0.3} />
<Suspense fallback={null}>
{/* ... */}
Not everything has to be modelled and imported. For example, chessboard is easy to achieve using simple shapes (simplified Square.tsx
):
<group position={[x, 0, y]}>
<mesh>
<boxBufferGeometry attach="geometry" args={[size, 0, size]} />
<meshStandardMaterial attach="material" color={color} />
</mesh>
</group>
It can be probably even simpler than this, since – if I remember correctly – there is Plane
as a primitive type but I haven't looked into that too much.
Basically, all game logic happens in one place here – when showing available moves. It is probably not the most readable or concise implementation you will see but was straightforward to define and implement.
For a given coordinate, I check:
I have a helper that checks a given direction (specified as { x: -1 | 0 | 1, y: -1 | 0 | 1 }
, which expresses any diagonal, horizontal or vertical movement in both directions) and goes on until:
8
, or 1 field).For pawns, I use an assumption (but I don't have to worry about the rules of chess changing or getting more complex any time soon, right?) that a white pawn on y === 6
or a black one on y === 1
is entitled to make the initial, 2-field-long move.
I haven't implemented castling, promotion or en passant as I was able to get away with not having them for the demo video (oops).
The game can be in one of the following states:
As a lazy React developer, I put all the actual logic wherever the final press event is handled. It happened to be in HoverableSquare
, as it is displayed over the fields that the user can move to.
I check if there is some piece on the target field (then it's an attack) or not (a move). I update my field -> Piece | null
mapping array (most likely buggy, but works reasonably well for the first few moves).
Then the tricky part happens: hovered
element becomes null
(re-render alert for <AvailableMoves />
and <Figures />
; basically pink color disappears from the screen). The game state is updated (the changes I mentioned in the previous paragraph come to life and the current turn is passed to the other player).
Presentation plays important role in any visual-heavy project. It's most likely the deciding factor for why you are actually reading this note.
I have (limited) experience with 3D modelling and wanted to finish it quickly so I went for simple tricks: directional lighting to make everything visible, shadows for realism's sake and additional point lights for fancy glares.
Comparison before and after effects.
This demo lacks at least several things:
react-spring
should be perfect for the job).Hope this was informative and met your expectations. If any part was unclear or you have any more questions feel free to mention/DM me on Twitter.
I think this is also a good place to thank some people without whom I wouldn't be writing this now:
react-three-fiber
.Have a great day!
Sometimes I write blogposts. It doesn’t happen very often or in regular intervals, so subscribing to my newsletter might come in handy if you enjoy what I am writing about.
Never any spam, unsubscribe at any time.