RoboCatz.com
About FLL | Navigation | Playbook | Strategy | Models | Techniques | RobotC | Programming | Robot News |

Roblox First Game

This lesson is a continuation of previous lessons in Roblox programming. If you are not familiar with how to create the genericShapes module, please review the other Roblox lessons first. For this lesson, you should start a new project. Create a genericShapes module in your ReplicatedStorage area. Then copy-and-paste the code from the website into the genericShapes module. The source code for the genericShapes module is linked below.

genericShapes Module (genericShapes-ModuleScript.htm)
Copy the code from this page into the genericShapes module in your program.

The next step is to go to the Workspace in Roblox and add a Script. Rename that script: startingScript and paste the code in the example below.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)
We will be working with this starting script.

First Game

Our First Game will involve a simple task, jumping onto objects. Progress will be recorded visually for the user by changing the color of the object as well as displaying a score obtained by doing this task.

A game may become boring if the objects were in the same positions every time you played. So, we will introduce some degree of randomness.

Also, to make the gamer interesting, we will have various levels of difficulty in the task (starting easy and then becoming more difficult over time or more difficult with each level or layer completed).

For our first game, to get started, we will create an empty array to store "spheres". A zOffset will be used to create the collection of spheres in front of the avatar's spawn point.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local spheres = {}
local zOffset = 25
local size = 9
table.insert(spheres, s.sphere({x=0, y=size/2, z=zOffset, size=size }))
In this first game, the layers of difficulty will be rendered as multiple layers of spheres.

A variable will be used to keep track of the current level.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local spheres = {}
local zOffset = 25
local size = 9
local numLevels = 1
local currLevel = 1
table.insert(spheres, s.sphere({x=0, y=size/2, z=zOffset, size=size }))
A for loop will be used to render the different levels. The incrementing variable in the for loop will be called: currLevel.

The first time through the loop, the currLevel will be initialized to the number of levels (stored as a variable: numLevels).

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local spheres = {}
local zOffset = 25
local size = 9
local numLevels = 7
local currLevel = 1
local yCoord = size/2
for currLevel=numLevels,1,-1 do
	for j = 1, currLevel do
		for k = 1,currLevel do
			table.insert(spheres, 
				s.sphere({x=(currLevel-j)*size-((currLevel*size)/2), 
				y=yCoord, 
				z=(k*size)+((numLevels-currLevel)*size/2)+zOffset, 
				size=size })
			)	
		end
	end
	yCoord = yCoord + size/math.sqrt(2)
end
This outer for loop will increment in reverse order (i.e., from the maximum number to 1). This is because a pyramid of spheres will be created starting with the lowest level of spheres as the base. The level with the most spheres will be drawn first. The pyramid will be drawn from the base up. Each subsequent layer will have fewer spheres until the highest layer when there is only 1 sphere.

The y-Coordinate for the spheres will start at 0.5 times the size of the sphere so that the bottom of the sphere will rest on the baseplate. The y-Coordinate will be incremented following the rendering of each layer of spheres. The increment to the y-Coordinate will be the square root of two which is approximately 1.414. This value is used as the increment to the y-Coordinate because a 45-degree angle is formed in stacking the spheres. This 45-degree angle creates an offset on the X and Z axes that have the same value.

The first time through the loop, the currLevel will be initialized to the number of levels (stored as a variable: numLevels).

The table.insert() function above is shown over several lines of text so that it can be viewed in this box without having to scroll horizontally. Please note that the table.insert() begins on one line and ends several lines later.

As an experiment, try changing the numLevels value to view pyramids of different numbers of spheres.


The Y-Coordinate

