4 Hugues Ross - Blog: Game Programming
Hugues Ross
Showing posts with label Game Programming. Show all posts
Showing posts with label Game Programming. Show all posts

12/13/20

Royale Week 9: Heaps.io

I didn't finish this week's lap, and frankly I don't have the motivation to do it. I'm in a bit of a weird position--I feel like I haven't delved deep enough to really give this engine a fair shake, and yet what I have seen so far doesn't inspire the confidence I need to keep going... all of this is to say that I don't like Heaps.io, but I'm not confident enough to say that it's entirely the engine's fault.

Regardless, let's talk about that experience a little.


My initial impression of Heaps.io was actually pretty good. The basic introductory text seemed to imply a pretty sane modern structure based on a scene graph, and the setup seemed simple enough. I didn't really like the focus on specific applications, but I figured that was mostly just to help out beginners.


My problems began immediately after that. The next major section of the documentation goes over 2D rendering, and shows a simple example of resource loading (here, scroll down to the Image section for that). What it doesn't mention is that you need to initialize a resource loader. Indeed, it doesn't mention those loaders at all! The first reference I found was a 1-line comment in a code sample several pages later.

If you don't realize this because you're not psychic, you'll get this helpful exception when you try that code:

"Resource loader not initialized: call to hxd.Res.initXXX() required"

...ok, so clearly a loader needs to be initialized, and the functions are in the hxd.Res namespace. See? I'm not being sarcastic, it is helpful!

 

Well, except that if you check the API docs you'll notice that there are no init functions listed. I'm not really sure why that is, but it's a problem. I also checked a good half-dozen of the official samples and conveniently, none of them had any sort of resource loader initialization either. Nice. The time I spent setting up loading code and searching the API and samples is a waste, because what I really had to do was:

  1. Skip from the second section of the documentation (H2D) to the fourth section (HXD). And as an aside, what does HXD even stand for?????
  2. Go into "Resource Management"
  3. ...and read that instead of the example that was given in the first place.

This is enough to progress, but there's no simple way to work out what resource loaders even exist. 'Embed' is used as an example, but if you look at the API page for it... Weird, there's nothing at all. And by the way, don't bother checking hxd.res.Loader. I know it seems like the obvious next place to look, but it contains nothing relevant to this search. To this day, I still don't know if there's a list of resource loaders out there.


After all that hassle, we finally have textures onscreen. At this point, I was pretty miffed but not nearly ready to throw in the towel. The apparent lack of vector math (more on that later) also didn't deter me, since I saw that collision detection code was available. Most of the math I've been doing in these demos has been bouncing a ball off of rectangles, so a basic collision API would cover that pretty well.

I'm going to spoil the punchline now and tell you that it doesn't cover that at all. You might think it would after looking at this sample, where they use built-in API calls to get the normals of a rotating collision object from a point in space. Amazingly, this function is entirely exclusive to the capsule collider class, for...some reason? Beats me. All I really need is the closest point from a circle-to-rectangle collision check though, so let's see what we get from that...oh. We get a bool, and nothing else.

 

At this point, I was starting to question how Heaps.io games handle physics, seeing as the built-in collision detection code doesn't seem to give anything that's needed for collision resolution. On a whim, I decided to check the hxd.col.Point class. It has all the vector math you could want! I should've been happy, but the only thought racing through my head was: "Wait, if this exists then why do all the scene graph nodes exclusively take raw floats???????"


And then, I stopped. Throughout these past 2 1/2 months, I've seen and dealt with a lot of weird and arbitrary API design, broken or incomplete docs, and a handful of runtime bugs. I've pushed through it all to bring you my opinions, but now? Now, I'm tired of this. Heaps.io put the last dagger in the back of my desire to try new game engines. I just don't have it in me to keep wondering about these things, and I think it's time to settle down and change gears.

Thankfully, that's already what's next on the agenda! We have one more post to round up all of the past engines and frameworks, and to declare a "winner" that I'll be playing with further.

11/23/20

Royale Week 7: Raylib

Ok, I'll admit it: I'm starting to get a little tired of Breakout. That won't stop me though, we're rapidly approaching the end of this comparison and I intend to see it through to the end!

Here's the customary gameplay video:


This one's pretty similar to some of the earlier options, but I mixed some of the paddle velocity when bouncing the ball and I think the 'mixed' version is an improvement. It gives the player a little more control on the ball, while still providing a more precise option.


I wasn't really sure what to expect from Raylib.It's a framework that I was aware of, but had never really heard of anyone using... I was very interested in this though, since I have my own little C framework (DFGame, from week 1) and I wanted to see how some of the problems I ran into were addressed. Being an old low-level language, C has a lot of limitations to think about in game development. I had to come up with my own solutions in DFGame, but many of them weren't ideal and I wanted to see how a more mature framework handled them.


Now that I've used Raylib for a bit, I have a pretty good answer: The framework seems to take a lot of cues from higher-level options like Monogame (but without the content pipeline), which relieves some of the wordiness with a simplified API. However, most of the other problems were 'solved' by being ignored outright.

