code of the Ninja();

// in search of swift, efficient, and invisible code

Showing posts with label sonic the hedgehog. Show all posts
Showing posts with label sonic the hedgehog. Show all posts

2012-12-31

Creating Old-School Palette Effects Using Pixel (Fragment) Shaders

Please note that the following only applies to Game Maker 8.0 using this shader extension. However, the principle can be applied to anything else that uses shaders.

Okay, time to get started. Let's say you have an image. It's made of pixels - obviously - and each pixel is a colour. Where things get interesting is how you choose to describe that colour, i.e what bits or bytes you use to signify it.

The simplest way is to use 1 bit per pixel - on or off. But that only gives you a monochrome image. What about colour? A very common way is to use 24 bits per pixel - one byte each for the red, green, and blue channels. This gives you 256 levels of intensity for each channel, allowing over 16 million vibrant colours, and indeed 24-bit colour is sometimes referred to as "true" colour.

The drawback to methods such as 24-bit true colour is that they use a large amount of memory. So, a common practice in old-school games was to use indexed colour: a palette contains the actual colours, while the pixels in an image are only described by a single value - the index (i.e. the address in the palette) of the colour to be used for that pixel. (Read more at Wikipedia.)

The indexing method, in addition to saving memory, has another big advantage. If you change a colour in the palette, any and all pixels that reference the index of that colour will change along with it. An otherwise static image can come to life in this way - lights can flash, water can flow, stars can twinkle - all without true animation of any kind. (See a beautiful example of this in action.)

As memory and CPU power improved, these little tricks largely fell by the wayside. Software such as Game Maker doesn't provide any functionality for palettes by default, which is bad news for anyone who wants to emulate the effects of old-school games.

Fortunately, LSnK has come to the rescue with his shader extension for Game Maker. (Sadly it is only compatible with version 8.0 of GM, but 8.0 is still a sufficient tool for making great games.) It allows one to use shaders in Game Maker with little more effort than setting the fog colour.

Shaders are quite a complicated subject, but for the purposes of palette effects (and this article) we will only be considering pixel shaders (also referred to as fragment shaders). You can think of a pixel shader as a tiny program that runs for each pixel that is drawn. If you activate a pixel shader and then use a drawing function, each pixel to be drawn is input to the shader, the shader runs, manipulating the pixel in some way, and finally the pixel is output to the screen.

There are a great many effects pixel shaders can achieve. A common example is drawing an image in greyscale: the red, green, and blue channels are used to determine the perceived brightness of the colour, and then that brightness value is output to all three channels, making the corresponding shade of grey.

Now, I'm not going to explain every little thing about how to use the shader extension or make a shader - there's plenty of instruction on how to do these things on its own page. Here I'm only concerned with explaining the idea behind emulating palette effects with a pixel shader. I'll also provide an example GMK at the bottom of the article to get you started.

So let's move on. First, we need a palette (any image that's one pixel in height with colours in horizontal sequence will do). Let's use Sonic's palette from Sonic 1 as an example.

We'll also need an image of Sonic.

The above image clearly won't do. It's already in colour; what we need is an image of Sonic where each pixel is an index in a palette. I've made a tool called Palettiser that lets you do exactly that. It'll analyse an image and replace all the pixels with shades of red - shades of red whose intensity corresponds to an index in a palette, for a max of 256 colours. Having completed that process one ends up with an image of Sonic that looks like this (since there are only about 16 colours in the palette, the shades of red are all very dark, almost black):

Now, both the palette image and the funky-looking converted image can be imported into Game Maker as a sprite or background resources.

The next step is to create a custom pixel shader. A shader that, when active, will perform the following operation on every pixel that is drawn:

  1. Get the amount of red in the pixel.
  2. Use that value as a horizontal coordinate, finding the colour at that position in the palette image.
  3. Draw a pixel of that colour instead.

By drawing the converted image with the shader active, the original Sonic image can then be recreated correctly. But this time it will palettised - any change to the palette image colours will be reflected in the Sonic image immediately.