To find the Y-Coordinate for the spheres, we need to create an algorithm which will enable us to solve the for the Y-Coordinate if given some other data points (such as the X and Z-Coordinates. For example, if we klnow the X and Z coordinates and we know the size of the spheres, we can get the computer to calculate the Y coordinates for us. All we need to do is to express, for the computer, how it should use the variables to find the Y coordinates.

In math, we sometimes express the relationships between variables. For example, consider this math expression:

A = B

If A is 5, then what is the value of B if I know also that A = B?

In trying to find the value of B we need to "solve the equation" for B.

to solve the equation, we often place the variable of interest on the left side of the "equals" sign. Therefore, we would re-write the expression as:

B = A

And then we would substitute the value for A which was previously determined to be 5. Through substitution, our new expression becomes:

B = 5

That was simple. Now let's take this one step further. Consider the expression:

A * A = B * B

This would often be written as:

A2 = B2

If A is 5 then we can solve the equation for B by re-writing it so that B is on the left side and substituting the value of 5 for A as:

B2 = 52

Since 52 is equivalent to: 5 * 5, we can simplify the expression as:

B2 = 25

The next step would be to take the square root of both sides of the equation which would give us:

B = 5

Right Triangle: A right triangle is one with a 90-degree angle.
We can extend our simple: A2 = B2 to include another term: A2 + B2 = C2

As in the previous example, you can solve the equation for a specific term if you know the values of the other terms. For example, if you know the values of A and B, you can solve the equation for C. And if you know the values of A and C, you can solve the equation for B.

From the previous lesson, we learned that Pythagoras discovered in 500 B.C. that the equation: A2 + B2 = C2 accurately describes the lengths of the sides of a right triangle.

Pythagoras: A Greek mathematician that discovered: A2 + B2 = C2 can be used to determine the sides of a right-triangle.
This is the formula for determining the length of a side of a right triangle (if given the lengths of two of the three sides). We will expand this equation to include another term.

When stacking spheres in a pyramid (with a square base), this same formula can be altered slightly to identify the change in height for each layer of the pyramid of spheres. The alteration to the Pythagoras formula is very simple. If the formula: A2 + B2 = C2 works in 2-dimensional space, the formula: A2 + B2 + C2 = D2 will work in 3-dimensional space.

How do we know that this formula will work in 3-dimensional space the same way that Pythagoras' formula works in 2-dimensional space? Simple, if we nullify one of the terms (say for example: C), then the formula becomes: A2 + B2 + 02 = D2 And since 02 is zero the formula becomes: A2 + B2 + 0 = D2 Just drop the zero term and this becomes the same as Pythagoras' formula.

To experiment with right triangles, explore the code below. Change the values of A and B to see different triangles.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local scaleMultiplier = 5
local A = 3
local B = 4

local point1 = { X=-1*B/2*scaleMultiplier, Y=4 }
local point2 = { X=-1*B/2*scaleMultiplier, Y=4+A*scaleMultiplier }
local point3 = { X=B/2*scaleMultiplier, Y=4 }
s.tube( { x1=point1.X, y1=point1.Y, z1=15, x2=point2.X, y2=point2.Y, z2=15, size=.5, thickness=2, color=Color3.new(1, 0.243137, 0.054902), material=Enum.Material.Wood })		
s.tube( { x1=point3.X, y1=point3.Y, z1=15, x2=point2.X, y2=point2.Y, z2=15, size=.5, thickness=2, color=Color3.new(0, 0.333333, 1), material=Enum.Material.Wood })		
s.tube( { x1=point1.X, y1=point1.Y, z1=15, x2=point3.X, y2=point3.Y, z2=15, size=.5, thickness=2, color=Color3.new(0, 1, 0.498039), material=Enum.Material.Wood })
s.drawText({x=point1.X - 2, y=4+A*scaleMultiplier/2, z=15, width=1, height=2, text="A"})
s.drawText({x=0, y=3, z=15, width=1, height=2, text="B"})
s.drawText({x=0, y=6+A*scaleMultiplier/2, z=15, width=1, height=2, text="C"})
3-Dimensional Space:
A2 + B2 + C2 = D2 is used to determine distances in 3-dimensional space.
You may be able to guess that the terms A, B, C correspond to the axes in 3-dimensional space: X, Y, Z. Although we are not measuring the length of a specific triangle, what we are going to measure is the change in the position (distances) of spheres from one layer to the next layer. We will call these changes delta's. So the change in the x-coordinates will be called: deltaX and the change in z-coordinates will be called: deltaZ. The number we are most interested in is the change in the y-coordinate: deltaY. The reason we are most interested in the deltaY value is because the deltaX and deltaZ are already known (i.e., "given").

How do we already know the values of deltaX and deltaZ?

Simple: each higher layer of the pyramid has spheres that are positioned exactly 1/2 of a sphere different from the lower layer. For example, the spheres in layer 2 of the pyramid are placed "inbetween" the spheres of layer 1. In other words, the center of a sphere in layer 2 is 1/2 of the diameter of the sphere different from the center of the sphere in layer 1. Run the program below to visualize this relationship between layer 2 and layer 1.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local spheres = {}
local zOffset = 25
local size = 9
local numLevels = 7
local currLevel = 1
local yCoord = size/2
for currLevel=numLevels,6,-1 do
	for j = 1, currLevel do
		for k = 1,currLevel do
			table.insert(spheres, 
				s.sphere({x=(currLevel-j)*size-((currLevel*size)/2), 
				y=yCoord, 
				z=(k*size)+((numLevels-currLevel)*size/2)+zOffset, 
				size=size })
			)	
		end
	end
	yCoord = yCoord + size/math.sqrt(2)
end
As you explore this Roblox example, notice that each row of spheres in layer 2 is in between two rows of spheres in layer 1. Each column of spheres in layer 2 is in between two columns of spheres in layer 1. Since the spheres in layer 1 are touching each other, the spheres in layer 2 are 1/2 the diameter of a sphere shifted on both the X and Z axes.

Therefore deltaX and deltaZ are the same value. Both are equal to 1/2 the diameter of the sphere. The diameter of the sphere is represented by the variable: size. Both deltaX and deltaZ can be represented by the expression: (size/2)

The equation for determining distances in 3-dimensional space is:

A2 + B2 + C2 = D2

We can now start to fill in the values that we know:

deltaX2 + deltaY2 + deltaZ2 = D2


(size/2)2 + deltaY2 + (size/2)2 = D2

We also know that D in the pyramid of spheres has the same value as the diameter of the spheres (i.e., size). Therefore:

(size/2)2 + deltaY2 + (size/2)2 = (size)2

Now all we have to do is solve for Y to know how high to position the spheres.


Solving for the change in Y (deltaY)

Starting with the formula:

A2 + B2 + C2 = D2

Replace terms with expressions representing the positions of spheres in the pyramid.

(deltaX)2 + deltaY2 + (deltaZ)2 = D2


(size/2)2 + deltaY2 + (size/2)2 = (size)2

Add like terms:

deltaY2 + 2(size/2)2 = (size)2

Place deltaY on one side of the formula.

deltaY2 = (size)2 - 2(size/2)2

Rewrite as:

deltaY2 = (size)2 - 2(size/2)(size/2)

Cancel the "2" as:

deltaY2 = (size)2 - (size)(size/2)

Rewrite as:

deltaY2 = (size)2 - ((size) 2)/2

In order to perform the subtraction, we need to make sure both terms have the same denominator. Multiply the first term by one as:

deltaY2 = (size)2 * 1 - ((size) 2)/2

Rewrite the 1 as 2/2 so that the first term will have a 2 in the denominator:

deltaY2 = (size)2 * 2/2 - ((size) 2)/2

Rewrite as:

deltaY2 = (2(size)2)/2 - ((size) 2)/2

Now you can perform the subtraction:

deltaY2 = (1(size)2)/2

Rewrite as:

deltaY2 = (size2)/2

The expression on the left of the equals sign is still squared. Get the square root of both sides as:

deltaY = size / sqrt(2)

The change in height (deltaY) of each layer of the pyramid is equal to the size of the sphere divided by the square root of 2. In summary, if you are building a pyramid of spheres (with a square base), each layer of the pyramid increases by: size / sqrt(2)

Creating a Playing Surface

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)



