Making a real game in a few hours on an iPad

Overview

At GameCraft London (on the 7th June 2014) I brought along a mere iPad and bluetooth keyboard (most seemed to have Windows machines, and quite a few had desktops). I turned up late (well actually what I consider early for a Saturday, 10am, but the start was scheduled for 8.30am!), took a couple of hours off in the middle and managed to finish a game (on the iPad) which came joint 5th of 24 (woo!) in the public vote at the end. That game, Deadly Divisors, is in the App store and works on both iPhone and iPad. Though with some recent iOS versions there are issues (though it seems to work fine on my iPhone with iOS 8.4).

What follows is a concise tutorial on and review of Codea (the App I used) and Lua (the language it uses) with a hideously inaccurate account of what I did that day (it was about a year ago, and I have reordered things for clarity). There should be just enough information to get started with Codea and make an App which you can submit to the App store.

This article contains the entire source code the the game so you can see exactly how it works.

The Idea

One of the major challenges of mobile gaming is control schemes: they cannot rely on precise tapping vis a vis location. Indeed iOS actively misreports touch locations to where it thinks you really meant to touch. What is (extremely) accurate is timing. With that in mind one of the game ideas I had been hoping to make was based on the idea of a simple tap-to-advance game, where you had to time your taps well (in the brief windows where advancing was allowed) and a single mistake meant game over (it worked for Flappy Bird, as did tapping anywhere as only action).

I considered colours, but settled on numbers for this; more precisely divisors (for example 3 is a divisor of 12 because 3 x 4 = 12). This means that you learn Mathematics as you play my game. Each tap would increase your score by the quotient (so in that last case 4). My vision was to have moving ‘tumblers’ of numbers on a treadmill (you have to keep moving or it is also game over).

Getting Started

I joined the not-real-game-developers table (i.e. not using Unity! And if anything Unity seems to have become even more dominant for Indie Devs since) and started working.

The theme: Composability

Gamejams usually have a theme, and the announced theme was remarkably suitable for the game I had in mind. A promising start.

Codea: a ridiculously polished, impressive interactive multimedia development platform

Codea is absurdly impressive. On an iPad it offers features that many major desktop IDEs and text editors still fail to offer. It has syntax highlighting, code completion, colour pickers, inline asset pickers, tabs, supplementary keyboard controls and at a tap of the play button you can run and experiment with your game or multimedia App.

It runs Lua (a beautiful, lightweight language) and uses an API based on Processing to offer a straight forward way of creating interactive graphics. If you have an iPad I insist you download it right now.

Lua

A primer from someone who doesn’t really know Lua and assumes it is basically a better Javascript with weird syntax

Lua has first class functions. It is lightweight and flexible. It has roughly Pascal/Ruby like blocks e.g. if/then/end. The for loops are weird: e.g.

for j = 1, 10 do 
end

Like Javascript you can create ‘Objects’ (in Lua tables) via

local v = {}

and these can work as both object like things and array like things.

Comments are -- comment goes here

There is no += (though Codea offers a button for this).

The class system will be clear through examples below. And for the most part we don’t need to care too much about Lua, we are using the Processing API with a much nicer than Processing’s pseudo-Java syntax.

Codea has lots of inline help, search, example etc.

There is a nice chapter in Seven More Languages in Seven Weeks on Lua. There are many resources on the official Lua site including various free books etc. But you probably don’t need any of this.

Let the hacking commence

Lua in Codea suggests (actually Lua is flexible enough to allow for many different approaches here) a lightweight class system based on objects which have two key methods a draw loop (60 frames a second) and touch events. The third major component is the init function (for on creation). This allows you to decompose your Game into a number of independent scenes. I highly recommend this approach (at least for comparable projects).

That is (Scene.lua):

Scene = class()

function Scene:init(params)

end

function Scene:draw()
    -- Codea does not automatically call this method
end

function Scene:touched(touch)
    -- Codea does not automatically call this method
end

Actually that above file is entirely unnecessary as this class system works by duck typing but is included just to make things clear. A Scene should have these methods (though there isn’t explicit inheritance).

So that leads us on the the main file (the thing Codea actually runs); it is just (Main.lua):

-- Deadly Divisors

displayMode(FULLSCREEN_NO_BUTTONS)
supportedOrientations(LANDSCAPE_ANY)


function setup()
    speed = 10
    highestScore = readLocalData("highscore", 0)
    scene = StartScene()
end


function draw()
    if scene then
        scene:draw()
    end
end


function touched(touch)
    if scene then
        scene:touched(touch)
    end
end

And works with a single major global scene variable (along with one for highestScore and a speed parameter). Super lightweight and simple, very little boilerplate.

Load everything, global variables, functions and some of the other violations of best practice

