Going from Blender to 2k Digital Cinema DCP

I copied this webpage:  http://johnnygizmo.blogspot.nl/2014/06/going-from-blender-to-2k-digital-cinema.html

into a PDF file for easy reference, and to make sure it doesn’t get lost to the interaether.

 

Going from Blender to 2k Digital Cinema DCP

A super simple note-taking tool – Firefox Add-on SDK

Firefox Add-on SDK code set that creates a context (right-click) menu item allowing the user to select any text and download it to a file on the desktop.  The filename is fixed (“OnlineNotes.html), but you as Super Developer can change that functionality!  Please note that this idea is mostly lifted from the FF tutorials; most changes I made were to fit it to my purposes, with little creative addition beyond that.

This is specialized code for a small project, so would need some work to be universally capable.  You will need to know or learn how to create and install a script to make this work.

Main code, stored in {your addon dir}/lib/main.js

exports.main = function (options, callbacks) {
   // load the context menu script. (Is part of the Firefox SDK.)
   var contextMenu = require("context-menu");
   // reference and store the local directory "data"
   // element-getter.js is stored in this directory
   var data = require("self").data;

   var fileMgr = require("fileMgr-test.js");

   contextMenu.Item({
      // name that appears in the right-click menu
      label: "Take a note.",
      // only show this menu item when something is selected
      context: contextMenu.SelectionContext(),
      // show this menu item after the entire page is loaded.  
      // This may take a while at times, and may make the user wonder where the menu item is.
      contentScriptWhen : "ready",
      // load this script onto the page (stored in the {local}/data directory).
      contentScriptFile : [data.url('element-getter.js')],
      // when a message is sent (from the page to the menu item), run this
      // elementContent = a JSON object with image name, URL, author, and copyright data
      onMessage: function (elementContent) {
         // record some info to the console -- run this script from the command line to see this data
         // console.log( elementContent );
         // save the image info
         fileMgr.saveText( elementContent, "OnlineNotes.html" );
      }
   });
}

.

Context menu functions, stored in {your addon dir}/lib/fileMgr-test.js

'use strict';

// super powerful Component script.  Allows access to everything a browser can access.
// As noted by Firefox, this is experimental, and may change or be removed in the future.
var {Cc, Ci, Cm, Cr, Cu} = require("chrome");

// joins an array with a single-quote and comma
var joinQuoted = function(arr){
   return arr.map(function(elem){
         // This will wrap each element of the array with quotes
         return "'" + elem + "'";
      }).join(","); // This puts a comma in between every element
};

// make joinQuoted a public function that other scripts can use
exports.joinQuoted = joinQuoted;

// save (create, else append) textJSON to the outFile
// This function is public to other scripts.
// NOTE:  The text in the file is an array of arrays, but it will not have a closing bracket.  This will have to be added manually.
exports.saveText = function(textJSON, outFile) {
   // netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');

   // load file stuff
   Cu.import("resource://gre/modules/FileUtils.jsm");

   // join textJSON array with quotes
   // var joinedText = ",[" + joinQuoted(textJSON) + "]";
   var joinedText = textJSON;
   // console.log("saveText.text = " + joinedText);

   // create a new file reference to the OS's current desktop directory
   var file = Cc["@mozilla.org/file/directory_service;1"].
           getService(Ci.nsIProperties).
           get("Desk", Ci.nsIFile);
   // reference a specific file for appending data
   file.append(outFile);

   console.log("intended file.path: " + file.path);

   // create the file if it doesn't exist
   if ( file.exists() == false ) {
      // console.log( "Creating file: " + file.path );
      file.create( Ci.nsIFile.NORMAL_FILE_TYPE, 420 );

      // fix joinedText for the first entry
      // replace initial comma with opening bracket
      // NOTE:  The file will not have a closing bracket.  This will have to be added manually.
      // joinedText = "[" + joinedText.substring(1,joinedText.length);
      joinedText = 
         "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\"><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><title>Notes</title></head><body >"  
         + joinedText;
   }

   // create an output stream
   var outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
      .createInstance( Ci.nsIFileOutputStream );

   // flags found here:  https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/FileUtils.jsm?redirectlocale=en-US&redirectslug=JavaScript_code_modules%2FFileUtils.jsm

   // initalize/start the output stream.
   // write only, create if non-existent (redundant), append (instead of overwriting)
   outputStream.init( file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_APPEND, 420, 0 );
   // write joinedText to file
   var result = outputStream.write( joinedText, joinedText.length );
   // close the stream
   outputStream.close();   
};

