Thursday, August 29, 2019

Scrolling Camera Tweaks

One of the subtle things that can really impact how a scrolling platformer feels is how the camera works. Does the character stay centered, or does the camera feel a bit looser? If it's loose, when does it scroll, and how quickly?  There are tons of articles out there about this, and they can describe it much better than I can, but it's a decision you have to make when designing a scrolling game.

In Halcyon, when moving horizontally, the camera tries to "stay ahead" of the player, by scrolling quickly until most of the screen is ahead of the direction the player is facing. That gives you plenty of time to think about upcoming obstacles and enemies.

This works really nicely for most of the game.

Until it doesn't.

One scenario that happens in Halcyon is that you will get out of the tank, use your grenades to destroy some walls, then get back into the tank. To work around limitations of the NES, destroyable walls regenerate when they go off the screen. This leads to situations like this, where you turn around to get back into your tank, and the destroyed wall immediately goes offscreen as the camera hurries to move ahead of you.

This is a tricky situation. Basically, I want my scrolling to work differently in this subjective situation. But how to define that and codify it into something that actually does what I want?

Tonight's attempt is some simple hackery:  Add a 4-second timer after destroying any block. During those 4 seconds, the camera won't scroll horizontally unless you get a lot closer to the screen edge in the direction you're wanting to scroll. 

This works great for the scenario in question:

The real question will be whether it causes annoying behavior in other parts of gameplay. That will remain to be seen....

Thursday, August 1, 2019

Crazy crashing bug

Normally after I test on an emulator for awhile, I eventually throw the game onto hardware, and get frustrated at it not working.

THIS TIME, however, I ran into something new. The game was working on my primary-use emulator, Mesen (which is accurate, easy to use, has a wonderful debugger, and an author who is always adding new features). It worked fine on hardware. But this week I was away from my computer a bit, and wanted to do some testing, so I threw it on my phone (which is using the quite common emulator, fceux).  And on my phone, it now won't run AT ALL.

fceux is a pretty good emulator. Not quite as 100% accurate as Mesen, but usually good enough.  What could cause the game to crash on it but not Mesen or real hardware?

As I usually do when I get stumped with a hard-to-debug issue, I did a git bisect. Git bisect is the one feature of git that, for a single-developer project, makes it better than subversion. It helps you quickly run through past changes, and find the one specific change you made that causes a behavior to happen. I won't go into detail here about it, but if you haven't used it, you're missing out.

Anyway, I did a git bisect, and discovered that the breaking change was just adding a new room to my game. No new code, no new features.  With some experimentation, it turned out that adding ANY new room would break it. Which sounds like maybe I was pushing something over a bank boundary in my rom.  So I dug up my assemblers map file, and no. Nothing was being pushed out of place.

So what could it be? fceux has a debugger, but for some reason, I can't understand how to use it. Did I really need to learn its debugger just for this?

Well, because the problem happened somewhere on startup, the next thing to try (before learning the new debugger) was to insert a bit of code that would turn the screen blue and then infinitely loop. I'd add that at my first line of code, compile and test.  It turned the screen blue on fceux, as it should. Which meant I was getting that far in my code.  For the next few hours, I moved that snippet of code forward and back, trying to find the exact function call that was crashing.

The result?  It turns out I was calling the update function of the music engine (ggsound) before ever calling the initialization function of the library.  Ok, that's bad.  But why would it work on hardware and Mesen but not fceux?  Suddenly it occurred to me.  Mesen and hardware start up with mostly random values in ram.  Fceux starts with zeros. I'm sure that some branch somewhere in the music engine branches on zero, and breaks.  The chance of that piece of memory being 0 in Mesen or hardware are just 1 in 256. So I never saw it crash on those platforms.

That said, I still have no idea why it didn't happen until I added one more room to the game. Probably some dark voodoo. I don't want to know.

Saturday, July 20, 2019

Prep for testing

