Friday, June 29, 2012

Tutorial 4 - A Splash Screen (Updated 17/01/16)

Webcomic Courtesy of Ethanol & Entropy

4.1 Should you do a Splash Screen?


Personally we like adding a splash screen to our Apps. However before starting todays tutorial we should point out Apple's position on this. Quoting from the iOS Human Interface Guidelines:

"Avoid using your launch image as an opportunity to provide:
  • An “application entry experience,” such as a splash screen
  • An About window
  • Branding elements, unless they are a static part of your application’s first screen
Because users are likely to switch among applications frequently, you should make every effort to cut launch time to a minimum, and you should design a launch image that downplays the experience rather than drawing attention to it."
So for all the reasons that Apple don't like a splash screen, we do. There are some lines with Apple that you can't cross and will get you rejected from the App store. This isn't one of those, We have 8 Apps in iTunes and they all have splash screens. So read up on the subject and make your own decision.
Note that even if you decide to toe the Apple line, you can use this class in less controversial instances (eg a win / lose screen or credits).

4.2 Classes You Need to Splash


Most of the classes that we will add are not strictly required to make a splash but when writing a real world App you will probably have something similar so we have included them. There is not a lot of new code required, it is more a case of stitching together what we have already covered. You will need:
If you have been following along then most of these classes will already be in your Menu project. The code for Fader and RoundBorder are shown in Interludes 3 and 4 below. The two classes we need to cover here are the changes to Main and SplashScreen.

4.3 The Main Class


Okay, let's have a look at the revised Main class first.



-- Use this function to perform your initial setup

function setup()
    
    print("Menu & Splash Screen Demo")
    
-- [[ saveProjectInfo("key", "value") allows you to assign metadate to your App. It currently supports the Description and Author keys. The value that you assign to the Description will appear under your App in the Project Browser screen. This block of code is optional but good practise. ]]

    saveProjectInfo("Description", "Menu & Splash Screen Demonstration Code.")
    saveProjectInfo("Author", "Reefwing Software")
    saveProjectInfo("Version", "1.0")
    
    -- [[ The next block of code is also optional. It will keep an eye on our Frames Per Second (FPS) as the App runs. ]]
    
    FPS = 0
    watch("FPS")
    
    timeInterval = 0
    frameCount = 0
    
    -- [[ create a new button which will represent our App menu, it wont be visible until after the splash screen fades away. ]]
    
    button = Button("  Easy Levels  ")
    button.action = function() buttonPressed() end
    
    -- [[ Create the splash screen. The variables passed to the SplashScreen class are text to display and the fading speed of the flash screen. The larger the fading speed the faster it fades. One is the minimum speed. ]]
    
    splashScreen = SplashScreen("Splash Screen Demo", 1)
    
end

-- This function gets called once every frame

function draw()

    -- This sets a black background color 
    
    background(0, 0, 0)

    -- Do your drawing here
    
-- [[ This block of code is optional. It calculates and displays every second the Frames Per Second (FPS) rate that our App is drawing at. ]]

    frameCount = frameCount + 1
    timeInterval = timeInterval + DeltaTime
    
    if timeInterval > 1 then
        FPS = math.round(frameCount / timeInterval)
        timeInterval = 0
        frameCount = 0
    end
    
    -- [[ Draw Splash Screen and when it has finished fading (splashScreen.shown = true) the menu button(s). ]]
    
    if splashScreen.shown == true then
        drawButton()
    else
        splashScreen: draw()
    end
            
end

-- [[ Button related functions - described in earlier tutorials. ]]

function drawButton()
    button.pos = vec2(400, HEIGHT/2)
    button:draw()
end

function buttonPressed()
    
    print("Button Pressed")
    
end

function touched(touch)
    
    button:touched(touch)
    
end

-- [[ This function was written by Vega and is used in the FPS calculation. It can be deleted if you are not including the FPS code. ]]

function math.round(num)
    return math.floor(num + 0.5)
end

4.4 The SplashScreen Class


You can tailor this class to include whatever you want to show on your splash screen. We picked some obvious things like text and a logo. To add a bit more pizzaz we added a rounded border.


SplashScreen = class()

function SplashScreen:init(splashText, fadeSpeed, logoURL)
    
    -- you can accept and set parameters here

-- [[ Those sharp eyed may notice that we only passed in two parameters from our Main class to this class. Consequently Lua will set logoURL to nil. We did this to demonstrate this point. For the tutorial we have hard coded the logo loaded but we will show you in the code below where you can change this. Hopefully most of the variables below are self explanatory. ]]
    
    self.splashText = splashText
    self.fadeSpeed = fadeSpeed
    self.logoURL = logoURL
    self.shown = false

-- [[ We created some colour name variables to make the code more readable. Andrew Stacey has produced a mammoth list of colour names, included in the Roller Coaster example App which comes with Codea. The class is appropriately called ColourNames. You could copy this into your projects if you want to use the names that he has defined. It is overkill for our purposes. ]]
    
    blackColour = color(0, 0, 0)
    redColour = color(255, 0, 46, 255)
    blueColour = color(0, 188, 255, 255)

-- [[ Start by initialising our splash screen border. This class is described in Interlude 4. ]]
    
    splashBorder = RoundBorder(10, 10, WIDTH - 10, HEIGHT - 10, 1, blueColour, blackColour)
    
    -- init Fader
    -- the larger the fade speed the quicker the fade
    
    fader = Fader(fadeSpeed)
    
    if fader.fading == false then
        fader: fade(fadeAnimationDone)
    end
    