.
Code added to the page content, stored in {your addon dir}/data/element-getter.js

// Get the text selected on the webpage.

// This javascript file is _not_ connected to the context (right-click) menu.  They are in two separate code areas.
// The only way this file can communicate with the context menu is "self.postMessage(info)", 
// where "info" is a JSON object.
self.on('click', function (node, data) {
   var text = window.getSelection().toString();

   console.log("saving text = " + text);

   text = "<div style=\"width: 515px; margin: 8px;\">\n\"" + text + "\"\n<br />&nbsp;&nbsp;&nbsp;&nbsp;<a href=\"" + document.URL + "\" target=\"_blank\">" + document.URL + "</a></div><p />\n" ;

   self.postMessage(text);
});

Flickr image and copyright download – Firefox Add-on SDK

Firefox Add-on SDK code set that creates a context (right-click) menu item allowing the user to download both an Flickr.com image and it’s information to the OS’s current desktop.

This is specialized code for a small project, so would need some work to be universally capable.  It was made to complement my Adobe Illustrator script for creating image posters.
It only works on Flickr.com, and will probably break for the rest of the internet. However, this context menu will only appear when you are on a Flickr website.

You will need to know or learn how to create and install a script to make this work.

Main code, stored in {your addon dir}/lib/main.js

exports.main = function (options, callbacks) {
   // load the context menu script. (Is part of the Firefox SDK.)
   var contextMenu = require("context-menu");
   // reference and store the local directory "data"
   // element-getter.js is stored in this directory
   var data = require("self").data;

   var fileMgr = require("fileMgr-test.js");

   contextMenu.Item({
      // name that appears in the right-click menu
      label: "Download image and info to desktop.",
      // only show this menu item when on Flickr website
      context: contextMenu.URLContext("*.flickr.com"),
      // show this menu item after the entire page is loaded.  
      // This may take a while at times, and may make the user wonder where the menu item is.
      contentScriptWhen : "ready",
      // load this script onto the page (stored in the {local}/data directory).
      contentScriptFile : [data.url('element-getter.js')],
      // when a message is sent (from the page to the menu item), run this
      // elementContent = a JSON object with image name, URL, author, and copyright data
      onMessage: function (elementContent) {
         // record some info to the console -- run this from the command line to see this data
         console.log( fileMgr.joinQuoted(elementContent) );
         console.log("title: " + elementContent[4]);
         // save the image info
         fileMgr.saveText( elementContent );
         // save the image
         fileMgr.downloadImage(elementContent[3], elementContent[4]);
      }
   });
}

.

Context menu functions, stored in {your addon dir}/lib/fileMgr-test.js

'use strict';

// super powerful Component script.  Allows access to everything a browser can access.
// As noted by Firefox, this is experimental, and may change or be removed in the future.
var {Cc, Ci, Cm, Cr, Cu} = require("chrome");

// joins an array with a single-quote and comma
var joinQuoted = function(arr){
   return arr.map(function(elem){
         // This will wrap each element of the array with quotes
         return "'" + elem + "'";
      }).join(","); // This puts a comma in between every element
};

// make joinQuoted a public function that other scripts can use
exports.joinQuoted = joinQuoted;

