Back Part 2 – Using GameBase to create an Haxe+Heaps game 31 | ♥ 40

Introduction

Every time I start working a new game, I use “GameBase” as a starting point. It uses the Haxe language and the Heaps.io engine.

GameBase offers a minimal set of classes and tools to get started quickly making your own game. It’s completely open source and easy to adapt to your needs. Oh, it’s also quite simple and not bloated with tons of useless classes or features.

Of course, you could also start from scratch, but that’s not something I’d recommend if your goal is to learn Haxe language + Heaps engine.

Getting GameBase

First thing first, here is the official GameBase repository on GitHub:

Installing Haxe + Heaps

I wrote a step-by-step tutorial on how to install the whole thing on Windows.

Please check it out if haven’t installed Heaps yet.

Installing dependencies

You’ll first need my deepnightLibs, which contain many useful classes I use in all my projects. To install it, just run:

haxelib git deepnightLibs https://github.com/deepnight/deepnightLibs.git

Also install CastleDB lib (a simple database for Haxe games, see castledb.org):

haxelib git castle https://github.com/ncannasse/castle.git

Getting the GameBase source code

Method A: template

Just go to the repository on GitHub and click on the Use template button. Learn more about GitHub templates here.

Method B: fork

Go the following GitHub repository: https://github.com/deepnight/gameBase

Just fork this repo and clone it on your computer. Feel free to adapt/edit/copy for your own usage (see LICENSE).

Method C: adding an upstream remote

This method is mostly for my own usage, as GitHub doesn’t allow me to fork my own projects.

  • Make a new repo on GitHub (WARNING: do not add any license or don’t commit anything in it for now)
  • Clone it somewhere
  • on a command line:
git remote add gameBase https://github.com/deepnight/gameBase.git

git pull gameBase master

git push origin master
  • Or for the Advanced game base branch:
git remote add gameBase https://github.com/deepnight/gameBase.git

git pull gameBase advancedBase

git push origin master

Checking if it works

Compiling

If everything is properly installed, just run the following command right from the gameBase root folder:

haxe hl.dx.hxml

This should generate a client.hl in your /bin folder.

Other platforms

You can build for other platforms:

  • DirectX for HashLink VM: haxe hl.dx.hxml (recommended)
  • Javascript (WebGL): haxe js.hxml
  • OpenGL for HashLink VM: haxe hl.sdl.hxml
  • Flash: haxe swf.hxml

