Keeping Score

Although our project is more fun to play now, it still isn't a real game. There's not really any point to the gameplay, and the player has to count coins themselves to keep track of their score. So let's build a simple scoring system into our game so the player can see how they are doing.

There are two pieces of information we want to display to the player. We want to show how many coins the player has collected. We also want to show how many coins are available in the level. To do this we will use two global variables which we will call CoinsCollected and TotalCoins. A global variable can be accessed anywhere in our game, by any script. Global variables should only be used when we need multiple scripts to access them, and it doesn't make sense to make them part of an object. Add the following code inside the App:Start() function in the MyCoin.lua script:

--Initialize the 'CoinsCollected' value to 0
CoinsCollected=0

--Initialize the 'TotalCoins' count if it hasn't been already
if TotalCoins==nil then
TotalCoins=0
end

--Increment toe 'TotalCoins" value by 1
TotalCoins=TotalCoins+1

This will set the initial CoinsCollected value to 0. This function will get called once for each coin in our scene, so we need to initially check to see of the TotalCoins variable has already been initialized, and set it to 0 if it has not. Then we will add one to the TotalCoins value. Because this function gets called once for each coin in the scene, the TotalCoins value will end up being equal to the number of coins we can collect. Neat, huh?

Now we need to make it so touching the coin causes the CoinsCollected value to increase by one. This can be done easily by adding this line of code at the end of the Collision() function in the "MyCoin.lua" script:

CoinsCollected=CoinsCollected+1

Your finished "MyCoin.lua" script now looks like this:

function Script:Start()

--Load a sound
self.sound = Sound:Load("Sound/coin.wav")

--Initialize the 'CoinsCollected' value to 0
CoinsCollected=0

--Initialize the 'TotalCoins' count if it hasn't been already
if TotalCoins==nil then
TotalCoins=0
end

--Increment toe 'TotalCoins" value by 1
TotalCoins=TotalCoins+1
end

function Script:Collision(entity, position, normal, speed)

--Make sure the sound exists and play it
if self.sound then
self.sound:Play()
end

--Hide the coin because it has been "picked up"
self.entity:Hide()
CoinsCollected=CoinsCollected+1
end

function Script:UpdateWorld()
s elf.entity:Turn(0,Time:GetSpeed(),0,true)
end

function Script:Release()
--If the sound was loaded, release it now and set the variable to nil
if self.sound then
self.sound:Release()
self.sound = nil
end
end

Finally, we need a way to display the score so the player can see what's happening. We're going to do this by adding a new function in the "MyBallPlayer.lua" script. Open that file and add this code to the end of the script, leaving the existing code in place. The PostRender() function is called after the world is rendered, and allows us to draw 2D text and images on the screen to make a simple HUD (heads-up display). In this case we will just display the number of coins collected and the total number of coins in the map. Make sure you add this code to "MyBallPlayer.lua", not to the coin script:

function Script:PostRender(context)
if TotalCoins~=nil and CoinsCollected~=nil then
context:SetBlendMode(Blend.Alpha)
context:DrawText(CoinsCollected.." / "..TotalCoins,context:GetWidth()-30,4)
end
end

Your "MyBallPlayer.lua" script should now look like this:

function Script:Start()

--Create a camera
self.camera = Camera:Create()

--Update the camera
self:UpdateCamera()
end

--Adjust the camera orientation relative to the ball
function Script:UpdateCamera()
self.camera:SetRotation(45,0,0)
self.camera:SetPosition(self.entity:GetPosition())
self.camera:Move(0,0,-4)
end

function Script:UpdatePhysics()

--Get the game window
local window = Window:GetCurrent()

--Add force to the player when a key is pressed
if window:KeyDown(Key.W) then self.entity:AddForce(0,0,10,true) end
if window:KeyDown(Key.A) then self.entity:AddForce(-10,0,0,true) end
if window:KeyDown(Key.D) then self.entity:AddForce(10,0,0,true) end
if window:KeyDown(Key.S) then self.entity:AddForce(0,0,-10,true) end
end

function Script:UpdateWorld()
--Update the camera each frame
self:UpdateCamera()
end

function Script:PostRender(context)
if TotalCoins~=nil and CoinsCollected~=nil then
context:SetBlendMode(Blend.Alpha)
context:DrawText(CoinsCollected.." / "..TotalCoins,context:GetWidth()-30,4)
end
end

Run the game now and you will see the score update as you collect coins.

Note that we chose to add the HUD code in the player script and not the coins script. This is because there is only one player script, but there may be many coins in the map. It makes more sense to draw the HUD just once rather than drawing it over and over again for each coin in the map. Also note that we used an "if" statement to make sure that CoinsCollected and TotalCoins are not nil values. This will allow the game to run even if there are no coins present in a map.

Finally, let's make our HUD a little more "game-like" by adding a custom font. In the Start function of the "MyBallPlayer.lua" script add this line of code:

self.font = Font:Load("Fonts/Ranchers-Regular.ttf",18)

Now in the same file, replace the Script:PostRender() function with the code below. This will use a larger cartoonish custom font and display it in the upper right corner, using the width of the rendered text to position it precisely:

function Script:PostRender(context)

--Set the font to a cartoonish style
if self.font~=nil then
context:SetFont(self.font)
end

if TotalCoins~=nil and CoinsCollected~=nil then

--Enable alpha blending
context:SetBlendMode(Blend.Alpha)

--Set drawing color to red
context:SetColor(1,0,0,1)

--Combine our variables into some text
local text = "Coins: "..CoinsCollected.." / "..TotalCoins

--Draw the text in the upper right corner of the screen
context:DrawText(text,context:GetWidth()-context:GetFont():GetTextWidth(text)-4,4)
end

--Restore the default font for drawing
context:SetFont(nil)
end

This will give a better appearance, as shown below:

There are many different ways we could have designed this code. We could have declared the CoinsCollected variable in the player script, or made it part of the player object. Game programming is all about coming up with creative ways to get the behavior you want.