// save (create, else append) textJSON to the (hard-coded) file "flickrFileInfo.txt"
// This function is public to other scripts.
// NOTE:  The text in the file is an array of arrays, but it will not have a closing bracket.  This will have to be added manually.
exports.saveText = function(textJSON) {
   // netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');

   // load file stuff
   Cu.import("resource://gre/modules/FileUtils.jsm");

   // join textJSON array with quotes
   var joinedText = ",[" + joinQuoted(textJSON) + "]";
   console.log("saveText.text = " + joinedText);

   // create a new file reference to the OS's current desktop directory
   var file = Cc["@mozilla.org/file/directory_service;1"].
           getService(Ci.nsIProperties).
           get("Desk", Ci.nsIFile);
   // reference a specific file for appending data
   file.append("flickrFileInfo.txt");

   console.log("intended file.path:");
   console.log(file.path);

   // create the file if it doesn't exist
   if ( file.exists() == false ) {
      console.log( "Creating file: " + file.path );
      file.create( Ci.nsIFile.NORMAL_FILE_TYPE, 420 );

      // fix joinedText for the first entry
      // replace initial comma with opening bracket
      // NOTE:  The file will not have a closing bracket.  This will have to be added manually.
      joinedText = "[" + joinedText.substring(1,joinedText.length);
   }

   // create an output stream
   var outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
      .createInstance( Ci.nsIFileOutputStream );

   // flags found here:  https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/FileUtils.jsm?redirectlocale=en-US&redirectslug=JavaScript_code_modules%2FFileUtils.jsm

   // initalize/start the output stream.
   // write only, create if non-existent (redundant), append (instead of overwriting)
   outputStream.init( file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_APPEND, 420, 0 );
   // write joinedText to file
   var result = outputStream.write( joinedText, joinedText.length );
   // close the stream
   outputStream.close();   
};

// save (create or overwrite) an image from URI to file on the OS's current desktop
// This function is public to other scripts.
exports.downloadImage = function(aURLToDownload, aSaveToFile) {

   // import file stuff
   Cu.import("resource://gre/modules/FileUtils.jsm");

   // create a new web browser instance
   // this is not visible to the user
   var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
              .createInstance(Ci.nsIWebBrowserPersist);

   // get or create a file named aSaveToFile on the OS's current desktop 
   var file = FileUtils.getFile("Desk", [aSaveToFile + ".jpeg"]);
   console.log("image output file = " + file.path);

   // create an URI from the string aURLToDownload
   var obj_URI = Cc["@mozilla.org/network/io-service;1"]
              .getService(Ci.nsIIOService)
              .newURI(aURLToDownload, null, null);

   // set up the listeners for the hidden web browser
   persist.progressListener = {

      onProgressChange: function(
         aWebProgress, 
         aRequest, 
         aCurSelfProgress, 
         aMaxSelfProgress, 
         aCurTotalProgress, 
         aMaxTotalProgress
      ){
         // display download progress
         // NOTE: DOM element "progress_element" has to exist
         var percentComplete = (aCurTotalProgress/aMaxTotalProgress)*100;
         var ele = document.getElementById("progress_element");
         ele.innerHTML = percentComplete +"%";
      },
      onStateChange: function(
         aWebProgress, 
         aRequest, 
         aStateFlags, 
         aStatus
      ){
         // do something
      }
   };

   // begin the download
   persist.saveURI(obj_URI, null, null, null, "", file);
}

.
Code added to the page content, stored in {your addon dir}/data/element-getter.js

var newurl = "";

// Search the DOM, and find various info about the image.
// This code is intened for Flickr.com, and will probably fail on other sites.

// This javascript file is _not_ connected to the context (right-click) menu.  They are in two separate code areas.
// The only way this file can communicate with the context menu is "self.postMessage(info)", 
// where "info" is a JSON object.
self.on('click', function (node, data) {
   console.log("Menu item clicked.");

   // store various DOM references for easy changes
   var tag = "a";
   var attr = "rel";
   var licenseVal = "license cc:license";
   var titleTag = "title_div";
   var imgTag = "liquid-photo";
   var imgAttr = "src";

   // store an array of tag elements
   var elements = document.getElementsByTagName(tag);

   // URL 1:
   // http://creativecommons.org/licenses/by-nc-sa/2.0/deed.en
   // URL 2:
   // http://www.flickr.com/photos/{photo rights owner}/{photo id}{/}{possible stuff}

   // process the array of tag elements
   elementLoop:  for (var i = 0; i < elements.length; i++) 
   {
      // only save files with CC licenses:
      if(elements[i].getAttribute(attr) == licenseVal)
      {
         // create an array for storing results
         var outv = [];

         // store URL
         outv.push(document.URL);
         // store author ID
         // This is not the author's real name, which should probably be stored as well.
         outv.push(document.URL.replace(/.*\/photos\/(.*)\/\d*\/.*/, '$1'));
         // store license type (CC type)
         outv.push("CC " + elements[i].getAttribute("href").replace(/.*licenses.(.*).deed.*/, '$1').replace(/[-|\/]/g," ").toUpperCase());
         // store image
         // the original can be accessed by changing "z" to "o", and there are other options, 
         // but Flickr will not let you download the original without going through their interface.
         outv.push(document.getElementById(imgTag).src); //.replace("_z","_o"));
         // store title
         outv.push(document.getElementById(titleTag).innerHTML);

         console.log("element-getter.click:  title: " + outv[4]);

         // send the data to the context (right-click) menu
         self.postMessage(outv);

         // break after first successful element, since any success means the whole page matches.
         break elementLoop;
      }
   }
});