Please note other platforms could be targeted by creating a corresponding HXML file (see: https://haxe.org/manual/target-details.html).

Running

To run the game, execute one of these commands:

For DirectX or OpenGL Hashlink:

hl bin\client.hl

For WebGL (javascript):

start js.html

You should see something like that:

This is your GameBase “Hello world”

Building and debugging from VScode (recommended)

From VScode, just run the “Debug: start debugging” command (press F5 by default, or check the VScode command line with CTRL-SHIFT-P).

This should run the default pre-configured HL Debug command which compiles & runs the HL DirectX version.

If you plan to debug WebGL (Javascript) content, you’ll need one of the following extensions for VScode:

Don’t forget to change the HXML used for code completion before starting to code:

Check the bottom left corner of VS Code
  • Click on base.hxml
  • Change it for whatever HXML you plan to use for building/testing your game (probably hl.debug.hxml or js.debug.hxml).
  • This HXML will be used for code completion

Making a game!

The hero we need

We’re now ready to make the very first any game needs: adding a playable character.

First step: create a Hero.hx in /src/en/Hero.hx. The “en” folder is the package for “Entity”-based classes.

This is our hero:

package en;

class Hero extends Entity {
	public function new(x,y) {
		super(x,y);

		// Some default rendering for our character
		var g = new h2d.Graphics(spr);
		g.beginFill(0xff0000);
		g.drawRect(0,0,16,16);
	}
}

In the Game.hx, you can create an instance of your Hero at the end of the new() constructor.

class Game extends Process {
	// [...]

	public function new() {
		super(Main.ME);
		// [...]

		new en.Hero(5,5);
	}

Controller

We now need a little bit of control. If you have a gamepad, you can use it easily. You can also still use a standard keyboard too.

We need the dn.heaps.Controller class:

class Hero extends Entity {
	var ca : dn.heaps.Controller.ControllerAccess;

	public function new(x,y) {
		super(x,y);
		// [...]

		ca = Main.ME.controller.createAccess("hero"); // creates an instance of controller
	}

	override function dispose() { // call on garbage collection
		super.dispose();
		ca.dispose(); // release on destruction
	}

	override function update() { // the Entity main loop
		super.update();

		if( ca.leftDown() || ca.isKeyboardDown(hxd.Key.LEFT) )
			dx -= 0.1*tmod;

		if( ca.rightDown() || ca.isKeyboardDown(hxd.Key.RIGHT) )
			dx += 0.1*tmod;
	}
}

Testing

Build & run the game (F5 with VScode) and test it. Your red hero should move left/right on key presses or gamepad.

That’s how any great hit actually started.

About “tmod”

tmod is a value connected to the duration of the frame, and more precisely, the time elapsed between two consecutive game frames.

  • on 60 FPS, tmod is 1.0 (the “normal” value)
  • on 30 FPS, tmod is 2.0
  • on 120 FPS, tmod is 0.5

tmod can be used to adjust values that are FPS dependent, such as velocities or accelerations.

The idea is:

  • if the game runs slower (ie. less than 60 FPS), all the movement increments should be higher, to compensate for the missing frames.
  • if the game runs faster (ie. more than 60 FPS), all the movement increments should be smaller to acknowledge the extra frames.

See Delta time to learn more about this classic concept.

Working without “tmod”

The dn.Process class has a fixedUpdate() which runs (or at least tries to run) at a fixed lower FPS, usually 30 or 24 FPS (check the fixedUpdateFps variable). The FPS of these fixed updates is guaranteed, within the realms of possibility.

The Entity class also has a similar fixedUpdate() which runs at the same fixed FPS as the Game.

A classic approach is to do every gameplay related things, like physics, or velocities inside the “fixed” updates, and do everything else (rendering, particles etc.) in “real FPS” updates.

This keeps the gameplay code quite simple, without having to rely on the tmod/delta time values.

You might think it would be an issue to have your gameplay code in a 30 FPS loop instead of 60 FPS or more, but bear with me: no one will notice. Dead Cells, which is known for its fast-paced action & gameplay actually uses 30 FPS loops for the gameplay elements. The render is done at 60 FPS.

Important: all the controller related checks should happen inside the real FPS updates, because that’s the only way to catch a user input at the exact moment it happens. The fixed updates won’t catch these events, because these fixed updates don’t occur on every single frame.

Classes & API

Boot.hx: where everything starts

The first thing called in your code is in the Boot.hx method main(). From here, we create a Main, which creates a Game.

Loops

All the updates are called in the following order:

  1. Boot.hx has an update() which takes care of low-level stuff. You shouldn’t do much here, unless you have very specific needs.
  2. Main.hx has an update() too, and is basically your global app main update. It also has a postUpdate() which happens after the update().
  3. Game.hx also has an update(), a preUpdate(), a fixedUpdate() and a postUpdate(). This is where your game related code will happen.

Note that Boot extends hxd.App which is the top-level app class in Heaps. I use a different class for Main and Game: dn.Process.

My dn.Process class takes care of the updates, and each Process can have children processes, support pausing, have preUpdates, fixedUpdates, postUpdates and few other useful things.

The philosophy with GameBase is quite simple: all your future Processes should be either children of Main (any section of your app which is not the game, like an intro screen), or Game (if it’s part of the actual game).

Important classes

All code files are in /src folder.

  • Main.hx: the main app loop, see the update() method
  • Game.hx: created every time a game is started, it also has an update()
  • Entity.hx:: the most important thing after the Game. It’s the base class for everything that moves in the game (player, enemies, bullets, items etc.). Everything except particles (see Fx.hx). Read below for more explanations about the Entity class.
  • Level.hx: your world, level, room or whatever is your environment. Some games might have none of these, or multiple instances, it’s up to you.
  • Camera.hx: a basic camera which can optionally track an Entity (say, the player)
  • Fx.hx: a simple particle system
  • Lang.hx: a neat way to automatically extract your texts directly from your code to generate PO files compatible with the popular GetText translation ecosystem. Check the specific tutorial to see how it works.
  • Const.hx: contains a set of constant values I use to tweak my game, like the standard FPS, your starting health points or stuff like that.
  • Assets.hx: a single class to access assets like tilesets or sounds. All your assets (art, sound, other data files) that are meant to be loaded/used by the game should be put in the /res folder. You can access them in your code using the hxd.Res.myAsset API.

Misc classes

  • import.hx: note the lowercase format of this file name, it allows to have global imports for every classes of your app (see: https://haxe.org/blog/importhx-intro/). Neat.
  • tools/CPoint.hx: a simple Point class that supports grid-based coordinates.
  • ui/Hud.hx: a process of Game which can be used for any HUD (head-up-display, aka. interface) for your game, like: gold, life, ammo etc.
  • ui/Window.hx: a simple pop-up object.
  • ui/Modal.hx: same as Window, except it pauses the game while it’s open.

The Entity class

Philosophy

Entity.hx uses the same “grid-based” logic as described in my Simple 2D engine article.

Please note that it’s totally up to you to write a very different Entity system to suit all your needs. Basically, all the 2D grid-based logic is here. Everything else in GameBase should fit any kind of game, be it a platformer, a match-3 puzzle or an hidden object.

Features

  • Loops: preUpdate, update, fixedUpdate and postUpdate
  • Safe disposal mechanic: just call the destroy() of an Entity to mark it as destroyed. It won’t be destroyed instantly, but only at the end of the current frame (the onDispose() is then automatically called), to avoid any null value in the course of the loops.
  • Listing: access Entity.ALL static array for a dynamic list of all existing entities. Note that some might be flagged as “destroyed” (see previous point).
  • Coordinates: each entity uses a grid-based coordinate system. cx,cy are the grid coordinates, while xr,yr are the position inside a single cell. For example, cx=5 and xr=0.5 means “in column 5, at 50% of the cell“. More explanations about this approach in my Simple 2D engine article.
  • Velocities: dx,dy are the current x/y speeds of the entity (added to coordinates on each frame). They also use “grid-based” values. So having dx=0.5 means “moving 50% of a cell on each frame“. dx,dy are multiplied on each frame with frictions which slow them down (if friction<1).
  • Sprite: the visual representation of an Entity is the variable spr. It’s a dn.heaps.slib.HSprite class, which is a super-charged h2d.Bitmap. Please note that the coordinates of this sprite are only updated during the postUpdate(), it’s never updated nor manipulated outside of this loop. If you need the coordinates of the entity object, you should always refer to cx,cy + xr,yr.

Leave a Reply

Your email address will not be published.

  1. Yvanoff:

    The problem is a Heaps one not specifically gameBase it seems. Hopefully it's fixed soon.

    Nice tutorial thanks for the hard work Sébastien !

    February 16, 2021 at 13:10
  2. Max:

    gameBase doesn't appear to work with Haxe 4.2.0 out of the box yet (had same problem as Agusti and Karl). Downgrading to Haxe 4.1.5 works with the current HEAD of gameBase's master (06e6a86).

    February 14, 2021 at 07:51
  3. Agustí Pellicer:

    Hi, I also have the same problem as Karl although I managed to pinpoint to this part of the code in MainLoop.hx:
    static function injectIntoEventLoop(waitMs:Int) {
    #if (target.threaded && !cppia)
    mutex.acquire();
    It just crashes in mutex.acquire() because access violation. Any idea on how to fix this?
    Thanks!

    February 10, 2021 at 10:57
  4. Karl:

    Hello. Thank you for sharing all this great info.

    When I type `hl bin\client.hl` nothing happens. Not sure how to debug this. I was using the directx target (`haxe hl.dx.html`). No errors, just warnings about Std.is being deprecated.

    February 10, 2021 at 09:19
  5. Lajbert:

    Thank you again, I see your point now and I can definitely work with it :)
    I would have just one more question if you don't mind, and I swear I'll try not to bother this often in the future :D
    How do you solve having bigger sprites than your grid size? For example, the giant's hands and the hand of the king in Dead Cells both seem to be way bigger than the grid size (although I don't know what grid you've used in the game, but I suspect that much smaller than the sprite), and they are both definitely bigger than the protagonist :) How do you make that work with your grid based logic? Do you "group" some cubes together for one character, all checking against collision etc., or do you have some other magic to solve this?
    Thank you and I wish you a happy new year! I hope we can see a lot of content from you in 2021 :)
    (P.S.: you should add a "reply" option to this forum, so people don't have to start a new thread each time they reply :) )

    January 3, 2021 at 17:14
    • Sébastien Bénard:

      For very large entities like the Giant, we actually stick to simple physics for individual parts (head and hands), and ignore physics for anything in-between (arms, torso etc.). Basically, my logic is: if it's not REALLY useful gameplay wise, I don't even try to make it "realistic". For a taller body, I'd probably just stick to having a collision point for the feet, and a second one in the middle of the head (or near the neck). The second point uses the exact same code logic as the first one. And if the character "crouches", I just ignore the head point, or lower its position.

      January 4, 2021 at 21:30
  6. Lajbert:

    Thank you for your answer Sébastien. I have moved the rendering of my square half unit to the left and up as well, so now the Pivot is at the center. I took your advice to display it :) I also fixed the collision distance offset in my update method, so it is always at the correct position. The collision still doesn't quite cut it though. Now my square falls through both from left and right (previously it was only from the left), at half 1/2 width of the square. The attached screenshot will explain it better. I think I am missing some important small bit here, but I fail to see what.
    https://ibb.co/VB7fD3y
    Do you have any more hints maybe? :) Thank you very much, and not just for helping me here, but all your shared knowledge and all the contribution for indie game development.

    December 28, 2020 at 14:07
    • Sébastien Bénard:

      The current collision system in GameBase is actually a "one-point" collision method. This means that the player actual bounds are only emulated, and collisions only happen for the single point at <code>cx+xr , cy+yr</code>. You'll need to implement an actual "box" based collision system to really support what you describe. But be aware that such collision system isn't always required: even a complex game like Dead Cells only relies on single point collisions (it's actually the same base gameplay "engine" as GameBase).

      January 1, 2021 at 14:58
  7. Lajbert:

    Sorry, here is the correct screenshot for my previous comment :)
    https://ibb.co/MSJF3QW

    December 27, 2020 at 10:16
    • Sébastien Bénard:

      Hi! It's because your "pivot" point in your game object isn't the center of the box: in your example, the pivot seems to be on the left side. The "pivot" (ie. the coord "cx+xr") is the reference point used for all your logic. The easy way to fix that is to move your visual object (ie. the square shape) half width to the left, so it would be centered on the coordinates of your object. The same should be done vertically: the pivot should be at the bottom of your square. As a reminder, you should render a small dot at your pivot coordinate (ie. “cx+xr” and “cy+yr”) to understand what actually happens here :)

      December 27, 2020 at 11:22
  8. Lajbert:

    Hello Sébastien,

    I have incorporated your grid based logic into my 2D game engine in MonoGame, but I have a bit of a trouble with collisions. The grid based collision works correctly when the character is on the right edge, but not when on the left edge because of the rounding. See the attached picture for clear explanation, the numbers in the boxes are grid coordinates:
    https://ibb.co/Dzwhnw4
    Did you encounter this? If so, do you have a simple way to solve it?
    Thank you

    December 27, 2020 at 10:13
  9. Maskren:

    Sebastian, do u have docs for deepnightlibs and redisthelper

    December 6, 2020 at 12:50
  10. Janek:

    Sébastien, a quick question. In the article above you say "A classic approach is to do every gameplay related things, like physics, or velocities inside the “fixed” updates", but then your code in gameBase.Entity does velocities and collisions inside update, not fixedUpdate. Why?

    September 3, 2020 at 22:56
    • Sébastien Bénard:

      You're right to mention that. Preferring fixedUpdate over update is a matter of simplicity and avoid including time factors into every operation. But as soon as you plan to have some kind of time distorsion in your game (like bullet time or slow mo), you’ll have to do that anyway.

      As I do use these effects often, I took the road of applying time factors everywhere

      September 3, 2020 at 23:21
  11. N.H.:

    I have fixed the error. basically you just have to put the new en.Hero(5,5) line at the very bottom of the public function new(). My fault

    May 18, 2020 at 18:49
  12. N.H.:

    Hello. I have a NULL access error when I try to compile the code. VScode highlights the 58 line of the Entity.hx file. Can you help me ?

    May 18, 2020 at 01:24
  13. Mark J:

    Thank you for making these tutorials. There's not too many resources for Heaps so what you're doing goes a long way for the rest of us.

    If you have/had a Patreon, I'd throw some money your way as appreciation.

    April 23, 2020 at 00:22
  14. André Simões:

    About my commenfropm ""April 20, 2020 at 00:42"…. no need to awnser. I solved (kind of..).

    April 21, 2020 at 18:18
  15. André Simões:

    Hi Sébastien!
    I'm trying to use Atlas to replace the hero's sprite with an image. I've been looking at other of your available sources but still can't figure out how to do it. I'm feeling that I probably trying to "take a step bigger than my leg".
    There are some other example codes that you can suggest to me as a source of study for this starting moment journey.
    I'm asking for that because I really enjoying Haxe.

    April 20, 2020 at 00:42
  16. Edwin:

    Hi, good tutorial.
    Questions:
    – How to apply multiple scenes (Home, Options, GamePlay, etc…). I want to apply scene transition when the system change the scene. Is it possible?
    – I was trying the code, and adding entity into (0, 0) position, but why it is rendered on the middle, not at the top left.

    Thanks

    April 6, 2020 at 17:44
  17. Tiago Ling:

    Great introduction! What are your thoughts on adapting Entity to use vector math instead of treating x and y separately? I'm wondering if there'll be any negative effects on movement and grid based calcuations. Thanks for the great article and resources!

    March 27, 2020 at 18:38
    • Sébastien Bénard:

      Hi! Sure, I think it's a great idea and I don't see any issue with this approach :)

      March 27, 2020 at 21:19
  18. Brianjar:

    Your knowledge is quite exciting.

    March 25, 2020 at 02:49
  19. Janek:

    Thank you so much for your tutorials. Thanks to them I finally managed to get started with haxe & heaps. Also, analysing source code of your games is a fantastic learning resource!

    I was wondering if you could share a bit more of advanced knowledge and your practical experience from developing Dead Cells. I am particularly curious how multi-platform development looks like and what are the pitfalls. And by multi-platform I mean not only different operating systems but also different distribution platforms like Steam and GOG. Also, did you run into any limitations of heaps and ended up reimplementing things that the engine provides?

    March 19, 2020 at 23:17
  20. Marcio Ramos:

    Hi!! Thank you for your support on HAXE community!! I have the following error:

    D:\APPS\HaxeToolkit\haxe\lib\castle/1,6,1/cdb/Lang.hx:200: characters 62-75 : Warning : This typedef is deprecated in favor of haxe.xml.Access

    March 11, 2020 at 18:12