The most egregious example of the bunch is asset paths. C doesn't offer a built-in way of finding the executable's location, so you have to use OS-specific system calls to find it instead. This is one of the factors locking DFGame to Linux, and it's one of the primary things I want to avoid in any new engine/framework. Raylib answers this problem by being completely unaware of it, and naively using the working directory to load assets. If you launch your game from anywhere it didn't expect, it will fail to locate your assets and crash.

This disqualifies Raylib for the purposes of the exercise, but I still wanted to play around with it. Let's look at the rest.

 

Now, you probably think you know where I'm going next: "He's going to moan about API documentation for a few paragraphs" You are wrong, of course, because Raylib doesn't really have any sort of formal API docs. It has this "cheatsheet" which covers only a subset of the API, and to my knowledge there's nothing more... not really much to say there.


Instead, talk about how Raylib feels to use. I think this is where Raylib shines, actually. Working in C made me feel at home like with DFGame, but since the API is much higher-level it felt a lot better to use. Honestly, I think if the dev can fix the lacking areas and put a good API doc in place Raylib could be a much more competitive option.

11/9/20

Royale Week 6: Pygame

I have not heard the name Pygame in some time. It just had a big update last month, so now's the time to see how it has shaped up over the years!

source ( GIPHY)

Royale Week 5: Monogame

With this post, we are officially past the halfway point of this series! There's a good chance we'll wrap this up at the end of the month, and if not we'll definitely finish before the end of the year.

 

But enough about that, let's take a look at this week's demo:


Overall, this demo is in-line with DFGame and HaxeFlixel. In the interest of transparency though, I should mention that I had a few days off last week and had much more time to work on this than usual... without that, the demo would probably have a lot less. In general, I'd say that Monogame is one of those frameworks that requires a bit of scaffolding to work in quickly. Since Monogame is C#, extending this sort of API is still pretty reasonable though. (As an aside, I really wish extension methods were a thing in more programming languages)


Setting that aside for a moment, let's talk about what I liked first. Obviously, as someone who works in C# professionally it's a pretty comfortable environment to work in. The compile times are also pretty good, although they still pale in comparison to raw C. To balance that out, I don't need to worry about defining code files in my build config or managing #include directives.

Like most of the tech we've reviewed so far, Monogame doesn't try to enforce a particular engine structure on the user. I always appreciate that, and in terms of complexity the framework seems to sit at a nice medium spot between DFGame and LÖVE.


...aaaand, that's the stuff I liked. I'm going to be honest, I was somewhat disappointed by Monogame. I have a number of friends and coworkers who use it, so I went in with pretty high expectations. However, I was less than thrilled by some of the design decisions.

Let's start with the big complaint: I really hate the Monogame Content Pipeline. To cater to larger games, Monogame compiles asset files into an intermediary binary format. This is great when you're publishing a game commercially, and terrible for everyone else! Every time you make a new asset, you have to pop open a special tool and 'import' your asset into the project. I haven't tested, but I suspect this also makes any sort of 'hot reload' feature infeasible. Regardless, at the end of the day it only serves to slow down the development process without bringing any real benefits until the game is ready to ship.

 

Now that you've marked down "Feature that slows down iteration" on your bingo card, let's move on the the free space: Griping about API documentation.

At a glance, Monogame's docs look fine. There's a search bar to filter through classes, and plenty of details. Unfortunately, there's one really serious issue: The sidebar listing class members doesn't scroll. Like, at all. If there are more results than your screen can display, you cannot see them. This is actually worse than not having such a bar, because it gives no indication that the results are trimmed. It's useless in the best case, misleading in the worst case, and you still have to Ctrl+F for class members.


I also have some complaints about the design of the API itself. I'm a firm believer that UX applies not only to User Interfaces, but to Programmer Interfaces (literally, APIs) as well. Monogame makes a good case for this, because I think it has pretty bad UX.

The SpriteBatch class, which handles most of the drawing functions, makes for a good example:

  • When drawing anything with the SpriteBatch, you have options to position, rotate, and scale the element you draw by passing vectors / angles. But if you want to do that to everything you draw, you need to provide SpriteBatch with a 4x4 transformation matrix instead.
  • Conversely, you can't use a transformation matrix with individual elements. You must pass in the individual transforms.
  • You can draw text, and you can 'measure' text for placement/alignment purposes... but you can't draw aligned text without writing the alignment code yourself.
  • You can recolor any element you draw. In fact, you are required to pass a color in every draw call. Yes, it will almost always be white. No, you still can't skip it.

 

While working with Monogame, I felt like I was spending most of my time doing busywork and adding things that should've been redundant instead of getting work done. I'm sure it can be a good framework with enough helper libraries, but it really feels like the API designers don't respect the user's time at all. I'm trying make games here, if I wanted to write my own framework I would rebuild DFGame in OpenTK. It's still leagues better than Godot tho.

11/1/20

Royale Week 4: LÖVE

I really love LÖVE. This past week has been a blast, and despite skipping a day (I spent Saturday trying to make some music, but alas I still absolutely suck at music compostion) I've managed to put together the most polished and stable demo so far:


Pretty much all of the features the other demos have were completed in the first two days, so I took it easy for the remainder and tried to add some extra polish and refinement to what was already there.

 