But how to change the palette image colours? It's a background or sprite, right? Well, what's cool is that a shader can also use surfaces. If you make your palette a surface, you can draw anything to it, and in so doing animate the palette.

But talk is cheap. It's much cooler to see this happen and play around with it in real time. To that end, here is an example GMK. (Be sure and install the shader extension first, of course!)

Have fun!

2011-03-01

Looking Up and Down

Hello again, Code Ninjas!

In most 2D platformers the player has the ability to shift the camera a short way by holding up or down on the D-pad. In Sonic games, it works great but there is minor flaw that's hard to notice and really doesn't cause any problems, but I thought it would nice to show how to fix it anyway.

The Problem

The problem arises when the player looks up or down near the top or bottom boundaries of the level (or whatever current boundaries the camera is limited to). In order to understand the problem, we need to look at the basic idea behind shifting the camera.

If there were no ability to look up or down, the process would be really simple. The camera would simply follow the player's position directly, with a simple check to make sure it doesn't leave the level boundaries. In order to shift the camera up and down, though, an extra step is needed. An offset is added to the player's Y position before the camera follows it. By increasing or decreasing the offset value when the up or down buttons are pressed, the camera will shift vertically relative to the player's position.

Clearly there need to be maximum and minimum values that the offset can't exceed, otherwise the player could continue to scroll the camera freely until it reached the top or bottom of the level. Normally this behaviour is not desired, so limits are set that will keep the player visible on the screen.

Imagine for a moment, though, that there aren't. Say the player is looking up - the offset decreases and decreases. It will continue to decrease even after the camera stops at the top of the level, because the offset doesn't know when to quit. The camera is limited, sure, and the player never sees beyond the boundary of the level, but the offset value is still decreasing away.

Now what would happen if the player stops pressing up? The offset value will start increasing until it returns to 0 (no shift at all). But depending on how long you've been pressing up, you'll have to wait a few moments before the camera starts to visibly scroll back to the neutral position. After all, the offset has been invisibly counting away for an undetermined amount of time, and that extra time has to be made up for when it tries to return.

This example with no upper/lower limit for the offset illustrates the problem I'm talking about, and that we're going to fix. It's less noticeable when limits are present for the offset, because the value can't continue to increase or decrease indefinitely, but it's still there. If you've got a copy of any Genesis Sonic game, try looking up or down near the top or bottom of a zone and see for yourself. Scandalous, isn't it?

It may not be the worst problem in the world, but it can be fixed, so let's give it a go.

The Solution

There are actually two solutions. One would be to reduce the offset by the appropriate amount when the player lets up from pressing up or down, but that's not the solution I'll describe. Why not? Because that method would require the detection of when up or down is released, which - while certainly possible - is harder to slot right into the way the code already works in the original Sonic.

Let's look at some basic code for handling the offset, and then we'll apply the fix to it.

The problem occurs in case -1 and case 1: stopping at the shiftLimit isn't good enough, because at the top and bottom of the level, we need to stop increasing or decreasing early.

How close to the top or bottom of the level must one be in order for the undesired behaviour to occur? Close enough that the distance between the top/bottom of the camera and the top/bottom of the level is less than the shiftLimit.

This suggests the solution. Instead of using the shiftLimit alone, we should use whichever happens to lesser - the shiftLimit or the difference between the view boundary and the level boundary.

(The reason why shiftOffset has to be subtracted from view_yview is so the difference won't change once the screen starts scrolling and the offset starts to change.)

And that's all that's needed. It may not be much, but persnickety folks might enjoy the game more!

If you enjoyed this and the previous Code of the Ninja, be sure to come back tomorrow for one more before I slip back into the shadows. =P

2010-09-13

The Nitpicker's Guide to Sonic Genesis - Part II

Hello again, Code Ninjas! It has been quite a while since Part I, but never fear - slowly but surely I will give Sonic Genesis the drubbing it deserves. Welcome to The Nitpicker's Guide to Sonic Genesis - Part II.

Code Flaw #002: The Demos Are Totally Nerfed

Original:

GBA:

The demo is completely different in the GBA version, and obviously much worse (Sonic is hurt twice, seems disoriented, and demonstrates less of the level). Why should this be? If we were uncharitable, we might chalk it up to the GBA team recording new demos which betray their underdeveloped skill level. However it is much more likely that the game is using the same demo data, but changes in the physics have thrown it off.

You see, the demo "movies" in the old Sonic games were not actual videos of the action - that would have taken up so much space it would have been prohibitive. Instead, the game itself is running, but with two changes: 1) the game resets when the player hits the start button or when 30 seconds have elapsed, whichever comes first; 2) the Sonic object isn't receiving input directly from the joypad, but from a chunk of data read sequentially from the ROM. This chunk of data was made by logging the button presses while someone played the level (for more information about recording joypad input "movies", see this post).

