Still a noticeable amount of quivering that I can’t tune out just by manipulating parameters. I played a little bit with trying to damp small amounts of energy, but I think they’re not actually low energy when they quiver like that so it’s hard to filter out. I think in many typical game uses it won’t matter much because they won’t generally stack up the same way, but hopefully I can iron that out.
A basic tile engine is now also working. Demo to come.
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.
After long hiatus, work proceeds in dribs and drabs on RocketHaxe 2.0. Most of the widgets and core algorithms in RocketHaxe 1.0 were solid, but I wanted to streamline the entities and overall screen management/game flow. The latter turned out to be not so bad on second look, but has been cleaned up a bit.
The former I’ve been thinking about a lot. On the one hand, I want to keep the library well scoped, and focused on small, relatively simple games. It’s not supposed to be the foundation of a decades-long franchise production effort. It’s also got to work in Flash on low-end machines, as well as limited Android mobile devices. That said, I strongly prefer more declarative approaches and am a big fan of component based architectures. So with the redesign I’ve been trying to walk a line between the two: Enough component style design to simplify the object hierarchy; not so much to be overwhelming or hurt performance.
It’s still in flux as I think about the design a bit, but the basics are in place for things to be once again moving around and such:
(the demo display is wider than this blog, so the ball goes off to the right a bit)
Code for that demo is fairly straightforward; excluding the imports declarations, this is all it takes to setup and launch the ball:
class Main
extends com.rocketshipgames.haxe.Game
{
private function new():Void
{
super();
trace("Balls Demo");
var game = new com.rocketshipgames.haxe.ArcadeScreen();
//-- Create a ball!
var ball = new Entity();
var kinematics = new Kinematics2DComponent({ xvel: 200, yvel: 200});
ball.addComponent(kinematics);
var bounds = new Bounds2DComponent();
bounds.setBounds(BOUNDS_DETECT,
25, 25,
Display.width-25, Display.height-25);
bounds.offBoundsLeft = bounds.bounceLeft;
bounds.offBoundsRight = bounds.bounceRight;
bounds.offBoundsTop = bounds.bounceTop;
bounds.offBoundsBottom = bounds.bounceBottom;
ball.addComponent(bounds);
ball.addComponent(new BallShape(game));
game.world.entities.addComponent(ball);
//-- Start the game
flash.Lib.current.addChild(game);
// end new
}
// end Main
}
The BallShape component is just a holder for some Flash DisplayList geometry:
private class BallShape
extends flash.display.Shape
implements com.rocketshipgames.haxe.component.Component
{
private var position:Position2D;
public function new(parent:flash.display.Sprite):Void
{
super();
graphics.beginFill(0xFF0000);
graphics.drawCircle(0, 0, 25);
graphics.endFill();
parent.addChild(this);
}
public function attach(containerHandle:ComponentHandle):Void
{
position = cast(containerHandle.findCapability("position-2d"),
Position2D);
update(0);
// and attach
}
public function detach():Void { }
//------------------------------------------------------------------
public function update(elapsed:Int):Void
{
x = position.x;
y = position.y;
}
// end BallShape
}
One of the big things in there is that I’m trying to avoid having to send/receive signals for every little thing, like a position update. It just feels like a lot of overhead to incur for constantly occurring tasks. So components are able to fetch each other, and it’s expected they’ll hold on to pointers and interact directly to some extent, particularly the core in-library capabilities like kinematics and collisions.
However, the mechanisms are there to code that way if you wish, which could make sense for higher level game events like taking damage. Just like in RocketHaxe 1.0, the overall game world provides for signals, states (properties), and scheduled events. But that’s been repackaged and reused such that each entity actually instantiates those mechanisms internally as well if any component wishes to use them. So you can do things like have your collision response throw a damage signal that any number of other components in that entity then process.
Where this all gets tricky is with collision physics and graphics, because they’re both intertwined with other mechanisms outside of the entity itself, with some precise execution ordering requirements. So I expect this to change a bit more, but it’s not half bad the way it is now.