I think a big part of what makes LÖVE so efficient for me to work in is that it's a framework like DFGame. This lets the programmer go as simple or complex with the architecture as needed, which can really speed up the production of simple games. Unlike DFGame however, this framework is based on a high-level language with a greatly simplified API. To top it off, the fact that it's based on Lua means the user can launch builds instantly. In short, it has most of the upsides of HaxeFlixel, with none of the downsides. Honestly, this test makes me want to kick old me in the shins for casually dropping it ~9 years ago. It's great!


But of course, we can't just be positive. LÖVE has one curious flaw: There's no vector math in the API. Seriously! Since Lua supports multiple return values, the devs have taken to just passing around pairs of floats. That's not such a big deal, but having to write your own math boilerplate to do basic vector manipulation is kind of a chore. I have absolutely no idea why LÖVE doesn't provide these functions, especially since the core engine almost certainly has them somewhere...

Thankfully, this is the sort of problem that can be solved with a helper library. Overall, I think LÖVE is handily "winning" this comparison so far.

10/18/20

Royale Week 3: HaxeFlixel

After last week's fiasco of an engine, I was a little worried going into something I hadn't tried before. Haxeflixel, however, was a breath of fresh air!



As you can see, the resulting demo is pretty close to what I made for DFGame. There are a few notable differences:

  1. I had never used HaxeFlixel or Haxe before
  2. I spent less time on this entry than the DFGame one
  3. At 395 lines, the resulting game has less than half the DFGame entry's linecount

This is the sort of result I was expecting to see from some of our contenders, and in general I quite enjoyed my time with HaxeFlixel. The engine offers most of the stuff I wanted out of the box, and the documentation is really well-designed. Just look at the HaxFlixel Snippets site for an example of what I'm talking about:

Each page has a minimal example (often just a single line of code!) showing the relevant call, API links, a real demo running in your web browser, and the full demo code acting as a more 'complete' example. It manages to frontload only the most relevant info while still offering enough depth to understand what you're doing.


Overall, I was impressed and I think HaxeFlixel is actually a quite good starting point for new gamedevs. There are a few negatives that make me unlikely to use it for big projects going forward though.

The first negative is the engine/language's compile times. A 'dirty' build (ie. no code modifications) of my 400-line game for linux takes ~13 seconds. A clean build takes 2 1/2 minutes building to an SSD on a 12-thread Ryzen CPU!!! For comparison, here are the build times for DFGame:

  • Dirty: too fast to perceive
  • Clean: 4 seconds

The Haxe codebase is nearly 40x slower to compile, and I can only imagine what a 5 or 6-digit linecount would do...

The other issue is a little less obvious, but worries me just as much. Part of what makes HaxeFlixel so quick to develop in is that many of the things you want are already handled in the engine. Want an object with a sprite? Make an FlxSprite and add it in! Want to give it physics? Just turn the physics on! Want to make it disappear when it runs out of health? Just call hurt() on it!

This is awesome for quick development, but it also restricts your visibility of what's really happening with your game objects. For instance, when I tried to make bricks move the ball started randomly teleporting, getting shoved out-of-bounds, etc. The changes I'd made on my end were so small, and the API was so 'foolproof', that there was no clear bug in my code. Something was going wrong in the backend, and I didn't have any good debugging options. This always worries me when engines try to hide the details, simplicity can bite back hard when it fails.


With that said, HaxeFlixel seems like it would make for a good prototyping tool. I'll probably keep it around, and I would recommend it for small games and game jams.

10/11/20

Royale Week 2: Godot

Given how many people recommended Godot when I started gathering my list, I was actually pretty excited to try it out. I didn't think it would be as good as claimed, but I figured it would make it into my top 3 and get some use as a prototyping tool, at the very least.


...How wrong I was. As it turns out, Godot is a hot mess.

I'm not bothering with a video this time, there's nothing interesting to see. I got the very basics in place (bouncing a ball around, breaking bricks) and immediately dropped the project, because I didn't want to use this engine any more than I had to. Instead of talking about the game itself, let's talk about why I hate Godot so much now.


First off, let's set the tone with a little anecdote. Here's my first interaction with Godot, step-by-step:

  1. Load an example project to see how everything works
  2. Open a scene and look around a little
  3. Open a script--wait, did that just replace my tab?
  4. Try to open the scene again. Nothing happens.
  5. Try to close the tab. The tab (and the script within) is still open, but the tab title changes to "[empty]"

If you use Godot, you already know what's happening. But if you don't, I'm sure you're about as mystified as I was when this happened. As it turns out, Godot's designers had a bright idea along the lines of:

"Hey, why don't we force users to open scripts on top of their scenes even though we have tabs?"

For some reason, in addition to 2D and 3D level-editing views they just added a script editing view instead of opening documents in a new tab like any normal piece of software. Just for good measure, double-clicking a script will change the view but double-clicking a scene won't change it back. Like many of Godot's failings, it's so obviously wrong that it almost feels like a deliberate joke.

 

I should also take a moment to point out that I'm a Linux user and casual FOSS advocate. I know that most open-source software isn't the pinnacle of UX, since I use it all the time. Even knowing this, Godot feels like it's on a totally different level somehow. Almost every interaction is slowed or complicated for no reason.