So, if the physics or level layout changes, the recording won't be appropriate anymore. It's as if your joypad were simultaneously plugged into two Sega Genesises (Geneses?), one of which contained a Sonic 1 cart, and the other, Sonic 2. You might be playing Sonic 1 beautifully, but anyone watching the Sonic 2 game will wonder why Sonic is suddenly acting like a drunkard. (They won't notice a difference in Tails' behaviour, though - wantonly flinging himself into harm's way is business as usual for him. =P )

The team who made Sonic Genesis should have recorded new demos after the physics were in place to avoid this issue. But, one can hardly blame them for not doing so; it would, after all, have involved playing the game, and I wouldn't wish that on anybody!

Fixing The Problem In Your Own Hack

This issue with screwy demos isn't confined to Sonic Genesis. ROM hacks will have the same problem if the physics, controls, or layout are changed, even slightly. I encountered it myself when making Sonic: The One Ring, and needed to find a way to record new demos that would be compatible with the ROM. I've turned what I learnt into a tutorial and utility - go here for the complete story. See, I'm not just complaining about Sonic Genesis just to be mean - I'm using it to frame programming tips to help you guys out! Aren't I nice?

Well, not that nice since I'm now going to throw in a few freebie Bonus Flaws out of spite!

Bonus Flaw #003: Wrong Credits Footage

In addition to having broken demos, the "demos" seen during the game's credits are broken, too. But none so badly as the Labyrinth Zone one: this time, not only does the control movie not sync up because of different physics, it's totally inapposite because the wrong area of the level has been loaded!

The famous underwater section where Sonic is pulled through the tunnel by the current, catching on to the breakable poles and avoiding spikes, has been inexplicably replaced by some other region of the zone.

Bonus Flaw #004: Marble Zone Button Keeps Turning Up Like A Bad Penny

This is pretty hilarious. It's supposed to load different graphics depending on the zone ID, but they can't even get that right.

Bonus Flaw #005: Underwater Palettes Incorrect

Sonic and the Badniks don't look right...

Original:

GBA:

...but objects like doors and blocks fare even worse.

(And why does Scrap Brain Act 3 have such a horrible dark blue background? It's supposed to be a lovely, rich purple.)

Bonus Flaw #006: Missing/Incorrect Background Tiles

This flaw really does take the cake. This isn't a matter of not being able to properly port a complicated game's physics to a new platform, or something relatively forgivable. It's a simple matter of gratuitous incompetence and unconcern for the product.

Well, try to keep your lunch down, Code Ninjas - I know it's not easy after that rogue's gallery. Until next time!

2009-09-25

The Nitpicker's Guide to Sonic Genesis - Part I

Hello again, Code Ninjas, and welcome to the first ever Code of the Ninja special, The Nitpicker's Guide to Sonic Genesis - Part I.

Some Code Ninjas are a disgrace to their title - they fail spectacularly at our subtle art. Perhaps they lack the necessary commitment or training. Or, perhaps they are not entirely to blame, and the reason for their failure is a lack of time, or budget.

Either way, the results of their efforts suffer terrible scars, belying the shoddy and haphazard code underneath. This is unacceptable, for the Code Ninja should be swift, efficient, and invisible.

The outstanding example of such an unsuccessful mission is Sonic the Hedgehog Genesis, for the Nintendo Game Boy Advance. It is supposed to be a port of the 1991 Sonic the Hedgehog for the Sega Genesis (Mega Drive), but you'd barely know it. Whereas the original Sonic the Hedgehog is an exemplar of good programming by a true Goemon of code, this embarrassing port is a shambles, infamous for being the worst Sonic game ever. In fact, it has a strong claim to be the worst programmed video game ever (a distinction a certain Bubsy Bobcat is used to enjoying).

In this special series of Code of the Ninja, I aim to draw attention to each of Sonic Genesis's plenitude of flaws, with special emphasis on their likely causes. It is one thing to notice that Sonic Genesis is bad - it is entirely another to find out why. It is a testament to the degree of the abject failure of the Sonic Genesis programmers that the likely causes of the many glitches in the game are not opaque.

To be sure, I cannot be 100 percent certain of any of the causes I will cite. I do not have access to the programmers' code, nor the inner workings of their brains (and I'm grateful, for they would assuredly be terrifying), but I can make educated guesses. As a Ninja whose current mission plants him squarely in the wilds of his own Sonic engine, I am in a better position than most to make such observations.

As in the infancy of the discipline of taxonomy, before the advent of the field of genetics, one simply looked at the external features of a lifeform when classifying it. The underlying coded information, the recipe for those external features, was invisible to taxonomists at the time, just as Sonic Genesis's code is unavailable to me.

They made mistakes, certainly, especially because of the wonderful yet maddening effects of convergent evolution, but plenty of good work was done, enough to cement the endeavor as respectable.

It is in this spirit that I undertake nitpicking Sonic Genesis. Whether all of my evaluations turn out to be true or false, I hope many of them will be incising insights, which will arm inchoate Code Ninjas and help them avoid the same traps and pitfalls (some of which the Sonic Genesis programmers' feet are still sticking out of, accompanied by contented digestive noises).

As a bonus, I will be pointing out some extra flaws each time which were not the result of programming.

Code Flaw #001: Sonic is not synched to moving platforms

Original:

GBA:

Programming moving platforms in a video game is actually relatively easy. When the character object detects a platform, it remembers the ID of the platform. From then on (until the character falls or jumps off the platform), the platform's motion is simply added to the character's.

Sounds easy enough. But a lot of beginners (including me, back in the day) are surprised to discover upon running their game, that the character's movement is not perfectly synchronised with that of the platform.

It turns out that it all relies on the order in which the code is performed. Every frame of the game (and there are usually 60 per second), the objects perform their code. But they can't do this at the same time - they queue up and do it one after another.

If the platform moves first, then Sonic follows suit. Then the screen is refreshed, and the player sees Sonic stuck fast to the platform. All is well.

But what if the platform comes later in the queue than Sonic? Then, Sonic moves based on the speed or position that the platform had in the last step. Then the platform moves to its new position. Then the screen is refreshed. The player sees Sonic juttering about the general vicinity of the platform, but not firmly atop it. Sonic is lagging behind, basing his position on variables that are one frame out of date!

Unless all moving solids complete their code before the character object's routine is run, this will be a problem. In Game Maker, this would amount to putting the platform routines in the "Begin Step" event.

Apparently the "programmers" of Sonic Genesis were too rushed or lazy to bother with this simple fact, and so they fail to achieve decent moving platform physics - something that early NES games can do in their sleep. It's pretty pathetic, when you think about it.

Bonus Flaw #001: The background in the title screen isn't animated

Not only is there no paralax, and the clouds don't blow by on the breeze, but the waterfalls and sparkles on the surface of the lake are totally frozen! The GBA can palette cycle, so there seems to be no explanation for this besides sheer sloppiness.

Bonus Flaw #002: There is no shrapnel when crushing through walls

Yes, folks - the segments of rock (or metal, in Starlight Zone) simply disappear, accompanied by a lame "poit" sound effect that is nothing like the original. I'm guessing that the 6 month delay still wasn't enough time to implement a few bits of shrapnel flying away.

Well, that's it for now. The normal Code of the Ninja will not be interrupted by the Nitpicker's Guide, so I'll see you next time.

Happy coding!