Thursday, August 28, 2008

Typo and my bool

Notice a problem with this?
if (abs(bullet.xSpeed > 0))

It took me a good 10 minutes to find that problem. Alas, I must have added that typo when I was moving some of my utility functions (min, max, abs) to a more logical home. Ah well.

I also got into trouble last night with bools. Fore some reason, most of the controls for the game worked, but L and R would not. Turns out it has to do with switching from my own typedef'd bool, which was just typedef'd from an integer, to using libnds's bool, which is enum'd as true and false (for those of you used to fancy-schmancy modern languages, C doesn't have a built-in bool, (well, sortof. C99 does, but we won't go there)). I was trying to return a bool value of 512, which using my old method, would evaluate as true. Using libnds's method, it's probably undefined behavior (I'm not sure what the C spec says about doing this), but definitely didn't work. But the fact that it worked for a bunch of other values that I threw at it somewhat incorrectly (32, etc), made it harder to track down.

But I did get a lot more stuff finished off last night. There was a renegade sprite that kept showing up at semi-random times, with semi-random contents. I think I fixed it: it hasn't come back since I changed what I think was causing it (I was using a few partially-uninitialized sprites, in my bullet management code). But, like many intermittant problems, it's hard to know for sure. I also got the currently-selected-item sprite to appear up in the upper-right where it's supposed to.

Next up, moving all the menu/map/etc to the bottom DS screen. I got a start on it last night, but there's plenty more to do.

Tuesday, August 26, 2008

On a roll

Wow, I'm getting a lot done this week. Last night, the kids went to bed quickly and easily, and Sara decided to watch the Olympics, giving me a little more than an hour to get stuff done!

I managed to:
  • Fix the priorities, so sprites appear behind the foreground tiles. This was fairly simple -- I just had the priority numbers wrong. Which makes me wonder how it ever worked in the GBA version, but I didn't get around to comparing the codebases to find out why.
  • Add black foreground borders around small dungeon rooms. Before that, it looked weird when you walked off a could see your character out in the limbo beyond the room as you exited. So now the black space around the outside of rooms is in the foreground. It was a pretty easy fix, as I took a random room tile, and just gave it an all-black palette, to avoid having to add a new tile.
  • Fix a few other minor scrolling glitches
  • Get the overworld tiles to load correctly. Like the priorities, I was doing this dead wrong, so it was an easy fix -- I was loading a fixed amount of tiles for each map, and that fixed amount was completely wrong.
Let's see if I can keep going at this rate.....

Monday, August 25, 2008

Correction and signed/unsigned ints

Well, turns out a few people do read this blog after all! Cearn left a comment on my last post correcting my incorrect understanding of bit-shifts, which if you are interested, you can read for full detail, but the gist is that bit shifting WILL preserve the sign bit if it's declared as a signed integer and not an unsigned integer. So I stand corrected. Thanks Cearn!

Which means, looking back through my code, that the problem resulted less from my willy-nilly bit-shifting, and more from using an unsigned integer in one particular function. (which at the time was reasonable, because my engine never used negative screen positions until now).

I managed to scrape together 30 or 40 minutes this weekend to finish changing all of my screen setup and scrolling functions to be happy with signed numbers, and finished added some special cases to deal with rooms that are smaller than the screen size. And now all the backgrounds seem to be drawing and scrolling correctly! The first major task of porting is complete!

There's still plenty of issues to work though, even with backgrounds -- the priority of the foreground tiles isn't right...sprites still appear in front of them. And the overworld tiles aren't loading completely -- I'm probably just not copying enough tile data in that case. But I'm getting there!

Wednesday, August 20, 2008

More screen size and premature optimization

Donald Knuth was right. Optimizations I had done for the gba are killing me. And it all has to do with screen size.

See, my engine wasn't designed to properly handle rooms that were smaller than the screen. The camera's position in the room is stored by a number that I assumed was always positive, because, being equal to or smaller than the room itself, I never had to go negative, or outside the bounds of the room. But now with the bigger screen, the camera is often outside of the bounds of the room. So I need to handle all these special cases where the rooms are smaller than the screen. Two things need to happen: first, it needs to just work right. Second, smaller rooms should be centered horizontally on the screen, (unlike the picture in my last post, which looks goofy being shoved up to the left), and bottom-aligned with the screen, so there is less overlap between the HUD and the game. So I've got that to do, which I've started on, but is messy.

A big chunk of the problem was because of stupid optimizations that I (needlessly) did. See, the gba and ds don't have floating point units. And can't do divides with any reasonable speed. But in unsigned binary, bit shifting right by 3 is the same as dividing by 8. And because the tiles are all 8 pixels, I'm dividing by 8 all over the place. So instead of telling it to divide, I bit shifted, to make sure it was fast. Now this was stupid, because a good compiler (which gcc most certainly is) will do this optimization for me. But I figured it wouldn't hurt to make it obvious that it was happening, and thus prevent the compiler from randomly deciding NOT to bit shift and do a slow division. BUT that doesn't work if your number is signed, which mine now is. This is because negative numbers are represented in two's complement. You can still do fast divides with clever bit-shifting, but not as simply as just shifting right 3 bits. So now I need to go change all those places where I was trying to be clever back to actually using a normal divide, and let the compiler do the optimization.

Lesson of the day: my compiler is smarter than me. Let it do the clever stuff.

Thursday, August 14, 2008

Screen size

Well, I'm getting closer! As you can see on the right, things look almost normal!

The biggest problem I ran into so far was the difference in size between the DS screen and the GBA screen. I still need to make better use of the full screen real estate (at least to center small rooms like this one), but the bigger problem was in how the graphics engine deals with the screen size.

See, most scrolling tile games on nintendo's handheld use a small tiled background which wraps around as you move. This background is just big enough to have one row and one column of tiles off the screen at a time. So as the screen scrolls, you redraw the off-screen row or off-screen column (or both), and then scroll it onto the screen. A little tricky, but no problem, really.

The problem comes in that the DS screen width is the same size as the default small wrapping background that I had been using. So I suddenly don't have an offscreen column to draw on. The answer, of course, is to make the background bigger. Which is what I did, but there are some tricks with that. For a 32x32 background, the tile indices are layed out linearly, so for any given tile, the index you want to write to is x + y * 32. But for wider backgrounds, it's different..instead of a big wide background, it's two small background stuck together. Which changes us to the slightly trickier system of:
index = x + y *32;
if (x > 31) index = index + 1024;

Not a big deal directly, but a whole lot harder to use pointer math (which I had been doing) to deal with. So I had to go through and change things to not use any optimized pointer math, and instead, compute it the hard way. (Which is probably best -- as Donald Knuth says, "premature optimization is the root of all evil"). Once that was done, things are pretty close to working!

Of course, there were some other gotchas along the way. For example, I had a horribly hackish way of keeping the foreground and background layers synced, which didn't really work the way I wanted it to for the DS, so I had to do it right this time.

Anyway, the game is actually almost playable now. There are lots of little things to do (enemies don't fade when they get hit, your selected item doesn't show up, the foreground tiles appear behind your character, the outdoor graphics are messed up, scrolling southward is glitchy, etc) and I still need to change things so that the 2nd screen is used for subscreen, minimap, enemy stats, etc. (I also just got around in this build to adding text to the 2nd screen...thus the "hi" at the bottom of the screenshot) But it's moving along faster than I expected! (at this rate, maybe I'll be able to start an iPhone port before the thing is obsolete)

