23 August 2009

Some Thoughts on Internet Piracy

This post is inspired by this Stephen Fry podgram.

There are those who would equate internet piracy with stealing. To do this is in essence correct, but it ignores a large grey area which I think must be addressed.

First, I want to point out that I in no way am an advocate of large scale piracy - ripping DVD's or CD's right after release or even weeks before, and then turning around and disseminating the content for your own monetary gain. This is arguably just plain wrong.

Instead, what I'll be talking about is the average person who shares music, who downloads a TV show because they can't get it in their country, or because it is no longer available on the air, or even on disc, or who mods their console to play different region games. This is the sort of 'piracy' I'm talking about.

Now we need to get another thing straight. I'm not going to go into OED definitions of stealing and piracy. I'm going to do something that is usually forbidden by my own rules, and redefine the terms for the purpose of this discussion. I feel the need to do so because the world is changing, and traditional concepts of theft just don't hold.

There are two ways to steal. One is to take something in such a way so that the original owner doesn't have access to it anymore. If I stole your car, this would be an example of what I'm talking about. You'd have to buy a new car, or walk to work. That would suck, and I should be duly punished for such a crime.

Way number two is to take something in such a way so that the original owner maintains access to the article, but is still sometimes inconvenienced as a result. If I were to somehow build an exact duplicate of your car, complete with the custom paint job, and drive around town, perhaps going to seedy places, and people mistook me for you due to the similarity of our vehicles, this would be an example. You'd be inconvenienced, but I wouldn't have technically stolen the car. I might have negatively impacted your life, and that would suck. But most would agree that any such punishment I would incur, if any, should be of a lesser magnitude than if I had actually driven away your real car while laughing maniacally.

This is pretty much a thought experiment. No one is going to build a duplicate of your car. For centuries, no one had to worry about this distinction. Taking food, or money, or tools from someone was theft, plain and simple.

Today, with the advent of digital transference of data, this sort of thing is now possible. If you make a song, a story, or even a whole movie, everyone in the world can take it from you, while leaving the original unchanged. They are no longer taking anything from you at all.

Or are they? If you make your living by charging people to see your bit of art, and someone else copies it and puts it up for display, you lose your income. They aren't just stealing a thing, they're stealing your livelihood. To add insult to injury, they usually make a buck off it, too.

This is unacceptable, of course. But the reality is that it's still not that simple. Whereas in the good old days, when someone took something from you, the loss was obvious and apparent. 'A thief stole my wallet. Thus, I am short one wallet.' But with this new method of theft, your loss is less salient, and must be calculated. And this is where it all falls apart. They aren't stealing anything from you that currently exists, but something that would have, or more likely, just might have, existed - i.e. future profit, or sales.

Let me make this very clear. No longer is it true theft, but simply an action that cuts into your future profit. The act must then be punished accordingly. Why? Because someone who cuts into profits must be punished. Right?

Well, if that's true, let's examine the situation to see what else might cut into profits. Let's take a movie, for example. What sort of things might also cause a movie to not make as much money as expected?

  • It might suck.
  • There might be a popular video game released on the opening weekend.
  • There might be, oh I don't know, swine flu. Or a bad economy.
  • The theatre might not be well situated, or air-conditioned.
  • The film may not have been advertised sufficiently, or well.
  • A critic at an early screening may have hated it, and said so.
  • The film may have content that prevents certain groups, such as minors, or Christians, from seeing it.
  • Another film may be competing with it, sucking up the moviegoers.

Or, somebody may have put it up on a torrent site.

We now see that this so-called 'piracy' is just one more factor in a long list of things that cuts into a film's financial success. It's not theft, it's just a nuisance. It may be the straw that broke the camel's back, and it might be easily punishable. For instance, it can be traced to one zitfaced teenager with a camera in the audience, who is easily blamed. Who do you blame when your movie sucks? But isn't it just as easy to trace a film's failure (or lack of projected success) to a single critic? Do critics get gaoled or fined for cutting into profits? No, they're payed for it. I suppose they alway have the capability of increasing profits for some other lucky movie. But doesn't piracy have that capacity, as well? Raising people's attention to the existence of the film?

I guess what I'm trying to say is that no major media corporation has been utterly obliterated by piracy as of yet. They post losses, they make wild calculations about what might have been, and they whine, and they want to hold someone responsible. Hey, I'd like it if I could take people to court for not paying me money to watch my antics, too, but it's not gonna happen.

