"Go-Go BunnyGun", first screen grabs.

Show us what you're working on, (preferably with screenshots).
zxade
Drutt
Posts: 18
Joined: Tue Feb 19, 2019 12:14 pm

Re: "Go-Go BunnyGun", first screen grabs.

Post by zxade »

Joefish wrote: Tue Sep 10, 2019 12:15 pm but then I could use screen paging, not stack copying, to update the display.
I've not used screen paging on the 128K before, and wonder if I've been missing a trick. How does it work in practice? Does it make it more tricky in terms of structuring the code? Do you lose the use of addresses from $C000 for other purposes? or is there no downside other than making the code 128k only?
User avatar
Ivanzx
Manic Miner
Posts: 736
Joined: Tue Nov 14, 2017 9:51 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Ivanzx »

How close to completion is the game?
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

Ivanzx wrote: Wed Sep 11, 2019 9:11 am How close to completion is the game?
Good question. No idea! Difficult to find time to work on it right now.
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

zxade wrote: Wed Sep 11, 2019 9:01 amI've not used screen paging on the 128K before, and wonder if I've been missing a trick. How does it work in practice? Does it make it more tricky in terms of structuring the code? Do you lose the use of addresses from $C000 for other purposes? or is there no downside other than making the code 128k only?
I've tried coding a scrolling demo with it. And the 50Hurts 50fps scrolling uses it too. It saves you a whole frame or more per game loop if you're using a back-screen and trying to avoid flicker.

The big drawback is you have to page the screen you're drawing on into the top of RAM (or write to the regular screen at $8000 and the back screen at $C000). This means you have slow RAM at the top of memory, so it's not good for storing graphics data or anything you need to access fast. Which means your game code and all the data it needs for the current stage pretty much has to fit in the middle 16K from $A000 to $BFFF.

Which means even if you're not swapping chunks of code around, you're going to need a dynamic data-management system so you can copy stuff like sprite data into the middle 16K - and that actually limits how much you can have going on in any one level, compared to a 48K game. You could at least stick any music and sound and data used during the interrupt/top-border functions in the slow RAM from $5B00 to $7FFF.

It's still sort-of 48K compatible, as on a 48K you could have your back screen at $C000 and just spend more time copying it down to $8000 instead of paging it. But you'll need a full-on strategy of managing or limiting other data to keep your game 48K compatible, and it'll run slower.

As I see it, the only reason for making a 48K game is to see what you can do with the limited system, in which case I'm not interested in 128K enhancements. On the other hand, if you are going to add 128K features, you might as well just forget about 48K compatibility and make a 128K game. Someone asking 'why isn't there any 128K music' is about as relevant to me as someone asking 'why didn't you write it for an iPhone'.

The only other trick you could use is to copy data from a fast RAM bank into a screen RAM bank by using an OUT to swap the banks over between reads/writes. But this is only efficient really if you're caching the copied bytes in lots of registers, e.g. a PUSH/POP copy, and then only really if you're copying a chunk of screen between the same addresses in both banks.
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

Finally done the last background graphic (bottom):

Image

The top two can be dynamically re-coloured and re-used in other levels.

See commentary here on development of the last one:
viewtopic.php?f=9&t=3073
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

Just a note to say this isn't dead yet, I've actually done a teensy bit of work on it lately.

I've integrated some compression functions for the sprites as I was rapidly running out of memory, whilst conversely absolutely sure I want an 'attract mode' where it can re-display the loading screen (or something like it) even on a 48K! and guess what? I'm still running out of memory!

And I don't want to go all Molyneux and boast about something that'll turn out a bit rubbish, but since it already seems to have a somewhat 'flexible' approach to the framerate I see it as a bit of a test-bed for programming ideas. The ability to re-colour just parts of a sprite's attributes on-the-fly is one such trick, the next is its very own scripting language, such that the level data isn't really data; it's actually a program that generates the level as it goes along. Then it can use loops and calls to simplify repeated sections. The enemy paths are a cut-down version of the same script. And it means the enemies can run little scripts when they're shot down and change the way others behave. So enemies can react in different ways; they might wait to pick you off, but shoot one down and they all fire back at once, or kamikaze you. Or they might scarper, so you have to let them get close first if you want to kill them all (which can then trigger a bonus). And some enemies can be shielded against hits from the front or back, or linked together to make bigger sprites.

