Line Test Demo

While entangled in the bowels of a diseased telecon I decided to make good use of my time and wrote this simple test:

Click, drag, and release to draw a new line. The text will (should?!) indicate whether or not the two points are on the same side of the line or not.

The demo uses some basic Phaser features for the interaction and drawing. Really though it’s just quadruple checking a basic calculation:

(Bx – Ax) * (Cy – Ay) – (By – Ay) * (Cx – Ax)

Where the line is defined by (Ax,Ay)–(Bx,By) and a point is (Cx, Cy). The value will be 0 if the point is on the line, and otherwise negative or positive depending on which side of the line it is on. The specific sign depends on the orientation of the line, but you can compare the sign for two points to determine if they’re on the same side or not. That can be boiled down into a single calculation. However, testing if four points are on the same side is a key part of a simple line/rectangle intersection test. For that use it seemed simpler to just calculate each separately.

The full Javascript code is:

var game = new Phaser.Game(530, 300, Phaser.AUTO, 'gamecontainer',
                           { create: create, update: update, render: render });

var line;
var setting = false;

var point1;
var point2;

var textSame;
var textDiff;


function create() {

  line = new Phaser.Line(game.world.width/4, game.world.height/4,
                         3*game.world.width/4, 3*game.world.height/4);

  point1 = new Phaser.Point(game.world.width/2, game.world.height/4);
  point2 = new Phaser.Point(game.world.width/2, 3*game.world.height/4);

  textSame = game.add.text(4, 0, "Same Side", { fill: '#ffffff' });
  textSame.visible = false;

  textDiff = game.add.text(0, 0, "Different Side", { fill: '#ffffff' });
  textDiff.visible = false;

  game.input.onDown.add(click, this);

  setting = true;

}

function update() {

  if (setting) {
    if (game.input.activePointer.isDown) {
      line.end.set(game.input.activePointer.x, game.input.activePointer.y);
    } else {
      setting = false;

        var sign1 =
            (line.end.x - line.start.x) * (point1.y - line.start.y) -
            (line.end.y - line.start.y) * (point1.x - line.start.x);

        var sign2 =
            (line.end.x - line.start.x) * (point2.y - line.start.y) -
            (line.end.y - line.start.y) * (point2.x - line.start.x);

        console.log("Signs are " + sign1 + "  " + sign2);

        if ((sign1<0 && sign2<0) ||
            (sign1>0 && sign2>0) ||
            (sign1==0 && sign2==0)) {

            textSame.visible = true;
            textDiff.visible = false;

        } else {
            textSame.visible = false;
            textDiff.visible = true;
        }
    }
  }
}

function click(pointer) {
  setting = true;
  line.start.set(pointer.x, pointer.y);
}

function render() {
  game.debug.geom(line);
  game.debug.geom(point1, '#ff0000');
  game.debug.geom(point2, '#ff0000');
}

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!