Showing posts with label pixel. Show all posts
Showing posts with label pixel. Show all posts

Friday, July 5, 2013

Tutorial 31 - Codea Collision Detection



31.1 Overview


Collision detection is a common requirement in game or simulator programming. It involves determining whether two or more objects have intersected. We will only be examining this problem in 2D (two dimensions) for this tutorial.

There are two main approaches to detecting collisions, discrete and continuous. In the discrete case, we advance the physical model by a small time step, and then check if any of our objects are intersecting, or are so close to each other that for all intents and purposes we consider them intersecting. For the continuous case, we write a collision detection algorithm which will be able to predict the trajectories of the physical bodies. The instants of collision are calculated, and the physical bodies never actually have to collide for us to detect the collision. 

The continuous approach is more accurate but is also more difficult. So for this tutorial, we will develop a few different discrete detection algorithms. Discrete detection will get the job done in many situations and is well suited to the Codea draw loop architecture. As long as your time steps are small enough, there aren't too many objects and the objects are large enough, you shouldn't miss any collisions.

Note that collision detection can chew up a lot of CPU time. If n is the number of objects on the screen that we need to test, then the:

Number of collision detection tests = (n² - n) / 2

Thus the number of tests increases exponentially with the number of objects being modeled. Consequently, if you have more than a few objects, you will want to look at optimising your tests.

31.2 Bounding Box Detection


If we have two rectangular objects with origins at (x1, y1) and (x2, y2) and dimensions (i.e. width and heights) of (w1, h1) and (w2, h2), then we can use the following function to determine whether the two rectangles are overlapping at a particular point in time.

function CollisionDetected(x1,  y1,  w1,  h1,  x2,  y2,  w2,  h2) 

  Collision = false

 
  if (y2 >= y1 and y1 + h1 >= y2) or (y2 + h2 >= y1 and y1 + h1 >= y2 + h2) 
    or (y1 >= y2 and y2 + h2 >= y1) 
        or (y1 + h1 >= y2 and y2 + h2 >= y1 + h1) then
     if x2 >= x1 and x1 + w1 >= x2 then          -- corner 1
        Collision = true
     end
     if x2 + w2 >= x1 and x1 + w1 >= x2 + w2 then  -- corner 2
        Collision = true 
     end
     if x1 >= x2 and x2 + w2 >= x1 then           -- corner 3
       Collision = true 
     end
     if x1 + w1 >= x2 and x2 + w2 >= x1 + w1 then  -- corner 4
       Collision = true
     end
  end

  return Collision -- return whether or not a collision is detected 


end
 


A faster version (about 10%) of the same function (albeit a bit more difficult to follow) is:

function CollisionDetected(x1,  y1,  w1,  h1,  x2,  y2,  w2,  h2) 
return not ((y1+h1 < y2) or (y1 > y2+h2) or (x1 > x2+w2) or (x1+w1 < x2))
end

And here is another version:

function CollisionDetected(x1,  y1,  w1,  h1,  x2,  y2,  w2,  h2) 
    local ax2, bx2, ay2, by2 = x1 + w1, x2 + w2, y1 + h1, y2 + h2
    return ax2 > x2 and bx2 > x1 and ay2 > y2 and by2 > y2
end

We will leave it as an exercise for the reader to demonstrate that these functions are all logically equivalent.


31.3 Bounding Circle Detection


The simplest method to detect circles colliding is to check whether the circles are overlapping. This can be done by calculating the distance between the centers of the two circles and seeing if it is less than or equal to the sum of the radii of the circles. If it is then a collision has occurred. 

function CollisionDetected(x1, y1, r1, x2, y2, r2)

    local dx = x2 - x1

    local dy = y2 - y1

    return math.sqrt(dx^2 + dy^2) <= r1 + r2


end


The square root in the distance formula is unnecessary because the inequality will still hold if we square both sides. Removing the square root will increase your collision detection speed, especially if you are checking for many collisions, because this is a relatively expensive calculation. 