Here's another example: Since we've established that you can technically open multiple files in the same tab, how do you suppose you save them? As it turns out, Godot has 2 save shortcuts, Ctrl+S and Ctrl+Alt+S. Normally, Ctrl+S will save the content of your tab. However, it's bound to save scene so if you don't have a scene open under your script you have to press Ctrl+Alt+S for save script. That's right, they fucked up the most basic action of saving your files.

Of course, that's a bit of a moot point since there is autosave. Unfortunately, autosave always saves open files, even if they're not modified! This means that if you use an external editor, every time you launch the game your editor will detect a file change and prompt you to reload. Nice. The classic loop of "make change, run, go back and adjust" now has an extra step, courtesy of Godot! Oh, but don't worry. In addition to always re-autosaving your files, Godot will also ask for confirmation every time you try to close the editor. Yes, even when nothing is modified. And yes, it also autosaves when you do that, making the entire confirmation pointless.


But you know what? If that was the end of it, I might be able to grit my teeth and push on. So next let's talk about the API documentation. Surely, a team of programmers (because I refuse to believe that there are any "creatives" involved in this engine) would make good docs right?

So the first thing I noticed when I opened the Godot docs was that the API docs were nowhere to be seen. The sidebar is filled with tutorials, but to get to the real API you have to scroll to the very bottom of the list. Or hit Page Down twice. Yes, it's that far down. You could also use the search bar, of course. From my experience, unless you put in the exact name of a type you'll have to scroll deep into the results to reach the relevant API page. Depending on what you search, you might also get the internal engine API, because they put both APIs in the same space for some reason. They also put ads in their documentation, but since Godot itself is free I have trouble blaming them for that.


Ok, so the workflow is bad and the API is annoying to search. But maybe the level editor is good? I'm not going to beat around the bush this time, it's not. The biggest offender is the "Select Mode", which does a lot more than select. Select Mode is ostensibly for selecting objects in your scene, but clicking and dragging moves them. Did your mouse move slightly when you clicked? Your object is now 1px out of alignment, sucker! I hope you noticed before you started doing any real work.


I think that's enough bashing to get my point across, I could keep writing for hours (these past 6 days have given me an endless supply of material), but I think I'll just block this past week out of my memory and move on to the next entry.

10/4/20

Royale Week 1: DFGame

Hello again!

Though it felt like an instant, a week has already passed. I spent my evenings banging rocks together, and a spark emerged. Roll the tapes!

This is not the most riveting game of all time, so I tried to combine all of the game's features (and bugs) into a single segment instead of making you watch a full playthrough. If you're curious, you can check out the code here.


What I got done:

  • Basic breakout gameplay, knocking a ball around with a paddle
  • Multi-hit blocks, walls
  • Moving blocks
  • Velocity transfer from moving objects to the ball

I wanted to try a slightly different formula, so instead of bouncing the ball at different angles based on where it hit the paddle I made the paddle give some of its velocity to the ball. I think using paddle movement to 'aim' the ball is a neat idea, but it's a little odd in practice. I'm not sure if I'll keep the idea or try something else next time.

The game itself has a couple of physics bugs as well. Both of them are totally fixable, I know why they're happening. Unfortunately, I simply ran out of time at the end and didn't get a chance to resolve them.


To sum up my thoughts about DFGame after this: It's handy, but needs more time in the oven.

The main drawback of DFGame is that as a C API it's pretty wordy. There are ways to improve this, and most of them involve adding more 'shortcuts' for common stuff to the API. The final game was ~800LoC, which isn't bad, but I think I could cut a quarter of that just with helper functions and refactoring. Since DFGame is my project, that means that any future project using it will have to account for framework development time as well.

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/11/19

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

8/6/18

Making the first Halberd game: Measuring foothills

In my last post, we did some simple calculations to estimate the amount of walking and combat to put into the game. Now, I'm going to use that data to do some real testing and level-building. But first, let's do a little more math.

Right now, Halberd's ability to resize maps is rather clunky. To make up for that fact, I want to get an idea of how big any given map should be before I make it. We can do this because we have the final length of the path the player has to follow: 250 tiles.

When in Manhattan...

The player won't have free movement in-game. Instead they'll be locked to the tile grid, with only north/south/east/west and diagonals available to them. Since position is locked, They're probably not going to take diagonals too often. So, we can get an estimate of our length using Manhattan Distance.

Unlike direct distance, Manhattan distance is just the sum of the distance on each axis. For instance, let's say your friend lived in a house 10 blocks north and 20 blocks west of you. With the Pythagorean Theorem, you could determine the distance "as the crow flies" to be ~17.3 blocks. But for the Manhattan Distance, you can just add the numbers together and get a distance of 30 blocks (the distance you're likely to walk to get there).

This can also work in reverse. Given a final length, we can subtract numbers from it to form a path, then add the segments on each axis together to get the dimensions of the map. It's hard to explain, so let me show you instead. Let's say we wanted to make a path that was 10 tiles in length. There are many ways to do this:

Even without drawing a box around these paths, we can still get their bounds by following them. Take the green one, for instance. It goes 5 units up, 4 to the right, then one more up:

