I had made other games before, but for a while I had wondered how to make a game where you could control different units separately or together. The issue was that I had always had the player character react whenever a keyboard or mouse input was detected, so if I had made multiple player units exist at the same time, they would all move and attack whenever you gave an order. That solution was simple, although it took me many months or maybe even years to think of: give each unit a variable: “selected”. When you click on a unit, selected=1, and when you click on another unit, selected=0. Then have the keyboard and mouse inputs only work if for the unit taking the orders, selected=1.
This is really all you need to know to make an RTS, as long as you know basic programming. The ability to select and deselect units in different ways, and their reactions when they are or are not selected gives you lots of options to build an RTS around.
Some tips to avoid issues when making a more advanced RTS:
- Centralisation– centralisation makes your code simpler to edit without having to make the same changes to multiple instances. It can also make it more efficient.
- control– some behaviour needs to be embedded into each individual unit, especially when they’re acting on their own such as for AI. Anything else, such as general army orders and player input should be handled by a single object that goes through each relevant unit and tells them what to do. You also don’t want them to act differently given the same order. This also allows you to have GUI buttons you can click on without clicking on units that would separately detect they’ve been clicked on, which is how SBMS currently is.
- input– similar to control, input should be handled by a single object, instead of having every unit and button do their own checks to see if they have been pressed. When you have 200 units in a match and more buttons, that’s 200+ unnecessary lines of code running 60 times per second, making your game run slowly.
- GUI– try to have a single object draw the UI and anything else in the game that needs to be standardised.
- databases– store relevant and related information together so you don’t forget where some of the information is, or lose access to that information later on
- functions– for anything that has to be done by many different things, such as checking if an enemy is visible, avoid directly checking the relevant variable. Instead, make a function that checks it and returns an appropriate response. That way, if you ever need to make enemy visibility more complicated, you don’t need to go to every object and script that checks it and change their code; instead you can change the one function, and all relevant things will perform.
- Don’t make too many functions and forget their names and then make new ones because you thought you hadn’t made one yet.
- Don’t centralise anything that would work better as an individual object. Too much centralisation can get things lost in very long lists or tons of scripts. Know when to add another object to handle more specialised things. Having just one or two other objects can help reduce processing power and memory usage, by avoiding having too many scripts or variables in a object when you only need the object for some of the things it contains.
- If you have a piece of code that’s being used a lot, be careful when replacing it with a function. The scope of a function should be as large as possible without doing anything extra that will sometimes be useful and sometimes won’t. Don’t copy extra bits and then realise that sometimes you want the extra bits of code there, and sometimes you don’t. If this happens, your function is trying to do too much, and you should make it smaller and take that extra code and put it outside the function, only when scripts need it.
- Planning– planning is essential for any game. Think of all the systems you will need and how they will interact before you build things that need to be modified later.
- multiplayer– sometimes multiplayer is best achieved when building the game to allow for it.
- input and networking– getting input directly from the keyboard might not work depending on how your systems do multiplayer, so input data will need to be sent to a host computer as a variable, which is then processed as input for a specific player’s units.
- unit behaviour– if you build a game where the enemies are hard coded to be computer controlled, it might be hard to then allow a player to use them. This is also true if you want to give a player control of enemy units in a single player game.
- efficiency
- Always try to minimise what individual units do. There are a lot of them, so even if you don’t want a lot of units on screen, do whatever you can to make sure they run as few lines of code, which are as minimally intensive as possible. Players with slower computers than yours will probably notice, and it will give you leeway if you ever want to add some intensive features later.
- naming– avoid duplicate names, not just for two similar units or abilities, but also between a variable used for some ability, like “power”, and the name of an ability, unit, script, or image that might also use the same name.
- Resource types- a prefix is useful for different resources, so spr_power could be the sprite you use to draw power, while obj_power is a player unit (object) in your game whose name is “Power”.
- Keep the prefixes short, so “bg_mission_1” is better than “bkgd_mission_1” or “background_mission_1”
- Don’t name your missions “mission 1”, “mission 2” etc. If you ever decide you want to add a mission in between others, your numbering will be completely off, which means you won’t just have to rename all the maps to one number higher, you’ll have to rename any variables associated with those maps. Use descriptive names instead, like “New York Alliance Naval Base”. Probably something shorter than that, but unique and memorable.
- Be careful with arrays. Sometimes it’s a good idea to store all relevant information in a single array, like having the index of the array refer to a unit number or mission number. Other times the number of the row or column is not clearly assigned to anything, in which case it may be better to make a new array.
- RTS Design
- Strategy and Tactics Depth– Strategy is not the same as tactics. An RTS, as we see it now, is not really about strategy as much as it is about knowing a few optimal strategies and being fast and accurate in your keyboard and mouse skills in order to pull them off. This is exactly what I wanted to get rid of, because it involves marginally more strategy than an action game might. StarCraft is not a strategically or tactically deep game, but chess is, and to a lesser extent something like Total War, where it’s not just about micromanagement and dodging, but unit formations and compositions (as opposed to the limited unit variety of rushes in StarCraft). Strategy is large scale and largely planning based, tactics are reactive and smaller scale. Mech Commander and Dawn of War 2 and X-Com focus on tactics, while Civilization and Total War and other games focus on grand strategy. Decide where you want to stand, and be very careful when trying to mix them like RTS does, because it might be too much to handle, which is why RTSs are so shallow. Tactics and strategy are about giving the player multiple viable choices and letting them decide which they would like to go for instead of having optimal choices. The more choices they have and can make for themselves, the better.
- Unit and Ability Design– a lot of cool abilities might make sense, but also might not lend themselves to tactical play. For example, deployable airmechs in SBMS can return to their mothership to repair as much as necessary. This reduces tactical play by giving you a unit that never dies if you’re careful with it, making it a tank with no penalties for taking damage. This is contrary to how every other unit is a tactical asset whose healthpoints and death actually have to be taken into account when making decisions. Abilities that require quick reactions are usually not tactical, while planning ahead is. I put some delays on abilities in the sequel so that you could activate abilities ahead of time instead of having to wait until the last second and time them perfectly. I made Valkyrie’s Magshield-B run on energy that lasts much longer than in SBMS, but does not recharge. This means that instead of mechanically having to reactivate them whenever it runs out, you have to plan ahead to make sure the shields are used at the right time, but also have plenty of error allowed because of the long duration.
- Economy– I took out economy altogether in SBMS because I don’t find it interesting at all. I like the idea of slowly expanding and finding and collecting new resources, i.e. the logistics and strategic choices in it, but I don’t like having to manage an economy during battle. Exploration and base building benefit from it, combat only benefits it when defending/attacking resource gathering is fun, but in competitive play it becomes restrictive and is a good way to shut down someone from ever being able to fight you. Instead I allowed people to get to the fun part without having to set up the infrastructure to get there.
- Resources– Unfortunately I made everything cost one resource, which means you have to be very careful about what you spend your money on. It’s easy to waste it and not even know until late in the game you figure out you needed something and bought useless things and basically cannot progress in the game.
Centralisation
SBMS started with player units being made separately from enemy units. All their behaviour was run with different sets of code, some of which I copy-pasted. This has the advantage of not needing to check which type of unit it is. Since then I’ve added an all_units object which is the parent of player_units and enemy_units, which contains certain variables I want both player and enemy units to have, and in the sequel game it contains most behaviour and graphical drawing for the two types of units. This means that I can make uniform changes to all units instead of having to do it twice, once for player_units, and again for enemy_units. The problem is that now it has to run an extra “if statement” to check what type of unit it is, since enemy units sometimes shouldn’t be drawn at all if they’re invisible. Instead of using an “if statement” which would take up processing power to pick which colour the units should be drawn in, I gave player and enemy units a preset colour variable, and the all_units object just draws in that variable colour, which can be changed at any time without having to constantly check what that colour should be.
Input
Player and Enemy units had checks to see if the player had selected them with the mouse. This meant that the more units you had active at one time, the more times the mouse button was being checked. Since collisions take up lot of processing power as they need to check every pixel of the unit’s model, the game could not handle many units at all. For the sequel I got rid of those checks, and now only the UI object checks when the mouse button has been pressed. It does a single collision check and will select the one unit you pressed, instead of every unit in the game asking you at the same time if they were being pressed. This should improve game speed a lot.
GUI
All abilities in SBMS are drawn by each individual unit. There’s a script that handles it, so it’s standardised, but the reason this isn’t handled by the UI object is because ability buttons on the UI need to change colour/sprite and show different numbers depending on what state the unit or ability is in. This requires some specialised “if/then statements” to check for how much energy the unit has, if the ability is active, if the deployed unit is dead, if the cooldown is active, etc. I’m not sure how to have this logic put into a centralised object, so I let individual units check to see if they’re selected, and if they have priority amongst the selected units, one of them draws the ability buttons it has. This could be handled by an object dedicated to that ability, or by a pre-made script dedicated to that ability, but this did not seem to be a good way to approach it. The drawback to this is that I have to modify each unit that has the same ability to make sure their ability button UIs act the same way, and that every player unit in the game is constantly checking to see if they should be drawing their buttons. It’s possible to have the UI draw ability button sprites, hotkeys, and a single variable for units by simply having those variables stored in the unit and the UI pulling them when it needs it. This would drastically reduce processing power used, but I don’t see how I could have the buttons changed depending on other variables, since that would require individual logic for each ability that does not fit into the rigid structure of a standardised UI drawing script.
Databases
I have most mission, unit, ability, and other things’ data stored in a central object called “game”. But, I stored some data that I didn’t think I would always need, like unit model-numbers (like the “F-22” part of the American fighter plane, the “F-22 Raptor”) in the Assembly room’s object. This means that I can only access that information in that room, and it’s unavailable anywhere else in the game. The “game” object is permanent, so everything in the game can get information from it.
The “game” object currently has 49 scripts containing all the database variables of missions, units, etc. I think there are far too many, although I remember where most are in the list, I think the (maybe half?) of these scripts that are unit related could go into another permanent object.
Planning
I have a lot of data stored in single arrays with many rows and columns. Unit names, sizes, sprites, descriptions, costs, etc. are all stored in a single array. Each column (or row, depending on how you visualise the array in your mind) is numbered based on the unique unit number that each unit has assigned to it. Valkyries are unit 1, Wraiths are unit 2, etc., so if I need to draw the Wraith’s blueprints somewhere, I just use the variable game.unit_types[2,4] to get that sprite. The problem here is, you need to remember that blueprints are stored on row 4, names are stored on row 0 (game.unit_types[2,0]), etc. Recently in the sequel I changed the unit abilities array, which stored the ability name and hotkey like this:
unit_abilities[i,0]=’Missile’
unit_abilities[i,1]=’M’
unit_abilities[i,2]=’Magshield B’
unit_abilities[i,3]=’T’
This meant I had to use even numbers to get ability names, and odd numbers to get the hotkeys from the array. The sequel has it like this:
unit_abilities[i,0]=’Missile’
unit_abilities_key[i,0]=’M’
unit_abilities[i,1]=’Magshield B’
unit_abilities_key[i,1]=’T’
This took a lot of testing and searching to change every script and unit that ever tried to access the information in the way it was previously stored.
You must be logged in to post a comment.