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!
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.