The Processing API assumes a global graphics context. Codea loads all of your source files. Unless you mark them variables in Lua are global. For medium or large projects obviously all of these things are unacceptable, but for a simple game they can save a lot of time. You draw a circle in a location with a size. You change the background colour. You transform your location. All without the boilerplate entailed by a system with an explicit context.

The main parts

Start Screen

I’ll start with the StartScene. This was actually really simple, then I added a little (accelerometer driven) behaviour for added interest. You can ignore this for now (or note how wonderfully easy it is to use the accelerometer in Codea; it is a lot more tedious in Swift/Objective-C).

The init function plays some music and generates the background, which uses a Lua table as an Array (Lua like Javascript doesn’t really have Arrays) to create bunch of background rectangles which are then moved as the device moves.

The draw method includes a bunch of text things, places so that when you touch near them the game starts or the help scene is shown. Thanks to iOS 7 having a very simple (text) idea of a button this was trivial to implement (on Touch.ENDED it does things).

I created a bunch of methods which I will eventually get to which effectively style the components (apply a certain background colour etc). This seemed to work quite well, allowing me to flexibly update things.

Notice how to start a scene I just set the (global) scene variable to a new one.

(StartScene.lua)

  StartScene = class()

  function StartScene:init()
      music("A Hero's Quest:Exploration", true, 0.6)
      sbg = generateBg()
  end

  function StartScene:drawBg()
      fillBg()
      fillSquare()
      rectMode(CENTER)

      dx = Gravity.x * 20
      dy = Gravity.y * 20

      for i=1,10 do
          for j = 1, 10 do
              if sbg[i + (j - 1)*10] == 1 then
                  rect(i * WIDTH/10 - WIDTH/20 + dx, j*HEIGHT/10 - HEIGHT/20 + dy, WIDTH/12, HEIGHT/12)
              end
          end
      end
      if math.random() < 0.1 then
          local r = math.random(1,100)
          if sbg[r] == 1 then sbg[r] = 0 else sbg[r] = 1 end
      end
  end


  function StartScene:draw()
      self.drawBg()
      fillMainText()
      textMode(CENTER)
      fontSize(HEIGHT/10)
      font("HelveticaNeue-Light")
      text("Deadly Divisors", WIDTH/2, HEIGHT* 5/8)

      fontSize(HEIGHT/16)

      text("Guide", WIDTH/8, HEIGHT/ 6)
      text("Easy", WIDTH*3/8, HEIGHT  / 6)
      text("Medium", WIDTH*5/8, HEIGHT /6)
      text("Hard", WIDTH * 7 /  8, HEIGHT/6)

      fillSecondaryText()
      fontSize(HEIGHT/20)
      text(string.format("High Score %d", highestScore), WIDTH/2, HEIGHT/2)
  end

  function StartScene:touched(touch)
      if(touch.state == ENDED) then

          if touch.y < HEIGHT / 3 then

              if touch.x < WIDTH/4 then
                  scene = HowToScene()
                  else
              if touch.x < (WIDTH/2) then
                  speed = 4
              elseif touch.x < (WIDTH * 3/4) then
                  speed = 2
              else
                  speed = 1
              end
              scene = GameScene()
                  end
          end
      end
  end

Game Over Scene

The game over scene is very similar to the start scene. The only new thing is the saving of local data (saveLocalData(key, value)). Again it was really simple to do something with touch areas (for restart or menu) and dynamic graphics.

(GameOverScene.lua)

  GameOverScene = class()

  function GameOverScene:init(params)
      self.highscore = params["highscore"]

      if self.highscore > highestScore then
          saveLocalData("highscore", self.highscore)
          highestScore = self.highscore
      end

      self.frame = 0
      music("A Hero's Quest:Hero's Triumph", true, 0.9)
  end

  function GameOverScene:drawBg()
      fillBg()
      fillSquareLight()
      rectMode(CENTER)

      dx = Gravity.x * 20
      dy = Gravity.y * 20

      for i=1,8 do
          for j = 1, 8 do
              if sbg[i + (j - 1)*10] == 1 then
                  rect(i * WIDTH/8 - WIDTH/16 + dx, j*HEIGHT/8 - HEIGHT/16 + dy, WIDTH/9.6, HEIGHT/9.6)
              end
          end
      end
      if math.random() < 0.1 then
          local r = math.random(1,100)
          if sbg[r] == 1 then sbg[r] = 0 else sbg[r] = 1 end
      end
  end


  function GameOverScene:draw()
      self.frame = self.frame + 1

      self.drawBg()
      fillMainText()
      textMode(CENTER)

      fontSize(HEIGHT/20)
      if(self.highscore == highestScore) then
          text("New High Score", WIDTH/2, HEIGHT * 2 /3)
      else
          text(string.format("High Score %d", highestScore), WIDTH/2, HEIGHT * 2/3)
      end

      fillSecondaryText()
      fontSize(HEIGHT/6)
      text(self.highscore, WIDTH/2, HEIGHT/2)

      fillMainText()
      fontSize(HEIGHT/20)
      text("Menu", WIDTH/4, HEIGHT /6)

      text("Retry", 3 * WIDTH/4, HEIGHT / 6)
  end

  function GameOverScene:touched(touch)
      if(touch.state == ENDED) and self.frame > 30 then
          if touch.x < WIDTH / 2 then
              scene = StartScene()
          else
              scene = GameScene()
          end
      end
  end

