Fade Colors on a Polygon using the Game Loop
2 min read

Fade Colors on a Polygon using the Game Loop

Fade Colors on a Polygon using the Game Loop

One of the features I need to implement is to smoothly change the fill/stroke color of a polygon, like this:

Game loop

A manual approach

In order to better understand the mechanics, I built my own fading PoC.

First, one would need to build the polygon:

hud = display.newGroup()
-- Build the text layer
score = display.newText{ text = "Level: " .. name .. " - ("
  .. "11".. " groups)",
  font = "scene/game/font/Mecha.ttf",
  fontSize = "32" }
snap(score, "topcenter", 8)

-- Build the background
hudBG = display.newRoundedRect(score.x, score.y,
  score.width+64, score.height+32,
hudBG.fill = {0,0,0}

-- Build the view

In this particular case, I have a text field and a rounded rectangle beneath it. the hud group contains both in the right order.

Once the components have been created, we build the function to be called each frame:

hudBG.iterator = 100
hudBG.lastTime = getTimer()

hudBG.enterFrame = function(self)
  local curTime = getTimer()

  local dt = curTime-self.lastTime
  dt = dt / 1000

  self.lastTime = curTime

  if(self.iterator > 0) then
    -- print(1.0/dt)
    self.iterator = self.iterator - 1
    self.fill={1-self.iterator/100.0, 0,0}

The idea is to only perform the loop once, for the first 100 frames. As you can see, the dt velue does not yet enter into play so, the dransition from black to red is not perfectly smooth.

Now, all we need to do is to register the function with the frame event:

Runtime:addEventListener( "enterFrame", hudBG )

Once we do this, the rectangle fades from black to red in 100 frames. At 60fps, this would mean approximately 1.5 seconds.

Constant speed

As mentioned above, the speed of changing the colors is not constant. If e.g. we have a longer delay between frames, there's going to be jitter and the time in which we'll make the transition will vary. To improve this, we put dt in play:

hudBG.iterator = 300
hudBG.duration = hudBG.iterator / 60.0 -- duration
hudBG.progress = hudBG.duration -- decreasing progress
hudBG.isActive = true
hudBG.lastTime = nil

hudBG.enterFrame = function(self)
  local curTime = getTimer()

  if (self.lastTime == nil) then
    self.lastTime = curTime

  local dt = curTime-self.lastTime
  dt = dt / 1000

  self.lastTime = curTime

  if(self.duration >= 0 and self.isActive) then
    self.progress = self.progress - dt

    if (self.progress < 0) then
      self.progress = 0
      self.isActive = false
    -- print(1.0/dt)

This is a bit verbose, but following components can be identified easily:

  1. .duration field which represents the duration in seconds at 60fps
  2. .progress the decreasing value. It will decrease with dt each frame
  3. curtime and .lastTime are used to compute the dt (delta) between frames
  4. if (self.duration ...) block performing:
    • .progress update
    • activation logic
    • the actuall logic (changing the fill colour)

As mentioned above, this variant provides a constant speed of change and better control.

Game loop

Its downside is the estra values and adding the logic of converting the dt into something meaningful.