Next up is handling the physics of the rest of the tile types.
Furthermore,
A lot of stuff in this chapter, but fairly little of it new; we primarily just check if we can move and then do so over and over again. Instead of dragging our heels, let's just implement all of the above so we can move on to something more interesting.
More Rapid Physics
But first, the physics runs way too slowly. We need to decrement 176 until we hit zero; 176's divisors are 1, 2, 4, 8, 11, 16, 22, 44 and 88. We're currently using 16, so the candidates are 22 and 44. Both are better than 16; 22 is still quite leisurely, at 44 the stones feel deadly. As a trade-off we'll go with 32 (which is not a divisor of 176), by simply calling physics twice in a frame (since 16 is).
call physics
call physics
(duh)
Water
We need some way to make some of the physics go slower; the stones are fine now, but if everything runs at that speed, it'll be a bit too hectic.
physloopcount:
db 0, 0
This counter will increment every time we've finished a full physics loop, like so:
jr nz, physics_notlooped
ld hl, (physloopcount)
inc hl
ld (physloopcount), hl
ld hl, 176
physics_notlooped:
We could have used the frame counter, but there's a few problems with that. First, if we (still) tweak the speed the physics runs, everything will blow up. Second, it's entirely possible we'll have so much stuff moving at some point that we spend more than one frame time. Tying the physics to frame count would make it nondeterministic and that's undesirable most of the time.
We'll add handlers for water source and water like before:
cp 9
jp z, physics_stone
cp 16
jp z, physics_watersource
cp 15
jp z, physics_water
The water source is pretty simple.
physics_watersource:
ld hl, (physloopcount)
ld a, l
and 7
jp nz, physicsdone
ld a, (ix+16)
cp 0
jp nz, physicsdone
ld (ix+16), 0x8f ; generate water
jp physicsdone
The primary complication with the water source is that we want to slow it down a lot. We get the physics loop count, do a modulo of 8 (i.e, check if all bottom 3 bits are zero) and bail out if it's not the time. If we are on the 1/8th physics loop, we check if the tile below is empty, and fill it with water if it is. There's no need to check if we're on the bottom border because water sources don't move and adding one to the bottom of the map would be stupid. Let's try not to be stupid.
I considered making a check here that if there's goo below the water source and turn it into a gem, but let's say the goo manages to block the water source.
Water itself is a bit more complicated.
physics_water:
ld hl, (physloopcount)
ld a, l
and 1
jp nz, physicsdone
Skip processing every other physics loop. We'll have to try to see if we can interleave the physics of other objects so that when we skip processing water, we process something else.
ld a, ixl
cp 16*11
jr nc, water_sideways
Map bottom check.
ld a, (ix+16)
cp 0
jr z, water_down
If we can go down, go down.
water_sideways:
ld hl, (physloopcount)
ld a, ixl
add a
add l
and 2
jr z, water_right
Take the physics loop count, and sum that with the current tile id * 2 and check if bit 2 is on. We don't use the first bit because we're skipping every second physics loop in any case. Depending on the result pick either to move left or right. This makes the water eventually fill everything it can.
water_left:
ld a, ixl
and 0xf
jp z, physicsdone
ld a, (ix-1)
; water+goo
; water+people eater
cp 0
jp nz, physicsdone
ld (ix), 0x80
ld (ix-1), 0x8f
jp physicsdone
No surprises here, map border check and empty tile check. We'll fill in the other interactions alter.
water_right:
ld a, ixl
and 0xf
cp 15
jp z, physicsdone
ld a, (ix+1)
; water+goo
; water+people eater
cp 0
jp nz, physicsdone
ld (ix), 0x80
ld (ix+1), 0x8f
jp physicsdone
water_down:
ld (ix), 0x80
ld (ix+16), 0x8f
jp physicsdone
The rest should be familiar by now as well.
Handling collisions with water and the people eater is really repetitive so I'll only cover one case here:
cp 0
jp z, water_left_water
cp 4
jr z, water_left_goo
cp 11
jr z, water_left_peopleeater
cp 12
jr z, water_left_peopleeater
cp 13
jr z, water_left_peopleeater
cp 14
jr z, water_left_peopleeater
jp physicsdone
water_left_water:
ld (ix), 0x80
ld (ix-1), 0x8f
jp physicsdone
water_left_goo:
ld (ix), 0x80
ld (ix-1), 0x85
jp physicsdone
water_left_peopleeater:
ld (ix), 0x80
ld (ix-1), 0x86
jp physicsdone
If there's empty space, move there. If there's goo, move there and turn into a gem. If there's peopleeater, move there and turn to stone.
A World of Goo
physics_goo:
ld hl, (physloopcount)
ld a, ixl
add l
and 15
jp nz, physicsdone
For slowing things down, we take the tile number and add the physics loop count to it, and skip unless all 4 bottom bits are zero. This makes the goo really unpredictable, staying still for a long time and then rushing out.
ld a, ixl
and 15
jr z, goo_right
ld a, (ix-1)
cp 0
jr nz, goo_right
ld (ix-1), 0x84
Then we go through all four cardinal directions in the exact same way: check for map border, check if there's space, if all's ok just add more goo, otherwise check the next case.
Note that the goo won't touch water and turn into a gem. It may be slow, but it's not stupid.
One Eyed Purple People Eater
The people eater consists of four separate tiles and thus four separate handlers; each of the handers does the same thing: check for map border, check if can move, move if you can, turn (into another tile) if you can't.
physics_peopleeater_left:
ld a, ixl
and 15
jr z, peopleeater_left_turn
ld a, (ix-1)
cp 0
jr z, peopleeater_left_move
cp 4 ; goo
jr z, peopleeater_left_move
cp 1
jr z, peopleeater_left_eat
peopleeater_left_turn:
ld (ix), 0x80 + 11
jp physicsdone
peopleeater_left_eat:
ld a, 1
ld (playerdone), a
; fallthrough
peopleeater_left_move:
ld (ix), 0x80
ld (ix-1), 0x80 + 14
jp physicsdone
There's no movement delay. It's supposed to be scary.
Stone and Gem Updates As listed in our to-do, rocks and gems should fall through water. This was a bit tedious. There were tons of cases like this:
ld a, (ix-1)
cp 15
jr z, dropdiagonal_left_ok1 ; water ok
cp 0
jr nz, dropdiagonal_right ; can't move, stuff on the left
dropdiagonal_left_ok1:
All of them follow pretty much the same pattern, though; wherever we'd check for zero, we now also check for water.
Splatting the people eater was curious. I went and implemented the handlers as expected..
cp 1
jr z, stone_splat
cp 11
jr z, stone_splat_peopleeater
cp 12
jr z, stone_splat_peopleeater
cp 13
jr z, stone_splat_peopleeater
cp 14
jr z, stone_splat_peopleeater
where stone_splat_peopleeater is the same as stone_splat, just without marking the player as done. Compiled, ran, tested, and... the people eater just slipped past the falling stone.
After looking at video capture frame by frame, I noticed that the falling stones would become inert mid-air for no clear reason. The reason was that the people eaters, which move on every cycle, would leave tiles with dirty flags on, and thus the falling rock would see that okay, there's something below me I don't recognize, so let's go inert.
This was simple enough to solve by adding an AND to mask off the dirty flag before checking against the tile numbers. We could do that everywhere we compare against tiles, but I don't think it's as needed elsewhere, because here moving stones are deadly while inert ones are not.
The expected result of doing more physics is that calculating physics takes more time. That's fine, we seem to have plenty of frame time left, and even if we'd peak momentarily to two frames, that's acceptable. Graphical glitches may occur near the top of the screen if a lot of tiles are updated; the fact that we draw our map bottom up actually makes this worse, as the last tiles to be drawn (and thus, later on the raster) are nearer to the top of the screen. It may be a good idea to revisit the map drawing and do it top down.
In any case, that should do it for physics, apart from the inevitable bugs that we'll squash as we come across them.
We still don't do anything special when player picks up gems, the exit door stays shut, and even if it were open, entering doesn't do anything. Level select doesn't let us pick between levels yet as we only have the one. And finally, we don't have sound.
This chapter's version of the source is available here.
Size-wise, we're at 5012 bytes, up 627 bytes. There's several things we could do smaller this time, as there were many repetitive things, which could be organized better. But sometimes getting things done is more important than how well they're made.
Any comments etc. can be emailed to me.