(Can't believe it was 2018 when I first posted about this! My Pac-Man maze generator might become a multicolour game one day and I'm still mucking about with 3D, which may yet become either a Mercenary-like walk-around object-adventure or a faster maze-like race/chase/combat thing. And my platformer Bertrand Bubblethwaite may happen eventually; I think I'm resigned that I'll finally have to embrace the 128K to make that happen, which means then having to learn all about sound and music on the AY).
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

OK, I wrote my simple BunnyScript bytecode interpreter, then wrote it a bit of script to unpack some sprites, generate five enemies in a loop, with an increasing delay on each appearing, then wait for a bit, then do five more, and it worked!
Now I'm thinking of all the horrible things I can do with programmable levels when I introduce a 'GOTO' function... :twisted:

Nah, the main reason is I want to be able to have subroutines and loops to introduce trails of enemies and scenery assemblies, so I can re-use them loads and save memory. A branching level, depending on what you've shot, is a possibility. Though it might look odd as the backdrop just scrolls continually; it can't go diagonally up or down to indicate a change of direction (like it does in Thunder Cross II). I could have an enemy though that continually re-spawns (by replaying the same segment over and over) until you figure out the proper sequence of killing it... That'd be mean, but plausible.
Ralf
Rick Dangerous
Posts: 2279
Joined: Mon Nov 13, 2017 11:59 am
Location: Poland

Re: "Go-Go BunnyGun", first screen grabs.

Post by Ralf »

Seems like you are trying to do something ambitious.

Just don't be overambitious. It's better to finish a project with less features than not finish it at all.
Good luck with your project!
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

Yes,you're right. It should just be able to generate a regular set of levels for a shoot-em-up with chains of enemies appearing on-cue. There's not a lot of scenery; it's more like chucking big chunks like indestructible asteroids or Flappy-Bird columns at you to weave between. But bosses often need some custom code to give them special behaviour. It'd be neat if I can script that sort of behaviour so it doesn't have to be treated as a special case.

Another trick is the high-score chasing 'Caravan Shooter' style (like Star Soldier R) where the quicker you kill one wave, the quicker you get onto the next, and the higher the score you can achieve, before the level comes to a natural end at a fixed distance / point in time. I could have aspects of that, though I'm thinking of another follow-up game for that (possibly just one pilot, possibly targeting a 16K cartridge, which I've always wanted to try...)

Overall though it's a more interesting way of doing it for me; writing the level as a program rather than using any sort of level editor. Hopefully the use of looping and subroutine functions will achieve the main aim at the moment though - to save memory!
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

I've still got to add enemy shots, the collision detection that kills the player, and so display updated lives and scores. Also sound effects, though that isn't going to be much, as it's all still going to be strictly 48K. The rest is just down to level layout. I even have a few sprite designs left over that could go in if there's a few hundred bytes to spare in the end. There is a bonus-collecting scheme I'd like to add if there's enough memory too; maybe use that as a way of gaining an extra life rather than basing it on the score.

All the backgrounds, scrolling, sprites and animations are done, player movement, power-ups, firing modes, shoot-down collision checking and explosions, and there are bits of music between levels and so-on.
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

Crikey, well over a year since I last made any notes on this game. Just tinkering a bit more with the script that makes the levels happen.
I've written two other games since then! And did some sketches while on holiday for a multicolour shmup.
Slow progress... :mrgreen:
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

Bit bad reading the last page of comments and realising they were March 2021, not 2022!

OK, done some more on the scripting engine. It can do neat things besides just generating the levels from a script; when you kill a baddie it can trigger a bit of script to run too.