Up 
  • 5
  • 1
Right
  • 4
If we add all of the numbers together, we predictably end up with 10. However, we can also add them in their respective groups to get 6 up and 4 across. Taking the first corner into account, that gives us a 5x6 area for this path.

So why are we doing this?

Let's get back to the spreadsheets. We already have a predetermined length, so we can get either of two things by picking the other:
  1. Map dimensions from the number/frequency of turns (as seen above)
  2. Ratio of up/down to left/right paths from a ratio of map dimensions.
For now though, we'll put this work aside. We can't make this decision before making some others first.

What is Area 1?

If we want to decide what our new path should look like, it would benefit greatly to know what the area should be like terrain-wise. I already know the answer, and so do you if you read the title of this post. However, I think the process is more useful than the solution.

Because Halberd is as limited as it is right now, a lot of my design decisions are derived from those limits. When I discussed narrative earlier, I brought up the transition from known/safe to the unknown, and used woods and plains as examples. In retrospect, I'm not fond of either solution:

Plains are generally very open and exploratory affairs. This can be great if exploration is the goal, but in a limited game with few mechanics on offer an open field isn't really that interesting. They also tend to be pretty sparse, which isn't a good first impression for a game that will mostly live or die on its environments.

Woods are fairly ideal in general. A forest offers a lot of opportunities for interesting environmental details, and trees are good for creating paths (and hiding little passages for keen eyes to find). However, such a solution won't work too well in Halberd's current state: With no layers in maps, we can't make tiles overlap anything. Not each other, and certainly not the player! As a result, an environment full of trees would be a nightmare scenario.

Clearly, I needed something else. I chose the foothills of a mountain for a few reasons:
  • On their own, the foothills of a mountain aren't as imposing or dangerous as the peaks. They also tend to have more greenery which helps lighten the atmosphere a bit.
  • Mountains seem to get used a lot as a symbol for a challenge or ordeal. So, starting in some foothills implies that the real danger is up ahead. To me, this fits well with the progression established earlier.
  • Mountain trails and the like are pretty restrictive. This presents the opportunity to add more interesting or complex scenery bits (translation: trees) where the player can't possibly reach and/or clip into them. Tile overlaps can be faked if you don't need tons of them, so it works for this situation.
Of course this isn't the only possible option, just the one I've chosen. Now that we know what the map will offer in terms of terrain, we can use this to get a general idea of the map's size. Since we're building a mountain trail, it stands to reason that we want our player to head up. This is especially true since making sloped tiles that work properly isn't yet possible in Halberd. However, most gentle trails have a lot of winding to them. If you look at any local mountains you'll probably see a long and circuitous 'easy trail' and a few more direct 'hard trails'. Since we want the first area to feel pretty non-threatening, I think it makes sense to have a winding path.

Whoops, more math!

Since the two ideas that I just outlined contradict each other, I think I'd like to see a fairly even map ratio. Unfortunately, getting the dimensions and path lengths will be difficult because the path winds back on itself. So, we need some more complicated calculations.

Lets say that we want a path that crosses the width of the map twice, like so:
Let's also assume that we know the path length (since in our real path, we'll know that too). In this case, we'll go with 100. Given that, the target map ratio (1:1) and the number of crosses, how do we determine the size of the map?

Since we have the width-height ratio, we can also determine the horizontal movement to vertical movement ratio by multiplying by the number of crosses. Better still, we can use this number with the total length to get the amount of horizontal/vertical movement and the width/height of the total map!

Let's try with a real example now. I want my 250-tile path to fit a map with a 2:3 ratio. So, I plug that information into this handy spreadsheet I just made:


Just like magic, I now have map dimensions! The 'pad' entries at the bottom represent the map dimensions with enough extra space to prevent the edges from scrolling onscreen. With this, everything is now in place to (finally) begin working on the map!

As a side-note: I think I'm starting to like spreadsheets a little too much. I suppose that's a fine quality for an RPG engine-builder, but still.....

7/25/18

Making the first Halberd game: Story Time!

If you haven't read this post, do that now. I'll wait.

The first step to making a successful game is coming up with a good title, clearly. I spent a solid 10 minutes or so trying to think of a good legally-distinct title involving words like "dragon", "knight", and so on before remembering that I can just rename it later. So, let's just be lazy and call the game Henry for the time being.

Believe it or not, the second step to making a successful game is to make a new folder somewhere on your PC to put all the files in. Halberd can do that for me, or it could had I remembered to make it installable. Let's fix that now...

With that nonsense out of the way, we can start designing the game for real. Normally it helps to start a game with some prototyping, but that's not yet on the menu with Halberd. Instead, let's talk narrative.

Warning: Actual writers may want to skip this bit, to avoid the aneurysms my... process... will cause them.

Story

Even with a game as small and simplistic as the one I described before, it still helps to put together a little timeline of what's going on and what the characters will do before we build any content. We start out with the obvious:
Incredible. 10/10 story.

Sarcasm aside, this foundation gives us a lot of room to expand, even without dialogue and cutscenes to work with. This pair of events alone raises some interesting questions:
  • Why is the knight slaying the dragon?
  • What must the knight do to slay the dragon (besides the obvious)?
  • What happens afterwards?
