This is a document I wrote in 1995, while working on Neversoft’s first game: Skeleton Warriors. It was the first game I’d worked on where I did not use 68K Assembly.
The photo shows me at work around that time. The Saturn dev kit (the “Small Box” and the ICE) is on the right.
The State of the game
The following document briefly describes the state of the code for Skeleton Warriors on the Sega Saturn, and also points at some of the many things still to be done…..
The primary purpose of this document is to effectively bring Dan, Ken and James up to speed with the current code, explaining what each module does, and how they interact. It will also give Mick an overview of the sad and sorry state of his code, hopefully prompting him to pull his socks up.
I will also maybe go into a little detail regarding the incorporation of data (the .GOB and .GOB files) into the program, and what we will be doing in the future.
The Development Hardware
The target machine is the Sega Saturn, which has two SH2 Risc microprocessors and one 68000. Currently we only use the Master SH2, the slave SH2 will be used when we get around to figuring out how. The 68000 is used to drive the sound chip, we should not have to write any code for that as we will be using the Sega supplied sound library.
The program is nearly all written in plain C. We use the GNU SH2 compiler to produce the SH2 assembly output. There are a few SH2 modules, mostly limited to pure data. I have not, as yet written anything at all of significance in SH2.
The development system we use is PsyQ. This is not the Sega standard development system, however, it is considered to be the best by everyone who has tried it. The alternative system is SNASM, by Cross Products, who are owned by Sega. Most of the sample code supplied by Sega is intended to run on the SNASM development system, however it is easily convertible to the PsyQ.
The PsyQ system consists of a SCSI interface board that you install on the PC, and a cartridge that plugs into the Saturn, and a cable to connect the two. Source is compiled on the PC and downloaded to the Saturn where it is run. The code can be debugged from the PC.
The link is controlled by a TSR program (PSYBIOS) that handles communications between the machines. It also allows the Saturn to load files from the PC in much the same way it would load them from CD. We use this feature to load the files for each level.
Mick also has a couple of large, and loud boxes in his room. He also has two PCs. The smaller of the two boxes is a E7000PC, which is an SH2 In Circuit Emulator, useful for telling where you program has crashed if the PsyQ debugger does not stop it. Also useful for watching memory writes, but I’ve not used it very much yet.
The Second of the loud boxes is what is known as a “Small Box” (The original “Large Box” was about the same size as a small fridge). This is essentially a Saturn, with extra interfaces for the E7000 and for the CD emulator. It also has switches on the front to change the country ID, and to switch between PAL and NTSC.
The CD emulator is what the other computer has inside it. A big board which uses the hard drive of the computer to pretend to be a CD drive. You can build a CD image on this and emulate in real time mode to see what the game will look like when it actually gets to CD. This seems to nearly more or less work to some extent, though there are a few problems which I am working with Sega to try and resolve.
Compilation and linking
The overall construction of the final program is controlled by a single makefile: MAKEFILE.MAK. This contains the dependencies and targets for the entire project, including the compilation of the .GOB and .GOV files.
Individual C source (.C) modules are compiled into SH2 (.OBJ) object modules by the program CCSH. This first calls the GNU C preprocessor CPPSH (in the C:\GNUSH2\BIN) then calling CC1SH on this output to produce SH2 assembly code, then finally calling ASSH (in C:\PSYQ) to assemble this into the final object format.
We do not use C++, as I was told it produced prohibitively large object files. However I have not experimented with this, and those // comment would be useful. Feel free to experiment.
The few SH2 assembly language files we us (with .S extension) as simply assembled directly to .OBJ files using ASMSH (not the same as ASSH, it is a more complex macro assembler). At the moment, they are really only used for the simple incorporation of data, and have no machine specific code in there.
The RAM on the Saturn that you can put code in is split into two one megabyte chunks. One at address $06000000 and the other at $00200000. The chunk at $00200000 is used exclusively to hold the graphics for the main character. The program code actually goes at $06010000 (the first $10000 bytes are used for system space, the stack and suchlike.)
The code is position dependent and is complied to run at that specific address ($06010000) and no other.
The .OBJ files are linked together with the PSYLINK program to produce a file MAIN.CPE which is an executable program with a small header that can be downloaded to the Saturn with the RUN program. PSYLINK uses the file TEST.LNK to specify which .OBJ files to include and where to put them.
The Data
The game is split into a number of levels, many of the levels use some of the same data, but mostly it is different from level to level. Each level has all the data for that level brought together in two huge files .GOV and .GOB. (for the mine it’s MINE.GOV and MINE.GOB). The GOV file contains a short header, then all the data that need to be in video memory. The .GOB file contains all the data that need to be in RAM.
A level consist of some of the following data files.
.SSQ – A sprite sequencer file
.SBM – A bitmap file, used for the bitmap backgrounds
.MAP- Both maps for the character mapped backgrounds.
.TIL – The tilesets and palettes for the character mapped backgrounds.
.PTH – The data for the path and trigger points.
.TEX – Textures for the path.
.SSQ and .SBM files are produced by Mick’s increasingly irrelevant sequencer “SEQ” .
.MAP, .TIL, .PTH and .TEX files are produced by Dan’s increasing amazing map editor “TULE” .
These files are assembled into the appropriate .GOV and .GOB files, using the ASMSH assembler. See files LEVEL.S and LEVEL1.S to see how it is done. Some level specific data is also included in the .GOV file.
The Modules
TEST.S – Nothing really, sets up a few labels.
MAIN.C – The top level of the program. Contains the hardware initialization, the level setup, the level playing code and various other little bits and pieces that should really be put in more appropriate modules. Has a fair amount of garbage in as it is the module easiest to put things in for quick testing. Contains the code for loading from CD or PC fileserver. Contains the Flag for switching the TIMING color bars on and off.
GFXLIB.C – Various routines for accessing the hardware and performing various graphics functions. These were all written pretty much from scratch by Dan and are often very inefficient. If you find yourself using a routine from here a lot, it might be a good idea to take a look at what it is doing and write a faster version incorporated with your code.
However, all the functions work and provide an excellent framework for the initial implementation and testing of things. Pat on the back for Dan, without whom, none of this would be possible.
SMP_PAD.C – Various routines for reading the Saturn joypad, very hardware dependent.
GLOBALS.C – All the global variables and a few general function. The use of global variables is appropriate programming practice. However, it is rather slow to implement global variables in SH2 for various reasons, so I may eventually convert some to these to global structures, if needs be.
Contain the variables describing the state of the MAN and the PATH.
MAN.C – Handles the movement and display of the man (Prince Lightstar, Talyn, Guardian or Grimskull – the character that you control). At present this is mostly logic for moving him around and colliding with the path. Also providing the appropriate animation for each action. A lot of work still remains to be done here.
OB.C – Handles movement and display of the objects in the game, specifically the enemy objects, like Skeleton Warriors and little alien things. This is were the bulk of the gameplay will be programmed in terms of enemy AI and basic movement and triggering. the data structure is still not finalized, specifically the issues of collision and animation are not fully worked out. Lots of work here.
DATA.S – Various tables, at the moment, mainly the animations of the main man characters.
LAYER.C – The scrolling of the parallax backgrounds. Updates the character mapped backgrounds and scroll the bitmaps. Also does the line scroll (the wavy effect) on the fog layer. At the moment the maps for the character mapped layers are stored uncompressed. They need to be compressed in the RLE format I was using on the genesis version. This task may fall to Ken if we get a Saturn dev system before a Sony.
PAL.C – The palette. There are 2048 colors to choose from, any pixel on screen can be any on these colors. I have logically divided the palettes into eight 256 color palettes. PAL.C has code for initialize these, setting them up and some provision for cycling them. They will also need fading and more complex cycling, as well as brightness flashes etc.
BUL.C – Primitive setup for handling bullets (sword blast, hand blast, wrist rockets etc.) as separate objects. Still needs a fair bit for work for more complex use of bullets. Still needs proper collision and animation code.
PAD.C – Simple module to remember the state of the joypad in a more usable format. Remembers if a button has recently been pressed, and if it is pressed now.
START.C – One line to say what the first level is, for ease of changing it with a batch file.
PANEL.C – Some simple routines for putting up a power bar.
PATH.C – Monstrous routines for drawing the path and also doing collision detection with the path.
MATH.C – Simple Sine, Cosine and Rotate a point by an angle.
[Update] Here’s some sample code from MAN.C. Everything is very hard coded, referring to a global “Man” data structure. Lots of hard coded numbers.
/**************************************************************/ /* Trigger jumping if needed, also variable height jump logic */ Man_JumpTrigger() { if ( Man.JumpFudge ) { Man.JumpFudge--; } if ( Man.Mode != M_Crouch || Man_StandingRoom() ) // ok if not crouched, or there is headroom { if (Pad_Jump->Pressed) /* jump button pressed */ { if ((Man.Contact || (Man.Mode == M_Hang) || Man.JumpFudge) && Pad_Jump->Triggered && !Man.Blocking) /* and not already jumping */ { if (Man.Mode == M_Hang && Pad1.Down.Pressed) { Man.Contact=0; Man.Mode=M_Jump; Man.AnimBase = LS_Jumping; /* Change base anim to jumping */ Man_TriggerSeq(LS_Jump); /* start the jumping start anim */ Man.YV.f = 0x10000; /* and have no YV */ Man.Y.i += 4; /* and have no YV */ } else { Pad_Jump->Triggered = 0; if ( !JetPacCheat ) Man.YV.f = -0x00080000; /* Initial jump speed */ else Man.YV.f = -0x00008000; // Initial speed in Jetpac mode Man.Contact = 0; /* not on the ground any more */ Man.JumpTime = 0; /* just started jumping */ Man.AnimBase = LS_Jumping; /* Change base anim to jumping */ Man_TriggerSeq(LS_Jump); /* start the jumping start anim */ Man.XV.f+=Man.FlyVel; if (Man.HangEnd && Man.Mode == M_Hang) // if hanging { // and on the end of a path Man.HangEnd = 0; Man.X.i += 12*Man.Facing; // the move past end of path Man.JumpTime = -3; // bit more fixed v jump time } Man.Mode = M_Jump; /* change mode to jumping */ } } else /* Already jumping */ { if (Man.JumpTime++ < MaxJumpTime) /* Still in initial jump period */ Man.YV.f -= 0x0005000; /* So can maintain jump YV */ } } else /* jump button not pressed */ { Man.JumpTime = MaxJumpTime+1; /* so can't alter YV again until landed */ } } }
The file OB.C grew to a 9000 line monster file, which included all the behavior patterns of the individual objects in the game. Also with a vast amount of hard-coded numbers, like this:
Drop_Arac(S_Ob *pOb) { int t; if (pOb->Jump==1) { pOb->yv.f+=0x7fff; pOb->y.f+=pOb->yv.f; t=Path_GetYZ(pOb->x.i,pOb->y.i,pOb)-15; if ((t>pOb->y.i)&&(ty.i+20)) { pOb->Jump=0; pOb->y.i+=15; Turn_Around(pOb); pOb->SeqFile=Sprites[SpriteMap[34]]; Object_TriggerSeq(Arac_JumpLand,pOb); } } else { if (pOb->Frame==16) pOb->Jump=1; if (pOb->AnimStat==AnimDone) { pOb->t1=0; pOb->Mode=&Pattern_Arac; } } Command_Arac(pOb); }
Nasty stuff. This style of code was appropriate when games were very small, and grew out of the 68K days.