Advertising (This ad goes away for registered users. You can Login or Register)

Lua Player Plus [Tutorial]

Post here your guides, tips, how-to, etc...
Post Reply
thesuicidalrobot
Posts: 8
Joined: Sat Aug 20, 2016 8:54 pm

Lua Player Plus [Tutorial]

Post by thesuicidalrobot » Tue May 23, 2017 7:53 am

Prerequisites
[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
The second type of variable is a string. Strings are nothing more than a collection of letters, numbers, and symbols.

Code: Select all

string1 = "Hello"
string2 = "123 ABC"
string3 = "?><|{}"
The third type of variable is a boolean. Booleans are true and false statements.

Code: Select all

statement1 = true
statement2 = false
The fourth type of variable is the object. Objects are instances of our classes. This may sound a little confusing at the moment, but hopefully you will understand this by the end of the tutorial.

Code: Select all

playerObj = Player:New()
enemyObj = Enemy:New()
The fifth type of variable is the table. I will go over tables more in the next section, but tables are nothing more than list of variables. Tables can contain numbers, strings, objects, etc.

Code: Select all

table1 = {"string1", "string2", "string3"}
table2 = {15, 78.659, .39}
[/spoiler]
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.
]]--

Comments will be in the code throughout this tutorial to give more detail to my code.[/spoiler]

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

We can also link together multiple if statements. Once a condition is met, it will execute the code in that specific condition, and then end the if statement. We can also attach an else at the end of our else and elseif statements, which will execute if none of the previous conditions are met.

Code: Select all


if (x == 12) then
-- execute this code

elseif (x == 75) then
-- execute this code

else
-- execute this code

end

For loops allow us to repeat the code inside of a condition statement until the condition set in the loop is true. You write a for loop by first writing the word for, then you declare a variable for your for loop, you then insert a comma to separate the next portion. After the variable declaration comes the condition you are comparing to, you then insert another comma, the last portion of the for loop is the action that will be taken if the condition has not been met. This is then followed by the word "do", and the loop is closed by the word "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

[/spoiler]

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"

The method above is great when you want to access specific elements of an table, but can become tedious if you want to access all elements of the table, especially when your table's size may be in constant fluctuation. For loops are perfect for accessing all elements of an table.

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


[/spoiler]

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
The function above does absolutely nothing, its pretty boring. So below we will add a value of 1 to our variable.

Code: Select all

-- Creates x variable
x = 0

function AddOneToX()
-- Adds a value of one to the "x" variable
x = x + 1

end
Now you see how easy it is to modify our variables through a function, but currently, we are unable to execute the function. Well executing our function is pretty simple. You write the name of the function followed by a set of parenthesis.

Code: Select all

x = 0

function AddOneToX()

x = x + 1

end

-- Calls function
AddOneToX()

Pretty simple right? There is one caveat to calling functions in Lua. Lua's code is procedural, meaning that you have to call a function after it has been created in your code. If you fail to do this, you will throw an error in your code, because the function technically does not exist. The code below would be considered invalid.

Code: Select all

x = 0

-- This would throw an error
AddOneToX()

function AddOneToX()

x = x + 1

end

Inside of the parenthesis can contain something called parameters. Parameters are variables and values that we can pass through to a function, allowing us to make our functions more dynamic, and reusable for multiple situations.

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)

Well now that you know variables that are created inside functions cannot be used outside of functions, it might make you frustrated if you want to access the variable created in the function. Variables created in functions may not be able to be accessed outside of a function, but we can pass back the value of a variable created in a function with a return statement. Functions can also be ended prematurely with the use of a return statement.

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()

[/spoiler]

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 next part may be a bit confusing since I am not the best person to explain things so I hope I can explain this is a half decent way. Our variables and functions in our classes can be global or local. Global means the function or variable can be accessed from any from any class. Local means that the function or variable can only be directly accessed inside its in own class. There is also a way to access locally stored variables and call local functions from outside of classes, and we will be covering all of that right now.

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

This is the external class that we will be accessing.

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