First test of this was a bunch of ships that fly across the screen. If you shoot one down, the others turn and race off. If you manage to shoot them all, it should send a bonus your way.

Then there's a line of missiles that appear down the side of the screen. After a pause, one-by-one, they fly across the screen towards you, but are easy to shoot down. But if you shoot one while they're still hovering, before they launch, then they all react and raise a shield that ups their hit points!

I've just programmed a sort of flying carrier with a deck gun that's made up of 5 sprites all moving around together and share the same hit points. When you shoot it enough times to kill it, it triggers a script that uncouples the deck gun sprite from the rest so that explodes, but then gives the rest of the group another 50 HP so the carrier itself survives a bit longer. It then attaches another (timed) script so that while the carrier is still around, it spawns new enemies that pop up through the deck and fly across the screen. This uses the same scriptable functions as the level data does to introduce enemies.

From a programming point of view, I'm doing something I've not tried before. I'm using the IX and IY registers and offsets to describe structured data objects. So my enemy sprite data is defined as a byte for the sprite number, another one for its animation frame, two bytes for X and Y position and two more for direction (DX and DY). So when IX points to an enemy's data, I know, for example, its X position is stored at (IX+2) and Y position at (IX+3). And I have labels for all of these (e.g. OBJ_X = 2, OBJ_DX = 4). So to move my sprite I do:

LD A, (IX+OBJ_X)
ADD A,(IX+OBJ_DX)
LD (IX+OBJ_X),A

LD A, (IX+OBJ_Y)
ADD A,(IX+OBJ_DY)
LD (IX+OBJ_Y),A

That adds the current value of DX and DY to its X and Y values.

And to draw it, I take those X and Y values, then take the sprite number from (IX+0), add the animation frame from (IX+1), and draw that sprite at that position.

Now my byte-code script is able to write a new number into any of the offsets into the sprite data. So with a single instruction in my scripting language, followed by an offset and a data value, I can re-write any of these values for an enemy sprite. So my script can change the appearance of an enemy, the direction it's moving, its colour, how many hits left to destroy it, etc. Or move onto an empty slot and write in the values to create a new enemy.

The scripting engine is a whole load of different little functions that can operate on the sprite data and a couple of stored variables (acting like registers), such as fetch a value from a sprite structure and store it as 'A'; write the value of 'A' to somewhere in a sprite structure; write a fixed value into a sprite structure; go to the beginning of the list of sprites; move on to the next sprite in the list; if a sprite has a certain parameter value branch to another script. These functions all sit at even addresses in 1/2K of memory. The code for each instruction is simply its relative address from the start divided by two. So my interpreter holds a script pointer in DE, a sprite pointer in IX, and does:

1. Fetch a byte code from the script.
2. Double it
3. Add it onto the address of the first function
4. Push a return address onto the stack
5. Jump to the calculated address
(the function itself is responsible for reading any more data from the script)
6. On return, repeat from step 1.

And to end a script, one of the functions called 'STOP' simply does an extra POP so that the RETurn bypasses the scripting engine and goes back to whatever called it.
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

That's the scripting engine that makes the levels happen and handles triggered events in the game. The event scripts are supposed to be really short and only make minor tweaks. The only big script is the one that schedules everything in the level.

There's also a kind of shorthand for describing the path enemies follow. One byte is split into two 4-bit values, sign-extended, and written into the DX and DY of the sprite (i.e. to change its direction), then the next byte is a countdown until the next change of direction. There's also a code that can write a new value into any of the other parameters of that sprite's data structure, or even trigger a bit of script to run, so it can alter the appearance or animation of an enemy as it travels along its path.

After all that, there's still a regular programmed game loop to move the player, scroll and refresh the screen, move and redraw the enemies (according to their current parameters), collision-check any shots, etc. At the end of that it runs the next bit of level script (if a baddie or obstacle needs to be introduced). And any scripts triggered by baddies being shot. Then the main game loop repeats.
worcestersource
Manic Miner
Posts: 512
Joined: Thu Feb 03, 2022 11:05 pm

