Showing posts with label App. Show all posts
Showing posts with label App. Show all posts

Sunday, January 27, 2013

Tutorial 25 - A Lua Primer for Codea (Part 1)

25.1 Introduction


Every year around Christmas there is a surge of activity on the Codea Forums and on this tutorial site. A common theme in the comments and emails that we receive, is that this site assumes quite a bit of knowledge and as Codea appears to be attractive to first time programmers there are some gaps which need to be filled.

This tutorial is an attempt to fill in some of those gaps. In particular, we will try and provide a quick primer on the Lua language which Codea uses. To ensure that Codea does not incur the wrath of Apple, there are some features of the Lua language which have been disabled in Codea. We will discuss these briefly but the list is diminishing with every new release of Codea and there isn't anything which will likely cause you any problems.


25.2 Lua


Lua is a light weight scripting language written in C. By light weight we don't mean that it isn't capable of writing complicated code but that the syntax is stream lined and straight forward. This makes it a great first language to learn. 

Lua means "moon" in Portugese, it is the evolution of another language called SOL (Portugese for "sun"). It was created in 1993 by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, and Waldemar Celes, members of the Computer Graphics Technology Group (Tecgraf) at the Pontifical Catholic University of Rio de Janeiro, in Brazil (hence the Portugese naming).


25.3 Variables and Data Types


Variables are memory locations that hold data. There are three kinds of variables in Lua: global variables, local variables, and table fields. All variables are global unless defined otherwise. Defining an identifier as local is done using the local keyword. Local identifiers are not visible outside the block in which they were declared, but are visible inside sub-blocks. This is called lexical scoping. For Codea a tab is considered a chunk,  which translates as follows:
  1. If you define a variable within any of the tabs it will, by default be treated as global.
  2. If you define a local variable within a tab (but outside a function), you will only be able to access it within that tab.
  3. If you define a local variable within a function, you can only access it from within that function. However, if a function is enclosed in another function, then it has access to all the local variables of that function. In these circumstances, the external local variable is called an "upvalue". We cover this in Interlude 7.
Assigning to a variable that has not been declared locally within the current chunk will recursively search for that name in the parent chunk, up to the top-level. If the name is found, the assignment is made to that variable. If the name is not found, the assignment becomes a global (either creating a new variable, or replacing an existing global). The consequence of this behaviour is that using local variables is much faster than globals.

Global identifiers are stored in the implicit global environment table, which can explicitly be accessed through the name _G (see section 25.5).  

Variable names must start with a letter or underscore and can contain letters, digits or underscores. Defined keywords in Lua can not be used as a variable name (e.g. and, break, do, else and end). As a convention, variables in capitals starting with an underscore (e.g. _G or  _VERSION) are reserved for internal Lua global variables.

Lua supports only a small number of data types. The ones you will use the most are boolean (true or false), numbers, strings and tables. 

By default, Lua's number type is represented by double-precision floating-point numbers. However, Codea's Lua interpreter uses another internal representation for numbers: single-precision float. This gives a precision of 6 to 9 significant decimal digits and a range for positive values of between about 1.4e−45 to about 3.4e+38 (reference from Codea Wiki).

Lua strings can hold any 8-bit character, including embedded zeros. Strings can be enclosed in single or double quotes, pick one style and stick with it, we tend to go with double quotes. Strings in Lua are immutable values, and thus you cannot change a character inside a string. You can also delimit literal strings using matching double square brackets. For example:

aLongString = [[
                   line 1
                   line 2
                   line 3
                   line 4
              ]]

Boolean variables are a relatively recent introduction to Lua. The boolean type has two values, true and false. Conditional tests consider false and nil as false and anything else as true (including 0 and "").

Lua is a dynamically typed language, which means that variables do not have a defined type, they get their type based on the data assigned to them. This means that the following is a valid chunk of code in Lua (albeit probably not best practise as it would make your program hard to follow):

x = 1
print("x as an integer: " .. x)
x = 3.141592654
print("x as a float or real number: " .. x)
x = 0xFE
print("x as a hexadecimal converted to decimal: " .. x)
x = "now a string"
print("x as a string: " .. x)
x = true
print(x)
x = {value = 999}
print("x as a table: " .. x.value)
x = function(n) return n*2 end
print("x as a function: " .. x(2))

