[spoiler]To follow this tutorial you will need to download Rinnegatamante's Lua Player Plus Version 5, and will also need some sort text editor or IDE to write your Lua code. I use Zero Brane Studio, you can download both of these for free and I will provide links below. I will also try to provide code examples for each lesson.
Lua Player Plus Version 5: https://github.com/Rinnegatamante/lpp-v ... ita_R5.rar
Zero Brane Studio: https://studio.zerobrane.com/download?not-this-time
Color Avoider Source Code: https://github.com/thesuicidalrobot/Color-Avoider[/spoiler]
What is a variable?
[spoiler]There are 5 types of variables I will like to focus on. First are numbers. Numbers are just as simple as they sound. Despite their simplicity, numbers are the backbone of your game. Numbers dictate where sprites are located, the current frame of animation, movement speed, and even collision.
P.S. Variables cannot contain spaces. I prefer to write my variables in camelCase. This means the first word is lowercase and any words that appear later will have their first letter capitalized.
Code: Select all
number1 = 1
number2 = 1.58
number3 = .48
Code: Select all
string1 = "Hello"
string2 = "123 ABC"
string3 = "?><|{}"
Code: Select all
statement1 = true
statement2 = false
Code: Select all
playerObj = Player:New()
enemyObj = Enemy:New()
Code: Select all
table1 = {"string1", "string2", "string3"}
table2 = {15, 78.659, .39}
What is a comment?
[spoiler]Comments are lines of code that do not get executed by the Lua interpreter. Comments are used to leave notes to yourself, or other coders so they can quickly learn what your code is supposed to do. Without a comment, other programmers will have to go line by line to figure out what your code actually does. The other use for comments is to nullify your code without erasing it. In Lua comments are initiated by -- and will continue until the line is finished. You also have block comments which are initiated by --[[ and closed by --]].
Code: Select all
-- This is a line comment and ends on the next line.
--[[
This is a block comment and
can exist on as many lines as it wants.
]]--
What is a condition statement?
[spoiler]Condition statements are the logic of our code. The most basic condition statement is an if statement. If statements are declared by writing the word "if" followed by a set of parenthesis (inside the set of parenthesis will be the statement we are checking to be true), followed by the word then. We close the if statement with the word end. Between the "then" and the "end" is the code we are going to execute if our condition statement is met.
Code: Select all
if (this is true) then
-- the code in this condition statement will be executed if the above statement is true
end
-- Operator "==" checks if the variables on both sides of the equation are equal to each other.
-- Operator ">" checks if the variable on the left is greater than the variable on the right.
-- Operator "<" checks if the variable on the left is less than the variable on the right.
-- Operator ">=" checks if the variable on the left is greater or equal to the variable on the right.
-- Operator "<=" checks if the variable on the left is less than or equal to the variable on the right.
if (variable1 == 5) then
variable1 = 0;
end
Code: Select all
if (x == 12) then
-- execute this code
elseif (x == 75) then
-- execute this code
else
-- execute this code
end
P.S. You can end a loop prematurely with the word "break".
Code: Select all
-- In the loop below i is are variable we have declared and it starts with the value of 0.
-- The second part of the loop checks if the value of our declared variable is equal to 10. If the condition is not met it will then execute the code inside of the loop.
-- The last portion then adds a value of one to our "i" variable.
-- This process will repeat until the condition is met.
for i = 0, 10, 1 do
end
-- This function does the same as above, but instead of adding one to our "i" variable, it subtracts one.
for i = 10, 0, -1 do
end
While loops are similar to for loops, but has one major difference, the code inside the statement executes until the condition inside is no longer true. A single while statement will essentially act as the engine executor to our game.
Code: Select all
-- This loop will continue until our variable is no longer equal to four.
while (variable1 == 4) do
end
Understanding Tables
[spoiler]I chose to give tables their own section, since they are probably the hardest to understand concept out of the rest of our variables. Tables are also the key to managing dynamically created object instances.
Tables are essentially the same thing as arrays, which are what they are usually called in other languages, except that the index of a table starts at 1 instead of 0. This may sound a bit confusing, but hopefully once this section is over you will know exactly what I mean.
From the variable section you should already know how to create a table, but you don't really know how to access the information inside of the table.
Code: Select all
table1 = {"apple", "orange", "pear", "strawberry"}
-- table1[1] is "apple"
-- table1[2] is "orange"
-- table1[3] is "pear"
-- table1[4] is "strawberry"
There are two types of for loops you can use to search through an table.
Code: Select all
table1 = {"apple", "orange", "pear", "strawberry"}
for i , value in ipairs(table1) do
-- Scans through the table in the order of apple, orange, pear, strawberry
end
for i = #table1, 1, -1 do
-- Scans through the table in the order of strawberry, pear, orange, apple
end
for i = 1, #table1, 1 do
-- Scans through the table in the order of apple, orange, pear, strawberry
end
What is a function?
[spoiler]Functions are procedures that perform actions. It is best to keep your functions limited to one action, so that way it is easier to manage your code and make changes. I prefer to name my functions logically, so I do not have to write too many comments in my code.
Functions are responsible for all game logic, they hold our condition statements, and can even be used to create new game objects. It is important that you have a good grasp on all of the previous categories because things are about to pick up pretty fast from this point.
You write a function by first writing the word function followed up by the function name followed by a set of parenthesis "()". You close your function by writing 'end' below your function.
Code: Select all
-- This is a basic function.
function Function1()
end
Code: Select all
-- Creates x variable
x = 0
function AddOneToX()
-- Adds a value of one to the "x" variable
x = x + 1
end
Code: Select all
x = 0
function AddOneToX()
x = x + 1
end
-- Calls function
AddOneToX()
Code: Select all
x = 0
-- This would throw an error
AddOneToX()
function AddOneToX()
x = x + 1
end
P.S. variables that are created inside of functions and variables that are created as parameters cannot be accessed outside of the function it was created in.
Code: Select all
x = 7
function AddValue(number)
-- Adds the value of the number passed to our x variable
x = x + number;
end
-- Passes a value of 5 to our function
AddValue(5)
-- Passes a value of 1 to our function
AddValue(1)
Code: Select all
x = 0
y = 0
function GetNewNumber ()
-- This function returns a value of 4
number = 4
return 4
end
function CheckValueOfX ()
--[[
This function checks if the value of x is equal to 7, and if this is true this function ends. If this is false, we add a value of one to our "x" variable.
]]==
if (x == 7) then
return;
end
x = x +1;
end
-- "y" will equal the return value, which is 4
y = GetNewNumber()
CheckValueOfX()
What is a class?
[spoiler]Classes are the files that will hold the variables and functions of your objects. Lua does not have a built in class system, so I will teach you how to build classes, which will make it easier to manage all of our game objects. Our classes do not have represent visual game objects on the screen, they can also hold abstract information that the player may never see, such as game rules or other behind the scenes mechanics. Our Index class is actually one of those abstract classes. The Index class itself is not a physical game object on screen, but instead starts and updates our game.
The first thing you need to do to add a class to your game is including it in your index class. We do this with the require function. I like to add these functions to the top of my index file.
Code: Select all
-- We do not add the Lua extension to the function.
-- I like to keep all of my script files, except for my index file in a script folder.
require("app0:/scripts/Class")
The code below is an example on how to build a basic class.
Code: Select all
-- Sets the table for the class
Class = {}
-- This function creates an instance of this class
function Class:New()
-- These 3 lines below set the instance of the actual class.
class = {}
setmetatable(class, self)
self.__index = self
-- You create variables for the instance of the variable by writing the name of the instance followed by a period and the variable name.
class.variable1 = 4
class.variable2 = "hello"
-- We usually want to attach our instances to a table in your index, so we use a return function to attach this instance to that variable.
return class
end
This is our index class, and starting point of our program.
Code: Select all
class1Table = {}
function CreateClass1()
-- Creates an instance of the Class1 object
table.insert(class1Table,#class1Table + 1,Class1:New())
end
CreateClass1()
while (true) do
for i, value in ipairs(class1Table) do
// Executes the Update function of each Class1 instance
class1Table[i]:Update()
end
end
Code: Select all
-- This variable is global, and can be easily accessed outside of the class.
-- This table contains the actual class. The name of the .lua file should match the name of this table.
Class1 = {}
-- All functions in the class should start with name of the classes table followed by a colon(:) and then the name of the function
function Class1:New()
-- This table will hold all of the variables for the instance of the class
class1 = {}
setmetatable(class1, self)
self.__index = self
-- This variable is local
-- Adding the table name followed by a period before a variable will make that variable local to class
class1.variable1 = 1
end
function Class1:Count()
-- Self is used as a prefix to reference a variable of an instance
self.variable1 = self.variable1 + 1
end
function Class1:Update()
-- Self is used as prefix to reference a function of an instance
self:Count()
end
What is a game loop?
[spoiler]The game loop is what updates our game logic, and redraws our sprites on screen each frame. We achieve this constant update by creating a while loop. We use a while loop over any other loop because it will repeat infinitely if its conditions are never met.
If we leave our game logic in just the while loop it will update as fast as our processor will allow, which can result in an extremely erratic frame rate, which may make our game unplayable. To get around this we will use LPP’s timer class and set a simple if statement to check the amount of time passed since the last update. Most gamers prefer their game to update 60 times per second or 60 fps, which means every .0167 seconds our game loop must update each object on screen. At the end of the if statement we want to make sure we reset our timer.
The first thing we update in our game loop is our controller class. Our controller is our exception to the rule of the game timer. We want our controller to update as often as possible, and catch every player input, so we will leave our controller update outside of our timer condition statement. If we place our controller logic in our timer condition, it can result in missed inputs and sticky/delayed controls.
Below is an example of a simple 'game loop' featuring the Lpp timer.
Code: Select all
-- Creates a new timer named timerObj at the start of our program
timerObj = Timer.new()
x = 0
function Update()
x = x + 1
end
-- Creates an infinite loop
while(true) do
-- Checks if enough time has passed to update game logic
if(Timer.getTime(timerObj) >= 16.7) then
-- Resets the timer, always reset the timer before executing any game logic, since executing game logic can take a lot of time, and you want the timer to execute again as soon as possible. If you reset the timer after your logic has executed, it may result in low fps.
Timer.reset(timerObj)
-- Calls the update function
Update()
end
end
How to draw sprites and objects on screen?
[spoiler]Now that you understand what makes a game work, we must draw all of this logic on screen. You cannot have a game without some kind of graphical feedback for the player. I hold the belief that no matter how powerful the system you are developing on, we must always conserve our resources, especially our precious ram. Because memory is always scarce, I recommend keeping all of your sprites and objects on as few sprite sheets as possible. This will ensure that blank spaces on your sprite sheets will be minimal, and you hopefully will not have to worry too much about managing your ram (unless you are making a huge game).
Before we start drawing images from our sprite sheets I want to discuss using LPP built in shape drawing functions.
Code: Select all
function Draw ()
-- Starts the drawing process, this must be called every time before you start drawing on the screen.
Graphics.initBlend()
-- This clears the last before we start drawing the new location of our sprites
Screen.clear()
-- This draws a red filled rectangle. Its top left corner is at (20, 100), top right at (50,100), bottom left at(20,130) and bottom right at (50,130)
Graphics.fillRect(20, 50, 100, 130, Color.new(255,0,0))
-- This draws a red outline rectangle. Its top left corner is at (300, 100), top right at (330,100), bottom left at(300,130) and bottom right at (330,130)
Graphics.fillEmptyRect(300, 330, 100, 130, Color.new(255,0,0))
-- This function draws all of our sprites on the screen.
Screen.flip()
-- This ends our drawing process, we want to make sure we call this or else we can crash our game.
Graphics.termBlend()
end
Draw()
There a few other built in drawing functions, such as drawing a line, circle, or a pixel, but I don't really care to cover it since it is not much different than drawing our rectangles. Instead, I would like to move onto loading sprites and drawing sprites. As I have stated before, it is best to keep the amount of image files to a minimum, so you should keep as many sprites on 1 sheet as possible. Sprite sheets should also be squares and powers of 2. Ex:(128x128, 256x256, 512,512, 1024x1024, 2048x2048). The vita may be able to handle 4096x4096, but I am unsure about the its limitations.
My next example is going to use 2 hypothetical sprite sheets. We will call them spritesheet1.png and spritesheet2.png. Spritesheet1.png will contain only 1 image (so it is not technically a true sprite sheet, but for the sake of an example just go with it), while spriteshet2.png will contain multiple frames and objects. First thing we have to do is load the image files. We only have to do this once, and as long as your game does not contain too many sprite sheets, we can actually load them all at the start of our game. If your game contains too many sprite sheets, then you would have to use some sort of library system to load sprite sheets when needed, which I will not be going over in tutorial.
Code: Select all
-- These 2 functions will load both of image files.
spriteSheet1 = Graphics.loadImage("app0:/spritesheet1.png")
spriteSheet2 = Graphics.loadImage("app0:/spritesheet2.png")
function Draw()
Graphics.initBlend()
Screen.clear()
-- This will draw the entire image file of spritesheet1.png. The Images top left corner would be placed at (400, 250).
Graphics.drawImage(400, 250, spriteSheet1)
-- This will draw a portion of our image file on screen.
Graphics.drawPartialImage(100, 100, 0, 0, 32, 32, spriteSheet2)
Screen.flip()
Graphics.termBlend()
end
Draw()
How to animate our sprites?
[spoiler]So now we drew our graphics on screen, but there is no life to them, they are boring. Animation is going to take a combination of our previous topics. For this example our imaginary sprite sheet will feature 4 frames on a 128x32 sprite sheet. First thing we are going to do is declare a variable for our game objects current frame.
In our draw frame we will update the current frame count. Each if condition statement will check for the current frame count. If our currentFrame variable meets the requirement it will draw the frame.
At the bottom of our Draw function is a check which will reset our frame count if our currentFrame variable exceeds our given number. This will ensure that our animation will loop, instead of ending on the last frame.
Code: Select all
Player = {}
image = Graphics.loadImage("app0:/playerSprite.png")
function Player:New()
player = {}
setmetatable(player, self)
self.__index = self
player.x = 0
player.y = 0
player.currentFrame = 0
return player
end
function Player:Draw()
self.currentFrame = self.currentFrame + 1
Graphics.initBlend()
Screen.clear()
if(self.currentFrame == 1) then
Graphics.drawPartialImage(self.x, self.y, 0, 0, 32, 32, image)
end
if(self.currentFrame == 2) then
Graphics.drawPartialImage(self.x, self.y, 32, 0, 32, 32, image)
end
if(self.currentFrame == 3) then
Graphics.drawPartialImage(self.x, self.y, 64, 0, 32, 32, image)
end
if(self.currentFrame == 4) then
Graphics.drawPartialImage(self.x, self.y, 96, 0, 32, 32, image)
end
if(self.currentFrame >= 4) then
self.currentFrame = 0
end
Screen.flip()
Graphics.termBlend()
end
How to play sounds effects and music?
[spoiler]LPP makes playing sounds and music rather simple. We will be loading and playing our imaginary song for our game. I will not go in-depth into this, since it is not much different from drawing sprites on screen.
Code: Select all
musicFile = Sound.openMp3("app0:/song.mp3")
Sound.init()
function PlaySong()
-- This would loop our song infinitely
Sound.play(musicFile, LOOP)
-- This would play the song one time
Sound.play(musicFile, NO_LOOP)
-- Integers can be used as well to play sounds and music at a set number of times
Sound.play(musicFile, 4)
end
Putting it all together
[spoiler]Now that you have went through all of the tutorials, we will make a small game that will take advantage of what we learned. The game will be a small avoider game, where the player must change the color of their character to avoid the incoming objects. I will not go line by line breaking down this code, but instead just post the code samples of each class and explain the purpose of each function. From what you have learned previously, you should be able to decipher the code.
The first class that I am going to focus on is our 'Index' Class. The Index class is the starting point of our program, and will be responsible for updating our game loop, and drawing our sprites.
The StartGame Function does exactly what it says, it starts the game. It adds the Game Controller, Player, Controller Listener, and Score Display objects to the our game. The UpdateGame Function is responsible for updating all of our game logic. The Draw Function is responsible for drawing all of the sprites on screen. The while loop at the bottom of the class is our actual game loop. The first function call is our Input Listener (since input detection should be done first), next is our Draw function, and inside of an if statement is our UpdateGame Function.
Code: Select all
-- Loads all Lua files that required to play our game
require("app0:/scripts/Player")
require("app0:/scripts/Enemy")
require("app0:/scripts/Controller")
require("app0:/scripts/GameController")
require("app0:/scripts/Score")
require("app0:/scripts/Pause")
require("app0:/scripts/GameOver")
-- Sets the CPU speed of our game to the max clock speed
System.setCpuSpeed(444)
-- initializes our sound engine
Sound.init()
-- loads our song
song = Sound.openMp3("app0:/CityLights.mp3")
-- plays the song and loops it forever
Sound.play(song,LOOP)
-- Loads our font
TextFont = Font.load("app0:/Fonts/6809chargen.ttf")
-- A list of tables, each one will hold specific game objects
playerObj = {}
enemyObj = {}
pauseObj = {}
gameOverObj = {}
gameControllerObj = {}
controllerObj = {}
scoreObj = {}
-- Our timer which will control our framerate
timerObj = Timer.new()
function StartGame()
-- Creates our controller, player, game controller, and score game objects and adds each to their table
table.insert(controllerObj,#controllerObj + 1,Controller:New())
table.insert(playerObj,#playerObj + 1,Player:New(450,300))
table.insert(gameControllerObj, #gameControllerObj + 1, GameController:New())
table.insert(scoreObj, #scoreObj + 1, Score:New())
end
function UpdateGame()
--Updates for all game logic
for i, value in ipairs(pauseObj) do
isDead = pauseObj[i]:Update()
-- Checks if our pause screen should close
if(isDead == true) then
-- Removes the pause screen
table.remove(pauseObj,i)
end
return
end
for i, value in ipairs(gameOverObj) do
isDead = gameOverObj[i]:Update()
if(isDead == true) then
table.remove(gameOverObj, i)
for y, value in ipairs(gameControllerObj) do
gameControllerObj[y]:ResetGame()
end
end
return
end
for i, value in ipairs(playerObj) do
-- The Update for all player objects
playerObj[i]:Update()
end
for i = #enemyObj, 1, -1 do
-- Update for all enemy objects, it also returns the alive state of the object. If the returned value is true, it removes the object.
isDead = enemyObj[i]:Update()
if(isDead == true) then
for y, value in ipairs(gameControllerObj) do
gameControllerObj[y]:AddToScore()
end
table.remove(enemyObj, i)
end
end
for i, value in ipairs(gameControllerObj) do
-- Updates our game logic controller.
gameControllerObj[i]:Update()
end
for i, value in ipairs(controllerObj) do
-- Gets the "was the button was pressed on the current frame" boolean value
controllerObj[i]:GetCanPresses()
end
end
-- Draws all of the objects on screen.
function Draw()
-- Starts the drawing process. This function must be called to draw anything to the screen
Graphics.initBlend()
-- Clears the screen of anything drawn from the last cycle.
Screen.clear()
-- Draws the player
for i, value in ipairs(playerObj) do
playerObj[i]:Draw()
end
-- Draws the enemies
for i, value in ipairs(enemyObj) do
enemyObj[i]:Draw()
end
-- Draws the score keeping object
for i, value in ipairs(scoreObj) do
scoreObj[i]:Draw()
end
-- Draws the pause screen
for i, value in ipairs(pauseObj) do
pauseObj[i]:Draw()
end
-- Draws the gameover screen
for i, value in ipairs(gameOverObj) do
gameOverObj[i]:Draw()
end
Screen.flip()
-- Ends the draw the process for the current cycle
Graphics.termBlend()
end
StartGame()
while(true) do
for i, value in ipairs(controllerObj) do
-- Gets button presses and releases
controllerObj[i]:GetButtonPresses()
end
Draw()
-- Checks if enough time has passed to update game logic
if(Timer.getTime(timerObj) >= 16.7) then
-- Resets the logic timer
Timer.reset(timerObj)
-- Executes the game logic update loop
UpdateGame()
end
end
The CanGetPresses function checks for the "first frame press" of the button. It checks if a button press is true, if the press is true, then we set the "canBePressed" to false, if not then they are set to true. As you may notice, there is no Update function in this class, this is because we run the GetButton function in the beginning of each update cycle, and the CanGetPresses function at the end of of each frame.
Code: Select all
Controller = {}
function Controller:New()
controller = {}
setmetatable(controller, self)
self.__index = self
controller.leftIsPressed = false
controller.rightIsPressed = false
controller.upIsPressed = false
controller.downIsPressed = false
controller.squareIsPressed = false
controller.triangleIsPressed = false
controller.circleIsPressed = false
controller.crossIsPressed = false
controller.startIsPressed = false
controller.squareCanBePressed = false
controller.crossCanBePressed = false
controller.circleCanBePressed = false
controller.triangleCanBePressed = false
controller.startCanBePressed = false
return controller
end
function Controller:GetButtonPresses()
-- Check if buttons are pressed if they are then it sets the boolean to true, if buttons are released it sets the boolean to false
if(Controls.check(Controls.read(),SCE_CTRL_UP)) then
self.upIsPressed = true
else
self.upIsPressed = false
end
if(Controls.check(Controls.read(),SCE_CTRL_DOWN)) then
self.downIsPressed = true
else
self.downIsPressed = false
end
if(Controls.check(Controls.read(),SCE_CTRL_LEFT)) then
self.leftIsPressed = true
else
self.leftIsPressed = false
end
if(Controls.check(Controls.read(),SCE_CTRL_RIGHT)) then
self.rightIsPressed = true
else
self.rightIsPressed = false
end
if(Controls.check(Controls.read(),SCE_CTRL_CROSS)) then
self.crossIsPressed = true
else
self.crossIsPressed = false
end
if(Controls.check(Controls.read(),SCE_CTRL_TRIANGLE)) then
self.triangleIsPressed = true
else
self.triangleIsPressed = false
end
if(Controls.check(Controls.read(),SCE_CTRL_SQUARE)) then
self.squareIsPressed = true
else
self.squareIsPressed = false
end
if(Controls.check(Controls.read(),SCE_CTRL_CIRCLE)) then
self.circleIsPressed = true
else
self.circleIsPressed = false
end
if(Controls.check(Controls.read(),SCE_CTRL_START)) then
self.startIsPressed = true
else
self.startIsPressed = false
end
end
function Controller:GetCanPresses()
-- The can be pressed ensures that an action is only done once per button press, instead of each frame the button is pressed.
-- In some cases you will want an action to be executed each frame of a button press such as in movement, which is why we are not checking for those button presses.
if(self.squareIsPressed == false) then
self.squareCanBePressed = true
else
self.squareCanBePressed = false
end
if(self.crossIsPressed == false) then
self.crossCanBePressed = true
else
self.crossCanBePressed = false
end
if(self.circleIsPressed == false) then
self.circleCanBePressed = true
else
self.circleCanBePressed = false
end
if(self.triangleIsPressed == false) then
self.triangleCanBePressed = true
else
self.triangleCanBePressed = false
end
if(self.startIsPressed == false) then
self.startCanBePressed = true
else
self.startCanBePressed = false
end
end
The Move function of this class is pretty straight forward. We check if our pad booleans are true, if they are the player will move accordingly. I have used 4 individual if statements instead of one long elseif statement so our player can move diagonally as well. This could also be done by creating 2 separate conditional statements, once involving vertical movement, and another for horizontal. After those 4 if statements, we have 4 more if statements that keeps our player sprite in bounds. We use 4 if statements, instead of 1 elseif statement, since it may be possible that our player can be out of bounds on the x and y axis at the same time. This could also be achieved in 2 separate conditional statements using if and else if for both vertical and horizontal checks.
The ChangeColor function checks the button presses of our Controller class. If the isPressed and canBePressed boolean of the pressed button both equals true we change the string name. This will then change the color of the player when the Draw function is called.
The CheckPlayerCollision checks our players collision against the enemies. We create a loop that cycles through each enemy on screen. Inside of that loop is a function that returns the values of our enemies collision points and color through our enemies GetDimensions function (I will go over this function in the enemy section). We then take those values and compare it against our collision points and colors. If the colors do not match and contact is made, we kill the player and show the game over screen.
The PauseGame function first checks if a pause screen is currently in existed, if this is true it returns the function, and if false it creates a new pause screen.
Code: Select all
Player = {}
PlayerSpriteSheet = Graphics.loadImage("app0:/Sprites/Player.png")
function Player:New(xPos,yPos)
player = {}
setmetatable(player, self)
self.__index = self
player.x = xPos
player.y = yPos
player.isAlive = true
player.currentColor = "red"
return player
end
function Player:Move()
-- Gets the list of controller objects
for i, value in ipairs(controllerObj) do
-- Checks if dpad buttons are pressed, if any are pressed it will change our x or y variable.
if(controllerObj[i].upIsPressed == true) then
self.y = self.y -4
end
if(controllerObj[i].leftIsPressed == true) then
self.x = self.x - 4
end
if(controllerObj[i].rightIsPressed == true) then
self.x = self.x + 4
end
if(controllerObj[i].downIsPressed == true) then
self.y = self.y + 4
end
-- Check if player is off the screen to the left, if true keeps the player in bounds
if(self.x < 0) then
self.x = 0
end
-- Check if the player if off the screen on the right, if true keeps the player in bounds
if(self.x > 928) then
self.x = 928
end
-- Check if the player is off the screen on the top, if true keeps the player in bounds
if(self.y < 0) then
self.y = 0
end
-- Check if the player is off the screen on the bottom, if true keeps player in bounds
if(self.y > 512) then
self.y = 512
end
end
end
function Player:ChangeColor()
-- Changes the color of the player based off the button presses
for i, value in ipairs(controllerObj) do
-- If triangle is pressed the player will turn red
if(controllerObj[i].triangleIsPressed == true and controllerObj[i].triangleCanBePressed == true) then
self.currentColor = "red"
end
-- If circle is pressed the player will turn yellow
if(controllerObj[i].circleIsPressed == true and controllerObj[i].circleCanBePressed == true) then
self.currentColor = "yellow"
end
-- If cross is pressed the player will turn green
if(controllerObj[i].crossIsPressed == true and controllerObj[i].crossCanBePressed == true) then
self.currentColor = "green"
end
-- If square is pressed the player will turn blue
if(controllerObj[i].squareIsPressed == true and controllerObj[i].squareCanBePressed == true) then
self.currentColor = "blue"
end
end
end
function Player:CheckCollision()
for i = #enemyObj, 1, -1 do
-- Gets the dimension and color of the current enemy object and adds them to the table in this order left, right, top, bottom, and color
self.dimensions = enemyObj[i]:GetDimensions()
-- compares the player right side to the enemies left side and then compares the players left side to the enemies right.
if(self.x + 32 > self.dimensions[1] and self.x < self.dimensions[2]) then
-- compares the players bottom side to the enemies top side and compares the players top side to the enemies bottom side
if(self.y + 32 > self.dimensions[3] and self.y < self.dimensions[4]) then
-- Compares the players current color to the color of the enemy, if the colors dont match it executes code
if(self.currentColor ~= self.dimensions[5]) then
for i, value in ipairs(gameControllerObj) do
-- Ends the game
gameControllerObj[i]:GameOverScreen()
end
-- breaks from the enemy do for statement, without this break it will throw can throw an error looking for objects that no longer exist.
break
end
end
end
end
end
function Player:Draw()
-- Checks the color of the player, and if the color of the player is equal to the value we are checking then it will the selected frame from the sprite sheet.
if(self.currentColor == "red") then
Graphics.drawImageExtended(self.x, self.y, 0, 0, 32, 32, 1, 1, PlayerSpriteSheet)
end
if(self.currentColor == "yellow") then
Graphics.drawImageExtended(self.x, self.y, 31, 0, 32, 32, 1, 1, PlayerSpriteSheet)
end
if(self.currentColor == "green") then
Graphics.drawImageExtended(self.x, self.y, 0, 31, 32, 32, 1, 1, PlayerSpriteSheet)
end
if(self.currentColor == "blue") then
Graphics.drawImageExtended(self.x, self.y, 31, 31, 32, 32, 1, 1, PlayerSpriteSheet)
end
end
function Player:PauseGame()
-- Checks if a Pause screen already exist
if(#pauseObj > 0) then
return
end
for i, value in ipairs(controllerObj) do
-- Checks if the start button has been pressed
if(controllerObj[i].startIsPressed == true and controllerObj[i].startCanBePressed == true) then
-- Creates the pause screen
table.insert(pauseObj, #pauseObj + 1, Pause:New())
end
end
end
function Player:Update()
-- Players update function
self:ChangeColor()
self:Move()
self:CheckCollision()
self:PauseGame()
end
The New function of the enemy class creates our enemies, we use a few parameters to set the position, color, and dimensions of our enemy.
The Draw function in our enemy class is a bit interesting for 2 reasons. First we use an if function to decide the color of our enemy, and second we use LPP built in rectangle function to draw our enemy.
The Move function is your first taste of AI (not really but play along), based off of our enemies given direction, it will move 10 pixels in that direction. Directions are assigned when objects are created.
The CheckDeathPosition checks if our enemy is out of bounds, and if any of those if statements are true it will mark it as dead.
The GetDimensions function should be a little familiar. We called this function in our player class, but I did not really give an explanation for the function. I will break it down here. We start by creating a table, which will hold the values of the enemies boundary, and color. We then return this value (which is what was passed to our player class).
Code: Select all
Enemy = {}
function Enemy:New(xPos,yPos,widths,heights,directions,colors)
enemy = {}
setmetatable (enemy, self)
self.__index = self
enemy.color = colors
enemy.x = xPos
enemy.y = yPos
enemy.width = widths
enemy.height = heights
enemy.direction = directions
enemy.isDead = false
return enemy
end
function Enemy:Draw()
-- Enemies are drawn using LPP rectangle function instead of using a sprite sheet
-- Checks if the color type is red
if(self.color == "red") then
-- Draws a red enemy
self.sprite = Graphics.fillRect(self.x, self.x + self.width, self.y, self.height + self.y, Color.new(255,0,0))
end
-- Checks if the color type is yellow
if(self.color == "yellow") then
-- Draws a yellow enemy
self.sprite = Graphics.fillRect(self.x, self.x + self.width, self.y, self.height + self.y, Color.new(255,255,0))
end
-- Checks if the color type is green
if(self.color == "green") then
-- Draws a green enemy
self.sprite = Graphics.fillRect(self.x, self.x + self.width, self.y, self.height + self.y, Color.new(0,255,0))
end
-- Checks if the color type is blue
if(self.color == "blue") then
-- Draws a blue enemy
self.sprite = Graphics.fillRect(self.x, self.x + self.width, self.y, self.height + self.y, Color.new(0,0,255))
end
end
function Enemy:Move()
-- Moves the enemies based on their direction
if(self.direction == "left") then
self.x = self.x + 10
end
if(self.direction == "right") then
self.x = self.x - 10
end
if(self.direction == "up") then
self.y = self.y + 10
end
if(self.direction == "down") then
self.y = self.y - 10
end
end
function Enemy:CheckDeathPosition()
-- Checks enemies current position, if the enemy is out of bounds it will be marked as dead
if(self.x < - 50) then
self.isDead = true
end
if(self.x > 1000) then
self.isDead = true
end
if(self.y < - 50) then
self.isDead = true
end
if(self.y > 630) then
self.isDead = true
end
end
function Enemy:GetDimensions()
-- creates a table of the enemies dimensions, and color {left, right, top, bottom, enemy color}
self.dim = {[1] = self.x, [2] = self.x + self.width, [3] = self.y, [4] = self.y + self.height, [5] = self.color}
-- returns the table
return self.dim
end
function Enemy:Update()
self:Move()
self:CheckDeathPosition()
return self.isDead
end
The GameController Class is what controls our game logic. It needs to keep track of our score, reset our game, create the game over screen, and create our enemies.
The AddToScore function just adds 1 point to our points variable. This function is called upon an enemies death.
Our AddEnemy function is where things get a bit interesting. We use random numbers to decide the color, and direction our enemies are supposed to move. Before I get into the random numbers, I want to make sure you know that that Vita's random number generator is not so random on its own. I found the same pattern being created in every play through, so I had to use a method to create "randomness." The method that we used is create a random number based off our vita's clock before we assign values for our enemy object. We use LPP System.getTime function. We then take the seconds variable from the function and use it in the math.randomseed function. This will help make our random number generator actually seem random.
After we create our initial random variable we create two new number variables; colorNum which will decide the color of our enemy, and directionNum which decides the direction the enemy will move. This is followed by 2 strings colorString and directionString. Both strings will hold the value assigned by the random numbers. We then use 8 if statements to assign the color values and direction values from our random numbers to our empty strings. The last portion of the function is 4 more if statements that will create an enemy object based off of the decided direction that has been assigned to our direction string variable.
The ResetGame function resets our game, it does this by reseting our timer, points, timeLimit, and resets our players position.
The GameOverScreen function removes our player from the game, and all of our enemies.
Code: Select all
GameController = {}
function GameController:New()
gameController = {}
setmetatable(gameController, self)
self.__index = self
gameController.timer = 0
gameController.points = 0
gameController.timeLimit = 240
return gameController
end
-- Increases the total score by 1 point
function GameController:AddToScore()
self.points = self.points + 1
end
-- Adds an enemy to the screen
function GameController:AddEnemy()
-- We use a random number generator to decide what will be the color of our enemy and the direction they will start in. These 2 variables will hold the number value.
h, m, s = System.getTime()
math.randomseed(s)
self.colorNum = math.random(1,4)
self.directionNum = math.random(1,4)
-- These two variables will hold the string values of the direction and color our enemies
self.colorString = ""
self.directionString = ""
-- these 4 condition statements will decide the color of the enemy
if(self.colorNum == 1) then
self.colorString = "red"
end
if(self.colorNum == 2) then
self.colorString = "yellow"
end
if(self.colorNum == 3) then
self.colorString = "green"
end
if(self.colorNum == 4) then
self.colorString = "blue"
end
-- These 4 condition statements will decide the enemy direction
if(self.directionNum == 1) then
self.directionString = "left"
end
if(self.directionNum == 2) then
self.directionString = "right"
end
if(self.directionNum == 3) then
self.directionString = "up"
end
if(self.directionNum == 4) then
self.directionString = "down"
end
--[[We use these 4 statements to draw our enemy objects based off of their starting direction.
We use 4 seperate statements instead of one universial statement because the staring position is based off the direction they are going to move, and I figured 4 condition statements would look a bit cleaner here, instead of in the New function of our enemy class. Though, you could easily put the directionString varible in place of the "up", "down", "left", and "right" string in the function. Then handle the placement in the New function of the Enemy Class.
]]--
if(self.directionString == "up") then
table.insert(enemyObj,#enemyObj + 1,Enemy:New(0,-48,960,48,"up",self.colorString))
end
if(self.directionString == "down") then
table.insert(enemyObj,#enemyObj + 1,Enemy:New(0,544,960,48,"down",self.colorString))
end
if(self.directionString == "left") then
table.insert(enemyObj,#enemyObj + 1,Enemy:New(-48,0,48,544,"left",self.colorString))
end
if(self.directionString == "right") then
table.insert(enemyObj,#enemyObj + 1,Enemy:New(960,0,48,544,"right",self.colorString))
end
end
-- Resets the game
function GameController:ResetGame()
-- Resets the timer counter, the time limit, the score, and players position. It also removes all of the enemies on the screen.
self.timer = 0
self.points = 0
self.timeLimit = 240
table.insert(playerObj, #playerObj + 1, Player:New(450,300))
end
-- Creates the Gameover screen
function GameController:GameOverScreen()
for i = #playerObj, 1, -1 do
table.remove(playerObj, i)
end
for i = #enemyObj, 1, -1 do
table.remove(enemyObj, i)
end
table.insert(gameOverObj, #gameOverObj + 1, GameOver:New())
end
function GameController:Update()
-- Increases the enemy add timer by 1
self.timer = self.timer + 1
-- Checks if the enemy add timer is equal too or over the limit
if(self.timer >= self.timeLimit) then
-- Adds the enemy to the screen
self:AddEnemy()
-- Resets the timer
self.timer = 0
-- Checks if the time limit is over 40
if(self.timeLimit >40 ) then
-- Decreases the time limit by 5
self.timeLimit = self.timeLimit - 5
end
end
end
Our Score class is fairly simple, its only job is to display a score text on the screen. This class only contains the New and Draw functions. Inside the Draw function, we display the points variable from our GameController class. Other than that, there is not much else to this class.
Code: Select all
Score ={}
function Score:New()
score = {}
setmetatable(score, self)
self.__index = self
return score
end
function Score:Draw()
for i, value in ipairs(gameControllerObj) do
Font.print(TextFont, 420, 10, "Score " .. tostring(gameControllerObj[i].points), Color.new(255,255,255))
end
end
The Pause class is also relatively simple. Our Draw function draws a semi-transparent rectangle to cover the screen, draws text that says paused, and on the bottom of the screen displays my name, and credits the author of the music.
The CheckClose function checks our button presses, and if start is pressed it will mark the the paused screen for removal. We also call our Controller classes GetCanPresses function, since our game is paused and this function is not called while the game is paused.
Code: Select all
Pause = {}
function Pause:New()
pause = {}
setmetatable(pause, self)
self.__index = self
pause.shouldClose = false
return pause
end
function Pause:Draw()
-- Draws a rectangle over the screen.
self.sprite = Graphics.fillRect(0, 960, 0, 544, Color.new(0,0,0, 120))
-- Prints the word pause
self.text1 = Font.print(TextFont, 430, 270, "Paused", Color.new(255,255,255))
-- Displays credits
self.text2 = Font.print(TextFont, 225, 520, "Game By: The Sucicidal Robot Music By:8-BitWonder", Color.new(255,255,255))
end
function Pause:CheckClose()
for i, value in ipairs(controllerObj) do
-- Checks if start was pressed to close the pause menu
if(controllerObj[i].startIsPressed == true and controllerObj[i].startCanBePressed == true) then
-- Sets the pause screen to close
self.shouldClose = true
end
-- This function is not called in our index class because of the early return function, so we must call the function here to change the first frame button presses.
controllerObj[i]:GetCanPresses()
end
end
function Pause:Update()
self:CheckClose()
return self.shouldClose
end
The GameOver Class is basically the same as the Pause class. The only real difference is the actual text that is Drawn in our Draw function.
Code: Select all
GameOver = {}
function GameOver:New()
gameOver = {}
setmetatable(gameOver, self)
self.__index = self
gameOver.shouldClose = false
return gameOver
end
function GameOver:Draw()
-- Draws a rectangle over the screen
self.sprite = Graphics.fillRect(0, 960, 0, 544, Color.new(0,0,0, 120))
for i, value in ipairs(gameControllerObj) do
-- Displays game over text and final score
self.text1 = Font.print(TextFont, 400, 270, "Game Over Final Score: " .. tostring(gameControllerObj[i].points), Color.new(255,255,255))
-- Displays the credits
self.text2= Font.print(TextFont, 225, 520, "Game By: The Sucicidal Robot Music By:8-BitWonder", Color.new(255,255,255))
end
end
function GameOver:CheckClose()
for i, value in ipairs(controllerObj) do
-- Checks if start was pressed to close the gameOverScreen
if(controllerObj[i].startIsPressed == true and controllerObj[i].startCanBePressed == true) then
-- Sets the screen to close
self.shouldClose = true
end
-- This function is not called in our index class because of the early return function, so we must call the function here to change the first frame button presses.
controllerObj[i]:GetCanPresses()
end
end
function GameOver:Update()
self:CheckClose()
return self.shouldClose
end
Building our game
[spoiler]Building our game is rather simple. All you do is place all of your game files in the build folder provided in LPP. Then run the Build.bat file, name your game, and then provide an ID number (USE ALL CAPS). It will create a vpk file that you can install onto your Vita. If you made it this far congratulations, you should be semi-prepared to make your own games. I suggest you also look over some of the tutorials provided in LPP to learn more about the framework. When designing your first game, I suggest you start small and make a breakout or pong clone, move onto a small SHMUP, and from there a platformer. After that, the sky is the limit.[/spoiler]
Where to go from here?
[spoiler]The first thing I reccomend is looking further into the Lua language. I only covered the bare minimum to get you going, but the lua reference manual will soon become your best friend https://www.lua.org/manual/5.3/ . The next thing I reccomend is looking into a game development book. One of my personal favorites is 'Foundation Game Design with Flash', which covers AS3 instead of Lua, but a lot of the principles of actual game development are the same. Plus, learning a additional programming languages is always a good thing. Lastly, just make games. Nothing too big, but small things that you can make in a week or two post and move on to the next project. Eventually you will get an idea of the scope you should be aiming for.[/spoiler]
I hope someone finds this tutorial helpful.