Thursday, August 7, 2008

Sprites, DMA, and caching

In the 2nd picture in my last post, you might have noticed the big brown square in the top-right. Strangely enough, that's a sprite that was appearing and disappearing at weird times. What you don't see in a static picture is how weird clones of the main character and enemy sprites would start appearing at semi-random intervals. Yuck.

Well, as usual, it's DS weirdness that I wasn't counting on that caused it. And this is where I launch into technical ramblings that make the eyes of the general public glaze over and start dreaming of Krispy Kremes.

See, the GBA and the DS both have this cool feature called DMA, which basically means they have a piece of hardware that copies memory from one place to another place, quickly (as opposed to the normal methods of loading a chunk of memory into the cpu, then writing it to another place in memory). Well, what I have been doing is setting up all my sprite information for a frame in regular memory, then, between screen updates, using DMA to copy all the sprite information from main memory into that magic vram that I talked about last time. (vram being the place where you tell the DS what your sprites and all other graphics should look like). But the more I debugged stuff, the more it looked like my DMA memory copy just wasn't working in this case. It worked other places on the DS, but not here. My conclusion: My DS hates me. But alas, I knew that chances were small that that was really the problem.

So after much digging (and much hatred directed at Nintendo), I found the issue: the DS has a memory cache, which, in the case of how I'm developing, is used more-or-less automatically. So, thinking I was writing to main memory, I was just writing to the cache, which hadn't been flushed back to main memory yet (the cache and memory were out of sync). The DMA doesn't trigger the cache to write back to main memory, so I was copying out-of-date information into vram. Bah. Easy fix, but like everything else in the land of debugging, took ages to figure out.