-- [[ The next bit of code comes from Simeon's Image IO example project which is included with Codea. In particular the parts related to downloading an image and displaying it. In your App you would more likely use a locally stored image so that it would be displayed regardless of whether you have an internet connection. ]]

     -- Start the logo request, didGetLogo is our callback function
    
    logo = nil
    
-- [[ The next line of code is from the original example and will download the Two Lives Left logo. In a blatant example of self promotion we have replaced this with the Reefwing Software logo. Feel free to insert your own logo URL here. Note depending on the size of the logo used you may have to tweak where it is drawn. To make this class more general and remove the hard coded logo, you can replace this text with self.logoURL. Just make sure you add this variable in the Main class when you initialise the splash screen. didGetLogo is called when the http request finishes. ]]

    --http.request( "http://twolivesleft.com/logo.png", didGetLogo )
    
    http.request( "http://www.reefwing.com.au/Reefwing_Software/Home_files/reefwing-logo-%28black-on-white%29.png", didGetLogo )
    
end

function SplashScreen:draw()
    
    -- Codea does not automatically call this method
    
    pushStyle()
    
-- [[ Set the background black and define the splash text. We haven't drawn it yet. ]]

    background(blackColour)
    font("Arial-BoldMT")
    fill(blueColour)
    fontSize(72)
    textWrapWidth(300)
    textAlign(CENTER)
    
    local w,h = textSize(self.splashText)
    
    -- Detect if the splash screen has started fading.

    if fader.fading == true then
        
-- [[ Start by drawing the border. The order that you draw is important. Drawing this last would make the text and logo hidden. ]]

        splashBorder: draw()
        
        -- Draw the logo we downloaded (when it's ready!)
        
        if logo ~= nil then
            sprite( logo, WIDTH - logo.width/2 - 20, logo.height)
        end
        
        -- Finally draw the text.

        text(self.splashText, WIDTH/2, HEIGHT/2)
        
        fader: draw()
    else
        self.shown = true
    end
    
    popStyle()
end

function fadeAnimationDone()
    
    -- Call back function for Fader complete
    
    print("Splash Screen Fade Complete")
    
end

-- [[ The next bit of code also comes from Simeon's Image IO example project which is included with Codea. ]]

function didGetLogo( theLogo, status, headers )
    print( "Response Status: " .. status )
    
    -- Store logo in our global variable
    logo = theLogo
    
    -- Check if the status is OK (200)
    if status == 200 then
        print( "Downloaded logo OK" )
        print( " Image dimensions: " .. 
                    logo.width .. "x" .. logo.height )
    else
        print( "Error downloading logo" )
    end
end

4 comments:

  1. How would I fade to my actual program after pressing the easy button? Sorry, I'm new to coding and these tutorials have been a great help.

    ReplyDelete
  2. Hi veeeralp - the secret is to use a state machine, have a look at Tutorial 5. In the action method for your button (the one that gets called when the button is tapped), change your gameState variable to the run program state. Then in your draw() method check what state you are in and draw the appropriate screen. The MineSweeper game in Tutorial 6 uses this exact technique. You can download the code for the Main class at https://www.dropbox.com/sh/dtvpqnsvltsqq7c/PnhlMtRnUI/Main.lua

    Let me know if you have any further questions.

    ReplyDelete
  3. Hey David! Finished this one. And at the end I did the same error again, I put button.draw() instead of button:draw Aggg!!!

    I assume the funky line near the beginning: "button.action = function() buttonPressed() end" is referring to something we will do in the next tutorial. Also is this structure normal? Why isn't it: I have seen it a few times already and it looks weird will you be explaining it?

    Thanks Again and Happy New Year!!

    ReplyDelete
    Replies
    1. Hi Josh,

      The funky button action thing is a Lua structure. I haven't seen it in any other language although there are usually other ways to achieve the same result.

      Functions in Lua are values, just as a string and numbers are values. As values, they can be created during runtime, stored in variables, and passed to and returned from other functions. The technical term for this is that they are first class values.

      In this specific example, "button.action = function() buttonPressed() end", what we are doing is assigning the function to be called when the button gets tapped. The groovy thing about this is that we can even change the function that gets called while the App is running (if you needed to). The equivalent structure in Objective C would be a call back or delegate. You need to define this "call back" function in your code somewhere and we usually stick them in the Main() tab. In the example above, the function just prints out "Button Pressed" to the console when the button is tapped.

      function buttonPressed()

      print("Button Pressed")

      end

      To see why this works have a look in the Button class. In the init() part of the code we define a variable called action and assign it to nil, so by default a button does nothing when you tap it. When we create a new button we assign the function that we want called when tapped using the "button.action = function() buttonPressed() end" assignment.

      The magic happens down in the Button:touched(touch) function. Assuming that you are passing your touches through to the button (in the example above we do this in he main touched function using the following, button:touched(touch)), then the button will detect if the touch has ended and is on the button using the hit(touch.x, touch.y) function and then if the action variable contains a function, it calls that function. The code for this looks like:

      if self.action then
      self.action()
      end

      The line, "if self.action then" is equivalent to "if self.action is not equal to nil then". In Lua, both nil and false will act as a false condition when tested. However, unlike c, zero doesn't equate to false in Lua.

      I expand on this first class value concept a bit in Interlude 7 talking about closures (http://codeatuts.blogspot.com.au/2012/07/interlude-7-recursion-closures-in-lua.html), but don't specifically cover it elsewhere, so let me know if it isn't clear and I will provide a more fulsome explanation.

      Delete