Thursday, November 19, 2015

Enemy Spawning

Still working on Crop Insurance. The one nice thing about it is that I'm tracking my hours, which lets me see a better estimate of how much time I spend on my side projects. The answer is about 5-6 hours per week.

No wonder it took 3 years to finish Robo-Ninja.

That being said, I'm feeling a bit stuck on Atari Anguna. I haven't been doing much, because when I do, I've been working on my semi-random enemy spawning routines. And I haven't been happy with it.

The issue is that I don't want enemies spawning stuck in walls, which means:

1. I can go completely random on spawns, but then if it's a collision, try again.
1b. To check for a collision, I either have to do the math to figure out if I've collided with a wall (which on the Atari is non-trivial due to the crazy arrangement of the playfield registers) or
1c. Draw the enemy once, check the collision registers, and then move (which means each try eats up a frame, which could be slow and look really tacky)

2. Instead, I could designate "safe spawn" areas in each room layout.
2b. Depending on how I do this, it could eat up quite a few bytes per room. (I potentially need at least 3 safe spots per room. This could be 6 bytes per room, more than I want to spend)
2c. I could cut down on rom usage by having a lookup table of safe spawns, the question is how this would work, and doing the randomization and lookup in a way that's fast and compact.

I spent WAY too long thinking through these, testing ideas, etc, and not being happy with the results. Which made me less interested in working on the game, and thus slower progress.

Finally I have a solution that I think I'm happy with, and seems to be mostly working.  I build a lookup table of 8 potential spawn points (16 bytes for the lookup table). Then each room has a byte to tell which of these potential spawns are allowed for each room layout -- a 1 means it's safe to spawn in.  Then I pick a random number between 1 and 8, check that bit. If it's not safe, try the next. Once I get a safe bit, spawn an enemy at the position that the bit refers to. Then if there are more enemies to spawn, I just check the next consecutive bits (instead of picking more random numbers, and risking enemies spawning in the same position as each other) until I've spawned them all.

It seems to work, doesn't require much storage, and is relatively fast.

For any 6502 assembly nerds, here's the code I used. You can probably spot all the places that I'm doing things really badly (I end up wasting a lot of cycles saving and loading values -- it seems I just don't have enough registers to track everything I need and still use X and Y for indexed loads (Why oh why didn't the 6502 have TXY and TYX opcodes to move values between X and Y directly?))

    ;TempPF2 should be the number of enemies we want to spawn
    ;TempPF2 then holds the number of enemies we want to spawn - 1
    dec TempPF2

    ;load the room index
    ldy #0
    lda (RoomDef),Y
    ;x now has room index

    ;Store #1 in TempPF1 to bit against..
    lda #1
    sta TempPF1

    ;get random number between 0 and 7
    lda Rand
    and #%00000111

    ;a has safe spawns
    lda SafeSpawns,X

    ;rotate the safe spawn based on the random number
    sty Temp
    ldx Temp

    cmp #$80
    bne .keepRotating

    ldx Temp
    ;now work our way through seeing if it's safe

    ;if it's safe, go to useThisValue (of x)
    bit TempPF1
    bne .useThisValue

    ;if not, rotate again, increase X
    cmp #$80


    ; if X equals start, fail. What then?
    cpx Temp
    beq .useThisValue

    ; if X equals 8, set to 0
    cpx #8
    bne .checkSpawnLoop
    ldx #0
    jmp .checkSpawnLoop

    ldy TempPF2

    ; otherwise, look up X and Y in table
    lda SpawnsX,X
    sta Enemy0X,Y

    lda SpawnsY,X
    sta Enemy0Y,Y

    dec TempPF2
    bmi .allDone

    jmp .beforeRotation

NES Anguna

Well, I had a little bit of time still, while Frankengraphics is finishing up her game Project Blue, to have a little downtime on Halcyon, s...