I wrote this in 1991, when I was writing Amiga and Atari ST games for Ocean Software in Manchester, UK. I think at the time I was working on Parasol Stars. It’s an interesting look at a simpler time in games programming.
An explanation of my 68000 development system - by Mick West ------------------------------------------------------------ The purpose of this document is twofold: 1 - To make the meaning of my code a touch more accesible to those that may come after me. 2 - To remind me of the meaning of my code. Fundamentals ------------ A 68000 development system, at present only encompassing the Atari ST and the Commodore Amiga. My present System is PC based, using the SNASM assembler, though this could change. I try to be modular and to this end I have lots of small INCLUDE files that contain short modules of code to do things like random numbers, heap management, sprite printing, etc. These modules are (hopefully) written to use the minimum of extrenal references and ideally to incorporate the functions of an Abstract Data Type - ie a 'Black Box' of code that can only be accessed by calling a number of specified routines. The ADT module can do whatever function it is required to do in whatever way it wants so long as it fullfills the specifications. A good example is the scroll routines I wrote at the start of the 'Darkman' project. I actually wrote three diffent scroll routines and they all did the same thing, but in different ways, some quicker, some using less memory. All three had the same external interface with the same named routines (SETUP_MAP_BUFFER, MOVE_LEFT_4, etc) to facilitated the screen scrolling. I could simply include one instead of the other and the program would assemble exactly the same. There are a set of fundamental routines that practically all games require, You need to read the keyboard, you need a Vertical Blank Interrupt, you need a double buffered screen, you need some way of reserving areas of memory. These basic routines are provide by the file KERNAL.S. For all my projects, the first part of the source code will INCLUDE \DEV\KERNAL.S. I should point out at this point that all my modules that are not entirely specific to a particular project are kept in a folder called \dev, which should also include this file you are reading now. Let's have a look at the very simplest program you can write using this method: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; SIMPLE.S - A very simple program org $1000 regs pc=$1000,sr=2700 include c:\dev\kernal.s main bra main my_vbl trap #0 rts ; data areas rsset free_memory something rs.b 10000 ; end of SIMPLE.S ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Ignoring the comments and blank lines, the first two lines are to tell SNASM where to assemble the program to and to turn the interrupts off before running. The next line includes the kernal routines, as it is at the start of the program then the first thing that will be executed will be a small routine at the start of KERNAL.S that kills the interrupt vectors, sets up the double buffered screen, installs a keyboard interrupt and sets up a VBL. The kernal needs two external labels: MAIN and MY_VBL. The first, MAIN (memories of C) is just the start of your program. The second, MY_VBL is a routine that is called once per Vertical Blank Interrupt. In the example above, all MAIN does is go into a loop. All MY_VBL does is a TRAP #0 to give SNASM access to the machine. This is not done in the kernal because you might want some control over when you let SNASM get it's claws in, maybe one per game cycle or whatever. The last couple of lines are rather fundamental in that they illustrate how I do my memory organisation. The Kernal allows a fixed amount (configurable, the default is 100K) for your raw code (ie - the assembled code, including all modules). All memory above that is assumed to be free and is reserved using the RS directive (which I use extensivly, so you have to understand it to understand me). The kernal will reserve about 70k itself for the two screens and the stack. Then the lable FREE_MEMORY will be SET to the end of this reserved memory. Any modules that you include may also reserve memory, if so they will set FREE_MEMORY to the end of the area they have just reserved. So in your main program, if you want to reserve areas of memory then you will simply use 'RSSET free_memory' to set the __RS variable correctly and the just use RS to Reserve Space as desired. If you are writing a module that needs memory then in addition to the above you will also need to SET the FREE_MEMORY variable so other modules and your main code recognises what you have done. This may sound complicated (it does to me!) but if you just look back at the example program you can see that in practice it is really very simple. More about the Kernal - since this is common to every program I write, I shall describe it's functions here. Modules should (if I have the time/inclination/energy) have a full specification at the start of the source. So if you want to know what it does, read the comments not the code! Kernal Configuartion variables (compile time) if these are undefined then they will be given a default ST true if target is an ST (true) THE_RPF rasters per frame (1) MAX_CODE max code size (100*1024) RASTER_COUNT if true then will display a raster count (false) Kernal Variables for external usage. TIME.L starts as zero, increments every VBL SCREEN.L address of the virtual screen REAL.L address of the real screen Major kernal routines FLIP_SCREENS flips screen and real (waits for RPF) Major macros PUSHEM save registers (always long) POPEM PUSH.s save a register (sized) POP.s PAUSE jiffies hangs for a certain time CLS screen fills 32000 bytes with 0 This is not everything in the kernal, you should look at the source comments for that. Mick's Code Style Conventions ----------------------------- I am trying to develop a consistent style of writing code, this will cut down on having to write unique routines every time I write a new program. The style should not just be about code, but about data structures and how the code accesses them. Eventually I hope to have macros for the most common code structures as soon as I have them fully formalised. Mick's Maxims (not in order!!) (!) ------------- 1 - Use Meaningful Labels! 2 - Use Local Labels! 3 - Save Registers! 4 - Don't repeat code, put it in a routine or a macro! 5 - Don't use numbers, use equates! 6 - Use lower case! 7 - Use data structures! 8 - Use consistent program structures! 9 - Use Comments! 10- Make Backups! 1 - Use Meaningful Labels! By meaningful I mean use full words, not abbriviations, use underscores to separate words, use the proper tense for the word and the correct plurality. EG: setup_heap flip_screens move_baddies game_over file_not_found 2 - Use local labels! Only use global labels for routines that can be accessed externally. ALL labels within a routine (including any local variables it uses) MUST be local. EG: main_routine clr.w d0 .loop add.w #1,.counter bsr another_routine bne .loop rts .counter dc.w 0 next_routine ... 3 - Save Registers! Unless you formally specify otherwise, all registers not used to return values should be UNCHANGED when the routine returns to caller. If you want to write faster versions then do so, but SPECIFY that they may corrupt registers. 4 - Don't repeat code! Put it in a routine or a macro! 5 - Don't use numbers! Use equates! EG: add.w #4,man_x ; is WRONG add.w #walking_speed,man_x ; is RIGHT 6 - Use lower case! Upper case programs are a useless reminder of the ancient days of computing. They make programs difficult to read, and your comments either have to be in upper case, or you have to mess around with the CAPS LOCK key. It's just a waste of time. 7 - Use data structures! This is a big subject, addressed later. 8 - Use consistent program structures! Like mine for example. 9 - Use Comments! At the very minimum each routine should have one comment, on the line before the label. This should give a brief description of what the routine does and it's input and output requirements. A full specification of a routine should include the following. Exact input/output requirements Register corruption The external references it needs Brief explanation (one line is ok) of the algorithm Possible areas for improvement Known bugs still to be fixed As well as the comments at the start of a routine it is usually a good idea to have comments in the routine itself. For espescially complex routines it is a great help to development to comment EVERY line to provide information on: Flow of control, meaning of decisions, contents of registers and just general elucidation of exactly what you think you are doing. 10 - Make Backups! Not strictly a coding requirement, but still a vital part of development. I use an Archive program and have a batch file to archive everything in the \DEV folder and everything in the folder containing the project that I am currently working on. They are archived to hard disk and then copied to floppy, which is then taken home and copied to my hard disk there. Besides the obvious frustration of having to rewrite code that you have already slaved hours over, there is the fact that there is a lot of money at stake here. Think money, make backup! Right, that's enough maxims for one day (ten was good enough for Moses so it'll do for me) Now, let's expand on the dual topics of Data Structures and Program structures. At university they taught me somthing called 'Data Driven Design', the idea was that you built your program around the structure of what you were doing things too, the data. They also waffled a lot about Abstract Data Types which I described briefly earlier and involve keeping the data structure seperate from the program code. Data driven design is really a very simple concept. Put at it's most basic level all you do is list everything in the program and then write routines to handle each of them. This document is not supposed to be a beginners guide to structured programming, so I'll just describe what I do, and what I think I might do if I get around to it. What came first, the program structure or the data structure? I usually start with a loose outline of the program structure and then write some specific data structures and then the program and the data structures evolve together as they respond to changing needs and new idea. This is not the way it should be, but it has happend like that because of the usual lack of time and energy. Anyway, enough waffling, what is a data structure? Well, it's like a RECORD in pascal or a STRUCT in C, which probably leaves you none the wiser. Essentially it is an array or a list of groups of bytes. It is best illustrated by an example. Say you are writing galaxians or something similar, obviously you need to store the positions of all the aliens. To do this you will just have a list of say 20 bytes for each alien, in those 20 bytes you will store information such as the x and y position, the speed, the sprite number, the energy, what it will score, how long before it next does something and things like that, depending on your game. To get information on a specifc alien you point an address register to the base of the alien and then use offsets to access the various bits fo data in that alien. EG: lea alien,a0 ; address of alien move.w (a0),d0 ; get x move.w 2(a0),d1 ; get y move.w 4(a0),d2 ; get sprite number bsr draw_alien ; draw it In line with my maxim "don't use numbers", I dont use numbers as the offsets. Instead I define my structures using the RS command. This let's you set up a data structure as if you were defining variables using DS, but instead of the labels being absolute addresses they will be offsets relative to the start of the code. For example: rsreset ; new data structure alien_flag rs.w 1 alien_x rs.w 1 alien_y rs.w 1 alien_xv rs.w 1 alien_yv rs.w 1 alien_sprite rs.w 1 alien_energy rs.w 1 alien_len rs.w 0 Having set up a data structure I will define an area of memory at the end of the program (using RS and explained a page or so ago) like: alien_table rs.w max_alien*alien_len+2 The table will be initialised by: fill #alien_table,#max_alien*alien_len,#0 move.w #-2,alien_table+max_alien*alien_len So the extra word at the end of the table will be in effect a dummy entry in the list with a flag value of -2. I usually end my lists with a negative value in the first field. There will be a routine called NEW_ALIEN that will return a0 as the first free baddy in the list. My usual way of going through all the objects in a list is as follows: lea alien_table-alien_len,a0 .next_alien lea alien_len(a0),a0 tst.w alien_flag(a0) beq .next_alien bmi .done ; just an example of the sort of thing I might do with it. move.w alien_x(A0),d0 move.w alien_y(a0),d1 move.w alien_sprite(a0),d2 bsr draw_sprite bra .next_alien .done You can kill a object using clr.w alien_flag(A0) This sort of structure is the fundamental basis of my programs at the moment. ------------------------------------------------------------- I have written this in a brief attempt to slightly formalise what I am doing, mostly to get a few things clear in my own mind. So if you are not me and this does not seem entirely sensible to you then hard luck...