Thursday, September 10, 2015

Implementing Circular Countdown Timer with Corona SDK

As part of Today Resolution, I implemented a countdown timer, which I think will be useful in other projects, so I decided to share it here.  But first, this is how it looks.


Now the code


CircularCountdown = {}

local rad = math.rad
local sin = math.sin
local cos = math.cos
local abs = math.abs


CircularCountdown.newCircularCountdown = function(radius, second, callback)
    local _ring = display.newGroup()
    local _ticks = {}
    local _timer = second
    local _label
    local _thandle
    
    
    for i=180-6,-180,-6 do
        local rd = rad(i)
        local sinrd = sin(rd)
        local cosrd = cos(rd)        
        
        local c = display.newRect(sinrd * radius, cosrd * radius, 4, 10)
        c.rotation = -i
        c:setFillColor(0, 0, 0, 0.2)
        _ring:insert(c)
        _ticks[#_ticks + 1] = c
    end
    
    _label = display.newText(""..second, 0, 0, native.systemFont, 96)
    _label:setFillColor(0.3, 0.3, 0.3)
    _ring:insert(_label)
    
        
    local timeStamp, now, dt
    local ms = 0
    local interval
    local i = 0
    
    local sec = 0
    local function update()
        now = system.getTimer()
        dt = now - timeStamp
        timeStamp = now
        
        ms = ms + dt
        if ms > interval then
            i = i + 1
            if _ticks[i] then
                _ticks[i]:setFillColor(0, 0, 0)
            end
            ms = ms - interval
        end
        
        sec = sec + dt
        if sec > 1000 then
            _timer = _timer - 1
            _label.text = "" .. _timer
            callback(_ring, _timer)
            if _timer == 0 then
                Runtime:removeEventListener("enterFrame", update)
            end
            sec = sec - 1000
        end
    end
    
    function _ring:start()
        interval = (second * 1000) / #_ticks
        timeStamp = system.getTimer()
        Runtime:addEventListener("enterFrame", update)
    end
        
    
    return _ring
end


The three parameters, radius, second, and callback are radius of the countdown circle, the second to countdown, and a callback function.  The callback function will be called every second.

Note that the number of second must be at least 2.

This is how I use it.



local cirCountdown = CircularCountdown.newCircularCountdown(100, 5, onCountdown)
cirCountdown.x = display.contentCenterX
cirCountdown.y = display.contentCenterY
cirCountdown:start()

 You can fiind the working demo project at https://github.com/uchat/circularCountdown

Monday, September 7, 2015

Today Resolution is a todo list app

I coded Today Resolution because I found other todo apps try to do too much.  Working as a indie dev, I usually setup a goal for a day (what features I want to implement and finish up today, something like that).  When all done, I can step away from the keyboard without guilt.  I just want an app that lets me list thing I want done in the morning and check them off one by one when they are done.  Nothing more. 

Since I couldn't find app like that, I coded one.  It's quite limited but very easy to use and very fast.

http://todayresolution.com/



Give it a try.  Feedback is very welcome.




Saturday, August 1, 2015

Building a physic-based game with Corona SDK in about 100 lines of code



We will be making is called Apple Rescue. It is a physics-based game where you try to bring the apple to the ground by removing blocks beneath it. Each time the apple falls and hits something, it is damaged, and the game is lost when the apple takes on too much damage. To win the game the you have remove the blocks in the right order.
 

Setting Up the Project

First go to Corona SDK website and download the latest version. Once installed, launch Corona Simulator and click on "NEW PROJECT". In the dialog that appears, enter "Apple Rescue" as your application name. For project template, choose "Blank"



 If you look in the project folder, you will see a bunch of generated files. main.lua is the main file for our game.


Source Code Structure

For simplicity, we will be only adding code to main.lua. For bigger projects, we will need to better organize the code. For now, adding these lines to your main.lua


display.setStatusBar(display.HiddenStatusBar)

local physics = require( "physics" )
physics.start()

local STATE = {wait=0, running=1, won=2, lost=3}

local state = STATE.wait
local doGround, doBackground, doApple, doDamageText

local function gameLost()
end

local function gameWon()
end

local function setupScene()
end

local function makeBlock(image, density, friction, bounce)
end

local function setupBlock(blocks)
end

local function setupApple(apple)
end


local function setupLevel(level)
    setupScene()
    setupBlock(level.blocks)
    setupApple(level.apple)    
end

-- main --

local level1 = {
    blocks = {
        {"block12x1.png", 1, 0.8, 0.1, 195, 295},    
        {"wood4x4.png", 0.5, 0.5, 0.3, 150, 320},
        {"wood4x4.png", 0.5, 0.5, 0.3, 225, 320},
        {"foam4x4.png", 0.2, 0.5, 0.7, 170, 360},
        {"foam4x4.png", 0.2, 0.5, 0.7, 210, 360},
        {"wood4x4.png", 0.5, 0.5, 0.3, 190, 400},
        {"wood4x4.png", 0.5, 0.5, 0.3, 190, 440},
    },
    apple = {
        maxDamage = 5,
        x = 190,
        y = 280
    }

}

setupLevel(level1)
state = STATE.running


This is the skeleton of the game source code. The first line tells the SDK to hide the status bar as we want our game to play in full-screen. Then the physics engine is imported and started. The physics engine must be started before we can use any physics features.

The constants and variables are declared in the next few lines. The variable doGround, doBackground, doApple, and doDamageText will be used to reference the "display objects". Display object is the term used by Corona SDK for any object that can be displayed on screen.

There are 7 functions in this game. gameLost() and gameWon() are called when the player lose or win the game respectively. setupBlock(), setupScene(), and setupApple() are called at the beginning of the game to setup the game level and put display objects on screen.

A level configuration is defined at the bottom. It is a table contains list of block configurations and an apple configuration. Only one level is defined here, but you can add as many levels as you want. To start the level, simply call setupLevel(..) with the level configuration as a sole parameter.
If you wish, you can run current code in Corona Simulator, but you will only see the a blank screen. Let's add background.


function setupScene()

Modify the function setupScene() so it looks like this. The explanation follows in a bit.

local function setupScene()
    doBackground = display.newRect(0, 0, display.contentWidth, display.contentHeight)
    doBackground.anchorX, doBackground.anchorY = 0, 0
    doBackground:setFillColor(177/255, 222/255, 255/255)
    doDamageText = display.newText("damage : 0%",
                    display.contentCenterX, 50, native.systemFontBold, 12 )
    doGround = display.newImage("images/ground.png")
    doGround.kind = "ground"
    doGround.x, doGround.y = display.contentCenterX, 480
    physics.addBody(doGround, "static", { density=1.0, friction=0.3, bounce=0.2 })
end

The blackground is just a blue rectangle. The function display.newRect(..) creates a rectangle display object. Since it need to completely fill the screen, the width and height are set to screen width and height. The constants display.contentWidth and display.contentHeight are just two of many display properties that Corona SDK provides. See here for the completed list.

Notice the proterties anchorX and anchorY? They control the anchor point of the display objects. Their value can be anything from 0 to 1. By default, they are both set to 0.5, which set the anchor point at the very center of the display object. It is very useful when you want to rotate the object as the anchor point is the center of rotation. However, the anchor point also controls where a display object are located when it is first put on screen. In this case, if left at 0.5, the center of our background rectangle will be located at (0,0), which is not what we want. We want the top-left corner to be at (0,0). This is why anchorX and anchorY are both set to 0. Can you guess what would happen if anchorX and anchorY are both set to 1? (answer: the bottom-right of the background rectangle will be at (0,0), so most of the background will be off-screen).

To create a text display object, we call display.newText(..). The anchor point for this text is left at default, this way, when its X axis is at display.contentCenterX the text will always be center-aligned relative to the width of the screen no matter how long the text is.

Adding Physics body

The ground is created next. It will be our first physics-enabled display object. In Corona SDK, attaching physics body to a display object is done by calling physics.addBody(..). This function will automatically determine the width and height of the given display object, and attach appropriate physics body to that object. By default, the physic body will be a rectangle just big enough to contain the given display object, but you can create custom physic body shape if needed, as you will see later in this tutorial.
There are a few types of physic bodies. The default is "dynamic", which means that it will interact with gravity and other forces. However, since we want our ground to stay put, we specify its body type as "static".

      physics.addBody(doGround, "static", { density=1.0, friction=0.3, bounce=0.2 })

A static body type will not interact with gravity. It can still collide with other physics bodies though, which is very useful because we want to know when the apple hits the ground. See here for details on body types.
So far we have added the background and the ground object to the level, it's time to add some blocks.

function setupBlock() and makeBlock()

Modify setupBlock() to looks like this:

local function setupBlock(blocks)
    for i=1,#blocks do
        local block = blocks[i]
        local blockObj = makeBlock("images/" .. block[1], block[2], block[3], block[4])
        blockObj.x, blockObj.y = block[5], block[6]
    end
end

As you can see, setupBlock() just takes the list of block configurations and feed each entry to a function makeBlock(...). Let's define that function now.
 
local function makeBlock(image, density, friction, bounce)
    local rect = display.newImage(image)
    rect.kind = "block"
    physics.addBody( rect, { density=density, friction=friction, bounce=bounce } )
    rect:addEventListener("tap", function(e)
        rect:removeSelf()
    end)
    return rect
end

Function makeBlock(..) expects 4 parameters. They are used to create a display object and an associated physics body. Now, look back at the block configuration list, you should see that different types of blocks have different properties. For example, the bounce value for "foam4x4.png" is much higher that the same values for "wood4x4.png" or "block12x1.png". This is because we want a foam block to be quite bouncy compared to others. See the difference between foam block and wood block in the image below. You can change these values to represent different types of materials. For example, if you want to add a icy block, you can set its friction value to a very low number so it becomes very slippery.


Detecting touches

We want the block to disappear when tapped on. For this, we add the "tap" event listener to each block we create.

    rect:addEventListener("tap", function(e)
        rect:removeSelf()
    end)
The tap event is generated when the player tap on the blocks on screen. The second parameter to addEventListerner() is the callback function, called when the event is generated. When that happens, we simply remove the block from screen by calling removeSelf(), which also remove the physics body attached the block. If you remove blocks at the bottom, expect everything else to fall down!

function setupApple()

Modify setupApple() as follow:

local function setupApple(apple)
    local appleShape = { -5.5,10, 0,11, 5.5,10, 10,1, 7,-6, 0,-7.5, -7,-6, -10,1 }
    doApple = display.newImage("images/apple.png")
    doApple.x, doApple.y = apple.x, apple.y
    doApple.damage = 0
    doApple.maxDamage = apple.maxDamage
    physics.addBody(doApple, { density=1.0, friction=0.3, bounce=0.2, shape=appleShape })
    doApple:addEventListener("postCollision", function(event)
        if event.force < 0.5 or state ~= STATE.running then return end
        doApple.damage = doApple.damage + event.force
        local percent = math.floor(doApple.damage / doApple.maxDamage * 100)
        doDamageText.text = "damage : " .. percent .. "%"
        if percent > 100 then
            gameLost()
            return
        end
        if event.other.kind == "ground" then
            gameWon()
        end
    end)
end
 
Remember about a custom shape for physic body? We need that for the apple. The custom shape is defined in appleShape list, which is a list of coordinates relative to the middle of the apple.

 


Note that a custom shape may have at most 8 points, but you can combine several shapes together to create complex physic body. However, 8 points are enough for this very apple.


Collision Detection

Next step is to detect when the collisions occur. The apple may collide with the blocks or the ground, both will cause damage to the apple. To detect such collisions, the event listener is added to the apple to respond to "postCollision" event. There are many useful properties included in each postCollision event. The one we are interested in is event.force, which basically tell us how hard the apple hits something. Note that the postCollision event is generated even for the smallest collisions, such as when the apple is rolling slowly on the blocks or on the ground. For this game, we ignore the postCollision events when the collision force is too small.

         if event.force < 0.5 or state ~= STATE.running then return end

Note that we also check to make sure that the game is running, since we don't want the apple to take on more damage when the game is over. On another hand, If the force is large enough, it is added to the apple's overall damage.

         apple.damage = apple.damage + event.force

The maximum amount of damage that the apple can take is defined in apple.maxDamage, which is in turn taken from the level's apple configuration. We use this value to calculate the percentage of the damage accumulated so far. The resulted value is then display on screen.

         damageText.text = "damage : " .. percent .. "%"

This value is also used to determine if the damage is too great. If that's the case, the player loses the game.

         if percent > 100 then
            gameLost()
            return
         end
 
Next we check if the object that gets hit by the apple is the ground object. If it is, then the player wins the game.

        if event.other.kind == "ground" then
            gameWon()
        end
 
With this function done, we are almost finish the game. The last two functions are just to inform the player when the game ends.

function gameWin() and function gameLost()

They look like this

local function gameLost()
    state = STATE.lost
    apple:removeSelf()
    local skull = display.newImage("images/skull.png")
    skull.x, skull.y = apple.x, apple.y
    transition.to(skull, {time=2000, y=skull.y - 200, alpha=0})
    damageText.text = "You Lost!\n" .. damageText.text
end

local function gameWon()
    state = STATE.won
    damageText.text = "You Won!\n" .. damageText.text
end
 
When called, they change the state of the game to STATE.lost and STATE.won respectively, so that the apple will not take any more damage. The text also changes to reflect the state of the game.

Display Object Transition

Out of these two functions, the interesting one would be gameLost(). It is there to show how easy to animate the display objects manually in Corona SDK. When the game is lost, the apple is removed from screen and replaced with the image of a skull. It would be boring if the skull doesn't do anything, so we move it up the screen and make it fading out. We do this by apply the transition the the skull.

    transition.to(skull, {time=2000, y=skull.y - 200, alpha=0})

This function takes the display object, and modify its property as specified in the second parameter. In this case, for the next 2 seconds, we want the skull to move 200 pixel up the screen, and gradually reducing its alpha value at the same time. The result is the skull floating up and disappear.
The transition.to (and its sister, transition.from) function can manipulate any property of the display objects. So you can use it to rotate, scale, and most anything you want your display objects to do to make your game interesting. See here for details.

Conclusion

Well, that's it! With Corona SDK, we just create a game in just over 100 lines! It has only one level, but it is easy to add more. If you add features such as level selector or leader chart, you can get pretty close to actually publish this game. For an indie developer, to be able to publish an app in a record time, it is a very good thing. Imagine if you have to do it in Java for Android, and repeat it all over again in Objective-C for iOS devices. Who has time for that?

You can download the whole source code and graphics on GitHub

If you are interested in what else I'm doing with Corona SDK, visit my portfolio at http://www.chupamobile.com/author/chatudom

Wednesday, July 29, 2015

Alien is Landing! Corona SDK game template


This is a small little game I did. It uses code from various projects and turns out to be quite fun.  I put it on codecanyon.com and the folks over there think the source code of the game should sell for $15.

Interested to try it out, download the APK at http://tmeta.com/files/alienLanding.apk

If you like coding, I'm sure you will probably think of a way or two to make it more completed.  Here's something I think of.

  • Make the gun heat up if you fire too much for too long and you will have to wait for it to cool down a bit., 
  • Or maybe you have a limited number of bullets and shooting some types of UFO gives you more bullets.
If you are interested in working on it, it is on sale at http://codecanyon.net/item/alien-is-landing-game-for-android-and-ios/12231856


Thursday, June 11, 2015

A little optimization trick for Corona SDK/lua using first-class function

Many times during development of  my games using Corona SDK I found that I have a function that gets called a lot, and at the beginning of the function I need to check for state of the game.  Something like this:


function listener(e)
     if state == S_PAUSED then return end
    
    -- OK, do real stuff
end


Every time this function gets called, the if statement gets executed, and in this case it is a wasteful since the game is not paused most of the time.  To get rid of this overhead, I often use a feature known as first-class function, which basically means you can treat a function as if it's variable so that you can pass it around or return it from other functions.

So, with first-class function, we can reconstruct our program like this


local listener

function listener_pause(e)
   -- do nothing
end


function listener_ready(e)
   -- OK, do real stuff
end


function setPause(v)
    if v then
        listener = listener_pause
    else
        listener = listener_ready
    end
end

listener = listener_ready


When the program is paused, the function listener_pause() gets called and do nothing, just like the original function.  However, if program is not paused, the function listner_ready() is the one that gets executed, and there is no overhead.

The downside of this method is that your program will be a bit longer, but it might worth it if the function get called a lot, like maybe the one that responds to 'enterFrame' event.

Saturday, June 6, 2015

Puzzle 4 design evolution

I want to do this puzzle for a while.  It's based on my previous Android game called Diamong Drift (no longer on play store since it crashes on newer devices and I can't afford to port it to newer game engine).  I call this new puzzle Puzzle 4.  The coding is going OK, but the design has not been settled yet.  This is what it looks like during the past few weeks.


