Part 34. Trials of a long distance programmer
Hardcorps.
For the last few years Graftgold worked on a project with a working title of M.T.B, standing for Main Battle Tank. It was basically a point of view 3D shooter. The player had a stealth tank that could infiltrate behind enemy lines to undertake a series of missions. When we started the game was built with the display engine from International Motox. We intended to make a PlayStation and PC version. We wanted 3d models in the game so needed a proper 3d engine. I bought a book on 3d programming that included a CD with a 3d engine written in a mixture of C and assembler. This was just what I needed. I used it as a start off point and integrated it with an interface that emulated the PlayStation calls. Then we could code for both machines with minimal changes. It was one of those times where I was under tremendous pressure. I needed the 3d engine and there was only me who could really deliver it in time. I worked on it at home as well as in the office until it was ready. It was quite a moment when we installed it in the game. Then we could develop the game using many PC's rather than just using the two PlayStation development systems we had. The PlayStation systems were so expensive to licence we could only afford two.
They had big ambitions for the game which they renamed Hardcorps and were to provide animated sequences to make the game like a TV live action show reporting on the combat. The tank commanders were to be treated as celebrities that were also rivals, occasionally meeting in combat. This did not change the existing design too much although getting AI players to play like a human would have been tough. Perfect wanted to take over the graphics and the sound development so we laid off our in house musician and all but one of our artists. Perfect found a PC publisher to finance the new game. They installed a network at Graftgold which was sorely needed.
We spent about another two years developing the game. All the basic mechanisms were working nicely. A Chinook style helicopter launched the player tank at the start of each scenario and extracted it at the end. We had a network system for Ai vehicles to drive along. It updated when things like bridges were blown to provide alternate routes. We developed a futuristic HUD display for targeting and navigating. You can see a demo of the game on you tube. https://www.youtube.com/watch?v=BA4ONWSfbtk
We converted the game to Windows with some help from Perfect and started work on versions for a couple of graphics cards. At a meeting I was asked if we could program the customs graphic systems without actually having samples of the cards! We received lots of samples from the sound engineer that fitted well in the game.
The biggest problem we faced was that we had no 3d models to place in the game to finalise the coding. Models need specific code to animate moving parts and make their behaviour realistic. We had a couple of deliveries of models that just did not work in the game. For example one tank was as tall as it was wide and just looked stupid travelling over a bumpy landscape. One really nice looking tank, a contender for the players tank, had an offset gun and a huge counter barrel behind the turret that dipped into the tank body when the gun was elevated. What we really missed was the interplay we usually enjoyed between artist and programmer. Our former artists would be given a brief but would have the freedom to come up with ideas for the programmer to try. The programmer would run these in a test rig and see what looked good and that inspired more ideas. This cycle of invent and try was totally missing with the remote graphic team.
In the end as a stop gap we started using models by a local artist who was trying to get into gaming. I ran into him a few years ago , he works as a plumber and was fixing a leak next door. He used to come along to the office and work for nothing. Soon most of the models in the game were produced by him or our remaining artist and Perfect said we could develop our own models. It was at this stage the plug was pulled and the game was never finished. It was a shame, with these few models in the game it was starting to play really well.
Deepest Blue
Last session I was testing AI, likewise the previous so guess what I have been doing. Perhaps I have made this too complicated. There are always moments in development where you have to decide to stick yo you guns or rip things up. I can be pretty stubborn at times. The only reason I have been able to solve problems in systems when others have failed at is that I keep bashing my head until it cracks.
So head down take it full on test each subsystem then each system. Having said this when I find a problematic piece of code that will not surrender I will assess it and if possible simplify it. Sometimes the act of simplification makes you realise where the issues are.
I was getting a bit depressed towards the end of the year. Progress was slow and the year disappeared so fast. There were a number of major issues I had revisited over the last couple of months and they were still sporadically causing problems. Also issues had crept into parts of the game that were done and dusted. I get so I do not want to touch the code if I am not feeling on top of things as it is so easy to make a mistake that could cost hours of work later on. However changing thongs is the only way it is going to get fixed.
Collisions:
They sometimes happened too much, such as when ships docked and blew the bases up. Also occasionally things would bounce too much. I went back to basics. I put breakpoints to trap all collisions to investigate if they were valid. I found it was the train of containers colliding with the base parts, or sometimes the ship colliding with a different base part than the one it was docking with.
I added special code to deal with these circumstances.
I had already added test graphics showing axis aligned collision boxes around colliding models. I then added a graphic to show the intersection of the axis aligned boxes that was used to see if ship parts collided. I added boxes around the parts of the models that thought they were colliding. It always gets me when you have to debug the debug code, or even write some debug debug code to prove the debug code works. Now I could see what was going on and started teasing out the final issues. One of the tweaks was to use the cross product of the difference in velocity and the axis of collision. This ensures only the part of the movement towards the colliding object is affected.
Collison testing highlighting the parts of objects that are colliding. |
Warping
I thought I had this nailed last session but occasionally the ship managed to escape the warp tunnel then not be able to get back. I had traced this so many times but never managed to catch it misbehaving. I added some test code so I could trap it happening. When I caught it I found the vector pointing down the warp tunnel was momentarily reversed. This had happened when the ship just passed a waypoint and still had a vector the waypoint it had passed rather than the next one ahead. How did I miss this? I decided the routine needed to be a lot clearer. If you cannot understand a routine it has to change. I tidied it up saw where the error was and solved the problem.
Freight
Ships have to take a mission to dock with a base, pick up some freight, navigate to another base, dock and unload. There were so many areas where this could go wrong. I watched AI ships through every stage and gradually eliminated the issues.
Base Building.
A faction orders a base part from the nearest base and a ship takes the mission. It navigates to the base docks with the part and transports it to a base site or existing base. It the docks it into position. I had this all working around July so it was messed up getting other things working. The faction were not ordering the parts, the missions were not set up right and a few other things went wrong on the way. Finally bases are being built and upgraded, Now the scenarios can grow properly.
Missions
Funny things were happening when missions were over or cancelled. I revised the system to end a mission then return to a safe place rather than have that as a mission stage. Then ships could choose another mission if they wanted before returning home, useful if there is a local mission.
The style of debugging is changing which is a good sign. I have been letting the game run for hours then have a look around to see if things are as they should be. Occasionally I find a ship heading to oblivion or looking like a dog chasing its tail Then it is a matter of breaking the game at the correct ship and seeing what it is doing. It is encouraging that its running without crashes for ages
To celebrate solving some key issues I thought I would write a brand new routine to sex things up a bit. My ships are built from many parts. I thought it would be cool when a ship exploded to disintegrate it into all the parts which could then be collected as scrap. I already had an unused scrap object so used that. It was really good working on a new bit of code for a change and I had it all working before going to bed.
Programming Tips
Using an objects matrix.
Most people use an engine of some kind to do the 3d graphics but it pays to understand some of the mechanism so you can manipulate things in a 3d world. I find that most of the time I am using a few key features.
Position Matrix.
I keep the models position/orientation matrix and all the transformed matrices of its child parts handy with the game object. Then if I want a direction of a part such as a gun to fire bullets I can easily access it.
A matrix has a 3 by 3 section that specifies the direction in space the object is placed. Depending on the 3d engine matrices are organised in rows or columns. I will use row in this discussion as its quicker to type but red "row or column" depending of the standard of your 3d engine. Think of a matrix as 3 vectors. A vector is made up of an X,Y,Z. Think of a vector as an arrow in 3d space, the direction specified as X,Y,Z units. For unscaled models the orientation matrix is made up of three vectors or arrows. Each one of the vectors points along one of the three axis of the model. If the model is unscaled the length of each of these vectors is always 1. In other words using Pythagoras 1 is the sum of the squares of X Y and Z.
Let us start with a model aligned with the world axis.
The vector for the X axis is X=1,Y=0,Z=0 written as (1,0,0).
The vector for the Y axis is X=0,Y=1,Z=0 written as (0,1,0).
The vector for the Z axis is X=0,Y=0,Z=1 written as (0,0,1).
Thus the matrix for axis aligned object is
Note that we cannot tell whether this is row based or line based as it would look the same either way.
When a ship turns in space the null matrix shown above gets scrambled like a Rubix cube with each number being multiples of various sines and cosines. We do not have to worry about that. The X row will always point to the ships forward direction , the Y row will always point to the ships up and the X row will always point to the ships left.
So if I want to know which way the ship is facing I just get the Z row to give me a unit vector in the ships facing direction. So if I want a position say 50 metres ahead I just multiply the vector X,Y,Z by whatever 50 metres is in the game scale. Then I add the X,Y and Z of the ships position.
Ok so if the rows represent the axis that the model is aligned to what do the columns mean? The represent from the ships point of view where the world axis are, which is the converse of the rows.
That can be useful. Suppose we want to know vector to a base from our ship so we can navigate to it.
What we can do is use the ships matrix the wrong way swapping rows and columns. To save time I have a special matrix transform routine I call MatrixUnrotateVector that just reads the matrix in columns rather than rows. That way there is no need to have to swap all the rows and columns round before the transform. So to find the way to go all that is needed is:
Calculate the world vector to the base by subtracting the Ships position from the base position.
Unrotate the vector using the ships matrix.
Then we have steer the ship to that ship relative direction.
If we want to know of the ship is aligned to a particular direction we can again use the Z row from the matrix ant calculate the dot product with the required world vector. That is we multiply each of the components of the Z row with the vectors components.
ZRow.X * Vector.X + ZRow.Y * VectorY + ZRow.Z*VectorZ
Assuming the Vector is normalised, ie a vector of length 1, if the answer is 1 we are directly aligned.
If the answer is 0 we are at right angles. If the answer is -1 we are completely in the wrong direction.
In fact the answer is the sine of the angle between the ships direction and the required direction so we can use that to decide how to turn and specify a tolerance rather than test for exact values..
If the vector is not normalised the answer ranges from 0 to plus or minus the distance.
The individual components of the dot product are sometimes useful. They show the contribution of each component to the dot product. This can be useful in situations where we want to operate on each component. The best example of the is when we are reflecting or colliding. using the components of the dot product of the meeting velocity ensures we only use velocity acting towards the axis of collision.
_______________________
Position Matrix.
I keep the models position/orientation matrix and all the transformed matrices of its child parts handy with the game object. Then if I want a direction of a part such as a gun to fire bullets I can easily access it.
A matrix has a 3 by 3 section that specifies the direction in space the object is placed. Depending on the 3d engine matrices are organised in rows or columns. I will use row in this discussion as its quicker to type but red "row or column" depending of the standard of your 3d engine. Think of a matrix as 3 vectors. A vector is made up of an X,Y,Z. Think of a vector as an arrow in 3d space, the direction specified as X,Y,Z units. For unscaled models the orientation matrix is made up of three vectors or arrows. Each one of the vectors points along one of the three axis of the model. If the model is unscaled the length of each of these vectors is always 1. In other words using Pythagoras 1 is the sum of the squares of X Y and Z.
Let us start with a model aligned with the world axis.
The vector for the X axis is X=1,Y=0,Z=0 written as (1,0,0).
The vector for the Y axis is X=0,Y=1,Z=0 written as (0,1,0).
The vector for the Z axis is X=0,Y=0,Z=1 written as (0,0,1).
Thus the matrix for axis aligned object is
Note that we cannot tell whether this is row based or line based as it would look the same either way.
When a ship turns in space the null matrix shown above gets scrambled like a Rubix cube with each number being multiples of various sines and cosines. We do not have to worry about that. The X row will always point to the ships forward direction , the Y row will always point to the ships up and the X row will always point to the ships left.
So if I want to know which way the ship is facing I just get the Z row to give me a unit vector in the ships facing direction. So if I want a position say 50 metres ahead I just multiply the vector X,Y,Z by whatever 50 metres is in the game scale. Then I add the X,Y and Z of the ships position.
Ok so if the rows represent the axis that the model is aligned to what do the columns mean? The represent from the ships point of view where the world axis are, which is the converse of the rows.
That can be useful. Suppose we want to know vector to a base from our ship so we can navigate to it.
What we can do is use the ships matrix the wrong way swapping rows and columns. To save time I have a special matrix transform routine I call MatrixUnrotateVector that just reads the matrix in columns rather than rows. That way there is no need to have to swap all the rows and columns round before the transform. So to find the way to go all that is needed is:
Calculate the world vector to the base by subtracting the Ships position from the base position.
Unrotate the vector using the ships matrix.
Then we have steer the ship to that ship relative direction.
If we want to know of the ship is aligned to a particular direction we can again use the Z row from the matrix ant calculate the dot product with the required world vector. That is we multiply each of the components of the Z row with the vectors components.
ZRow.X * Vector.X + ZRow.Y * VectorY + ZRow.Z*VectorZ
Assuming the Vector is normalised, ie a vector of length 1, if the answer is 1 we are directly aligned.
If the answer is 0 we are at right angles. If the answer is -1 we are completely in the wrong direction.
In fact the answer is the sine of the angle between the ships direction and the required direction so we can use that to decide how to turn and specify a tolerance rather than test for exact values..
If the vector is not normalised the answer ranges from 0 to plus or minus the distance.
The individual components of the dot product are sometimes useful. They show the contribution of each component to the dot product. This can be useful in situations where we want to operate on each component. The best example of the is when we are reflecting or colliding. using the components of the dot product of the meeting velocity ensures we only use velocity acting towards the axis of collision.
_______________________
Comments
Post a Comment