Joypad control

Graftgold Memories

Did you ever dream of writing games and working for a software house like Graftgold? It was really hard getting a job in the industry without a track record.  We had a strategy of taking on promising newcomers. This was very successful and meant we could train recruits to do things our way.  We did take on a few experienced developers such as Dominic Robinson and John Cumming but the majority of staff were new to the industry.  We could do this because we worked with programmer shells that meant the coders did not have to start from the ground up. We could quickly get them coding game code where they see results on the screen, rather than struggling with the mass of code that sits behind the scenes supporting the game code. It was much the same with the artists. We had many in house tools that meant we could use new artists to do things such as building the backgrounds.

There were several reasons why I liked to start developers off from scratch. Experienced developers were highly sought after and hard to come by unless you could attract them with a decent wage. They tended to want to work in the way they were used to so were harder to integrate into a team that worked in a set way. I preferred local staff when I could get them. It was almost impossible to source local experienced staff. I liked local staff because it could be a stressful job at times and living away from home just added to that. I figured local staff would be less likely to be poached by other software houses. I took full advantage of schemes such as  YTS which gave a subsidy towards the wage if the staff member went to a one day a week training.   We also had several undergraduates   and even sixth formers on work practice. We employed at least two of the undergraduates when they had finished their course.  As time went on the style of programming changed. You had to be able to read a lot of documentation and work with other peoples software. I found that the best trainees were those that had done at least some programming on a graduate course.

The standard of artwork also changed dramatically. No longer was the specific skill of drawing in pixels important. The person had to really be an artist capable of drawing anything, creating ideas, using colour. Another breed of artist needed was the technical artist that understood 3d art and could work with complicated packages. The ideal artist could do both, starting off with conceptual art through to modelling and texturing.  We used concept art a great deal to attract publishers. Pictures are far better to convey a game design and gave a publisher an idea of what the game would look like. Graphics sold games, it was the most important thing to sell a game concept.
We also home grew our musicians. I was really lucky in finding a couple of really talented musicians. Again the scope changed  completely from the ability to be able to squeeze sounds out of an 8 bit to digital multi track recording. The musician had to have real musical ability with the technical knowhow of how to work with MIDI, samples and music packages.

I used to do the main interviewing. I liked applicants to show the really loved the work they were applying for. I expected artists to bring a portfolio of work. It did not have to be computer work I wanted to see they loved drawing and were capable of drawing figures and faces. That sorts out the real Mcoy from the wanabees. For games coding I liked to see that the coder had an expertise in a programming language and also a love of games.  Finally I wanted to see the applicant could really fit in Graftgold. 
Keeping staff was difficult in a market where experienced staff were sought after. When publishers started recruiting their own development teams with big budgets it was hard to compete. I tried to pay a competitive wage with bonuses when our games sold well. If we had a period when our games were not published or did not sell well it was demoralising and staff would understandably get fed up. Worse was when we had a period when we could not get enough funding to keep everyone on. It is always hard to tell someone they are redundant, even harder if they are you friends.  I would always phone round and try to set them up with other software houses.

It was my philosophy that to build great games you had to build a great team and by and large that is what we did. Andrew put a great deal of time and effort influencing the coders, drawing up standards and documenting our system code. It was hard getting publishers to understand the way we worked aas a team, although that is how all developers had to change and is now standard practice.  Once a product manager came to see Fire and Ice and said  something like "The way I see it is Andrew Braybrook and his sidekick". This completely underrated the influence of Phillip on the game and he left soon after.

Deepest Blue Progress.

Well Christmas has come and gone and I am still at it. I got a few joypad controllers for Christmas so decided to write the input routines. I was pleasantly surprised at the  interface using Microsoft's XInput routines.  I fixed up the main ship controls very quickly and had the ship flying about. I realised that controlling a 3d ship on a joypad is not as easy as I had expected.
There are six ways you can thrust :  left, right, up, down, back, forwards. Also there are six directions to turn:  Yaw left, yaw right, pitch up, pitch down and   roll left, roll right.  On the pad there are two joysticks  which only cater for four of the six actions.