You'll notice that these questions fill gaps before, between, and after the 'story'. Let's tackle the 1st and 3rd.

Motivation - Why slay the dragon?

Fictitious knights usually go on quests for kings. Kings, not typically portrayed as stupid, usually have a reason to send their knights off on a quest. Perhaps...
  • The king had a vision of his kingdom's destruction at the dragon's hands
  • The king is a greedy man, who wants the dragon's treasure hoard
  • The dragon threatened the king directly, demanding tribute
We could go on, with more ideas and deeper motives and circumstances. Of course, we couldn't depict most of that well with Halberd as it is. Let me instead reach once more into Ye Olde Bag of Cliches, and The dragon kidnapped the king's princess.

I don't want a totally boring story, though. 'Princess' is also used as a dog's name on occasion, so that might be a pretty achievable twist.

Conclusion - How does it all end?

We now have a knight, a dog, and a dragon. The little twist we just set up will provide a decent ending as well, so that can go at there. We can push it even harder, too. Since the player will likely expect a human princess, we can imply certain (wrong) things by stacking up some bones and a crown just before the dragon, and make the surprise even bigger.

The Rest - To Find a Dragon

I'm not cocky enough to try recreating the entire Monomyth in 10 minutes of lukewarm gameplay. Still, it's a good reference for brainstorming an RPG plot. Look back at the existing story points, and you'll see a couple similarities already.

Next, let's consider the (in-game) journey through our constraints. Given how short the game will be, I think 4 distinct 'areas' is a sensible goal to aim for. Clearly the 4th must be the den/lair/2-story condo that the dragon lives in, so that leaves 3 unknowns. Looking back at the Monomyth, there are a few things that interest me:
  • Most examples involve some transition from the known/safe to the unknown, then back at the end. This seems doable through environments alone, by starting the player off somewhere 'happy', like some lively woods or plains, then moving on to more foreboding locales.
  • There's a lot of events involving 'trials' and hardship around the middle. I happen to know of a couple simple events that could be made possible in Halberd, so I'll probably use those in the middle two areas.
So, we now have the following:
With this, we now have enough story to begin the game for real.
 Next time, we'll begin making some content.

7/22/18

Making the first Halberd game

It's done! Earlier this week, I finished the last of the features I needed for Halberd's MVP. I'm going to sit down and make a game with it now, so I thought I'd bring you along for the ride. Please bear in mind that Halberd is still in a very rough state, I've done basically no polish or UX work with this version yet.

Why Make a Game Before Release?

Longtime readers have likely noticed that I do this all the time, but don't really explain why I take the time to make games before engine releases. In fact, I even denounced this practice during a retrospective last year (For the curious but lazy, check the section titled "Total Overkill"). So why am I doing it now?

There are a few good reasons why I feel a need to make a game before pushing my work to the internet and calling it a day:
  1. Making a game is a way to validate that the tool works, by forcing me to engage with all of its features. If there are any bugs or missing parts, I can fix them before release. It also lets me see which features need the most love and which are 'good enough' as-is.
  2. Having games made in an engine is also good for proving its capabilities. Anyone can make game development tools, but if no good games have ever been made with them then they'll be a tough sell for other devs.
  3. Finally, games can also serve as a useful reference for anyone who wants to try Halberd out. I don't think anyone will at this point in development, but when people do start using it they'll have something to help them.
Clearly, there are some real benefits to using your own tools before offering them to the world. But why not small demos like I did with DFGame?

In response to my previous post, I would argue that the circumstances around Halberd make points 2 and 3 legitimate in a way that they weren't for DFEngine. My stated goal with Halberd is to produce a game engine as a product for others to use, whereas my old mistakes were mainly for personal use. I've seen certain engines receive criticism in the past because the devs didn't use them to make their own games, so clearly this is a factor that some people care about.

Additionally, the large scope of Halberd makes it a real marathon of a project. I need to break it up with smaller game projects if I want to keep my sanity! Not all of my 'filler' will be made with Halberd, but it's still a convenient excuse to give the engine a workout.

About the Game

Right now, Halberd's limited featureset heavily restricts what I can and can't do. To get around that, I'm going to start things off with a very simple game about a knight slaying a dragon. It's a typical story that has been done to death, but that makes it easy to work with, perfect for a limited toolbox. We can try some more exciting things for future versions of the engine.

Due to the limitations, I'm also aiming for around 8-10 minutes of gameplay. That should be enough for the game to show off everything Halberd currently has to offer without overstaying its welcome.

Too Much Information?

With this mini-project, I'm going to do something different from my usual fare. Nowadays I mostly write posts when I have some specific 'theme' or aspect of a project that I want to cover, but that leaves out a lot of potentially interesting things. I never really write about anything outside of fairly dry programming topics, but I enjoy other aspects of development too.

I think it could be interesting to share my process in detail, so I'm going to try documenting everything I do and see how things turn out. Assuming it all goes how I want it to, you'll be able to see the game's assets take shape from start to finish, including any drafts and thrown-out ideas. It'll slow the pace of the project down, but I think the idea has enough merit to try just once.

