Guide to Creating a Game On iPad -- Vector Graphics in Codea

31. October 2014

 

Now that we know how to create vector graphics on iPad, let's take a look at the process of getting them into and using them in Codea.  This part is insanely easy, in fact if this was all there was to it, I could cover this entire process in a single Tweet!  Let’s do it.

 

First of course you need to create a vector graphic in some application and save it as PDF to your Dropbox folder.

Then fire up Codea, create a new project with the following code:

function draw()
    background(40, 40, 50)
    sprite("Dropbox:Jet",WIDTH/2,HEIGHT/2)
end

 

Yeah... that's it.  Select the image from your Dropbox, run it and you will see:

 

Untitled5

 

So basically vectors are treated exactly the same as bitmaps as far as Codea is concern.  Well, with a few exceptions… you can scale them as much as you want and never lose visual fidelity.

 

You can scale by passing in the width and height to the sprite call, like so:

sprite("Dropbox:Jet",WIDTH/2,HEIGHT/2,100,100)

 

So then, vectors are easy to load, look great, scale well are pretty easy to draw... perfect no?

Well… no, there is a catch.  Performance.

Let’s take a quick look at how vectors perform, using the following code:

 

-- VectorTest
function setup()
    fontSize(160)
    fill(255, 0, 25, 255)
end

function draw()
    background(40, 40, 50)

    local outString = "fps:" .. math.floor( 1/DeltaTime)
    local dimensions = textSize(outString)
    
    strokeWidth(5)

    for i= 1,100 do
        local x = math.random(WIDTH)
        local y = math.random(HEIGHT)
        sprite("Dropbox:Jet 2",x,y)
    end
    
    text(outString,WIDTH-dimensions/2,80)
end

 

Here it is running on my iPad Air with drawing 100 vectors:

 

1

 

60 FPS is as good as we can get, so so far so good!  Now lets see what happens when we crank the number up to 500.

 

2

Oh… 

 

From my tests, you can draw about 200 vector images before framerate starts to drop down.  You can easily draw twice that many normal sprites without seeing a noticeable drop in framerate.  

 

Right now our simple frame rate counter is changing far too often to be of much use.  So instead I am going to sample and average across minutes.  Basically every frame we take a sampling of framerate, after a second elapses we move that sample to other array, average and display the results.  So basically we sample the data per frame, but average it out over the span of a second.  This should give us a much smoother frame rate total.  Here is the updated code:

 

 

-- VectorTest
frameRateSamples = { 60 }
fpsSampledPerSecond = { 60 }
elapsedTime = 0
    
function setup()
    fontSize(160)
    fill(255, 0, 25, 255)
end


function mean( t )
  local sum = 0
  local count= 0

  for k,v in pairs(t) do
    if type(v) == 'number' then
      sum = sum + v
      count = count + 1
    end
  end


  return math.floor(sum / count)
end

