This is more of a note than a tutorial, but I do hope it is complete.  I had trouble figuring out how to break into an entity system framework, and through various conversations, reading blog posts, and reading the code, I pieced it together.

A caution about using Ash:  you have to make sure to destroy your objects.  As the code below stands — and I may change it in the future — none of the game’s objects get destroyed, so the memory will fill up.  Ideally every object should come from an object pool, since there is plenty of re-creating going on; besides, it’s just good coding to do so.

Overview

Systems are initialized with the type of Node that it will process. That is the part I have been missing. To create a new System, generally you should extend a ListIteratingSystem, even if there is only going to be a total of one node, as it solves a couple of the setup things. The type of node is sent in the super() call; that node is now the filter. During an update() call, all current nodes — which includes anything active in the program — is filtered by the System‘s node type, and each of this subset is sent to the system‘s update() method.

The component

package
{
   public class TestComponent
   {
      public var value:String;

      public function TestComponent(initialValue:String) 
      {
         this.value = initialValue;
      }
   }
}

The node

package asteroids.systems
{
   import ash.core.Node;
   import asteroids.components.Collision;
   import asteroids.components.Display;
   import asteroids.components.Position;

   public class TestNode extends Node
   {
      public var testComponent:TestComponent;
      public var position : Position;
      public var collision : Collision;
      public var display : Display;
   }
}

The view class that defines the TestNode‘s appearance. I added a TextField to show that two different systems will be acting on TestNode at different times.

package asteroids.systems
{
   import flash.display.Sprite;
   import flash.text.TextField;

   public class TestView extends Sprite
   {
      public var tf:TextField;

      public function TestView(radius:int)
      {
         var angle:Number = 0;
         graphics.beginFill(0xddaa00);
         graphics.moveTo(radius, 0);
         graphics.drawEllipse(0, 0, radius, radius * 2);
         graphics.endFill();

         tf = new TextField();
         tf.width = 50;
         tf.textColor = 0xff0000;
         tf.text = "test";
         this.addChild(tf);
      }
   }
}

TestSystem, which updates instances of TestNode. It sets the TextField and scales the current node.

package asteroids.systems
{
   import ash.tools.ListIteratingSystem;

   public class TestSystem extends ListIteratingSystem
   {
      public function TestSystem(nodeClass:Class = null, nodeUpdateFunction:Function = null, nodeAddedFunction:Function = null, nodeRemovedFunction:Function = null)
      {
         super(TestNode, updateNode);
      }

      private function updateNode(node:TestNode, time:Number):void
      {
         if (TestView(node.display.displayObject).tf.text != node.testComponent.value)
         {
            TestView(node.display.displayObject).tf.text = node.testComponent.value;
         }

         if (node.display.displayObject.scaleX < 2 && node.testComponent.value == "larger")
         {
            node.display.displayObject.scaleX = node.display.displayObject.scaleY = node.display.displayObject.scaleX * 1.1;
         }
         else if (node.display.displayObject.scaleX > .5 && node.testComponent.value == "smaller")
         {
            node.display.displayObject.scaleX = node.display.displayObject.scaleY = node.display.displayObject.scaleX * .9;
         }
      }
   }
}

Fitting it into the game

In Asteroids.as, add a TestSystem, setting the priority as desired:

game.addSystem( new TestSystem(), SystemPriorities.resolveCollisions );

In EntityCreator.as, make a method that creates and adds the node and it’s various entities and components:

public function createTest():Entity
{
   var test:Entity = new Entity();
   test.add(new TestComponent(""));
   test.add(new Position(200, 40,0));
   test.add(new Collision(5));
   test.add(new Motion(10,0,0,0));
   test.add(new Display(new TestView(Math.random() * 20 + 20)));
   game.addEntity(test);
   return test;
}

Finally, in GameManager.as, make a node list of the TestNode nodes:

Initialize it in the method addToEngine():

testNodes = game.getNodeList(TestNode);

Destroy it in the method removeFromEngine():

testNodes = null;

Create the actual nodes in the update() method, within the node for-loop (the code below will only create a single node, but look at the rest of the file for how multiple asteroids and spaceships are created):

for (node = gameNodes.head; node; node = node.next)
{
   [...]
   if (testNodes.empty)
   {
      creator.createTest();
   }
   [...]
}

Manipulating the TestNodes from a different system.

Create, initialize, destroy the same as in GameEngine.as, and update() is slightly different.  update() changes the text held in the current TestComponent:

private var testList:NodeList;

// addToEngine():
testList = game.getNodeList(TestNode);

// removeFromEngine():
testList = null;

// update():
for (var test:TestNode = testList.head; test; test = test.next)
{
   astLoop: for (asteroid = asteroidList.head; asteroid; asteroid = asteroid.next)
   {
      if (Point.distance(asteroid.position.position, test.position.position) <= asteroid.collision.radius + test.collision.radius)
      {
         test.testComponent.value = "larger";
         break astLoop;
      }
      else
      {
         test.testComponent.value = "smaller";
      }
   }
}

Conclusion

Please let me know if this doesn’t work, and, if so, what the problem seems to be.

Advertisements