function CollisionDetected(x1, y1, r1, x2, y2, r2)

    local dx = x2 - x1

    local dy = y2 - y1

    return (dx^2 + dy^2) <= (r1 + r2)^2


end





31.3 Box2D Collision Detection


If you don't want to roll your own collision detection, then you can utilise the built in collision detection functionality of Codea, which is provided courtesy of its Box2D implementation. To use this, the bodies which are colliding need to be created using the physics.body() function.  The downside of using this approach is that physics bodies can only have the following shapes (the property is called shapeType):
  • Circle; 
  • Polygon; 
  • Chain; or 
  • Edge (usually used for the ground or walls).

So for pixel perfect collisions you will need to use another approach if your sprites can't be outlined using one of the shapes above. In many cases however, you can approximate the sprite shape using these.

In addition to the shape type, we need to define the body type for our physics object. There are three options:
  1. Dynamic - these objects move under the influence of collisions, forces, joints and gravity (we will use this type for our asteroids);
  2. Static - these objects are not supposed to move and are unaffected by collisions and forces (e.g. the ground or walls); and 
  3. Kinematic - these objects can move by setting their linear velocity but like static bodies are unaffected by collisions and forces (we will use this type for our ship, if the ship collides with an asteroid we want it to explode not bounce away). 
To demonstrate how easy this is to implement we will create a simple Asteroids game with a ship in the center of the screen and randomly generated asteroids, which will destroy our ship if we detect a collision.

While Box2D will model the movement of our physics bodies for us, it is our responsibility to render (i.e. draw) representations of these bodies on the screen. To do this we will use a simplified version of the PhysicsDebugDraw class (called PhysicsDraw) which is included with the Physics Lab example. A copy of this simplified class is included at the end of the tutorial.

function setup()

    physics.gravity(0.0, 0.0)
    physicsDraw = PhysicsDraw()
    ship = createShip(WIDTH/2, HEIGHT/2)
    physicsDraw:addBody(ship)
    asteroidTimer = 0

end


The physics.gravity function allows us to set the gravity for our game for the x and y axis respectively. The units are pixels per second squared. We have set our program gravity to zero since we are in space. The createShip function will create a new physics body which we add to the table of bodies in PhysicsDraw. Every draw cycle, PhysicsDraw will render all the physics bodies at their current location.

function createShip(x, y, w, h)

   -- (x, y) are the centre co-ordinates of the ship.
   -- (w, h) define the width and height of the box containing the ship,
   -- if not specified they are 50 and 35 respectively.

   local width = w or 50
   local height = h or 35
   local shipBody = physics.body(POLYGON, 
                                  vec2(0, height), vec2(width, height/2), 
                                  vec2(0, 0), vec2(height/2, height/2), 
                                  vec2(0, height))

   shipBody.x = x
   shipBody.y = y
   shipBody.type = KINEMATIC
   shipBody.sleepingAllowed = false
   shipBody.info = "ship"
   shipBody.interpolate = true

   return shipBody


end

Next up we need functions to create our asteroids. We will use the same function (createRandPoly) to generate the debris field for when our ship collides with an asteroid and explodes.

function createRandPoly(x, y, s1, s2)

   -- s1 and s2 define the range of length for the poly sides
   -- count defines the number of sides of the poly

   local minLength = s1 or 1
   local maxLength = s2 or 5
   local count = math.random(5,10)
   local r = math.random(minLength, maxLength)
   local a = 0
   local d = 2 * math.pi / count
   local points = {}

   for i = 1,count do
       local v = vec2(r,0):rotate(a) 
                     + vec2(math.random(-10,10), math.random(-10,10))
       a = a + d
       table.insert(points, v)
   end

   local poly = physics.body(POLYGON, unpack(points))

   poly.x = x
   poly.y = y
   poly.type = DYNAMIC
   poly.sleepingAllowed = false
   poly.restitution = 0.5
   poly.info = "poly"
   physicsDraw:addBody(poly)

   return poly

end

function createAsteroid(x, y)

   return createRandPoly(x, y, 25, 65)


end