Ultimately, piracy cuts into profits. Just like competition. And America thrives (or is supposed to thrive) on competition. When you start losing money to the other guy, step up your game. Think of piracy like the other guy - like competition. When piracy cuts into sales, and you start losing money - step up your game. Should theatres have killed TV? Should cable have killed VHS? No, and moreover, they didn't. And media corporations shouldn't - and I think, won't - kill piracy. The smart thing to do is to compete. No back-stabbing, no throat-cutting. If you don't want people to pirate, make them not want to pirate. I can't download the IMAX experience. I can't e-mail myself to a concert. I can't print out a limited edition Master Chief figurine. Maybe someday we'll be able to, and then the corporations will have to step up their game once again. That's what they do, that's how they survive. But we'll continue to cause friction, trying to bring them down. Cos we're weasels, and that's what we do.

Stop gouging the little guy. Stop threatening teenagers with gaol time who just want to play foreign games. Stop denying the average citisen a well-rounded cultural education just because you want a buck for every song they'll ever hear.

And, for the love of all that's good, stop whining and looking around wildly for someone to blame when your movie doesn't make as many millions of dollars as your over-eager marketing department thought it would.

07 August 2009

Code of the Ninja: At the Movies

If you haven't already, read the Code of the Ninja: Introduction

Welcome back, Code Ninjas!

Last time, we learned how to implement a joypad system using binary values. Now we're going to be expanding on that a little by adding some scripts for recording joypad "movies".

Joypad movies can be useful in many ways. For one, you can easily record yourself playing a level of your game to make a demo, which might play if the player lingers on the title screen for too long. Or, you might allow players to record themselves, and share these movies as a way to show off their skills.

But they are also useful to you, as the game developer. If there is a bug or unexpected behaviour in your game, just start recording the joypad, and then trigger the bug. Now you can replay that sequence over and over until you determine the source of the bug. Then, once you think you've fixed the bug, you can play it once more to make sure that it's absolutely gone.

Also, if you want to record a true video movie of your game, for instance to make a YouTube trailer, many programmes that record video of the game window will slow down your game. If you record a joypad movie first, and then make a video of the joypad movie being played back, you won't have to actually be playing the game while it's slow.

Well, let's get to it!

Joypad movies will be saved to binary files. Our first script is save_joypad_file, which will set things up to start writing input to a file. It takes one argument, which is the name of the file to write to.

save_joypad_file()

close_joypad_file();
joy_mode = 2;
jfile = file_bin_open(argument0,1);
file_bin_rewrite(jfile);

You'll notice that the first thing it does is perform the script close_joypad_file. That's our next script. It takes no arguments.

close_joypad_file()

if joy_mode > 0
  {
  joy_mode = 0;
  file_bin_close(jfile);
  }

This script has to be called first so that if you call save_joypad_file while it is already recording or playing back a joypad movie, it will close the first one automatically.

It is also important to call close_joypad_file in the game end event of the Joypad object.

Next, let's write our next script, open_joypad_file. Like save_joypad_file, it takes one argument - the name of the file to read back.

open_joypad_file()

close_joypad_file();
joy_mode = 1;
jfile = file_bin_open(argument0,0);
jsize = file_bin_size(jfile);

You'll have noticed by now that the preceding three scripts all set the variable joy_mode to a value - 0 for normal, 1 for playback, and 2 for recording. Where joy_mode comes into play is in the next script - joy_step.

This script, joy_step, is not new. We wrote it in the last lesson, but it needs to be rewritten to accomodate the joypad movie system. Replace the whole script with this code:

joy_step()

JoyPast = JoyPrevious;
JoyPrevious = JoyCurrent;

if joy_mode = 1
  {
  JoyCurrent = file_bin_read_byte(jfile)+
  file_bin_read_byte(jfile)*256;
  jpos = file_bin_position(jfile);
  jprog = jpos/jsize;
  if jpos >= jsize
    {
    close_joypad_file();
    }
  }
else
  {
  JoyCurrent = 0;

  for (t=0;t<6;t+=1)
    {
    if joystick_check_button(1,Joy[t])
    or keyboard_check(Key[t])
    JoyCurrent |= 1<<t;
    }

  if AnalogCount
    {
    if joystick_xpos(1) < -AnalogDeadzone JoyCurrent |= LEFT;
    if joystick_xpos(1) > AnalogDeadzone JoyCurrent |= RIGHT;
    if joystick_ypos(1) < -AnalogDeadzone JoyCurrent |= UP;
    if joystick_ypos(1) > AnalogDeadzone JoyCurrent |= DOWN;
    }
  }

