_____ _ _ _ ____ _ | ___(_)_ __ ___| |_ | | _ _ __ _| _ \| | __ _ _ _ ___ _ __ | |_ | | '__/ __| __| | | | | | |/ _` | |_) | |/ _` | | | |/ _ \ '__| | _| | | | \__ \ |_ | |__| |_| | (_| | __/| | (_| | |_| | __/ | |_| |_|_| |___/\__| |_____\__,_|\__,_|_| |_|\__,_|\__, |\___|_| ____ __ _ _ |___/ / ___| __ _ _ __ ___ ___ / _| ___ _ __ | |_| |__ ___ | | _ / _` | '_ ` _ \ / _ \ | |_ / _ \| '__| | __| '_ \ / _ \ | |_| | (_| | | | | | | __/ | _| (_) | | | |_| | | | __/ \____|\__,_|_| |_| |_|\___| |_| \___/|_| \__|_| |_|\___| ____ ____ _____ | _ \/ ___|___ / | |_) \___ \ |_ \ | __/ ___) |__) | [First LuaPlayer Game for the PS3] |_| |____/____/ by TheOuterLinux (https://theouterlinux.gitlab.io) Last updated: 2024/09/20 .--------------. | Introduction | '--------------' For your first LuaPlayer game for the PlayStaion 3, we are going to keep things as simple but as featureful as possible. This example will contain: - Use variables within a plain-text file at the start as a "chunk" - A 720x480 PNG background image at a 720x480 resolution - Draw text on the screen somewhere - A movable (not animated) PNG sprite using the DPad - A Sinlge, reasonably-sized, looping, MP3 background audio - Press the start button to exit - Save player (x,y) to the same plain-text file used as a "chunk" before quitting ...and not a whole lot else. Matter of fact, you may have already had some experience and noticed that older versions of Lua-based game engines are somewhat limited. Most of these are only good for arcade- like and simple point-and-click games. Besides, you also need to consider that the PS3 is a computer and therefore has RAM and CPU usage limits just like anything else. .-------------------------------. | USB HARD DRIVE FILE-STRUCTURE | '-------------------------------' The first thing to do is create a file called "app.lua" in the root of a USB hard drive. The LuaPlayer for the PS3 looks for this file to launch the game; however, from there, you can place the resources, such as images and audio, anywhere else. Just rememebr that your file-paths to these resources are not relative; meaning, the path to a background image would look like "/dev_usb/example/background.png". If a file- manager on your Jailbroken PS3 says that your USB is "dev_usb071" and not "dev_usb000," you may be trying to use an SD-to-USB adaptor and it may not work. Your directory structure for the USB hard drive should look like... app.lua Resources (Folder) | +--- BGImage.png | +--- BGAudio.mp3 | +--- Player.png | +--- Saves (Folder) | +--- Player.txt Anyway... Start your app.lua code off with the following... Skip to the very end for the whole code. .-------------------. | INITIALIZE THINGS | '-------------------' ResolutionWidth = 720 ResolutionHeight = 480 InitGFX(ResolutionWidth,ResolutionHeight) snd.Init() pad.InitPads(1) The "InitGFX(###,###)" part tells the PS3 to switch to 720x480 pixels since those values are stored inside of the variables "RsoluationWidth" and "ResolutionHeight". I am honestly not sure what the limits are on screen resolutions but are probably something like 720x480, 1280x720, and 1920x1080. The "snd.Init()" part tells the PS3 to get ready to play audio files. The "pad.InitPads(#)" part tells the PS3 to get ready to accept input from the PS3 controller, specifically the first controller "1", in this case. If this were a two-player game, then there would be a "2" inside of the function. Functions like these are placed at the beginning as they only need to be ran once; do not put them in a while-loop. .-------------------------------------. | ADD SOME VARIABLES BECAUSE WHY NOT? | '-------------------------------------' Add the following to the app.lua script after all of the initialization parts... isRunning = true Voice1VolumeL = 127 Voice1VolumeR = 127 Player_Speed = 5 ...I am only spacing these lines out from the edge of this document to make it easier to notice since this is a plain-text file; it is for decoration. You obviously would not have the extra spacing in your code. The "isRunning" part is a variable that we are creating so that when we do the while-loop for the PS3 controls, we can script it as "while isRunning do... blah blah blah..." because the game "is running". The "Voice1Volume[L,R]" parts will be used with the "snd.SetVoice()" function mentioned below. Setting variables and then using them in functions makes those values easier to change when needed. The "Player_Speed" variable will be used when moving the sprite up, down, left, or right. Set this number higher to increase the speed of the player or lower to decrease the speed. By the way, you can separate these sections out by using empty lines if it will help you organize things. However, when it comes to tabs/spaces, I recommend using four spaces instead of tabs if the code calls for that sort of thing, such as with personally created functions. .-----------------------------------. | GET THINGS READY FOR TEXT DRAWING | '-----------------------------------' If you want to print text on the screen, add the following lines next... FontSize = 20 InitFont("/dev_flash/data/font/SCE-PS3-MT-R-LATIN.TTF", FontSize) --font path/size Text_to_Draw = "Your First PS3 Game" CenterTextHelper = ResolutionWidth - (string.len(Text_to_Draw) * FontSize) / 2 The variable "FontSize" is hopefully obvious what it is fore. The "InitFont" function is required in order to print text as otherwise, the LuaPlayer for the PS3 has no idea what font to use and therefore will not print anything but it also does not result in an error. We have a variable "Text_to_Draw" to make it easier to change the text rather than editing the whil-loop we will be making soon. The "CenterTextHelper" variable subtracts the length of the string in the "Text_to_Draw" variable from the "ResolutionWidth" variable and then muliplies that result by the FontSize and then is divided by two. The result of this should be a "somewhat" centered text. This variable will be used in the while-loop mentioned before. The "order of operations" you have been taught in school apply here and so when doing complicated math, parenthesis are your friend. .----------------------------------------------. | RUN A "CHUNK" OF CODE FROM A PLAIN-TEXT FILE | '----------------------------------------------' Since the example is to be as easy as possible, the player X and Y positions are to be (eventually) saved within a plain-text file in which looks something like: Player_X = 360 Player_Y = 240 And because of the way the dofile function works and because it is placed neat the beginning where the other "variable = " stuff are, the contents of the "Player.txt" file will be treated as if the text were there in the app.lua file itself. Think of the "dofile" function as "do something within a file" or a file treated as a function. Place the following line near the beginning of the app.lua file... -- The Player.txt files contains the last saved (x,y) coordinates for the player dofile("/dev_usb/Resources/Saves/Player.txt") Make sure that you already have a "Player.txt" placed in the correct directory with the content mentioned near the beginning of this section. And in case you are wondering, using lines that start with "--" in Lua scripts are treated as comments; these are personal notes. .-----------------------------------------------. | LET LUAPLAYER KNOW ABOUT THE BACKGROUND IMAGE | '-----------------------------------------------' Unless you want a blank background, you may want a background image. But before LuaPlayer can draw the image, you need to let it know a few things... Background = surface() Background:LoadIMG("/dev_usb/Resources/BGImage.png") And just for "poops and giggles"... BeachBackground = surface() BeachBackground:LoadIMG("/dev_usb/Resources/BG_Beach_Image.png") ...works just as well; however, you would then of course have to refer to background-related functions with "BeachBackground:..." .----------------------------------------------------------. | LET LUAPLAYER KNOW ABOUT THE SPRITE IMAGE FOR THE PLAYER | '----------------------------------------------------------' Now that the script knows about the background, you may want an small image that you to move around on the screen when you use the DPad, aka a "sprite," one that is to represent the player... Player = surface() Player:LoadIMG("/dev_usb/Resources/Player.png") This only lets LuaPlayer know about "Player" and its associated image. To draw both of the background image and the player sprite, you will need to place associated functions in a while-loop, which we will do later. And if you are still confused, another for "poops and giggles" example: Banjo = surface() Banjno:LoadIMG("/dev_usb/Resources/Banjo.png") Kazooie = surface() Kazooie:LoadIMG("/dev_usb/Resources/Kazooie.png") ...will load "Banjo.png" and "Kazooie.png" sprites into memory. The point is, you can name these things whatever you want. .----------------------------------. | ADD SOUND AND PLAY AT GAME START | '----------------------------------' Next, add the following to let LuaPlayer know what MP3 file we want to play over and over... music = snd.SetVoice(1, "/dev_usb/Resources/BGAudio.mp3") snd.PlayVoice(1, 0, 0, Voice1VolumeL, Voice1VolumeR, 1) sys.TimerUsleep(0) The "snd.PlayVoice(1,0,Voice1VolumeL,Voice1VolumeR,1)" part is snd.PlayVoice(VoiceNumber,???,VolumeLeft,VolumeRight,Loop) in which I have no idea yet what the second argument to the function does, hince the "???" part. The last argument uses "1" for true and "0" for false in whether or not to loop the audio. The "VolumeLeft" and "VolumeRight" parts of the function are for the left and right side speakers if for some reason you need to have sound "whoosh" by in your headphones or whatever. The "sys.TimerUsleep(0)" part is honestly still a mystery to me but with the examples I have seen so far, it is always used right after the "snd.PlayVoice()" function. Note that this is not being placed in a while loop. Unless you have a way to let LuaPlayer already know that the particular audio is already playing, it may continue loading and playing the audio over and over on top of what is already loaded. NOT A GOOD IDEA. It is possible to crash/freeze a system if not careful when it comes to while-loops. .----------------------------------------. | ADDING A CLEANUP FUNCTION JUST IN CASE | '----------------------------------------' Okay, so I am borrowing this heavily as I do not know much about what is going on other than the "EndGFX()" function stops SDL, which is what LuaPlayer uses for most things. The "snd.Finalize()" function finalizes the sound library and frees all of the sound resources from memory. The "sys.UtilUnregisterCallback()" function is doing something in regards to "unregister sysutily callback" or something like that. The "io.open" parts write the variable "Player_X" and "Player_Y", which are the player's (x,y) coordinates, to the plain-text save files. The "\n" part tells the write-related function to go to a new line; otherwise, it will write everything on the same line. function QuitGame() EndGFX() snd.Finalize() file = io.open("/dev_usb/Resources/Saves/Player.txt","w") file:write("Player_X = ", Player_X) file:write("\n") --Places the next write on a new line file:write("Player_Y = ", Player_Y) file:close() sys.UtilUnregisterCallback() isRunning = false end Add this function somewhere before the PS3 controller stuff mentioned next since the function we will create there needs to know about this function before-hand; this is because the start button will be used to run this function. .----------------------. | PS3 CONTROLLER STUFF | '----------------------' To get the PS3 controller to move the player around, add the following to function near the beginning of the script but after the initialization parts and after the QuitGame() function. You usually want to add personally made functions after adding any "variable = something" lines. Do not place functions before the "Init" parts. function ReadKeys() if pad.right(0) > 0 then playerx = playerx + Player_Speed end if pad.left(0) > 0 then playerx = playerx - Player_Speed end if pad.up(0) > 0 then playery = playery - Player_Speed end if pad.down(0) > 0 then playery = playery + Player_Speed end if pad.start(0) > 0 then QuitGame() end end We create this function so as to more easily add or edit controls but only need to refer to the function as "ReadKeys()" in the while-loop we will be placing it in soon. The "pad.[right,left,up,down]" functions refer to the DPad on the PS3 controller. The "(0)" part refers to the first controller or "Player 1," if you will. The "Player_Speed" part was mentioned before when setting up variables; the higher the "Player_Speed" value, the faster the sprite/player will move on the screen when using the DPad. The "pad.start(0)" is the start button on Player 1's controller. The "EndGFX()" function ends the game while setting "isRunning" to "false" pulls us out of the while-loop mentioned next. If you want to add more buttons, here is a list. Take note of how L1 and R1 are handled. if pad.up(0) > 0 then ...Do stuff if DPad up is pressed end if pad.down(0) > 0 then ...Do stuff if DPad down is pressed end if pad.left(0) > 0 then ...Do stuff if DPad left is pressed end if pad.right(0) > 0 then ...Do stuff if DPad right is pressed end if pad.L1(0) ~=0 then ...Do stuff if L1 is pressed end if pad.R1(0) ~=0 then ...Do stuff if R1 is pressed end if pad.L3(0) > 0 then ...Do stuff if L3 is pressed end if pad.R3(0) > 0 then ...Do stuff if R3 is pressed end if pad.start(0) > 0 then ...Do stuff if the start button is pressed end if pad.select(0) > 0 then ...Do stuff if the select button is pressed end .------------------------. | THE DREADED WHILE-LOOP | '------------------------' The "dreaded" while-loop is dreaded because while-loops go on forever as long as certain conditions are met and therefore if there are bugs in a while-loop, they also go on forever, compounding problems. Anyway, place the following in a while-loop like so... while isRunning do StartGFX() Background:setRectPos(0,0) BlitToScreen(Background) DrawText(CenterTextHelper, 10+FontSize, Text_to_Draw) Player:setRectPos(Player_X, Player_Y) BlitToScreen(Player) ReadKeys() FlipGFX() end I am currently not sure what the "StartGFX()" and "FlipGFX()" functions do, but they appear to be required at the beginning and at the end of the while-loop like in the above example. This probably helps refresh the graphics on the screen. This while-loop first draws the background image, which before we assigned as "Background" and then draws text on top of the background image, hopefully at near the top-center area. The top-right corner is has the coordinates of (0,0). The Player sprite image is then drawn at (Player_X,Player_Y), which its coordinates are defined near the beginning because of the "dofile()" function. If you want the player to be below anything, note the order in which things are drawn in the while-loop. The while-loop then looks to see if any controller buttons are being pressed becuase of the "ReadKeys()" function we crreated and if that function detects that you are pressing buttons, then it does things based on what we told it to do in regards to that function. And then, it goes through all of this over and over again until "isRunning = false". By the way, in case you are not that familiar with while-loops, saying things like "while purpleBunny do..." is the same as saying "while the purpleBunny is true do stuff." "True" is assumed until told otherwise. .-----------------. | THE WHOLE THING | - Place the script below in the app.lua file '-----------------' mentioned at the beginning of this tutorial. Not bad for less than 100 lines ;) ResolutionWidth = 720 ResolutionHeight = 480 InitGFX(ResolutionWidth,ResolutionHeight) snd.Init() pad.InitPads(1) isRunning = true Voice1VolumeL = 127 Voice1VolumeR = 127 Player_Speed = 5 FontSize = 20 InitFont("/dev_flash/data/font/SCE-PS3-MT-R-LATIN.TTF", FontSize) --font path/size Text_to_Draw = "Your First PS3 Game" CenterTextHelper = ResolutionWidth - (string.len(Text_to_Draw) * FontSize) / 2 --Used to help center text -- The Player.txt files contains the last saved (x,y) coordinates for the player dofile("/dev_usb/Resources/Saves/Player.txt") Background = surface() Background:LoadIMG("/dev_usb/Resources/BGImage.png") Player = surface() Player:LoadIMG("/dev_usb/Resources/Player.png") music = snd.SetVoice(1, "/dev_usb/Resources/BGAudio.mp3") snd.PlayVoice(1, 0, 0, Voice1VolumeL, Voice1VolumeR, 1) sys.TimerUsleep(0) function QuitGame() EndGFX() snd.Finalize() file = io.open("/dev_usb/Resources/Saves/Player.txt","w") file:write("Player_X = ", Player_X) file:write("\n") --Places the next write on a new line file:write("Player_Y = ", Player_Y) file:close() sys.UtilUnregisterCallback() isRunning = false end function ReadKeys() if pad.right(0) > 0 then Player_X = Player_X + Player_Speed end if pad.left(0) > 0 then Player_X = Player_X - Player_Speed end if pad.up(0) > 0 then Player_Y = Player_Y - Player_Speed end if pad.down(0) > 0 then Player_Y = Player_Y + Player_Speed end if pad.start(0) > 0 then QuitGame() end end while isRunning do StartGFX() Background:setRectPos(0,0) BlitToScreen(Background) DrawText(CenterTextHelper, 10+FontSize, Text_to_Draw) -- Should center text near the top; 10 is for top-padding Player:setRectPos(Player_X, Player_Y) BlitToScreen(Player) ReadKeys() FlipGFX() end