Adobe Illustrator Scripting Example

Below is a sample script I wrote to make web posters automatically.

An array is defined with arrays of image data (copyright, author, image name, etc.).  Then one image is loaded into Illustrator, scaled to fit the screen, placed on screen, and clipped to give it some border space.  A title is added and styled according to a pre-defined style called “MainTestStyle”.  A copyright notice is added underneath the image.  There are a few glitches, and it is not universal code, but this was a short project that needed a quick solution.

It does require some setup in Illustrator.  This script assumes:

  1. at least two layers.
  2. the document has a style called “MainTextStyle”.

I will comment more on this post if anyone asks for more details.  Otherwise, this is just a reference for me to a lot of AI scripting ideas.

// Note:  This script requires the correct context.  
// If you are using ExtendScript Toolkit CS6 (version 3.8.x or maybe others): 
// 1) In the top right corner of the script view, there is a drop-down menu where "Adobe Illustrator CS6 Type Library" should be selected.
// 2) In the dropdown box immediately above the script area, select "Adobe Illustrator CS6 [...]" should be selected.  

var rootdir = "C:\\Root\\directory\\for your images\\";

var ND_FILE = 0;
var ND_NAME = 1;
var ND_LICENSE = 2;
var ND_QUOTE = 3;

var arr = [
["some image filename 1.jpg","Author Name","License Type","Some quote to paste over the image."],
["some image filename 2.jpg","Another Author Name","License Type","Some different quote."],
["some image filename 3.jpg","Some Name","License Type","Third quote."],
];

// store font names
// Note:  show a list of font names available in Illustrator:  var str = "";  for(var i = 0; i < {some number}; i++){str += app.textFonts[i].name + "\n";}; alert(str);
var copyrightFontName = "ChaparralPro-Regular";    

// store document references
var doc = app.activeDocument;
var artb = doc.artboards[0];

// store rectangle sizes.  They do not work as expect, just remember that.
var inrect = Rect(27,-37,artb.artboardRect[2], -artb.artboardRect[3] );
var cliprect = Rect(-inrect[0], -inrect[1], inrect[2]+(inrect[1] * 2), inrect[3]-(inrect[0] * 2));

// store colors:
var blackRGB = new RGBColor();

var baseRGB = new RGBColor(); // #fbb03b
baseRGB.red = 251;
baseRGB.green = 176;
baseRGB.blue = 59;

var greyRGB = new RGBColor(); // 50% grey
greyRGB.red = 127.5;
greyRGB.green = 127.5;
greyRGB.blue = 127.5;

// get the text style
// "MainTextStyle" has to exist in the Illustrator document.  
// One issue in AI JS is that you cannot access a pre-defined style's settings, 
// (e.g., you can apply a "Drop Shadow" using code, but cannot change it from the default settings)
//  so you cannot build your own style in code.
// (Please research creating a new style if you do not know how.)
var mainTextStyle = doc.graphicStyles.getByName ("MainTextStyle");

// variables:
var lyr = null;
var imgClipGroup = null;
var img = null;
var scl = 0;
var clipbox = null;
var borderbox = null;
var maintext  = "";
var copyright = "";

// loop through the images