How To Scene

This was by far the simplest Scene. Codea has excellent support for PDF assets, so for the help scene I just created such an asset and rendered it in the centre of the screen (scaling it to fit). On touch it goes back to the start scene.

(HowToScene.lua)

  HowToScene = class()

  function HowToScene:init(x)
      howImg = readImage("Documents:dd_guide", WIDTH*2/3)
  end

  function HowToScene:draw()
      -- Codea does not automatically call this method
      fillBg()
      sprite(howImg, WIDTH/2, HEIGHT/2)
  end

  function HowToScene:touched(touch)
      if touch.state ==  ENDED then
          scene = StartScene()
      end
  end

The Game Itself

And now we (finally) get to the game scene. Somewhat out of chronological order, but by now you should understand the simple Codea system of a drawing loop, handling touch events and initialisation of things (scenes).

Let’s go through the code, roughly in order.

init

A game scene needs a grid of numbers which are generated. It starts at the middle tumbler of the middle column. Music is played.

draw

Two things are drawn each frame. In retrospect I would definitely have split this out into more functions (plus there are some major opportunities for optimising this code by drawing each column once and then rendering this graphic each frame (perhaps with some position/activity overlay) but this was a Gamejam so no).

The draw tumblers code is the only really messy code here (ignoring issues of global state etc elsewhere). It renders each column of numbers, moving in a sinusoidal (wavy) way; I considered linear movement but this is uglier, less interesting and would have had jarring transitions at ends (so I didn’t even try it).

Each square is drawn (the currently active column is locked vertically). There are some complications such as periodic boundaries i.e. once a column goes off screen it is recycled from right but the fundamentals are quite simple.

Drawing the score overlay is simple and uses partial transparency via fill(0, 0, 0, 91).

touch

Touch events are quite simple: really just figuring out if safe touch or not (and most complexity comes from recycling/looping of columns).

(GameScene.lua)

     GameOverScene = class()

    function GameOverScene:init(params)
        self.highscore = params["highscore"]

        if self.highscore > highestScore then
            saveLocalData("highscore", self.highscore)
            highestScore = self.highscore
        end

        self.frame = 0
        music("A Hero's Quest:Hero's Triumph", true, 0.9)
    end

    function GameOverScene:drawBg()
        fillBg()
        fillSquareLight()
        rectMode(CENTER)

        dx = Gravity.x * 20
        dy = Gravity.y * 20

        for i=1,8 do
            for j = 1, 8 do
                if sbg[i + (j - 1)*10] == 1 then
                    rect(i * WIDTH/8 - WIDTH/16 + dx, j*HEIGHT/8 - HEIGHT/16 + dy, WIDTH/9.6, HEIGHT/9.6)
                end
            end
        end
        if math.random() < 0.1 then
            local r = math.random(1,100)
            if sbg[r] == 1 then sbg[r] = 0 else sbg[r] = 1 end
        end
    end


    function GameOverScene:draw()
        self.frame = self.frame + 1

        self.drawBg()
        fillMainText()
        textMode(CENTER)

        fontSize(HEIGHT/20)
        if(self.highscore == highestScore) then
            text("New High Score", WIDTH/2, HEIGHT * 2 /3)
        else
            text(string.format("High Score %d", highestScore), WIDTH/2, HEIGHT * 2/3)
        end

        fillSecondaryText()
        fontSize(HEIGHT/6)
        text(self.highscore, WIDTH/2, HEIGHT/2)

        fillMainText()
        fontSize(HEIGHT/20)
        text("Menu", WIDTH/4, HEIGHT /6)

        text("Retry", 3 * WIDTH/4, HEIGHT / 6)
    end

    function GameOverScene:touched(touch)
        if(touch.state == ENDED) and self.frame > 30 then
            if touch.x < WIDTH / 2 then
                scene = StartScene()
            else
                scene = GameScene()
            end
        end
    end

The (somewhat) Complicated Bits

