RocketHaxe 2.0: Gettin’ All JRPG Up In Here

A new demo from tonight; arrow keys to move (click first to focus), and mouse-drag to move the viewport around:

This is another demo of RocketHaxe 2.0’s simple tile engine, here using the physics engine and collisions for basic moving around in a top down view rather than platforming. Note that the guy sometimes gets hung up on corners and doesn’t walk right up to the water because the tiles are fairly large and not filled by the water. The map is a simple CSV file autotiled against some nice tiles from OpenGameArt by Robotality:

Tiles by Robotality.

Tiles by Robotality.

The big recent progress here though is showing off sprite animations, ported over fairly directly from RocketHaxe 1.0. The character here is from an RPG character pack by Antifarea and also released on OpenGameArt:

RPG townfolk character by Antifarea.

RPG townfolk character by Antifarea.

Code

Ignoring the imports, this is the entirety of the code for that demo right now:

class Main
  extends com.rocketshipgames.haxe.Game
{

  private var game:ArcadeScreen;

  private var spritesheet:SpritesheetContainer;

  //--------------------------------------------------------------------
  public function new():Void
  {

    //------------------------------------------------------------------
    //-- Initialize ----------------------------------------------------

    trace("Tiles Demo");

    // The base Game class sets up the display, mouse, audio, etc
    super();

    // ArcadeScreen is a Screen which drives a game World (entities,
    // mechanics, etc), renders graphics, pauses on unfocus, etc.
    game = new ArcadeScreen();

    // Spritesheets wrap a single bitmap containing multiple frames,
    // which are ultimately used as textures for polygons to draw
    // characters, tiles, etc., with a single memory transfer per
    // frame rather than blitting each one.  This can be just a
    // touch slower on PCs, but is much faster on mobile devices.
    spritesheet = new SpritesheetContainer
      (Assets.getBitmapData("assets/spritesheet.png"));
    game.addGraphicsContainer(spritesheet);


    //------------------------------------------------------------------
    //-- Tilemap -------------------------------------------------------

    // Tile catalogs capture information about map tiles: Size,
    // sprite frame, collision classes, etc.
    var tileCatalog = TileCatalog.load(Assets.getText("assets/tiles.xml"),
                                   spritesheet);

    // A chunk is an array of tiles, here capturing the overhead map.
    var chunk = TileChunk.loadCSV(tileCatalog,
                                  Assets.getText("assets/map.csv"),
                                  TileChunk.autotileRPG);

    // The collider detects and resolves objects colliding with tiles.
    var collider = new ImpulseTileChunkCollider(chunk);
    game.world.mechanics.add(collider);

    // The renderer actually draws the tiles.
    var tiledraw = new TileMapRenderer(chunk);
    spritesheet.addRenderer(tiledraw);

    // Center the viewport over the map to begin with.
    game.viewport.activate({bounds: chunk, drag: true});
    game.viewport.set(((chunk.right()-chunk.left()) - game.viewport.width)/2,
                      ((chunk.bottom()-chunk.top()) - game.viewport.height)/2);


    //------------------------------------------------------------------
    //-- Character -----------------------------------------------------

    // Sprite catalogs collect all of the different sprites on a
    // spritesheet and their information: Size, animations, etc.
    var spriteCatalog =
      GameSpriteCatalog.load(Assets.getText("assets/sprites.xml"), spritesheet);

    // The renderer draws all the sprite instances currently active.
    var sprites = new GameSpriteRenderer();
    spritesheet.addRenderer(sprites);


    // The character is a completely generic RocketHaxe component
    // container to which we'll add functionality.
    var walker = new com.rocketshipgames.haxe.component.ComponentContainer();

    // Get the particular sprite to use in order to pull its dimensions.
    var sprite = spriteCatalog.get("walker");

    // Add a physical body to the walker, a simple box body and basic
    // kinematics properties for walking around at a reasonable pace.
    walker.add(RigidBody2DComponent.newBoxBody
               (0.75*sprite.pixelWidth/game.viewport.pixelsPerMeter,
                0.75*sprite.pixelHeight/game.viewport.pixelsPerMeter,
                {x: (chunk.right()-chunk.left())/2,
                 y: (chunk.bottom()-chunk.top())/2,
                 xvel: 0, yvel: 0,
                 xvelMax: 5, yvelMax: 5,
                 ydrag: 42, xdrag: 42,
                 collidesWith: 1,
                 restitution: 0.5,
                }));

    // Add a generic keyboard controller to the walker.  Custom
    // controls could of course be written.  This component is
    // inserted after the rigid body representation because it's
    // dependent on the body's kinematics, but it's inserted (front of
    // the walker's component list) rather than added (back of the
    // list) because we want it each loop before the kinematics.
    walker.insert(KeyboardImpulseComponent.create({facing: DOWN}));

    // Instantiate a sprite to display the character on screen.  The
    // component used here is a generic default that uses some
    // conventions on the sprite to make it face left/right/up/down
    // and animate when moving.
    walker.add(new FacingGameSpriteComponent(spriteCatalog.get("walker"), true));

    // Have the viewport install a tracking component into the walker.
    game.viewport.track(walker, {margin: Math.max(tileCatalog.width,
                                                  tileCatalog.height) * 4});

    // Finally, make the walker live by adding to the sprite render,
    // tilemap collider, and overall gameworld.
    sprites.add(walker);
    collider.add(walker);
    game.world.entities.add(walker);


    //------------------------------------------------------------------
    //-- Startup -------------------------------------------------------

    // Add the game to the display.  In a real game this would be
    // done using ScreenManager to transition between menus, etc.
    flash.Lib.current.addChild(game);

    // Display the cursor for dragging the viewport.
    Mouse.enable();

    // end new
  }

  // end Main
}

