tag:blogger.com,1999:blog-69974405516100605602024-03-13T09:53:57.946-05:00Nathan's Development BlogI started by blogging the process of porting my homebrew game <a href="http://www.tolberts.net/anguna">Anguna</a> from the Gameboy Advance to the Nintendo DS. Now, my random thoughts on development and chronicling whatever hobby project catches my fancy. Nathanhttp://www.blogger.com/profile/14248157226095069791noreply@blogger.comBlogger238125tag:blogger.com,1999:blog-6997440551610060560.post-16489060148897025652020-04-21T23:01:00.001-05:002020-04-21T23:01:15.451-05:00NES AngunaWell, I had a little bit of time still, while Frankengraphics is finishing up her game Project Blue, to have a little downtime on Halcyon, so I decided to see what kind of side project I could do in just a handful of weeks, but would be a full-sized decent game.<br />
<br />
Because I tend to go big (Anguna, Halcyon), or small (cheesy competition games like Robo-Ninja Climb), but I rarely do anything medium sized. Which means that despite being part of the nes homebrew community for around 4 years now, I still don't have any games released on cartridge.<br />
<br />
So I started brainstorming -- what's something I could make and finish quickly, but is worthy of a cartridge release?<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-HjmEVeUvkLM/Xp_BYFKFKdI/AAAAAAAAqf8/xwwIa4CwsbovSa6mPPhJ38yUv0rmTYqkQCLcBGAsYHQ/s1600/again.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="348" data-original-width="480" height="290" src="https://1.bp.blogspot.com/-HjmEVeUvkLM/Xp_BYFKFKdI/AAAAAAAAqf8/xwwIa4CwsbovSa6mPPhJ38yUv0rmTYqkQCLcBGAsYHQ/s400/again.png" width="400" /></a></div>
<br />
<br />
What I came up with is Anguna. <i>again. </i>But what would make this port fun is to see how much of the Atari version I could reuse. By remaking the same game, reusing the Atari code and world definitions, could I throw the game together in record time?<br />
<br />
Both the NES and the Atari use the 6502, and the NES has 16 times as much RAM. So in theory, I could take the Atari code and plot it almost directly into the NES, and the game logic would run. I just wouldn't have any I/O. <br />
<br />
So...I/O. That means graphics. I would need graphics for this port -- reusing the Atari graphics wouldn't cut it. With some experimentation, I found that most of Chris's sprites from the GBA Anguna would work if I converted them to be 3-color sprites instead of 15 colors, and cut the number of animation frames in half. The background graphics weren't going to work well though, because of the color use and assumption of a foreground layer that you could walk behind. But I found some nice zelda-like art on opengameart.com that looked like it would work, so I figured I had everything I needed to try to port this game as quickly as possible.<br />
<br />
I also decided to keep it mostly secret. (which is why I've been quiet here). I've said a lot about Halcyon, but I wanted to free the freedom on this project to quit halfway through without feeling judged.<br />
<br />
<br />
<blockquote class="twitter-tweet">
<div dir="ltr" lang="en">
A comparison of the Gameboy Advance, Atari 2600, and upcoming NES versions of Anguna.<a href="https://twitter.com/hashtag/NESDev?src=hash&ref_src=twsrc%5Etfw">#NESDev</a> <a href="https://t.co/ttM9QnWl9R">pic.twitter.com/ttM9QnWl9R</a></div>
— nathantolbert (@nathantolbert) <a href="https://twitter.com/nathantolbert/status/1252441247249133569?ref_src=twsrc%5Etfw">April 21, 2020</a></blockquote>
<script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"></script>
<br />
I won't go into too many details about the development process (because I'm lazy), but some points of interest:<br />
<br />
<ul>
<li>Bringing the main engine in went without a hitch. Just a few hours of converting assembler syntax, I had a no-graphics proof of concept running.</li>
<li>Replacing the graphics code for sprites was pretty quick and easy, thanks to enough tools that I'm comfortable with from other small projects. Actually wrangling and preparing each sprite (by loading the GBA version, playing with the palettes, rearranging tiles to make it fit in the reduced space, etc), took a good bit of effort. Particularly for the main character sprite.</li>
<li>Collisions took some time to get working, because I had depended on hardware collisions on the Atari, but the NES doesn't have any hardware support for collision detection. Collision logic wasn't difficult, but took some re-writing.</li>
<li>While the overall room contents stayed the same, room layouts had to change from the Atari layouts to accommodate the NES graphics. To design the room layouts, I pulled in the whole map tooling/pipeline from Halcyon. This was a bit of overkill, as these are non-scrolling small maps, but it was quick and easy, which was the goal. </li>
<li>Doors (opened by killing enemies, or that close behind you) took longer than I expected. I had weird specific Atari code to handle them using various atari graphical objects (the atari "ball" was used for asymmetrical doors), and that had to be replaced by code that would update the NES nametables on the fly when you opened or closed doors.</li>
<li>One of the things that took the longest was the "glow" that surrounds the player in dark rooms. This was a pain to get right, and still looks a little janky. I just rewrite the background tiles as you move around the room. It was annoying enough to program that I almost ditched it, but I felt like it was one feature that distinguishes the game from a NESMaker game, so it was worth keeping in place.</li>
<li>I hired Thomas Cipollone to compose music for it, which was a great decision. He cranked out a handful of good melodies to make the whole thing sound good. </li>
<li>Beyond that, the other thing that took time was just reworking all the extra things: title screens, menus, subscreen, status bar, ending sequence. All that had to be completely rewritten, and all that stuff takes longer than you'd like.</li>
</ul>
That all said, I started somewhere around Christmas, and the game is "done" and ready for testing now in April. So a full adventure game in 4 months of spare time isn't too shabby.<br />
<br />
Next steps are to test like crazy, polish and debug anything that's too broken, and then figure out how I want to go about publishing and selling carts. Then back to Halcyon!Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-23202884877368900222019-12-11T22:27:00.004-06:002019-12-11T22:34:28.421-06:00NNNNNN in 12 hoursI recently announced my NESdev competition entry for this year: <a href="http://www.bitethechili.com/nnnnnn/">NNNNNN</a>, a multiplayer NES port of the Gravitron from <a href="https://store.steampowered.com/app/70300/VVVVVV/">VVVVVV</a>. Now that it's done, I thought I'd share about the development process.<br />
<br />
<h3>
<span style="font-size: large;">What to make?</span></h3>
<br />
The first question I had was what to make for the competition? Because I'm focusing most of my effort on Halcyon these days, I wanted to think of a game that I could develop really quickly, but still be fun. Treating it like a short game jam, I wanted something that I could take from start to finish in a week's worth of evenings, but still be a reasonably fun game. Honestly, this was the hardest part of the process, which I've been mulling in my brain for months. I have a few ideas (some of which I still might save for next year), but all of them are things that either don't sound fun right now, or would take just a little more work than I'm prepared to invest.<br />
<br />
But about 2 weeks ago, it suddenly dawned on me that a NES port of the Gravitron would be tons of fun to play (at least in my opinion), really easy to write, and light on assets (ie I wouldn't need to spend ages fussing with finding new graphics). The only real problem was IP -- what happens if Terry Cavanagh finds out and shuts down my effort? I debated whether to get permission ahead of time, or to wait and ask forgiveness. But decided that regardless, I'd start on the project and see what I could come up with.<br />
<br />
<h3>
<span style="font-size: large;">Research</span></h3>
<br />
Step 1 is probably the easiest part. Play the game a bunch, and take some notes, to see if it would be as simple as I remembered. There's some Youtube videos out there of people playing the gravitron, which is helpful, and there's a free dedicated Gravitron-only app for mobile. So after spending 20 minutes looking at how the game worked, I was more convinced than ever that I could pull it off.<br />
The most important part of this research bit was thinking through how to make the game match the NES restrictions. The important bits (the top and bottom borders and the timers at the top) could easily be made with background tiles, and the character and obstacles are obviously sprites. There's not too many colors, and not too many sprites on any given horizontal line, which are normally two of the major things that are hard to convert to the NES.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-SBkDVaqCJbU/XfE02tRLjGI/AAAAAAAAlOI/8BbDBQseXZEwfwzfvL1A5KPW2cXsYWBXgCLcBGAsYHQ/s1600/gravitronReal.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://1.bp.blogspot.com/-SBkDVaqCJbU/XfE02tRLjGI/AAAAAAAAlOI/8BbDBQseXZEwfwzfvL1A5KPW2cXsYWBXgCLcBGAsYHQ/s400/gravitronReal.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">A screenshot of the Super Gravitron from my phone</td></tr>
</tbody></table>
The aspect ratio was going to be a problem. The original gravitron is really short and wide, which is problematic for the nearly-square nes screen. I'd just have to make do with making a smaller play area (which makes the game a little harder), and going for it. I was also a little concerned about the animated backdrop. Animated backdrops can be difficult on the NES, and it wasn't completely obvious how I'd be able to pull that off. But I figured that this wasn't a show-stopper, so I'd move forward and figure it out when the time came.<br />
<br />
<h3>
<span style="font-size: large;">Getting Started: the easy stuff</span></h3>
Now the race has begun, and I wanted to see how quickly I could get this thing off the ground. Because the goal was speed more than cleanliness, I started by copying my entire git repository from <a href="http://www.bitethechili.com/superhomebrewwar/">Super Homebrew War</a>, and then going through and doing mass amounts of deleting unnecessary code. Within 30 minutes or so, I had a good skeleton of a project, which would start-and-do-nothing without crashing.<br />
<br />
I loaded a screenshot of the original game into GIMP and extracted the player and obstacle graphics, and fairly quickly coded up the majority of the main character's behavior in C. Because the player does <i>nothing</i> but move left and right, and uncontrollably bounce, this was another simple process. I pulled in some existing bitmap fonts (stolen from another old NES game, which, as it turns out, is legal in the US, because bitmapped fonts aren't eligible for copyright!) Using NES Screen tool, I sketched out the general layout of the screen, and had the very basics of the game going without a hitch.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-XBFlamWrA6g/XfE3Qi34DtI/AAAAAAAAlOU/8Qtj4vk1N5ILzxJaApbXAr98C3k1uPp3wCLcBGAsYHQ/s1600/step1.gif" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="446" data-original-width="476" height="298" src="https://1.bp.blogspot.com/-XBFlamWrA6g/XfE3Qi34DtI/AAAAAAAAlOU/8Qtj4vk1N5ILzxJaApbXAr98C3k1uPp3wCLcBGAsYHQ/s320/step1.gif" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The easy parts are done! Now I have to start using my brain....</td><td class="tr-caption" style="text-align: center;"><br /></td></tr>
</tbody></table>
The game also needed music, so I copied over the <a href="https://github.com/gradualgames/ggsound">ggsound library </a>that I've used in previous projects. I had a techno-sounding song lying around that I had previously commissioned from a composer known as Chip Jockey (I had bought the song without having any idea what I'd use it for), so I plugged it into the game, and it sounded great.<br />
<br />
<h3>
<span style="font-size: large;">Next: Obstacles</span></h3>
<br />
I've been doing enough NES game development recently that this type of thing is fresh in my brain and easy enough to churn out. The concept of a handful of obstacles, which merely march across the screen with a simple animation, was really simple. The harder part is figuring out the spawn patterns of the enemies. Back to research mode!<br />
<br />
I watched a few videos of the game, and made some notes. The enemies clearly come out in some pre-defined patterns, which seems to randomly selected. That's enough information for now.<br />
<br />
<br />
But now I actually have to start using my brain. How do I want to store and process the spawn patterns? I decided on each pattern being a list of pairs of bytes, with each byte being a list that tells enemies spawning that frame, and a timer until the next frame:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">pattern1: </span><br />
<span style="font-family: "courier new" , "courier" , monospace;">.byte %10010000, 10 ;First bit is left or right side</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">.byte %10001000, 10 ;bits 2-6 are the Y position of which<br />.byte %10000100, 10 ;enemy to spawn. So this will spawn</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;">.byte %10000010, 40 ;4 enemies in a diagonal line, 10 frames<br />.byte $ff ;apart. The $ff marks end of the pattern</span></span><br />
<br />
This is the first part of the process that took any real debugging and work, making sure my bit shifts and comparisons were correct for each byte in the pattern array. But with that in place, I had enemies spawning. Using the <a href="https://atariage.com/forums/topic/71120-6502-killer-hacks/page/3/?tab=comments#comment-1054049">famous fast rectangle collision routine</a> from AtariAge user Supercat, collisions against the player were simple and fast. Although I only had a couple of test patterns, and the timing and speed of everything was way off, and hitboxes needed to be adjusted, I had the bones of the game in place, after 4 or 5 hours.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-3sMaC_emDCM/XfFA6apE7fI/AAAAAAAAlOg/XuoECOPqzRkKwf8pGPFVuaOxYySvsmmsACLcBGAsYHQ/s1600/step2.gif" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="504" data-original-width="508" height="317" src="https://1.bp.blogspot.com/-3sMaC_emDCM/XfFA6apE7fI/AAAAAAAAlOg/XuoECOPqzRkKwf8pGPFVuaOxYySvsmmsACLcBGAsYHQ/s320/step2.gif" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Fans of the real game will notice that I have the sprite falling backward (head-first). I didn't notice until significantly later in the process.</td></tr>
</tbody></table>
<br />
<h3>
<span style="font-size: large;">Backgrounds</span></h3>
<br />
Now things start to get interesting. It's time to see what I can do about the background. First is the timers at the top of the screen. It's easy to put the "current time" and "best time" text as part of the background graphics, as well as the times. But it was also time to think about the animated backdrop. I did a lot of thinking about this during the rest of development. The big issue is that the NES can only change a handful of background tiles per frame, because you (generally) can only write to the background safely during vblank. There's not enough to time to rewrite each background tile. And really, using basic 8x8 pixel background tiles is messy for doing a big animated backdrop like the one in the gravitron.<br />
<br />
Eventually I decided that I needed to take advantage of the CHR-RAM and redraw the actual tiles every frame.<br />
<br />
For those non-NESdev folks reading this, the NES handles graphics by taking pre-made 8x8 tiles, and arranging them on the screen. A lot of games use CHR-ROM, which means the actual tile graphics are on a ROM chip in the cartridge, and can't be changed. Others use a RAM chip for the graphics, and copy the graphics from the game's main program ROM to the video ram at run time. There are advantages and disadvantages of both methods, but by using CHR-RAM, I can, on every single frame, redraw the handful of 8x8 tiles that make up the backdrop. <br />
<br />
I sketched it out and determined that I'd need to redraw 16 different backdrop tiles in order to animate this properly. That's 256 bytes of data that would need to be written during vblank, (about 1800 free cycles after OAM DMA finishes) That's doable, but would definitely require me to optimize things. First, I decided that the millisecond part of the timers (At the top) could be sprites, so I wouldn't have to update them as background elements, saving a little time. (I could cheat for the seconds and minutes, and just skip updating the backdrop when I update them, which wouldn't be noticeable at one missing frame every second)<br />
<br />
Then, I needed to optimize and unroll all the background tile updated during vblank. A naive way of re-drawing a tile during vblank might use a loop like this:<br />
<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">;first tell the NES what video ram address you </span><br />
<span style="font-family: "courier new" , "courier" , monospace;">;want to write to.</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> lda #TILE_VIDEO_RAM_ADDRESS_HI_BYTE ;2</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> sta PPUADDR ;4</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> lda # </span><span style="font-family: "courier new" , "courier" , monospace;">TILE_VIDEO_RAM_ADDRESS_LO_BYTE ;2</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> sta PPUADDR ;4</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br />;then do a loop to write from a ram buffer to the tile</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> ldx #16 ;2</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">top:</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> lda buffer,x ;4</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> sta PPUDATA ;4</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> dex ;2</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> bpl top ;3 </span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
That's 13 cycles per byte, plus 14 cycles of setup (minus 1 cycle for the last bpl). If I did my math right, that's 221 cycles per tile, or over 3500 cycles. About double what I have available. But hardcoding addresses and unrolling things saves a ton of time:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;"> lda #TILE_VIDEO_RAM_ADDRESS_HI_BYTE ;2</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> sta PPUADDR ;4</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> lda # </span><span style="font-family: "courier new" , "courier" , monospace;">TILE_VIDEO_RAM_ADDRESS_LO_BYTE ;2</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> sta PPUADDR ;4</span><br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;"> lda buffer+0 ;4</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> sta PPUDATA ;4</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> lda buffer+1 ;4</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> sta PPUDATA ;4</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> lda buffer+2 ;4</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> sta PPUDATA </span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> ;etc, 13 more times</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"></span><br />
<br />
<br />
Using this method, it's 140 cycles per tile. That still comes to 2240, which is too much, but we're getting there. Luckily, due to the pattern of how the backdrop updates, a few of our tiles are the same thing repeated vertically, so we can optmimize them further:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;"> lda #TILE_VIDEO_RAM_ADDRESS_HI_BYTE ;2</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> sta PPUADDR ;4</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> lda # </span><span style="font-family: "courier new" , "courier" , monospace;">TILE_VIDEO_RAM_ADDRESS_LO_BYTE ;2</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> sta PPUADDR ;4</span><br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;"> lda buffer ;4</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> sta PPUDATA ;4</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"> sta PPUDATA ;4</span></span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"> sta PPUDATA ;4</span></span></span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"> sta PPUDATA ;4</span></span></span></span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"> sta PPUDATA ;4</span></span></span></span></span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"> ;etc, 11 more times </span> </span> </span> </span></span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;"> </span> </span><br />
That's only 56 cycles! It's enough to balance things out, and let us fit into the limited time during vblank.<br />
<br />
To verify, I used Mesen's event viewer to see if all of my writes to video memory occurred during vblank, and it looks good!<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-w0WiS1uun40/XfFYz014oCI/AAAAAAAAlOs/waejhol0KEY8c0Kx14weP_YJpdg9XoNpwCLcBGAsYHQ/s1600/eventViewer.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="614" data-original-width="931" height="262" src="https://1.bp.blogspot.com/-w0WiS1uun40/XfFYz014oCI/AAAAAAAAlOs/waejhol0KEY8c0Kx14weP_YJpdg9XoNpwCLcBGAsYHQ/s400/eventViewer.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">All those dots at the bottom are writes. And they're all in that big blank space at the bottom, where they belong.</td></tr>
</tbody></table>
<br />
Now let's see what the tiles look like when animated:<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-vY8d0_tsHt4/XfFaCyVX7_I/AAAAAAAAlO4/ogPmO9p7sQk-OYaoDEYsdCDhzbPI05j0wCLcBGAsYHQ/s1600/bgAnim.gif" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="116" data-original-width="532" height="86" src="https://1.bp.blogspot.com/-vY8d0_tsHt4/XfFaCyVX7_I/AAAAAAAAlO4/ogPmO9p7sQk-OYaoDEYsdCDhzbPI05j0wCLcBGAsYHQ/s400/bgAnim.gif" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Trippy!</td></tr>
</tbody></table>
<br />
So the finished effect, which probably took more time than any other single element of the game, at about 3 hours, looks like this:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-OUTn-bt60uY/XfFbsTz4Y1I/AAAAAAAAlPQ/_7rmU1BBcYEsWa-NxGQ81ex3WmrXMObywCLcBGAsYHQ/s1600/bg.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="446" data-original-width="478" height="298" src="https://1.bp.blogspot.com/-OUTn-bt60uY/XfFbsTz4Y1I/AAAAAAAAlPQ/_7rmU1BBcYEsWa-NxGQ81ex3WmrXMObywCLcBGAsYHQ/s320/bg.gif" width="320" /></a></div>
<br />
<br />
<br />
<h3>
<span style="font-size: large;">Making it match the real thing</span></h3>
<br />
At this point, I felt like I had all the features in place, so it was time to play it back to back with the real thing and see what needed to be adjusted. I knew my speeds/timings were all off, and I had none of the real patterns from the game.<br />
<br />
So I opened up a youtube video of someone playing the game, resize it to match my emulator size, and started adjusting things to match the speed. After a few minutes, I had the movement speeds all close enough.<br />
<br />
There were a few other minor things that I had forgotten (the enemies change color, and the top and bottom trampoline borders changed color when you hit them). These were fairly simple changes based on how the NES uses palettes.<br />
<br />
The one important thing that I had forgotten was warnings about upcoming enemies. These are small arrows that appear on the sides, and warn when an enemy is about to spawn there. They're crucial to surviving, particularly if you take advantage of wrapping around the screen to dodge obstacles.<br />
<br />
Unfortunately, adding these warnings wasn't quite as straightforward as everything else in the process. Because they appear about half a second before the actual obstacle, they could be part of the same pattern as currently-spawning obstacle, or the next pattern, or even (for very short patterns), 2 patterns ahead of the current obstacle.<br />
<br />
Previously, I had been only tracking the "current" pattern, and when that pattern finished, I'd randomly pick a new one. But now I had to keep a short queue of patterns. The pattern a half second in the future, the currently spawning pattern, and any patterns in between. So I basically have to have a few things:<br />
<br />
- A queue of pattern ids<br />
- A pointer to where in the queue we're at for the "warnings". <br />
- A pointer to where in the queue we're at for the "current"<br />
- A pointer to where within the "warning" pattern we're curretly at<br />
- A pointer to where within the "current" pattern we're curretly at<br />
<br />
It wasn't overly complicated, but was just enough logic and fiddly indirection that it actually took some real debugging, as opposed to most of the rest of the project.<br />
<br />
After I finally had that working, I sat down with a piece of paper and the youtube video (set to 1/4 speed), and wrote down all the enemy patterns that spawned. To this day, I'm still not sure whether every single pattern was pre-generated, or if some of them were completely randomized. But to simplify the game, I limited it to a handful of the patterns that I saw during the first few minutes of the video. Transcribing them to the source code took another hour, but after that, the game was in pretty good playable shape! At this point (around 9-10 hours), I was confident that I had a viable entry for the competition.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-J7A7zk4Frr8/XfFuoPLh8AI/AAAAAAAAlPo/LMtGHall3own-KcwR2ckpL0pHe1UvYUmwCLcBGAsYHQ/s1600/gameplay.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="446" data-original-width="478" height="298" src="https://1.bp.blogspot.com/-J7A7zk4Frr8/XfFuoPLh8AI/AAAAAAAAlPo/LMtGHall3own-KcwR2ckpL0pHe1UvYUmwCLcBGAsYHQ/s320/gameplay.gif" width="320" /></a></div>
<br />
<br />
This was also the point where I decided that I should probably check about getting actual permission for this project. With much fear and trembling, I send Terry Cavanagh a message (on twitter) with a screenshot, telling him that I had made the game, and asking if I could release the rom. His response: "Sure!" WHEW. That worry was resolved.<br />
<br />
<h3>
<span style="font-size: large;">Title Screen</span></h3>
<br />
Next up was a title screen. I lucked out by the fact that I had (by complete accident) included my font in just the right place in video memory so that the tile id of each glyph was the same as its ASCII code. That meant that I could actually use C strings for text! I whipped together some routines in C to write text to the screen (which were horribly slow, but because they run during couple frames of black screen before the title appears, nobody would notice). With those, I threw together a primitive title screen, but it was ugly and needed something.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-j4t55-CbGxA/XfFwSh2QfLI/AAAAAAAAlP4/AK9kUhoOkKAsLe_qAyR8hmqreJlf0pfAACLcBGAsYHQ/s1600/title.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="446" data-original-width="478" height="298" src="https://1.bp.blogspot.com/-j4t55-CbGxA/XfFwSh2QfLI/AAAAAAAAlP4/AK9kUhoOkKAsLe_qAyR8hmqreJlf0pfAACLcBGAsYHQ/s320/title.gif" width="320" /></a></div>
<br />
<br />
The title screen for the mobile Super Gravitron had these cool-looking little rectangles flying around in the background, so I stole that idea. Using sprites, I generated a bunch of them to randomly fly around. And because they have no logic other than flying across the screen before reappearing randomly somewhere else, they were painless to create.
<br />
<br />
<h3>
<span style="font-size: large;">Multiplayer</span></h3>
<br />
Now really what I had been hoping to add all along (but not sure if I'd have time) was multiplayer. I'm a huge fan of 4-player simultaneous games (which are sorely lacking on the NES). And the chaos of this game would be perfect for multiplayer madness.<br />
<br />
For the most part, adding extra player characters was straightforward, but two things are worth mentioning.<br />
<br />
First, I did something goofy for the main character logic -- instead of handling each character's input and updates in a loop, I copied the player update logic 4 times. Why? Becuase I wrote it in C, but the 6502 C compiler is notoriously bad about handling loops where you loop through elements repeatedly in arrays. Instead of storing the loop counter in a register (x or y), it stores it in a variable, then reloads it to x every time you try to look up a value in an array. <br />
<br />
So to keep the performance from being stupid, I really needed to either rewrite the routine in assembly, or copy/paste the routine 4 times and let it hard code the addresses. I chose copy/paste because I was at around 11 hours and wanted to be finished.<br />
<br />
The other painful thing was handling player deaths. When a player dies, everything pauses, the player changes color for half a second, then the remaining enemies do a cool extra-fast flyaway, then it starts over. Somehow, even though it seems really simple, I had a difficult time getting the logic right to handle deaths for all 4 players. I just kept introducing bugs when trying to manage the "make the player stop moving and change color while dying" state for some players but not all players were dead. I don't know why I had so much trouble with it (the logic isn't complicated!) but I wasted a good hour on it. <br />
<br />
<h3>
<span style="font-size: large;">Finished?</span></h3>
<br />
Now everything was done. Except one small thing. There's a sound effect that occurs when you die, and I wanted it in the game. And I wanted it to sound somewhat like the real thing. And I'm terrible at making sound effects.<br />
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-hCJatKVTSD4/XfFzl47zPgI/AAAAAAAAlQU/v5DaOeD1xWceXH6l-XCRSPdaM6ypJwNvgCLcBGAsYHQ/s1600/famitracker.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="604" data-original-width="672" height="287" src="https://1.bp.blogspot.com/-hCJatKVTSD4/XfFzl47zPgI/AAAAAAAAlQU/v5DaOeD1xWceXH6l-XCRSPdaM6ypJwNvgCLcBGAsYHQ/s320/famitracker.png" width="320" /></a></div>
<br />
<br />
There's a
great program out there, FamiTracker, for working with NES audio. But
I'm not very good at using it. And certainly not good at trying to
reproduce an existing sound. But there was no other way to do it, so I
used my last hour fiddling with instrument settings in FamiTracker until
I got this sound reasonably close to the real thing.<br />
<br />
<h3>
<span style="font-size: large;">Finished!</span></h3>
<br />
So, after about 12 hours of work (spread out throughout the week), I was done! I loaded it onto a cart (which is always scary, as it usually never works right on hardware), and (thanks to the accuracy of Mesen), it worked on the first try! I gathered my family around and forced them to test it out with me. Our family average was somewhere around 3 seconds per life. Success!<br />
<br />
All in all, I'm pretty happy with how it turned out. There's definitely some things that could be slightly closer to the real thing (better analysis of how the patterns were generated, making sure speeds matched the real thing, making fonts match, etc), but I feel like it captures the fun energy of the original game and successfully adds a multiplayer element. And I managed to pull off a reasonable competition entry in a week.<br />
<br />
<a href="http://www.bitethechili.com/nnnnnn/">You can download the game here</a>. If you try it, let me know your best time!<br />
Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com1tag:blogger.com,1999:blog-6997440551610060560.post-15960524286337541632019-11-20T12:14:00.001-06:002019-11-20T12:14:10.833-06:00Seeker missiles revisitedIf you follow me on twitter, <a href="https://twitter.com/nathantolbert/status/1098088201028292609">you saw back in February</a> that I was attempting to get enemy-seeking missiles working correctly. At the time, I felt like I got all the bugs worked out, and they were good to go. But now that I'm at the point in content-creation of the game that the player actually GETS the seeker missiles, I'm not quite satisfied with how they behave, so it's time to revisit them.<br />
<br />
The thing about seeker missiles (or any sort of "move an entity directly toward another entity at a set speed) behavior is that it's not as simple to get right on the 6502. (at least if you care at all about performance and would like to get more than 1 frame per second in your game).<br />
<br />
The reason why is because of trigonometry.<br />
<br />
There are two parts to making good enemy-seeking missiles. First is to select which enemy to target (usually the closest, although you might have a priority system instead to that they always target the most dangerous enemy or something. For this post, I'll assume we're targeting the closest enemy) <br />
<br />
The way you'd do this on modern hardware is by first finding the closest enemy by using the Pythagorean theorem -- distance is the square root of x<sup>2</sup> * y<sup>2</sup>. You can even omit the square root if you only care about finding the closest -- just find the sum of squares of the x and y distances for each enemy, and the closest is the smallest.<br />
<br />
Unfortunately, arbitrary multiplication is slow on the 6502. Without a multiply instruction, you're stuck adding in a loop, (or bit shifts for multiplications times a power of 2). If the x distance is 74, that means adding 74 to itself 74 times in a loop.<br />
<br />
That would look something like this (squaring a single 8-bit value into a 16-bit value):<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-size: x-small;"> ldx val ;3 cycles<br /> lda #0 ;2<br /> sta result ;3<br /> sta result+1 ;3 (11 cycles for loop setup)<br />:<br /> lda result ;3 <br /> clc ;2<br /> adc val ;3<br /> sta result ;3<br /> lda result+1 ;3<br /> adc #0 ;2<br /> sta result+1 ;3<br /> dex ;2<br /> bpl :- ;3 (24 per each time through loop)<br /><br /> ;11 + (24 * 74) = 1787 cycles </span></span><br />
<br />
I scribbled that down without checking it, so it may be off by 1 or something, but it's something like 6% of a frame just to do that single 74*74 calculation. Good luck trying to also do y<sup>2</sup>, then computing this for each possible enemy. So we need a better way.<br />
<br />
To be honest, I haven't found a perfect solution for this. Luckily, for this part of the problem, we don't really have to be exact. If the missile targets an enemy that's not exactly the closest, I don't really care. So for this first step, I just compare x + y instead of x<sup>2</sup> * y<sup>2</sup>. Sure, that overestimates how far away enemies are that are more diagonal from the missile, but ... I don't care.<br />
<br />
The more interesting part is the second half of the problem. Doing the math to determine the X and Y components of movement to move the missile toward the target.<br />
<br />
The easiest solution is just to move at full speed horizontally until your x component matches the target, and also move at full speed vertically until your y component matches the target. The problems with this are obvious though: the missile moves too quickly in a straight diagonal, until it gets even with the target in one dimension, then moves straight until it hits it. This is a fairly obviously ugly movement. It's fine in some cases, particularly if the entity is moving fast enough that you don't really pay attention to the movement itself. I used this method in Super Homebrew War when a player gets the "swap everyone's location" power. I didn't have the programmer time or rom space for something more complicated, so I just went with this. The swapping all happens so quickly that you don't really notice that it's ugly.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-cBSNGeLHG6o/XdVVcJR6UdI/AAAAAAAAkMg/B3ZQjDyDtXk14Kikwmt_vwaVQ_i0ELLCACLcBGAsYHQ/s1600/attempt1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="400" data-original-width="640" height="250" src="https://1.bp.blogspot.com/-cBSNGeLHG6o/XdVVcJR6UdI/AAAAAAAAkMg/B3ZQjDyDtXk14Kikwmt_vwaVQ_i0ELLCACLcBGAsYHQ/s400/attempt1.png" width="400" /></a></div>
<br />
Another solution would be to use to a constant TIME instead of constant VELOCITY. If you assume that the missile will always take approximately a second to reach the target, you can divide the x distance into 64 pieces (dividing by 64 is fast with bit shifts), and divide the y distance into 64, and move each by that amount every frame. That will produce a nice even movement, BUT the speed will change based on the total distance traveled. A missile will take the same amount of time to reach enemy A as enemy B, which may be undesired.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-gGliUbM5qyw/XdVWWJsCTmI/AAAAAAAAkMs/aINFRE7O3GA4cs39zJ_capxfQQcv_bFAQCLcBGAsYHQ/s1600/attempt2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="400" data-original-width="640" height="250" src="https://1.bp.blogspot.com/-gGliUbM5qyw/XdVWWJsCTmI/AAAAAAAAkMs/aINFRE7O3GA4cs39zJ_capxfQQcv_bFAQCLcBGAsYHQ/s400/attempt2.png" width="400" /></a></div>
The REAL solution involves trigonometry, which is hard on the 6502. You find the angle (the arctan of y / x). Then to find each frame's x and y components of movement, you use cos(angle) * vel = x. (or sin to find y). It's simple high school trigonometry. Easy if you have a calculator or a modern processor. Not as easy on the 6502.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-OKjBuJAaV7M/XdVYhfu22XI/AAAAAAAAkM4/PIX08rPMWjk7KE2_hWOX3ef7U9QTlbMfgCLcBGAsYHQ/s1600/attempt3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="400" data-original-width="640" height="250" src="https://1.bp.blogspot.com/-OKjBuJAaV7M/XdVYhfu22XI/AAAAAAAAkM4/PIX08rPMWjk7KE2_hWOX3ef7U9QTlbMfgCLcBGAsYHQ/s400/attempt3.png" width="400" /></a></div>
<br />
But for Halcyon, I want things to look nice and polished, so I wasn't happy with either of the two previous solutions. So trigonometry it is.<br />
<br />
For the arctan, I found <a href="http://codebase64.org/doku.php?id=base:8bit_atan2_8-bit_angle">a pre-written routine</a> that does a pretty good fast estimation of arctan that was written by one Johan Forslöf. I don't pretend to understand the math behind it (he claims it simply "uses logarithmic division to get the y/x ratio and integrates the power function into the atan table"), but it does what I need it to do.<br />
<br />
For sin and cos, the trick (like many things on the 6502) is just to use pre-computed tables. If you assume that the angle is a 1-byte value, then there are only 255 possible sin values you have to compute. So you can compute them ahead of time (using a python script or something similar), and just put them into your rom as a lookup table. The cartridge rom that I'm using for Halcyon has plenty of rom space, so a 255-byte table is no big deal. Then to compute the sin, you just look up the precomputed value for any angle. Table lookups are one of the things that the 6502 does best, so this is nice and fast:<br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;">ldx angle ;3</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">lda sinTable,x ;4 (7 cycles total)</span><br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;"></span>For cosine, you can take advantage of the fact that cosine is just sine rotated by one quadrant, so you add (or is it subtract? I never remember) 64 from your angle and use the same table.<br />
<br />
So with these tricks, you can have a pretty nicely functioning enemy-seeking missile, without using too much CPU time.<br />
<br />
Now if I could only figure out why it's not working, and my missile is just bouncing along in a goofy-looking sine wave pattern.<br />
<br />
<br />
<br />
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-gT4e304zfPU/XdVa9i4Ds9I/AAAAAAAAkNE/1r6Nhwj23d0DnKSOEZO7x4KTUlJk1g12QCLcBGAsYHQ/s1600/missile.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="476" data-original-width="512" height="297" src="https://1.bp.blogspot.com/-gT4e304zfPU/XdVa9i4Ds9I/AAAAAAAAkNE/1r6Nhwj23d0DnKSOEZO7x4KTUlJk1g12QCLcBGAsYHQ/s320/missile.gif" width="320" /></a></div>
<br />
<br />
<br />
<br />Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-38457196967473232782019-11-12T22:55:00.003-06:002019-11-12T22:55:54.716-06:00Scrolling glitchFor an 8-way scrolling engine with 4 nametables (ie 4 in-memory screens for the camera to pan across), it's easy to end up with a lot of small glitches in the background. Scrolling diagonally across the seam of screens, it's easy to get some of the PPU memory address math wrong, and end up writing to the wrong spot on the screen. But it might only happen when the screen is aligned exactly wrong, so it can take awhile to find and fix all the glitches.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-m8wZ4WoHY2E/XcuLfzwxxlI/AAAAAAAAkCI/-svsn15A0PYqcFlOlzIOK0AgxI4k9R_AACLcBGAsYHQ/s1600/nametables.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="633" data-original-width="709" height="356" src="https://1.bp.blogspot.com/-m8wZ4WoHY2E/XcuLfzwxxlI/AAAAAAAAkCI/-svsn15A0PYqcFlOlzIOK0AgxI4k9R_AACLcBGAsYHQ/s400/nametables.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The gray outline shows what's actually on screen. The rest is video memory that's not shown.</td></tr>
</tbody></table>
<br />
<br />
Over the past couple of years of working on this project, I've gradually worked through what I thought was every single scroll glitch, and fixed them. <a href="https://mesen.ca/">Mesen</a>, the NES emulator that I use, has some wonderful features for debugging this sort of thing. You can play through the game, and when you notice a rare, hard-to-reproduce bug like this, you can save your game history, then go back and replay the whole history through the debugger, stepping through what you did, rewinding if you went too far, and watching updates as they happen. It's amazing.<br />
<br />
Despite all this, there's one scrolling glitch that, until tonight, I've never been able to reliably reproduce enough to find and fix. It only seems to happen in one particular room, and then, only rarely, even if I do the exact same movement when I enter the room.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-sbqgyXA6Pc8/XcuMHfQHL8I/AAAAAAAAkCQ/-WjnKKhanVwazSsLW3OvZhZugt7IHFA1ACLcBGAsYHQ/s1600/6-12.gif" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="508" data-original-width="512" height="396" src="https://1.bp.blogspot.com/-sbqgyXA6Pc8/XcuMHfQHL8I/AAAAAAAAkCQ/-WjnKKhanVwazSsLW3OvZhZugt7IHFA1ACLcBGAsYHQ/s400/6-12.gif" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The room that gave me all the trouble. Depending on some random numbers, you can end up with 7 enemies going at once, which has the potential to slow things down.</td></tr>
</tbody></table>
<br />
<br />
<br />
Luckily, tonight I caught the glitch in the emulator! I went to quickly hit the "save history" button, and I accidentally pressed "reload save state" and jumped back to some previous point in the game, and lost it. ARRGH!<br />
<br />
<i>But, </i>I had the good fortune of being able to reproduce it again fairly soon after, and <i>this time</i> I managed to save the history, and replayed it over and and over and over again.<br />
<br />
It didn't make any sense -- as I was scrolling diagonally through the room, it would just skip updating the background for a single frame. So instead of writing new tiles at the edge of the screen, garbage tiles would just scroll in instead. I couldn't figure out why it was skipping an update.<br />
<br />
After about the 30th time I replayed it, though, I noticed something. My frame counter was jumping from $1d to $1f when I advanced a single frame. That's not right! <br />
<br />
To make a long story short (too late), I tie some of my scroll logic to the frame counter (to alternate between a few types of updates). In the rare case that too much is happening in a single frame, you can end up with that dreaded NES slowdown, which means that you hit vblank before you finish your frame's logic (ie you get 2 screen draws per logical frame, cutting your game to 30fps instead of 60). During the screen draw interrupt (NMI) is when I advance my frame timer. <br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-tuZ22rmSuyk/XcuMsAiL8hI/AAAAAAAAkCY/AJAraKnJZ684JapMMbZDxMoW1DZfTNnQQCLcBGAsYHQ/s1600/profiling.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="536" data-original-width="512" height="400" src="https://1.bp.blogspot.com/-tuZ22rmSuyk/XcuMsAiL8hI/AAAAAAAAkCY/AJAraKnJZ684JapMMbZDxMoW1DZfTNnQQCLcBGAsYHQ/s400/profiling.png" width="381" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">You can use a grayscale bit in the video settings as a poor-man's profiler. The gray shows how much of my CPU time I'm using this frame.</td></tr>
</tbody></table>
<br />
<br />
So this one single room has enough enemies to very occasionally cause a slowdown (usually only for 1 or 2 frames so I never even notice), BUT during those slowdowns, my frame timer advances twice per logical frame. So my scrolling updater (which depends on that timer) fails.<br />
<br />
It was an easy fix once I realized what was happening -- I now just use a separate counter for the background updates, and only increment it after actually doing an background update. It wastes a byte of ram, but this is the NES, which has a whopping 2k of ram! What's a wasted byte?<br />
<br />
So now, for maybe the 100th time, my git commit message said "Fixed what was hopefully the last scroll glitch." Maybe I'm actually right this time?Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-89824560714376574332019-11-11T22:10:00.003-06:002019-11-11T22:10:51.943-06:00Who needs enemies?One of the things I keep thinking as I'm building content in Halcyon is "does this room really need any enemies?" I mean, the fun of the game is supposed to be about exploration. Enemies just slow that down, right? Or maybe it's just that I'm being lazy and don't want to figure out what enemies to put into a room. Do I need to mix it up for each room, or is it ok to have a few rooms in a row with really similar enemies? Will the players find this room to be boring, too similar to the last room? <br />
<br />
I have no idea. But regardless, it sure is tempting to leave a lot of rooms empty.Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-64310634032373440532019-11-05T21:57:00.000-06:002019-11-05T21:57:08.500-06:00Enemy namesI realized that I'm not all that creative when it comes to enemy names. For my enemy code, each enemy has a textual name that it's referred to in labels, in the map editor, etc.<br />
<br />
Here's a handful of the names so far:<br />
<br />
<ul>
<li>flapper</li>
<li>hopper</li>
<li>walker</li>
<li>gunner</li>
<li>diver</li>
<li>zapper</li>
<li>spitter</li>
</ul>
<div>
And then some that are even more boring:</div>
<div>
<ul>
<li>blob</li>
<li>boss1</li>
<li>boss2</li>
<li>boss3</li>
<li>barrier</li>
<li>fish</li>
</ul>
<div>
Good thing these names are all secret....</div>
</div>
Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-22263345505658505422019-11-02T22:22:00.000-05:002019-11-02T22:32:59.215-05:00What sort of game is this anyway?I've said before that I'm making a Metroidvania. A kind of mash-up of Metroid and Blaster Master in this case. But these days, that Metroidvania label (as well as "rogue-like") gets attached to almost everything under the sun. <a href="https://dead-cells.com/">Dead Cells</a>, a procedurally generated level-based action platformer routinely gets labeled as both a roguelike and a metroidvania (while I would say it is neither). Even among things that actually could accurately be described as Metroidvanias, there's a huge range of variety in what that can mean, and what sort of thing a game really is. So this post is my attempt to answer the question: "What sort of metroidvania is Halcyon?"<br />
<br />
To answer that, there's a number of different aspects that are worth discussing: non-linearity, challenge, and reward structures. Let's dive in.<br />
<br />
<h2>
Linearity</h2>
<br />
The first question is how linear is the game? At the far non-linear side of the spectrum are games with a big open world where you can really go anywhere in any order you want. The recent NES homebrew game Lizard by Brad Smith is a good example of this, as is Super Pitfall. There's a big giant world, and it's yours to explore. You can really do things in just about any order you want.<br />
<br />
<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-DtfPXQH4UVc/Xb5DvYVPtfI/AAAAAAAAjx0/18WlUPKzHeglr0JUYGvfwDquCgwYpPp4gCLcBGAsYHQ/s1600/superPitfall.gif" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="224" data-original-width="256" height="350" src="https://1.bp.blogspot.com/-DtfPXQH4UVc/Xb5DvYVPtfI/AAAAAAAAjx0/18WlUPKzHeglr0JUYGvfwDquCgwYpPp4gCLcBGAsYHQ/s400/superPitfall.gif" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">You can go any way you want in Super Pitfall, but mostly you just die. A lot.</td></tr>
</tbody></table>
<br />
<br />
Then there are games that have a little more linearity to them: the original Metroid is a good example. You can't really progress until you find the missiles and bombs. But when it comes down to it, you can go kill Ridley or Kraid in either order, without ever finding a number of the other powerups. There may be a recommended order to do things, but you're not always doing everything in the same order. (The original Legend of Zelda was very similar in it's non-linearity. There was an order to the dungeons, and some of them required items from other dungeons. But there's not much preventing you from doing some of them out of order(and we did so when we were first exploring the game as kids....if you got stuck on level 6, you went on to try level 7 instead).<br />
<br />
Next up are games that are slightly more linear, like Super Metroid. Advanced players know how to exploit the game and can do things out of order, but the average player will end up doing things in mostly the same order every time. Sure, there's plenty to explore, and plenty of secret passages that you may or may not find. But the general order ("find this powerup to open the way to the next section. Find another and then you can go back through that other section") is generally the same for each player. (assuming they aren't purposely going back and trying to break the game sequence).<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-3eOfcoGoRhQ/Xb5EV2PN2vI/AAAAAAAAjx8/dX6ZGw8lO5ctvvpH33t3vbZlGJYvXLRwQCLcBGAsYHQ/s1600/com.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="1131" data-original-width="1548" height="291" src="https://1.bp.blogspot.com/-3eOfcoGoRhQ/Xb5EV2PN2vI/AAAAAAAAjx8/dX6ZGw8lO5ctvvpH33t3vbZlGJYvXLRwQCLcBGAsYHQ/s400/com.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Despite the awesome Metroidvania-style map, Circle of the Moon was a huge disappointment to me. Each colored section was a distinct "level" that you had to play through in order.</td></tr>
</tbody></table>
<br />
<br />
<br />
At the far linear end of the Metroidvania genre are games like Castlevania: Circle of the Moon. Although it's one big world, it's divided into clear level-like areas. You beat the boss of the first to get an item that lets you explore the next. Beat that next level, and you get rewarded with the power that lets you explore the 3rd section. And so forth. These are the least interesting to me: although they pretend to be big contiguous-world metroidvanias, they're really not much different than a linear level-based game.<br />
<br />
Where does Halcyon fit into this spectrum? I'm attempting to place it somewhere between Metroid and Super Metroid. I'd like the player to have a little bit more freedom in the order of things than Super Metroid, but I found Metroid to be a little too painful in terms of a beginning player not having any idea of where to go. A few parts of Halcyon will require you to go get powerup X right now to continue, but there will be other parts of the game where you can decide which way you want to go, with a number of different areas to explore and routes to take.<br />
<br />
As a side tangent -- the world in Halcyon is a euclidean contiguous world. There are no warps between worlds or front/back (like Goonies 2) or weird non-euclidean layouts like Blaster Master. It (like Metroid) is just one large mappable world.<br />
<br />
<h2>
Difficulty</h2>
<div>
<br /></div>
I've made some hard games in the past. My friends who have played my
original <a href="http://www.bitethechili.com/roboninja/">Robo-Ninja for Android</a> may have had some unkind words about me
at times. Robo-Ninja (also a metroidvania) was designed to be HARD. The
world wasn't big or complicated, but each room was a challenge.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://1.bp.blogspot.com/-0_fVPZaWlGY/Xb5EyN6b12I/AAAAAAAAjyE/cUDTMLL425cgZ7plNIr9uNtJkTSUkKifACLcBGAsYHQ/s400/roboninja.png" style="margin-left: auto; margin-right: auto;" width="400" /></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Robo-Ninja -- possibly the world's first tap-to-jump metroidvania?</td></tr>
</tbody></table>
<br />
<br />
<br />
When I'm talking about a difficulty spectrum here, I'm talking not about how big or confusing the world is, but how difficult the enemies, obstacles, and other "platformer skill" elements are. The original Metroid was a pretty tough game, particularly at the beginning when you didn't have many energy tanks). Lizard is HARD -- you die A LOT. Blaster Master had a weird difficulty curve -- some levels were really easy, but some levels (and bosses) made you want to punch the screen. <br />
<br />
<span id="goog_1119912034"></span><span id="goog_1119912035"></span><span id="goog_1119912036"></span><span id="goog_1119912037"></span><br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-fZIVqlxFF_A/Xb5FjzAkISI/AAAAAAAAjyY/zpOhrtI1g6kmE0VJedzpWT8HCrRHOCuLgCLcBGAsYHQ/s1600/giantEnemyCrab.jpeg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="360" data-original-width="480" height="240" src="https://1.bp.blogspot.com/-fZIVqlxFF_A/Xb5FjzAkISI/AAAAAAAAjyY/zpOhrtI1g6kmE0VJedzpWT8HCrRHOCuLgCLcBGAsYHQ/s320/giantEnemyCrab.jpeg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The game was fun and easy-going until you had to fight THIS GUY.</td></tr>
</tbody></table>
<br />
<br />
Super Metroid, on the other hand, wasn't a very hard game. There were a couple parts that were tricky, but you really didn't die all that much. The fun of the game was exploring and figuring out where to go, and looking for secrets. The enemies were mostly just there to keep you on your toes. <br />
<br />
Symphony of the Night was also like that. Other than that one boss (I don't remember his name, but I'm sure if you played it, you know who I mean), it was a fairly easy game, mechanically. The joy of the game wasn't in the finesse of fighting and jumping.<br />
<br />
Unlike Robo-Ninja, this time I'm going easy. Halcyon is (hopefully) going to be more like Super Metroid or Symphony of the Night in this regard. If I can get the difficulty set the way I want, you might die a couple times, just enough to keep you interested and careful. But the combat isn't supposed to be hard. Anybody with moderate video game skill should be able to take their time and explore and enjoy the game.<br />
<br />
<h2>
Reward</h2>
<br />
One of the things that I want from Halcyon is for exploration to feel rewarding. Super Pitfall had a giant world with lots of areas to explore, but it was just a vast mostly-empty world. There was nothing that made you feel rewarded when you went this way vs that way. You'd explore a route and die, without having found anything interesting. You'd try a different route and die. You never seemed to make any sort of progress.<br />
<br />
Lizard also had a fairly brutal reward system (in my opinion). There were coins scattered around the world, but you had no idea what they were for, and you'd lose them if you died. If you explored a lot, you could find a new power (a new lizard costume), but you didn't get to keep it. Even defeating a boss just mostly felt confusing to me. These were all purposeful design decisions that Brad made (as opposed to just poorly-designed flaws), but playing it helped me figure out what sort of feeling of reward I wanted to put into Halcyon.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-BzRxLSuwVpo/Xb5GFXSAU_I/AAAAAAAAjyg/7ccxZp_QwCo9g-khY5IZd27Oi0A47PBfACLcBGAsYHQ/s1600/lizard.jpeg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="350" data-original-width="350" height="320" src="https://1.bp.blogspot.com/-BzRxLSuwVpo/Xb5GFXSAU_I/AAAAAAAAjyg/7ccxZp_QwCo9g-khY5IZd27Oi0A47PBfACLcBGAsYHQ/s320/lizard.jpeg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">This screenshot from Lizard sums up how I felt most of the time.</td></tr>
</tbody></table>
<br />
<br />
First, I wanted a map screen that would be permanently revealed as you explored. (Like Super Metroid, Symphony of the Night, or many other newer games). With a revealed map, you always feel like you're making progress. Even if you get fairly lost, and don't discover where to go, your adventurous route is <i>marked on your map</i>. There's some internal feeling of reward in uncovering the map. You did something permanent. You got here once and you know how to get here again.<br />
<br />
Second, I wanted a lot of powerups scattered throughout the world. Heart containers in Zelda and missile packs in Metroid were great -- they could distribute them freely all over the place, giving you lots of minor rewards throughout the game. I love Super Metroid's method of putting a minor reward behind some secret passage. The real thrill was finding the secret passage, but they gave you a permanent upgrade as a reward once you found it. To make this happen, I have a number of types of powerups in Halcyon, so that I can hand them out like candy. There are a few major ability powerups (which are the keys to unlocking content and progressing), but there are also new alternate weapons, max health increases, weapon power increases, energy increases, etc. If it was ONLY health increases, I think they would quickly become boring and lose their sense of reward. So my goal is to have all sorts of types of things to find to keep it feeling rewarding.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-GDXRwib3aSY/Xb5Gv63OV8I/AAAAAAAAjyo/p69UH_zawq8X5IUNq0Xe3jYL4XCToUaYACLcBGAsYHQ/s1600/map.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="168" data-original-width="552" height="121" src="https://1.bp.blogspot.com/-GDXRwib3aSY/Xb5Gv63OV8I/AAAAAAAAjyo/p69UH_zawq8X5IUNq0Xe3jYL4XCToUaYACLcBGAsYHQ/s400/map.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">A tiny sliver of Halcyon's minimap</td></tr>
</tbody></table>
<br />
<br />
Hopefully my describing where Halcyon fits in along each axis will help provide a better picture of "just what is this game all about, anyway?" But just a few more bullet points might be helpful:<br />
<br />
- There's a little bit of story/plot, but not much. If all goes to plan, and<br />
we can get the details ironed out, there will be an opening story sequence, a tiny bit of plot along the way, and and ending story. But this won't be a story-driven game. You won't be talking to lots of people, getting clues in towns, or interacting with a lot of characters and dialog.<br />
<br />
- Like Blaster Master, there will be a dynamic of getting in and out of your<br />
vehicle to explore different areas that are better handled on foot or in-vehicle. Unlike Blaster Master, there are no overhead sequences. The world is one single contiguous side-scrolling map.<br />
<br />
- There will be secret passages and hidden items. More Metroid than Blaster<br />
master in this regard. Some will be pretty obvious. Others might be rather difficult to find. (dare I say obtuse?) My goal (if I pull it off well, which I may not) is that all of the secret passages necessary to win the game will be fairly intuitive to find, while many of the optional routes or powerups will be better hidden.<br />
<br />
If you have any questions, I'd love to hear from you! Pester me on <a href="https://twitter.com/nathantolbert">Twitter</a> or email at nathan@bitethechili.com.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-3PfF85ijJKE/Xb5HyeqgAQI/AAAAAAAAjy0/tU6Nl-r1tSYUNHrWa6flJpVJUoiz4Vi4wCLcBGAsYHQ/s1600/powerup.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="508" data-original-width="512" height="396" src="https://1.bp.blogspot.com/-3PfF85ijJKE/Xb5HyeqgAQI/AAAAAAAAjy0/tU6Nl-r1tSYUNHrWa6flJpVJUoiz4Vi4wCLcBGAsYHQ/s400/powerup.gif" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-63883764739101400922019-10-24T22:43:00.001-05:002019-10-24T22:43:38.632-05:00Asset PipelineOne of the biggest tasks in marking a video game with a significant amount of content is managing your asset pipeline. What is an asset pipeline? It's the process of getting assets (graphics, music, maps, dialog scripts, or any other sort of content) into the game. In smaller games, you can usually get by without much of a pipeline. If there's not much content, you can usually find a way to manually shoehorn it into your game. Or even manually hack or hardcode the data into the game. But with more content, that quickly becomes unmanageable.<br /><br />Modern platforms like unity have a lot of built-in editors and tooling that manage a lot of the pipeline for you, and the more powerful target platforms (ie your PC) can afford to work with a lot more options of data formats. But on old systems like the NES, it can be a lot of work getting your assets into a format that is usable and efficient.<br /><br />For example, most Atari games store their sprite graphics upside-down in ROM. This lets you save a few cycles in your rendering kernel (because on the 6502 counting down to 0 is faster than counting up to an arbitrary number, so you count backwards when rendering most things). You can manually enter your sprite graphics backwards by hand, or you can draw your graphics in a user-friendly tool, and let it take care of turning them upside-down. <br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-hwDeaM26miw/XbJq21NaFmI/AAAAAAAAjjU/51qYoHFlNSQGulX4d6ABRsaPYZHo28c3QCLcBGAsYHQ/s1600/atari.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="182" data-original-width="464" height="156" src="https://1.bp.blogspot.com/-hwDeaM26miw/XbJq21NaFmI/AAAAAAAAjjU/51qYoHFlNSQGulX4d6ABRsaPYZHo28c3QCLcBGAsYHQ/s400/atari.png" width="400" /></a></div>
<br />
<br />For the NES, there's a huge range of ways that people manage the asset pipeline. I know folks who design things on paper, then manually enter hex data into their editor. I know a guy who built an editor entirely as a NES game itself so that you can just press save in the editor, and everything is ready in a NES-friendly format.<br /><br />My philosophy is this: I want all my assets to be saved in the most easy-to-edit and user-friendly format. That way I can work with them easily during development. Then I want the build process to automatically convert them all into the right format for the game when I compile. I specifically DON'T want to deal with a separate export step during editing -- that usually ends up with a situation where you forget to save or export something, and your exported version differs from the original asset, and you aren't sure which is correct. Instead, I want the original asset to always be the authoritative version. So any conversion or export steps must be automatically done at build time.<br /><br />So, let's get into examples. Text data is probably the simplest pipeline. I have a number of dialog boxes in the game. I want to be able to write my dialog text in any text editor, and save it as a text file that's tracked in git. So I have a separate text file for each dialog script.<br /><span id="goog_2024066664"></span><br /><br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/--P2PpVz32RU/XbJrNU7JRxI/AAAAAAAAjjc/AYaStkwcGWI4vKkby3Xv-fGfhGMaHvVQgCEwYBhgL/s1600/scripts.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="487" data-original-width="719" height="270" src="https://1.bp.blogspot.com/--P2PpVz32RU/XbJrNU7JRxI/AAAAAAAAjjc/AYaStkwcGWI4vKkby3Xv-fGfhGMaHvVQgCEwYBhgL/s400/scripts.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">One text file for each dialog</td><td class="tr-caption" style="text-align: center;"><br /></td></tr>
</tbody></table>
<span id="goog_2024066665"></span><br />
<br />At build time, my makefile runs a python script to convert each of these into the format that my game wants: It converts each character to the value of the graphic tile id that displays that character. It converts special control characters (like line-breaks, special non-ascii glyphs like button icons, etc) to the correct codes. Then it generates symbol tables of both the address of that script, and the text length of the script. So if I want to change any text, I only have to change the text file and recompile, and I'm good to go.<br /><br />Now, let's get into the more substantial example: backgrounds, metatiles, and map data.<br /><br />NES background graphics are made of 8x8 pixel tiles of 3 colors plus a solid background color. Different palettes can be applied to those 3 tiles (with all sorts of rules that I won't get into here), but at the core, NES graphics can be represented by a 4-color image. Frankengraphics (the artist I'm working with) likes to use a program called <a href="https://shiru.untergrund.net/software.shtml">NES Screen Tool</a> for making the graphics. Screen Tool generally saves data in a NES-native graphics format, which most people really like. This goes against my core philosophy though, as it's a hard-to-use format. You can't see a preview of the graphics in your operating system's window manager, or pop open any old graphics tool and make quick changes. It doesn't help that Screen Tool is windows-only, and feels REALLY buggy and janky running in WINE on Linux. So when I get graphics from her, I immediately load them into Screen Tool once, and export them to 4-color indexed png format. During the build process, I have a python script that goes back and converts the png format to the NES-native graphic format (as well as generates address symbols based on the file name) This has the added benefit of making it easier to have arbitrarily-sized chunks of graphic data that I can refer to directly. <br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-XCIWCoiPAVg/XbJr4OdgL7I/AAAAAAAAjjo/ATn9Vf4j7UgH3caSB5bvP-N3SZNO4ZIzACLcBGAsYHQ/s1600/screenTool.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="645" data-original-width="823" height="312" src="https://1.bp.blogspot.com/-XCIWCoiPAVg/XbJr4OdgL7I/AAAAAAAAjjo/ATn9Vf4j7UgH3caSB5bvP-N3SZNO4ZIzACLcBGAsYHQ/s400/screenTool.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">NES Screen Tool. A lot of people swear by this thing. </td></tr>
</tbody></table>
<br /><br />Then, Halcyon makes maps out of 32x32 pixel "metatiles" -- logical map tiles built out of 16 graphic tiles. So I need a way to define those. Since there aren't standard ways of representing metatiles (and since each engine has its own requirements for what data is bundled with metatiles), I decided to use a format that's both human readable and easy to program against: json. I built a simple (ugly) graphical editor for building metatiles out of graphics tiles. It edits json files, so standard tools (text diffs, git merges, etc) can easily work with the output of my metatile editor. In my engine, metatiles contain 16 graphical tiles, as well as collision and palette data for the 4 quadrants of the metatile. These are also saved with the json file. At build time (do you see the pattern here?) a python script converts these json files to striped arrays of binary data (and will also put them into various rom banks to ensure that I don't overflow the amount of space I have in any given bank). The other resulting file is a temporary graphical file that re-assembles the actual graphical tiles into the metatiles, which I use for designing maps. This graphics file is never used in the final build -- it's only a convenience to make map design easier.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-entHp13SisE/XbJsfRW6IjI/AAAAAAAAjjw/LkQB4zeMMngQXgy8Bxz68ulEXbxYPFt-ACLcBGAsYHQ/s1600/metatileEditor.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="569" data-original-width="1169" height="193" src="https://1.bp.blogspot.com/-entHp13SisE/XbJsfRW6IjI/AAAAAAAAjjw/LkQB4zeMMngQXgy8Bxz68ulEXbxYPFt-ACLcBGAsYHQ/s400/metatileEditor.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Speaking of janky, my metatile editor is JANK-O-RIFFIC. But it gets the job done.</td></tr>
</tbody></table>
<br /><br />So now, on to maps. While there's not one single standard file format or tool for maps, there is a well-known open source program that I'm a big fan of: <a href="https://www.mapeditor.org/">Tiled</a>. Tiled is a general purpose tile-map editor. It's fairly flexible and configurable, and the author is a really friendly guy who's very invested in making it a well-run open source project. Tiled saves and loads maps in a simple easy-to-read text format (either based on xml or json), which is perfect for my philosophy of assets.<br /><br />So I load a tileset graphics file into Tiled and build my maps, saving them in Tiled's native format. At build time, a python script processes all of my map files, and converts them all to the right format for Halcyon. (which is a big array of properties about each map, with pointers to arrays of the actual map data, and arrays of enemy spawn information). Then the script builds a giant index of all my maps, and makes a big lookup table that lets me find the address of a room based on its X,Y coordinates.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-6-m7EKVVVsM/XbJs58X3hnI/AAAAAAAAjj4/rzy9nbdCAeo6nvHqPeSzOHF4uFr4jbCpQCLcBGAsYHQ/s1600/tiled.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="814" data-original-width="1345" height="241" src="https://1.bp.blogspot.com/-6-m7EKVVVsM/XbJs58X3hnI/AAAAAAAAjj4/rzy9nbdCAeo6nvHqPeSzOHF4uFr4jbCpQCLcBGAsYHQ/s400/tiled.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Tiled is the best thing ever. </td></tr>
</tbody></table>
<br /><br />Now that I've talked about HOW I do the asset pipeline for levels, it's worth giving a few more reasons for WHY I do it this way. I mentioned a few above, but I like to repeat myself, so:<br /><br />- Editing is MUCH easier when you use text formats as much as possible, and<br /> standard well-known formats for non-text<br /><br />- Version control (ie git) is a lot more useful with text files, as you can<br /> easily see a diff of what changed<br /><br />- Building from the original source asset at compile-time prevents the "out of<br /> sync" problems you can get with manual exports<br /><br />- Using a script at build time lets you change the engine's expected format<br /> without having to touch each of your assets again. For example, at one point I made an optimization in my sprite rendering that required sprites to be stored offset by 32 pixels. To accommodate this, I only had to change my build script without touching each sprite definition. Or if I go back and decide to add compression on graphics, I can do it without having to manually handle each graphics file.<br /><br />- Using a script lets you automatically build secondary derived assets from<br /> your assets. For example, I automatically generate a minimap based on my actual maps without having to manually build a minimap to match the actual map. I also automatically build some reference notes for myself -- a text file that includes notes about the location of every powerup and the id's that I've assigned to them. I love not having to manage that notes file by hand.<br /><br /> I could talk about all the other types of assets (enemy definitions -- which I've blogged about previously, music, palettes, etc), but I'd start to sound pretty redundant I think. I just do my best to folow the guiding philosophy: store in the most editor-friendly format, and convert everything at build time. Manual exports are NOT allowed.<br /><br />Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-17173696988513706872019-08-29T21:30:00.001-05:002019-08-29T21:30:27.213-05:00Scrolling Camera TweaksOne 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.<br />
<br />
<br />
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.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-1N683GdJRlw/XWiGk0sLzMI/AAAAAAAAiW8/gUJQC2jm70U0wCMEdwKfTsKN4neb0FknACLcBGAs/s1600/srollingRight.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="626" data-original-width="1216" height="328" src="https://1.bp.blogspot.com/-1N683GdJRlw/XWiGk0sLzMI/AAAAAAAAiW8/gUJQC2jm70U0wCMEdwKfTsKN4neb0FknACLcBGAs/s640/srollingRight.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
This works really nicely for most of the game.<br />
<br />
Until it doesn't.<br />
<br />
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.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-F5OgOIps9Vw/XWiHjBzSDqI/AAAAAAAAiXI/4DLGuwx_JX4xx2lGzHPZrSRQhhEx7NYuACLcBGAs/s1600/scroll.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="598" data-original-width="608" height="314" src="https://1.bp.blogspot.com/-F5OgOIps9Vw/XWiHjBzSDqI/AAAAAAAAiXI/4DLGuwx_JX4xx2lGzHPZrSRQhhEx7NYuACLcBGAs/s320/scroll.gif" width="320" /></a></div>
<br />
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?<br />
<br />
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. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
This works great for the scenario in question:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-vRnGehOchCk/XWiKFkzNHTI/AAAAAAAAiXg/HY_eAM4_HnwmAxoh_RKRde1tg2KP2ED6QCLcBGAs/s1600/scroll2.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="598" data-original-width="608" height="314" src="https://1.bp.blogspot.com/-vRnGehOchCk/XWiKFkzNHTI/AAAAAAAAiXg/HY_eAM4_HnwmAxoh_RKRde1tg2KP2ED6QCLcBGAs/s320/scroll2.gif" width="320" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br /> <br />
The real question will be whether it causes annoying behavior in other parts of gameplay. That will remain to be seen....<br />
Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-75128254028916661642019-08-01T22:26:00.001-05:002019-08-01T22:26:15.782-05:00Crazy crashing bugNormally after I test on an emulator for awhile, I eventually throw the game onto hardware, and get frustrated at it not working.<br />
<br />
THIS TIME, however, I ran into something new. The game was working on my primary-use emulator, <a href="https://mesen.ca/">Mesen</a> (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.<br />
<br />
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?<br />
<br />
As I usually do when I get stumped with a hard-to-debug issue, I did a <a href="https://stackoverflow.com/questions/4713088/how-to-use-git-bisect">git bisect</a>. 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.<br />
<br />
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.<br />
<br />
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?<br />
<br />
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.<br />
<br />
The result? It turns out I was calling the update function of the music engine (<a href="http://www.gradualgames.com/p/sound-engine.html">ggsound</a>) 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.<br />
<br />
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.Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-70713346292791701312019-07-20T23:20:00.000-05:002019-07-20T23:20:02.784-05:00Prep for testingMy 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?<br />
<br />
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.<br />
<br />
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.<br />
<br />
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 <i>don't</i> 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)<br />
<br />
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.<br />
<br />
I'm almost there though. Time to do some more playthroughs.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-TszcQg1bfNA/XTPmxCctaAI/AAAAAAAAhZo/QmJCebziJ4cNg34g4QlDvpGwOZoZtpnKgCLcBGAs/s1600/menu.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="533" data-original-width="512" height="400" src="https://1.bp.blogspot.com/-TszcQg1bfNA/XTPmxCctaAI/AAAAAAAAhZo/QmJCebziJ4cNg34g4QlDvpGwOZoZtpnKgCLcBGAs/s400/menu.png" width="383" /></a></div>
<br />
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:<br />
<ul>
<li>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)</li>
<li>New Enemy Graphics (I'm using tons of placeholders)</li>
<li>The ability to shoot upward at an angle once you find the tank</li>
<li>Fix buggy behavior with the health-hungry blob enemy</li>
</ul>
Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-24301023625826653582019-07-19T11:20:00.002-05:002019-07-19T11:20:21.886-05:00Pseudo-fixed bankI wrote about <a href="https://anguna-dev.blogspot.com/search?q=bank+switching">bank switching</a> 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.<br />
<br />
Many variants of cartridge hardware (commonly known as <i>mappers)</i> 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.<br />
<br />
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 <i>trampoline) </i>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.<br />
<br />
That's worked well for me so far on Halcyon. But now I've realized I have about 15 subroutines that I call <i>a lot</i> 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.<br />
<br />
Unfortunately, <a href="https://www.cc65.org/">cc65</a> (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.<br />
<br />
First, I compile the whole project once, leaving blank space in the places where the code should be duplicated <i>to. </i>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 <i>again</i>, this time injecting that extracted binary into each place that it needs to be duplicated.<br />
<br />
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.Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-21371822191088796752019-07-14T23:02:00.002-05:002019-07-14T23:02:48.224-05:00First BossI feel like I'm finally making real progress. Using some placeholder graphics from the ever-amazing <a href="https://opengameart.org/users/surt">surt</a>, 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.<br />
<br />
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 <i>aren't</i> 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:<br />
<ul>
<li>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 <i>above</i> 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)</li>
<li>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.</li>
<li>My enemy animation system was broken for animations that had multiple different repeating sections</li>
</ul>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-YPMEQM5YZxQ/XSv6TeopAiI/AAAAAAAAhTo/2Ky9YmqtGeIdTIzjfvlvy8HoCjBAaIOnACLcBGAs/s1600/firstBoss.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="627" data-original-width="610" height="400" src="https://1.bp.blogspot.com/-YPMEQM5YZxQ/XSv6TeopAiI/AAAAAAAAhTo/2Ky9YmqtGeIdTIzjfvlvy8HoCjBAaIOnACLcBGAs/s400/firstBoss.png" width="388" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">This guy's look might change entirely once Frankengraphics gets ahold of him.<br />I'm also not sure about the vertical placement of the enemy health bar. We'll see.</td></tr>
</tbody></table>
<br />
Once I got those things worked out, developing the things specific to bosses was pretty quick:<br />
<ul>
<li>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!</li>
<li>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.</li>
<li>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.</li>
</ul>
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. <br />
<br />
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.<br />
<br />
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!Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-22106930552588309952019-07-11T23:41:00.002-05:002019-07-11T23:41:41.467-05:00Defining enemiesEnemies 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?)<br />
<br />
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).<br />
<br />
For Halcyon, I decided to use a <a href="https://en.wikipedia.org/wiki/YAML">YAML</a> 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.<br />
<br />
<br />
<pre>- 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
</pre>
<br />
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.<br />
<br />
<br />Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-19984428779147900042019-07-07T22:42:00.002-05:002019-07-07T22:42:25.804-05:00Big 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 <i>metasprite</i>. <br />
<br />
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.<br />
<br />
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 <i>pops</i> 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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-pglYXrpQ_9E/XSK7Y9r6WdI/AAAAAAAAhOg/9yDKkFnvTKUN_HI4I07Wwe7XN6vD5vtngCLcBGAs/s1600/barrier.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="536" data-original-width="512" height="400" src="https://1.bp.blogspot.com/-pglYXrpQ_9E/XSK7Y9r6WdI/AAAAAAAAhOg/9yDKkFnvTKUN_HI4I07Wwe7XN6vD5vtngCLcBGAs/s400/barrier.png" width="381" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">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.</td></tr>
</tbody></table>
<br />
<br />
That was tonight's challenge -- how to work around my optimizations and allow larger enemies?<br />
<br />
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 <i>meta-metasprite, </i>being made of a few different metasprites at different offsets.<br />
<br />
Ugly? You bet. But it works.<br />
<br />Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com1tag:blogger.com,1999:blog-6997440551610060560.post-79077978872692867892019-06-21T23:24:00.002-05:002019-06-21T23:24:33.844-05:00Stairs and a hoppy guyOne 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 <a href="https://anguna-dev.blogspot.com/2013/12/ramps-are-weird.html">ramps are weird.</a><br />
<br />
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.<br />
<br />
<br />
<blockquote class="twitter-tweet" data-lang="en">
<div dir="ltr" lang="en">
Finally got around to adding stairs that you can climb without jumping. I was expecting it to take 4 hours, only took about 30 minutes. One of those rare magical moments when my code actually works.<a href="https://twitter.com/hashtag/NESdev?src=hash&ref_src=twsrc%5Etfw">#NESdev</a> <a href="https://twitter.com/hashtag/halcyonNES?src=hash&ref_src=twsrc%5Etfw">#halcyonNES</a> <a href="https://t.co/XxYoKPDg2D">pic.twitter.com/XxYoKPDg2D</a></div>
— nathantolbert (@nathantolbert) <a href="https://twitter.com/nathantolbert/status/1140476863271317509?ref_src=twsrc%5Etfw">June 17, 2019</a></blockquote>
<script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"></script>
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.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-aBzhSWh3E4Q/XQ2tYg5G0aI/AAAAAAAAgaY/ltIVdpeW-R8Qj8Ip8v8Cnl_5DThBHJZzgCLcBGAs/s1600/hopper.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="508" data-original-width="512" height="317" src="https://1.bp.blogspot.com/-aBzhSWh3E4Q/XQ2tYg5G0aI/AAAAAAAAgaY/ltIVdpeW-R8Qj8Ip8v8Cnl_5DThBHJZzgCLcBGAs/s320/hopper.gif" width="320" /></a></div>
<br />Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-70031795679712567742019-06-15T23:15:00.003-05:002019-06-15T23:15:26.181-05:00Level Design in Earnest<div class="separator" style="clear: both; text-align: center;">
<br /></div>
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.<br />
<br />
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. <br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-pL7_QVmkRP4/XQXB80TpsxI/AAAAAAAAgQQ/hnrT6arFs4EzPzKys1gOjGR7je2vOe_aQCLcBGAs/s1600/tiled.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="863" data-original-width="1450" height="380" src="https://1.bp.blogspot.com/-pL7_QVmkRP4/XQXB80TpsxI/AAAAAAAAgQQ/hnrT6arFs4EzPzKys1gOjGR7je2vOe_aQCLcBGAs/s640/tiled.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Spoiler alert!</td></tr>
</tbody></table>
<br />
<br />
I've realized a few things:<br />
<ol>
<li>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.</li>
<li>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 <i>implemented</i> the running up stairs yet. Time to do that I guess.</li>
<li>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!?</li>
</ol>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-uM_FWlGSDKs/XQXCKNeDsLI/AAAAAAAAgQU/XqgjmjgcRJYTTYXKOdGAr-vuPrJqpnP9QCLcBGAs/s1600/broken.gif" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="476" data-original-width="512" height="371" src="https://1.bp.blogspot.com/-uM_FWlGSDKs/XQXCKNeDsLI/AAAAAAAAgQU/XqgjmjgcRJYTTYXKOdGAr-vuPrJqpnP9QCLcBGAs/s400/broken.gif" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="color: #0000ee;"><u>Exhibit A, in which I decide that I hate computers.</u></span></td></tr>
</tbody></table>
<div style="text-align: center;">
</div>
Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-77946026988277956402019-06-07T13:16:00.000-05:002019-06-07T13:16:13.708-05:00Moving platforms workingOk, another lunch hour devoted to the platforms, and they're working! My idea of how to make them ended up working perfectly.<br />
<br />
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.<br />
<br />
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.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-0LcQjZBegl4/XPqpss7KCPI/AAAAAAAAgB8/7wUN42UGlYcEUQZkK81Yf1FckTMhc8D-wCLcBGAs/s1600/obs2.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="476" data-original-width="512" height="297" src="https://1.bp.blogspot.com/-0LcQjZBegl4/XPqpss7KCPI/AAAAAAAAgB8/7wUN42UGlYcEUQZkK81Yf1FckTMhc8D-wCLcBGAs/s320/obs2.gif" width="320" /></a></div>
<br />
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.Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-68166328046802850042019-06-04T22:45:00.003-05:002019-06-04T22:45:48.835-05:00Catching 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.<br />
<br />
I realized I didn't say a thing here about <a href="http://www.bitethechili.com/superhomebrewwar/">Super Homebrew War</a>, my 4-player nes brawl, using characters from other well-known NES homebrew games, which scored 2nd place in this year's NESdev competition. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-PEjSnuPY1Jw/XPc4DWYaUCI/AAAAAAAAf54/b9a3_Z-Ex30fUN_82yLd_F9leyBLWL4twCLcBGAs/s1600/homebrewWar.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="502" data-original-width="514" height="312" src="https://1.bp.blogspot.com/-PEjSnuPY1Jw/XPc4DWYaUCI/AAAAAAAAf54/b9a3_Z-Ex30fUN_82yLd_F9leyBLWL4twCLcBGAs/s320/homebrewWar.png" width="320" /></a></div>
<br />
<br />
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 <a href="http://www.bitethechili.com/roboninja-climb/">Dreamcast version of Robo-Ninja Climb finished.</a><br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-UNg1ZncjQLI/XPc4mXRUBxI/AAAAAAAAf6A/KWLwFUG6f08224-mQ4kZv5OP0U-ZXUcfQCLcBGAs/s1600/rncdc.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="413" data-original-width="420" height="314" src="https://1.bp.blogspot.com/-UNg1ZncjQLI/XPc4mXRUBxI/AAAAAAAAf6A/KWLwFUG6f08224-mQ4kZv5OP0U-ZXUcfQCLcBGAs/s320/rncdc.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The nice thing about Dreamcast homebrew is that you can test on hardware for the low price of a CD-ROM!</td></tr>
</tbody></table>
<br />
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.<br />
<br />
After a few false starts, my current theory of how they'll work is like this:<br />
<br />
<ul>
<li>Moving platforms are actually just an enemy instance, with a modified collision routine. </li>
<li>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)</li>
<li>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.</li>
<li>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</li>
</ul>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-tv3OVYqUtro/XPc6sjfx1AI/AAAAAAAAf6M/qsCBNcEfOXY1_BXnfjIQoOth-pNLatCWQCLcBGAs/s1600/obs.gif" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="476" data-original-width="512" height="371" src="https://1.bp.blogspot.com/-tv3OVYqUtro/XPc6sjfx1AI/AAAAAAAAf6M/qsCBNcEfOXY1_BXnfjIQoOth-pNLatCWQCLcBGAs/s400/obs.gif" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Believe it or not, but that moving thing above the tank is a platform.</td></tr>
</tbody></table>
<br />
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.Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-38462101242207231762018-07-06T22:56:00.001-05:002018-07-06T22:56:44.241-05:00Robo-Ninja Climb 2600So I had this crazy idea.<br />
<br />
The game logic of Robo-Ninja Climb is really simple. And so is the display. So in theory, with the NES and the Atari 2600 both having the same 6502 processor, I could take the NES source code for the game, rip out the rendering bits (and sound and controls), leaving just the core game logic, move it directly to the Atari, then rewrite all the rendering stuff.<br />
<br />
In theory, this is made even easier by the fact that the Atari has built-in collision detection. On the NES, about half the work was tracking where you had scrolled, so you could do the math to compute collisions against spikes. On the Atari, I don't have to do that -- as long as I can draw them, it will do collisions for me. Which saves a ton of processing time.<br />
<br />
So I got to work. First thing was stripping down everything I didn't need, replacing the controller code, and bringing over the main character physics. That went smoothly, and compiled for the Atari quite quickly. Of course, then I had to find a way to see if it worked.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://4.bp.blogspot.com/-nPt1OU9gcHQ/W0A5vAuOJPI/AAAAAAAAY6E/mhf1WiTp5JAKmaJX6DY-S8-J1a4JSjMggCLcBGAs/s1600/robo-ninja-climb.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="420" data-original-width="576" height="291" src="https://4.bp.blogspot.com/-nPt1OU9gcHQ/W0A5vAuOJPI/AAAAAAAAY6E/mhf1WiTp5JAKmaJX6DY-S8-J1a4JSjMggCLcBGAs/s400/robo-ninja-climb.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">It's certainly not as pretty as the NES version</td></tr>
</tbody></table>
<br />
<br />
Which meant writing an Atari kernel. I think it's somewhat like what women say after having a baby -- after a few years, you forget the pain and only remember the happy parts. Back to counting cycles, trying to squeeze everything into the 76 clock cycles per scanline. Ugh.<br />
<br />
But it turns out it wasn't as bad for this game, as there's not nearly as much on the screen (Anguna had a multi-color player, a variable-length player missile (sword), changing playfield, a single color enemy, and en enemy missile. This has a single-color player, changing playfield, and single-color enemy. So not as bad.)<br />
<br />
Anyway, after all that, I've got most of an Atari port of Robo-Ninja Climb working. Over the last few weeks, I've been slowly working through the more tedious or difficult parts: rendering items, adding title and level transition screens, etc. With a few more hours of work, I might have a Robo-Ninja Climb port for the Atari! (and I wonder if it's the first Atari port to ever directly use source code from the NES game that came first?)<br />
<br />
Yes, I'm still working on the BlasterMasterMetroidVania game. And the 4-player adapter. I promise.Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-90179294795343549722018-03-21T00:11:00.001-05:002018-03-21T00:11:28.763-05:00I blame TwitterHuh. I haven't posted here since last December. Not because I'm not doing things. But mostly because of all the NES dev folks that hang out on twitter and discord, so <a href="https://twitter.com/nathantolbert">I've been posting short status updates there along with my usual twitter nonsense</a>.<br />
<br />
But because in 4 years when I come back here to this blog to try to remember what I was doing in early 2018, here you go future me. Turns out I have WAY too many projects going on at once! Begin wall-of-text mode!<br />
<br />
<ul>
<li>I've got cool destroyable blocks going in my metroidvania game, and the basics of some enemies. I've added music thanks to the <a href="https://github.com/gradualgames/ggsound">awesome sound engine</a> that <a href="http://www.gradualgames.com/">Derek of Gradual Games</a> made. And unfortunately, I've realized that the current version of the game won't actually boot and run on hardware. UGH. I think on EVERY PROJECT I've ever done, I say "oops, I wish I would have tested on hardware sooner." Time to pare things down and figure out where it's going wrong. Hopefully in time for <a href="https://www.midwestgamingclassic.com/">Midwest Gaming Classic</a>, where I hope to show it off a little.</li>
</ul>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://4.bp.blogspot.com/-GJornh25ekQ/WrHlQkfvbxI/AAAAAAAAVW0/La_hFt5t8jkXdbETl9CYChhdXOTblhIIwCLcBGAs/s1600/map.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="495" data-original-width="514" height="385" src="https://4.bp.blogspot.com/-GJornh25ekQ/WrHlQkfvbxI/AAAAAAAAVW0/La_hFt5t8jkXdbETl9CYChhdXOTblhIIwCLcBGAs/s400/map.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">I also added a map screen to help you figure out where you've been. </td></tr>
</tbody></table>
<div>
<br /></div>
<div>
<br /></div>
<ul>
<li>The Atari 4-player adapter hit a slight roadblock when I realized that there's something causing a weird static/flicker on the screen whenever player 4 moves their controller. It's hardware-related, not game-related, so I've been needing to do some hardware debugging, which is HARD. Thanks to some advice from Paul at <a href="http://infiniteneslives.com/">InfiniteNESLives</a>, I've added some capacitors in the last week or so, and they seemed to have helped. But now (based on some suggestions from another awesome Atari homebrewer, John Champeau), I've decided to test another design, based on 2 separate 1-to-2 joystick adapters, which gives some more options for a game to work with other peripherals at the same time (ie 2 joysticks plus an AtariVox speech synthesizer). We'll see how that goes -- I have the parts and the design should be pretty quick to slap together, but after that, I'm stuck in the same "how do I turn this from prototype to production" situation.</li>
</ul>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://3.bp.blogspot.com/-UZETQnwx_f8/WrHoIkLlmhI/AAAAAAAAVXU/GCegZY3k-KEo7imsuk9ZcEWbQP7f6MiJACLcBGAs/s1600/capacitors.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="758" data-original-width="568" height="400" src="https://3.bp.blogspot.com/-UZETQnwx_f8/WrHoIkLlmhI/AAAAAAAAVXU/GCegZY3k-KEo7imsuk9ZcEWbQP7f6MiJACLcBGAs/s400/capacitors.jpg" width="298" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Yeah, I'm pretty sure that's NOT the recommended way to attach the capacitors to my board</td></tr>
</tbody></table>
<div>
<br /></div>
<div>
<br /></div>
<ul>
<li>Speaking of hardware, I've also started helping <a href="http://www.khangames.com/">Kevin from KHAN games</a> (he pronounces that K-Han, not KHAAAAAN, which is crazy) on a project that involves connecting a NES to the internet. So I've rigged up my raspberry pi to my NES via the controller port, and I've been trying to get them to talk. So far it's been mixed results, but this week I managed to get the PI to tell the NES to print the alphabet. Until halfway through the screen, when things got horribly out of sync. I'm going to have to read up on serial protocols and error checking, I think. </li>
</ul>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://2.bp.blogspot.com/-rIB3L_PSB8U/WrHmuPfgN4I/AAAAAAAAVXA/OvHhvvj8Jrssjf6IqIH_q0tyd8QD-0uNwCLcBGAs/s1600/nesABC.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="468" data-original-width="351" height="400" src="https://2.bp.blogspot.com/-rIB3L_PSB8U/WrHmuPfgN4I/AAAAAAAAVXA/OvHhvvj8Jrssjf6IqIH_q0tyd8QD-0uNwCLcBGAs/s400/nesABC.jpg" width="300" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Things were looking good until suddenly they weren't.</td></tr>
</tbody></table>
<ul>
<li>The results came in from the <a href="http://nesdevcompo.nintendoage.com/contest17/">NESDev competition</a>. Robo-Ninja-Climb got middle-of-the-pack results, which is what it deserved. The entries were really high-quality this year, so I'm pretty satisfied with where I ended up. If you want to hear me attempt to talk about it on the <a href="https://soundcloud.com/user-463768680/e10-nesdev-competition-part-1">Assembly Line Podcast</a>, now's your chance.</li>
</ul>
<div>
<br /></div>
<ul>
<li>Speaking of the competition, I decided to make a multi-cart of my competition entries (and in theory, will update it each year with any new small games that I make for the NES). So I spent the last couple weeks modifying my competition entries to work on a different board/mapper. That took a bit of work, as the new mapper (GTROM) uses ram instead of rom for tile graphic data (chr-ram), and four screens-worth of tile map data (nametables) that I have to account for. But now I have a slick demo cart to show off my games. If I can't get the Blastervania game ready by MGC, I'll at least have something to demo!</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-ZNgzbBSc3FI/WrHnqUHHLOI/AAAAAAAAVXM/AO7SnZkhDwIzNnnaJYmO98oNvSY7rszAwCLcBGAs/s1600/sampler.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="495" data-original-width="514" height="308" src="https://2.bp.blogspot.com/-ZNgzbBSc3FI/WrHnqUHHLOI/AAAAAAAAVXM/AO7SnZkhDwIzNnnaJYmO98oNvSY7rszAwCLcBGAs/s320/sampler.png" width="320" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<br />
<br />
<br />Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-72151923191548760002017-12-17T22:51:00.002-06:002017-12-17T22:51:57.701-06:00Half-height collisionsI'm back at the metroidvania game tonight. I had just gotten collisions mostly working, at block sizes of 16x16 pixels. But the artist I'm working with (the amazing <a href="https://frankengraphics.com/">Frankengraphics</a>) asked if we could do it at half-height -- so 16x8 pixel collision resolution.<br />
<br />
One thing I've learned in life is to try to say yes to every feature the artist recommends. It will make your game better (which is why the original Anguna turned out so well: Chris had tons of great artistic suggestions, which required me to completely rewrite the engine after he came on board!) <br />
<br />
In this case, I reserved 1 byte of data for each metatile of 4 16x16 blocks, which means 2 bits per block. That could allow 4 different collisions types: open, blocked, destructable, and "special" where special depended on the room (with options being things like lava, water, etc). To add half-height collisions, something was going to have to change. But I didn't want to re-tool the whole engine to support EVERYTHING operating at a 16x8 level.<br />
<br />
So the answer I settled on (for now, at least...we'll see how it works) is that I'll add an extra byte of collision data per metatile, meaning each block gets 4 bits, which is 16 different collision types. One of those collision types is half-high-blocked. So I check for that separately in my collision routines, but everything else (block destruction, etc) can still operate at the 16x16 pixel level, which should make everything easier.<br />
<br />
We'll see if that holds true....<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-vKwkUJpmJd8/WjdJBE10-BI/AAAAAAAASEA/UY2sfy6F-ZwpT-bIpGmUlg6m9h_QPaiTwCLcBGAs/s1600/halfVertHeight.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="529" data-original-width="514" height="400" src="https://1.bp.blogspot.com/-vKwkUJpmJd8/WjdJBE10-BI/AAAAAAAASEA/UY2sfy6F-ZwpT-bIpGmUlg6m9h_QPaiTwCLcBGAs/s400/halfVertHeight.png" width="387" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">It's hard to really tell, but the big block that the vehicle is <br />sitting on near the top-left is 32 pixels wide, and 24 pixels high. Meaning the top<br />layer is 2 half-high-collisions.</td></tr>
</tbody></table>
<br />Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-12968377526148626132017-11-29T22:46:00.001-06:002017-11-30T10:14:50.702-06:00PRGE 17I just realized I never did a post after the Portland Retro Game Convention talking about how great it was!<br />
<br />
The first great part was just standing in line to get in. I started chatting with the fellow in line behind me, who not only was familiar with my Atari version of Anguna, but had actually bought a copy just a few weeks before the convention!<br />
<br />
The convention itself was a blast -- the biggest highlight was meeting (in person) many homebrewers that I've gotten to know in the <a href="http://atariage.com/forums/">AtariAge</a>, <a href="http://forums.nesdev.com/">NesDev</a>, or NESDev twitter communities. It's nice to finally put faces with names (or real names with internet names!).<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-ySfJFfFe_ZQ/Wh-KZmTiFKI/AAAAAAAARYY/wr4WUJ9HI6sEX3wbKeG-cPgK5Vhi_8o3gCLcBGAs/s1600/prge-aa.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="785" data-original-width="1395" height="225" src="https://1.bp.blogspot.com/-ySfJFfFe_ZQ/Wh-KZmTiFKI/AAAAAAAARYY/wr4WUJ9HI6sEX3wbKeG-cPgK5Vhi_8o3gCLcBGAs/s400/prge-aa.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><br /></td></tr>
</tbody></table>
<br />
<br />
Other than that, I spent a lot of time hovering in the background, watching people try out the Quadtari. It was fun seeing people pick it up and give it a try. (What surprised me most was the number of teenagers who had no idea how to hold an Atari joystick.) At one point, Joe Decuir, the engineer who designed much of the hardware of the original Atari, made a stop by to see it!<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://3.bp.blogspot.com/-r889OrpS6LE/Wh-Lhw0n_XI/AAAAAAAARYk/cBRzBwAW1KkuCQqXDdLlTdWdX3zwhkyiwCLcBGAs/s1600/prge-demo.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="735" data-original-width="1307" height="223" src="https://3.bp.blogspot.com/-r889OrpS6LE/Wh-Lhw0n_XI/AAAAAAAARYk/cBRzBwAW1KkuCQqXDdLlTdWdX3zwhkyiwCLcBGAs/s400/prge-demo.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">As they were walking away from trying Quad-Tank, I overheard of these guys say,<br />
"That was the most fun I've ever had playing Atari!"</td></tr>
</tbody></table>
<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-DNN7vZOziOs/Wh-MIMzrwZI/AAAAAAAARYs/acNHVnNeTKQ0Fl5GLWroGqbRJHzbspO5QCLcBGAs/s1600/albert.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="735" data-original-width="1307" height="223" src="https://1.bp.blogspot.com/-DNN7vZOziOs/Wh-MIMzrwZI/AAAAAAAARYs/acNHVnNeTKQ0Fl5GLWroGqbRJHzbspO5QCLcBGAs/s400/albert.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Albert, the mastermind behind AtariAge, hard at work.</td></tr>
</tbody></table>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-JOR8ELePhwQ/Wh-MIOXaYSI/AAAAAAAARYw/blpk1-6Tp6QQuuo1K-6XnM_z5lDfGigyQCLcBGAs/s1600/quadGames.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="735" data-original-width="1307" height="223" src="https://3.bp.blogspot.com/-JOR8ELePhwQ/Wh-MIOXaYSI/AAAAAAAARYw/blpk1-6Tp6QQuuo1K-6XnM_z5lDfGigyQCLcBGAs/s400/quadGames.jpg" width="400" /></a></div>
<br />
<br />
Now I have a lot of work to do. The first prototype took me hours of soldering, about $25 in parts, and is already getting a little unreliable after being jostled so much. So it's time to design a proper PCB and it get it properly made. I've got a board designed that I <i>hope</i> is the same as what I prototyped, and I'm in the process of getting quotes on PCB fabrication and soldering assembly from a place in China. We'll see what happens!Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-22727039688399463322017-11-29T22:26:00.004-06:002017-11-29T22:26:49.450-06:003 weeksSo one of the main goals of Robo-Ninja Climb was to see how quickly I could crank out a reasonably playable NES game. Nothing ground-breaking, but a short fun game.<br />
<br />
The answer is 3 weeks of evenings. Approximately 40 hours, maybe. I've got a game that's certainly playable (although by all reports, a little too hard. Oops, I'm bad at that) <br />
<br />
I decided partway through to ditch the "endless" part of it -- I realized I didn't have a good plan for making random levels that were actually fun to play. So I designed 5 short levels, with a few power-ups part way through. Each has slightly different background graphics, to give the player a sense of progression.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-RtWAalrngNI/Wh-GXGCmBfI/AAAAAAAARYM/Wno3dNLpOo8rb6qNRN2TzMoynK6fIixVQCLcBGAs/s1600/rn-final.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="847" data-original-width="916" height="295" src="https://2.bp.blogspot.com/-RtWAalrngNI/Wh-GXGCmBfI/AAAAAAAARYM/Wno3dNLpOo8rb6qNRN2TzMoynK6fIixVQCLcBGAs/s320/rn-final.png" width="320" /></a></div>
<br />
A few thing that I felt like were somewhat interesting during the development:<br />
<br />
<ul>
<li>I used the <a href="https://nesdoug.com/2015/12/02/13-sprite-0-trick/">NES's Sprite-0 flag</a> to make the spikes at the bottom not scroll away with the background. But to make it more interesting, I cycle sprite 0 up and down slightly, to make the spikes bounce and look a little scarier. </li>
<li>Robo-Ninja is a giant character by NES standards. Most games have characters that are 16x32 or so. Robo-Ninja is 32x48 or larger (depending on the animation frame). I can get away with it because there aren't many other sprites in the game. Most games have enemies, bullets, and so forth to deal with. Robo-Ninja mostly dodges background elements.</li>
<li>I didn't feel like mapping levels out manually in a tile editor (mostly it's blank space!), so I mapped them all out using text like this, and then have a pre-processor as part of my build script that converts it to actual binary level data:</li>
</ul>
<br />
<div>
<br /></div>
<div>
<div>
<br /></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">>.......</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">>.......</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">>.......</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">>.......</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">>.......</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">>.......</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">>.......</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">>.......</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">........</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">........</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">...*....</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">.......<</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">........</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">........</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">.......<</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">........</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">........</span></div>
<div>
<br /></div>
</div>
<div>
<br /></div>
<div>
Well, other than collecting feedback, tweaking difficulty, and possibly adding some music from another composer, I think it's done. And I still have 2 months left before the deadline! You can try it out using any NES emulator, and grabbing <a href="http://www.bitethechili.com/roboninja-climb/">the rom from my bitethechili site</a>. If you do, let me know if you have feedback!</div>
Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0tag:blogger.com,1999:blog-6997440551610060560.post-39977661808873456882017-11-10T00:09:00.001-06:002017-11-10T00:09:48.405-06:00Robo-Ninja ClimbWell, out of the blue, I decided that I was missing out by not entering something into <a href="http://nesdevcompo.nintendoage.com/rules/2017/">this year's NesDev competition</a>, so a couple weeks ago I decided to see how quickly I could churn out a simple-but-fun game.<br />
<br />
My idea is an endless-climber type of thing, based on the Robo-Ninja character. You just bounce back and forth scaling walls while dodging obstacles.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-3Yd923ODUA0/WgVBhoqJ34I/AAAAAAAAQcs/_TAbgI5EpdkTXMAMgw8Ykxd7nywTfagkwCLcBGAs/s1600/rnPalettes.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="529" data-original-width="514" height="400" src="https://1.bp.blogspot.com/-3Yd923ODUA0/WgVBhoqJ34I/AAAAAAAAQcs/_TAbgI5EpdkTXMAMgw8Ykxd7nywTfagkwCLcBGAs/s400/rnPalettes.png" width="387" /></a></div>
<br />
It's been fun, trying to churn this out as fast as possible. To be as efficient as I can, I reused a number of routines from my primary nes project, and then have been coding everything in a mix of C and assembly, switching back and forth at whim, using whichever seems easier for any given part of the game.<br />
<br />
I just finished my 7th day of development, and it's almost a playable game -- I just need to make collisions with the obstacles actually work, and tweak the controls a bit, and it will be time for some preliminary testing: the standard "is it actually fun" sort of thing. We'll see.<br />
<br />
There's a lot more I'd love to say about some of the technical challenges of writing this game, but with trying to finish it as quickly as possible, I end up not wanting to waste time writing about it.<br />
<br />
Like last year, I don't have a lot of intentions of winning the competition, but I couldn't pass up a chance to at least enter something.Nathanhttp://www.blogger.com/profile/02763963217187530121noreply@blogger.com0