When you complete a level in a video game, it often shows your score in an animated fashion. Sometimes this involves the numbers “spinning” in such a way that they count up from the 1s column to the 10s column and so on, like an odometer of a car but much faster.

nums

Achieving this effect in Corona begins with some simple math. Let’s jump directly into some code:

local scoreText = display.newText( "", display.contentCenterX, 232, native.systemFontBold, 32 )
local score = 750800

local function lerp( v0, v1, t )
    return v0 + t * (v1 - v0)
end

local function showScore( target, value, duration, fps )
    if value == 0 then
        return
    end
    local newScore = 0
    local passes = duration / fps
    local increment = lerp( 0, value, 1/passes )

    local count = 0
    local function updateText()
        if count < passes then
            newScore = newScore + increment
            target.text = string.format( "%07d", newScore )
            count = count + 1
        else
            target.text = string.format( "%07d", value )
            Runtime:removeEventListener( "enterFrame", updateText )
        end
    end

    Runtime:addEventListener( "enterFrame", updateText )
end

local duration = 2000
local fps = 30
showScore( scoreText, score, duration, fps )

The first thing to understand is that Corona is a frame-based system. This means that the screen updates essentially at a fixed pace. By default, this pace is 30 times per second, usually referred to as 30 frames per second ("fps"). Corona can also run at 60 fps, which many developers choose for a smoother look.

At 60 fps, the screen can only update every 1/60th of a second (16.6667 milliseconds). At 30 fps, the screen can only update every 1/30th of a second (33.3333 milliseconds). Trying to force a faster update rate is an exercise in futility.

Controlling the Rate

Obviously, game scores can vary greatly -- one game may have a maximum score of 12 while another will have a maximum score in the millions. Thus, we don't want our function to count up by a fixed amount like 1 on each frame update. If so, a score in the millions would take too long to update, while a score like 12 would finish far too quickly.

A better solution is to determine how long the animation should run and then calculate the amount that it should increment on each frame update. In this example, the score will have 2 seconds to spin up from 0 to the final value. For an app set to run at 30 fps, this means that there will be 60 total updates.

Determining the Passes and Increment

First we need to figure out how many "passes" are required. This value is simply the time in milliseconds (duration) divided by the frame rate of the app (fps).

Next, we need to determine the amount to increment the score by. This is done via a method called lerp which stands for linear interpolation. This method basically allows you to compute intermediate points between two values based on some criteria.

In our example, the lerp function is straightforward. We take the difference between the start and stop values (v1-v0) and multiply it by the fraction of the time we want to know the increment for -- in this case, 1/passes since we know how many iterations we need over the span 2 seconds. Then we add that to the start value (v0).

The showScore() Function

The actual showScore() function requires four arguments:

  1. The display.newText() text object that will be updated.
  2. The "final value" to show for that text object, for example, the score the player achieved.
  3. The duration of the count-up routine in milliseconds.
  4. The frame rate of the app.

Inside the function, we set a temporary value, newScore, so the score displays to zero. We also use the duration and frame rate to calculate the number of required passes. All of this is computed inside of a Runtime "enterFrame" listener that will fire every frame. Finally, we use a counter to track the number of passes.

When the loop is done incrementing, we need to adjust for any fractional remainder of the final score. This is done simply by setting the text object to the final value.

Conclusion

Hopefully this tutorial gets you started with a classic "spinning score counter" feature for your game. If so inclined, you could easily extend the showScore() function to accept both duration and fps values instead of using the static values for the timers. As usual, the sky's the limit!

  1. Very good tutorial!

    but I believe it’s a bug in this line:

    local passes = duration / fps

    it works if fps is 30 but wont work if fps is other values :) and here is my fixing:

    local passes = duration * fps
    duration is in seconds and fps is in frames per second.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>