You can expect a few weeks (hopefully no more than that) of posts talking about various parts of the game as I work on it. Following that, I'll probably return to my usual non-schedule.

6/17/18

Halberd: Finally, a Proper Demo!

Well, it has been 2 1/2 months in the making. But today, Halberd has reached its first milestone!



Video captions can't really convey how happy I am to see this happen. I've been thinking about Halberd for years now, and it's great seeing it take its first step.

Granted, I still have massive amounts of work to do, but this still feels special regardless.

MVP, or Not?

One thing that I've been struggling with is figuring out what an MVP actually looks like, in the context of an RPG engine. What features are required for such a tool to become 'viable'? Where do I draw the line between wants and needs? I've generally tried to stick to as minimal a definition as possible, but such a definition is probably not what a user would actually expect. It's a tough balance.

In the end, I've decided to go with a little cop-out: The next milestone is what I'm referring to as my 'AMVP' (Absolute Minimum Viable Product), and the next one is the 'True MVP', with additional features and polish that your average user would likely expect as their minimum. I'm still figuring out the details of the latter, but my current benchmark is Dragon Quest 1 (a.k.a. Dragon Warrior). If Halberd can do everything that game can (minus some polish and a few of the more specific features like torches), then I feel like I can more confidently proclaim Halberd to be a viable (if bare-bones) engine.

After that? Who knows! Once Halberd has reached a usable state, a lot of potential avenues will be open to me. I'm probably going to keep finding progressively more varied and complex games to compare against, and mix those features with quality of life updates.

Whatever happens, I'm excited to see where the project goes next!

9/10/17

Let's Make a Roguelike - Stats, Items and, Messages

Well, here's a new tutorial segment!

This one was pretty awful to write, mostly because I chose too broad of a topic. As a result, it's also quite a bit longer than most of the others (~14 pages, with a previous record of ~11). On the upside, I'm very nearly done here. Currently, I'm planning the remaining 3 segments to cover:
  1. AI and combat
  2. Generating artificial spaces (e.g. mazes, dungeons, buildings, etc.)
  3. Various remaining loose ends, like visibility and overall game structure
I didn't quite do item systems justice here, unfortunately. I seem to have missed dropping/equipping items in particular, although these actions should be fairly trivial to implement with the knowledge from the tutorial.
Since it looks like things are mostly on schedule despite last month's delays, I'm currently pegging the completion date as December 17th, 2017. This will be one full year after I restarted the series, minus a couple days to make it land on a Sunday.

Speaking of dates, this series will be 3 years old in a couple weeks. All I can say at this point is that I'm glad it's coming to a close. It's going to be a while before my next series, I have a few ideas but I want to put a lot of consideration into any future endeavors. However, I do have a one-off informative...thing that I'll be putting up on the tutorials page not too long after this series.

2/27/17

Let's Make a Roguelike - Generation

In a shocking turn of events, I actually finished this week's tutorial segment last night! This means that I was done a full day early, and I've opted to post the new segment this morning before I head to work.

The trick this time was starting early. I finished the followup last weekend, then did half of the work on Saturday and the rest on Sunday. I'm pretty sure that if I keep to this formula, then I should be able to keep up with the schedule without causing myself too much stress.