local spheres = {}
local zOffset = 25
local size = 9
local numLevels = 3
local currLevel = 1
local yCoord = size/2
s.disc({ x=0, y=0.1, z=0, 
         height=200, thickness=0.1, 
         size=200, RotateBy=Vector3.new(0, 0, 90), 
         Material=Enum.Material.Granite, 
         Color=Color3.new(1, 0.898039, 0.917647)
})

for currLevel=numLevels,1,-1 do
	for j = 1, currLevel do
		for k = 1,currLevel do
			table.insert(spheres, 
				s.sphere({x=(currLevel-j)*size-((currLevel*size)/2), 
					y=yCoord, 
					z=(k*size)+((numLevels-currLevel)*size/2)+zOffset, 
					size=size })
			)	
		end
	end
	yCoord = yCoord + size/math.sqrt(2)
end

Releasing the Spheres

Now the next step is to "release" the spheres onto the world by changing the ".Anchored" property.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local spheres = {}
local zOffset = 25
local size = 9
local numLevels = 7
local currLevel = 1
local yCoord = size/2
for currLevel=numLevels,1,-1 do
	for j = 1, currLevel do
		for k = 1,currLevel do
			table.insert(spheres, 
				s.sphere({x=(currLevel-j)*size-((currLevel*size)/2), 
				y=yCoord, 
				z=(k*size)+((numLevels-currLevel)*size/2)+zOffset, 
				size=size })
			)	
		end
	end
	yCoord = yCoord + size/math.sqrt(2)