function draw()
    background(40, 40, 50)

    elapsedTime = elapsedTime + DeltaTime
    if(elapsedTime < 1) then
        table.insert(frameRateSamples,math.floor(1/DeltaTime))
    else
        print("Second elapsed")
        table.insert(fpsSampledPerSecond,mean(frameRateSamples))
        framerateSamples = {}
        elapsedTime = 0
        -- ever 360 seconds reset the table so it doesnt grow forever
        if #fpsSampledPerSecond > 360 then
            fpsSampledPerSecond = { 60 }
        end
    end

    local outString = "fps:" .. mean(fpsSampledPerSecond)
    local dimensions = textSize(outString)
    
    strokeWidth(5)

    for i= 1,200 do
        local x = math.random(WIDTH)
        local y = math.random(HEIGHT)
        sprite(“YourImageHere",x,y)
    end
    
    text(outString,WIDTH-dimensions/2,80)
end

    

 

Now when we run it, we get the follow values, after letting each run for over 60 seconds to take the seed value out of the equation:

Image Type FPS at 100 FPS at 250 FPS at 500 FPS at 1000 FPS at 1500 FPS at 2000
Vector 58 38 21 23 15 9
Bitmap 58 37 21 21 17 10

 

Hmmm, that's interesting.  So once the framerate sampling is smoothed out, the performance between vector and bitmap graphics is basically identical.  In both cases too, oddly enough, 500 sprites caused a drop in performance compared to 1,000…  very very very odd.

 

Now let’s take a look at if scaling is a factor.  In this test we will scale the vector down to 32x32, vs drawing a fixed 32x32 sprite and see if it impacts performance.

 

As a 32x32 pixel image, at 2000 sprites, the framerate a solid 57.

As a vector scaled down to 32x32 in the sprite call the framerate at 2000 sprites is 57.

 

There you have it, contrary to my initial findings, the performance between identically sized bitmap and vector images in Codea is about the same.  Internally, Codea must be rasterizing vectors before drawing them.

 

So, why did my initial tests show a massive improvement in bitmap performance over vectors as the count increased?  Well, that’s because I compared apples to oranges… I used a “nearly the same dimensions” graphic for the test, and that was a huge mistake. Something critical you can take away from this experiment is the size of the sprite being blitted has a huge impact on performance, much more so than the type of sprite.  As you saw above, we can blit > 2000 32x32 pixel images without a drop in performance, while the same number of 200x200 pixel images brings it to it’s knees.

 

So, in a nutshell, its equally as valid to use a vector with code than it is a bitmap.  And as you can see, it uses the exact same code.  Oh, and if you are by chance wondering why we were toping out at 57-58FPS instead of 60 you would expect, that is simply because I floored() the results (aka, rounded down) to make the numbers prettier.  Since I was measuring relative performance, not overall performance, the actual framerate really didn’t matter all that much.

Programming ,




Creating Vector Game Art using iDraw on iPad

29. October 2014

 

As part of the ongoing Guide to Creating a Game Entirely on an iPad series, we recently looked at a number of 2D raster and vector graphics programs available onidraw_large_icon iOS.  One of the programs that really stood out was iDraw, a vector drawing program.  I’ve actually used iDraw for a while now, going back to the concept art I did for the Blender tutorial series and I am certainly a fan.  Although both Inkscape (Desktop) and Inkpad (iPad) are available for free, I find both to be pretty unwieldy at times.  iDraw just hits that sweet spot between usability and functionality, at least for me.

 

In this video I take a more in-depth look at iDraw. 

 

In this video I show the functionality available in iDraw.  THe first half of the video I complete this game art tutorial that was written for Inkscape, so if you are familiar with Inkscape, you can see how iDraw works at the same task.  If you’ve got no experience with vector graphics at all, this video should certainly give you the basics.   The last half of the video is a tour of iDraw’s additional functionality.

 

 

 

If you have a Mac or iPad and are looking for a vector graphics solution, iDraw is certainly worth checking out.  iDraw is available for $8.99 on the AppStore for iPad, while it is also available (with almost identical functionality) on the Mac for $24.99.   If you are looking for a tutorial on creating vector graphics, or are looking for more details on using vector graphics for game dev, 2dgameartforprogrammers is a great resource you should check out.

 

… I still hate the name iDraw though.  I hate an irrational hatred for all iNaming iProducts, I think many others do too!  Get over the name though and you will find an excellent product.

 

Next up, I will look at using Vector graphics from a Codea code perspective, to see how it’s done and what the performance is like.  Stay tuned!

Art , ,




Adventures in Phaser with TypeScript–Tilemaps Part 2 Now with more stuff!

29. October 2014

 

 

In the previous tutorial we took a look at creating, loading and displaying a tilemap in Phaser and for many simple projects this may be enough.  That said, there are two very common scenarios this doesn’t really address, and we will look at those in this tutorial.  The first is positioning a sprite within a tilemap.  The second is collision detections between tiles and sprites. 

 

Placing a sprite within a tilemap is a pretty common behavior, but the way you deal with it isn’t immediately obvious in Phaser.  To understand what I am going on about it’s good to understand how layers of tilemaps work.  You may recall in our previous example, our tilemap was composed on three layers, one for the background, one for the midground (a word I apparently made up) and one for the foreground.  Basically the background is where you would put the stuff that happens behind the scene, things like clouds or the blue sky for example.  The midground is generally where the bulk of the stuff in your world would go.  The foreground then is the stuff drawn on top of the midground. 

 

Quite often your playersprite will then be drawn in front of the foreground.  That said, what happens when your player is behind something in the world, such as say… a shrubbery?  How do we deal with this?  Well first, let’s visualize how the layers of a tilemap come together.

 

No better way than an image, right?

 

Layers

 

Think of each layer of tiles as sheets laid one over the next.  The top most layer obscuring the contents behind it, and that layer obscuring the contents behind it.

 

So, in Phaser, what determines draw order?  Simple, the order they were added in.  Change the order layers are drawn, you change what draws on top of what.  Know this then, how do we could we for example draw our character sprite between the foreground and midground layer so it appears behind the chandelier, like so:

 

 

Layers2

 

 

The answer is actually quite simple but possibly a bit confusing, let’s look at the code:

 

/// <reference path="phaser.d.ts"/>
class SimpleGame {
    game: Phaser.Game;
    map: Phaser.Tilemap;
    player: Phaser.Sprite;
    
    constructor() {
        this.game = new Phaser.Game(640, 480, Phaser.AUTO, 'content', {
            create: this.create, preload:
            this.preload, render: this.render
        });
    }
    preload() {
        this.game.load.tilemap("ItsTheMap", "map.json", null, Phaser.Tilemap.TILED_JSON);
        this.game.load.image("Tiles", "castle_0.png");
        this.game.load.image("Decepticon", "decepticonLarge.png");
    }
    render() {

    }
    create() {
        this.map = this.game.add.tilemap("ItsTheMap", 32, 32, 50, 20);
        this.map.addTilesetImage("castle_0", "Tiles");

        this.map.createLayer("Background").resizeWorld();
        this.map.createLayer("Midground");
        this.map.createLayer("Foreground");

        this.player = new Phaser.Sprite(this.game,200,50,"Decepticon");
        this.game.world.addAt(this.player, 2);
    }
}

window.onload = () => {
    var game = new SimpleGame();
};

 

And when we run it:

image

 

Note the order we called createLayer matches the order we want the layers drawn, from back to front.  After that we load our sprite then do something a bit different from what we’ve done in the past.  Previously we just added Sprites using this.game.add().  Now however we want a bit more control of where they are added.  This is why we created our Sprite instead using new, then explicitly added it to the world using addAt().  By adding it at index 2 ( the 3rd item ) this means we will be between the mid and foreground in the world list.

 

You should be aware however that this is incredibly fragile an approach.  You can only rely on layers being where they are because they are the only other objects we’ve created.  In a non-trivial example, you will dozens or hundreds of other objects in your scene, this may not be as easy to figure out.  There are of course alternatives.  One of the easiest, and possibly crudest options, would be too iterate through the world, find the index of layer you want to insert before and then insert like so:

 

        var index: number;
        this.game.world.forEach((child) => {
            index++;
            if (child.layer.name = "Foreground")
                index = child.index;
        }, this, false);
        this.game.world.addAt(this.player, index);

 

I suppose I should point out this particular solution iterates through every item in the world, even if the first child is what you are looking for. Also, it doesn't take into account the possibility that the given child doesn't even exist, which is probably a bad idea. That all said, I think you get the gist of how this works.  As you may notice, the world variable is pretty important, as essentially it is the scene graph, the source of all things to be rendered.  At the end of the day though, it’s just a group that we covered earlier.

 

Ok… that was a really long way of saying the order you add things to the world is really important… let’s move on, collisions.

 

Quite often you are going to want to test to see if “stuff” (scientific term there… ) hits other “stuff”.  In this particular case, we want to see if our game sprite collides with the “floor”.  First lets take a look at adding physics to our game sprite.  I’ve used physics in past tutorials, but havent gone into detail how these things work… and I am not going to here either.  For now just think of it as the work of moon wizards.  I will cover physics at a later point, I promise!

 

So, back to enabling physics and gravity for game sprites.  Here’s how:

 

    create() {
        // Enable the physics system in Phaser
        this.game.physics.startSystem(Phaser.Physics.ARCADE);
        this.map = this.game.add.tilemap("ItsTheMap", 32, 32, 50, 20);
        this.map.addTilesetImage("castle_0", "Tiles");

        this.map.createLayer("Background").resizeWorld();
        var world = this.map.createLayer("Midground");
        this.map.createLayer("Foreground");

        this.player = new Phaser.Sprite(this.game, 200, 50, "Decepticon");
        // Apply physics to our game sprite
        this.game.physics.enable(this.player, Phaser.Physics.ARCADE);
        // Turn on gravity.  10pixels / second along the y axis
        this.player.body.gravity.y = 10;

        this.game.world.addAt(this.player, 3);
    }

 

The comments explain the basics of what’s going on, like I said, I will go into a bit more detail later.  Now if you run the code with the above changes the player sprite ( the Decepticon logo ) will slowly fall to the bottom of the screen, to infinity and beyond.

 

Now what we would prefer to see is the sprite stop when it collided with the tiles composing the floor.  This takes a bit of understanding of how tilemaps are actually stored as data.  Open up map.json and you will see a layer contains an array called data full of tiles that make up the layer, like so:

 

image

 

Each of those indices present a location within your sprite image.  Let’s take a look back in Tiled for a second.

 

image

 

You can see the ID of each tile by simply clicking it in the Tilesets window.  In this case we are going to want the ID of each red topped brick.  Specifically we want the ID of the tiles that make up these particular tiles

 

image

 

In my particular case, the IDs were 32,33,34,35 and 36.  Now that we know the IDs of the tiles we want to collide with, lets set that up.  Code time!

 

/// <reference path="phaser.d.ts"/>
class SimpleGame {
    game: Phaser.Game;
    map: Phaser.Tilemap;
    layer: Phaser.TilemapLayer;
    player: Phaser.Sprite;
    
    constructor() {
        this.game = new Phaser.Game(640, 480, Phaser.AUTO, 'content', {
            create: this.create, preload:
            this.preload,update:this.update, render: this.render
        });
    }
    preload() {
        this.game.load.tilemap("ItsTheMap", "map.json", null, Phaser.Tilemap.TILED_JSON);
        this.game.load.image("Tiles", "castle_0.png");
        this.game.load.image("Decepticon", "decepticonLarge.png");
    }
    update() {
        // You actually need to perform the collision test between the map and player sprite
        this.game.physics.arcade.collide(this.player, this.layer);
    }
    render() {
        // Display the outline of the physics body
        this.game.debug.body(this.player);
    }
    create() {
        // Enable the physics system in Phaser
        this.game.physics.startSystem(Phaser.Physics.ARCADE);
        this.map = this.game.add.tilemap("ItsTheMap", 32, 32, 50, 20);
        this.map.addTilesetImage("castle_0", "Tiles");

        this.map.createLayer("Background").resizeWorld();
        this.layer = this.map.createLayer("Midground");
        this.map.createLayer("Foreground");

        this.player = new Phaser.Sprite(this.game, 200, 40, "Decepticon");
        // Apply physics to our game sprite
        this.game.physics.enable(this.player, Phaser.Physics.ARCADE);
        // Turn on gravity.  10pixels / second along the y axis
        this.player.body.gravity.y = 10;
        this.player.body.collideWorldBounds = true;

        // Now setup the tiles in our midground layer for collisions
        // When using multiple layers it is critical you specify the layer the collisions occur on!
        this.map.setCollisionBetween(32, 36,true,this.layer.index,true);
        

        this.game.world.addAt(this.player, 2);

        // Add a keyboard handler on 'R' pressed the resets the sprite to it's starting position
        this.game.input.keyboard.addKey(Phaser.Keyboard.R).onUp.add(() => {
            this.player.position.set(200, 40);
        });
    }
}

window.onload = () => {
    var game = new SimpleGame();
};

 

 

Here is the code running.

 

 

Click in the IFrame and press R to reset the sprite to it’s starting position.

 

Most of the important bits are commented in the source code, but a few things are worth noting.  The most important is how you add collision tests to individual tiles within the world.  As mentioned earlier, you need the tile ID of the tile within the layer.  You then use setCollision methods to mark which tiles perform collisions.  In this example we used setCollisionBetween() which allows you to specify a range of tiles to collide with.  There is also a version that takes an individual tile ID as well as one that takes an array.  The critical part of this call is you have to provide the layer where the collision takes place.  Also of importance is the call to physics.arcade.collide() which tells Phaser to check for a collision between that layer and your player sprite.

 

One other thing you may notice is in render(), I call game.debug.body(this.player).  This causes the renderer to draw a bounding box around the physics shape of our sprite.  This is important because it exposes a very common bug that I thought I would address.  Notice when you run the applicaion, once gravity is done, our image is resting above the ground like this:

 

image

 

This is because our source image has some empty pixels.  The physics bounding volume is going to be around the entire sprite, not just drawn pixels.  If you are using physics, be sure your image is cropped as much as possible!

 

So that illustrates how to insert a sprite between tilemap layers and how to perform collisions against tiles.  That should be the majority of what you need to get started using tilemaps.

 

Before we continue, I should answer a commonly asked question.  What about isometric maps?  Yes, Tiled can export isometric tilemaps, but currently Phaser does not support them.  There are some third party code samples to load them, but nothing in the core as of yet.

 

Programming , ,




Re-using actions in LibGDX

28. October 2014

 

A question came up in a comment in the Scene2D part of the LibGDX tutorial series about re-using actions.  You very much can re-use actions, so I decided to do it in post form here.  This entire post is mostly just one large code sample.  It’s just easier to do it here than in comments.  For a greater context of what this code is doing, see the earlier linked tutorial section.

 

Here is a sample showing action re-use.

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actions.MoveToAction;


public class Scenetest implements ApplicationListener, InputProcessor {


   public class MyActor extends Actor {
      Texture texture = new Texture(Gdx.files.internal("badlogic.jpg"));

      public MyActor(){
         setBounds(getX(),getY(),texture.getWidth(),texture.getHeight());
      }

      @Override
      public void draw(Batch batch, float alpha){
         batch.draw(texture,this.getX(),getY());
      }
   }

   
   private Stage stage;
   private MyActor myActor;
   MoveToAction moveToOrigin,moveToCenter;

   @Override
   public void create() {
      stage = new Stage();
      myActor = new MyActor();

      myActor.setPosition(Gdx.graphics.getWidth()/2 - myActor.getWidth()/2,
            Gdx.graphics.getHeight()/2 - myActor.getHeight()/2);
      
      moveToOrigin = new MoveToAction();
      moveToOrigin.setPosition(0f, 0f);
      moveToOrigin.setDuration(2f);
      

      moveToCenter = new MoveToAction();
      moveToCenter.setPosition(Gdx.graphics.getWidth()/2 - myActor.getWidth()/2,
            Gdx.graphics.getHeight()/2 - myActor.getHeight()/2);
      moveToCenter.setDuration(2f);

      myActor.addAction(moveToOrigin);
      stage.addActor(myActor);
      Gdx.input.setInputProcessor(this);
   }

   @Override
   public void dispose() {
   }

   @Override
   public void render() {
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
      stage.act(Gdx.graphics.getDeltaTime());
      stage.draw();
   }

   @Override
   public void resize(int width, int height) {
   }

   @Override
   public void pause() {
   }

   @Override
   public void resume() {
   }


   @Override
   public boolean keyDown(int keycode) {
      if(keycode == Input.Keys.NUM_1)
         if(myActor.getActions().contains(moveToOrigin,true)) {
            // this is "A" currently running action, do nothing
            // If you wanted you could restart the action which 
            // would cause the duration to start over, like so:
            moveToOrigin.restart();
            // This action will now have a 2 second tween between 
            // its current location and target
         }
         else {
            moveToOrigin.reset();
            myActor.addAction(moveToOrigin);
         }
      if(keycode == Input.Keys.NUM_2)
         if(myActor.getActions().contains(moveToCenter,true)) {
            // this is "A" currently running action so do nothing
         }
         else {
            moveToCenter.reset();
            myActor.addAction(moveToCenter);
         }
      return false;
   }

   @Override
   public boolean keyUp(int keycode) {
      return false;
   }

   @Override
   public boolean keyTyped(char character) {
      return false;
   }

   @Override
   public boolean touchDown(int screenX, int screenY, int pointer, int button) {
      return false;
   }

   @Override
   public boolean touchUp(int screenX, int screenY, int pointer, int button) {
      return false;
   }

   @Override
   public boolean touchDragged(int screenX, int screenY, int pointer) {
      return false;
   }

   @Override
   public boolean mouseMoved(int screenX, int screenY) {
      return false;
   }

   @Override
   public boolean scrolled(int amount) {
      return false;
   }  
}

When you run this code, the graphic will start at the center of the screen and move towards the origin, with a total duration of 2 seconds.  If you press the 2 key, it will start an action to move back to the center of the screen.  Pressing 1 will move back to the origin.  Pressing 1 while a moveToOrigin is active will cause that action to restart, basically resetting the total duration back to 2 seconds again, just from your current position.

 

The only things to really be aware of here are that an Actor getActions() will only return actions that are currently run.  When the action is finished, it is removed from the Actor.  The other thing of note is, although you can reuse an Action, you will have to reset() it before you can add it again, or it will already be over.  If it is currently run, calling restart() has the same effect as resetting.

Programming , ,




Guide to Creating a Game on an iPad -- Our First Project: Adding Spritesheet support to Codea

26. October 2014

 

As I mentioned earlier in the introduction to Codea, it does not support Spritesheets out of the box. I also mentioned earlier that it was a fairly simple thing to add. In this tutorial I am going to show how I created a class in Codea for enabling sprite sheets, which can also serve as a bit of an introduction to non-trivial Codea development.

 

A spritesheet is simply a number of sprite graphics that have put together into a single image.  This often results in better performance because of more efficient memory usage, as well as quicker load times and easier asset management.  There area  number of spritesheet creation tools such as TexturePacker which I discussed how to use here and here.  I am also using the spritesheet graphic mentioned in this post.  It’s important to realize that tools like TexturePacker can create very tightly packed sheets that rotate textures to fit them in best.  In this case however, we are expecting our textures to be arranged in a simple grid pattern.

 

Let’s jump right in to Codea and create a new project.  At the home screen simply click the Add New Project button shown below:

IMG 0280

 

In the resulting dialog, name your project, I’m going with SpritesheetDemo.  When done, click Create.  In my case the create button is slightly bugged, in that case, click slightly to the left of it.

IMG 0283

 

This will now bring you to the main code editing page.  It will start with the following simple “Hello World” code:

 

-- SpritesheetDemo

 

-- Use this function to perform your initial setup

function setup()

   print("Hello World!")

end

 

-- This function gets called once every frame

function draw()

   -- This sets a dark background color 

   background(40, 40, 50)

 

   -- This sets the line thickness

   strokeWidth(5)

 

   -- Do your drawing here

 

end

 

 

What we want to do now is add a new class for our Spritesheet, simply click the + icon in the top right corner of the Codea screen:

IMG 0279

 

Now select Create New Class

IMG 0278

 

In the text field, enter Spritesheet as the name:

IMG 0281

 

It will start with an empty class definition, we will replace it will this code:

Spritesheet = class()

 

function Spritesheet:init(img,x,y,rows,cols,frameWidth,frameHeight,frame,totalFrames)

   self.x = x

   self.y = y

   self.frame = frame

   self.totalFrames=totalFrames

 

   self.frameWidth = frameWidth

   self.frameHeight = frameHeight

   self.frameRows = rows

   self.frameCols = cols

   self.frameSize = vec2(self.frameWidth,self.frameHeight)

 

   self.counter = 0

 

   self.mesh = mesh()

   --self.texture = img

   self.mesh.texture = img -- self.texture

   self.mesh:addRect(0,0,self.frameWidth,self.frameHeight)

end

 

function Spritesheet:draw()

 

   self.counter = self.counter + DeltaTime

   if self.counter > 1/30 then

 

   self.frame = self.frame + 1 

   if self.frame > self.totalFrames then

       self.frame=0

   end

 

   self.x = self.x -1

 

 

   colUnit = 1/self.frameCols

   rowUnit = 1/self.frameRows

 

   row = math.floor(self.frame / self.frameRows)

   col = math.floor(self.frame % self.frameCols)

 

   self.mesh:setRectTex(1,

   col * colUnit,

  ( 1-rowUnit) - row * rowUnit,

   colUnit,

rowUnit)

 

       self.mesh:setRect(1,self.x,self.y,self.frameWidth,self.frameHeight)

 

    self.counter = 0

    end

 

   self.mesh:draw()

end

 

function Spritesheet:touched(touch)

 

end

 

A quick explanation of what the above code does.  First off, you create a Spritesheet by passing in the image to use for the sheet, the x and y coordinates to draw the individual sprite at, the number of rows and columns in your sprite grid, the width and height of each row and column, the frame to start on as well as the total number of frames in the sheet.  The rest of the init() method is mostly about storing these values.  The key call is addRect() which you can think of as creating a viewport into the sprite sheet, so we can view a single sprite.  The critical part is we create a mesh.  This is a quad that we apply our texture to.  In the draw() method you will see why this is important.

 

The draw() function has a lot of functionality built in that you obviously wouldn’t want in a more generalize Spritesheet class.  It does however show you how to display a single frame within the sheet and to iterate through each frame.  First we update our internal counter by DeltaTime, which is the amount of time since the last frame was drawn.  We are animating at a fixed rate of 30hz (obviously something you would want to change ), and each time 1/30th a second elapses, we move on to the next frame, unless its the last frame of animation, in which case we go back to first frame.

 

It’s all of the following bit that is the core of how we make the sprite sheet work.  We have created a mesh and applied the image texture to it.  Obviously though, that texture has a number of sprites on it.  What we are doing is finding the coordinates of our current frame of animation within the mesh.  The critical part here is mesh does not use pixel coordinates!  Instead it uses standard GL device coordinates ( similar to UV coordinates ), that start at 0 and go to 1.  The coordinate (0,0) is the bottom left corner of the texture, while (1,1) is the top right.  So we have to map our texture coordinates to pixel coordinates within the image.  This is also a challenge since our frames of animation start at the top left, not bottom left.  This is why we do (1-rowUnit).  In simpler English, colUnit and rowUnit convert from pixel sizes to fractions of 1.0.  For example, if we have 5 columns, each column is then 0.2 in width.  Once we have figured out the location of our individual frame in texture space, we set it using setRectTex().  Finally we set our elapsed time counter back to 0 then draw our updated mesh using mesh:draw().

 

Now let’s take a look at how we actually use spritesheet in code:  Go back to your Main and alter it like so:

 

 

function setup()

   local img = readImage("Documents:birds")

   bird = Spritesheet(img,WIDTH,HEIGHT/2,5,5,240,314,0,21)

end

 

function draw() 

   background(40, 40, 50)

   bird:draw()

end

 

function touched(touch)

   bird:touched(touch)

end

 

Here we simply load our spritesheet from the local pictures ( or use dropbox, wherever you wish ).  We then create a Spritesheet passing in the image, as well as the parameters that describe the sheet.  Draw and touched aren’t called automatically, so each time we get a draw or touched call in our app, we simply call our sprite’s draw and touched functions.

 

When you run the code you should see ( assuming you used the same image I did that is! ):

IMG 0275

 

If you are curious how I got an animated gif of a playing application out of Codea and onto this site, there is a neat bit of functionality built into Codea.  You can record a video of your game running in the run screen of Codea using this button:

IMG 0282

 

As you may be able to guess, I then used the iPad app PicPlayPost to convert to animated gif.  It did a solid job and produced a small file, but it also stripped away all of the colour!  So instead here it is exported as MP4 and created with my ole trust Gif Brewery:

AnimatedBird

 

So that’s a simple project in Codea.  Next up I’m going to start looking at Vector graphics instead of sprites.

Programming , ,