So a bit a thought was necessary to see the best way of fixing up the controls.  After a bit of research I found that players like to set up their own configurations so decided the best thing was to program a configuration screen. The I could test a few configurations and  save them as presets and leave the player to be able to copy and customise these. While I was at it I decided to do the same with the keyboard controls.

Adding a new form to the game was fairly easy due to my Visual Basic like form system.  I worked out that configuring keys and pad worked best with different strategies.  For a keyboard it is best to start with a list of actions and assign keys to them. As the pad has fewer controls than possible actions it is better to start with a list of controls and assign an action to each.  In addition to the actions I added the ability to configure a dead zone and sensitivity.

Joypad configuration form with test graphics

As there were not enough analogue controls I coded the input routine to allow buttons to be converted to an analogue value. This was the start of a refactoring exercise. I wanted keys and buttons and analogue controls to be interchangeable for the analogue spaceship controls.  I decided the AI ships would work better if they used analogue steering etc rather than using the old on/off key controls.  I quickly changed all the AI routines and that is when the trouble started. Now the AI ships were behaving really badly, it was not as easy as I had thought. It took a few goes to get the formula right to work out how fast a ship can fly to arrive at a certain point, or how fast to turn to end up in the right direction.  If you use the proper formula it irritatingly has a square root in it so for efficiency you have to find a substitute. I got this working except for the thrust controls;  instead of homing in to a value the  gently oscillated to and fro.  I added some damping and finally got ships working again.

An AI miner upside down returning to base with its load

With the new year my VS2017 decided to make it more challenging for me. Instead of showing a nice list of routines in alphabetical order for each module it now shows a blank list. I managed to fix this by fiddling about and rebuilding but it has now happened again this time permanently. Worse still "find all references" sometimes misses references. You think you have  found an unreferenced variable or routine remove it and the compile whinges. 
The actual debugger added a few new nuances too. Sometimes after I have changed code  it lets me insert a breakpoint but refuses to stop at it, even though it said "changes compiled successfully". It runs the old version of the routine. This means when adjusting constants I never know whether the program is using the old or new value unless I stop the program recompile and start the test again.
So back to the AI testing. To speed up testing and support the configuration screens I am going to put in a mini scenario so you can fly around and test the control configuration and practice dogfighting etc.  I can use this to test AI actions rather than play the whole game up to that point. This should speed up development as the dodgy debugger means I have to keep restarting.


Programming Tips.

Metadata.
At one of my commercial jobs a huge part of an application consisted of over 300 individual routines to parse net input into structures. These were very costly to maintain, changing every time a field was changed in any of the interfaces to the front end, This happened all the time when clients wanted changed to the front end forms. This could all be replaced by one general routine using metadata.
I love metadata. It allows me to write general routines that work with any structure  such as print, display, save , load send receive. Metadata fills a gap missing in straight C or C++. You cannot code things such as loop through the fields in a structure and print the name, type and value.  To do this you need to access data about each field in the structure.  I have a structure called meta data that I populate for each C struct that I code.

Each field within a metadata has the following fields:
Name: A unique identifier within the structure for the field.
Address: the address of the field in a sample structure. This allows the relative address of the field to be calculated.
Type: The type of the field in the structure. As well as the standard types I have added special types for embedded strings, addresses of strings, text references (used for a dictionary lookup), and a special type indicating a reference to another structure.
Length: This specifies the number of digits rather like the length in a print statement.
Datatype: This is the display datatype which is a bit more specific than the struct datatype. For example a UDWORD may be a decimal number of a hex number. For references  it specifies the structure type of the reference and  whether the reference is contained within a collection such as an array.
There is also a table with one entry per structure. This allows the program to find the metadata for any structure type making the process entirely generic.  This table contains the following fields:
Name:  A unique name for the structure. This is used to identifier the structure and for debugging I actually include at the start of each instance of the structure. This is useful in identifying memory usage, especially useful if one structure has been overwritten by another causing a memory check error. Each memory block has bytes to identify the start and end and these can be checked regularly. If corrupted I look at the previous memory block and see what structure it is. 
Structure Type: a numeric identifier to the structure.
Memory Type. This specifies whether the structure is in fixed memory (preloaded), allocated memory or part of an allocated array. This is useful if data is parsed in or generic clean up routines are used.
Size: the size of the struct for memory allocation.
Work Fields. There are a few work fields used when parsing in data to record  things such as the next data location.