//for(var idx = 0; idx < 2; idx++)
for(var idx = 0; idx < arr.length; idx++)
{
    // =================
    // create a layer
    // name the layer {name: "woods scene"}
    // move the layer below the foreground
    // =================

    lyr = doc.layers.add();
    lyr.name = "image" + (doc.layers.length - 2);

    // Note: Moving layers DOES NOT work correctly.
    // move the layer below the foreground
    // lyr.zOrder (ZOrderMethod.SENDBACKWARD);  

    // =================
    // place image
    // create a clipping group
    // scale to max: 37p x 27p from artboard edges
    // center
    // =================

    imgClipGroup = doc.groupItems.add();
    imgClipGroup.name = "image, clipped";

    img = imgClipGroup.placedItems.add();
    img.name = "image";
    img.file = File (rootdir + arr[idx][ND_FILE]);
    scl = Math.max(  
        (doc.width - (37* 2)) / img.width,
        (doc.height - (27* 2)) / img.height,
    ) * 100 +1;

    //alert("scale:" + scl + " --- doc.width: " +doc.width + " --- img.width: " + img.width + " --- post img.width: " + (img.width * scl));

    img.resize(scl,scl);
    img.left = doc.width - inrect[2] - inrect[1];
    img.top = doc.height - inrect[3]- inrect[0];

    // =================
    // clipping mask
    // max: 37p x 27p from artboard edges
    // fit to smaller wid/ht, but not to larger
    // clip the image with the box
    // =================

    clipbox = imgClipGroup.pathItems.rectangle(cliprect[0], cliprect[1], cliprect[2], cliprect[3]);
    clipbox.filled = false;

    imgClipGroup.clipped = true;

    // =================
    // border
    // same size as clipping mask
    // fill: none
    // stroke: solid, black, 3pt
    // =================

    borderbox = doc.pathItems.rectangle(cliprect[0], cliprect[1], cliprect[2], cliprect[3]);
    borderbox.name = "border";
    borderbox.strokeColor = blackRGB;
    borderbox.strokeWidth = 3;
    borderbox.filled = false;

    // =================
    // text: "Image © copyright {name} under {license}."
    // font: {font name}, regular, 12pt
    // applied appearance: none
    // position: one (imaginary) line below bottom of border's stroke, flush left to border stroke
    // =================

    copyright = lyr.textFrames.add();
    copyright.name = "text, copyright";

    if(ND_LICENSE == "none" || ND_LICENSE == "" )
    {
        copyright.contents = "Image by " + arr[idx][ND_NAME] + ", no known copyright.";
    } else{
        copyright.contents = "Image ©Copyright " + arr[idx][ND_NAME] + " under " + arr[idx][ND_LICENSE] +".";
    }

    //var str = "";  for(var i = 15; i < 45; i++){str += app.textFonts[i].name + "\n";}; alert(str);
    copyright.textRange.characterAttributes.textFont = app.textFonts.getByName (copyrightFontName);
    copyright.textRange.characterAttributes.size = 14;
    copyright.top = -cliprect[3] +cliprect[0] - 5;
    copyright.left = cliprect[1];
    copyright.textRange.characterAttributes.fillColor = greyRGB;

    // =================
    // text: {phrase}
    // font: {font name}, 56pt, regular
    // applied appearance: 
    // stroke = 1.6 point, offset 1.0666 px, solid black
    // fill = solid, bg color
    // effect = under all, drop shadow, multiply, 100% opacity, x and y offset 5.33 px, blur 12.8 px, color black
    // position: center 
    // =================

    maintext = lyr.textFrames.add();
    maintext.name = "text, main";
    maintext.contents = arr[idx][ND_QUOTE];
    maintext.textRange.characterAttributes.size = 56;
    // copy previously created style:
    mainTextStyle.applyTo( maintext );
    maintext.top = (-cliprect[3] ) / 2 ;
    maintext.left = (cliprect[2]  - maintext.width) / 2 ;

}

// =================
// leave user with control of the placement of the clipped image
// =================

Adding Functionality to Ash Entity Systems Asteroid Game

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.

Compulsive Columbus — A Flash Entity System Framework Test Game

Using Ash entity framework for Flash, I am attempting to modify the sample game asteroids into a social commentary on Christopher Columbus.  The idea is that you plant your flag on a bunch of islands before the time runs out.  What happens when the time runs out?  Off with your head, else a promotion, where you get to do it all again.  Simple, similar to asteroids on a lot of the code, and adds a few elements that asteroids does not have (return to ship, path finding, timers, and (compared to the sample code) a menu, score, and end level/game screens).

The game will be made to fit any screen size (at least the pads, maybe the phones), so it will be sold for Apple devices, Android, Nook, and Kindle.  Part of this challenge is to develop a better workflow for the tablets that I have not released an app for yet.