And here is the final file. Which became a bit of a catch all. On a system with better refactoring support (even moving some code to another file is a bit tedious on an iPad) this would be the obvious place to start. I’ll go through it roughly in order.

isPrime

I will want to exclude prime numbers (or really numbers which are coprime to all other numbers used). Which is why for me 2 or 3 is not prime (because I decided to allow 2 and 3 as base numbers then add multiples to the sets). I’m basically leaving this in to annoy mathematicians (though I have a couple of Maths degrees).

generateNumber, generateTumbler, generateGrid

generateNumber uses isPrime to generate a valid (non (co)prime (to all available)) number. generateTumbler generates a list (actually a Lua table, used as a list) of such numbers. generateGrid generates a grid of these (a table of tables, all of which are used as arrays).

isPossible

I don’t want a number such that there are no numbers in the next column which can be jumped to (i.e. it must divide at least one of those or one of those must divide it). A fairly simple brute force with short circuiting on success will do.

generatePossibleGrid

The hardest part of the code (but by no means super complex). Just iterates (repeatedly) through a candidate grid of numbers, whenever it gets something not possible to continue from it is replaced and the process restarted (by changing a number we may make a previous number impossible so we need to start all over again). The theoretical general worst case is probably horrific here, but in practice (for our grids) this process is fine.

The rest

A little utility method and some (effectively) styling rules to apply (setting colours etc).

(Util.lua)

  function isPrime(p)
      if p == 2 or p == 3 then
          return false
      end

      for test = 2,p-1 do
          if p % test == 0 then
              return false
          end
      end
      return true
  end


  function generateNumber(max)
       n = math.random(2,max)
      while isPrime(n) do
          n = math.random(2, max)
      end
      return n
  end


  function generateTumbler(n)
      local t = {}
      for i = 1,n do
          t[i] = generateNumber(n * 2)
      end
      return t
  end


  function generateGrid(n)
      local grid = {}
      for i = 1,n do
          grid[i] = generateTumbler(n)
      end
      return grid
  end


  function isPossible(el, n, tumbler)
      for i= 1,n do
          if el % tumbler[i] == 0 or tumbler[i] % el == 0 then return true end
      end
      return false
  end

  function generatePossibleGrid(n)
      local grid = generateGrid(n)

      local madeChanges = true

      while madeChanges do
          madeChanges = false

          for i = 1, n do
              nrIdx = i + 1
              if nrIdx > n then nrIdx = 1 end
              for j =1,n do
                  while not isPossible(grid[i][j], n, grid[nrIdx]) do
                      madeChanges  = true
                      grid[i][j] = generateNumber(n * 2)
                  end
              end
          end
      end
      return grid
  end

  function generateBg()
      bg = {}
      for i=1, 100 do
          bg[i] = math.random(0,1)
      end
      return bg
  end

  --global functions to set style/do background
  function fillBg()
      background(64)
  end

  function fillSquare()
      fill(48)
  end

  function fillSquareLight()
      fill(92)
  end

  function fillMainText()
      --fill(211, 212, 211, 255)
      fill(234, 218, 73, 255)
  end

  function fillSecondaryText()
      fill(213, 212, 192, 255)
  end

Done

Apart from a single pdf asset which I created in Sketch (to explain the game) that is it. If you were to create these files in a Codea project (thanks to Apple’s bullshit restrictions on sharing code that can run on an iOS devices there isn’t a particularly easy way of sharing such projects; though there are some community things around Github Gists etc) and press play you would be able to run it.

The App Store

Codea offers a very simple export option allowing you to export an Xcode project. From there you can customise it a little and assuming you are a paid up Apple Developer submit it to the App store.

Concluding Thoughts

The lack of version control (it would be wonderful to be able to work with Git) is the major thing I miss from ‘real’ development environments. Also some kind of support for testing would be nice. But that is missing the point. Codea offers an amazing environment for creating little games and interactive multimedia Apps. It lends itself particularly well to generative graphics (defined by maths rather than .pngs) and interactive stuff which will work well with a draw loop rather than the scene graph based systems that ‘proper’ games frameworks would use (you could create a very simple object system to do this if you wanted).

Codea can even be used for 3D graphics(!), shaders, physics and many other advanced things; but I think it is ideally suited for 2d Games, with minimalist (perhaps generative) graphics. It makes it easy to add a soundtrack, use the accelerometer, handle touches and save simple data. It also offers remarkably smooth deployment to the App Store. I would love to see a desktop version of Codea (so I could use Git and a proper keyboard) but for now it offers a brilliant programming sketch pad, that in most ways has surpassed the original Processing (on which it is loosely based).

Deadly Divisors could do with a bit of tidying up and there are some easy potential performance improvements (in particular it shouldn't be rendering 100 squares each time) but it is already fast enough.