Video Summary and Transcription
This Talk discusses the process of recreating Age of Empires II in React. The speaker shares their inspiration for the project and explores different approaches to game development using React. They demonstrate how to create an isometric grid, enable scrolling, and render units. The Talk also covers handling unit clicks and implementing right-click movement, as well as techniques for making React render more consistently. The speaker concludes by highlighting the value of exploring different tools and approaches in software development.
1. Introduction to Age of Empires II in React
Welcome to recreating Age of Empires II in React. I'm Joe, a web engineer consultant with experience in React and front-end development. I also enjoy stand-up comedy and creating video games. Check out my Twitter and homepage for more details.
♪♪ Hello, and welcome to recreating Age of Empires II in React or the other subtitle. Why do I keep doing this to myself? If you're wondering who myself is, hello. Hi, I'm Joe. I'm a web engineer consultant. I previously worked for places like the BBC, Monzo, Lego, doing all manner of React and front-end things for people.
Nowadays, I shop my skills around for anything view-related, React-related, or general web stuff. And as well as doing web engineering, I also do many, many silly things in my spare time, including stand-up comedy. I also make silly video games, and indeed combine them where I do stand-up comedy where we play silly video games as well. If you want to see some more details about that, you can check out my Twitter at joeheart, or you can go to my lovely homepage, joeheart.co.uk. I spend a lot of time styling it. Please let me know what you think of those styles when you have a look at it. And if you ever want to e-mail me for any reason to tell me how much you didn't like this talk, please drop me an e-mail at joeheart.dev.
2. Setting the Context: Katamari Node Modules
One of my favorite games I ever made for a developer event called Smush back in the day was called Katamari Node Modules, which is a version of Katamari Damacy. It's a little video game where you play a little node modules folder and you roll around the world. The aim of the game is to get as large as possible.
Now to set the context of where this talk is going, I thought it would be good to talk about an example of the kind of things I've done in the past. One of my favorite games I ever made for a developer event called Smush back in the day was called Katamari Node Modules, which is a version of Katamari Damacy, which if you've played that, you'll know where this is going where it's a little video game where you play a little node modules folder and you roll around the world. And as you do, and as you hit into things, things stick onto you. And the aim of the game is to get as large as possible. So to roll around this little world where all the objects in it represent NPM packages of various sizes, you want to get the largest NPM packages as possible and just bloat yourself as much as you can, much like our real Node Modules folders do on our dev machines every single day. So that's the kind of set the context a little bit.
3. Recreating Age of Empires II in React
This part discusses how the speaker got the idea to recreate Age of Empires II in React and provides a brief introduction to the game. The speaker explores different approaches to recreating the game and shares tips for developing a video game using React.
And then to kind of explain how I got here and talking about Age of Empires II, like all good talks, this started with a random tweak because I was a little bored at work where we were talking about graphing libraries and I referenced in the meeting this Age of Empires Two graph, which happens at the end of the game, which kind of renders all this stuff for you, which I think is really beautiful about how it splits up these things and it shows you like when certain events happened in the game.
And I played, joked around on Twitter for a while, for a few people, and then eventually it was Lorenzo who suggested why don't you submit the CFP to React Advanced for this? And I did, and now we're here. Now, when this first started, I guess I need to add a little bit of context for what is Age of Empires Two, for those of you who may not know what it is. It was a real time strategy video game. It was released in 1999, I think. I played it a lot when I was a kid. And it was often played on one of these, a giant CRT monitor that went boom when you played it.
And it was a game where you would assume the command of one of culture's greatest armies, and you would evolve them from the stone age up to the castle age and the medieval age and get more technology. And you would mine resources and build your base out and choose your units and your armies very carefully, compose them, and then go and fight the enemy. But you'd really just spam knights. That would be mostly what you would do, and it would work incredibly effectively.
So I started thinking what could I do in the context of Age of Empires II and React? What would be an interesting talk to come and put together about React technology in Age of Empires II? And there were many different ways you could go about it. Age of Empires II has relatively famous menus in it. There are these gorgeous skeuomorphic designs. How would it be to try and recreate that and React? And there's lots of really interesting stuff there Just getting the fonts to render exactly like that, because that's just Georgia, but on a model website is a little bit tricky. You have to turn off any kind of font smoothing that's going on in the browser. And even the main logo of Age of Empires, it uses a particularly old version of the Castellan font, which is from 1997 and is really difficult to get ahold of now.
I thought maybe I could go and create a jokey framework for creating Age of Empires II components, which would be like this oil-oil CSS and I could build an amazing medieval website generator where people would spend all their time learning my own custom design token system rather than just learning CSS. But I thought I wouldn't do that, I thought that would be not as fun. Instead, I started thinking about what if I could try and make Age of Empires II? Or how much of Age of Empires II could I make using React? Recreate this game in React as much as possible? How hard would it be, in general, to recreate Age of Empires II? And like, if you ever do get the inkling to I'm gonna go and remake one of the greatest strategy video games of all time, here are some tips of good ways of doing it. These would be good decisions to make. You could use a video game engine. This has always grown when doing video game stuff because they have loads of helper functions and rendering things. They have solved a lot of those common video game problems time and time again, and that will just make your life so much easier. Use a type language, because it'll just give you a lot more predictability about things that are updating, all your kind of event cycles. You'd be able to build out quite a sizable amount of your app kind of automatically by just making sure that things red line in the correct places. And then if you are gonna develop a video game, you do ideally want to be using a kind of like rasterized style rendering method. So if you are going to do it in the web, try and use something like HTML 5 canvas where you can draw actual sprites and images straight onto it. Or even if that's slightly too low if you use something like Three.js where you could do like a pseudo 3D thing or even possibly like React 3D Fiber, React 3 Fiber would be a great thing for hooking into like React state management things. So those would all be like really good ways of approaching this project.
4. Building the Age of Empires 2 MVP
But I decided to start from scratch using JavaScript and React to render everything with DOM elements. The main reason for this is to make the project as painful as possible, which I believe is more entertaining and a way to learn about the tools. We want to create an Age of Empires 2 MVP by rendering an isometric background, allowing scrolling, selecting units and buildings, and moving units by right-clicking on the ground. Rendering the isometric battlefield is easy using CSS. The grid is made relative, centered, and each row is a flex row with no wrap. CSS variables are used for size and grid layout. The code shown renders a grid, not an isometric one, but it's a starting point.
But I decided to instead start entirely from scratch use only JavaScript and React and render everything using DOM elements. And the main reason I decided to do this is because fundamentally, I just wanted to make sure that this project was as painful as possible for me. Mainly because I think it's more entertaining for you. And I just think you can learn a lot about a tool if you use it in the wrong ways. And that's kind of what we're going to try and do today.
So, you've gone to your project manager, you've talked about rebuilding Age of Empires 2 and React. You've got approval, you now need to crack out the JIRA and try and figure out what is an Age of Empires 2 MVP? What's the minimum viable Age of Empires that we can make? And for me I think it's gonna be, we want to be able to render this isometric background, this battlefield that's going. You can't quite see necessarily this isometric here but there is basically a grid of isometric tiles that all the units can exist on. You want to be able to scroll around the battlefield. So in the game, when you would move your cursor to the top left, it would move to the top left. When you move to the top right, it would move to the top right. You want to be able to click on a unit, a building, in a fairly generic way so we can select things around the battlefield. And then we want to be able to right-click on the ground to move the unit. I think if we can start with that, we can then be able to build out and build the rest of Age of Empires.
So rendering an isometric battlefield, this is actually surprisingly easy using CSS. So our render method is very simple. We're just gonna have a div which will give a class of grids too. And then I ended up creating two arrays of indices so that we can just map over them for, one for the rows, where we add a nested row on each of them and columns where mapping over and rendering tiles. Also, I should probably just point out now, this is deeply not accessible. I thought trying to make an accessible DOM based Age of Empires 2 would be slightly more painful than I was quite up for this time. And so our grid, we're just gonna make it relative. We're gonna pop it at the top left of the page and center it a little bit more. Each row is just gonna be a flex row. We're gonna make sure there's no wrap on it because I had a weird bug where they were like, if you accidentally shrunk this, it wouldn't screen too much. It would like pop background. You end up with this like jaggedy grid. And we just use some very simple CSS variables to make sure that each block has the correct size and the correct grid. Now if you're looking at these two pieces of code, you're going, Joe, that just renders a grid, not an isometric one, you would be right. That bit of code that I just showed you would render this kind of thing on the right hand side. It's just a little grid like that.
5. Creating an Isometric Grid and Enabling Scrolling
Using a few lines of CSS and a render method, we can create a lovely isometric grid for Age of Empires 2. To obtain the game textures, I downloaded a modding software called Turtle Pack and extracted the SLP and DRS files. After exporting a grass tile as a bitmap, I adjusted it in Photoshop to fit the grid. With the background image set on the tiles, we quickly met our first requirement. Moving on, we needed to enable scrolling on the battlefield. By utilizing React's mouse move events and a ref connected to the container div, we implemented a function that scrolls the view when the mouse is near the container's edge.
But then using just a lovely little bit of transform where we're gonna rotate everything in the X and then in the third dimension, we're gonna rotate the Z a little bit. We end up with this lovely isometric grid, all done with just a few lines of CSS and a render method.
Now you're gonna look at that and go, Joe, that doesn't look anything like Age of Empires 2. And I'll be like, you are correct. This is a screenshot from Age of Empires 2 and look at this lovely kind of mushy grass that this unit is standing on. So we wanna be able to go and get those textures.
Now I could just go and Google textures for Age of Empires 2. Weirdly, they were quite hard to find. So instead I ended up going and downloading this wonderful modding piece of software called Turtle Pack, which allows you to extract the SLP and DRS files, which were particular files. I don't know if they were particular to that exact game, but that stored all of this data inside. So I went to this lovely forum, which, by the way, I just wanna say how much joy it gave me to see a website that was talking about disabling download accelerators and download managers, which has made me very nostalgic for a sense of time. So I managed to download this wonderful little tool, and I booted up my CD, and you can see that inside each of these SLPs are these almost GIF-style storage of these tiles, where it's a single file, but there're all these different ones that would be like an animation. But I managed to export one of them as a bitmap. There we go, boom, we've got our grass tile. There's a whole six pixels there, this is amazing.
Now, the problem is we've done that transform on our grid. So if we put that as a background image, it's gonna skew that again, which is too far. So, quick pop into Photoshop, straighten it up a little bit, which makes it look kind of significantly more muddy, and then we can just set the background image on all of our tiles, boom, we've got our lovely isometric grid. Look how quickly we have smashed through our first requirement, this is going really well.
Now, we wanna be able to scroll around this battlefield, as well. This is gonna get a little bit more tricky than our previous one. Now, thankfully, React makes it very, very easy to hook into mouse move events. So we can go on mouse move, have a little handler, and the handler is just gonna store the mouseX and the mouseY in some React state so that we can effectively use that basically however we want. And we're gonna use a ref, and connect it to that div, so that we can pull out properties about that div, about how tall it is, and things like that. And then we're gonna write this function. And I say, we, I mean, I did this, where basically we want to kind of do the logic of, when the mouse moves, is it within 50px of the edge of the container? Then scroll a bit in that direction if it is. And the way we're gonna do this is there's this scroll method on HTML Elements, but the scroll method isn't go and scroll a little bit, it's scroll to this exact point, kind of because that's what web documents want, right? You wanna be able to like scroll to a place, you wanna scroll to a tag or things like that. So we have to do a little bit of maths to calculate where we want the left and the top scroll to be, where it's pretty easy where we go and get the width and the height of the current container. Scroll margin is kind of like using it as the distance of how far near the edge of my screen do I want to start scrolling? And then scroll amount is essentially the speed that we're going to scroll. We're gonna get the current scroll position, that's the left scroll and top scroll.
6. Implementing Scrolling and Rendering Units
We implement a scrolling feature that allows the battlefield to move when the mouse is near the container's edge. By using React's mouse move events and a ref connected to the container div, we can scroll the view accordingly. We then move our state into a reducer to handle rendering units and buildings. We create a units array and a UI object to manage the state. We add a reducer to handle the update mouse position logic. We render units by mapping over the units in our state and dynamically setting their position as tiles. We also apply transformations to correctly display the Spearman unit. Finally, we make the units interactable by adding an on-click handler for each unit.
And then we just do, if the mouse is further along than the width of the container minus the margin, so width of the container minus the margin, just there, then add a bit of scroll to the left. And if it's the other way, take a little bit off the left, add a bit to the top, minus off the top. And then we can take this use call back, which I've wrapped, I've wrapped this method in use callback saying that it's dependent on the mouse X and mouse Y state that we defined further up. And now we can put that inside a use effect. And we've got this lovely little kind of game loop thing happening here. This was really my idea around, I wanna try playing around with React for video and stuff because the use effect loop is very similar to that gameplay loop that game engines or game software has where you're constantly doing a little bit of logic. And now when you move your cursor to the edge of the screen, it goes and moves across. We've got ourselves scrolling across the battlefield. This is fantastic with a lot of console logs as well. Very necessary, very required.
So we've smashed through this super quickly. We've got our first set and our second set of requirements done clicking on units and buildings to select them. So first of all, to be able to click on them, we need to be able to render them. So we're going to move our state that we were keeping in just some state hooks into a little bit more of a reducer because we're going to be doing a lot more. If we're going to have lots of units in the future, we want to be able to access them all in a reducer. So we're going to create a units array, add one unit in there for now, and then move all of our other stuff into a UI object so it's out of the way. And for now we're just going to add a reducer in that does the update mouse position stuff. So the logic we were storing state before, but we're now storing it in a reducer.
Now, adding a unit and rendering it, we're going to take a very similar approach to what we were doing with rendering the tiles, where we're just going to add this div that exists inside the grid element, and we're going to map over all the units in our state. We're going to give them a div where they'll have a class of unit, which will give them some generic attributes about their width and their height. And then they have whatever particular type it is. So for this, he's a Spearman, so he uses the Spearman background. And then we're going to dynamically set the position as tiles because they're going to move later rather than doing it in the CSS. And that means that we get this wonderful rendering. Now, the reason why this is happening is because the texture of the Spearman is a square and he's upright, but we've obviously done this transform so that it's down a bit. So we need to find a way to essentially flip him back. And the way we're going to do that is essentially un-rotating him a little bit, where we're going to do a negative Z rotation, rotating the X, and then also scale the Y a little bit, and boom, we have our Spearman standing up and he's ready for action. But scrolling around a battlefield with one unit in the middle isn't particularly exciting. So how do we make it interactable? Now, this is really where React shines because on-click handlers are bread and butter. So very easily for every single unit that we're going to render, we're going to give it a handle unit click, where we're going to pass in the unit ID.
7. Handling Unit Clicks and Right-Click Movement
This part discusses handling unit clicks and implementing right-click functionality for movement. It explains how methods are generated for each unit and how actions are dispatched to select entities. The text also covers the use of CSS magic to show health and implement smooth movement using CSS variables. It addresses the issue of transition time and suggests dynamically setting it based on the distance moved. Additionally, it explores the scrolling behavior and explains why the initial approach using useEffect as a game loop is not effective.
This handle unit click is actually going to generate a method for each of them. So it's going to create a method that will be called when it's ready, where it will just dispatch an action to select the entity, the select entity action. It's just going to set a selected ID of whatever the current things that I've clicked on is. And we've just added a little bit of initial state here for null, which should be fine.
And now when we click on him, and with a little bit of extra CSS magic to show a bit of health in a select bar, we can now click on him and click off, and click on, and click off. Who wouldn't play that for several hours? Isn't that fantastic?
Now, if we want to be able to right click to move around, this very similar thing of which you can right click, have a right click handler, which this is what that on context menu method does. This is essentially your on right click method, if it had another name. And we're gonna do the same thing we did for the unit, where for every single tile, we're going to give it a handler, where we're going to handle the right click. We're going to generate a new method from the X and the Y, and dispatch this method. This is a little bit more complicated than the other one, but that's because we want to go and find the currently selected unit, and then basically tell it to move by taking the position from this new one we've just admitted, we've clicked on a tile, we wanna move there. And then we're gonna go through the units, update it, and reset the state.
So now click, boom, move. Oh, that's a bit fast. We don't really, that seems unfair, and doesn't seem too realistic for the medieval era to quite have teleportation yet. So, but because we're moving it just using CSS variables, we can set transition to just linear, and suddenly we've got this moving unit. Isn't that fantastic? Oh, actually. Well, now the problem with this is, it is moving smoothly, but irrelevant of how far he moves, it's gonna take one second, which is slightly too long. Which means that if he moves far away, it's super fast, and nearby, super slow. So how can we change this? Well, we could dynamically set that transition time distance based on how far he moved. So now, if you click very, very far away, the distance will be quite long. And if you click very short, it'll be quite a shorter time. So here's an example of that working, where now he's moving super far, and it's kind of slow, and he moves a little bit, and it's much faster. Much more consistent amount of time. But then, if you start to click around a bit quicker, it's not really working too much, and suddenly this isn't quite exactly what we want. You're clicking over here, you're clicking on one of them, and it's setting the position. You click on the next place, it speeds up the animation, but the unit hasn't actually moved that far. And also, actually, if we go back to that scrolling that was happening, it's actually only scrolling when you're moving the mouse. So if you move the mouse to the edge of the frame, and you don't actually wiggle it at all, it won't keep scrolling for you. This isn't really the behavior you want. And the reason why this is happening is because that idea I had around using that useEffect as a game loop, isn't really working, because the useEffect is only running when you actually move your mouse.
8. Making React Render More and Exploring Other Tools
And it would be much more like a game if it was running every single time. We can make React render more by using various techniques like modifying variables, using timeouts, and the useAnimationFrame hook. These methods allow for more consistent rendering and create a smoother experience, similar to a game loop. As React developers, it's important to explore the boundaries of what React can do and consider other tools in our toolkit, such as HTML5.
And it would be much more like a game if it was running every single time. Where a main loop in a game is usually, handle a bunch of input, render a bunch of items, and then recalls itself, and it just keeps going, and going, and going until you exit. That's kind of fundamentally what a game loop is.
So this leads us to the horrible question that most of us normally ask the opposite of, how can we make React render more? Now I have decided to reveal many of my Dark Secrets where we can go and read our React book of dark arts to find all the various ways of making extra renders. We can use a set timeout to trigger a render by modifying a variable that then triggers our use effect. We can update a state variable amount. I'm sure many of you have done this accidentally as bugs in your own code where you have a user effect that runs on mount, but inside that user effect you also change some variable that's also in the dependency tree, and it just runs forever. Although that kind of causes more stack overflow errors than fun rendering things.
Or we could do a call call, call the actual callback inside the user effect itself. So, you do that initial use effect and then we call the callback in there. But the trouble with these things is these are ways of making it render more, but not necessarily making it render more consistently, which we would want for more kind of animation things.
So for this, we could use the wonderful exclusive React API window.requestAnimationFrame. I'm joking. It is of course not a React API. It is a browser API. It's one that helps with a lot of animation-based things, but we can hook it into React here. And I decided to nick this lovely little hook from CSS Tricks, where it's useAnimationFrame. And it basically allows you to parse in a callback, which will then be called by requestAnimationFrame, creating this very smooth way of rendering things where you are able to do it time-based rather than only on events that are happening. This is much, much closer to that game loop that I was talking about.
Now, you can just have this one animate function where you're given time. You can do a bunch of logic. You can move the mouse if it's in the margin. You can move the units. You can update sprite animations. You can walla-la-la your way for freedom. And really, it's at this point that you probably should have realized that this isn't really about rebuilding Age of Empires II and React. The reason why I kind of wanted to talk about this kind of stuff today, the kind of thing that you learn as you start to play around the edges of what React can do and what React can't do, is the real question to ask is, what is React good at and what other tools do we have? I think it's quite easy for us whenever we're looking at React things to go, it must be in the React API. There must be a React solution for this. But we have to remember that, as React developers, we have a large amount of tools in our toolkit. We can build things in HTML5.
9. Building with CSS or Skulls
We can build things in just CSS or Skulls. The things you can learn from making something do terrible things can be quite useful. I hope you've enjoyed my painful experience of attempting to rebuild Age of Empires 2 in React. I highly don't recommend it. But I hope you can go and find something fun to do with React that's completely different. If you have any comments, please let me know on Twitter. Thanks.
We can build things in just CSS or Skulls, for some reason, because I couldn't think of a fourth one to add there. Maybe the Skulls is testing, I don't know. But I think it's the things you can learn from making something do terrible things can be quite useful.
I hope that you've enjoyed my painful experience of attempting to rebuild Age of Empires 2 in React, which was awful and terrible, and I highly don't recommend it. But I hope that you can go and find something fun to go and do with React that's completely different from forms or SPAs or any of those things that React is good at. Go and do something that React is terrible at.
I've been Joe Hart. If you have any comments about how this took wing, please let me know on Twitter. Go and check out my website or drop me an email and come and have a chat. It's been an absolute pleasure. Thanks.
Comments