Skip to content

Fade Colors on a Polygon using the Game Loop

March 11th, 2018 - SoftwareTutorial(1 min)

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,
  8)
hudBG.fill = {0,0,0}

-- Build the view
hud:insert(scene.hudBG)
hud:insert(scene.score)
view:insert(hud)

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}
  end
end

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
  end


  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
    end
    -- print(1.0/dt)
    self.fill={1-self.progress/self.duration,0,0}
  end
end

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.

HTH,

Share on
Reddit
Linked in
Whatsapp

A little experiment: