Rusher 2 – Getting Started

Go to Rusher 2 project homepage
Rusher 2 tutorial list
View Cursor example
View Keyboard example
Example source

In this tutorial I’ll show you how to put together two minimalistic games, or two very simple applications in particular, one with mouse control, and one with keyboard control.

Extending The Engine

First, you’ll need to extend either the Engine class or BasicEngine class. The Engine class is the most abstract game engine, without any systems added to it. The BasicEngine extends the Engine class and already has some basic systems added to it, including the Clock system, the Mouse system, and the Keyboard system (not all of the added systems are listed here).

You override the addSystem() method and add new systems here. The system manager is passed in as an argument for you to add systems to. Here we add two systems to the engine, which we will write later. The DisplayObjectUpdater is the system that updates every entity’s view with their position data, although we’ll pretty much create only one entity in this example. The EntityCreator system gives our game a “kick start” by creating an entity and adding several components to it.

Our document class looks like this. It passes a sprite to the engine constructor, where the sprite is intended to be the main container.

public class Main extends Sprite
{
    public function Main() 
    {
        var canvas:Sprite = new Sprite();
        addChild(canvas);
        
        var engine:IEngine = new CursorExampleEngine(canvas);
        engine.start(stage);
    }
}

And this is what our engine extension looks like. Note that you must first call the addSystems() method defined in the superclass, otherwise the aforementioned systems will not be added first.

public class CursorExampleEngine extends BasicEngine
{
    private var _canvas:Sprite;
    
    public function CursorExampleEngine(canvas:Sprite) 
    {
        _canvas = canvas;
    }
    
    override protected function addSystems
    (
        systemManager:ISystemManager
    ):void 
    {
        super.addSystems(systemManager);
        
        systemManager.addSystem(new DisplayObjectUpdater(_canvas));
        systemManager.addSystem(new CursorCreator());
    }
}

The Position and View Components

We are going to write a component class that stores an entity’s spacial data, the 2D (x, y) coordinate. It’s as simple as declaring two variables named x and y. Also, you’ll need to implement the IComponent interface for this to be added to an entity. This interface required that a dispose() method be implemented. In this method you should clean up any resource that is no longer needed. Since this component only has two primitive properties, it’s okay to just leave this method blank.

public class Position implements IComponent
{
    public var x:Number;
    public var y:Number;
    
    public function Position(x:Number = 0, y:Number = 0):void
    {
        this.x = x;
        this.y = y;
    }
    
    public function dispose():void
    {
        
    }
}

Next, the View component. This component is for holding a reference to an entity’s corresponding display object. The code looks pretty much straightforward and similar to the Position component.

public class View implements IComponent
{
    public var display:DisplayObject;
    
    public function View(display:DisplayObject)
    {
        this.display = display;
    }
    
    public function dispose():void
    {
        
    }
}

The CursorController Component

This component listens to the Clock system and updates every frame. When it updates, it assigns the current mouse position to the Position component of the same entity. Thus this component needs three references: the Clock system, the Mouse system, and the Position component. We’ll use Dependency Injection to obtain these references.

After the dependencies are injected, we’ll need to listen to the clock, so we mark a method with the [PostConstruct] metadata tag, so this method will be invoked right after the dependencies are injected.

Also note that in the implemented dispose() method we actually are going to write something. We need to remove the listener function from the clock and dispose of all the references at hand.

public class CursorController implements IComponent
{
    [Inject]
    public var clock:Clock;
    
    [Inject]
    public var position:Position;
    
    [Inject]
    public var mouse:Mouse;
    
    public function CursorController() 
    {
        
    }
    
    [PostConstruct]
    public function listenToClock():void
    {
        clock.add(update);
    }
    
    private function update(dt:Number):void
    {
        position.x = mouse.x;
        position.y = mouse.y;
    }
    
    public function dispose():void
    {
        clock.remove(update);
        clock = null;
        
        position = null;
        
        mouse = null;
    }
}

The DisplayObjectUpdater System

Now it’s time to write the system that updates the position of display objects according to the Position component of the same entities.

This time let’s take a look at the final code first, and I’ll explain it afterwards.

public class DisplayObjectUpdater implements ISystem
{
    [Inject]
    public var clock:Clock;
    
    [Inject]
    public var entityManager:IEntityManager;
    
    private var _canvas:Sprite;
    
    public function DisplayObjectUpdater(canvas:Sprite) 
    {
        _canvas = canvas;
    }
    
    private var entities:IEntityCollection;
    
    public function onAdd():void
    {
        clock.add(update);
        
        entities = entityManager.getEntityCollection(RenderNode);
        entities.onAdd.add(addChild);
        entities.onRemove.add(removeChild);
    }
    
    private function addChild(node:RenderNode):void
    {
        _canvas.addChild(node.view.display);
    }
    
    private function removeChild(node:RenderNode):void
    {
        _canvas.removeChild(node.view.display);
    }
    
    private function update(dt:Number):void
    {
        var iter:IEntityIterator = entities.getIterator();
        var node:RenderNode;
        
        while (node = iter.current())
        {
            var display:DisplayObject = node.view.display;
            var position:Position = node.position;
            
            display.x = position.x;
            display.y = position.y;
            iter.next();
        }
    }
    
    public function dispose():void
    {
        clock.remove(update);
        clock = null;
        
        entities.onAdd.remove(addChild);
        entities.onRemove.remove(removeChild);
        entities = null;
        
        entityManager.disposeEntityCollection(RenderNode);
        entityManager = null;
    }
}

Similar to the components, a system class has to implement an interface, the ISystem interface, which requires the implementation of two methods: onAdd() and dispose(). The onAdd() method will be invoked after a system is added to the engine and the dependency injection is finished. The dispose() is for resource clean-up, which will be invoked after the system is removed from the engine.

How does the system obtain every single reference of every entity that has both a Position component and a View component? Don’t worry, the entity manager takes care that for you. All you have to do it use the IEntityManager.getEntityCollection() method. You pass a class reference to this method, which should extend the EntityNode class and should specify that it needs both the Position component and the View component. We’ll see the code for the RenderNode class later.

The entity collection has two signals we need to listen to, the onAdd and onRemove. They are dispatched when a new entity with the two desired components are added to the collection. So, we simply add the display object of the new entity to the display list, and we remove it from the display list, respectively.

In the update method, we generate an iterator that allows you to traverse through the collection by invoking the IEntityCollection.getIterator() method. A fresh iterator should point to the first node in the collection. The IEntityIterator.current() method returns a reference to the current node it’s pointing to, if the iterator is pointing past the last node, the method returns a null value. The code>IEntityIterator.next() method makes the iterator to point to the next node in the collection. We make use of these two methods to pull out information about each of the entities in the collection, again, although there is only one entity in this example.

Now it’s time to take a look at this mysterious RenderNode class. It’s quite simple, actually. You override the getNeededComponents() method and return an array of class references of the components you need, and you pull out the information in the overridden pullComponents() method. A Dictionary is used here. The key is the component class reference, and the value would be the actual component object of the entity.

public class RenderNode extends EntityNode
{
    public var position:Position;
    public var view:View;
    
    override protected function getNeededComponents():Array 
    {
        return [Position, View];
    }
    
    override protected function pullComponents
    (
        components:Dictionary
    ):void 
    {
        position = components[Position];
        view = components[View];
    }
}

The EntityCreator System

This is in fact a “one shot” system, which creates entities we need and does nothing afterward. This isn’t what a system is supposed to do, but for convenience, we’ll use a system this way. In later tutorials, I’ll show you how to perform “one shot” actions using the Command system.

Here’s the code for the system class.

public class CursorCreator implements ISystem 
{
    [Inject]
    public var entityManager:IEntityManager;
    
    public function CursorCreator() 
    {
        
    }
    
    public function onAdd():void
    {
        Mouse.hide();
        
        var cursor:IEntity = entityManager.createEntity();
        cursor.addComponent(new Position());
        cursor.addComponent(new View(new CursorGraphics()));
        cursor.addComponent(new CursorController());
    }
    
    public function dispose():void
    {
        
    }
}

We only need to focus on the onAdd() method. An entity is created using the IEntityManager.createEntity() method, and components are added to the entity using the IEntity.addComponent() method. The CursorGraphics class is just a display object subclass for the cursor appearance.

That’s it! Our game is finished. Isn’t it fantastic that all you need to focus on is the game logic instead of the architecture concerning reference management? All thanks to SwiftSuspenders!

The Keyboard Example

Wait! I mentioned this tutorial has two examples, right? The other example is similar to this one, only that it uses a keyboard to control the entity. So, I’ll just show you the code worth mentioning, the KeyController component class.

Instead of adding a CursorController component in the EntityCreator system, you add the KeyController component instead.

public class KeyboardController implements IComponent
{
    [Inject]
    public var clock:Clock;
    
    [Inject]
    public var position:Position;
    
    [Inject]
    public var keyboard:Keyboard;
    
    public function KeyboardController() 
    {
        
    }
    
    [PostConstruct]
    public function listenToClock():void
    {
        clock.add(update);
    }
    
    //400 pixels per second
    private const SPEED:Number = 400;
    
    private function update(dt:Number):void
    {
        var dx:Number = 0;
        var dy:Number = 0;
        
        if (keyboard.isDown(Key.RIGHT)) dx += SPEED;
        if (keyboard.isDown(Key.LEFT)) dx -= SPEED;
        if (keyboard.isDown(Key.UP)) dy -= SPEED;
        if (keyboard.isDown(Key.DOWN)) dy += SPEED;
        
        //one of dx and dy is not zero
        if (dx || dy)
        {
            //both dx and dy are not zero
            if (dx && dy)
            {
                //normalize
                dx *= Math.SQRT1_2;
                dy *= Math.SQRT1_2;
            }
            
            //update position
            position.x += dx * dt;
            position.y += dy * dt;
            
            position.x = RusherMath.clamp(position.x, 20, 780);
            position.y = RusherMath.clamp(position.y, 70, 530);
        }
    }
    
    public function dispose():void
    {
        clock.remove(update);
        clock = null;
        
        position = null;
        
        keyboard = null;
    }
}

I won’t go through the code in the update() method, because I know you’re clever enough to figure that out yourself 🙂

The complete source code for these two examples with proper imports can be found on Rusher 2’s example repository.

Hvae fun 🙂

About Allen Chou

Physics / Graphics / Procedural Animation / Visuals
This entry was posted in Rusher. Bookmark the permalink.

Leave a Reply