Hello! This week, I made more progress on the MSX port of Wandering Magic in my spare time in the evenings.
I managed to implement a few things that are pictured in this GIF.
First off, I made a map loader, It loads an uncompressed tilemap from ROM into RAM, each part of the map is made up of 16x16px blocks called "metatiles" -- 2x2 arrangements of 8x8px hardware tiles, along with some associated metadata like collision flags.
In addition to loading the tilemap into RAM, the metatiles for each cell are then unpacked into the raw 8x8 tilemap format that the MSX VDP expects, and this will be copied to the screen between room switches.
The upper area of the screen that contains playfield of the game is 16 tiles wide x 10 tiles tall. The lower part is reserved for HUD elements. The camera doesn't scroll -- the MSX1 is bad at hardware scrolling, and I designed the game around having individual screen-sized rooms.
Next, I added player input and movement with the keyboard. That was pretty fast! One thing that was a little tricky is that the MSX returns a directional code from 0 - 8 (0 = no direction, 1 = up, 2 = up-right, 3 = right, 4 = down-right, etc. going around the circle clockwise), rather than having a separate input for each direction. This is at least if you use the standard "direction reading" routine from the MSX BIOS, so turning that into independent x and y axis takes a little bit of translation. But a nice feature of the BIOS, is that it standardizes reading both keyboard or joystick for directions and buttons.
There's also a way to read the keyboard matrix directly, which is necessary if you want to use other keyboard keys besides arrows + space bar. It seems pretty common to find MSX games that use arrow keys, space bar as a primary action, M as subaction, function keys for menus, and support 2 button gamepads. So I guess eventually I'll need to look into all of that.
After that, I added entity movement with tile collision support. The entity movement was pretty challenging to write but after some trial and error and lots of debugging to step through things, I managed to make something functional,
The collision code even supports a neat feature -- corner nudging! This will automatically slide the player around corners when it detects only one side of the collision test is obstructed. For example, if you're walking up into a corner that is obstructed to the right but not the left, then the movement code will redirect your movement to the left, off the obstruction. As soon as the way is clear again, the movement code goes back to behaving as normal, proceeding in the direction the player inputted. Corner nudging makes it much nicer to do pixel-based movement through narrow spaces (especially single-tile wide ones), without needing to lessen the size of the player's collision box too much.
Collision still needs a lot of work to optimize things better, since it does slow down when there are several entities on screen. For testing this, I duplicated the player entity 32 times. I observed the framerate dip as soon as the movement began, with even more slowdown happening when moving on diagonals (since it needs to test on x and on y). When the entities are still, or there are less entities, no slowdown occurs, This slowdown problem also occurs at 16 of 32 entities, but only when moving in both directions at once. I'm not sure the exact tipping point where things spill out of the available frame time, but there's a bunch of things that could be improved. I can live with some slowdown in heavy scenes, but I'd like to push things better than they stand right now.
I need to measure things better, but there's a quite a few steps involved:
- converting values between 2's complement fixed-point formats (4.4 -> 8.8 -> 16.8 with sign extension)
- adding large numbers, using the carry flag to synthesize larger additions from smaller additions.
- bounds-checking the entity position. This takes a fair amount of time, but is necessary since entities can move off the sides of the map.
- converting pixel coordinates to tile coordinates
- reading the actual data out of the tilemap
- branching on the collision results
- updating the position (if succcessful/unobstructed), or stopping the movement (if failed/obstructed)
Here's some code for anybody interested.
The optimization steps to improve this could be messy, but hoping that my experience from writing programs for the Game Boy should help here. The Z80 is more powerful than the GB's processor, so hopefully some of the more ugly tricks for performance aren't needed, but I still have ideas for things to try.
The performance issues are also not even accounting for the added processing required to do entity-vs-entity hitbox collisions, going to need to do that soon too!
Lastly, I looked at adding a graphics loader for entities, so each room can have unique sprites depending on what is spawned there.
Half the sprite tileset is reserved for the player + common sprites like treasure chests, item icons, explosions, enemy spawn indicators (like Zelda 1's cloud puffs), etc.
The other half is loaded between each room, allowing for entities to reserve their own sprite art, as well as the art for all their subobjects ahead of time. Then when the game is in action, no new graphics are required -- simply spawn things that reference tile indexes based on what's in the loader cache (cleared each room).
This approach of loading ahead does mean that any particular room's full contents (including subspawns) must be known ahead of time, but it also frees up more performance to gameplay, since graphical loading when the screen is in use means sacrificing vblank time, and potentially causing things to split across multiple frames. This quickly becomes messy, and I'd rather avoid any of that for this.
There's still a big list of stuff remaining! Not sure what I'll do yet, but hoping I can continue to chip away at stuff on this.
These are all features from the original Wandering Magic game that still need implementing:
- entity-vs-entity collisions
- support for obstructing other entities
- hit-scanning for bump attacks (think Ys, Xanadu, Fairune, and similar games)
- pushback on attack hits
- entity movement/collision optimizations
- hit flashes (temporarily colors the entity palette upon a hit)
- HUD drawing
- health bar + exp bar
- health number + level number
- attack + defense + gem icon and values
- enemy health bar HUD (shows health bar and name + sprite when attacking an enemy)
- modal full-size textboxes (hides HUD and displays text until player confirms)
- short timed messages (uses same area as enemy health popup, disappears after time)
- pause menu - lets you look at your equipment, and use healing items.
- room switching
- switching rooms when the player moves outside the screen.
- doors that switch rooms when walked over
- entity implementations
- enemies (walk into to attack)
- slime (weak, moves randomly, infrequently attacks unless the player hits them)
- bat (alternates between flying around the room, resting, and flying at the player)
- ghost (floats around the room slowly, occasionally moves toward the player)
- dark mage (teleports around, shoots projectiles aimed at you)
- snake (fast and aggressive, charges at the player a lot)
- knight (slow and indestructible, you need a much stronger weapon)
- skeleton (fast and runs around around, throws bones)
- "red"/stronger versions of enemies (these have improved hp/atk/def/speed, and also grant more exp for defeating)
- new enemy types
- destructible trees/doors
- chests (walk into to open)
- dialog npcs (walk into npc to see their textbox)
- player projectiles
- enemy projectiles
- shop items (stand over with sufficient gems, press action button to buy)
- mini doors (if the player stands in front of them with a ring, transform them to a miniature form, and allow the player to proceed through the door)
- enemies (walk into to attack)
- save system
- checkpoint tiles that trigger saving when the player presses the action button while standing on them.
- game systems
- level ups and experience
- attack and defense stat gains on level up
- healing HP to full on level up
- equipment (automatic, each item belongs to exactly one slot, the next piece of equipment is always better than the last so it replaces it)
- magic (an infinite use item that spawns a projectile when the player presses the action button)
- healing items (consumable / limited use, restores player HP)
- joystick input
- support for more keyboard keys
- title screen
- game over
The above list started as an immediate task list, but quickly grew to a larger list of stuff to do while I was writing this. Heh, whoops!
Well, that's all for this week! Hope to see you again soon!