A few comments about the preceding code and some general observations on variables: 
  • Lua is case sensitive, so x and X are different variables. 
  • In the example above, when we store a hexadecimal value in x it is automatically converted and printed as a decimal.
  • You can concatenate (i.e. join) two strings or a string and a number using the".." operator. Numbers are automatically converted to strings in this situation. You can't concatenate a boolean.
  • This automatic conversion (or to use the technical term - coercion,  works the other way around as well, a string will be treated as a number if used in that context. Any arithmetic operation applied to a string tries to convert this string to a number. Note that comparison operators (== ~= < > <= >=) do not coerce their arguments. Thus a number is not equal to its string representation.
  • Each line of code is terminated by a new line. You can optionally use a semi-colon (as used in C, but it isn't recommended). 
  • You can do multiple assignments on the one line (e.g. x, y = 1, 2), and you can use this to swap two variables (e.g. x, y = y, x).
  • A variable that hasn't been assigned a value will be nil by definition. 
  • Unlike C, the value 0 is not a false test condition in Lua, only nil or false is. You can use the fact that nil equates to false to assign default values to a variable (e.g. x = x or 2 will assign the variable x a value of 2 if it hasn't been previously assigned a value).
  • Note that you can assign a function to a variable. As an example, we make use of this in Codea to pass call back functions to our Button class to indicate what function to call when a button is tapped. The technical term for this feature is first-class functions. As such, they can be created during runtime, stored in variables, and passed to and returned from other functions.
There are two other data types called userdata and thread. Userdata and tables will be covered in subsequent sections. Thread is way outside the scope of this tutorial so we are just going to ignore them.


25.4 Tables


Tables in Lua are amazingly versatile. Which is just as well since they are the only built in composite data type available. The technical term for tables in Lua is a hashed heterogeneous associative array and they are worthy of a separate tutorial, which is exactly what we have done. You can access our tutorials on tables here:
  1.  Understanding Tables;
  2.  Converting a string to table and table to string
  3.  Saving and Loading complicated tables; and
  4.  Classes in Lua and Codea.

25.5 The Global Variable Table _G


This section goes beyond the scope of this tutorial, so feel free to skip ahead. It is included here out of interest. We mentioned above that Lua has a number of internal variables. One of these is _G, a global variable which points to the global environment. It includes all of the global variables and functions and even includes a reference to itself! It is sometimes useful to understand what has been defined globally and you can use the following to display the contents of _G: 

for k, v in pairs(_G) do print(k, v) end


25.6 Userdata


Userdata are variables that encapsulate arbitrary C/C++ data within a Lua interface. Many Lua modules extend the capabilities of Lua by binding external libraries, including the creation of new types as userdata. Userdata variables can only be created using the C API (i.e. in our context this means the Codea runtime), this can't be done in Lua.

Userdata is largely outside the scope of this tutorial but you need to know that Codea extends Lua with 12 user-defined types such as codeaimage, mesh, matrix, vec2, vec3, touch and color. You can read all about these on the Codea User Defined Types page.

Userdata is also useful when we want to expose C/Objective C functions in Lua via the runtime. Have a look at our tutorials on integrating Game Centerbuilding a Universal App or implementing iAds if you are interested.


25.7 Operators


Lua supports the following arithmetic operators:  + (addition), - (subtraction), * (multiplication), / (division), % (modulo),  ^ (exponentiation) and  - (negation).

The relational operators in Lua are: == (equal), ~= (not equal), < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to). These operators will return a boolean (true or false).

The logical operators in Lua are and, or, and not. As for control structures, all logical operators consider both false and nil as false and anything else as true.

As mentioned in section 25.3, the string concatenation operator in Lua is denoted by two dots ('..'). If either operand is a number, then it is converted to a string before joining.

The length operator is denoted by #. The length of a string is its number of bytes (i.e. characters). The length of a table is more complicated. For a table used as a simple array the length operator will work as expected and return the number of elements in the array. For more complicated tables, the returned length can be any of the indices that directly precedes a nil value. You can read more about this in our tutorial on tables.

25.8 Classes


In Interlude 11 we spoke about the use of classes in Codea. Codea comes with a built-in global function called class() that is used to emulate the functionality of a class data structure using a table (and metatable). We use classes extensively in our tutorials but we haven't explained the difference between the "." and ":" operators when applied to a class. As this has been the subject of a number of questions, we will cover the proper usage in this section.

In order to demonstrate, we will first construct a simple ship class. By convention the base class name starts with a capital, while instances of the class have names which start in a lower case letter. 

The Codea class() constructor is a function which, when called, sets up a new table and attaches the class metatable to it. The metatable redirects unrecognized events to the class method table (as well as possibly handling events itself). This is covered in some depth on the Codea wiki.

Ship = class()

function Ship:init( hitPoints )
    self.points = hitPoints or 100
end

function Ship:hit( damage )
    self.points = self.points - damage
    print( self.points )
end

In our Main tab, setup() function we may then instantiate our class as follows:

myShip = Ship()

This will create a new object called myShip of class Ship. Note that we called ship without any parameters. This means that hitPoints will be nil and self.points will be assigned 100 (our default value) since nil is interpreted as false by Lua. 

One of the potentially confusing parts of the class definition is where does "self" come from and why do we use it? The self variable is created automatically by calling class() and provides a reference to the object created when we instantiate a class. This means that when we change the points variable using self.points we only change it for that object, not for every object of class Ship. So in our example above self = myShip. 

Try converting the Ship class so that it uses points instead of self.points. If you do this and have created another ship (e.g. myOtherShip = Ship()), then every time you call myShip:hit(20) this will also reduce the hit points of myOtherShip, which is usually not what you want.

If our ship gets hit then we want to record the damage and print out the current hit points using the hit(damage) function of our class. There are two ways you could do this (the right way and the wrong way!). In the Main tab of your program, you could use:

myShip:hit(20)

or you could try to use:

myShip.hit(20)

Which is correct? To work this out we have to understand the difference between the two statements. Using ":" we are actually passing two parameters to hit(), the damage and a hidden reference to the object being hit (i.e. self). So myShip:hit(20) is equivalent to myShip.hit(self, 20). 

The second option - myShip.hit(20) will throw an error in Codea. If you want to access the class instance variables directly from the Main tab, you can do something like:

myShip.points = myShip.points - 20

This will operate as expected, but only if you have defined it as self.points in your class.


25.9 Other Resources


If what is provided here isn't sufficient then have a look at the official Lua Tutorial site, which has a MUCH more detailed treatment on Lua. The Codea reference documentation is also very useful, as is the active forum and wiki (in particular have a look at the Hints and Tips page which contains references to items not covered elsewhere). 

Codea (v1.4) uses version 5.1 of Lua (you can determine the current version of Lua using the statement print(_VERSION)). The definitive treatment of the Lua language can be found at the official on-line Lua 5.1 Reference Manual

Sunday, October 21, 2012

Tutorial 22 - Building a Universal App in Codea

Figure 1.

22.1 Going Universal in Xcode


A universal application is defined as one targeted at both the iPhone/iPod touch and iPad. If you want to build a universal iOS application then you have to use the runtime and Xcode. There is currently no way to target iPhone development from within Codea.

This is handy, since we plan to use our old staple Mine Sweeper to demonstrate how to turn your Codea App into one which can run on an iPhone (or iPod Touch for that matter). As we have added Game Center code to MineSweeper, all future development of this game will have to occur in Xcode.

In principle this is a simple three step process:
  1. Target the application to iPhone/iPad in the Xcode deployment build settings;
  2. Add the relevant iPhone graphical assets (icons, default images etc.);
  3. Use a bunch of if (device is an iPhone) then do one thing else do another thing. Step 2 is simplified by Apple providing a new macro called UI_USER_INTERFACE_IDIOM() that will tell you what device your code is currently running on. There are two values defined, UIUserInterfaceIdiomPhone and UIUserInterfaceIdiomPad, and this macro will return the appropriate value based on the current device.
It would be a very short tutorial if that was all there was to it. If your App has any sort of complexity then there are a few things that you need to watch out for.

22.2 Step 1: Targeting iPhone and iPad in Xcode


As with the previous few tutorials, we are assuming that you have already got your Codea App up and humming in the Xcode iPad simulator using the runtime. If you haven't got to this state then go back and have a look at Tutorial 12.

Targeting your App for iPhone and iPad is easy one you have found the setting. Open up Xcode with your App. You need to have the Project Navigator open (top left hand tool bar item - it looks like a file folder). Click on the item at the very top of the Navigator called "Codea Template."

When you create a new project, it includes one or more targets, where each target specifies one build product and the instructions for how that product is to be built. You can use the project editor to specify every aspect of the build, from the version of the SDK to specific compiler options.

In the next column over you will see two items, Project (CodeaTemplate) and Targets (MineSweeper - in our case, this will be the name of your App). 

Selecting the project file opens the project settings editor. The project settings editor is Xcode 4′s replacement for Xcode 3′s project and target inspectors. Select the target from the project settings editor. 

Figure 2. Setting your App to Universal.

To specify the device families on which you want your app to be able to run:

  1. In the project navigator, select the project. 
  2. From the target list in the project editor, select the target that builds your app, and click Summary. 
  3. From the Devices pop-up menu, choose Universal (to target both families - iPhone and iPad are the other two options - see Figure 2.).
And that is it. You should now be able to select either iPhone or iPad from the simulator. It is unlikely that the iPhone version will work but you can give it a shot if you want. MineSweeper certainly crashed and burned. The app should still compile and run ok as an iPad app.

22.3 Step 2: Adding iPhone Graphical Assets


Once you have made your app universal, Xcode will allow you to set up the icon and default images for your iPhone. Before we do this, here is a quick refresher on the screen resolutions that we need to consider. 

iPad:
  • 1024 x 768 px; and
  • 2048 x 1536 px for Retina displays (@2x).
iPhone:
  • 320 x 480 px;
  • 640 x 960 px for Retina displays (@2x); and
  • 640 x 1136 px for the iPhone 5 and iPod touch (5th Generation).
You will also need an icon in the 57 x 57 pixel size and 114 x 114 pixels (@2x for the retina version). These are fairly easy to scale from your iPad icons.

You can also use your  default iPad images for your iPhone but they won't scale exactly (you will need to crop either the width or height) if you want to maintain the aspect ratio.

The easiest way to add the iPhone versions of your icon and default load images is to drag them from Finder onto the deployment information page in Xcode. To bring up this page, select the CodeaTemplate at the top of the left hand Project Navigator pane. Click on Targets (Minesweeper in our case) in the middle pane and Summary in the far right pane (see Figure 3.)

Figure 3. Adding the iPhone Graphical Assets

Once you have the screen shown in Figure 3, you can drag across the appropriate image files from Finder. If necessary, Xcode will rename the files and copy them into your project. You should now be able to run your application in the iPhone simulator and see the default image load and when you exit the app using the home button you will be able to see the appropriate icon. That may be all you need to do, but probably not.

22.4 Step 3: Code Modifications


To get our App running on the iPhone we need to make some changes. What you have to watch out for is any hard coded view sizes. Some of our graphical assets will have to be redone to suit the dimensions of the iPhone screen. Let's start by getting the splash screen to work.

In the CodifyAppDelegate of the runtime the base app window is created when the application finishes launching. The good news is that the following line of code will create a window which will fit either an iPhone or iPad.

self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];

So there is nothing we need to do from a runtime perspective (unless you are using Game Center - see below). If we did need to modify anything in the Objective C portion of our code we could use the following pattern:

// Check if device is an iPhone or iPad
// We now also need to check whether we have a iPhone 5 
// or 5th Generation iPod touch.
    
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
        NSLog(@"App Delegate - iPad device detected.");
    else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
    {
        CGSize result = [[UIScreen mainScreen] bounds].size;
        if (result.height == 480)
        {
            NSLog(@"App Delegate - iPhone/iPod Touch detected (< v5).");
        }
        if (result.height == 568)
        {
            NSLog(@"App Delegate - iPhone/iPod Touch detected (> v5).");
        }
    }

Paste a copy of this in the - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method of the CodifyAppDelegate.m file if you want to try it out.

Now we do have to modify our Lua splash screen class as it loads background images which are too large for the iPhone screen. To assist with the conditional coding we need to add, we wrote a new helper function for the Lua portion of the code to determine what device we are running on. This was added to the Main tab, so that we know it will be available throughout the rest of the code. Depending on what resolution the forthcoming iPad mini ends up with, we might need to update this in the next couple of weeks. 

-- This function will detect the Device which is in use, 
-- required as our app is now universal

function deviceIsIpad()

    if CurrentOrientation == LANDSCAPE_LEFT 

      or CurrentOrientation == LANDSCAPE_RIGHT then
        if WIDTH == 1024 then
            return true
        else
            return false
        end
    else
        if WIDTH == 768 then
            return true
        else
            return false
        end
    end
    
end

As the device isn't going to change mid program, you can call this function once and assign it to a global boolean if you are worried about performance. Performance isn't an issue for MineSweeper so we wont bother.

Using this function we can now conditionally load the background image depending on the device. In the function SplashScreen:init(splashText, fadeSpeed) we have modified the sprite loading as follows:

    if deviceIsIpad() then
        landscapeImage = readImage("Dropbox:MS_TitleScreen_1024x768")
        portraitImage = readImage("Dropbox:MS_TitleScreen_768x1024")
    else
        landscapeImage = readImage("Dropbox:Default-Landscape~iphone")
        portraitImage = readImage("Dropbox:Default-Portrait~iphone")
    end

Remember that if you need to add new images to your Dropbox folder on the Mac then you will have to do a clean build for Xcode to recognise them. If you don't do this you will get a sprite loading error. To find the folder on your Mac to add the images, right click on the Drop Box sprite pack folder in the Xcode Project Navigator (Frameworks -> Codea -> SpritePacks) and select "Show in Finder". You can then drag across the new images that you want to use.

The other change that we need to make is in the function SplashScreen:draw().

        if deviceIsIpad() then
            sprite(logoImage, WIDTH - logoImage.width - 20, 20)
            text(self.splashText, WIDTH/2, HEIGHT/2 + 250 + portraitTextOffset)
            fill(whiteColor)
            fontSize(20)
            text("Version: "..version, WIDTH/2, HEIGHT/2 + 200 + portraitTextOffset)
        else
            fontSize(42)
            text(self.splashText, WIDTH/2, HEIGHT - 80)
            fill(whiteColor)
            fontSize(14)
            text("Version: "..version, WIDTH/2, 50)
        end

You will probably need to make similar adjustments in font size and composition to suit the smaller iPhone screen We also don't load the Reefwing Software logo if the device is an iPhone due to the space constraints. Make sure that the resulting view looks okay in both landscape and portrait orientations (command right arrow to change the orientation of the simulator in Xcode). As an aside you should find that the Fader class works as expected regardless of the device.


Now that you have the splash screen sorted we can turn our attention to the Menu screen. For us the changes here are minimal for the portrait orientation. We load up a smaller MineSweeper logo to fit on the iPhone screen and once again there is no room for the Reefwing and Codea logos so we dispense with them. The RoundBorder class works on all devices without any changes.

In the Main setup function where we load images we added:

-- Load the MineSweeper Text Logo
    
    if deviceIsIpad() then
        msTextLogo = readImage("Dropbox:minesweeper_logo 400x76px")
    else
        msTextLogo = readImage("Dropbox:minesweeper_logo 200x38px")
    end

And then in drawMenu() we shuffle the button positions and eliminate the "Medium" and "Hard" game options since those grid sizes wont fit on an iPhone screen (see section 22.5 Lessons Learned). The upside of removing the "Medium" and "Hard" buttons is that the remaining three buttons fit easily on the iPhone screen in both portrait and landscape orientations. Since there is only one game difficulty available on the iPhone we also changed the button text from "Easy" to "Start".


The achievement and leader board Game Center functionality works fine on an iPad but due to the way these modal views are displayed, on an iPhone the Lua draw() loop gets stopped if you tap the "Achievements" or "High Score" buttons. This results in the app still running but the screen never gets refreshed. To prevent this from occurring we need to modify the aGameCenter_Codea.m file that we introduced in the previous Game Center tutorial. The fix is to call [[SharedRenderer renderer] startAnimation] when the Game Center view controller is dismissed. This method is in the BasicRendererViewController of the runtime. An example  for dismissing achievements is shown below. The same is required for dismissing leader boards.

- (void)achievementViewControllerDidFinish:(GKAchievementViewController *)viewController
{
    [[SharedRenderer renderer] dismissViewControllerAnimated:YES completion:nil];
    [[SharedRenderer renderer] startAnimation];
}

This isn't a problem on the iPad because the Game Center view doesn't cover the whole screen (which causes the viewWillDisappear method to be called in the rendering code and stops animating the draw loop).

We have two more screens that we need to tweak and then we are done. Firstly the High Score screen which shows the scores stored locally is not particularly useful on the iPhone version (and is somewhat redundant as we are now using Game Center). So in the function scoreButtonPressed() we wont show this screen if we are an iPhone using the following code.

if deviceIsIpad() then
        gameState = stateScore
end

Remember that the gameState determines what the draw() loop displays. By not setting this to stateScore if the device is an iPhone, this screen wont be displayed.


Our last job is to sort out the game playing screen. The grid display is platform agnostic so it is only the button and text placement that we need to worry about. In the function drawCellsLeftDisplay() we do some more conditional placement.

    local iPhoneHeightAdjustment = 0

    if CurrentOrientation == LANDSCAPE_LEFT
      or CurrentOrientation == LANDSCAPE_RIGHT then
        iPhoneHeightAdjustment = 50
    end



    if deviceIsIpad() then
        text(cellsLeft, w + 50, HEIGHT - 100)
        text(math.round(gameTime), WIDTH - 150, HEIGHT - 100)
    else 
        text(cellsLeft, w - 30, HEIGHT - 100 + iPhoneHeightAdjustment)
        text(math.round(gameTime), WIDTH - 70, HEIGHT - 100 + iPhoneHeightAdjustment)
    end

The buttons on the iPhone screen will need to be placed based on the current orientation due to the space constraints. We do this in the drawGameButtons() function.

function drawGameButtons()
    
    -- These are the buttons visible on the game run screen
    -- Adjust the button position based on the device and orientation.
    -- In particular, we need to move the button location
    -- if the current device is an iPhone/iPod touch.
    
    local mButtonSize = vec2(100, 50)
    local mLocX, mLocY = 0, 0

    if CurrentOrientation == LANDSCAPE_LEFT 
      or CurrentOrientation == LANDSCAPE_RIGHT then
        if deviceIsIpad() then
            mLocX = WIDTH - 200
            mLocY = HEIGHT - 195
        else
            mLocX = WIDTH - 200 + 90
            mLocY = HEIGHT - 195 - 100
        end
    else
        if deviceIsIpad() then
            mLocX = WIDTH - 125
            mLocY = HEIGHT - 320
        else
            mLocX = WIDTH - 120
            mLocY = HEIGHT - 320 - 140
        end
    end
    
    flagButton.x, flagButton.y = mLocX, mLocY
    
    if CurrentOrientation == LANDSCAPE_LEFT 
      or CurrentOrientation == LANDSCAPE_RIGHT then
        if deviceIsIpad() then
            mLocY = 110 + mButtonSize.y*2
        else
            mLocY = 110 + mButtonSize.y
        end
    else
        if deviceIsIpad() then
            mLocY = 240 + mButtonSize.y*2
        else
            mLocX = 20
        end
    end
    
    newGameButton.x, newGameButton.y = mLocX, mLocY
    
    if CurrentOrientation == LANDSCAPE_LEFT 
      or CurrentOrientation == LANDSCAPE_RIGHT then
        if deviceIsIpad() then
            mLocY = 110
        else
            mLocY = 90
        end
    else
        if deviceIsIpad() then
            mLocY = 240
        else
            mLocX = WIDTH / 2 - mButtonSize.x/2
            mLocY = HEIGHT - mButtonSize.y - 10
        end
    end    
        
    menuButton.x, menuButton.y = mLocX, mLocY
    
    flagButton:draw()
    newGameButton:draw()
    menuButton:draw()
    
end



22.5 Lessons Learned


Converting to a universal App was easier than we were expecting. The Codea runtime works out of the box without modification so it is only your Lua code and graphical assets which need to be modified.

For us the most important lesson is that if you want you App to eventually be universal then design it to work on an iPhone/iPod sized screen (320 x 480 pixels) first. It is much easier to convert this to work on a larger screen than vice versa.

The other lesson is to avoid using hard coded co-ordinates if possible and use positions relative to the WIDTH and HEIGHT constants. This will make conversion between different screen sizes much easier and in many cases wont require any changes to your code at all.

Finally if your app includes Game Center functionality then you need to modify the methods which dismiss the Game Center view controllers to restart the Lua draw() loop.



22.6 Download the Code


You can download the updated code below. For simplicity we have provided all the classes even if they haven't changed from a previous version. You should be able to tell from the version number whether you need to download the code or can use a previous version (v1.6 indicates code that was modified for this tutorial).

Lua Code:
  1. Main v1.6
  2. Cell v1.5
  3. Colors v1.1
  4. Physics v1.0
  5. PhysicsDebugDraw v1.0
  6. RoundBorder v1.2
  7. ScoreScreen v1.0
  8. SplashScreen v1.6
  9. Button v1.3
  10. Fader v1.1
  11. IconImages v1.1
  12. TextBox v1.0
  13. Twinkle v1.1
Runtime (Objective C) Code:

Tuesday, September 11, 2012

Interlude 12 - MineSweeper "Sales"

It has been ten days since MineSweeper was released into the wilds of the App Store so I thought I would provide an update on how downloads are going. In summary, downloads are better than I expected, given it is a clone and there are a bunch of established clones already in the store. Figure 1 shows downloads per day (you will probably have to click on the image to see a version big enough to read). 
  
Figure 1. MineSweeper Downloads By Day.
   
It shows the usual trend with a large hump on the day after release followed by a rapid fall over the next four days. You then enter a gentle oscillating phase which will gradually decline if you do nothing else. The small bump on the far right is the weekend kick which I see on most of my App sales. There is many a developer who saw their second day sales, multiplied this by their App price and then by 365 and thought they were on the road to retirement. Alas, my experience is that the post ten day average is a better indicator of long term sales.
   
Figure 2. MineSweeper Download Detail.
    
My guess is that sales will settle down around 50-100 per day, which if I was making any money from them would be fantastic. It does suggest that it would be worth adding iAds to try and extract some return from the effort of writing the App (apart from the educational benefit of course).
  
Figure 3. Early Ranking Data.
  
Looking at how the App ranks is very interesting and illustrates the mysterious Apple ranking algorithm at work. You can see that rank roughly follows downloads (with a slight lag). However, if we look over the ten day period we see a curious result.
  
Figure 4. Ranking Data at the end of the 10 day period.
      
Figure 4 shows that even though sales are not as high as day 2, the rank has continued to climb suggesting that there is some sort of cumulative effect. There is definitely a feedback mechanism whereby the higher you rank, the higher your downloads, which means the higher you rank, etc. The other observation is that it is easy to make progress when you are ranking between 200-300. Getting sub 100 is tougher and top 10 is virtually impossible (unless you call your App Angry Birds).
    
Figure 5. Downloads by Country.
   
Downloads by country contain a couple of surprises. I always expect to do best in America given it is the biggest English speaking market and the UK at number 3 and Australia at number 5 are equally predictable. Germany at number 2 is unexpected and Russia at number 4 is also a pleasant surprise. So thank you to all the German and Russian downloaders. France and China are normally up there and I don't tend to have a lot of luck with the Canadians. Where this sort of information is useful is in deciding what countries to localise your App for. There isn't a lot of text in MineSweeper so localisation would be a fairly simple matter. I have read that localisation can have a dramatic effect on downloads in that market but I havent tried it myself.
    
Figure 6. Reefwing Software Downloads by Product.
     
Figure 6 graphs downloads by product for all of my Apps over a one month period. You can see that the free Apps (MIneSweeper, Personality, LifeAudit, and Number Converter) dominate downloads, however the majority of my revenue comes from LifeGoals. Note that MineSweeper has only been available for 10 days of the month reported on in Figure 6. I will give another update after MineSweeper has been out for a month.
   
Figure 7. Early MineSweeper Reviews.
    
A factor in driving sales is review comments and the number of stars people rate your App at. Trying to bootstrap these by getting your Mum to write a review is always a strategy but in reality unless you have a LOT of friends in MANY countries, real reviews and ratings will swamp any attempt to game the system. BTW - thanks to all the folks from the Codea forum for their positive reviews!
  
Figure 8. Reefwing Software iAds Revenue.
     
The next step in this grand experiment will be to attempt to convert the MineSweeper downloads into cash. Theoretically, iAds should be a fairly good mechanism for this. However, looking at Figure 8 you will see that my current revenue from iAds in the Personality App can only be called pathetic. 
       
Let's see what MineSweeper can do...

Saturday, August 18, 2012

Tutorial 13 - Submitting to the App Store Part 2

    

13.1 Some Runtime Things to Watch


Further to part 1 of this tutorial, there are a couple of things, which if you are not aware of them, can cause problems with running your Codea App in the runtime.

13.1.1 Using Version in the pList

The runtime expects your version variable to be a string. So if you set a version number in Codea using something like saveProjectInfo("Version", version), where version is a number then you will get an error when you try to compile your project.
  
The solution is to either delete this line in your code or to modify a line in the runtime objective C code. If you want to take the second option then in the CodifyAppDelegate class go to the - (NSString) versionForProjectAtPath:(NSString)path method and change the line:
   
NSString* version = [infoPlist objectForKey:@"Version"];
   
To:
   
NSString* version = [[infoPlist objectForKey:@"Version"] description];

   
If you are using a float to represent your version (e.g. 1.4) then it is likely that within the info.plist file (which is found in the Project.codea folder) that this wont be the number you  are expecting (e.g. you get 1.39999999999234 instead of 1.4). This is due to the size of the memory allocated to store numbers in Codea. Anyway, just edit this in your pList file in Xcode back to what it should be. Note that there are two info.plist files, one for your App (found in classes -> Supporting Files) and one for your Codea project.
    
13.1.2 Supporting Orientations

Another trick for young players is that the function orientationChanged(newOrientation) is called before the setup() function in Main. So if you use global variables which are defined in setup() in the orientationChanged() function you will get a runtime error because they haven't been defined yet.

If you are in this situation, the solution is a three step process:

1. In Main, add the following line before anything else:

setupHasRun = false

-- Other global stuff

2. Then in your setup() function, once your setup is done set the variable to true:
  
function setup()
    
    -- setup stuff
  
  setupHasRun = true
      
end
 
3. Finally in the orientationChanged() function:

function orientationChanged(newOrientation)

    if setupHasRun then
        -- Your existing orientation code
    end

end


And that should sort out the problem. Thanks very much to Simeon from Two Lives Left for tracking this solution down.

     

13.2 Customising the Runtime


     
Before submitting your App to iTunes, there are a few things that you will probably want to change in the runtime. We will look at the simple things in this tutorial, a subsequent tutorial will explore how to add things like iAds and Game Centre support.
   
  Figure 1. Changing your Icon & Launch Images in Xcode.
    
13.2.1 App Icons and Launch Images
      
The runtime comes with some default icons and launch images. The easiest way to replace these with your own is to use Xcode.
     
  1. Select the Project Navigator (top left button in the tool bar) and click on the top most item in the left pane, called Codea Template.
  2. If you then select the summary tab in the right hand (or middle pane depending on how you have Xcode configured) you should see something like Figure 1.
  3. Right click (control click) on any of these images and chose "Select File" to change them to your custom versions.
  4. There is a plethora of app icon and launch image sizes required, but you can get away with just having these six plus the App store image (which we will get to shortly). The iOS Custom Icon and Image Creation Guidelines lists exactly what dimensions are required and the naming conventions for these images.
           
13.2.2 Hiding the Status Bar
        
Note that you should take into account whether you will be showing the status bar when creating your launch images. Xcode will warn you if the dimensions of your selected images are incorrect. You can hide the status bar by setting the "Status bar is initially hidden" to YES in the info.plist file for your App (found in classes -> Supporting Files).
      
Figure 2. iOS Dev Centre Web Site.
          

13.3 The iOS Dev Centre

            
13.3.1 Provisioning Portal
      
For this next step we are assuming that you already have set up a paid developer account with Apple. The good news is that the App provisioning and certification process has gotten much easier than it was, but it is still a bit tricky so we will step you through the correct order in which things need to happen.
       
Figure 3. iOS Provisioning Portal.
                
  1. Open up you favourite browser and go the iOS Dev Centre site (Figure 2). Log in using the Apple ID associated with your Developer Account.
  2. Once you are logged in, at the top right of the iOS Dev Centre page is a set of links grouped as "iOS Developer Program". Click on the link which says iOS Provisioning Profile.
  3. You should already have a development and distribution certificate. If you haven't, the easiest way to do this is to use the Development Provisioning Assistant. You can start this off by clicking on the button which says "Launch Assistant", it is located in the middle bottom of the page (Figure 3).
  4. After you are certified we need to create a new App ID for our App. In the left hand pane of the iOS Provisioning Profile page, click on the link called "App IDs". On the App ID page you will be able to read more than you ever wanted to know about the care and feeding of App ID's. You will also be able to see the status of any App ID's that you currently have in place. Before your eyes glaze over there is one important paragraph buried in there. It starts with "The Bundle Identifier portion of an App ID can be substituted with a wild-card character (asterisk '*') so that a single App ID may be used to build and install multiple applications." We suggest that you don't do this, as wild-card App IDs cannot be used with Push Notifications or for In-App Purchase. 
  5. Click on the "New App ID" button in the top right of the screen, and this will take you to the Create a New App ID page shown in Figure 4. Here you have to answer just three questions but the first time you see this, it can be a bit confusing as to what to do. 
  6. For description we used "MinesweeperAppID". The description is not important, it is just to remind you which App this ID is for. The next question is important. It is to set the "Bundle Seed ID (App ID Prefix)". We suggest using the default "Use Team ID" as the prefix. As mentioned previously don't use a wildcard "*" for the prefix. The last question is used to set the "Bundle Identifier (App ID Suffix)". This needs to be unique for every App and the usual practice is to use a reverse domain name string. In our case we used "au.com.reefwing.Minesweeper". So what if you don't have a domain? It doesn't matter you just need to use something unique - you could perhaps base it on your name (e.g. "davidsuch.YourAppName"). Click on submit and you will be taken back to the previous App ID status screen where you should now be able to see your shiny new App ID.
  7. The last step in this stage is creating our Provisioning Distribution Profile. In the left hand navigation pane click on the "Provisioning" link and then on the Distribution tab. This will show a list of existing Distribution Profiles. Click on the "New Profile" button in the top right. Fill in the details. Distribution method is "App Store", choose an appropriate profile name (we used Minesweeper Distribution Profile), and select the App ID that you just created. Click on "Submit" and you are done.
  8. In the top right you will see a link which says "Go to iOS Dev Centre". Click on that and we will be ready for the next stage.
                
Figure 4. Creating a New App ID.
               
13.3.2 iTunes Connect
             
Figure 5. ITunes Connect.
             
Back at the iOS Dev Centre we now want to prepare our App for upload to Apple for review. We do this by using the link below the Provisioning Portal called iTunes Connect. Click on this now. You will probably be asked to sign in again at this stage. 
            
  1. On the iTunes Connect page (Figure 5) you will see a link to "Manage Your Applications" at the top of the right hand column. Click on this and then click on the "Add new App" button (top left).
  2. You will be asked to select your App type (iOS or Mac OS X). Click on iOS.
  3. Enter your App information. This is pretty self explanatory. For SKU we usually use the date (SKU = stock keeping unit, businesses use them to identify a unique product) and make sure that for Bundle ID you select the App ID created in section 13.3.1 above. The App name that you select will also need to be unique - in our case Minesweeper was taken so we went with iMinesweeper instead. When done click "Continue".
  4. Next select the availability date and price tier. Use the current (default) date if you want your App released as soon as it is approved by Apple. Select some future date if you want to control the release date. This can be handy if you are co-ordinating a massive marketing effort to drive up demand for your App and its first day sales. Selecting anything else but FREE for the price will bring up a matrix showing the price for that tier in different countries. Click "Continue".
  5. Fill out the version information and the age rating survey to determine its rating in the App store (e.g. Minesweeper comes out at a 4+ rating, the lowest possible). Clicking Frequent/Intense for some of these questions will mean that your App can't be sold in selected countries or in some instances at all. Next fill out your App's Metadata, this information will appear in the iTunes App store. The keywords are important because this is how customers will discover your App using the search functionality in iTunes. You are probably okay to go with the standard End User Licence Agreement (EULA), so the last step is to upload the images displayed in the App Store. The large App Icon needs to be 1024x1024 pixels in size. For the iPad screenshots, the easiest way to get these is using Organiser in Xcode (select your iPad from the left hand navigation panel and then screenshots. Run the App on your iPad using Xcode and then click the new screenshot button to take the images you want. You can then export these to a handy folder for upload to iTunes Connect). You can have up to 5 screenshots, drag and drop to change the order. Click "Save" when you are done.
  6. After saving your App data you will be brought to the App summary screen. Check that all the details are correct. The status at this stage should be a yellow "Prepare for Upload". Click the "View Details" button below your App icon, then in the top right hand corner, click on "Ready to upload binary".
  7. Answer the question about cryptography then click "Save" and then "Continue". Your App status will now be a yellow "Waiting for Upload". Click on App Summary and Done and you should see something like Figure 6, and we need to head back to Xcode for the next step.
            
Figure 6. iTunes Connect - Manage Your Apps.
       
13.3.3 Uploading Your Binary
         
In Xcode, we need to associate our App with the bundle ID that we set up in iTunes Connect. 

  1. Before proceeding, we need to download the distribution provisioning profile. In the Organiser window, click on Devices and then Provisioning Profiles in the left hand navigation pane. Click on the Refresh button (bottom left corner) and your new profile will be downloaded.
  2. Select the Project Navigator (top left button in the tool bar) and click on the top most item in the left pane, called Codea Template.
  3. If you then select the summary tab in the right hand (or middle pane depending on how you have Xcode configured) you should see something like Figure 1. Scroll up to the top of the Summary tab. The Bundle Identifier will currently have: com.twolivesleft.Minesweeper. This needs to be changed to the bundle ID that we established in the steps above. In our case we changed it to au.com.reefwing.Minesweeper. Check that the correct version number is listed while you are here.
  4. While we are here we will sign our code with the appropriate distribution profile. Select the "Build Settings" tab and scroll down to the Code Signing section. Under Code Signing Identity -> Release -> Any iOS SDK, select the distribution profile that you just downloaded.
  5. Before uploading we need to recompile the binary with all this good stuff included. Select Product -> Archive from the menu bar. After compiling you should see Archive Succeeded in Xcode and the Organiser window will automatically be presented showing your App Archive (Figure 7).
  6. In Archives (Figure 7), click on the Validate button (top right) and your archive will be validated prior to upload to Apple. At this stage, you may get an error - "No identities are available for signing." If you do, choose the Connect to iOS Dev Centre, "Download Identities" button. You should now be validated, click on "Finish"
  7. Click on Distribute, select submit to the iOS App Store, click "Next" three times and you will see a message "Your application is being uploaded".
  8. If all goes well you will get a "Submission Succeeded" message (Figure 8).
          

  Figure 7. Your App Archive in the Organiser Window.
    
Well done if you made it to this point. Believe it or not, Apple have actually streamlined and automated a number of steps in this process which used to be much more cryptic (especially for your first attempt at it).
  
If you want, you can go back into iTunes connect and validate that your App status has changed to orange and "Waiting for Review". In fact, we would recommend that you do this. There have been instances where people have uploaded their binary but for some reason it never moves on to "Waiting for Review." If this happens you can try rejecting the binary and re-uploading it. It will take about a week for the App to work its way up the review queue. Watch this space for updates...
     


Figure 8. Success! You App has been submitted for Review.