NOTICE: v1.2.2+ available on Github

New functions for object attachment, sprite sheet extraction, and debug console messages are available in the Github distribution.

Checkout the updated Space Shooter Demo or the new Turret Defense Demo for usage examples.

iio App Basics

This document provides an overview of the main features of the iio Engine.

All code samples are assumed to be in a JavaScript document loaded onto an HTML page.

Here is the simplest HTML page and iio Application we can create:

<!doctype html>
  <body>
    <script type="text/javascript" src="iioEngine-1.2.1.min.js"></script>
    <script type="text/javascript">

        Helloiio = function(io){
          io.addObj(new iio.Text('Hello iio!!', io.canvas.center)
              .setFont('30px Consolas')
              .setTextAlign('center')
              .setFillStyle('black'));
        }; iio.start(Helloiio);

    </script>
  </body>
</html>
 

Main App Functions

The best way to utilize the iio Engine when developing a web application is to create a function that receives an AppManager object:

MyApplication = function(AppManager){
  //application code...
};

This code structure will allow you to use the start functions available in the iio core package to easily create canvas elements and bind your application script to them:

//create a full screen canvas and start MyApplication
iio.start(MyApplication);

//attach MyApplication to an existing canvas and start it
iio.start(MyApplication,'canvasId');

A new AppManager gets created and passed on to the given application script when you call a start function.

The AppManager provides structures for manipulating and managing all of the assets in your application. You can create content immediately:

//I shorten AppManager to io for convenience
DrawSquareApp = function(io){

  //draw a 60x60 blue square at canvas center
  io.addObj(new iio.Rect(io.canvas.center,60)
    .setFillStyle('blue'));

}; iio.start(DrawSquareApp, 'canvasId');
 

this & Cascading Code

Many of the mutator functions in the iio framework return this, which is a reference to the object that called the function. This design frequently allows you to code in a cascading structure - where multiple functions and objects interact in the same line of code.

This structure is especially useful when initializing objects and setting properties. For example:

//I shorten AppManager to io for convenience
ShadowSquareApp = function(io){

  io.setBGColor('white')
    .setFramerate(40, new iio.Rect(io.canvas.center,60)
      .setFillStyle('#00baff')
      .setStrokeStyle('black')
      .setShadow('rgb(150,150,150)',10,10,4)
      .enableKinematics()
      .setTorque(.15));

}; iio.start(ShadowSquareApp, 'canvasId');

The entire application is created in just one statement. I like to code like that, but it may not be for everyone.

One downside to structuring code in this way is that it makes it impossible to target a single function with a breakpoint when debugging.

If that concerns you or you if just don't like the cascading style, you can always rewrite code like that to this:

ShadowSquareApp = function(io){

  io.setBGColor('white')

  var blueSquare = new iio.Rect(io.canvas.center,60);
  blueSquare.setFillStyle('#00baff');
  blueSquare.setStrokeStyle('black');
  blueSquare.setShadow('rgb(150,150,150)',10,10,4);
  blueSquare.enableKinematics();
  blueSquare.setTorque(.15);

  io.setFramerate(40, blueSquare);

}; iio.start(ShadowSquareApp, 'canvasId');
 

iio Namespace

All of the functions, classes, properties, and constructs included in the iio Engine are contained within the namespace iio.

When the iio Engine is loaded, all of its core components are synthesized through prototypal inheritance and attachment. After this, all iio objects and functions are directly accessible from the iio namespace:

//initialize an iio Object
var line = new iio.Line(0,0,50,50);

//use iio Functions
var rand = iio.getRandomNum(0,10);
var randInt = iio.getRandomInt(-10,10);

//use Application Control functions
var app1 = iio.start(AppName, 'canvasId')
var app2 = iio.start(AnotherAppName, 'canvasId2');
 

Drawing Objects

iio Objects do not draw themselves by default, but depending on which functions you call and how much you interact with your AppManager, you may not need to worry about drawing at all.

If you want to use an object without drawing it, simply use the constructor and save a local reference:

//create an invisible 3x3 grid with 200x200 pixel cells
var grid = new iio.Grid(0,0,3,3,200);