end
for i = 1,#spheres do
	spheres[i].Color = Color3.new(1,1,1)
	spheres[i].Material = Enum.Material.SmoothPlastic
	spheres[i].Anchored = false
end

Creating A Score Value

The score will be stored as an attribute of the shape object. The point value attribute is added through a "method" for the shape. The point value will be stored in the shape object and you can get the point value from the shape object using the :GetAttribute() method.

In summary, when storing "user defined" data in an object you will use:
NameOfObject:SetAttribute("NameOfAttribute", ValueOfAttribute) - to store the value in the specified "Attribute".
NameOfObject:GetAttribute("NameOfAttribute") - to retrieve the previously stored value


In our example, to store 1 point in a sphere object, we will use the following statement:
spheres[i]:SetAttribute("PointsAvailable",1)

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local spheres = {}
local zOffset = 25
local size = 9
local numLevels = 7
local currLevel = 1
local yCoord = size/2
s.disc({ x=0, y=0.1, z=0, 
         height=200, thickness=0.1, 
         size=200, RotateBy=Vector3.new(0, 0, 90), 
         Material=Enum.Material.Granite, 
         Color=Color3.new(1, 0.898039, 0.917647)
})

for currLevel=numLevels,1,-1 do
	for j = 1, currLevel do
		for k = 1,currLevel do
			table.insert(spheres, 
				s.sphere({x=(currLevel-j)*size-((currLevel*size)/2), 
				y=yCoord, 
				z=(k*size)+((numLevels-currLevel)*size/2)+zOffset, 
				size=size })
			)	
		end
	end
	yCoord = yCoord + size/math.sqrt(2)
end
for i = 1,#spheres do
	spheres[i].Color = Color3.new(1,1,1)
	spheres[i].Material = Enum.Material.SmoothPlastic
	spheres[i].Anchored = false
	spheres[i]:SetAttribute("PointsAvailable",1)
end

Leaderboard

Under ServerScriptService add a script and rename it: Leaderboard

Your Leaderboard script should contain the following code:

Example:
local Players = game:GetService("Players")

Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local points = Instance.new("IntValue")
	points.Name = "Points"
	points.Value = 0
	points.Parent = leaderstats
end)
This script creates a nameless function inserted directly into the PlayerAdded:Connect() method. Whenever a player is added to the game, this function will be executed which will initialize the player's data on the leaderboard. The initial point value assigned to the player will be zero (0).

If you examine the code above, you will notice that there is a parentheses after the end keyword. This "close" parentheses will close one of the "open" parentheses. Can you tell which one is being closed?

Answer:

Interaction with the User

In order for the script to react to the movements and actions of the Avatar, you need to include the service called "Players"

local Players = game:GetService("Players")

The variable Players can then be used to access the method: GetPlayerFromCharacter

Each object created in the game will include a function that will be triggered whenever something touches the object. Each time something touches a sphere, the sphere's .Touched event will be fired. If you create a function and Connect that function to the .Touched event, the function will be executed whenever something touches the object. Please keep in mind that .Touched is not a method of the object. The purpose of .Touched is to store the function that will be executed when something touches the object. In order to make this work, you need to :Connect() the function to the object. This is done using the method .Touched:Connect(). Then you just need to pass the function as a parameter to this method.

For our game, we will create a function that will be executed whenever something touches a sphere and in the function we will check to see if it was an Avatar "part" that touched the sphere--specifically one of the Avatar's feet. This means that you will need to kick or jump onto the sphere in order to score a point.