Diamond Drift was somewhat successful.  Almost 100K download with minimal marketing.  Hopefully people will like Puzzle 4 as well. Puzzle 4 is developed using Corona SDK so it will be available on both Android and iOS devices.

Monday, January 26, 2015

Implementing Explosion with Corona SDK


The gif is about 600K so please wait a bit..

While working on my physics-based game City of Carbon, I needed to implement an explosion that send physic objects flying.  This is what I came up with.    The code for explosion part is below.  If you use it, you will have to change the hardcoded values so it fits your game

Download the completed project at http://tmeta.com/files/ExplosionDemo.zip.





Explosion = {}

local sin = math.sin
local cos = math.cos
local random = math.random
local rad = math.rad

function Explosion:new(x, y)
    local expl = display.newGroup()
    local r = 80
    local angle = 0
    expl.x, expl.y = x, y
    local pdelay = 225/80 * r
    local balls = {}   
   
    function expl:exec()
        for a=0,360,30 do
            local ball = display.newRect(x, y, 10, 10)
            physics.addBody(ball, "dynamic", {density=1, bounce=0.5})
            ball.isBullet = true
            ball.linearDamping = 12 - ((r - 80) / 10)
            ball.gravityScale = 0
            ball.alpha = 1
            local ang = rad(a + angle)
            local f = 2500 / 80 * r
            ball:setLinearVelocity(f * sin(ang), f * cos(ang))
            table.insert(balls, ball)
            self:insert(ball)
           
            if a % 60 == 0 then
                local expl1 = display.newImage("smoke.png")
                self:insert(expl1)
                expl1.rotation = -a + 180
                local s0 = random(1, 10) / 100
                local s1 = random(100, 150/80*r) / 100
                expl1:scale(s0, s0)
                local ra = rad(random(a-20, a+20))
                local r2 = random(r-15, r+15)
                transition.to(expl1, {time=pdelay, x=r2*sin(ra), y=r2*cos(ra), xScale=s1, yScale=s1,
                    onComplete=function(o) o.alpha = 0 end})
            end
        end
        timer.performWithDelay(pdelay, function(e)
            balls = nil
            self:removeSelf()           
        end)
    end
   
    return expl
end
 

return Explosion