The asteroids are placed off the screen in random locations and accelerated towards the screen where eventually one of them will collide with our ship. Inside the draw() function we create a new asteroid every second.

function placeRandomAsteroid()

   -- Generate (x, y) co-ordinates which are
   -- initially off the screen.

   local x = math.random(WIDTH)

   if x < WIDTH/2 then
       x = x - WIDTH
   else
       x = x + WIDTH
   end

   local y = math.random(HEIGHT)

   if y < HEIGHT/2 then
       y = y - HEIGHT
   else
       y = y + HEIGHT
   end

   -- Create an asteroid at the new co-ordinates

   local asteroid = createAsteroid(x, y)

   -- Give the asteroid a linear velocity
   -- towards the screen (and our ship)

   local dX, dY = math.random(50, 100), math.random(50, 100)

   if x > WIDTH then
       dX = -dX
   end

   if y > HEIGHT then
       dY = -dY
   end

   asteroid.linearVelocity = vec2(dX, dY)

end

function createExplosionAt(x, y)

   -- Creates 6 small polygon moving out from (x, y)
   -- to simulate our ship exploding.
   --
   -- the body.info field is set to debris so that we
   -- can render it the same colour as our ship (i.e. to
   -- distinguish them from the asteroid polygons.

   randPoly1 = createRandPoly(x, y)
   randPoly1:applyForce(vec2(50,50))
   randPoly1.info = "debris"
   randPoly2 = createRandPoly(x, y)
   randPoly2:applyForce(vec2(50,50))
   randPoly2.info = "debris"
   randPoly3 = createRandPoly(x, y)
   randPoly3:applyForce(vec2(50,50))
   randPoly3.info = "debris"
   randPoly4 = createRandPoly(x, y)
   randPoly4:applyForce(vec2(-50,50))
   randPoly4.info = "debris"
   randPoly5 = createRandPoly(x, y)
   randPoly5:applyForce(vec2(-50,50))
   randPoly5.info = "debris"
   randPoly6 = createRandPoly(x, y)
   randPoly6:applyForce(vec2(-50,50))     
   randPoly6.info = "debris"   

end

-- This function gets called once every frame

function draw()

   -- This sets a dark background color 

   background(40, 40, 50)

   -- Generate a random asteroid every second

   asteroidTimer = asteroidTimer + DeltaTime

   if asteroidTimer > 1.0 then
       placeRandomAsteroid()
       asteroidTimer = 0
   end

   -- Do your drawing here

   physicsDraw:draw()

end

-- Collision Detection
--
-- This is simply a matter of checking whether one of the two
-- colliding bodies are our ship.

function collide(contact)

   if contact.bodyA == ship or contact.bodyB == ship then
       createExplosionAt(ship.x, ship.y)
       ship.info = "destroyed"
       ship = nil
   end


end

Collision detection is then just a matter of calling the built in collide() function and checking whether one of the two colliding bodies is our ship. Included below is the simplified PhysicsDebugDraw class which is responsible for rendering our physics bodies each frame.

--# PhysicsDraw

PhysicsDraw = class()

-- Simplified Physics Debug Draw class from the Physics Lab
-- example in Codea.

function PhysicsDraw:init()
   self.bodies = {}
   self.contacts = {}
end

function PhysicsDraw:addBody(body)
   table.insert(self.bodies,body)
end

function PhysicsDraw:draw()

   pushStyle()
   smooth()
   noFill()

   for i,body in ipairs(self.bodies) do

       pushMatrix()
       translate(body.x, body.y)
       rotate(body.angle)

       if body.type == STATIC then
           stroke(255,255,255,255)
       elseif body.type == DYNAMIC and body.info ~= "debris" then
           stroke(150,255,150,255)
       else
           stroke(150,150,255,255)
       end

       if body.shapeType == POLYGON and body.info ~= "destroyed" then
           strokeWidth(3.0)
           local points = body.points
           for j = 1,#points do
               a = points[j]
               b = points[(j % #points)+1]
               line(a.x, a.y, b.x, b.y)
           end
       elseif body.shapeType == CHAIN or body.shapeType == EDGE then
           strokeWidth(3.0)
           local points = body.points
           for j = 1,#points-1 do
               a = points[j]
               b = points[j+1]
               line(a.x, a.y, b.x, b.y)
           end      
       elseif body.shapeType == CIRCLE then
           strokeWidth(3.0)
           line(0,0,body.radius-3,0)            
           ellipse(0,0,body.radius*2)
       end

       popMatrix()
   end 

   stroke(255, 0, 0, 255)
   fill(255, 0, 0, 255)

   for k,v in pairs(self.contacts) do
       for m,n in ipairs(v.points) do
           ellipse(n.x, n.y, 10, 10)
       end
   end

   popStyle()
end

function PhysicsDraw:collide(contact)
   if contact.state == BEGAN then
       self.contacts[contact.id] = contact
       sound(SOUND_HIT, 2643)
   elseif contact.state == MOVING then
       self.contacts[contact.id] = contact
   elseif contact.state == ENDED then
       self.contacts[contact.id] = nil
   end

end

A complete download of the code presented in this tutorial is available from our Gist repository.

Saturday, November 24, 2012

Tutorial 24 - Basic 3D Graphics

Figure 1. Ripple Shader.

24.1 Setting the Scene


Version 1.5 of Codea is a huge update. In addition to camera access, image blend modes and a tween library for simple animation, it includes full access to shaders and a shader editor. This feature gives you full access to GLSL (OpenGL Shading Language) vertex and fragment shaders (which can be used to apply the ripple shader effect shown in Figure 1). To understand how to implement and use shaders we need to take a few steps back and provide some graphical foundations.

24.2 OpenGL


OpenGL is a multipurpose open-standard graphics library. Although it is actually a specification, it is usually thought of as an Application Programming Interface (API), which is the manifestation of this specification. The OpenGL API uses C and GLSL is very similar in structure to C but has its own peculiarities. As a C API, OpenGL integrates seamlessly with Objective-C based Cocoa Touch applications. The OpenGL API is defined as a state machine (see Tutorial 5), and almost all of the OpenGL functions set or retrieve some state in OpenGL. The only functions that do not change state are functions that use the currently set state to cause rendering to happen.

OpenGL for Embedded Systems (OpenGL ES) is a simplified version of OpenGL that provides a library which is easier to learn and implement on mobile graphics hardware. Apple provides implementations of OpenGL ES v1.1 and OpenGL ES v2.0. Codea uses v2.0.

OpenGL ES 2.0 is very similar to OpenGL ES 1.1, but removes functions that target the fixed-function vertex and fragment pipeline stages. Instead, it introduces new functions that provide access to a general-purpose shader-based pipeline. Shaders allow you to write custom vertex and fragment functions that execute directly on the graphics hardware (which is very fast). 

24.3 Rendering Graphics


Everything displayed on your iPad screen is a 2 dimensional array of pixels. Each pixel has a particular colour defined by a red, green, blue and alpha (transparency) value in the range 0 to 1. It is the purpose of the graphics pipeline to determine what colour to put in each pixel to provide a representation of your image. Displaying a 2D image is fairly straight forward but what about 3D? The process of converting a 3D world into a 2D image is called rendering.

There are many different rendering systems. The one that we will concern ourselves with is called rasterization, and a rendering system that uses rasterization is called a rasterizer. In rasterizers, all objects that you see are represented by empty shells made up of many triangles. These series of triangles are called "geometry", "model" or "mesh". We will use the term mesh as that is what is used in Codea.


Figure 2. Rasterizing a Triangle.

The process of rasterization has several phases. These phases are ordered into a graphics pipeline (figure 3), where the mathematical model of your image, consisting of a mesh of triangles, enter from the top and a 2D pixel image comes out the bottom. This is a gross simplification but may help in the understanding of the process. The order which triangles from your mesh are submitted to the pipeline can effect the final image. Pixels are square, so they only approximate the triangles (Figure 2), just as the triangles approximate the 3D image. The process of converting your triangles to pixels is called scan conversion, but before we can do this we need to perform some mathematics to check whether the triangle is visible and convert it from 3D to a 2D representation.




24.4 Graphics Pipeline Overview


Triangles are described by 3 vertices, each of which define a point in three dimensional space (x, y, z). To represent these in two dimensions we have to project the vertex co-ordinates onto a plane. We maintain the illusion of depth by using tricks like perspective (i.e. things the same size appear smaller the further away they are). We get to influence the graphics pipeline at two points, the vertex shader and the fragment shader, shown in orange in Figure 3.

If you are interested in a much more detailed explanation then we suggest that you read Andrew Stacey's tutorial on Using Matrices in Codea.

Step 1 - Vertex Shader (Clip Space Transformation)

The first phase of rasterization is to transform the vertices of each triangle into "clip space". Everything within the clip space region will be rendered to the output image, and everything that falls outside of this will be discarded. In clip space, the positive x direction is to the right, the positive y direction is up, and the positive z direction is away from the viewer. Clip space can be different for different vertices within a triangle. It is defined as a region of 3D space with the range [-w, w] in each of the x, y, and z directions. 

This is difficult to visualise and use so the vertices are normalised by dividing each co-ordinate (x, y, z) by w. After being normalised, the (x, y, z) co-ordinates will be in the range of -1 to +1. Dividing by w also applies a perspective effect to each of our triangles.

The entire process can be thought of as a mapping from the projection volume to a 2 unit cube with the origin at (0, 0, 0).


Figure 4. Clip Space Transformation & Normalisation.

In terms of the graphical pipeline (figure 3), this transformation is coded in the Vertex Shader. Open up the Shader Lab in Codea and tap on the vertex shader tab. In the main() function, the line:

gl_Position = modelViewProject * position;

performs the clip space transformation for you. 

The inputs to the vertex shader consist of:
  • Attributes - per vertex data supplied via vertex arrays (e.g. position, color and texCoord). They are signified by the attribute tag in GLSL;
  • Uniforms - constant data used by the vertex shader (e.g. modelViewProjection). Labelled as uniform in GLSL; and
  • Samplers - a specific type of uniforms that represent textures used by the vertex shader. These are optional.
The outputs of the vertex shader are called (somewhat redundantly) varying variables.

Step 2 - Primitive Assembly

A primitive is a geometric object which can be drawn by OpenGL ES (e.g. a point, line or triangle). In this stage, the shaded vertices are assembled into individual primitives.

Normalisation and Clipping will happen automatically in the Primitive Assembly stage between the vertex shader and fragment shader. Primitive Assembly will also convert from normalized device coordinates to window coordinates. As the name suggests, window coordinates are relative to the window that OpenGL is running within. Window coordinates have the bottom-left position as the x, y (0, 0) origin. The bounds for z are [0, 1], with 0 being the closest and 1 being the farthest away. Vertex positions outside of this range are not visible. The region of 3D space that is visible on the screen is referred to as the view frustum.

Step 3 - Rasterization

Rasterization converts the graphic primitives from the previous stage to two dimensional fragments. These 2D fragments represent pixels that can be drawn to the screen (Figure 2).

In this stage, the varying values are calculated for each fragment and passed as inputs to the fragment shader. In addition, the colour, depth, stencil and screen co-ordinates are generated and will be passed to the per-fragment operations (e.g. stencil, blend and dither).

Step 4 - Fragment Shader

The fragment shader is executed for each fragment produced by the rasterization stage and takes the following inputs:
  • Varying variables - outputs from the vertex shader that are generated for each fragment in the rasteriser using interpolation (e.g. vColor in the Ripple Shader Lab example).;
  • Uniforms - constant data used by the fragment shader (e.g. time and freq in the Ripple Shader Lab example).; and
  • Samplers - a specific type of uniforms that represent textures used by the fragment shader (e.g. texture in the Ripple Shader Lab example).
The output of the fragment shader will either be a colour value called gl_FragColor or it may be discarded (see Step 5).

Step 5 - Per Fragment Operations

The final step before writing to the frame buffer is to perform (where enabled) the following per fragment operations.
  1. Pixel ownership test - checks if the pixel is currently owned by the OpenGL context. If it isn't (e.g. the pixel is obscured by another view) then it isn't displayed.
  2. Scissor Test - if enabled, is used to restrict drawing to a certain part of the screen. If the fragment is outside the scissor region it is discarded.
  3. Stencil & Depth test - if enabled, OpenGL's stencil buffer can be used to mask an area.The stencil test conditionally discards a fragment based on the value in the stencil buffer. Similarly, if enabled the depth buffer test discards the incoming fragment if a depth comparison fails.
  4. Blending - combines the newly generated fragment colour value with the corresponding colour values in the frame buffer at that screen location.
  5. Dithering - simulates greater color depth to minimise artifacts that can occur from using limited precision. It is hardware-dependent and all OpenGL allows you to do is to turn it on or off.

24.5 A Simple Shader Example


Version 1.5 of Codea comes with a sample ripple shader (see Figure 1). The following fragment shader code will tint a texture with the tint colour by the tint amount.

// A basic fragment shader with tint.


// This represents the current texture on the mesh
// uniform lowp sampler2D texture;

// The interpolated vertex color for this fragment
// varying lowp vec4 vColor;

// The interpolated texture coordinate for this fragment
// varying highp vec2 vTexCoord;

void main()
{
    // Sample the texture at the interpolated coordinate
    
    lowp vec4 texColor = texture2D( texture, vTexCoord );
    
    // Tint colour - red is currently hard coded.
    // Tint amount - select a number between 0.0 and 1.0
    // Alternatively you could pass the tint color and amount
    // into your shader by defining above:
    //
    // uniform lowp vec4 tintColor;
    // uniform lowp float tintAmount;
    
    lowp vec4 tintColor = vec4(1.0,0.0,0.0,1.0);
    lowp float tintAmount = 0.3;
    tintColor.a = texColor.a;

    // Set the output color to the texture color
    // modified by the tint amount and colour.
    
    gl_FragColor = tintColor * tintAmount + texColor * (1.0 - tintAmount);
}

24.6 Appendix - GLSL Precision Qualifiers


You will notice the lowp, mediump and highp precision specifiers in the shader lab example. It is much faster to use lowp in calculations than highp.The required minimum ranges and precisions for the various precision qualifiers are:


Apple provides the following guidelines for using precision in iOS applications:
  • When in doubt, default to high precision.
  • Colours in the 0.0 to 1.0 range can usually be represented using low precision variables.
  • Position data should usually be stored as high precision.
  • Normals and vectors used in lighting calculations can usually be stored as medium precision.
  • After reducing precision, retest your application to ensure that the results are what you expect.

Tuesday, July 10, 2012

Tutorial 6 - MineSweeper Part 1 (Updated 23/01/16)


6.1 Game Design


To further illustrate the many uses of Finite State Machines (FSM) we will deconstruct a simple game called MineSweeper. This game will illustrate a bunch of new concepts including recursive coding and closures as well as using a lot of the code that we developed in early tutorials.

Our MineSweeper code uses no less than three state machines to keep track of what is happening. The main state machine is illustrated above. This is a pretty standard pattern for most games. The goal was to produce the entire game using just an iPad. 

The aim of MineSweeper is to clear the minefield represented by a square grid without tapping a mine. When the splash screen fades away you will be presented with a menu of three buttons: Easy, Medium and Hard which you can use to select the game difficulty and launch the game. 



Game difficulty is tracked using a state machine and is used when the New button is tapped on the game screen to ensure we launch another game at the same difficulty level as the current game. The variables effected by difficulty are shown in the following table.


Game Difficulty
Easy Medium Hard
Number of Mines 10 15 40
Grid Width 8 12 16
Grid Height 8 12 16


Once you select the game difficulty by tapping on the appropriate button, you will be presented by the main game screen. The blue grid represents the minefield. The number on the left above the minefield is the number of cells left to clear. The number on the right is the elapsed time in seconds (approximately). The game timer will start as soon as you tap a minefield cell. Winning, losing or tapping the New or Menu buttons will stop the game timer.

The button in the top right will initially have "Show" as its button text. This indicates that you are in Show mode when you tap the grid. Show mode will reveal the cell contents. This will either be blank, a number or a mine. If it is a mine then the game is over. If the cell is blank it means that there are no mines in the neighbouring cells (each cell can have up to eight neighbours depending on its grid position). If the cell contains a number, this represents the number of mines in adjacent cells. You can use these numbers to deduce where the mines are located. If you determine that a cell contains a mine you can flag it by using Flag mode. You enter Flag mode by tapping the Show Button. When in Flag mode this button text will change to "Flag". Tapping it again will return it to Show mode. Tapping any unrevealed cell while in Flag mode will place a flag icon on that cell. This icon can be toggled on and off by tapping the same cell in Flag mode.

If you tap a blank cell in Show mode, the game will also reveal any other adjacent blank cells. This can reveal quite large areas depending on the mine disposition.

You don't have to use flags to indicate suspected mine locations if you don't want to. The game is harder if you don't use flags.

The game ends if you tap a mine (lose) or if you reveal all of the cells without mines (win).


6.2 Download the Code


If you just want to run MineSweeper and play the game then the easiest approach is to download the entire code in one file and paste it into the Main tab of a new project. If you are more interested in reusing the classes and understanding how it works then you are better off downloading the individual classes and pasting them into separate tabs. The order of the tabs can be important, have a look at this article on inherited classes if you want to know why.

We suggest that you use the same tab order as our project. The classes you need can be downloaded using the following links. Use these names for your tabs/class.

1. Main
2. Cell
3. Button
4. RoundBorder
5. SplashScreen
6. Fader
7. Twinkle
8. IconImages

We have already discussed a number of these classes (RoundBorder, SplashScreen, Fader and Twinkle) in previous tutorials so we won't be covering them here. We had previously been using the Button class provided with the Codea sample project "Sounds Plus", but we found this class took too much tweaking to make the various buttons look right. Consequently, we are now using the mesh button class developed by Vega. We tweaked this a little bit, adding callback functionality and push/popStyle to preserve the various graphic variables.

The following tutorial will delve into the MineSweeper Main and Cell classes, which is where most of the action is, but we did want to touch on developing the images.

6.3 Pixel Images in Spritely



The sprite images used to represent the table cells were done in Spritely, the pixel image editor provided with Codea. This was fun but you probably wouldn't want to do a lot of images with it. Drawing every sprite, pixel by pixel using your finger is an exercise in patience. A side effect of this approach is that the MineSweeper code is over 5,000 lines long. Most of this is Spritely images. Each 32x32 pixel image takes over 1,000 lines of code to define it. Now Spritely automatically generates this for you but you need to draw the images first. To make life easy we have included all the code (including images) which you can download from dropbox.


For future games we probably won't use Spritely images. The largest sprite that you can produce is 32x32 pixels. This is just big enough for my little fingers to tap reliably but I imagine some folks could have difficulty. We would have preferred to make the sprites a touch bigger for MineSweeper. The advantage of this method is that all the game assets are in one file which makes it easy to distribute, however this is offset by the 32 pixel size constraint and the long files required to represent each image. On my first generation iPad, editing the IconImages class (which contains the 4 sprites) was very slow and crashed frequently. If you are going to use Spritely, a separate tab for each image is probably the way to go and don't forget to back up your code. The easiest way to backup is to tap and hold on your project icon in the project explorer screen of Codea and then tap copy. We then paste and email the resulting file, clean it up in TextWrangler and finally save it in DropBox.

But this does demonstrate that the iPad is moving from a content consumption device to a content generation device. How amazing is it to be able to code and produce graphics on this fantastic tablet?

Stay tuned for part 2 of the MineSweeper Tutorial...