Lua Programming Techniques
Source:
Yeldarb's Web Site
After having read
Lua Lesson 01, you should have a basic concept of how to run Lua
programs and how to setup a simple script. You are now ready to move on to some more advanced concepts that
will prepare you to create fully featured applications and games.
In this tutorial, you will learn to: use if statements, display a background, load and display images, get
button input, take screenshots, and make a loading screen, along with a bunch of other neato concepts.
So, basically this tutorial could be called "how to make a souped up, useless, graphical Lua script." But,
alas, it's not. It's called "The Basics," because that's what this souped up, useless, graphical Lua script will attempt
to teach you.
If you haven't read the
first tutorial,
I highly recommend it. If you decide to go on anyway, without reading
it, and become baffled, and in the process having your mind transfored
in to a gooey puddle of radioactive mush, don't come complaining to me.
(Aside: actually it's not really that difficult, I just thought I'd add
a little drama to the tutorial).
Before you start, you will need to download a
zip file containing the images used in this tutorial.
(The preceding phrase was a link, for those of you who skimmed right over it).
Now that we have all that introductory BS (baloney sandwich) out of the way, on to the tutorial!
For firmware 1.50 users, there's a nifty little trick that you can use
(sorry 2.00+ people, it requires kernel mode which isn't yet available
to you) to speed up your development cycle. By adding the following
line into the top of your code, you can activate USB Disk Mode, which
allows you to edit your script without having to exit out of it and go
back through the menu. To enable this, create a new lua file and use
this as the first line:
System.usbDiskModeActivate()
Now the USB port on the PSP will be activated when you run your script.
Next we are going to define our colors like we did in the
first tutorial. We will be using
pink,
blue,
orange,
gray, and
black.
Logically, we will name our variables accordingly. So, to instantiate
these color objects, we will enter the following lines of code:
pink = Color.new(255, 0 , 153)
blue = Color.new(0 , 153, 255)
orange = Color.new(255, 102, 0)
gray = Color.new(153, 153, 153)
black = Color.new(0 , 0 , 0)
If you have forgotten how these lines work, go back to
the last lesson and refresh your memory.
Now we want to display a loading screen. Our loading screen will say
"Loading: 0%" then "Loading: 1%" and so on and so forth until "Loading:
100%." To start it off, we need to print "Loading: 0%" to the screen.
You should remember the screen:print function from
Lua Lesson 01.
But in case you don't, here's the code that you will need to insert:
screen:print(194, 136, "Loading: 0%", yellow)
screen.flip()
As you should recall, "screen:print()" will store our text in an
offscreen buffer, and then "screen.flip()" draws that buffer to the
screen.
Next, we need to load our images (which, if you missed it before, can be downloaded
here). Extract the zip file into the folder
where your script file is on your PSP. Then add this line. It will load the background.
background = Image.load("images/background.jpg")
Before we move on, let's get a firm understanding of what this line of
code actually does. The first thing we see is a variable name,
background. We then set that variable equal to the result of the
function on the right ("Image.load()") using the assignment operator,
or equal sign. The actual loading of the image comes through the
"Image.load()" function which is built into Lua. It takes one
parameter, the path to the image file we want to load. Our image is in
the "images" folder, and the picture we want to load is
"background.jpg." Remember, you must always use the file's extention
when loading it (in this case, it was a jpeg file, so we used the
extention ".jpg").
Now that we have loaded the background, we want to update our loading status text.
screen:clear()
screen:print(194, 136, "Loading: 20%", yellow)
screen.flip()
This should all be familiar to you by now. All, that is, except for the
"screen:clear()." What do you suppose this does? No, it doesn't
initiate the hyper flux capacitor. Nope, sorry, it doesn't make your
PSP transparent. What's that? Yes, you in the back. Correct! It erases
everything we have on the screen up to this point. It lets us start
over with a clean slate.
Now we'll do the same process over again, but loading different images (and loading one sound):
circle = Image.load("images/buttons/circle_button.png")
down = Image.load("images/buttons/down_arrow.png")
screen:clear()
screen:print(194, 136, "Loading: 40%", yellow)
screen.flip()
l = Image.load("images/buttons/left_trigger.png")
left = Image.load("images/buttons/left_arrow.png")
screen:clear()
screen:print(194, 136, "Loading: 60%", yellow)
screen.flip()
r = Image.load("images/buttons/right_trigger.png")
right = Image.load("images/buttons/right_arrow.png")
boltsnd = Sound.load("music/comp.wav")
screen:clear()
screen:print(194, 136, "Loading: 80%", yellow)
screen.flip()
square = Image.load("images/buttons/square_button.png")
triangle = Image.load("images/buttons/triangle_button.png")
screen:clear()
screen:print(194, 136, "Loading: 90%", yellow)
screen.flip()
up = Image.load("images/buttons/up_arrow.png")
x = Image.load("images/buttons/cross_button.png")
splash = Image.load("images/lua.gif")
screen:clear()
screen:print(194, 136, "Loading: 100%", yellow)
screen.flip()
The one thing in this block of code that
does
need to be explained is the loading of the sound. The line 'boltsnd =
Sound.load("music/comp.wav")' is what loads our sound file.
Symantically, it is identical to the image loading code. The only real
difference is that the "Sound.load()" method is defined in the "Sound"
class rather than the "Image" class. You needent worry about this just
yet, it may come up in some advanced programming later, or you may
never have to deal with it (unless you learn Java, then you most
definitely will).
After all of our stuff is loaded, we are just going to quickly pause at
the "Loading 100%" so our user gets a chance to see it. To do this,
we'll use the "waitVblankStart" function that we talked about at the
end of
Lesson 01.
screen.waitVblankStart(60)
This time we have passed a parameter to the function. This just tells
it to pause for a certain amount of time. There are sixty Vblanks in a
second, so this pauses our program at this point for exactly one second.
We've finished our loading screen
(and our loading). Now it's time for the intro screen, thanks go to
indianajonesilm, the author of PSP Air Hockey.
First things first, let's display the background.
screen:blit(0, 0, splash, false)
screen.waitVblankStart()
screen.flip()
This step is simple. To display the background, we use the
"screen:blit" command. This is much like the "screen:print" command in
that it writes to the offscreen buffer. But, in this case it is writing
an image rather than text. The first two numbers (as in "screen:print")
are the x and y coordinates. Since our image is the since our image is
the size of the screen, we left these two numbers as zero (so that the
picture would start in the top-left corner). "splash" is the variable
name that we used for the image. And then we will pass "false" for the
alpha parameter. Our image doesn't have transparency, so we dont need
this flag set.
Now we have the background displayed on our intro screen! But, of
course, the intro screen is not done. We need... MUSIC! Or... at least
some sound. To accomplish this, we simply need to add one line:
boltsnd:play()
This is where Lua
really shines. It may not provide a whole lot of flexibility for
advanced coders, but it is lines like this that make it such a great
programming language. In C, you would have had to install all sorts of
libraries, linked them, and not to mention programmed in a bunch of
routines to handle the sound. With Lua, this is all done for you, and
you can start playing sound with just a single line of code.
The line is fairly
straightforward, we just call the "play()" function for our "boltsnd"
variable (which we already loaded our sound clip into above). That's
really all there is to it!
Now we are going to flip the screen again, set the volume, and wait on
the screen until the sound is finished. Put this in your code:
screen.waitVblankStart(240)
screen.flip()
Music.volume(128)
As we touched on before, there are sixty Vblanks in a second, so we are
pausing the program for four seconds (240/60 = 4). "screen.flip()" just
updates the screen. And, Music.volume(128) sets the volume to 128; this
is the highest that the volume goes (0 is the lowest). Now our loading
screen (and our intro) are done! Hooray! Give your self a big hug.
(Aside: whitehawk is a little weird for adding that in, but hey who am
I, the editor, to judge?)
But it's not all fun and games yet, we still need to code the actual program. So, let's go.
The first thing we're going to do is start our game loop and display the background.
while true do
screen:blit(0, 0, background, false)
Remember from the
first tutorial
when we used the infinite loop at the end? Well, this uses the same
concept, except we are going to do the main bulk of our program until
the user exits the script rather than just pausing. The other line
(obviously, since this is about the gagillionth time we've done this)
writes the image into the offscreen buffer.
We are almost to the part of our code where we will
interpret button input, but first we need to get the state of the
buttons. We do that through the "Controls.read()" function, like this:
pad = Controls.read()
This saves the button states to a variable, "pad." It is like taking a
snapshot, if we never redid this line, it would always contain the
button status that we have at this specific moment in time. But,
remember, this is still in the game loop, so every time our loop goes
through it will update the variable.
Ready for interpretting the input? Yeah, I'll bet you are, but hold
your pants on. We need to print the instructions on the screen first
(how else are our users going to know what to do?).
screen:print(135, 251, "Press 'Start' to restart", blue)
screen:print(110, 261, "Press 'Select' for a screenshot", blue)
screen:print(383, 35, "Support:", black)
screen:print(365, 45, "PS3Lounge.net", orange)
screen:print(365, 55, "scriptscribbler.com/psp", gray)
screen.flip()
Now we have our instructions on the screen (along with a little bit of self promotion).
NOW it is time for the interpretation of the button input. Did you keep
your pants on? Oh God, I didn't want to hear that. It was a rhetorical
question, RHETORICAL!
What is going to happen in our little application is this:
When we press [X], it will display a picture of the PSP's X button on the screen.
When we press [^], it will display a picture of the PSP's UP button on the screen.
When we press [L], it will display a picture of the PSP's Left Trigger on the screen.
And so on and so forth.
Now that it's understood what we are attempting to do, let's take a
gander at trying to understand how we will write the code. We are going
to use a series of "if" statements. "If" statements are perhaps the
most widely used programming convention. What they allow your program
to do is make decisions. "If this, do that. If not this, then do
something else." Now that you understand
what "if" statements do, let's look at
how we make them do it. Here's the code we are going to use for the first button:
if pad:cross() then
screen:blit(50, 228, x)
screen.flip()
end
First of all, "pad:cross()" is the way that we check to see if when the
"pad" variable was set, the [X] button was pressed. It will return
"true" if it was, and "false" if it wasn't. If it is returned true, the
code between "then" and "end" is run. It it is returned false, the code
in between those two identifiers is skipped. The code, of course, is
our code to display the image of the [X] button.
The code for the first button is set, now we just need to do the same thing for each of the other buttons. Here's the code:
if pad:circle() then
screen:blit(90, 195, circle)
screen.flip()
end
if pad:triangle() then
screen:blit(45, 165, triangle)
screen.flip()
end
if pad:square() then
screen:blit(15, 195, square)
screen.flip()
end
if pad:up() then
screen:blit(60, 40, up)
screen.flip()
end
if pad:right() then
screen:blit(90, 65, right)
screen.flip()
end
if pad:down() then
screen:blit(60, 80, down)
screen.flip()
end
if pad:left() then
screen:blit(30, 65, left)
screen.flip()
end
if pad:l() then
screen:blit(4, 6, l)
screen.flip()
end
if pad:r() then
screen:blit(403, 4, r)
screen.flip()
end
Congratulations! You have finished your first true Lua Application. So we're done... or are we? (*cue dramatic music*)
Nope. We're not. But don't fret, the bulk of the work is done, we only
have five lines to go. We need to put in the code to restart the
application when you press [START] and to take a screenshot when you
press [SELECT].
We'll do the screenshot code first:
if pad:select() then screen:save("screenshot.tga") end
Notice that we did this whole "if" statement on one line. It's
perfectly legal. The function "screen:save()" just takes a screenshot
to the file that we specify inside the parenthesis. In this case, we
are saving our screenshot to "screenshot.tga."
Now we'll just make the code to restart when the user presses [START]:
if pad:start() then
break
end
The break statement will exit us out of the infinite while loop (the game loop), effectively restarting the script.
And finally, we must insert a line to terminate our while loop (so the
Player knows it's time to go back to the beginning of the loop and
start over).
end
Now we are done! Remember that
hug you gave yourself before? Well, don't even think about doing it
again, because it creeped me out. But you
can go test your script
on your PSP! Congratulations. You are now a developer.
Special thanks to: Shine, indianajonesilm, and PSPUpdates.
Be sure to add the
feed to your RSS Aggregator (or Google Homepage, or Firefox Live Bookmark) to stay updated with the latest
tutorials.
If you have enjoyed this tutorial and have a spare buck or two, please consider
donating to the author.
If there's a calling, I will consider making more tutorials. Please
contact me with your feedback on the tutorials and on what you'd like
to see in the next lessons. My e-mail is whitehawk45 [at] gmail [dot]
com.