Tile Physics

The tile physics demo of course also shows off tiles and objects interacting, now with some basic platformer autotiling (this is a GIF recording):

GIF of the tile physics demo.

GIF of the tile physics demo.

Next up: Sound!

Floating Point in Haxe on Android

haxeOne thing I discovered recently is that Haxe, via hxcpp, by default builds to a fairly old Android target platform. A consequence of this is that it uses software floating point emulation. But any Android device from ARM v7 on supports hardware floating point. Switching to this produces a dramatic speedup, assuming you’re ok with that requirement. To do so, use the HXCPP_ARMV7 option, something like so:

openfl build -DHXCPP_M64 -DHXCPP_ARMV7 android

The HXCPP_M64 flag is to build correctly on a 64bit machine; details here.

However, there’s apparently a bug somewhere in the hxcpp toolchain. Enabling the ARM v7 target adds a `-7` prefix to a variable denoting the target library extension (`.so`). Seemingly some part of the toolchain doesn’t use this extension variable, so you get a warning like this:

Error: Source path "export/android/obj/libApplicationMain.so" does not exist

NOTE that if you had previously built under the default architecture, the toolchain will blithely continue on using that old version and you’ll be left confused as to why none of your code updates are being applied. You have to delete that file or your Android build folder, typically export/android/.

I haven’t looked into how to really fix this problem, but it seems to be enough for now to simply not add that extension prefix. In your haxelib folder, edit hxcpp/3,0,2/build-tool/android-toolchain.xml and comment out this line:

<set name="ARCH" value ="-7" if="HXCPP_ARMV7" />

Everything should now build correctly and run speedier. Huzzah!

RocketHaxe 2.0: Tile Physics

Fresh new demo:

What’s new & cool about this one is that the green boxes are actually a tilemap, not objects in the same sense as the red boxes. This demo doesn’t use it, but there is some support for autotiling bitmaps, etc., and there’s a fair bit going on with the tile engine already. Bouncing off the screen edges is also using the same physics as the object collisions, whereas in the earlier demos the bounds just manipulated velocities and didn’t apply friction and position correction. So, the physics engine is now encompassing several different mechanisms for colliding different kinds of things. The internal structure of it has also matured quite a bit, the classes decompose pretty nicely now.

Along the way a few bugs have been found, and some improvements made. The previous demos actually have a bug whereby the collision normal for circles that have completely penetrated inside boxes was not being computed correctly if the boxes weren’t square, causing them to fly off in the wrong direction. Impulses are also now being computed & applied multiple times per update, so collisions travel back up stacks and such. On Linux it’s actually pretty stable, for the most part a stack needs to get fairly large or an object stuck in a tight space (just within the error tolerances) to see a lot of quiver. Flash is a lot more unstable and still has a fair bit of quiver, though it’s generally ok until you have 4+ objects stacked up. I’m not sure yet what the relevant difference is between the two platforms.

So, excitingly, “all” that’s left from here is to clean up the tile engine, port over the sprite animations and sound controls from RocketHaxe 1.0, and it’s all set for some fancy platformer-type games!