[/spoiler]
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

[/spoiler]
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()


[/spoiler]
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

[/spoiler]

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

[/spoiler]
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 next class I am going to focus on is our Controller class. This class is responsible for handling all button inputs. The class can be reused for most projects. The GetButton function checks for button presses. We use the built in Controller read function of LPP. Its pretty straight forward, if a button is pressed we turn the boolean to true, if its not pressed we set the boolean to false.

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 next class we are focusing on is our Player class. The our Player class has to do a few functions. Beside our Draw and Update functions, our player must be able to move, change colors, pause the game, and detect collisions.

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


[/spoiler]

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.
Advertising
Last edited by thesuicidalrobot on Thu May 25, 2017 6:26 pm, edited 1 time in total.

User avatar
Rinnegatamante
VIP
Posts: 758
Joined: Sat Mar 26, 2011 10:02 am
Contact:

Re: Lua Player Plus [Tutorial]

Post by Rinnegatamante » Thu May 25, 2017 11:15 am

Well made, i think several people will find this very useful! Concerning code optimization btw, would be interesting to add info about:

- Difference between LUA_NUMBER and LUA_INTEGER (starting from Lua 5 these two types are different, working with LUA_INTEGERs is way faster due to how they are internally managed.)
- local variables/functions, this could give you a pretty nice frameboost.
- Bitshifting operations (these got added in Lua 5.3). To write complex stuffs, not using such operations would kill performance.
Advertising
If you want, visit my website: http://rinnegatamante.it :D

SilicaAndPina
Posts: 99
Joined: Sun Aug 14, 2016 9:12 am

Re: Lua Player Plus [Tutorial]

Post by SilicaAndPina » Thu May 25, 2017 12:48 pm

shouldnt this be under the "TUTORIALS" section. and not the "PROGRAMMING AND SECURITY" section?

thesuicidalrobot
Posts: 8
Joined: Sat Aug 20, 2016 8:54 pm

Re: Lua Player Plus [Tutorial]

Post by thesuicidalrobot » Thu May 25, 2017 4:13 pm

Rinnegatamante wrote:Well made, i think several people will find this very useful! Concerning code optimization btw, would be interesting to add info about:

- Difference between LUA_NUMBER and LUA_INTEGER (starting from Lua 5 these two types are different, working with LUA_INTEGERs is way faster due to how they are internally managed.)
- local variables/functions, this could give you a pretty nice frameboost.
- Bitshifting operations (these got added in Lua 5.3). To write complex stuffs, not using such operations would kill performance.
I was thinking about adding some optimization related things in there as well, but I have been busy with a few other things, and I really wanted to get this tutorial out to those that wanted it. I will probably go back and add some of your suggestions in the coming days.
SilicaAndPina wrote:shouldnt this be under the "TUTORIALS" section. and not the "PROGRAMMING AND SECURITY" section?
I was kind of torn where to put this honestly, since it is a tutorial, but it is also programming related. I figured this was a better place since there seems to be quite a few programming related questions in here.

VitaBix
Posts: 1
Joined: Wed Oct 24, 2018 8:01 pm

Re: Lua Player Plus [Tutorial]

Post by VitaBix » Wed Oct 24, 2018 8:09 pm

:D Great tutorial, but i need some help...
EDIT: Are there layers in Lua Player Plus Vita? Because some images aren't showing up, maybe that's the cause?
:?: I can't animate, nothing shows up on screen! I have a sprite sheet of 100 by 200 pixels, 100:100 is one frame, 200:100 is the other. WHat would the code be so that will show up on screen.
This is what i have:
qblock = Graphics.loadImage("app0:/sprites/qblock.png")
frame = 1
if(frame == 1) then
Graphics.drawPartialImage(200, 100, 330, 250, 100, 100, qblock)
frame = frame + 1
end
if(frame == 2) then
Graphics.drawPartialImage(200, 100, 350, 250, 200, 100, qblock)
frame = frame - 1
end

Post Reply

Return to “Tutorials”