My goal is to have the first area of the game ready for a few folks to sanity check and test for me soon. The goal of this first round of testing is just to make sure I'm not completely off-track. Does it actually run properly for other humans? Is it playable? Is the difficulty completely out of wack? What about the controls?

That means that before I hand it over to folks I need to add just enough polish on everything that it's not a frustrating experience. There are tons of things that aren't done, but that's ok. It just needs to be enough that you get to experience the game properly.

I've been playing through the first area a few times to make sure if feels ok to play to me. It's not very big -- only about 20 different rooms. But that's enough to take a few minutes exploring. And enough to get frustrated by if it doesn't play well. I've already had to change a few rooms that ended up being annoyingly hard.

The other thing that I spent some time on was getting saving/loading working. I fully expect my testers to die a couple times, even this early. I don't want them to have to start completely over every time they die, which means getting game saves working. I have another blog post I need to write about how flash saves work on the mapper I'm using. But I had that part working. What I didn't have working was everything that interacts with actually saving: What happens when you die? How do you load a saved game? How do you start over instead of continuing from your saved game? Does everything get loaded properly when you resume (answer: NO)

I think I finally have all that debugged and working. Tonight was the final piece of it: adding a simple UI for letting the player choose to Start or Continue. Which means title/menu work. Which always takes significantly longer than you expect, and is really boring. Yeehaw.

I'm almost there though. Time to do some more playthroughs.