if joy_mode = 2
  {
  file_bin_write_byte(jfile,JoyCurrent mod 256);
  file_bin_write_byte(jfile,JoyCurrent div 256);
  }

First it checks to see if joy_mode is 1, for playback. If so, it reads from the file, updating a variable called jprog (you can take that bit out if you want, but it's useful for drawing a progess bar), and calling close_joypad_file automatically when it reaches the end. If joy_mode is something other than 1, input is received from the joypad and keyboard as normal, instead of from the file.

Next, it checks if joy_mode is 2, for recording. If so, it simply writes the value of JoyCurrent to the file as two bytes. If you choose to use more than 8 buttons (this tutorial only uses 6), the second byte will be necessary.

Okay! Now, all you have to do is include some interface for calling open-, save-, and close_joypad_file, and the rest will be taken care of. The simplest way is to call them in events for the press of function keys in the Joypad object.

Before we close this lesson, there is one more cool thing that recording joypad movies can do for you. Ghosts!

In Mario Kart, one of the coolest features is the ability to race against "ghosts". A ghost is just another driver, but with one important distinction. Instead of being controlled by computer AI, it is being controlled by a joypad movie. In this way, you can race against yourself, or go head to head with legendary runs by expert players, made years ago!

So, how do you include ghosts? If you simply play back the joypad file, JoyCurrent will receive its value from the file, and not the joypad, and your player object will follow the movie. We don't want that, though. We want the player object to be left alone, and have a new ghost object that follows the movie, co-existing with the player object. How do you get a ghost to play from the file, but leave the player object controlled by the joypad?

Well, you have to use a second set of variables besides JoyCurrent, JoyPrevious, and JoyPast. Let's call them gJoyCurrent, gJoyPrevious, and gJoyPast. These won't be global, but local to the ghost object, which should be a copy of the player object. Next, we'll need another set of scripts for checking them. Duplicate the joy, joy_pressed, and joy_released scripts and call them gjoy, gjoy_pressed, and gjoy_released. Then edit them so that they query gJoyCurrent, etc, instead of JoyCurrent.

Now, in the ghost object's code, replace anywhere the joy scripts were called with the gjoy scripts. Finally, you have to add code for opening, reading and closing the joypad movie in the ghost object, so that it does it all independently of the rest of your game.

I'd go into more detail about this last step, but it's easier to just look at it to see how it's done. You can use the link below.

Download the GML scripts and a GMK example of this lesson here.

Next time - Smart Triggers! Until then, happy coding, fellow Code Ninjas!

If you use my code or scripts in your game or engine, no credit is necessary. But I'd love to hear about your project if you do! Just drop me a comment below, or e-mail me at us.mercurysilver@gmail.com

03 August 2009

Code of the Ninja: Joy to the World

If you haven't already, read the Code of the Ninja: Introduction

Welcome to your first lesson, my esteemed Code Ninjas in training! You have come here seeking knowledge of the Code in order to create your own video games. This is a worthy goal. It is my hope that I can teach you valuable lessons that will allow you to fulfill your dreams more quickly and capably. Armed with the secrets I shall reveal, you will be able to make your game engines more professional and - dare I say it - more fun. I do not mean to illude you - in no way can I make the path you have chosen easy. Game design is hard work. But I can make it easier.

Today's tutorial is about handling the player's input. Almost everyone playing your game will have a keyboard, but the keyboard is not the ideal input device for classic games of the type we want to make. Mario, Sonic, Metroid, Zelda, Final Fantasy, Klonoa - all these games are designed for joypads. So, ideally, for players who have access to PC compatible joypads, they should have the choice to use either their joypads or keyboards.

But it can be somewhat tricky to programme your game to either query both, or decide which one to query depending on the player's choice. Furthermore, Game Maker doesn't natively have very complete joypad functions.

These things are what I aim to teach you to overcome.

First, create a new object. We'll call it Joypad. It should be set to persistent, so that it is always present, even when the player moves between rooms. It shouldn't be visible, or have a sprite. Then you should place an instance of Joypad in your initial room, the one that your game starts in.

Make a script - we'll call it joy_init, and put it in the create event of Joypad.

Let's start writing joy_init:

joy_init()

globalvar JoyCurrent, JoyPrevious, JoyPast, Key, Joy, AnalogCount, AnalogDeadzone;

AnalogCount = joystick_axes(1) div 2;
AnalogDeadzone = 0.25;

What we're doing here is setting up a few global variables that can be referenced easily by every object in your game. The variable AnalogCount is set to the number of analog sticks the player's joypad has. We determine this by returning the number of axes and dividing by 2. Then we set up the variable AnalogDeadzone. A dead zone is essential when an analog stick is concerned. Neutral is 0, full on is 1. But due to the sensitivity of most controllers, the stick is never exactly at neutral, but fluctuates around .1 or even .2. A lot of driver software lets people set up dead zones for their joypads automatically, but we can't always bet on that. It's better to have your game take them into account. You can supply any value you think is reasonable (I've used .25 here), but it's even better if you include some option for the player to adjust the dead zone values manually - perhaps even independently for each stick.

Next, we add this to joy_init:

joy_init()

Joy[0]=3;
Key[0]=97;
Joy[1]=10;
Key[1]=13;
Joy[2]=13;
Key[2]=38;
Joy[3]=15;
Key[3]=40;
Joy[4]=16;
Key[4]=37;
Joy[5]=14;
Key[5]=39;

These are the joypad buttons numbers (Joy[]), and the keyboard keycodes (Key[]) we'll be using later to check for input. I've only done 6 buttons here, 0-5, because that's all a classic Sonic game really needs, but you can include as many as you'll be needing in your game. Any more than 16, though, is probably not a good idea, since most joypads won't have that many buttons. I've also entered the keycodes as raw numbers, but you can use the vk_... constants, or the ord() function as well.

Alternatively, you can read values into Joy[] and Key[] from an ini file, or even include an interface for the player to change them manually, which is best. Control configuration interfaces would be a tutorial in their own right, though.

Now that we know which buttons and keys we'll need to be checking for, we need to give them names. This is just a convenience for the programmer. I suggest using constants, and naming them after buttons on a console controller, such as A, START, LEFT, etc.

For example:

Code:

A = 0;
START = 1;
UP = 2;
//etc...

If you did the above, then you could type Joy[A] or Joy[START] instead of Joy[0] or Joy[1]. This is useful, especially for the direction buttons, since remembering which number corresponds to each of the four can be difficult.

But, actually, we're going to be doing something just a little more complicated than just making A = 0 and START = 1. We're going to be using some binary shifting, and you'll see why a little later.

Instead of setting A to 0 (or whichever number you want to call "A"), we'll be setting it to 1 left-shifted by 0. START will be set to 1 left-shifted by 1, and so on. This is what the code should look like:

Code:

A = 1<<0;
START = 1<<1;
UP = 1<<2;
//etc...

That means, in binary, A = 1, START = 10, and UP = 100.

Now that we've got our constants named, it's time to make a new script - let's call it joy_step - and put it in the begin step event of Joypad.

joy_step()

JoyPast = JoyPrevious;
JoyPrevious = JoyCurrent;
JoyCurrent = 0;

for (t=0;t<6;t+=1)
  {
  if joystick_check_button(1,Joy[t])
  or keyboard_check(Key[t])
  JoyCurrent |= 1<<t;
  }

What the for loop does is set up a binary variable, JoyCurrent, where each bit corresponds to one of the buttons being active. It checks both the joypad and the keyboard, so either one the player uses will work.

So, by default, both the joypad and keyboard are detected by the game and no setup or choice between the two is necessary. Although, it's very easy to rewrite the loop to not check for one or the other, if for instance you wanted to let the player choose which mode they'd rather use. Some people may not have a joypad at all and there's no reason to do extra checks.

So, if either the joypad button or the keyboard key (or both) is detected for button 0, JoyCurrent becomes a value of 1. If no other button is detected, it remains a value of 1. But if another button is detected, the new value is or-ed together. If both buttons 0 and 1 are detected, for instance, JoyCurrent becomes a (binary) value of 11. In this way, with only one variable, you can store which buttons are being detected during this step.

Of course, before the loop runs, we dump the value of JoyCurrent into a buffer value called JoyPrevious (and, one step further, dump JoyPrevious into JoyPast). This is going to be used to detect pressing and releasing the buttons, akin to the keyboard_check_pressed() and keyboard_check_released() functions. We could probably get by with only two values, JoyCurrent and JoyPrevious, but some joypads are subject to signal noise, and the addition of JoyPast will improve things in those cases. For instance, the device I use to convert my Nintendo Gamecube to be compatible with a PC stops detecting some buttons for an instant while others are rapidly pressed. This makes performing the spindash in Sonic 2 nearly impossible, because the Down button stops registering when the A button is tapped, causing Sonic to launch early.

Now we write another script, just called joy.

joy()

return (JoyCurrent&argument0);

Now, as long as the Joypad object is present in the room, any object can call the joy script to test for buttons. For example:

Code:

if joy(A)
  {
  //make the player jump
  }

if joy(B)
  {
  //make the player attack
  }

if joy(LEFT)
  {
  //move the player left
  }
else
if joy(RIGHT)
  {
  //move the player right
  }

Now, what about presses and releases? If you used something like the code above, the character would continually jump as you held down the A button. What we need is another script - joy_pressed.

joy_pressed()

return (JoyCurrent&argument0) and !(JoyPrevious&argument0) and !(JoyPast&argument0);

This will only return true when the button is active in this step, but not in the previous step, or the one before that.

And now for joy_released.

joy_released()

return !(JoyCurrent&argument0) and !(JoyPrevious&argument0) and (JoyPast&argument0);

This script only returns true if the button is not active during this step or the one previous, but was active in the step before that.

And there you have it. A very simple way of having robust joypad and keyboard support for your game, that's fully customisable to boot. If you don't feel like including an interface for mapping keys and buttons in your game, at least include an ini settings file. Nothing is more annoying than actually having joypad support in a game, but then finding that the buttons are all mapped wrong!

And finally, if you want to use an analog stick to emulate the directional buttons, you can add this code to the joy_step script we made.

joy_step()

if !AnalogCount exit;
if joystick_xpos(1) < -AnalogDeadzone JoyCurrent |= LEFT;
if joystick_xpos(1) > AnalogDeadzone JoyCurrent |= RIGHT;
if joystick_ypos(1) < -AnalogDeadzone JoyCurrent |= UP;
if joystick_ypos(1) > AnalogDeadzone JoyCurrent |= DOWN;

You can check as many axes as you want, of course. You can even use code like this to make the right-hand analog stick emulate the X, Y, and Z buttons like in The Legend of Zelda - Ocarina of Time (Nintendo Gamecube version). The great thing is, since the values are or-ed together, either the buttons or tilting the stick both work.

nd that's it! You've got a complete joypad system that takes input from either the joypad or keyboard, and is incredibly easy to use.

Download the GML scripts and a GMK example of this lesson here.

Until next time, happy coding, fellow Code Ninjas!

If you use my code or scripts in your game or engine, no credit is necessary. But I'd love to hear about your project if you do! Just drop me a comment below, or e-mail me at us.mercurysilver@gmail.com

Code of the Ninja: Introduction

Programme code must be quick, efficient, and invisible - like the Ninja.

Every post with the "Code of the Ninja" tag will offer valuable lessons in video game programming.

The tutorials herein will be designed specifically for Game Maker 8 Pro, which I use, but most of the knowledge gained can be universally applied.

Readers are assumed to already have a working knowledge of GML, and not just the drag-and-drop features of Game Maker. Beginners with little knowledge of GML are encouraged to check out the Game Maker Community forums to learn more.

Also, they say a wise teacher learns from his students as well. If you have any improvements to suggest or notice any mistakes in the pieces of code I post, please let me know in the comments.

01 August 2009

Game Review: Sonic Rebirth

Sonic Rebirth is another Sonic fan game, showcased at this year's SAGE. It's a remake of the original Sonic the Hedgehog for the Mega Drive. The full version was just released today, which is why I didn't post my thoughts on it earlier.

Since I'd first seen the Sonic Rebirth SAGE booth a few days ago, I'd been really looking forward to it. The screenshots looked impressive, and the fact that there were remakes of the 8-bit zones mixed in was intriguing, as well.

Now that I've downloaded and played through the story mode, I have, as they say, some good news and some bad news. Let's start with the good news.

It's Finished

The most exciting prospect about Sonic Rebirth was that it was a complete game, not yet another demo. Thus playing it will give you a full gaming experience, and not just a few moments of excitement over a tech demo. Beyond simply having the full complement of zones, it also has extras - such as cinemas, emblems to collect, and unlockable artwork.

The Sprites

The sprite set for Sonic is nearly perfect. It's easily the best set I've ever seen used in a fan game. If the sprite set is borrowed from somewhere, and not original to Sonic Rebirth, then I apologise for giving it the credit. But I haven't seem them elsewhere.

I am, though, confused as to why there is a choice in the options menu to play with the original Mega Drive sprite set. I believe players should be given freedom of choice in many areas, such as control and difficulty, but allowing them to change the art style of the character seems a little much. It also makes the experience feel less authentic, because I can't imagine a professional Sonic game giving you such a choice.

Sadly, that's all the good news there is. Now on to the bad news.

The Physics

While the game doesn't have many overt flaws in the physics engine, there is an overall sluggishness to Sonic that makes it hard to get used to for anyone who is well-aquainted with the Mega Drive titles.

This in itself is an easy enough thing to ignore - with practice, you get more used to it. The same has to be said for Sonic Advance, or Sonic Rush. But where the physics really fail isn't Sonic's movement, but everything else. The timing for all of the Badniks and their projectiles is so off that it makes them look like they are on fast-forward. The platforms in Scrap Brain are timed in an almost perverse manner that it makes it nearly impossible to play comfortably. The bosses pose much less of a challenge than in the original. And the see-saws in Starlight Zone are so badly done that sometimes the little spiked balls fall right through them, rendering them useless!

The Music

The music (both MIDI and OGG) is so badly remixed that it would have been better to just leave it alone. The 1-up and Zone Clear jingles are painful, and the bass instruments in particular are atrocious.

Fortunately the music is stored separately, so we can hope for improvements in the future.

The Graphics

Sadly, the graphics are inconsistent. Instead of being as good as the sprites throughout the whole game, some parts are gorgeous (such as the Bridge backdrop), and others are hideous (Scrap Brain in general).

Furthermore, instead of improving on the original, in some places it takes a step backward. The flowers in Green Hill don't spin, Marble's backdrop is completely butchered, and the lights in Starlight don't glow. Speaking of Starlight, that enormous moon in the sky destroys the entire feeling of the zone, and the stars move faster than it does, making them seem to go in front! Inconceivable.

The Story

There really is no story, though there are about 4 cinema scenes. Instead of trying to add to the story of the game, by perhaps explaining Robotnik's motives and why Sonic travels to these locations in particular, they (perhaps wisely, perhaps not) did little more than illustrate what is implied in the original. There is nothing to be gained by watching them, but they are mercifully short, and skippable.

The cinemas aren't drawn badly in their own right, but they are a completely different style than the rest of the game. It is bizarre to see a green-eyed, modern Sonic on the top of the screen, but the portrait by his text box is black-eyed, classic Sonic. Also, the Emblem Screen that is shown when you complete a zone is hideous, and looks transplanted from a much less polished game. There needs to be a consistency of style. I know that several artists worked on the game, but they needed to set some standards, as Sega does when using multiple artists. It's called a style sheet, people!

The Gameplay

This is the aspect that I'm most disappointed by. I can allow for graphics and music not being up to par. I can't really expect them to be on a professional level to begin with. But there is little excuse for such sorry gameplay.

The zone layouts are minimally changed, making the occasional slightly different area jarring. Also, most of the changes seem to be made to avoid programming a difficult part of the original.

There is nothing added - there are no new puzzles, platform types, loops, or tricks that aren't present in the original. Sonic has no new moves to speak of. In fact, lots of things are subtracted. Gone are many enemy types, and hazards. No more Flame Throwers in Scrap Brain? No more Caterkiller, or Newtron? What's the point in remaking Sonic the Hedgehog if, instead of improving it, you cut out half the gameplay that made it fun in the first place, and with nothing to make up for it?

The Bottom Line

Overall, there is nothing to be gained by playing Sonic Rebirth instead of the original Sonic. I can't see anybody preferring to spend their time playing a gimped version of a good game simply because some texture is added to the blocks and grass.

One thing that could have helped save it is the inclusion of the 8-bit levels, expanding the main game into a longer narrative. Instead, they are unlockables that can only be accessed after you collect enough emblems. Well, I'm sorry, but most people probably won't want to suffer through the zones enough times to unlock them. So, probably the best part of the whole thing is also the part you don't get to access right away.

I hope that most of these issues are due to the fact that the game might have been rushed in order to have it ready to show at this SAGE. If that's the case, we can look forward to patches and improvements that address some of the issues. Ultimately, though, even with all the outright bugs fixed, there doesn't seem to be much point in a remake that subtracts instead of adds.

Maybe someday the sprite set will be reused in a game with original zones that's a little more exciting.