Next step: why my background is all wonky.

Tuesday, August 5, 2008

Title screen and vram banks

Well, I've got the title screen working:

But it wasn't without a fair amount of pain. The first step was pretty easy: when I copy the tile data from regular memory to video memory (vram), I needed to make sure I was copying enough -- since I'm using different memory copy routines than I did on the gba.

I did that, ran it in my emulator, and it looked great. Till I ran it on the actual DS, and it was just a blank screen. Bleh. I loaded it up in the No$GBA emulator, which is the most accurate emulator out there, (but you have to pay to get access to most normal debugging features, and unfortunately, the author has disappeared, meaning it's impossible to purchase, so I'm out of luck getting a useful debug version!), and it black screened also. Bother.

So I spent a few hours fiddling around with some DS demos and copying code back and forth, trying to figure out where I went wrong. Turns out it's with DS's funky vram banks.

See, on the gba, your vram is vram. In the DS, there's a few banks of memory that are meant to be used for vram, but aren't assigned to specific memory addresses by default. You have to assign them yourself, depending on what you want to do with your video. If you are doing fancy 3d stuff, you assign them to the 3d texture memory addresses. If you're doing 2d tile graphics, you assign them somewhere else, and so forth. The trick is that there are certain configurations that are valid, and others that are not. And somehow I had been using an incorrect configuration. So changing that all around fixed things. So now it's working. Although I'm still not sure I have my head wrapped around how these vram banks are supposed to work, and why It's working now, but I don't completely understand it....and that's a dangerous place to be (If you want a taste of what the vile documentation looks like, check this out) . Oh well, for the sake of time, I'll move on, and come back to these things if they bite me again.

Next step: get this looking a little more normal:

Saturday, August 2, 2008

Getting Started

After enough pestering from random folk, and donations of hardware from the wonderful people at Electrobee, I've decided to go ahead and port my Gameboy Advance game Anguna to the Nintendo DS. It should be a fairly straightforward port, as the DS's 2d hardware is remarkably similar to the GBA, but the fact that my wife and I have 5 month old twins really slows things down. So, since the development will be slightly slow, I figured it'd be interesting to keep a record of where I'm at, and what progress I am (or am not) making on it.

For starters, I decided my first tasks should be:
  1. Get the GBA code to build on Linux. I did the GBA version working entirely in Windows, but I've recently switched over to mostly using Linux, so the build framework needed to work in Linux.
  2. Clean up the messy/ugly bits. There were plenty of those, and plenty of places where I built in dependencies on gameboy hardware at too high a level. This, I thought, would be the perfect time to fix those.
  3. Change just enough stuff to get it to compile and run, in some fashion, on the DS.
I've managed to get 2 out of the 3 taken care of. Getting it to build on Linux wasn't too hard. The toolchain was all based on gcc and MinGW stuff, so it was perfect for moving to linux. The hardest parts were some stupid places where I had been abusing windows' case insensitivity, some issues with the line break differences between *nix and windows, and a few of the graphics/level conversion tools that had been compiled as windows command line programs. Some of those tools I recompiled in linux, and one (which I could never find the right linux libraries for) I just run under wine.

The cleaning up of messy bits then got started. And after a few hours of starting to move stuff around, my eyes started glazing over, and I realized I could spend forever just on this task. Once you start trying to get something perfect, you could spend years on it. And with the twins, years of development time is something I don't really have at the moment. So after awhile, being the pragmatist that I am, I decided to leave the ugly code alone, and just go on with things.

That left converting it over to run on the DS. This was annoying, but not too bad. After an hour or two with twiddling with my makefile, and a few more hours of doing mass amounts of vim regular expressions to replace the GBA constants, memory locations, and functions with their DS counterparts, the thing compiled and ran. Most of the graphics are completely messed up, and there's no sound, but it runs! Next step: getting the graphics fixed.