4 Hugues Ross - Blog: 06/01/2019 - 07/01/2019
Hugues Ross

6/18/19

Back to AMAZE - 3 - Lookin' Good!

In just under a week, I've managed to produce a mockup for the remake. To keep things simple, I decided to redraw the screenshot on AMAZE's game page. I think the results speak for themselves:


Honestly, I'm really happy about how this one turned out. The environment feels like it came straight out of my nostalgia. But that's enough gushing, let's examine some of the changes.

What's New?

If you compare this mockup to the original, the first thing that you might notice is the difference in size. The new one is actually much smaller than the old one. This might seem odd, considering the difference in detail, but in reality the mockup just uses space more efficiently. The original was 640×480, but for the new one I decided on 480×270. This was for two reasons:
  1. It scales perfectly to 1080p (×4)
  2. It gives a similar aspect ratio to the original's viewing area, but with 24px tiles instead of 32px
32 was a really common number to see back when I started making games with GameMaker 6.1. However, I've since come to appreciate 24×24 as a tile resolution because it strikes the perfect balance of time investment and detail. 16 tends to be difficult to work with, and I often feel limited by it. On the other hand, 32 often feels like overkill, and the extra effort feels like a waste of time. 24 gives just enough space to add good detail without creating much more work.

Next up is the UI. The original game has this big tacky bar at the top of the screen with information on it. Most of the information on the bar is fluff, and I'm not convinced that your average player would even recognize what some of the bars meant.

By contrast, the new UI is simpler. The Nintendo-esque counters on the top-left are pretty clear, and the only other element is the active tool. You'll notice that I opted for numbers as opposed to the bar in the original. That bar's ticks didn't always line up with the actual number of uses, which is kind of an insane design decision in retrospect. This time, I wanted it to be perfectly clear.

The last big changes are, of course, the collectible and enemy sprites.
I mostly swapped those out to better fit the rest of the game. It seemed odd for a robot to be dodging fireballs in a cave for gold and jewels, and by changing the sprites I felt that I could get everything to fit a common theme.

The Next Stage

I think I'm happy enough with this mockup to move on. As I explained last time, my next goal is to get the old data working in a new codebase. That means putting this mockup away for the time being, but with any luck it'll be back soon!

Next Time: Good Old-Fashioned Programming Updates

6/11/19

Back to the Maze - 2 - Setting Our Sights

I had my fun in the last post, now it's time to get a little more serious. If I want this project to be successful, I need to plan a little in advance.

What Do I Want?

Before jumping deciding into this project, I think it's worth stepping back and deciding if I really want to do this. In this case, it's not actually a hard decision to back up. I was never really content with how the game wound up the first time around, that much is made clear by the release and postmortem. I've toyed with the idea of remaking the game for several years, but it never really felt like the right time. With Halberd shuttered, my slate is now pretty clean.

My drive here is pretty simple. I think AMAZE was a fun concept, but the execution was really lacking. With my vastly improved programming and art skills, I think I'm in a good position to do it justice. That informs my goal:

"Take the original concept of AMAZE, and turn it into a fun and polished result"

About the Project

Overall, AMAZE is actually on the smaller end. At almost 4kloc it's slightly bigger than Cloudy Climb, but dwarfed by Halberd and DFGame. Besides the antique rendering, it has a few odd quirks:
  • A custom scripting language, which looks like this:
    Component Physics yvel 0
    Component Physics xvel 2
    Wait 48
    Component Physics xvel 0
    Component Physics yvel 2
    Wait 48
    Component Physics yvel 0
    Component Physics xvel -2
    Wait 48
    Component Physics xvel 0
    Component Physics yvel -2
    Wait 48
    Repeat 
    • And yes, those times are measured in frames
  • A data-driven pseudo-ECS, but with (originally) no concept 'prefabs'. Besides some later parts of the project, every object in every level was made by hand.
    • And where prefabs were made, the files looked like this:
      Body
      0 4 0 0
      Collide
      ?P Kill . !c Info solid false = Destroy .
      Sprite
      Obstacle.spa:Cannonball
  • A sprite format that looks rather similar to DFGame's, but with separate images per-frame instead of spritesheets. This was the original prototype that eventually led to DFGame's (improved) sprite assets.
  • My first ever attempt at graphical development tools, in the form of a simple asset exporter. This was needed, because the asset format is compressed binary for some reason.

Baby Steps

Starting over completely from scratch doesn't seem like a great idea. Instead, my current plan is to handle this remake in stages:
  1. Mockups
  2. Recreate the engine, and get the original game's data working in it
  3. Upgrade the engine, adding features and replacing the godawful custom scripting language with something sane
  4. Build simple development tools, validate them by remastering the original game's content with new assets
  5. Build a better game with the result, and include the redone original levels as a fun little bit of side content
This is a fair bit of work, but I'm optimistic because it's broken up into concrete sub-projects with well-defined endpoints. If I ever get tired of the project, I can just wrap up the stage that I'm on and set it aside for a while.

Looking Deeper

Just making a list is all well and good, but I think it would be good practice to try and predict the future. My predictions are often wrong and I don't believe in ship dates, but it might be interesting to compare predictions with reality as I progress.

Mockups

