29 Game world mechanics.
I often get asked where we got the ideas for games. Its a hard question to answer. Sometimes an idea is there all along almost fully developed. Seiddab Attack was like that. I had a dream of seeing this game at a show of this city at night. The only graphics in the game were the city lights. In the dream I thought "I wish I thought of that", Then I woke up and declared "I did think of that". The dream was probably inspired by Night Flight. The game was also inspired by one of the last scenes in the classic film version of War Of The Worlds. I just put the two ideas together and just had to figure out how to code it.
Paradroid was another game where the concept was pretty much all there at the start. I have Andrew's original note that describes the game he wanted to write. I briefed him to design a game with a cute robot. He just came in with it one day after discussing the game with some friends. The core idea did not change throughout the development, just the implementation details. Note even the droid number concept sketched at the top was there from the start.
|Andrew's original Paradroid design note|
Other games we have written were certainly not developed from a preconceived idea. Sometimes the mechanics came first then we looked for a game idea that would exploit the new engine we had just built. Quazatron was like that. Other games gradually evolved in the design stage. Realms started off as a game similar to Rome in an attempt to utilised the graphics capability that the 16 bit machines gave. We looked at the success US games where static screens were used to give the game a feel of epic scale. The Roman Empire prototype design then morphed into the Battle Of Britain, with the emphasis on the sector controllers maps giving a real time strategy game supported with lots of screens to cover talking with the PM to get more planes, radar stations, and action scenes. I went to Duxford to photograph the control room and bought a load of books to provide information and maps of Airdromes. We almost signed the game with Microprose but would not agree to their contract terms. We talked with Activision who already had a team working on a Battle Of Britain game. Activision suggested a fantasy war game so we redesigned the game and it became Realms.
Each iteration of its evolution actually helped the design and by the time we started Realms we had a firm idea of how the game would pan out. In fact we started several people off on different parts of the game. It is impossible to do that without getting the design in place first.
|Realms battle screen|
I believe that you do not get ideas without input. My personal method was to saturate my mind with a subject I liked by reading books, looking at films etc. That filled my head with elements that could be used in a game. Then it was just a question of choosing what the game play would be. Designing a game is as much choosing what its restrictions are, what is not going to be in the game. I define a game as an abstraction of a subset of reality that sets players goals and rewards them for attaining them. It is easy to fall into the trap of trying to put too much into a game. The beauty of the 8 bit era was that you just did not have the memory or power to put too much into a game so choice was forced upon you. I find it much harder now machines have so much capacity.
Christmas has come and gone also the dreaded winter flu but I have made some progress. I decided it was time I upgraded my Direct X version to get up to date. The main reason was to have better shader routines. The old DX9 shader compiler was way behind my graphics card version and only allowed a handful of lines of code. Most shader routines I needed were too complex too fit. I had a look at DirectX 12. I downloaded and installed it without much trouble but was appalled when I found I could not even get a Hello World program to work on my graphics card. Gone are the attributes philosophy whereby you look at the card and decide what it can do. Instead to initialise DX12 you need a card that has loads more features than my poor old laptop has. I want to write a game that works on a standard laptop not just high end games machines so decided Direct X 12 was not suitable. A pity because it is designed for multicore machines. I have a multithread system already, I have not used it yet to ease debugging. I intend to used a thread to feed the hungry graphics card while other threads run the game. DX12 would have fitted better , I could have probably used more threads for the graphics.
So I decided instead to download the June 2010 SDK which had a better shader compiler version. It is always fiddly changing to a new SDK version. They are never fully backward compatible. Some redundant Lib's were no longer there so all references to them had to go. Also one Lib had changed its name slightly. Also the properties files had to be changed to point at the new SDK. I can never remember how to do the properties stuff. Its years since I set them up and my version of Visual Studio has changed a few times. I just couldn't see how to get properties to cascade to the whole solution. I would expect all the tabs to be present on the solution properties that you wanted as defaults for every project. In the old days there used to be a setting in options but they discarded that. So I ended up doing it the dumb way of amending each project in turn with gritted teeth. I was amazed when it all compiled and actually ran with no issues. Sometimes you find all the routine names have changed and you have to send ages fixing up your code.
Well back to testing. I had just tested the deploy base routine. While doing this it exposed some issues in my collision processing. The collision engine detects collisions then sends them to the game for processing. The order of the two objects colliding depends on where they are rather than what they are making the game collision decisions clumsy. So I decided to a pre-processor that swapped the objects so they were in a set order. Then I had less alternatives to code for when looking for the combination of object types that had possibly collided. Lots of combinations have special exclusions.
For example when a container is docked to a ship it touches it and it is not a collision. The rearrangement clarified the processing and allowed me to get rid of non collisions earlier in the process without having to route them to objects event handlers which often had duplicate coding.
As I have said before often when you have finished a routine you realise how you should have written it.
I decided that I should write a new year task list to focus on the routines I needed to get the AI world up and running. I had coded lots of these but were in different states of working.
Ships to export from home world to origin base to boot materials into the game. (not coded)
Ships to import gold and other valuables to home planet in exchange of money to boot money into game (not coded)
Bases deployed (tested)
Minefields deployed (uncoded)
Ships collect resource , delivering to process plant(untested)
Ships picking up delivering freight (partially tested)
Most of the above has to have HUD supported player code to message player and give HUD display.
All of the above need AI routines to allow computer ships to achieve the mission stages.
I tackled resources first. I decided to carry asteroids rather like the large base parts I had just tested. That required tweaks to the dock mechanics to dock with an asteroid, also tweaks to the base dock mechanics to allow you to dump the asteroid without actually docking, which would be hard landing with a huge asteroid underneath. That all went pretty well.
Home ships were next. I needed initial processed resource to be pumped into the game to fuel the initial expansion, and also to make up gaps if resources were unavailable. I decided to have a locked warp point that only home ships could access. A special base port stocked all the goods which could then be bought by allied factions and picked up as freight. So I needed to keep a list pf what was need it, create some home ships which were stocked up from the list while in the invisible home sector. They travel to the base sector and unload using the normal freight code. This meant a few more tweaks to make sure they only picked up goods destined for the home sector and took them to the special warp point , disappeared for a bit and returned with more wanted goods.
I had some issues with AI ships approaching bases. They have to fly round to the correct runway direction and then line up and land without colliding with the base. In the end I discarded the code I had written for them in favour of the method I use for the player ship. I set up a waypoint that leads the ship round the base if its in the way. Then the next waypoint is projected out in the runway direction. The next is just halfway from this to the runway. The ships end up in the correct direction by travelling from waypoint 2 to 3. Then the just have to home on the landing point taking into account ship and base dock point offsets.
Sentries had to be picked up from a base like freight then taken to a region in space and released. The sentries look for ships and report them to the Ai faction processing. So they form an important part of the Ai. I used the data in the collision system to look for nearby ships. Then a report ship routine was called . I started work on the Ai decision process which depends on the ship type and the relation of the ships factions to the sentries faction. I have a simple alliance system whereby Ai factions can allow trade, resource, military ships depending on alliance level.
Using much of the same code I got deploying minefields and fortresses working. Mines are really a special class of bullets that sit in a place until the detect an enemy ship within range. My bullet class already included homing, exploding on proximity, shrapnel so it was easy to add an extra couple of states for mines. Other bullets have a limited life whereas mines last until they explode. Once I had the player working I had to get the AI to do the same. I had to do some remodelling of the mission processor. All tasks have mission data which details each stage of the task. When a ship has completed a stage it rolls on to the next or completes the mission or for some missions starts again. That is complicated by squads which can do a mission between them.
|Prototype Mission Screen|
I decided rather than have to keep tweaking lots of versions of this same algorithm I could code a generic routine that would be easier to work with. In the past every time I tweak the ships processing these routines needed adjusting. Essentially turning or moving is all the same thing. Inputs were velocity, distance to target, acceleration , control bit for plus, control bit for minus and the address to place the control bits at. I wondered whether to make the routine a macro but decided against it as they are harder to debug or change.
Programming Tips.If you read a book on 3d programming you will find angles treated as radians or degrees. I find both unwieldy for games programming and use a system where 1.0 equals 1 complete revolution of 360 degrees.
In the days of 8 bit when integers were the only choice I used an 8 bit byte to store angles between 0-255. Adding angles was a doddle as single bit numbers automatically wrap around when they overflow. Thus 128+128 = 0. In words a half turn followed by a half turn is the same as no turn.
You have to be careful if you want to subtract angles to find which way to turn as the answer may need to be treated as negative. When 16 bits came we used a standard of 4000 hex equals one revolution. The top bit was used as a signed bit so when adding you had to make sure you cleared it.
Now using a float as an angle you can conveniently represent negative and positive angles and also multiple turns. The fraction part is the angle while the integer part is the number of whole turns, usually zero in games. The downside is that C maths functions return things in radians, I have always hated them even in maths at school. Ok that makes calculating circumferences or volumes easier at the expense of an intuitive feel of how big an angle in radians is. I use my own Sine/Cosine routine that returns angles that I can understand. That makes for easier debugging. I have AngleAdd and AngleDiff routines. AngleAdd adds two angles then gets rid of any overflow or into the integer part to make sure the result to 0.0 to 0.9999. AngleDiff preserves the sign so returns a range of -0.9999 to +0.9999.
I differ from a lot of 3d programmers in that I do not use quaternions. Every 3d object of mine has a 3d matrix and I keep that as the primary representation of a ships position and direction. My columns and rows are the other way round to direct X. If I want a vector for the ships current facing direction I use the X, Y and Z elements of the Z column. If I want the vector of up I use the X, Y and Z elements of the Y column. Similarly the direction right is the X, Y and Z elements of the X column. For the other directions you just negate those vectors. Thus having a 3 matrix around is really handy for all sorts of things like firing bullets or checking whether you are in line with things by using dot products.
You can apply each cycles roll pitch and yaw directly to the matrix for proper combined movement.
If you want to have a roll, pitch or yaw variable to use in your processing you can calculate these from the matrix although you have to take care when pointing up or down were the calculation is different.
For multipart objects I like the game to calculate the position matrices for each part so they are available for game processing. Consider my ships docking. The routine has to know where the dock point is and which way it is facing for both the ship and the thing it is docking to.
I find the most important thing is to use ways that are easiest to understand when things go wrong. Speed of execution is a consideration but not as important as being able to get things to work. I like to watch a matrix or number as I step through whole processing cycles to see how the number changes, whether its behaving and tending towards the result I want. That is much easier if the number means something to you.