Part 33 Ai ai ooh!
Renegade sponsored a Motox rider and requested that we make a game. Sports games endorsed by famous sports persons were very successful at the time. We had our doubts about Motox as it was not a well known sport so wondered whether most games players would know the rider. However there was plenty of scope for a Motox game and several bike games had been very successful and playable in the world of budget games. The subject oozed playability so we set about designing a game for the PC and Amiga. We wanted a 3d design to dramatically show the undulating tracks. We had Offroad racer under our belts so knew how to construct a race game. The initial prototype was on the PC and the programmer attempted to create a voxel based engine. This method was all the rage and had just been used for a helicopter game that had stunning 3d landscapes. It seemed the look we wanted so we tried it out. It was also Graftgold's first use of the C programming language. A demo was created but the landscape looked great in the distance but too blocky close up. Voxel landscapes are plotted as 3d cubes. When the cubes are small the effect is really good, but get close up and the blocks are really obvious. A bike has to ride along the ground so the nearest blocks are really near and it just looked like a Minecraft landscape totally unsuitable for a bike. If we made the blocks smaller it looked good but slowed down too much, even replacing the plot routine with assembler.
|Start line Motox International PlayStation version|
The project was nearly abandoned. I took over the plot routine at this point. I knew that on the pc and the amiga using textured polygons would be too slow. I had the experience of the plot routines in Simulcra which were plain polygons rather than textured. So I needed a plot method that was faster than polygons but looked at least as good. The inspiration came from looking at the Doom engine. That showed that in 256 colour using a special mode on the pc you could plot a textured scene.
I reasoned that a view of a landscape could be thought of as vertical scans up the pc screen. If you took a map and a camera point you could project these lines across the map in a fan. On a flat landscape the line of pixels of each vertical scan were a series of dots along one of these lines. The distance between each dot got bigger exponentially like frets on a guitar as they went into the distance. I realised that I could precalculate the positions of these dots saving loads of maths and speeding up the plot. I divided the terrain into 16*16 texture tiles and applied my logic to a single tile. There were only so many ways of drawing a straight line across a tile. Get that into a table of data and step across it exponentially and I could rapidly do in software what graphics cards had began to do.
The complication was that the landscape was undulating but that meant the positions of the dots slid along the scan line on the landscape map. If I travelled along a sight line across the map tiles I could calculate the height of each edge I crossed. Sometimes the edge would be lower than the last, then you just don't plot that tile. If it was higher I worked out the screen Y position and hence the number of needed sample pixels and used that to index into my exponential cross a tile table.
To save calculating heights over and over I kept all the heights of the previous scan lines edge crossing and just added or subtracted gradients from them of the next scan line. It was revolutionary but I never used the technique again. Graphics cards were rapidly getting better and making any software techniques redundant. My technique was actually better than even the modern graphics cards in that it used an exponential step across each texture tile "polygon" . So it did not distort textures so a line across the diagonal appeared bent. The modern day solution seems to be cut the polygon into sub polygons rather than do it properly. The fantastic thing about exponential tables is that you can use the same one for all scales and angles, you just step through it with bigger or smaller gaps.
|International Motox Playstation|
Renegade was sold to Warner who in turn sold to GT Interactive before the game was published. This was really bad for Graftgold. International Motox went on to be Graftgold's most lucrative game but due to all the publication delays a huge chunk of the royalties only arrived after I had sold the majority share of Graftgold to Perfect Entertainment. The royalties that could have saved the companies autonomy came to me personally as part of agreement with Perfect and I used part of it to give a bonus to those of the Motox team that still worked at Graftgold and was heavily taxed on the remainder.
Ai TestingTesting the Ai continues. I must say this seems to be taking forever. In a typical session I assign missions to 3 of my ships in Ai mode and flick between them to check they are behaving sensibly. Meanwhile all the game ships go about their business and occasionally something will blow the game up so I investigate, try to correct it while the game is running and continue. Most debug sessions end up with Visual C telling me I have changed a structure which I haven't changed for years and I stop recompile and start again. It is slow and tedious but it has to be done.
Improving The warp
Occasionally when I spot something that could be better a add a new routine or something to improve the look and feel. For example I was travelling down a warp tunnel and thought it was a bit pedestrian. I decided to make it a hundred times faster, that feels a bit more like warping. I wanted a warp in and warp out effect and came across a nice effect by accident. If you multiply the Z column in a matrix you scale things Z wise. Do this dynamically and it stretches the graphics into the distance. Do this with the background and it looks like you have warped space. I just had to figure out how to do this on a background that was being plotted with an inverse matrix form a sky cube. It scaled nicely at first but the scaling was not in the travel direction if you pointed the camera another way. It took a few attempts to get the Z stretched at the right time so other transforms were not conflicting with it. I decided to accelerate all ships into the warp so you see the suddenly speed off into the distance. That was more like it. Then came the associated issues. I wanted to see ships start to warp on the map then disappear. They had the habit of staying around too long as they did not change sector until the end of the warp.
Warping also caused problems in my ship display screen. I did not want to show the warp tunnel when you viewed a warping ship or allow the player to take control of a warping ship. So a few subtle states were needed. The ship screen nor shows "communication lost" if you attempt to view a warping ship. A similar message is displayed if a ship is destroyed. I had to add some disabling of functions that were not allowed while warping.
Squads.Time to test squad warping to do their various missions. This caused many issues. There were too many cases and complications. A lone ship, lead ship, follower ship and player ship all had their own control routines. I streamlined this cutting out a load of repetition. Only the follower has its own routine as it is very different. Its only decisions are how to keep position in the formation until it does a mission. I improved the mission life cycle so ships returned where possible after a mission and if they had nothing to do were assigned somewhere to park. I added a routine to check if a ship was damaged and direct it to a station for repair.
Missions.Missions were also problematic. Squads contained ships that could do a mission and those that were along to give support. The game had to sort this out. If the leader of the squad has to do the mission then a follower has to take over the squad which supports and protects the ships doing the mission. I wondered why other games do not seem to have AI squads. It quickly gets very complicated. I had to cater for squads getting a mission, a ship with a mission becoming a squad, and many other permutations of squads missions and player actions.
Collisions.I noticed AI ships were crashing into each other too easily so added a couple of routines. They use the collision data to look ahead so they can plot a course around an object in front of them. Also the course level collision triggers a last minute evasive reaction routine. Both of these have to cater for all the times you want a ship to be flying towards an object, such as docking or targeting.
Now and then I noticed one of the bases had gone missing. Ai ships were colliding occasionally while docking and blowing them up. I realised their shields and repair mechanisms were not working. Bases are like loads of ships stuck together. I realised that I had a design issue. The base object that was trying to run the power system had no shields or power. I needed to collect the various bits and associate them with the base rather than base parts. This was a structural issue. I needed to split off an object that I call the Nexus. This is the control system of a base or ship made up of generators, shields, weapons etc. Base parts had to add or subtract their components as bases changed dynamically during the game.
Control.It is a mixed blessing when you discover a fundamental issue. It solves many things but can have huge knock on effects. I discovered another flaw in the basic ship control. I had muddled up thrust and velocity. It was something that had to be sorted out so that different ships behaved properly. When corrected several things had to be retuned including the horrible control routines that ensured a ship applied the correct thrust to stop at the correct place or angle or maintain the correct speed or course. The AI ships tended to vibrate where they were alternately reversing various controls and over compensated.
Summary.The game is now running for longer periods without stopping. The AI seems to be discovering all the sectors and warp paths between them. Their scanning and mapping out of unknown sectors needs a bit of adjusting, they are not finding all they should. For the first time they are travelling to different star systems. That caused a few issues but now seems to be working. It creates new systems as they are needed so the player can explore here they want. I have not met the Seiddab yet although they have caused a few game crashes.
I once worked with a system of several components that had to be started off in a strict order. If you got it wrong or any component failed the whole thing just stopped working. I replaced this with a system I described as organic. The idea is to make each part as autonomous as possible. even if it needs other parts do its full job it should do something sensible at all times. My new system could be started in any order. You could even deliberately stop any component and replace it and the whole thing would continue working. I call it organic as its a bit like the ways cells work. The do not expect instructions to come in any particular order or time, they just go about there business and respond to instruction when needed.
In games the various objects need to be like this. They can be created or destroyed at any time and may interact with each other at any time. The code has to be resilient, each object needs to be able to go about its business no matter what avoiding timing issues such as each waiting for the other.
Any sequence of activities needs to be able to be interrupted. The object may have to do something more important than its normal activity. It has to do the new activity and at the end of all is ok may have to return to the normal activity. The interrupt is not like a classical interrupt as the control of the object is inverted. That is the control happens in game cycle long chunks. For example the object may be travelling a few metres each cycle periodically checking if it has got where it has to go. The "interrupt" is a diversion from this sequence of steps to another then a restoration of the original inverted control program. To make the control programs organic they have to be made resilient so they can restart and ascertain from the object what should be done next. It may be they cannot carry on where they were and may have to restart at a prior stage which for example gets them in the correct position for an activity.
Objects have to cater for other objects not behaving as expected. They may be destroyed or may have to abandon their current activity. So objects need to constantly check that any other object it is acting with is still valid and must act accordingly. My network component for instance will go into a wait till connected state of it loses a connection and automatically resume when reconnected.
For game objects I favour a system whereby controller routines handle a lot of this processing and orchestrate the various action sequences that do the control of each object. One of things I find necessary in this kind of processing is to eliminate any sequence specific variables that need to maintain their state. Sequences may have their own variables but can initialise them and thus restart whenever necessary. Sequences that need to maintain state must do this by using world state. For example when my ships survey a sector they look at the world sector data to get a survey location which is ticked off. This prevents the same ship and/or other ships surveying the same place over and over.