Re: "Go-Go BunnyGun", first screen grabs.

Post by worcestersource »

This is all sounding incredibly exciting and rather interesting as to how you’ve implemented it!

Steve
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: "Go-Go BunnyGun", first screen grabs.

Post by dfzx »

Joefish wrote: Mon Aug 08, 2022 8:36 pm From a programming point of view, I'm doing something I've not tried before. I'm using the IX and IY registers and offsets to describe structured data objects. So my enemy sprite data is defined as a byte for the sprite number, another one for its animation frame, two bytes for X and Y position and two more for direction (DX and DY).
In C that's:

Code: Select all

struct enemy
{
  uint8_t sprite_num;
  uint8_t animation_frame_num;
  uint8_t x;
  uint8_t y;
  uint8_t direction_x;
  uint8_t direction_y;
};
Joefish wrote: Mon Aug 08, 2022 8:36 pm So when IX points to an enemy's data, I know, for example, its X position is stored at (IX+2) and Y position at (IX+3). And I have labels for all of these (e.g. OBJ_X = 2, OBJ_DX = 4). So to move my sprite I do:

LD A, (IX+OBJ_X)
ADD A,(IX+OBJ_DX)
LD (IX+OBJ_X),A

LD A, (IX+OBJ_Y)
ADD A,(IX+OBJ_DY)
LD (IX+OBJ_Y),A

That adds the current value of DX and DY to its X and Y values.
That matches what the C compiler generates, only the compiler points IX at the end of the structure not the start, so it uses negative offsets:

Code: Select all

;main.c:19: enemy.x += enemy.direction_x;
 	ld	a,(ix-4)
	ld	c,(ix-2)
	add	a, c
	ld	(ix-4),a
As you'd expect, your handcrafted approach is a few T's and a register usage better than the compiler. :)

That's with the SDCC C compiler. Interestingly, the other compiler which z88dk offers, called sccz80, doesn't use index registers. The code it generates is:

Code: Select all

;  enemy.x += enemy.direction_x;
        ld      hl,2    ;const
        add     hl,sp
        push    hl
        ld      e,(hl)
        ld      d,0
        ld      hl,6    ;const
        add     hl,sp
        ld      l,(hl)
        ld      h,0
        add     hl,de
        pop     de
        ld      a,l
        ld      (de),a
That's using HL as the structure reference and works from the start of it, using hardcoded offsets (+2 and +6). It's clearly a lot less efficient.
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

It's C and C++ that made me realise what the IX and IY registers are designed to do.

I also wondered about the offset being signed; if the pointer is to the middle of the object then that's the most efficient as you can then have the largest structures. I wonder if that initial compiler starts with negative offsets so that it can then go on to use positive ones if the structure gets big enough..?

