Sunday, December 16, 2007
XEO3 Lives!
I am pleased to report that it has all been moved over, and while I would like to do a bit of optimisation (plenty of scope for that), I have it all compiling and back in ship shape.
I've actually had it mostly done for a while now, but I could not get it running on the emulator... turns out I had just one or two bits wrong in the SNA file format
- No system variables in the output file... caused interrupts to go haywire
- Wrong page value written out for port $7ffd
- Wrong interrupt mode value....
Once I had sorted these out it worked like a charm and then I just had a couple of crashes to sort out, just little bugs in the assembler... but these are all sorted.
Now I want to add a few items to the assembler to help with packing all the code in (there is a lot of wasted space at the moment), so I am going to add in sections, like I would use with a linker.. but I am going to omit the linker and just build it into the assembler so it will reorder the sections before saving out.
At that point, I'll fix the 128K code (currently broken) and sort out how I will work with the level data (looking at having everything loaded and not have any loading going on, using a single 128K snapshot).
Once I have that in place I will then be in a position to add content and work on the first level!
I think I'll even put the assembler up as a download for anyone else sad enough wants to use... once I have speeded it all up, shamefully it is taking a full 3s to assemble the code (not very big)... but I know that this is mostly going on tokenising the input stream, as that is painfully slow (and written far too quickly), the good news is that this is easily fixed... I just need to find the time to do so... probably over Christmas sometime.
Wednesday, September 12, 2007
RKASM!
Well Mike has been complaining that my posts are very sporadic (he's a fine one to talk!). So I thought I would update you all on progress since SigGraph...
Well while I was away I implemented the sprite movement and bullets (including the collision detection), so I now have a pretty full game system in there now...
But I was getting very annoyed with the assembler that I have been using, mainly because I wanted to have a linker and a lot more power for putting everything together. I decided to take a leaf out of Mikes book and write the tools that I need.
So I've been writing a Z80 assembler for the Spectrum that does exactly what I wanted it to do, it is called RKASM (Russell Kay assembler) which is not very original.
My main goals just now are to get something that can assemble the code that I have already, for that I need
* basic instructions
* ORG, EQU, ALIGN
* SNA output.
* macros
* structs
I have the top 3 done already and I'm working on the last 2.
After that I'm going to add sections and linking semantics into the assembler, so that I can then organise things to my satisfaction within the game.
Tuesday, August 07, 2007
Live from San Diego!
Anyway I have had a chance on the plane over and between some sessions to add a functional section of the path moving code, so I can now have (logically at least, not hooked it up to the render code yet, though it is trivial) sprites moving around the screen, following paths that are specified in the level data.
I have followed Mikes layout as much as possible so that we can share a lot of the path data... It will not always be possible because of the resolution differences but I will where possible.
Anyway I'll post more details in the near future.
Monday, July 16, 2007
Lists
The sprite cache is used to maintain a least recently used list of all the entries so that we can reuse an entry that will have as little impact as possible, the whole point of the sprite cache is to minimise the work done when drawing a sprite.
The main data structure is a doubly linked list that is setup like a queue where we know the top item and the end item. So each time a sprite is rendered it is moved to the top of the queue, then when we need a sprite entry we use the end of the queue.
Now normally a doubly linked list needs to be updated in an operation like moving to the top of the list requires some coniditionals (for updating the head and tail of the list). So in C# (I like to prototype the ideas first and these days C# is the language of choice).
so in this snippet m_cachePrev and m_cacheNext are arrays of ints (pointing at the previous and next entries in the list, First is the first entry in the list and End is the last entry in the list.
public void MoveToTop( int _index )
{
// unhook previous entry
int prev = m_cachePrev[ _index ];
int next = m_cacheNext[_index];
if (next != End)
m_cachePrev[next] = prev;
else
Last = prev;
if (prev != End)
m_cacheNext[prev] = next;
else
First = next;
m_cachePrev[_index] = End;
m_cacheNext[_index] = First;
m_cachePrev[First] = _index;
First = _index;
}
Now on the 6502 because the flags are set on load then Mike can get some benefits and speed ups, so this type of code is fine for Mike, but on the Z80 the flags are not set when an entry is loaded but only after specific operations. So I have been looking for a method of doing this that minimised the branching to make the Z80 code a bit nicer, well my first area of attack was on the First and End entries and the checks that go in there.
So why do we use End as an indicator that this is the start or end of the list? Well just convention really (normally End would be set to NULL) but if End was set to an entry within the array then First and Last could be set to those entries. So I have set End to be the last index of the array and allocate an extra entry (so if we have 128 entries in the cache then the 129th entry becomes the First and Last entries)
First == m_cacheNext[ End ];
Last == m_cachePrev[ End ];
then we can lose the conditionals because the actions would be the same. So the code becomes in C#
// unhook previous entry
int prev = m_cachePrev[_index];
int next = m_cacheNext[_index];
m_cachePrev[next] = prev;
m_cacheNext[prev] = next;
m_cachePrev[_index] = End;
m_cacheNext[_index] = First;
m_cachePrev[First] = _index;
First = _index;
and then in Z80 this becomes
DSO_MoveToTop:
ld h,high sprite_cache_entry_prev
ld b,h
ld l,c
ld e,(hl) ; e = prev
ld (hl),SPRITE_CACHE_END ; m_cachePrev[_index] = SPRITE_CACHE_END
ld h,high sprite_cache_entry_next
ld d,h
ld c,(hl) ; c = next
ld a,e
ld (bc),a ; m_cachePrev[next] = prev
ld a,c
ld (de),a ; m_cacheNext[prev] = next
ld e,SPRITE_CACHE_END
ld a,(de) ; a = First
ld (hl),a ; m_cacheNext[_index] = First
ld c,a
ld a,l
ld (bc),a ; m_cachePrev[First] = _index
ld (de),a ; First = _index
If you spot any optimisations then let me know.
Wednesday, July 11, 2007
Progress!
I can get 10 masked sprites on screen and still have time to render bullets and even some map sprites (these are cheaper to do as they do not need to be masked, but they do need to be ORed), so I feel fairly comfortable with where I am and I could do a fairly faithful representation of the Plus4 version.
The main difference is that 10 - 16x16 sprites makes the screen fairly busy, as the Plus4 has a wider effective screen than the Spectrum, so I might revise that down for aesthetic (rather than technical reasons).
So the next thing to do, are the path code for the enemies and it will start to feel like a game.
Thursday, July 05, 2007
Now with added sprites!
Currently it is not using masks or anything like that so it all looks very dull, but all the logic is in place now.
I have to tidy up a few things, and I'll post the sprite cache linked list code in the near future as I have managed to make it branchless which improves the readability of it and I believe performance on the Z80 (as the flags are not set on load for Z80, so I have to introduce an instruction to check for zero).
Anyway, progress is being made and I hope to have something with several sprites flying around over the next few days.
And then I'll work on getting the path code working so that we can have formations of enemies.
Wednesday, April 25, 2007
I'm Back!!!!
So major news to date is that I'm going to be a Dad again soon, so we have been preparing for that (and it gives me deadline for sorting out these pesky sprite routines, before changing nappies takes over).
Anyway Mike and I have been talking about the sprite cache system that he has been using on the Plus4 and I think it is the way to go for the Spectrum too, so I've been looking at his routines and seeing what would be the best way to translate them into Z80.
In Pseudo code the DrawSprite routine looks something like this
Draw Sprite
Calc sprite function to use (could be hard coded to Draw Sprite routine name).
Calc address on screen
if sprite_number+rotation not in cache
[Add to cache]
Get lowest entry on the stack
Remove entry from the cache
Add this sprite to the cache
Rotate the sprite and put it into the cache.
[Get address in cache]
Move this cache entry to the top of the list
retrieve sprite data address
Push information onto sprite stack
This uses the sprite stack that I talked about earlier to do the actual rendering, but caches the rotated graphic for each sprite entry, this enables the use of lots of different sprite types without having to store all the possible rotations in memory at once (they are only rotated when required). Usually in the bad old days graphics would all be pre-rotated and restricted at what x coords they could be rendered at (Miner Willy springs to mind, his walk animation required it to move across the screen).
So I'm going to write the general sprite cache routine over the next week or so and then spend a few weeks debugging it properly, so that the game code can just issues a simple DrawSprite call and everything will just work...