Creating an invisible grid is very useful for tile based apps, since Grid gives you access to a matrix structure and positioning functions.

If you want to draw this grid, you have a few options. Most iio Objects have a draw function that ultimately gets called when they render. One option is to call this function directly:

//draw the grid (requires context of the rendering canvas)
grid.draw(io.context);

This is a good solution if you only need to draw the grid once and you never need to clear the canvas that its drawn on.

Another option is to give the grid to your AppManager. From then on, the AppManager will manage the grids rendering, and it will clear and redraw the object whenever necessary:

//give the grid to the AppManager (denoted as io)
io.addObj(grid);

This is most often the best way to handle object rendering, so when in doubt about drawing, just give your object to the AppManager.

For objects that move or have animating images, there are some other functions that trigger drawing. These will be covered in the following sections.

 

Moving Objects

iio Object translation and rotation can be auto-handled by iio's Kinematics Engine. There are three steps necessary to add automatic movement to an iio Object.

First, call the enableKinematics() function in the desired object:

//create a shape and enable kinematics
var rect = new iio.Rect(100,100,50,70)
                  .enableKinematics();

Then add a velocity for movement, or a torque for rotation:

//move to the right 1px per update
rect.setVel(1,0);

//rotate .1 radians every update
rect.setTorque(.1);

The last step is to set a loop to handle updates and rendering. There are two primary ways to accomplish this.

You can assign an update loop to the object itself like this:

//assign a 40fps update loop
io.setFramerate(40,rect);

If you need to run some code after each update, you can pass on a callback function like this:

//assign a 40fps update loop and define a callback function
io.setFramerate(40, rect, function(){
  //code runs on each update
});

//or...

updateRect = function(){
  //code runs on each update
};
io.setFramerate(40, rect, updateRect);

This technique is good to use when you only have a few objects that need to update, because the loop only redraws the object if it has changed position and the object's clearing function only clears its own spot on the canvas.

JavaScript allows multiple timers to be set at the same time, so you can implement this technique on as many objects as you want.

If you need to have a lot of objects updating and moving around though, a better approach is to give the objects to your AppManager and set an update loop on the canvas element:

//AppManager denoted as io

//add the object
io.addObj(rect);

//assign a 40fps update loop to the base canvas
io.setFramerate(40);

//if a callback is needed:
io.setFramerate(40, function(){
  //update code..
});

The AppManager will clear the entire canvas and redraw all of its objects on every update.

When developing apps that need a consistent FPS, this technique will serve you best. Here's an example with multiple updating objects.

MultiSquare = function(io){

  for (var x=50; x<400; x+=100)
    io.addObj(new iio.Rect(x,50,60)
      .setStrokeStyle('#00baff',2)
      .enableKinematics()
      .setTorque(.15));

  io.setFramerate(40);

}; iio.start(MultiSquare, 'canvasId');

Performance gains can be made in some situations by using multiple, overlapping canvas elements that update at different rates. See the Multiple Canvases section for more info.

 

Attaching Images

Images are not given their own class structure in the iio Framework. Instead, they are attached to Shapes.

Rect is the most common base shape for an image (since all image files are rectangular), but Circle and ioPoly can be used as base shapes as well.

There are two functions you can use to attach an image to a shape: addImage and createWithImage.

The createWithImage function sets the shapes dimensions to the dimensions of the given image, and the addImage function makes the image conform to the dimensions of the host shape.

Images must be loaded before they can be drawn, so a good method of creating a shape with an image attachment is as follows:

//create a rectangle with the dimensions of the given image
var imgRect = new iio.Rect(positionVector)
   .createWithImage('imageName.png'
    //add the object when the image loads
    ,function(){io.addObj(imgRect)}); 

//create a 40x40 square and attach an image
var imgSquare = new iio.Rect(positionVector)
   .addImage('imageName.png'
    //add the object when the image loads
    ,function(){io.addObj(imgSquare)});

Images can also be loaded before the shape creation takes place, here's a full application sample:

ImageDemo = function(io){

  //define a function to run when the image loads
  initApp = function(){
    io.setFramerate(40,new iio.Rect(io.canvas.center,80)
      .addImage(img)
      .enableKinematics()
      .setTorque(.15));
  };

  //load the image
  var img = new Image();
  img.src='meteorBig.png';
  img.onload = initApp;

}; iio.start(ImageDemo, 'canvasId');

This technique is useful for applications that need to load a lot of images before starting up.

 

Attaching Animations

In addition to attaching images, you can also attach animations to a shape by giving it a series of images:

//get the source images
var flySrcs = ['fly_normal.png'
              ,'fly_fly.png'];

//create a rectangle with the dimensions of the first anim image
var fly = new iio.Rect(io.canvas.center)
    .createWithAnim(flySrcs), function(){
        //image onload code
    });

To change which animation frame the shape is drawing, you can use the setAnimFrame or nextAnimFrame functions:

//change the displayed image to the fly_fly one
fly.setAnimIndex(1);

//advance to the next anim frame
fly.nextAnimFrame();

In the nextAnimFrame function, if the index gets to the end of the image sequence, it wraps back to the beginning.

In order to get the animation animating, we need to set a frame rate. We could set a frame rate on the canvas and advance the animation's index on each update:

//set the canvas to update at 3fps
io.setFramerate(3,function(){
  //move to the next image on each update
  fly.nextAnimFrame();
});

Or we could attach the loop to just the animating object:

//set the fly to update and redraw at 3fps
io.setFramerate(3,fly,function(){
  fly.nextAnimFrame();
});

A shortcut function exists for this option - to advance the animation at a specific framerate, use the setAnimFPS function:

//set the fly to update and redraw at 3fps
io.setAnimFPS(3,fly);

This is a great function to use when you have multiple animating objects that need to animate at different rates. See the animation example on the demos page for a sample implementation.

Another way to get animations updating at different rates is to give them an animation timer and update them all with the same canvas loop. This is the best technique to use when you have a large number of animating objects or when you have a lot of animation shapes using kinematics.

All of the principles of image preloading discussed in the 'image attachment' section also apply to animations.

Here is a full sample application to show you what I mean:

FlyAnims = function(io){

  //define an initialization function
  initApp=function(){
    for (var i=70; i<600; i+=100)
      io.setAnimFPS(i/50+1, //make each fly update faster than the last
        new iio.Rect(i,iio.getRandomInt(30,60))
          .createWithAnim(imgs));
  };

  //load the source images
  var imgs = [];
  imgs[0] = new Image();
  imgs[0].src='enemies/fly_normal.png';
  imgs[1] = new Image();
  imgs[1].src='enemies/fly_fly.png';

  //initialize app once the first image has loaded
  imgs[0].onload = initApp;

}; iio.start(FlyAnims, 'canvasId');
 

Object Bounds

iio Kinematics adds a bounds structure that you can use to get an object to do something if its position ever gets to a specified x or y coordinate.

The default behavior for a bounded object is to have it remove itself when it hits the bound:

//create a square that will move down from canvas center
//and remove itself when it hits the bottom of the screen
var square = new iio.Rect(io.canvas.center,50)
                  .enableKinematics()
                  .setVel(0,1)
                  .setBound('bottom', io.canvas.height);

We can easily specify our own behaviors though by giving the bound a callback function:

//create a square that will move down from canvas center
//and reverse its direction when it hits the bottom of the screen
var square = new iio.Rect(io.canvas.center,50)
                  .enableKinematics()
                  .setVel(0,1)
                  .setBound('bottom', io.canvas.height
                    ,function(obj){
                      obj.vel.y = -1;
                      return true;
                     });

The parameter obj will be a reference to the object that hit the bound.

Note that you must put return true at the end of this callback function if you don't want the object to remove itself.

Here's a simple demo where bounds are used to keep this box moving back and forth on the canvas:

Check out the full code for that sample.

 

Object Groups

If you have multiple different types of objects in your application and you need to control their z-indexing (draw order) or collisions, you can use the group structure that your AppManager controls.

A group has a tag, a z-index value, and the array of objects that you've added to it.