Once the Avatar jumps onto the sphere, the "PointsAvailable" attribute of the sphere will be reset to zero (0) so that no one else can obtain points by jumping on that sphere. Resetting the attribute to zero (0) also helps ensure that if you continually jump on the same sphere, the points are not being continually added. Each sphere has just one point that will be awarded to whoever jumps on it.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)
local Players = game:GetService("Players")

local spheres = {}
local zOffset = 25
local size = 9
local numLevels = 6
local currLevel = 1
local yCoord = size/2
s.disc({ x=0, y=0.1, z=0, 
         height=200, thickness=0.1, 
         size=200, RotateBy=Vector3.new(0, 0, 90), 
         Material=Enum.Material.Granite, 
         Color=Color3.new(1, 0.898039, 0.917647)
})

for currLevel=numLevels,1,-1 do
	for j = 1, currLevel do
		for k = 1,currLevel do
			table.insert(spheres, 
				s.sphere({x=(currLevel-j)*size-((currLevel*size)/2), 
					y=yCoord, 
					z=(k*size)+((numLevels-currLevel)*size/2)+zOffset, 
					size=size })
			)	
		end
	end
	yCoord = yCoord + size/math.sqrt(2)
end
for i = 1,#spheres do
	spheres[i].Color = Color3.new(0.898039, 1, 0.788235)
	spheres[i].Material = Enum.Material.SmoothPlastic
	spheres[i].Anchored = false
	spheres[i]:SetAttribute("PointsAvailable",1)
	spheres[i].Touched:Connect(function(hit)
		if spheres[i]:GetAttribute("PointsAvailable") > 0 then
			if hit.Name == "HumanoidRootPart" or hit.Name == "LeftFoot" or hit.Name == "RightFoot" then
				spheres[i]:SetAttribute("PointsAvailable", 0)
				spheres[i].Color = s.randomColor()
				local player = Players:GetPlayerFromCharacter(hit.Parent)
				local leaderstats = player and player:FindFirstChild("leaderstats")
				if leaderstats then
					leaderstats.Points.Value = leaderstats.Points.Value + 1
				end
			end

		end
	end)
end
if workspace:FindFirstChild("Baseplate") then workspace.Baseplate:Destroy() end

Advancing to the Next Game Level

To make the program interresting, the computer will generate varying numbers of spheres at each level. The first game level will start with one layer of spheres containing just one sphere. The second game level will have two layers of spheres which will be a total of 5 spheres. The third game level will have three layers of spheres which will be a total of 14 spheres. Because the pyramid of spheres is square, each layer of the pyramid is a square of some number. For example, the second layer (from the top) has 4 spheres and is the square of 2. The third layer (from the top) has 9 spheres and is the square of 3. The fourth layer (from the top) has 16 spheres and is the square of 4.

Advancing to the next game level will be conditioned upon clearing the game board of any available points. This means that each sphere needs to be cleared of its point value. However, because the spheres will eventually roll off the game board, the computer will also need to check to see if there are any spheres on the board that have available points.