As for the contents, I'm continuing my policy of "give the reader some fun stuff early on" by sharing a couple simple random generation algorithms. In my old plan for the tutorial, this probably wouldn't have happened until part 6 or 7 (we're on part 4), so I think this was a good move. I'm also trying to get through things quickly because (as I said before) I'm still not 100% happy with this tutorial and would like to get it out of the way so that I can focus on making better-planned tutorials in the future.

Lastly, I wanted to say that I'm going to try and revive the old one post a week policy from my college days. I've actually been working pretty hard on a couple of interesting projects lately, so this will give me an opportunity to share them on non-tutorial weeks. I'm painfully aware of the fact that the tutorial is the only thing I've written about this year and it's nearly March.

Anyways, you can click here to read the latest part.

2/14/17

Let's Make a Roguelike: Fashionably Late

Had you worried, didn't I?

I almost had this weeks segment done last night, but I was quite tired and decided to put it off. That was apparently a good decision, since I put several hours into it today before wrapping up! This was mostly poor time management on my part, but I'm happy that the delay was small this time.

As for the content, I'm trying to accelerate the early bits a bit more than in my previous iterations. Even for a long-running series, it's important to keep the readers engaged, and the shape drawing from last time didn't seem up to snuff. I'm taking a bit of a write once, refactor later approach this time: I present the technique in a fairly simple and "bad" way first, then show how the implementation can be refined and improved later. This allows me to frontload much of the exciting stuff, while still providing some basic design lessons down the line. We'll see how that goes.

This series has really been a bit of an interesting experiment for me. There are so many things wrong with my approach to writing these that I almost want to start fresh again, but for the sake of actually getting things done I'm resisting that urge. I think this series is helping me learn a lot about tutorials though, and future seasons will benefit from those lessons.

Here's the link to the new part.

1/30/17

Let's Make a Roguelike: Back Again!

It's done!

If you've been watching the blog, you'll know that I've had some fun these past few weeks. That's been dealt with, and I can finally release the next part of my tutorial series. You can read it here.

For those of you who don't care for tutorials, but are curious about the demo that I was making, here are some links:

Windows build
Source code

It may look a tad underwhelming, but being able to distribute builds to platforms other than my own is an important first step. Now that that's been dealt with, I think things will be going much more smoothly from here on out. Keep watching this space for more updates as they come!

12/19/16

Let's Make a Roguelike, for the 3rd Time

Hello again! Last week, I promised the return of my roguelike tutorial. Today, I'm making good on that promise. Sort of.

I've pushed out a new update to the site and blog, which the observant among you may have noticed already. In addition to making the navigation bar a little bit nicer looking, I've also reorganized the pages a bit. The portfolio section is completely gone, because it never had any content and I don't really need one right now. To fill the space, I've split the games page off of the software page, and added a section for tutorials. The overall theming of the site has gotten some tweaks, and if you click the logo in the upper left-hand corner, then you'll see a very rough work-in-progress landing page. I could have withheld it, but I think it looks better than the word 'Placeholder'.

The tutorial section is also a bit of a work-in-progress. I'm planning on remaking the ring segment collision tutorial that I did a while back, but that could still be a while from now. i also need to play with the theme some more to make it look more polished.

The Tutorial

I've put up the first entry of the new-and-improved roguelike tutorial, a nice little introductory page. You can read it here. It borrows a couple sections from the old part 1, but most of the content is new. It's probably not the most exciting start for a new series but I expect the new tutorial to have a slightly faster pace overall, so hopefully that will offset the slow start.

That's all for the moment! You can expect the first numbered chapter of the tutorial to be up on January 2nd, and more will come every other Monday after that. This means that I might have the beginnings of a schedule! I'll be posting here whenever I put up a new part, and you can expect the other regular posts to appear once in a while, as they typically do. I'm still waiting for my projects to get an official OK from on high, so in the meantime please enjoy these updated tutorial chapters.

6/7/16

DFGame Summer Progress: More Graphics!

Time for my first summer post. I haven't talked about dfgame's improvements since my second three-week project a few months ago.I've been making some decent progress, so it's time for another update blog post. Here are some of the highlights:

Data Structures

One of the biggest things that I miss from C++ is STL, the Standard Template Library. It may not be the fastest or most reliable thing on the planet, but it still provides basic structures for storing data that make my life a tad easier. For better or worse, C has none of that. I could probably seek out a library to provide me with some basic data structures, but I figured it would be good practice to roll my own.

The first one that I built was an arraylist (also known in some parts as a vector). It's a fairly simple structure that more or less serves as a self-resizing array. When I was making my demos for Consol Programming, I noticed that I was making arrays that could grow on their own over and over. Since it's a common need, I decided to just add it to dfgame.

I recently also made a self-balancing binary search tree. Trees don't get a whole lot of love, but they do provide a relatively quick way to find values, so I figured it would be good to have an implementation on hand.

Convenience Features

I noticed a number of code bits that kept getting reused during the creation of the dfgame demo, so I also spent some time putting them together into the main library.

I started off by trying to improve the safety of my code a little, with macro wrappers for malloc, calloc, and free. These add extra error checking and logging as well as automatically zeroing pointers when freeing. I also updated my destructors to zero-out pointers via macros as well.

I also added a camera object that combines a transform matrix with a projection matrix to generate a view-projection matrix as needed. Now, I can just tell the camera to move around instead of doing extra math whenever I want to look around in a game.

Meshes, Shaders, and Fonts

Alright, now it's time for the fun stuff. I've made a lot of progress on adding more graphical capabilities to dfgame in the past few months.

I kicked things off with a revamp of how shader programs were handled. Before, there was just a function that compiled shaders, but now shaders are handled by a struct and some functions that allow binding data and retrieving handles. The result is less boilerplate code as well as the potential for more options later on. For instance, I'm planning on tracking uniforms with a hash table, which should make setting values a tad quicker. While I was at it, I also added support for geometry shaders!
My test geometry shader may have had a bug or two...
One obvious feature that dfgame has been missing for a while was loading/rendering actual meshes. I've thrown together a quick-and-dirty OBJ loader, so this issue has now been resolved. Combined with the new shader setup, I can play around with graphics programming much more easily.
The fixed shader
Lastly, I've added some initial support for font loading/text rendering. For the first time in my life, I've finally managed to get TTF fonts to render properly with FreeType. This will be super useful for making any demos/tutorials in the future. I'm currently in the process of creating actual text objects with word wrapping and some other handy features, which will hopefully be working by next week. If you looked at the above images, you might've noticed the Japanese Hiragana character floating to the right of Suzanne. One of the nicest features of my new system is that it has (basic) Unicode support. For now, text objects are going to stick to ASCII only, but all of the necessary groundwork has been laid out, and at least Unicode can still be used for icons and the like.

Next Up

Once text is figured out, I'm going to go back and fill in some holes. There are a few bugs with audio that need to be fixed, and I need to finish the input implementation. After that, I'm probably going to get the html5 version up to speed, while also continuing work on Halberd. I'm also thinking of adding particles somewhere in-between just for the fun of it!