A simple example of its use is to print a debug message containing the struct data. It is called passing the data instance address and the meta data struct name. It looks up the metadata and loops through the fields formats them according to datatype and prints the result on the screen along with the field names.

So if for example I wanted to save a struct I call the save routine passing the address of the instance and the struct metadata name. The save routine does the following:
Find the metadata.
Address the sample struct so address offsets can be calculated into the data instance. It calculates a relative offset to add to each meta data field sample struct address in turn.
Loop through the metadata table for each field:
 Add the address offset to address the data.
 switch on type to read the data.
 Use the datatype to format the output data.
 If a reference is found to another structure output the reference to a reference list. his lists each unique reference. The index to this list is output rather than the address.
When all fields are done the reference list is processed. Each reference is used to generate  a recursive call to this routine.
This carries on till all references have been resolved so a whole tree of cross referencing structures can be processed.

I use the metadata to bind structures to from fields. I specify a list of controls in XML containing their control type an screen position and a data name. I bind each field to its corresponding structure metadata name creating a binding list. This does not happen automatically so it is more flexible, A form can obtain data from several structures. When I want data moved to the form I just call an update routine passing the data address and the binding list address and it gets on with it. This removes all the tedium of shopping list programming where each field has to be moved individually. For grids I go one step further and assume the XML data names are the same as the meta data names. This can be done as a grid usually only displays a single struct. I just have to make sure all the names match.

code example.
//the structure for the grid data
typedef struct _RemapGrid
{
 UWORD Control;  //which button/pad
 UWORD Action;   //what it does

}REMAP_GRID;
//Meta data for my remap grid
REMAP_GRID ParseRemapGrid; //sample struct
META_DATA ParseMetaRemapGrid[] =
{
 { "", &ParseRemapGrid, PARSE_NULL, 0 , NULL},   //Start address
 { "GCON", &ParseRemapGrid.Control, PARSE_TEXT_INDEX, (FP)GROUP_CONTROL, &DT_TEXT_INDEX },
 { "GACT", &ParseRemapGrid.Action, PARSE_TEXT_INDEX, (FP)GROUP_ACTION, &DT_TEXT_INDEX },
 { "", NULL, PARSE_NULL, 0, NULL }, //tail to mark end
};
The grid definition in XML
<GRID NAME=GMAP X=20 Y=25 Z=0.1 WIDTH=300 HEIGHT=260 GRAPHIC=1 GROUP=99 TEXT=0 ITEM=GRMAP>
<TEXTBOX NAME=GCON HEIGHT=20 WIDTH=150 GROUP=99 TEXT=1 CFLAGS=TXT_LABEL GRAPHIC=0></TEXTBOX>
<TEXTBOX NAME=GACT HEIGHT=20 WIDTH=150 GROUP=99 TEXT=2 CFLAGS=TXT_LABEL GRAPHIC=0></TEXTBOX>
</GRID>
The column names for the grid are specified by GROUP and TEXT. The text dictionary is organised in phrase groups.
So to update the data in my grid I just call this:
GridSetData(m_pGridRemapCurrent, m_pRemapGrid, KEY_MAPPINGS);
The first argument is the pointer to the data, the second the pointer to the grid control and the third is a data count.

Comments

Popular Posts