- Part 1: introduction
- Part 2: debugging tools
- Part 3: interacting with the game
- Part 4: under the hood
We recently added a feature to Powargrid we are very excited about: a framework/interface that allows you to program your own AI to pit against yourself, your friends, and other AIs! User AIs are written in the Lua programming language, a very popular programming language for scripting and modding games. Famous examples include Civilization V and World of Warcraft. Add-ons (just user AIs for now, but maybe we'll add custom maps later!) are loaded from the Addons directory, but you have to enable this first. To enable add-ons, go to the Settings menu, select Add-ons, and check the "Enable add-ons" box. This lets you run AIs you've put in the add-ons directory. If you want to write your own AI, also check the "Enable dev tools" box. This enables the debugging console in skirmish games with user AIs. There's a button to open the directory in a file browser; it's located at .../path/to/Powargrid/Add-ons/AI. Inside you'll find a directory containing "Daft Wullie", our example AI. It's named after a Discworld character, because we are both huge Terry Pratchett fans :) The next posts will discuss the debugging tools we've included to help you write your own AI, the functions you have available to do so, and a bit about the technology behind the user AI framework, for those that like to take a look under the hood. Stay tuned!
0 Comments
Sometimes you fix a bug and you're almost sad to see it go. In this case, we finally fixed the one where the game would spawn an additional AI (computer player) when you return to the main menu scene after playing a skirmish map. That means that you have three computer players controlling two teams, so they start taking each other's turns, which means the game goes crazy and it's awesome :) And then it goes OM NOM NOM and then your buildings go *poof* So yeah, that's just a quick preview of the next mission we're working on: take out the green blobbies' new secret weapon, a giant mechanical worm that'll eat everything. As usual, it's mostly particle effects... I know one trick and I am going to milk it for all its worth ;-) The "worm sign" is just two particle systems: one spraying sand upwards, the other leaving the dust trail behind. Then, we have a simple model that pokes up through the ground and noms a building. That's it for now! We still need to finish the previous mission, but we tend to go back and forth between missions if we feel like it. Next up is probably some sound effects and some more detail work, like rocks, cacti, etc. Stay tuned for more Powargrid! Happy hacking,
Michiel Back from vacation! And back to working on streaming. I added a "loader" scene to the application which flashes our studio and game logos at you, while loading the first actual scenes (the menu and the first campaign mission) in the background. Instead of having to load the whole game, you now only need about 20% of it to start playing. Of course, if you jump straight to one of the later campaign missions, or a skirmish game, you'll still have to wait until that content has been downloaded. But as mentioned, blobbies! Unity3d note: one thing I discovered while adding a progress indicator to the loading screen: Application.GetStreamProgressForLevel() actually takes all the level's resources into account, including stuff that's been downloaded because it is in earlier scenes. The documentation doesn't say, and I assumed it would return zero if the level hadn't started loading yet, so I was summing up the progress of all levels up to and including the one I was loading. Took a while to figure out that was why I was getting values over 100% even though it wasn't done loading yet. Turns out you get to a point where, for example, every level is about 50% loaded, so if you sum those... Moving on! The new build has just gone live, so now you'll be able to jump into the game a bit faster, I hope. Enjoy!
Powargrid currently clocks in at about 120 MB, half of which is the sound track, and all of which has to be downloaded before you can play. Depending on your internet connection, this may take a while! Fortunately, Unity comes with built in streaming support: you can load a game level by level, and start playing as soon as the bits you need have arrived. So that's what I'm working on now: making sure the game loads more quickly. This mainly involves not loading stuff before it's needed. All of our levels have a "jukebox", an invisible object that's responsible for playing the music. Currently, all these have a list of all our music tracks, so to load the very first level (the main menu), you'd at least need to load about 60 megabytes of music, even though you'll probably not look at the menu for more than a minute. So, now the menu only comes with two songs: the familiar one from our trailer, and one other song for variety. The first campaign mission, which we expect most new players to start with, has four songs, so that also loads quickly. In the meantime, the web player is downloading the rest of the game, so by the time you get to mission two, which has all of the songs in its playlist, everything should be in. If not, you'll have to wait a short while at the loading screen (instead of waiting up front, before you got to do anything). Also, our loading screen is less boring than the plain white Unity one, since it features blobbies! Of course, the menu scene still needs all the building models and textures, but all in all, this should reduce the loading time to about a quarter of what it used to be. Not bad, eh? We have some testing to do to make sure everything works, but we'll let you know when the new version goes live. Game on!
-- Michiel Hi! Michiel here, and I've been playing with the Unity animation system. Pretty cool! Never done anything like this, so I just wanted to give you a peek at what I'm working on. One of the factories in the mission we're working on has a huge fan on the roof, and of course that's gotta get blown off when you destroy the factory. I've done most similar effects using the physics engine, but in this case I needed more control than that would give me. Unity has a very nice system for creating animations. You just set up a few key frames: after one second, I need this thing to be here, and oriented like this. Then, a little later, it should be here. The animation then interpolates between your key frames, making everything nice and smooth. You can even "animate" other things: the pitch and volume of an audio source, for example. That's all in the curves you see above. Finally, you can trigger events at certain times. For example, when the fan detaches, it triggers a particle effect and a nice KA-THUNK sound. Well, enough talk - let's have a sneak peek! Michiel told you about the development of our Skirmish AI here and here. In this post, I'll take a look from a higher vantage point. Climbing an actual space elevator would help me do that, but with a distinct lack of such devices on this little planet of ours, I'll have take that look in a non-literal way. In "The Science of Discworld", Terry Pratchett, Ian Stewart and Jack Cohen used the space elevator* as an analogy for any process or technology that lifts a system to a new level of operation. So in that vein, the printing press, the internet and RNA are all space elevators. Being big Pratchett fans, this added use for the term space elevator stuck with Michiel and me. So why is our Skirmish AI a space elevator in that sense? It started out as a nice first experiment in building an AI that could play Powargrid at a basic level. But since working on an AI is fun**, we happily kept adding bits of logic to it. And at one point the Skirmish AI became a computer opponent that was no longer trivial to beat, which was cool. It was at this point that we developed what you could call a Powargrid sub-hobby: watching AI versus AI matches on a skirmish map. This is also what inspired us to have an AI battle as the background for the main menu in Powargrid. Before we got to the point where the Skirmish AI became good, we'd been working on the first three missions of the campaign. Each of those missions had a simple AI to take care of the enemy turns, so they were playable in a rudimentary way. But with our new best friend the Skirmish AI, I could suddenly drop an actual opponent into any level I designed. So I could simply think of an interesting layout for the game grid, combine it with a setup for the player and enemy bases that I thought would provide a nice challenge and then immediately test it. And believe you me when I say that's a big step up from thinking of a mission in my head, sketching it out on paper and then asking Michiel to painstakingly code it into reality before testing whether the mission is actually any fun. This was certainly a new level of operation for mission design, so we feel that our Skirmish AI qualifies as a space elevator in the Pratchett-Stewart-Cohen sense of the word :). Some of those missions are already in our playable alpha and others are lined up for use in later missions. And we have more mission concepts we think are fun to play, but we had to pick and choose in order to keep the campaign at an interesting length (and to be able to ever to finish this game ;). For most missions, we do write a subclass for the AI, to handle specific aspects like Swap attacking the dam in 'Dam It' and Ford attacking the temple wall in 'Welcome to the Jungle'. An interesting case came up in 'Piece of Paradise', where you have two opponents in one team. Since team members can't damage each other's buildings, there is a possibility that one AI blocks the other. This takes the fun out of the mission, so we had to prevent that from happening. The best approach from a level design standpoint turned out to be simply have each AI leave a path for the other AI. I defined a this path but then found out that I couldn't feed a set of forbidden tiles into our pathfinder. So I called in Michiel's help and we ended up changing the Skirmish AI to accommodate these forbidden tiles. We usually shy away from changing the Skirmish AI, but it was clearly the best way to implement this. The change is also general enough that we may use it in more missions. The reason we shy away from changing things in the Skirmish AI is that it may affect other missions. We're quite happy with the difficulty level of the campaign, and we wouldn't want to throw it off balance by further optimizations to the AI. By now, we only change the Skirmish AI if we're sure it won't get any meaner or if it behaves stupid or silly in a non-human way. We'll probably have to branch the Skirmish AI in the future, since there are certainly things we'd still like to improve. A really nice improvement would be to teach the AI to be smarter about claiming territory by making use of bottlenecks in the play field, but that's going to be a tough nut to crack. The last thing I'd like to say about this subject (for now) is that we plan to create an interface through which you can build your own Powargrid AI. We think it's huge fun to do so. It's not too hard to build an AI that plays the game in a (semi) sensible manner and seeing it get better with every tweak keeps you hungry for more improvements. The only risk I see is that if others do build AIs for Powargrid, the time Michiel and I spend just watching AI battles will see a significant increase. Which would eat into Powargrid development time. But I think that's a risk we're willing to take ;). Well, that was that. We hope you enjoyed our little series on AI building. I think we'll have more to say on this subject in the future. In the meantime, be sure to let us know if you'd like to know more!
- Willem - * A space elevator is a really interesting concept that would make bringing stuff up into space orders of magnitude cheaper than it is now (in energy and likely also in money). ** We once participated in an AI building contest for Open TTD, where we built a very mean AI together with our good friends Otto and Marnix. Welcome back to our series (part 1 here) on building the Powargrid AI! Michiel here again, and this time I'll look at the general tactics the AI uses. Our generic AI quickly became known as the "skirmish" AI, since it should be able to play a nice game of Powargrid on the skirmish maps. No mess, no fuss - just you against them, in a no-holds-barred fight to the last power plant. We also created several specialized AIs for the various campaign missions, and Willem will tell you more about those in the next part of the series. I already mentioned the basic steps the AI goes through:
This core logic has stayed mostly unchanged, but we've tweaked the internals of each step over a period of several months, slowly making it smarter and meaner. At first, we had to give the AI a large head start (starting it off with more power plants than the player) but as it improved, we were having a hard time keeping up! Now,the AI will give you a good fight even if evenly matched. Three power plants against your two is hard, and we've not been able to beat the current version if you give it four power plants. There's a setting to give it five, if you're feeling particularly brave ;-) After going through its main loop, there's some housekeeping to be done: maybe it should build another power plant, or build some substations to store any leftover power. Decision making What to attack? Where to build a new power plant? Which weapons to charge? Those are all decisions the AI has to make. We've decided on a simple approach for all of these: make a list of things, then assign them a score based on how good they are. For example, if we're figuring out which weapons to charge:
These numbers were determined by Then, just sort everything by their total score, and work your way down the list, charging each weapon which has more than the minimum score (or we run out of power). One of the moves in the game, overcharging your power plants, had us pondering for a while. Overcharge, in case you haven't played the game yet (why not? go ahead, I'll wait!), boosts a power plant's output, but also damages it each turn, and you can't turn it off, so it's a a choice not to make lightly. The AI will use it in two cases: defensively, if it's about to lose a power plant, it'll overcharge it to get some extra juice out of it. Offensively, it will overcharge a power plant only if it has more power plants than the opponent, and it could reach a strategically valuable tile but doesn't quite have enough power to get there. Again, simple rules, but the AI still manages to surprise us with a sudden charge every now and then. Randomness A* is great, but it is a deterministic algorithm: if you give it the same inputs, it'll give you the same results every time. In a game, this is generally not what you want. Especially in a grid based game, it's very obvious: even though "up, up, right" is equivalent to "right, up, up" and "up, right, up" in terms of getting where you want to go, the AI would have a fixed preference for one option. To remedy that, we add a small random amount to every cost we calculate for A*. Not enough to make it choose a suboptimal path, but just random enough so that it'll make slightly different choices every time. And the effect is huge: if you look at the red and blue AIs fighting each other behind the main menu (tip: press F10 to hide the menu for a better look at the action), you'll notice that no two games are the same. In fact, we've been unable to predict which one will win, even though they are both running the same algorithms in every game they play! All because of a tiny, tiny bit of randomness. The difference between the AI and a human is still quite visible, though. The AI is very good at math but lacks the high level overview a human player has. It doesn't see "obvious" moves where a single power line would block off and defend a large section of the board, for example. It also doesn't realize that if it had built a tower one tile away, it would be able to hit two targets at once. On the other hand, I regularly have it happen to me that I think it's left me an opening, I rush forward, and it turns out I don't have enough power to actually get there. Which it already knew, of course. Campaign missions So after a while, we had built a pretty decent, generic AI - put it on the field and it'll come for you! However, the campaign missions need a bit more flavour than just having differently coloured pieces rushing at you headlong, so each campaign mission is additionally scripted to make your opponents more interesting. As you play the campaign, you may notice the first couple of moves are always the same. That's the first couple of turns of the mission script being run. You can't really call that AI - it's just us deciding what we want it to do, specifically. In addition, the AI needs to know about mission objectives. For example, in the "Dam It!" mission, it'll only attack the dam, not your power plants. In "Welcome to the Jungle", the red blobbies are trying to blast their way into some ancient ruins, while trying to keep you out of their valley, and you'll have to learn some new tricks to make it there in time! However, in later missions, once the game's afoot, we can generally just check for some specific conditions, and then just turn it over to the generic AI. This also allows us to quickly test out missions. After designing the initial layout of the grid, we can just throw in one or more AIs and quickly get a feel for how it'll play. Willem would like to tell you more about that process, so I'll leave that to him, in a future blog post!
Hi there! Michiel here, with the first post in a short series on the Powargrid AI (Artificial Intelligence) - the algorithms that control the computer players. The AI is what you'll be playing against in the single player campaign and the skirmish mode. In this first post, I'll give you a quick overview, and then wander off (if you'll pardon the pun) into pathfinding. So what's the goal of an AI? At first glance, it seems simple enough - it should play Powargrid, and try to win! However, an AI that's mercilessly good is actually not what you want in a game. The ultimate goal is to make sure the player has fun playing against it! Our plan was to throw a simple "placeholder" AI together, then figure out how to do it properly. What actually happened was that we just kept improving that initial prototype. We'd sit back with a beer and figure out what rules, tactics, patterns and heuristics we'd been using when playing ourselves, then try to teach them to the computer. The basic behaviours are pretty simple:
Pathfinding Pathfinding is figuring out how to get from A to B. In Powargrid, A is usually wherever your buildings are, and B is the spots where you can shoot at those juicy, juicy powerplants of your opponent. People are great at pathfinding - we can take one look at a game board and see exactly where to go thanks to our massively parallel visual system. Unfortunately, a computer is basically looking at the board through a tube that's one tile across. Suddenly, just figuring out where to go is hard - try it! The granddaddy of pathfinding is an algorithm called A* (A-star). It's one of my favourite algorithms! I'll try (and fail) to be brief, since there's loads of good reading already available online. Amit Patel of Red Blob Games has an awesome series on pathfinding in general and A* in particular. It's a must read for anyone in game dev, and there's much more great stuff there! I'll try to sum up A* in a few sentences, though, and how it applies to Powargrid. A* works on any grid - it just needs to be told which points are connected to which others. In Powargrid, that's just the tiles of the playfield and their neighbours. To find a path, you give it a list of tiles you want to go, and which tiles to start from. If we're trying to attack another player, we can start from any tile we already built on, and we're looking to reach any tile from which we can shoot at their power plants. Then, for every tile it currently knows, A* looks at its neighbours and estimates how far each might be from the goal. In our case, that estimate is easy! It's just how many tiles the target is away, horizontally and vertically (this is called the Manhattan distance because it's like measuring distance in city blocks). It then continues finding a path by looking at the best estimates first, which makes it much faster than just looking at everything. And it still guarantees you it'll find the shortest possible path! However, you can't always take the shortest path. In Powargrid, enemy pieces will block the way, and the grid is not always a neat rectangle. To find the best path, you tell A* the cost of traveling from one tile to another. This is where the wonderful versatility of the algorithm comes from: you can make up any cost you like, and define what the "best path" actually means. In Powargrid, we tell A* your own tiles are cheap - you already own them! Empty tiles are a little more expensive, since you'll have to spend power to actually build something on them. Tiles occupied by an opponent are much more expensive, because you'll have to build and charge weapons to blast through them. The costs are 1 point for your own tiles, 3 for empty tiles, and 20 for enemy tiles. That means the AI will make a 7 tile detour if it can avoid going through one of your buildings. These simple rules will make the AI build around your buildings, unless the path gets too long. Then it'll just plop down a tower and shoot you inna face. Defending also uses pathfinding, but the other way around: the AI knows how much power you'll have next turn, and figures out where you'll be able to go. If that includes places where it would like you not to be, it'll claim those tiles itself. Next time That "short detour into pathfinding" I had in mind actually turned out rather longer than I expected. I'll end part 1 here, and continue the story in the next part. See you soon!
I'm certainly not a programmer, but I do understand enough about algorithms, math and logic to write some code. I always thought that in order to help build the actual software part of Powargrid, I'd have to learn much more about how programming works. And that was holding me back. Then, I think about two years ago, Michiel sent me a link to this blog post by James Hague: 'Write Code Like You Just Learned How to Program' The gist of that post is that there is a big difference between writing a program to achieve a specific end result and writing the architecturally best piece of software you can. James' main advice is that it's often better to focus on designing the user experience instead of on writing the program. And while the post was written for software engineers who know three orders of magnitude more about programming than I do, it was still very helpful by making me focus on making stuff work. The central example James uses is from a class in BASIC which he took, where one of his fellow students made an amazing visual demo with, amongst other things, a skull that dripped blood from its eye. So I started to call this way of working 'bleeding skull programming'. And it has been a great boon to me. I just build whatever works. If I know how to improve my code with loops, lists and larceny (a.k.a. 'borrowing' neat parts of Michiel's code), I certainly will. But if not, I don't fret about it and I just keep typing in ugly and unwieldy stuff untill it works. Even, and this is the important part, if I know Michiel has once shown me a better way to get the job done (which I have forgotten the specifics of). Working this way, I can build missions and test them without having to consult Michiel too often in the process. Since we often work at different times and in different places, this allows me to make much better progress. Once a mission works the way I want it to, I review the code with Michiel. He then usually rewrites or condenses parts of it, so the code ends up clean, neat and efficient. And as a bonus, I get to learn more about programming and about the things I can improve on. Although I must admit that a lot the more complicated stuff still goes over my head. At the current rate, I figure I'll be a full-fledged developer about six or seven games from now :). An example: In one of the missions, we have a couple of factories; special buildings unique to that mission. In the mission script, I wanted to test whether a certain piece was a factory, but I had no idea how to refer to a factory and couldn't find any similar references in the missions we'd already made. The only way I could figure out to actually perform the test, was by checking if the piece was not anything else. So my if-statement looked like this: After showing this to Michiel, it turned out that there indeed was no way (yet) to directly check if a piece is a factory. Instead, there was a simpler solution. Factories are compound pieces and none of the other pieces in the scene are. On top of that, there was no need to check who the owner of the piece in question is. So this is the replacement code: In the same mission, I ended up checking a lot of different things for each of the five factories. All these checks were the same, but I saw no way to loop over them. So I actually wrote all of them out in full (or rather copy-pasted them and then used Sublime Text's multi-line edit feature to save me a lot of typing). Michiel did create loops, combined with sets of attributes for factories in general, and shaved over 100 lines off the code, even while adding functionality. To conclude, don't be shy to do some bleeding skull programming, whether you're a seasoned programmer or a beginner who knows that a seasoned programmer will look at your code. If said seasoned developer ever tsks at your code (which, I must emphasize here, Michiel never ever does), just say 'bleeding skull' to make him or her see reason :).
-Willem- |
AuthorWe're Michiel and Willem. Hi! Archives
June 2017
Categories |