RocketHaxe 2.0: Physics! Components!

I made some more cool updates to RocketHaxe 2.0 while I was on vacation. Here’s a little demo:

The big thing here is that the component model matured a bit more, and there’s some basic physics. The software architecture of the collisions/physics components is probably going to change, but I’m fairly happy with the underlying component model now. The physics will need a ton of tweaking and corner case catching, but the basics are working well and already kind of fun to watch.

Components

This is what the main function for the demo program currently looks like:

  public function new():Void
  {
    trace("Balls 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();

    //-- Create the container to collectively collide all the bouncers
    collisionGroup = new RigidBodyImpulseCollisionContainer();
    game.world.mechanics.add(collisionGroup);

    //-- Create the container for the bouncers' graphics.  It takes a
    //-- flash.display.Sprite (which an ArcadeScreen ultimately is) as
    //-- the root layer in which to place the graphics.
    graphics = new DisplayListGraphicsContainer(game);
    game.addGraphicsContainer(graphics);

    //-- Add an entity to the world and schedule more
    generateBouncer();

    //-- 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);

    // end new
  }

The balls are populated by the generateBouncer() function:

  private function generateBouncer():Void
  {
    //-- Bouncer is a simple entity defined in this sample
    var ball = new Bouncer();

    //-- Add new behavior on the basic Bouncer, in this case bounds
    placeBounds(ball);

    //-- Add the new Bouncer to the game world and display and make it live!
    collisionGroup.add(ball);
    graphics.add(ball);
    game.world.entities.add(ball);

    //-- Schedule another Bouncer to be created in a second
    if (bouncerCount < 9)
      game.world.scheduler.schedule(500, generateBouncer);

    bouncerCount++;
    trace(bouncerCount + " bouncers");

    // end generateBouncer
  }

Although it doesn’t have to be, the ball entities are defined in a class Bouncer. This really doesn’t do anything but populate some standard components:

class Bouncer
  extends com.rocketshipgames.haxe.component.ComponentContainer
{
  public function new():Void
  {
    super();

    add(new Kinematics2DComponent({ x: 0, y: 0, xvel: 50, yvel: 200}));

    //-- Add a description of this object's physical shape
    add(RigidBody2DComponent.newCircleBody(25, 1, 1));

    //-- Add a graphical Flash Shape representation to the Bouncer.
    //-- If there were more than one, e.g., for different panels on
    //-- the UI, the shape can be optionally tagged.
    var shape = new flash.display.Shape();
    shape.graphics.beginFill(0xFF0000);
    shape.graphics.drawCircle(0, 0, 25);
    shape.graphics.endFill();
    add(new DisplayListGraphicComponent(shape));

    //-- If the ball is placed within bounds, respond to hitting them
    signals.add(Bounds2DComponent.SIG_BOUNDS2D,
                function(SignalID, opt:Dynamic):Bool
                {
                  var s:Bounds2DSignalData = opt;
                  if (s == BOUNDS_RIGHT)
                    trace("RIGHT");
                  return false;
                });
    // end new
  }
  // end Bouncer
}

Back in the main program’s generateBouncer(), bounds are added to balls like so:

  private function placeBounds(ball:Bouncer):Void
  {
    /*
     * The Bouncer could impose bounds on itself, but in this sample
     * it's just a basic object that flies around.  The outer game
     * imposes bounds on that movement by adding a new component.
     */

    var bounds = new Bounds2DComponent();
    bounds.setBounds(0, 0, Display.width, Display.height);

    //-- Bounds2DComponent contains a number of common bounds
    //-- reactions, such as stopX, bounceX, cannotLeaveX, cycleX, and
    //-- doNothing, where X is Left, Right, Top, Bottom.
    bounds.offBoundsLeft = bounds.bounceLeft;
    bounds.offBoundsRight = bounds.bounceRight;
    bounds.offBoundsTop = bounds.bounceTop;

    //-- But a user defined function can also be specified.
    bounds.offBoundsBottom = function() { trace("BOTTOM");
                                          bounds.bounceBottom(); }

    //-- By default Bounds2DComponent doesn't issue a signal, but we
    //-- can turn it on so other components are notified.
    bounds.enableSignal();

    ball.add(bounds);

    // end placeBounds
  }

So, the model does a reasonable job of allowing the entity code to just specify behaviors and properties of that entity (kinematic movement, circular rigid body, ball graphic, etc.), and then the outer program to place it within some context (game world bounds, collision group, etc.).

Internally, the components can rely on their container supporting signals, states, and scheduled events. However, none of these mechanisms are instantiated if they’re not used, so lightweight entities don’t consume unnecessary memory resources for them. Components can also claim and search for capabilities/tags in order to find each other. E.g., the impulse physics component needs both a rigid body description and the kinematics component. All of these elements are labelled by numeric identifiers, which in turn are generally hashcodes of some label generated at startup. The convention is that dependent components are expected to be long-lived, so it’s safe to cache a handle to them rather than hit the search map constantly.

Possibly my personal favorite part, scheduled events are managed via a heap, enacting a very simple but efficient discrete event scheduler.

Physics

RocketHaxe 1.0 had a good collision detection implementation, but it only detected them. That was enough for shooters like Gold Leader, which threw stuff around a little bit in response to collisions but mostly just applied damage, but it’s a major missing component for other games. With the update I’m trying to incorporate some optional physics so it’s easy and efficient to bounce things off each other and get some basic interactions going.

Internally I’m still working the architecture, but some of the components are solid. RocketHaxe 1.0’s collision detection was actually very efficient. Algorithmically, it used a modifed sweep line algorithm for the broadphase: A heap drives the sweep along one axis as usual, but rather than build a map on the other axis it just does a linear scan of active entities. The (unproven) idea is that in a game setting, few enough entities are active at any particular vertical band to make the algorithmic advantages of a map on the horizontal not overcome the lesser constant factors of a linear sweep. Implementation-wise, RocketHaxe 1.0’s collision detection was super tight. Not only are objects recycled frame to frame, meaning there’s no new allocations provided there’s no increase in the number of entities active at any given point, but it’s all packed into one very simple object reused across the heap, scanline, and deadpool.  Combined, it worked very well.  Depending on how you looked at it, versus a naive O(n^2) all-pairs broadphase, the sweep-scan implementation either doubled the framerate or increased the limit number of entities by more than 20x.

RocketHaxe 2.0’s core broadphase is the same, though the implementation is in some sense a little less tight, less hacker-ish. Again there are no new allocations without an increase in the active entity high count, but it uses some generic deadpool and list classes in RocketHaxe’s core data structures module. That means there’s a few more objects created overall per entity—list item wrappers and so on—but in return it’s a much more readable implementation.

The rigid body collision reaction is a simple impulse response. Although the demo, and indeed the code so far, only does circular rigid bodies, the architecture’s there for axis-aligned boxes and balls. Together they should support a variety of interesting games. Most importantly, I’m trying to steer the architecture toward easily mixing different types of collisions, i.e., a tile map alongside entity collisions, and making that clean is the part I’m trying to imagineer right now.