4 Hugues Ross - Blog
Hugues Ross

9/2/19

Art Project - RPG16

Ever since it came out in high school, I've really loved Minecraft. Despite never mentioning it here, I've sunk hundreds of hours into it over the years. And that's why I'm worried about the future.

Some years ago, Microsoft bought Minecraft. They haven't done anything crazy like the doomsayers claimed at the time, but they have gradually de-emphasized the original Java version in favor of new editions and spin-offs.To my knowledge, those new editions
  1. Don't run on Linux
  2. Aren't moddable
  3. Have been more aggressively monetized with MTX and DLC
None of this stops me from enjoying Minecraft, but it does make me wonder if someday I'll be locked out of the game entirely.

To try and avoid that problem before it happens, I've been looking for alternatives. As far as I can tell, the only really active open-source block builder is a little game called... Minetest.

Nothing in this world is easy though, and Minetest has 2 little problems...
Warning: Strong opinions ahead

It looks bad

As you might expect of a volunteer-run effort, most of the content made for Minetest is made by non-artists. When you start a new game, you're likely to be met with a scene like this:

Not only are most of these textures worse than what Minecraft offers (and it's not exactly known for amazing graphics), there's like 3 different art styles in this screenshot alone!

Like Minecraft, some of this pain can be alleviated with texture packs. Or it could, but for 3 problems:
  1. There are very few texture packs
  2. 90% of the texture packs look as bad or worse (and indeed, many of them are just the default textures run through a filter)
  3. The good-looking texture packs are extremely limited in scope, leading to nasty stylistic clashes with the unaltered content.
Well, at least the game's great fun! Right?

It also plays bad

Whoops.

Now, to be fair this is highly dependent on certain parameters. Minetest itself exists more as a sort of 'platform' that games can be built off of rather than a game itself, and there are a handful of interesting and promising games to play. However, the community almost universally considers the default game to be unenjoyable, and even the coolest games still feel incomplete, unpolished, and just generally not as fun as I'd like...

All of this leaves me in a pickle. I don't want to stick to Minecraft, but I can't see Minetest as a viable long-term way to scratch my creative survival itch. But then, a thought occurred to me: What stops me from fixing this myself?

Introducing: RPG16

Because it seemed easier, I decided to tackle problem 1 first. Consider the following:
My goal with the RPG16 texture pack was to make a big complete texture pack with a fun 16bit RPG look.

Whatever you think of the aesthetic, the visual consistency is much improved. Personally, I'm much happier with a game that looks like this, and I believe that my texture pack makes Minetest visually competitive with its rivals.

I released the pack towards the end of July. And so far, onlookers seem to agree with me! The community have given my textures a fair bit of praise, and I've been busy updating the pack with more content.

If you play Minetest and you're interested in trying this out, you can find it here.
Just for fun, here are a few more screenshots:
A fork in a river

A wasteland of cracked dirt and burnt trees, with some mushrooms on the side (Work in Progress)

Unique armor sets (Work in Progress)





About that gameplay...

This fixes the first of my complaints. However, I still don't feel at home with any of Minetest's current offerings. Ultimately, I'm going to try making a fun and polished game that matches RPG16 in quality.

It's going to be more of a long-term back-burner project, and most of my other stuff will be taking priority. Still, I fully intend to try making more fun content for myself and others to enjoy.

8/13/19

Back to the Maze - 4 - Hey, it Works!

Hello again!

I wasn't expecting it to go so smoothly, but I got AMAZE's rewrite to a point where it mostly works. At the very least, every level is fully playable at this point so I feel happy enough calling Stage 2 of my plan a success (and in just a month and a half, if you ignore how long it took me to sit down and write this)

But how did I do it? Let's talk strategy:

1 - Data

My original goal for this stage of the project was to get the games final data files loading and running in the new engine, but I very quickly abandoned that idea in favor of loading the source files instead.

The main benefit of this change was greatly simplifying the asset loading process. Instead of handling custom compressed binary files, I just had to deal with simple text and xml. Many of these files were similar to existing formats in DFGame, so loading them was pretty easy.

2 - The Critical Path

I thought a lot about how to approach the problem of recreating the game, but in the end I decided to simply play through it. By visiting every level, I could implement just the parts that were necessary to make the game function and ignore the rest, which was another good speed boost.

3 - Writing Bad Code

I think terrible code is underrated. No-one wants it in their codebase and it has a very high maintenance cost, but ignoring quality allows for lightning-fast initial development speeds. Even at work, I usually start by hacking together the "naive solution" to quickly get a prototype in place that I can then redesign to better fit with existing systems.

I applied the same concept here. Since I'm already planning months of cleanup, polish, and refactoring, I just wrote the cheapest possible recreation of the original I could. I'll be paying that debt back soon, but I'll be doing it with the luxury of a working copy.

Like a finely-aged...steak

Since I had to play through the game for testing, I can also give a verdict on the original content! I hadn't touched this thing in forever, so I came in totally fresh. And let me tell you, it sucks!

I remembered that the game was lacking in polish and some challenges were too tight, but but the latter was much worse than expected. Looking back, I think there are two even bigger problems:
  1. Minimal Checkpointing - AMAZE is sort of a hybrid action/puzzle game, but there are no in-level checkpoints. Replaying an action sequence isn't too bad, but following the same steps to complete a puzzle after dying feels pretty bad. (The fact that resetting a level when stuck costs a life is also bad)
    Additionally, running out of lives is very easy and a game over can push the player back pretty far.
  2. Blindness - A lot of puzzles suffer from not showing the player enough in-game. This becomes especially obvious in some later areas, especially with ice puzzles. There are multiple places where the player has to run into areas full of deathtraps, completely blind!
Naturally, the result is a really un-fun game. If I want to make the new game a success, I need to keep these points in mind.

Planning My Approach

As you may remember, the next two stages are about refactoring the new code into something solid, and building tools to produce the new data. Before I can do that, I need a better idea of what to do with this game.

With that in mind, I'm taking a break from the project for a little while. Now that I have it running with newer libraries I can worry a little less about compatibility (though I don't plan to stop long enough for that to be an issue), so I'm going to spend some time on a few other projects and come back to this with some fresh ideas later.

My next post be about one of those projects, which is already released!
Stay tuned.

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