Will update this post as things progress. . . .

Flash Ash Entity Systems Programming notes

What It Is

Ash entity framework for Flash shows some real promise for rethinking how I write code.  There is an example asteroids application to review, but there is not much documentation in the code.

Brief Description, Theory

The idea is not new, since it is, essentially, relational database concepts.  The data (aka, component) exists in only one place (d.r.y. coding) and various systems use that data in various ways.  No one system owns the data, and no system keeps the data past it’s usage.  There is a priority queue (c.f. this example and the SystemList class) that manages which system gets to use the data next.
I am having a bit of difficulty, since it is not object oriented programming.  Entity systems do not have objects that encapsulate data, so it is not passed around in the same manner as in OOP.  Like relational databases, the data does not know the systems or objects that access it, and vice versa.  The persistence of data is not held in a serialized class, but rather in a simpler list (map).  Once the program starts, data can be manipulated in any way at all: saved data can be loaded, data can be removed or added, changed in the middle of some action, and so on.

For example, level editors in entity systems are easily added, since that is just another system affecting the data.  The process would be along the lines of:

  1. Assuming these systems exist and are present, removing the GravitySystem, removing the UserInteractionSystem, removing the CollisionSystem, etc.
  2. Adding an EditorSystem, a ClickAndDragSystem, etc.
  3. Saving the Game state.
  4. Loading a Game state.
  5. Removing the EditorSystem, ClickAndDragSystem, etc.
  6. Adding GravitySystem, UserInteractionSystem, CollisionSystem, etc.

The game does not have to be restarted either, since it is actually still running during the editing phase.

My Problems

This is great in theory, but I am still having a problem with actually doing any of this.  Richard Lord includes a sample game, asteroids.  The main problem I am having is putting a menu system into the game.  I want to create a TextField showing the number of asteroids, the number of ships left, and the number of bullets.  However, though I can create the TF, I am not getting any data during the update.
I cannot debug that I can figure out, since it is not an object structure I am used to.  Any help on this would be great.

Note: Like Recursion

Thinking in an entity framework is a lot like working with recursive functions.  The function assumes that the object handed to it meet certain criteria; maybe that is that an object exists, or that it is a linked list, etc.  But it makes no reference to specific objects, except in a general way.  Any other thing of the same type can be passed, and the function will chug away at it, without knowing any specifics.  Also, each time the function runs, it has no memory of the last time it ran.  Nothing specific is stored in the class that contains the function, and all data is manipulated in some referenced component.  In that sense, the code is “clean”.

Note: Object Creation/Destruction

Object creation is still a bit of a mystery.  I can make things show up and get updated now (another post on that in the near future), but I cannot figure out a good way to destroy them, nor make sure that all references are removed, nor know if they have been garbage collected or not.

Note: There Are Non-Component Objects

An unexpected part of an entity system is all the things that are neither entities nor systems.  Mostly this falls into the idea of “creator” objects and “graphics” objects (I assume “destructor” objects will be needed as well).  There are a few other things, too, but that there is anything that is not a component or system was unexpected.  And I don’t know the best way to deal with them.  These are like psuedo-components:  they are not data that is stored, do not generally exist in linked lists, but seem to be similar otherwise.

Note: Using Other Frameworks in Ash

Many of the sites I visit hook other frameworks into the entity system code.  Not sure quite how to do that, either, though I think it will be easy to answer if I find the best way to solve the object problems I am having.  (And vice versa, actually.)

Note: Automatic System Automation

In a useful blog post, Shaun Smith writes about his working with Ash.  He points out that systems automatically work on any entity that has the right components added, “When an entity is given both a Display and a Position component a Render node will be created for that entity. If either of those components is removed from the entity the Render node for that entity will be destroyed.”

Note:  What Is Really Going On

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. E.g.,

The component:

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

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

The node:

package
{
   import ash.core.Node;

   public class TestNode extends Node 
   {
      public var testComponent : TestComponent;
   }
}

and the system that updates all the instances of TestComponent:

package
{
   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 
      {
         // use node as any linked list.  Access the members like:

         node.testComponent.value = Math.sin(time); // or whatever.
      }
   }
}
Follow

Get every new post delivered to your Inbox.