You can create a group with the addGroup function.

//create a group called 'background layer'
//with a -10 z-index
io.addGroup('background layer', -10);

To add an object to this group, use the addToGroup function:

//add an object to the 'background layer' group
io.addToGroup('background layer', bgObject);

Note that you can also perform both the creation and the object addition with the addToGroup function:

//do both steps in one line
io.addToGroup('background layer', bgObject, -10);

The AppManager always manages objects through group structures. When you call addObj the first time, a group at z-index 0 with the tag 0 is created and then all objects from then on out that get added with addObj are added to that default group.

The iio Debugger provides a console overlay that allows you to visualize your groups and the arrays of objects that they contain.

 

Object Collisions

The iio Engine has a built in collision detection framework. In order to use it, you'll need to organize your objects into groups so that you can use the setCollisionCallback function:

io.setCollisionCallback(groupTag1, groupTag2, function(obj1, obj2){
    //Collision callback code...
});

This code will tell your AppManager to run the specified callback code whenever any objects from group 1 collide with group 2. If you need to check for collisions between members of the same group, just leave out 'groupTag2'.

As an example, here is an app with two boxes that collide and reverse their directions.

Check out the full code for that sample.

 

Detecting Input

To listen for an input event, add an EventListener to your canvas:

io.canvas.addEventListener('mousedown', function(event){
    //code called when the mouse is clicked
});

You can learn more about EventListeners here.

The only events that need to be set up differently are 'keydown' and 'keyup'. The canvas cannot listen for these events, so you need to add a listener to the window element:

window.addEventListener('keydown', function(event){
    //code called when a keyboard button is pushed
}

iio's hasKeyCode function makes deciphering key events much easier:

window.addEventListener('keydown', function(event){
 
    if (iio.keyCodeIs('up arrow', event))
        alert('up arrow pushed');
 
    if (iio.keyCodeIs('right arrow', event))
        alert('right arrow pushed');
 
    if (iio.keyCodeIs('down arrow', event))
        alert('down arrow pushed');
 
    if (iio.keyCodeIs('left arrow', event))
        alert('left arrow pushed');
});
 

Handling Resize Events

When you start an iio application in full screen mode, the AppManager will set an onresize callback on the window element so that it can auto-update its canvas' size properties.

To add code to this callback function, declare a this.onResize function anywhere in your app.

this.onResize = function(event){
    //code run when the window is resized
});

The event parameter will be an HTML DOM Event object.

Apps that are not in full screen mode never have an onresize listener assigned, so you'll need to add one to the window or canvas element yourself:

//create a callback for a window resize event
window.addEventListener('resize', function(event){
    //code run when window is resized
},false);

//create a callback for a canvas resize event
io.canvas.addEventListener('resize', function(event){
    //code run when canvas is resized
},false);
 

Working with Multiple Canvases

In some situations, great efficiency gains can be made if an application is split up into two overlapping canvas elements. Your AppManager allows you to do this with its addCanvas function.

This is useful when you have one layer of objects that needs to be constantly updating and redrawing, and another layer of objects that only rarely needs to be redrawn.

You can add a new canvas with this code:

io.addCanvas();

There are other options for using this function, but its default behavior is to create a new canvas behind all the other canvases that your current app holds.

Your first canvas - which is referred to as the 'base' canvas in iio - can still be accessed with io.canvas. All your other canvases can be accessed through the io.cnvs array.

The first element in this array is the base canvas, the others are new canvas elements that you add, indexed in the order that you created them.

io.canvas;
//is equivalent to
io.cnvs[0];

//if we add a canvas
io.addCanvas();
//we can access it like this:
io.cnvs[1];

//if we add another
io.addCanvas();
//we would access it like this:
io.cnvs[2];

All of the AppManager functions you are used to calling can be called on one canvas in particular (the default behavior is to act on the base canvas):

//add a 60fps framerate and callback to our
//second canvas element
io.setFramerate(60, updateFunction, 1);

//redraw our second canvas element
io.draw(1);

//add an object to our second canvas
//0 is the z-index
io.addObj(myObj, 0, 1);