Part 31 Are we there yet?

Graftgold Memories.

I just read loads of my blogs as I can no longer remember what I have written previously. Apologies if I repeat myself no and then. I have explained how I got my trademark thick synthy sound out of the Spectrum. The next sound routine I worked on was for the C64.  The C64 SID chip was brilliant to work with after the Spectrum's limited beeper.  It had 3 channels of sound that could each be set to a different frequency. You could choose a waveform for each. A triangle waveform was good for smooth sounds such as Gribbly blowing bubbles.  Andrew was amazed that I could choose the Sid settings for the kind of sound I wanted. That came from a lot of experimentation and some knowledge of how sound and music works.    A sawtooth wave gave a thinner rasping sort of sound. I liked the pulse  wave option best as it gave the most interesting sounds. You could specify the width  of the pulse which varied the harmonic content of the sound.  Finally you could choose a white noise random wave which was useful for explosions or percussive effects.

You could also play around with the dynamics of each sound by setting the attack, decay, sustain and release.  A high attack and decay gave the sound a percussive start like a muted guitar string. A low attack would give a sound rather like you were turning up the volume of a radio while a note was playing.   Sustain was the volume a sound settled at until you triggered the release. For synthesizer sounds the sustain level was generally high.  Triggering the release was like taking you finger of the key of a piano. The note would fade at the rate specified by the decay setting. 

That was a lot of settings to play about with for each channel. In addition to make things more interesting you could change most of the settings on the fly while the sound was playing.  So our sound routine used to run each game frame and time the sounds and alter all sorts of things on the fly. One of the easiest to play around with was frequency. For each sound I used to have a start frequency and a change in frequency and a count of how many times to apply the change. There was a setting that said what to do when the count expired. It could just reset the start frequency and repeat, it could reverse the frequency change or just stop changing the frequency. 

I did a similar thing with the pulse width when using the pulse wave. That gave chorus or phasing effects.  You could also vary the sustain volume to give tremelo effects.

We found that when you changed the wavelength dramatically it would wrap round as the number overflowed its maximum value giving all sorts of strange noises. That is what Andrew used to make the Paradoid intro droid conversations.  That's creativity, putting an accidental effect to good use.

I converted this routine for use on the AY chip for the Amstrad.  It had three channels but just did not have the wave sounds of the SID so just did not have the same sort of quality. Another  thing
 was like some other chips (Sega Megadrive if I remember correctly) the frequency setting was replaced with a wavelength setting.  Same thing you might say but large wavelength have low sounds, small wavelengths have high sounds. The issue was that high musical notes easily heard by the ear were represented by small numbers. Adding 1 to the number gave you a note that you could hear was very different. That made it impossible to do a fast high frequency sweep and difficult to accurately represent each musical note in the high octaves.  To make a not an octave higher you have to divide by 2. Sooner or later you end up with an odd number that you then cannot accurately halve.
All the precision in the data range was where you did not need it, in the low notes.

When we started doing sampled sounds for 16 bit machines we used similar ASDR processing and frequency (sample rate) changes. This allowed much more variation in a sound rather than just replaying a sample as it was recorded.

Deepest Blue Progress.

I always keep a diary when I am programming so I can record what I have done and the reasoning behind it. I remember when Dominic handed me Simulcra to complete one of the crucial documents he game me was his game diary. It charted out the evolution of his ideas of what the game was and how each mechanism worked. It was invaluable for getting inside his design and finishing the game off.   Nowadays without my diary I would lose all direction I regularly flick through to remember what I was doing and it essential for the blog.

It was a back to the drawing board moment
I had mini crisis while landing at a space station and realising the collision process not only did not work but could never work as coded.  I was relying on objects being generally round  in shape but by ships were now so long and thin that it did not work. The routine would ignore the fact that I had plunged the node or tail of my ship into the deck, or irritatingly thing I had collided when moving past another ship at certain angles. It was a back to the drawing board moment.

It took 8 pages of sketches in my diary
The thing about collision is the processing is simple for spheres and gets progressively more complex for eccentric shapes. Complex programming take game time, I knew I needed to make this efficient but it had to work properly.  I used a cascading technique to progressively get rid of all easy cases when it was impossible to collide. Step 1 was to use a bounding sphere, then  an axis aligned bounding box.  I intersected two objects bounding boxes to give a new box which was the only possible space a collision could occur in. I then checked each part of each object to see if it went into the collision box. It took 8 pages of sketches in my diary and a of week of web research for inspiration . I could not find any technique on the web that would be fast enough just lots of math like the way to find how far a point is from a line , a plane or a box etc. I essentially had a ship made up of loads of parts each vaguely cylindrical. I found a how far is a point from a cylinder  method which was the closest I got to what I needed.  I coded up my idea and with a few rewrites it works much better. It is a compromise as all games routines have to be, but seems to be accurate enough with the ships I have at the moment.
Testing point of collision by freezing laser as it hits

The big thing left to do was the AI mechanisms that controlled each faction. I had basic coding in place that made missions, bases, built ships etc but no control that of when, where and how many.
As the game world was effectively a market economy paying for ships to defend it I needed data the AI could use to make decisions. I also needed to see what the factions were doing to tune up and debug the process. I decided to record market data so I could see the performance of the factions rather like looking at stock market graphs.  It is the price of resources that drive many of the AI decisions. If a resource price is high then it makes missions and builds plants to acquire and process resource.  Goods can be ordered from Earth but that is expensive and is done as a last resort. 