The code excerpt below creates a variable to store the number of spheres on the board. Then it will enter a while loop. The game will remain inside this while loop while there are spheres on the board. Initially, this variable is set to the size of the array of spheres (as referenced by the shortcut: #spheres). The purpose of the task.wait(1) is to allow the players to move around on the game board while the computer is still in the while loop. Without this wait(), the computer would "block" the user from using the game. The task.wait(1) tells the computer to simply wait a small amount of time so that the game players can move their avatars.

A for loop is used to check two important characteristics of each of the spheres: its Y-coordinate and PointsAvailable attribute. If the Y-coordinate is above zero (i.e., the sphere hasn't rolled off the game board) and there is a point available in this sphere, then add 1 to the numSpheresOnBoard variable.

Once there are no more points or spheres available, then the code will exit the while loop.

Example:
-- Check to see if there are any points left to be collected.  
-- If not, then exit the while loop and advance to the next game level
local numSpheresOnBoard = #spheres 
while numSpheresOnBoard > 0 do
	task.wait(1)
	numSpheresOnBoard = 0
	for i = 1,#spheres do
		if spheres[i].Position.Y > 0 and spheres[i]:GetAttribute("PointsAvailable") > 0 then 
			numSpheresOnBoard = numSpheresOnBoard + 1 
		end
	end
end

Complete Game

Once all the spheres/points have been cleared from the game board, the while loop exits and then the gameLevel variable is incremented. But also, the gameLevel variable is checked against the maximum number of levels allowed by using the modulus operator (%). The modulus operator will return a zero if gameLevel equals numLevels. Therefore, just add 1 to the result to ensure that there will always be at least one layer of spheres shown.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)
local Players = game:GetService("Players")

local spheres = {}     -- An empty Array (for the spheres)
local zOffset = 25     -- Offset from the spawn point so that the pyramid is not drawn on the Avatar
local size = 9         -- Size of the spheres
local numLevels = 6    -- Maximum number of levels
local currLevel = 1    -- The current level
local yCoord = size/2  -- Initial y-coordinate
local gameLevel = 1
s.disc({ x=0, y=0.1, z=0, height=200, thickness=0.1, size=200, RotateBy=Vector3.new(0, 0, 90), Material=Enum.Material.Granite, Color=Color3.new(1, 0.898039, 0.917647)})
if workspace:FindFirstChild("Baseplate") then workspace.Baseplate:Destroy() end

-- Enter an invinite loop where the game will last forever
while true do
	yCoord = size/2
	for currLevel=gameLevel,1,-1 do
		for j = 1, currLevel do
			for k = 1,currLevel do
				table.insert(spheres, 
					s.sphere({x=(currLevel-j)*size-((currLevel*size)/2), 
						y=yCoord, 
						z=(k*size)+((numLevels-currLevel)*size/2)+zOffset, 
						size=size })
				)	
			end
		end
		yCoord = yCoord + size/math.sqrt(2)
	end
	for i = 1,#spheres do
		spheres[i].Color = Color3.new(0.898039, 1, 0.788235)
		spheres[i].Material = Enum.Material.SmoothPlastic
		spheres[i].Anchored = false
		spheres[i]:SetAttribute("PointsAvailable",1)
		spheres[i].Touched:Connect(function(hit)
			if spheres[i]:GetAttribute("PointsAvailable") > 0 then
				if hit.Name == "HumanoidRootPart" or hit.Name == "LeftFoot" or hit.Name == "RightFoot" then
					spheres[i]:SetAttribute("PointsAvailable", 0)
					spheres[i].Color = s.randomColor()
					local player = Players:GetPlayerFromCharacter(hit.Parent)
					local leaderstats = player and player:FindFirstChild("leaderstats")
					if leaderstats then
						leaderstats.Points.Value = leaderstats.Points.Value + 1
					end
				end

			end
		end)
	end
	-- Check to see if there are any points left to be collected.  
	-- If not, then exit the while loop and advance to the next game level
	local numSpheresOnBoard = #spheres 
	while numSpheresOnBoard > 0 do
		task.wait(1)
		numSpheresOnBoard = 0
		for i = 1,#spheres do
			if spheres[i].Position.Y > 0 and spheres[i]:GetAttribute("PointsAvailable") > 0 then 
				numSpheresOnBoard = numSpheresOnBoard + 1 
			end
		end
	end
	-- Advance to the next level by adding 1 to the gameLevel
	-- But you should use modulus to check to make sure that it does not go above the maximum number of levels
	-- Because modulus will return a zero if the number is divisible by the modulus value, add 1 to the final result
	gameLevel = ((gameLevel + 1) % numLevels) + 1
end
If you play the game now, using this code, you may notice that at higher gameLevels, the Avatar may meet an unfortunite demise (i.e., crushed by the next pyramid drawn. To help the Avatar survive, add a task.wait(5) to give the Avatar some time to get out of the way. Or, you could just add "5" to the Y-Coordinate of the sphere constructor so that the spheres are drawn 5 roblox unites higher (i.e., above the Avatar).

Also, after the pyramid is drawn, it might be nice to climb onto the pyramid for a few seconds before releasing them by setting their .Anchored property to false. Of course, that only needs to be done if there is more than one layer being shown (i.e., currLevel > 1).

Try to think of where you should make these modifications to the code.

Your Turn


Over the past several weeks, we've explored various small programs as exercises. Revisit some of these earlier programs and turn them into games by adding a Leaderboard, points attribute, and user interaction (i.e., jumpung by the Avatar).

End of Document