I also wondered about using negative offsets to allow the script to access data from the previous item in a list (since they're consecutive in memory). Although if I'm treating the list as a wrap-around then that's not going to work in every case. Then again, it would cost me nothing to add those definitions so that my scripting language could do it, as long as I'm certain that bit of script is never going to act on the first item in the list.
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: "Go-Go BunnyGun", first screen grabs.

Post by dfzx »

Joefish wrote: Tue Aug 09, 2022 1:55 pm I also wondered about the offset being signed; if the pointer is to the middle of the object then that's the most efficient as you can then have the largest structures. I wonder if that initial compiler starts with negative offsets so that it can then go on to use positive ones if the structure gets big enough..?
Close - for the SDCC compiler, when a subroutine is entered it assigns IX at the stack pointer. That means the subroutine's parameters, passed on the stack and in higher memory addresses, are accessible via IX plus positive offsets, and the subroutine's local variables, stored on the stack which grows downwards to accommodate them, are accessible via IX plus negative offsets.

I'm interested in the mechanics of your scripting language. Do you actually write in a DSL which is converted to bytecode by your build process, with that bytecode being dropped into the Z80 binary as DEFBs or similar? Surely the script itself doesn't end up in the Z80 image?
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

dfzx wrote: Tue Aug 09, 2022 3:44 pm I'm interested in the mechanics of your scripting language. Do you actually write in a DSL which is converted to bytecode by your build process, with that bytecode being dropped into the Z80 binary as DEFBs or similar? Surely the script itself doesn't end up in the Z80 image?
I define labels for each mini-function as I go, that look like this (from memory; may not be perfect). Assuming that as each function is called, IX points to an enemy sprite structure and DE to the script data:

Code: Select all

execute_script
	ld a,(de)							; get the bytecode of a function from the script
	inc de
	ld b,0
	ld c,a							; put bytecode in BC
	ld hl,first_function
	add hl,bc
	add hl,bc							; calculate address of function
	ld bc,execute_script
	push bc							; put return address on the stack to execute next byte code
	jp (hl)							; jump to the function (A, BC and HL can be scrapped)
The functions themselves must all fit in 1/2K and start on even addresses for this to work.

Code: Select all

first_function
...
..
.

ds ($ AND 1)							; waste a byte if the address is odd
function_put_val						; absolute label not entirely necessary
X_PUT_VAL EQU (( $ - first_function) / 2)		; defines X_PUT_VAL as an offset from the first function
	ld a,(de)							; get next byte of data from script (should be a structure offset)
	inc de
	ld (x_put_val_op+2),a				; reprogram the ld (ix+0) statement below with the offset
	ld a,(de)							; get next byte of data from script (a value)
	inc de
x_put_val_op
	ld (ix+0),a							; write the value into the structure
	ret								; done. process the next bytecode.
	
function_end
X_END EQU (( $ - first_function) / 2)			; defines X_END as an offset from the first function
	pop bc							; throw away the scripting engine return address from the stack
	ret								; return to whoever called the scripting engine
Then my script is simply a definition of bytes using these parameters. The compiler turns the labels into the appropriate byte values and (as you thought) stores them somewhere using a defb. So to reprogram an object's hit points at offset OBJ_HP into its structure with a value of 20, then exit, the script is simply:

Code: Select all

example_script
	defb	X_PUT_VAL, OBJ_HP, 20
	defb	X_END
The tricky bit is not getting my DEFs (capital letters) and labels (lower case) confused, so I don't accidentally put an actual address in my script, or JP to a bytecode number thinking it's a valid address, as that will jump into the ROM and cause a big crash!

If you can fit all your functions in 256 bytes and start them on a page boundary then the labelling and the scripting engine all becomes a lot simpler. The command codes are then an exact match for the low byte of the jump instruction to execute them. But if that's not enough you could use 4x the bytecode, giving you 1K to fit your functions in, though you'd have to throw away up to 3 bytes between functions to keep everything on a multiple of 4.
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

In case anyone's wondering the big question, WHY?, let me explain:
a) It's way more fun this way! What better opportunity to make up your own programming language than inside a cheesecake animé Shmup!?! :lol:
b) I could do this with assembly, or macros that expand into assembly, but ultimately that eats up memory. Look at the code to move to the next sprite in the list:

Code: Select all

ld bc,OBJ_SIZE
add ix,bc
That's 5 bytes, whereas in my script it's just a one-byte token to call the function.
Can I save enough though to allow for a 1/2 K of scripting engine? Probably barely. Unlikely to cut it if it was just coding enemy actions and behaviours, but since it's governing the whole level layout then it's probably just about worth it.
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by Joefish »

Just noticed the '2020' at the bottom of the title screen and thought I'd been working on this for a while. Then came here and noticed that first screenshot was dated 2018!!! :lol:

And I've done four other games since then...
User avatar
patters
Manic Miner
Posts: 467
Joined: Thu Apr 11, 2019 1:06 am

Re: "Go-Go BunnyGun", first screen grabs.

Post by patters »

!remind me in 6years :lol:
Post Reply