I could see absolutely nothing
It is easy adding screens with my XML based screen definition system. I then copied the code from a similar static screen to add any coding needed to extract the data. I had to write a couple of new plot routines to plot a graph and a bar chart. Guess what , I could see absolutely nothing when I finished the routines. They were not even being executed.  I few tweaks here and there and gradually they took shape. I added a colour fade to each to make them sexier.

I had to collect the history data for each resource. I added a routine that ran each period and stored a few key figures. Then the tricky bit, to find the places in the game processing to add to the period figures. While doing this I decided having juts 1 game market did not really work for factions at war with each other. I decided to have a market for each top alliance. At some point I may add a way of trading between alliances but it is not really needed. Maybe individual ships can trade with enemies for a big profit , but I wont have processing plants order from the enemy.

All this allowed me to add an item of data for each resource that basically said that how much it was needed. When high the AI  strives to get more by adding more missions or increasing production. When this is done the value lowers. This simple feedback mechanism regulates the building of bases and non military ships. 

The AI has to spend its money wisely. I decided to split its money into a ship budget and a build budget by specifying the proportion of each. This allowed each faction to play slightly differently.

Market history graph of gold price and sales volume

I went old school
For military ship building I needed more information to act on. I had written a sighting system whereby ships and probes report enemy ships. I needed to revise this to end up with data I could make decisions with. I also needed to add ship reports and known ship locations to the sector map display to allow the player to get an overview of a sector. So I decided I would need a list of ships sighted in each sector. I realised this needed to be driven by the sighted ships so when they were sighted in a new sector they could erase a sighting in the previous sector. Also when they died any sightings referring to them needed to be deleted if I wanted to free the memory. So I needed a new object Sighting and methods to add a sighting to a sector, remove a sighting, or move a sighting to a new sector ( no need to create a new one just reuse the old one). I went old school and planned this on paper in pseudocode before committing myself to code. That paid off. I spent hardly anytime writing it and  it worked almost at once.  Writes note to self:  In future design all code before writing it. It is so easy to get lazy and skip the thinking and start writing. In the end that takes a lot more work yet still most programmers make the same mistake.

So onto testing that the whole universe works. I put all the ships back in the game it runs for a while and crashes. At least they are exciting crashes they can come from anywhere, sometimes while I am debugging something completely different.  I find a few things not initialised properly, all the resource prices are zero so nothing gets built. A few revisions and checks to make sure new data is initialised and it all starts working until the next crash.  There is going to be loads of testing to establish that the world is building ok but at least I can let it get on with it and just check the progress.

I try my new map screen with the ships and it works but is not much use. The scale of space is so large that ships near each other just print over the top of each other.  That needs thinking about. Maybe I will not try to put text for each ship, just a small coloured icon to give the player an idea of the danger . I try clicking on a ship and it says the type and faction. I double click on a player ship and it takes me to the ship data screen.

Sector map with literals overprinting each other

Then I think of a show stopper. I run the game in the background even when you are in static screens. That means that if any ship dies and is purged from memory the static screens may be referring to memory that has been released. Another one to put on the to do list. I could keep the memory around till nothing is referring to it or make static screens check whether ships have died, or maybe have a ship died event that the screens subscribe to. I would like a generic system that is easy to add to any other object such as a base that I similarly reference.  Then I sometimes need a special message, there is no point in displaying a screen about a dead object, other than to say it has died and would you like to pay for another one.

 So what is next. I want to revisit mission and squads so that the AI can build squads. At the moment squads are limited to 5 ships and players build them. I want big formations. I can easily increase the number and also could have squads of squads. I need to work out when the AI forms a squad. It maybe an AI squad is strongly related to a mission. The AI can create a mission that  requires multiple ships. I could strengthen the relationship between a squad and a mission so the squad is automatically created for one of these missions so when you join the mission you join the squad. 

I am a whisker away from being invaded by Sieddab fleets. I cannot wait to get onto the combat AI.

Programming Tips

Using floats as 0.0 to 1.0

When I was coding the new graph and bar chart routines I had to cater for different data sizes and screen sizes.  When I designed the data for the graph plot position I decided to use a scale of 0 to 1.
Zero meant the plot would be on the axis, one meant the plot would be at the maximum position allowed for by the size of the window less a margin  that defined the graph size. Thus my graph routine is generic and works for any data. All I have to do is scale the data to a range 0-1 by finding the maximum and minimum values and hence the range.

Using floats in the range of 0.0 to 1.0  is a really useful device, I use it for all sorts of things such as throttle, shield strength. It takes the scale of the variable out of most of the calculations making for much more similar code.  At the last possible moment you just multiply by the scale which can be defined as a constant and easily adjusted.

Rounding to multiples of 10

  I wanted labels along the axis to show the scale of the axis and the starting value. It took me a bit of thing to work out a scale that scaled the data to the correct range and meant that the labels were reasonably rounded numbers. For example of the data ranged from 3.7 to 31.9 I wanted labels of 0 10 20 30 40.  To do this I worked out how many times I had to divide 10 into the range to get it to the range 0-1.0 . To get a rounding such as nearest 10 , 100 or 1000  you just divide the  number by 10,100 or 1000 , floor it then multiply it by the same number you used to divide it by. Exp in the following is 1,10,100,1000 calculated when finding out how many times you have to divide the range by 10.
     Rounded =  floor( X/Exp) * Exp;  


Post a Comment

Popular Posts