As I mentioned in part 1, this all sprung from a desire for mockups. Since that was my original goal, it stands to reason that I should do that first. This is a simple art project, so there's no real risk here. I expect a couple weeks of work for a couple simple mockups, and judging by the original screenshots there's a solid guarantee of improvement regardless of what I do here.

Time estimate: 1-3 weeks

Recreate the Engine

This is an interesting one. Given that I'm changing language and underlying APIs, it's safe to say that I'm going to have to commit to a full rewrite. On the other hand, I don't have to worry about tools, exporters, or asset creation. In the old codebase, that's a modest 2.5kloc and I have a spec to build against.

On a third hand, there's one possible stumbling block: Asset loading. AMAZE had fully custom binary asset files (because of course it did), and I'll have to translate those into some DFGame equivalent when loading. This, combined with a custom scripting language and nonsense code, could create some problems. Just to be safe, I think it's worth padding this figure.

Time estimate: 3 1/2months

Beyond the initial engine rewrite, I'm not confident in any predictions. Stage 3 could last forever if I kept adding features, and beyond that requires more design and scope considerations. Still, what I've looked at here feels like enough to wrap up most of the year.

I haven't yet decided how I want to handle this project from a blogging standpoint. I know for certain that I'll be capping off each stage with a post, but I haven't yet worked out how much I want to do in-between. We'll see when we get there.

Next Time: Actual Real Progress

Back to the Maze - 1 - TERROR AND MADNESS

I think it's about time for another video game. In the spirit of last year's Cloudy Climb remake, why not take another of my old games out for a spin? Why not, say, AMAZE?

What could go wrong?

And So It Began

It all started innocently enough: A certain art community that I'm in announced an activity about re-drawing an old piece of work. Just for fun, I thought I'd grab a screenshot of AMAZE and make a mockup in my style. However, AMAZE doesn't really work anymore. The Linux build compiles, but the old renderer doesn't, err, render.

The Windows build fared a little better in Wine, but crashes frequently. I can't really blame Wine for this, I'm not entirely convinced that the game is crash-free under normal circumstances...

Foolishly, I began to think: "Man, I wonder what that drawing code looks like? Maybe it's a simple issue, I'll bump up the OpenGL version and we'll be all set."

As it turns out, the game is running OpenGL 2.1's fixed-function pipeline. For the unaware, OpenGL 2.1 is roughly 13 years old at this point, which is very interesting considering that AMAZE is only 5.

Delving Deeper

So, ok, maybe the rendering code is unsalvageable. You win some, you lose some. However, I thought it might be fun to poke around the rest of the codebase and see how I used to write code.

After all, why not take a fine trip down memory lane?
   23 class Game{
   24  public:
   25   Game();
   26   void loadspr(string s){Sprite spr = spriteLoader.load(s); sprites.insert(pair<string, Sprite>(spr.id, spr));}
   27   Sprite* get_sprite(string s){return &(sprites[s]);}
   28   void frameStep();
   29   void gameLoop();
   30   void end();
   31   bool get_keydown(byte key){return keys[key];}
   32   MapLoader* get_map(){return &maploader;}
   33   float camera_x = 0;
   34   bool camera_xlock = false;
   35   float camera_y = 0;
   36   bool camera_ylock = false;
   37   float camera_xscale = 1;
   38   float camera_yscale = 1;
   39   void drawText(float xpos, float ypos, SpriteFont f, string text);
   40   unsigned score = 0, level_score = 0, hub_score = 0;
   41   unsigned equipment = 0;
   42   float equipment_percentage = 0;
   43   unsigned lives = 3;
   44   unsigned max_lives = 3;
   45   unsigned hub_max_lives = 3;
   46   game_states gamestate = SPLASH;
   47   float splash_alpha = -1;
   48   float splash_timer = 30;
   49   string disptext = "";
   50   vector<c_CResponse*> active_scripts;
   51   vector<int> script_positions;
   52   vector<int> script_timers;
   53   vector<pair<int, int>> script_args;
   54   void updateScript(c_CResponse* script, int& position, int& timer, pair<int, int> arg);
   55   ScriptLoader scripts;
   56   unsigned disptxttimer = 0;
   57   bool keys[5] = {false, false, false, false, false};
   58   int menu_select = 0;
   59   string hub_level = "Z1-Beginning.rma";
   60   string hub_tileset = "Tutorial_tiles.tla";
   61   string hub_song = "/TitleMusic.ogg";
   62   string current_song = "/TitleMusic.ogg";
   63   int hub_progress = 0;
   64   map<string, ALLEGRO_SAMPLE*> sounds;
   65   bool scripts_run = true;
   66   bool game_running = false;
   67   MapLoader maploader;
   68   int triggers[10] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
   69   string var(string input);
   70   void alter(string key, string val);
   71   bool compare(string key, string val, char op);
   72   float sound_volume = 1.0f;
   73   bool godmode = false;
   74   void writeSave();
   75   bool dead = false;
   76   bool shield = true;
   77   int dcooldown = 60;
   78   int difficulty = 0; //0: easy, 1: med, 2: hard
   79  private:
   80   Sprite* s;
  ... 
  105 };
  106 #endif
oh no.

Some of you may be wondering to yourselves: "Strange, what's that 'Sprite* s' for?"
ABSOLUTELY NOTHING


Next Time: Busting out the hacksaw