Now it's a fairly short list of things that I hoped to have in this demo but are missing, and probably won't make it to this first testing round:
  • Super NES controller support (there's a lot of "hold down + A" type things in the game. If the player has a SNES to NES controller adapter, they can use dedicated buttons for those)
  • New Enemy Graphics (I'm using tons of placeholders)
  • The ability to shoot upward at an angle once you find the tank
  • Fix buggy behavior with the health-hungry blob enemy

Friday, July 19, 2019

Pseudo-fixed bank

I wrote about bank switching a few years ago when working on Atari Anguna. The idea being that the 6502 (or worse, the 6507 which is the variant of 6502 in the Atari) can only address a certain amount of ROM memory, so larger cartridges use multiple "banks" where you can switch which bank of ROM you're using. Nintendo cartridges used the same technique, so bank switching is important on both consoles.

Many variants of cartridge hardware (commonly known as mappers) divide ROM space into 2 (or more) sections. One that always stays active, and a second section that you can switch. This is a lot easier than switching the entire ROM space at once, as you can control things from the fixed bank, and just load data or call routines from the swappable banks as needed.

The mapper that I'm using for Halcyon (GTROM) doesn't have a fixed bank. Like Atari Anguna, it swaps out the entire bank all at once. Which means that you have to have special code to call a routine in a different bank. You put a stub of code (commonly called a trampoline) in the same place in both banks (or in RAM, which doesn't get swapped). You call that stub, it changes banks, then calls the new routine.

That's worked well for me so far on Halcyon. But now I've realized I have about 15 subroutines that I call a lot and from all sorts of different banks. I decided it would be faster (both in terms of writing code, and in execution) to avoid trampolines for those, and instead, duplicate just those routines into every bank that uses them.

Unfortunately, cc65 (the compiler/assembler suite I'm using) doesn't have direct support for duplicating routines multiple times in different places. So instead I'm doing some ugly hacks.

First, I compile the whole project once, leaving blank space in the places where the code should be duplicated to. One of the linker outputs is a nice map file, which tells the address of every piece of code in the final assembled output. So I have a python script that analyzes the map file, and extracts the portion of compiled binary that should be duplicated in other banks. It writes this to a file, and compiles everything again, this time injecting that extracted binary into each place that it needs to be duplicated.

This worked perfectly until last night when I started getting weird crashes calling code in this duplicated pseudo-fixed bank. Why was it crashing?  After an hour of angry debugging, I discovered the issue:  My python script had an off-by-one error. It was omitting the last byte of the pseudo-fixed bank.  Which means I had half an instruction dangling without the other half. That would do it.

Sunday, July 14, 2019

First Boss

I feel like I'm finally making real progress. Using some placeholder graphics from the ever-amazing surt, I've got the first boss fight finished. This boss is the main obstacle in your way from finding the tank vehicle, so you have to fight it on foot.

In Halcyon, bosses are mostly just another instance of a regular enemy, just harder. There were a few differences which I'll talk about in a minute, but interestingly, those differences aren't what took me the most time with this fight. Really, the things that took the longest were bugs in my main enemy-handling code that only revealed themselves while developing the boss fight:
  • The boss shoots missiles. The system for spawning a bullet for a boss had a bug based on the sign of the vertical offset.  Meaning if you spawned a bullet above an enemy's reference point, it failed. (every bullet I had spawned until this point was shot downward from an enemy, so I never noticed)
  • The edge-of-screen clipping was broken on the left side of the screen. Other enemies were narrow enough that I didn't notice, but enemies would disappear once about halfway off the left edge.
  • My enemy animation system was broken for animations that had multiple different repeating sections
This guy's look might change entirely once Frankengraphics gets ahold of him.
I'm also not sure about the vertical placement of the enemy health bar. We'll see.

Once I got those things worked out, developing the things specific to bosses was pretty quick:
  • I wanted a boss HP meter so you had some idea of how the fight was going. I thought this might take a while to develop, but I was able to reuse the code from the player's HP meter without much work, so this only took about 10 minutes to get working!
  • Bosses need a way to stay dead once you kill them. I already had the engine infrastructure in place to persist important global game state, so I just needed to add hooks before spawning the boss, and after killing it, to read and write that game state.
  • I added another entity, a type of gate, that blocks a pathway until all other enemies on the screen have been killed. This could be generally useful in other screens where I want to force a fight, but was important to make you actually stay and fight the boss.
The two things that I'm still missing are special boss music (which should be easy, but I just need new music), and a larger and more interesting explosion for bosses.

That said, other bosses in the game might require something more complicated -- larger bosses might be made from background tiles, and require some sort of background-scrolling magic to animate them. We'll cross that bridge when we come to it.

It's almost time for another round of testing, to see what's completely broken, or if the challenge level is acceptable. If you're reading this, and are interested in helping test the game, send me a message!

Thursday, July 11, 2019

Defining enemies

Enemies are the type of data-driven thing in video games that are fun to think about, in terms of tooling and development.  They have lots of stats (health, size, damage they do, etc), and a lot of different behaviors attached (What function runs to update them? Does anything special happen when they take damage? What about when they deal damage? Or when they die?)

There are tons of solutions for how to handle this. But one of my soap-boxes is that game data should be defined in a textual format, and be easy to edit (either by editing the text format, or with an easy UI tool).

For Halcyon, I decided to use a YAML format for enemy definitions. YAML is a data definition language, similar to XML or JSON. If you read about it, there's all sorts of reasons people say you shouldn't use it. But it has one important thing going for it: it's really easy to read and write. Which is what I need for this game.

- name:        topgunner
  hp:          4
  width:       16
  height:      16
  dmg:         1
  on-collide:  NORMAL
  on-offscreen: KILL
  update:      enemyb_topgunnerUpdate
  animation:   anim_topgunner
  frames:      frames_topgunner
  init:        enemyb_topgunnerInit

- name:        topgunnerBullet
  hp:          4
  width:       4
  height:      4
  dmg:         1
  on-collide:  NORMAL
  on-offscreen: KILL
  on-shoot:    NONE
  update:      enemyb_8wayBulletUpdate
  animation:   anim_shooterBullet
  frames:      frames_shooterBullet
  flags:       BULLET

I define my enemies in a YAML format, specifying both their numeric stats, the name of some animation data to display them, as well as various routines and properties to define their behavior. My build script then converts this YAML at compile-time into an assembly language file that defines arrays of data to represent each enemy.  Symbol names (like enemyb_8wayBulletUpdate) get injected right into the generated assembly code, and the linker knows how to translate those into the addresses of the functions that they refer to.

Sunday, July 7, 2019

Big and Tall

One of the things that can use up a lot of your CPU time on the NES is metasprite rendering.  Each hardware sprite is 8x8 pixels (or optionally 8x16). Complicated characters are then made up of a bunch of different sprites together, which is often referred to as a metasprite

Rendering a metasprite isn't complicated -- you push X, Y, tile, and color values into the right spot for the first sprite, then read the next sprite, adjust X and Y appropriately, and repeat.  The problem is twofold. First, it's just a lot of instructions on the 6502 to read, add, and write the sprites, when there's a lot going on onscreen.  Second, you have to deal with clipping.

Sprites can be positioned from pixel 0 to 256 horizontally, which is represented in a single byte. If you wrap around from 256 to 0, the sprite will appear on the left side. So for a large metasprite that's near the edge of the screen, you have two options.  First is to hide the object entirely if any of the sprites go off the edge. This is faster, but looks bad -- the character pops onto or off of the screen. A lot of old nes games do that, and you get used to it as a player, but it looks cheesy.  The second is do check each single sprite as you're computing its position. If a sprite's X value wraps around from 256 to 0 (or vice versa), you omit drawing that sprite. That gives a much better appearance of smoothly scrolling off the edge of the screen.  Unfortunately, it's that much more math you're doing for each sprite in the game.  Since enemies are often made of at least 4 sprites, and often 8 or more, that can become cumbersome very quickly.

In Halcyon, I wasn't happy with the popping-off-the-edge solution, so I opted for taking the time to check each sprite. To try to keep it easier on the CPU time, I have a number of ugly optimizations that make the wrapping/clipping checks a bit faster.  Unfortunately, one side effect of those optimizations is that my metasprites can only be 32 pixels wide or tall.

That was fine until this week, when I wanted to make a tall barrier that opens when you defeat a boss.  It needed to be more than 32 pixels tall.

That ugly thing in the middle is an "enemy" wall that can raise/lower depending on what other enemies are alive. The graphics are just placeholders.

That was tonight's challenge -- how to work around my optimizations and allow larger enemies?

The answer ended up being fairly simple: I still limit each metasprite to 32x32 pixels, but I allow a metasprite to specify a new base position and start a new metasprite from there.  So really, a metasprite can be more of a meta-metasprite, being made of a few different metasprites at different offsets.

Ugly?  You bet.  But it works.

Friday, June 21, 2019

Stairs and a hoppy guy

One thing that Frankengraphics (the artist I'm working with) suggested was adding 8-pixel tall blocks that the character could run/drive up without jumping. Sort of like ramps, only without the continuous nature. I've been putting it off, because ramps are weird.

I finally decided to tackle it in the last few days. And it turned out that the discrete nature of the steps, combined with how I've handled collisions so far, made them really easy.  As in 30 minutes of work, and they just worked.

Next I decided I needed some hoppy bouncy enemies. Because erratically bouncing enemies are always a giant pain to fight, which is a good thing. My hoppy enemy took a bit longer than I thought to get it working right (a couple of hours for this guy, way longer than my stairs!), but I finally have it working mostly right.  Except that it needs art.  I'm using a random placeholder for now, so it mostly looks like a bouncing cheeseburger.

Saturday, June 15, 2019

Level Design in Earnest

Well, with moving platforms done, I decided I'm fair enough along that it's time to start doing "real" level design. I've got a bunch of maps that I've designed for demo purposes, so you can play through a bit of how the world will play, and I can test out how transitions work.  But they were never intended to be the real map.

So I've wiped out all that, and started now on the "real game."  And after a couple years of fiddling about with the engine, it's really refreshing churning out a few maps. 

Spoiler alert!

I've realized a few things:
  1. I'm bad at making interesting maps.  I have these big rooms, and it's hard to fill them with things that will actually be fun. I'm trying though.
  2. I don't have to go very far before I run into features that I need to add into the engine.  I hit the first save room quickly, a few days ago, and remembered that I hadn't implemented game saves. I got that added, then immediately started on a room with some 8-px steps (which the main character is supposed to be able to run up without jumping). Of course, I haven't actually implemented the running up stairs yet.  Time to do that I guess.
  3. Nothing reveals bugs like real content.  Just tonight, I plopped an enemy (the eyeball creepy crawly guy that I've used all over the place in the demo rooms) into my map, and he's broken.  WHY, computer, WHY!?
Exhibit A, in which I decide that I hate computers.

Friday, June 7, 2019

Moving platforms working

Ok, another lunch hour devoted to the platforms, and they're working!  My idea of how to make them ended up working perfectly.

I ended up wasting about 45 minutes though due to a stupid optimization. Instead of checking collisions against the full world coordinates of the player and platform (which are 2 bytes instead of 1, thus requiring quite a bit more instructions for the collision check), I was just caching and comparing their on-screen rendered coordinates (which is just 1 byte).  That works well EXCEPT for the fact that the player's render coordinates are calculated AFTER the collision check, meaning they're one frame behind.  For a lot of things, that one frame might not matter, but here it caused the player to keep getting stuck in the platforms.

Once I removed my silly optimization, things worked great!  Although I need to make some minor changes to the camera code. Right now the camera only scrolls horizontally when the player moves naturally, but not when the platform pushes the player.  Meaning currently a platform can drag you offscreen. That needs to change.

The nice thing about this addition is all the ways it can be used in gameplay: moving platforms, moving walls that get in your way, enemies that can be frozen and climbed, barriers that can get moved by a switch or trigger, etc.

Tuesday, June 4, 2019

Catching up, and Halcyon moving platforms.

Hmm. I haven't posted here in almost a year. I got thinking about my progress on Halcyon, and decided it was time to actually get back to writing about what I'm doing here.

I realized I didn't say a thing here about Super Homebrew War, my 4-player nes brawl, using characters from other well-known NES homebrew games, which scored 2nd place in this year's NESdev competition. 

Nor did I mention that I got inspired to try my hand at Dreamcast development. Like last year's port of Robo-Ninja Climb to the 2600, I decided to once again take my NES code, and try porting it directly to the Dreamcast. As usual, it took slightly longer than I hoped, but I now have a Dreamcast version of Robo-Ninja Climb finished.
The nice thing about Dreamcast homebrew is that you can test on hardware for the low price of a CD-ROM!

Now I'm back on to working on my metroidvania, Halcyon.  Tonight's challenge is obstacles/platforms that aren't part of the background, and thus can move around.  Like ramps, moving platforms are always slightly trickier than you'd think they should be.

After a few false starts, my current theory of how they'll work is like this:

  • Moving platforms are actually just an enemy instance, with a modified collision routine.  
  • During the enemy platform's update routine, I'll do a collision check: If the player collides with the platform, I'll move the player by the same amount that the platform moved. (which lets the platform push the player around)
  • During the player's movement, it will look for any enemies flagged as platforms, and handle collision results against them just like collisions against background tiles.
  • During the enemy's movement/collision checks, the collision box of the platform will be adjusted to be a tiny bit larger than during the player's collision checks, so that the platform will "grab" the player and move it if the player is riding on top
Believe it or not, but that moving thing above the tank is a platform.

I spent the evening writing the first half of this (the enemy update phase, which can move the player around).  Hopefully tomorrow or soon I'll get to the 2nd half (the player's adjusted collision routine), and we'll see if my theory works out.

Scrolling Camera Tweaks

One of the subtle things that can really impact how a scrolling platformer feels is how the camera works. Does the character stay centered, ...