(Originally written by zardOz (RIP), provided in mostly its original form with some formatting and spelling improvements)
Note: This lesson is written for Principia users but beginner Lua users may find it more friendly and useful than the other online guides (just download Principia already it's awesome).
Looking at complex code can be quite intimidating for those who have never even considered programming anything... But I assure you it's not nearly as bad as you think. Many of the Principia users are very young and many do not use English as their first language so I will keep these lessons free of most programming terms and write as if the reader has never seen code before. These lessons will focus on some of the basic commands and format of the code and will not be covering most Principia specific things like 'controlling the adventure robot' with LuaScript.
While most of the online examples use text in the majority of their examples, coding = 90% math stuff, plain and simple. Good news is 90% of the math stuff is pretty basic. It will help you greatly if you have an understanding of basic algebra, geometry and a healthy respect for the equation a^2 + b^2 = c^2
. Anything you will want to do with curves and circles, which is more than you might think, will require Pythagoras' help. Knowledge of the X/Y grid system and negative values will be needed and this lesson will also assume that you have a basic understanding of the 0.0-1.0 signaling system used in Principia or something similar.
Pick a very simple idea and try to discover a coding solution... Most of the commands you will learn one at a time looking at other examples, help levels and tutorials or by reading the wiki. Through repetition and patience you can create great works with only a few techniques. As you improve your skills with these few techniques you will find yourself able to figure out each of your coding puzzles more easily and might even find yourself able to create things from scratch simply by thinking about it for awhile... The singular most important thing you need to master is the 'if then' command, but first you will need to know about how to format your codebox so it will run on Principia.
If you open a LuaScript box you will find some strange words and symbols called functions, a function is simply a part of a code that is pre-made and runs in a section by itself, like a tool to a carpenter. The first section is,
function init(is_sandbox)
end
You will not need the is_sandbox
for this lesson but it won't hurt anything. You will notice there are no capital letters in the function init
command, all codes are case sensitive.
function init()
end
init()
is the section where we put all of our code that only need to run once at the beginning of the program. There might be nothing at all, in which case you can erase it entirely... or it might be very, very long depending on what you are making. Everything from your pixel map(graphic images) to variables get set here. Not running commands constantly is very important to good code management. There will be much more on that later.
Variables are placeholders for numbers or words. This is so things can be shorter and easier to remember. When I refer to numbers I am talking about anything which might represent a number.
Example Numbers: 0
, -50.12
, 1 / 7
, 1 + 1
, (23 / 52 * (0.012 - 2))
, math.pi
, math.min(math.pi, 370)
, math.sqrt((A * A) + (B * B))
, 100 % 25
(see part VI)
math.pi
is the permanent placeholder for the number 'pi', math.min
is equal to the lowest of the values separated by commas inside the brackets(Arguments) and math.sqrt is the square root of the value inside the brackets in this case the value of 'C' in the equation A^2 + B^2 = C^2
. 'A' and 'B' are examples of variables. If you were to use the 'sqrt' example it would cause an error because the value of 'A' and 'B' are undefined, So we begin our first actual code by defining some values for 'A' and 'B',
function init()
A = 5
B = 10
end
If we instead put these lines in the step()
portion of the code, it will tell the computer a=5 B=10, a=5 B=10, a=5 B=10...
125 times per second, but since it only needs to be told once it goes into the init()
section. Our first goal is to determine the value of 'C' based on the two variables. Since we do not plan on changing the values of 'A' and 'B' we also put our Pythagorean equation inside the init() function,
function init()
A = 5
B = 10
C = math.sqrt((A * A) + (B * B))
end
Now when the program is run, the value of 'C' will be calculated once before the rest of the code begins and we will be able to use 'C' as a number in place of the three code lines it took to figure it out.
Many things will produce one or more variables which you will have to name first, for example the function game:get_cursor() lets you find the x and y position of the mouse or touch cursor. you will need to name them first:
CursorX, CursorY = game:get_cursor()
You may have already noticed that each person will write their codes a little bit differently, even though the code may be doing the exact same thing. The computer reads the lines as one long wall of text, any extra spacing at the beginning of a line or line changes are simply to help us read it. The following code snippets are all identical as far as the computer is concerned.
function init()
A = 5
B = 10
C = math.sqrt((A * A) + (B * B))
end
function init()
A,B=5,10;
C = math.sqrt(( A * A ) + ( B * B )) end
function init() A=5 B=10 C=math.sqrt((A*A)+(B*B)) end
--This code was written by Jimmy "The greatest coder ever" Wilson
function init(); A , B = 5 , 10 ; C=math.sqrt((A*A)+(B*B)); end;
--wasn't it awesome!!!!!
The ';' symbol is ignored by the program and is very popular among coders, it is simply used as a visual clue to indicate the end each individual section of code. It is required in some other programming languages, but in Lua it is optional.
A line change is considered as a space and will break for the next chunk, anything written after a '--' is a comment that will be ignored on that one line. All of these code snippets are identical, but the bottom three would be considered more "sloppy" and difficult to read. Do whatever you are most comfortable with, just don't be surprised if you ask for help and nobody can read your sloppy code! ;)
The many styles can make figuring out a code difficult and intimidating, but once you learn the basics you will be able to ignore many things.
If you only put codes inside the init()
function, it will do exactly nothing, Principia runs at 125 hertz or steps per second. That means your step()
code is being read through 125 times per second. If your creation produces a lot of lag it may mean that you have written something wrong or extremely memory intensive and the code cannot figure out what to do or can't keep up. All of the code we will be using goes inside the second function found in the default LuaScript box;
function step(count)
end
There are some special functions which run outside of the step()
function but they are rarely needed and are for more advanced coding techniques.
function step(count)
game:show_numfeed(count, 0)
game:set_score(count)
end
Our developers were kind enough to implement a variable into the step()
function called 'count'. You can use count a great deal as it is a very helpful number, count is equal to the number of steps the program has been running. If you leave your program running for a long time this number can get very big indeed but it is still helpful.
game:show_numfeed(number)
is used to show a number at the top of the screen when running a program, while useful as a display number it should be used during play testing to find errors in your calculations and make sure your numbers are behaving as intended, a.k.a debugging. The number after the comma is how many decimal places you wish to show, the example above will show the total step count. game:set_score(value)
will display the value minus the decimal in the score position, if you have display score enabled.
Our first programs will use the count variable to make a few simple timers, turn on the score display to test it. Every line you write in the step()
function will run constantly unless you tell it not to,
function init()
ten_second_timer = 0
end
First we set a variable for one our timers, some people like to use long variable names that explain exactly what they represent. The underscore '_' is used as any other letter and is a common way to create an artificial space in the variable name. This timer is set to begin counting from zero.
The Lua "find remainder" (Modulo) operator is an extremely useful tool figuring out the divisibility of a number, learn it well. It is used by putting % value
behind any number, the result is the number divided by the "value"... so 25 % 10 will divide 25 /10 = 2 +5r
... it discards the 2 and return the remainder number.. 5
function step(count)
every_step_timer = count / 125
if count % 1250 == 0 then
ten_second_timer = ten_second_timer + 10
end
game:show_numfeed(every_step_timer, 3)
game:set_score(ten_second_timer)
end
The every step timer is very easy to figure out, just divide the current step count by 125... every time through the code the value of the variable will increase by .008 or 1/125. The ten second variable refers to itself and therefor needs to be initialized above, it uses our first if/then command... an if/then command can be read like this,
IF (this condition is met) THEN (do this) END
IF (the remainder of current step count / 1250 equals zero) THEN
(add ten to the current value of the variable named ten_second_timer) END
You will note that the if "condition" uses a double '='... This is because a single '=' is used for assigning a value to a variable, and '==' is for comparing the equality of two values.
Lastly we can skip a few steps, get rid of the extra variable and put one of the timers directly into the set score position... As your coding gets better you will be able to shrink your codes into shorter versions, the minimum needed for a simple timer is this:
function step(t)
game:set_score(t / 125)
end
I can only speak from my own experience so I would highly recommend learning by constant playtesting and repetition. Reading about it is nice but without actually putting the codes into the box and pressing play, you wont learn much. Find a simple code made by someone else and playtest it thoroughly until you have a better grasp on that particular code. Maybe you'll even improve it!
Find a code, change a number and press play to see what happens, keep changing things until you can try to write it from scratch. A good place to start is with my Lua School Basics level, it has codes which behave the same as many of the parts in Principia. If you know how the part works it will help you understand the associated code. Many of the old examples use if this:first_run() then
in place of the new function init()
you will have to change this to use the older codes. Also new is the step function... to use old codes you will have to place everything that is listed outside of the first run
into a function step() (insert old code here) end
command.
Everything I know about programming comes from making levels and testing things on Principia over the last 9 months, my knowledge is still quite limited but I feel confident that I could make most super-Nintendo quality games at this point... While it may take you longer than me it IS possible for you to learn a great deal in a short time and be on your way to making wonders! Hell I cant even look at my microwave without thinking "I could make that!"
The if/then command can be used to accomplish almost anything you want to do... most of the other commands are used to make the codes shorter so you don't have to do so much work, but a good knowledge of if/then alone can open up infinite possibilities to you.
function init()
my_number = 500
end
function step(count)
if count <= my_number then
game:set_score(1)
else
game:set_score(2)
end
end
IF (this condition is met) THEN (do this) ELSE (if the condition is not met, do this instead) END
IF (the current step count is less than or equal to 500) THEN
(display 1 on the score counter) ELSE (if the current step count is greater than or equal to 500, display 2 on the score counter) END
This code will make the score equal one for the first four seconds and then switch it to two permanently after that. While it will work perfectly, it is poorly written and uses more memory than it should, I will try to give you some good habits before you learn the bad ones.
Currently it is checking to see if the condition is met on every step the program runs, but this is unnecessary. Here is a better code and since we are not using the variable more than once I will eliminate the init()
part of the code...
function step(count)
if count == 1 or count == 500 then
game:add_score(1)
end
end
Now it is as small as can be, this simple code will only be read on step #1 and step #500. The game already tracks a score on every step, so you can skip that part. Like the count variable you can treat the score as a second in game variable already provided and initialized at 0. You can use this variable even if the score isn't being displayed by using game:get_score
.
If the game:add_score(1)
was outside the if/then command it would add 1 to the score 125 times per second. It can also be used to add negative numbers to the score. The 'or' in the code above is another of the basic tools you will use... It is important to remember that all of the conditions must be spelled out, you cannot use a shorthand like this:
if count == 1 or 500 then
The above code will not work and will create an error.
Nesting is when you put one code inside of another, the code on the inside will only be run if the parent code is happy. this:read(0)
is how you read the 0-1 signal number from the IN0 socket of the LuaScript object, and if nothing is plugged in then it will just be 0.0.
function step()
if this:read(0) == 1 then
if this:read(1) == 1 then
game:add_score(2)
else
game:add_score(1)
end
end
end
The code above will add +1 to the score every bit where the signal from IN0 equals 1 and the signal from IN1 does not equal ~=1. It will add +2 if both inputs equal =1. The following codes are identical to the one above,
(SETUP: Make two RC buttons and send the wires to the LuaScript IN0 and IN1, try it with and without Sparsifiers.)
function step()
if this:read(0) == 1 and this:read(1) < 1 then
game:add_score(1)
end
if this:read(0) == 1 and this:read(1) == 1 then
game:add_score(2)
end
end
function step()
total_signal = this:read(0) + this:read(1)
if this:read(0) > 0 and total_signal == 1 then
game:add_score(1)
elseif total_signal == 2 then
game:add_score(2)
end
end
Since the total_signal
variable is reading the inputs it needs to be set outside of the init()
so it can read every step. elseif
can be used to add more specific conditions without needing a huge string of 'end's to close it, the stuff inside the elseif
wont run if the parent condition is satisfied. 'and' is another of the basic tools in your arsenal, it's pretty much what is sounds like and it works much like the Principia object.
(SETUP: Wire a grapher to lua OUT0, RC(slider OUT0)--luaIN0)
function step()
in0 = this:read(0)
if in0 ~= 0 then
if in0 <= 0.25 then
this:write(0, 0.25)
game:set_score(25)
elseif in0 > 0.25 and in0 <= 0.5 then
this:write(0, 0.5)
game:set_score(50)
elseif in0 > 0.5 and in0 <= 0.75 then
this:write(0, 0.75)
game:set_score(75)
elseif in0 > 0.75 then
this:write(0, 1)
game:set_score(100)
end
end
end
Here we begin by shortening the name of the IN0 input.. The ~= sign is used for "not equal to", another useful tool for your belt. If the IN0 signal is anything but 0.0 then it checks the IN0 signal and does the list of actions. There is no shortcut for "Is between" but you may need it often, use the >= or < method above... If the IN0 signal is 0.25 or less then send a .25 signal to out0 on the LuaScript and set the score at 25. If it isn't 0.25 or less(elseif) see if it is more than 0.25 and less than or equal to 0.5, if it is then send 0.5 to out0 and set the score to 50... and so on.
If you call for a variable or table or other item that is not defined you will get an error which tells you the thing you are looking for is 'nil', this means it does not exist...yet. You can use nil in a multitude of ways to improve your codes. Normally you might define your variable in the init()
, but this isn't always needed,
(SETUP: RC with a sparsified button sent to IN0... RC->sparsifier->luaIN0)
function step()
in0 = this:read(0)
if in0 == 1 then
if A == nil then
A=0
end
if A < 10 then
A = A + 1
else
A = nil
end
end
if A ~= nil then
game:show_numfeed(A)
end
end
The order in which you write things can be critical to the operation of you code. Since we did not initialize 'A' in the init()
, 'A' = nil to begin with. if we were to write the code in this order,
if A < 10 then
A = A + 1
else
A = nil
end
if A == nil then
A = 0
end
...an error will occur since it cannot compare A to the number 10 until 'A' is not nil. If the A == nil then A = 0
part comes first, it can then recognize the 0 < 10. We now have a code which will add up from 1 to 10 by one each time the RC button is pressed. On the eleventh press 'A' is returned to nil. And finally if 'A' does not equal nil the value of 'A' will be shown in the numfeed spot.
Returning things to nil when they are no longer needed can be a good way to manage memory, for example you might have a character creation program where you have a large selection of data to choose from, after the character type is selected, all of the data for the other traits is no longer needed and could be turned back to nil.
The for/do loop is your second primary weapon along with the if/then command. A for/do loop is used to repeat a code many times. Each time it repeats a special variable increases each time making the result of the code different each time. It will repeat until the number of loops you set is reached and then move on to the next line of code. These examples will use the default loop setting which increases the loop variable by 1 each time through.
function step()
this:write_frequency(1, 0.1)
this:write_frequency(2, 0.2)
this:write_frequency(3, 0.3)
this:write_frequency(4, 0.4)
this:write_frequency(5, 0.5)
for i = 6, 10 do
this:write_frequency(i, i / 10)
end
end
This code sends a signal to all 10 frequencies from 1-10, each signal is one tenth of the number of the frequency. The first 5 are set one at a time with copy and paste. Six through Ten are set with the for/do loop. The write_frequency line runs five times in a row with the value of 'i' increasing by one each time(6,7,8,9,10). It begins with the first number and ends with the second number. The order the numbers loop might be critical to the code you are working on, later we'll learn how to reverse the order.
Principia has some special requirements to make things work smoothly, to use read_frequency()
. you must first "listen_on" the frequency you want to read. you may need to use a lot of frequencies, in which case you'll want to use a for/do loop.
function init()
for i = 1, 100 do
this:listen_on_frequency(i)
end
end
We are now ready to read all frequencies from 1-100,
function step()
for i = 1, 100 do
if this:read_frequency(i) == 1 then
this:write_frequency(i + 100, 1)
end
end
end
As you can see this method is vastly preferable to writing out the three lines of code 100 times each. This simple code just sends any frequency from 1-100 that is reading 1.0 to the corresponding frequency +100 higher(1,101...99,199).
Below we begin to make a more sophisticated loop, starting by only listening to every other frequency from 1-99, we do this by multiplying every number from 1-50 by two and subtracting one.
function init()
for i = 1, 50 do
this:listen_on_frequency((i * 2) - 1)
end
end
function step()
for i = 1, 50 do
if this:read_frequency((i * 2) - 1) == 1 then
this:write_frequency(i * 2, 1)
end
end
end
We are now detecting every odd frequency from 1-100 and if it is 1.0 we send it on to the even numbered frequency above it(1->2, 99->100)
By adding a third number to the loop variable you can change the amount the loop increases each time from +1.0. The loops below work the same as the ones above by simply increasing by 2 each loop instead one, you do not need to use whole numbers any type of number will do.
function init()
for i = 1, 99, 2 do
this:listen_on_frequency(i)
end
end
function step()
for i = 1, 99, 2 do
if this:read_frequency(i) == 1 then
this:write_frequency(i + 1, 1)
end
end
end
You may want to reverse the order of the loop, to do so you only need list the larger number first and use a negative number in the 3rd "count by" position.
for i = 99, 1, -2 do
if this:read_frequency(i) == 1 then
this:write_frequency(i + 1, 1)
end
end
Or as always you can "invert" a value by subtracting your results from the higher value of your scale...
for i = 1, 99, 2 do
if this:read_frequency(i) == 1 then
this:write_frequency(101 - i, 1)
end
end
This version sends 1.0 to frequency #100 if frequency #1 =1.0... if frequency #3 = 1.0 then it sends to frequency #98 and so on.
Double for/do loops are a critical method to learn and the first thing you will encounter which may actually lag your program if used incorrectly or abused. We will be using these for many of the future examples. When you nest one loop inside another it will perform the # of loops from the first code * the # of loops in the second code and can get out of hand very quickly. Before learning the details about how to manipulate sprites and graphics we are going to use a default sprite to help us test.
this:draw_sprite(0, 0, 0, 0.5, 0.5, 0, 0, 1, 1)
The first two numbers are the X location and Y location that the sprite will be drawn in the world, for now we will only be using those two numbers. The code above will draw a square sprite 0.5 wide and 0.5 tall at world location 0x,0y. The order things are drawn is very important to sprites, sprites listed later in the code will draw on top of the ones earlier in the code.
Our goal is to draw 100 sprites at every coordinate location from 1x,1y to 10x,10y, we do this with a double for/do loop,
function step()
for x = 1, 10 do
for y = 1, 10 do
this:draw_sprite(x, y, 0, 0.5, 0.5, 0, 0, 1, 1)
end
end
end
This for/do loops a total of 100 times before moving on to the next line of the code. it begins at x=1, x=1
and then moves up completing the inner loop for all ten Y positions at x=1
. After that X becomes 2 and the inner Y loop repeats again. If you need the grid to scale in a different direction you may need to re-organise the double loop. You can set the Y direction to be the outer loop or invert some of the numbers to do this.
You can quickly overwhelm your program by making large nested for/do loops, a double loop from 1-100 will perform 10,000 actions before moving on to the next part of code, multiplied by *125 times per second means the 100x double for/do loop is performing 1.25 million actions per second. You may need to find alternate methods for codes with such large requirements.
Also referred to as arrays, lists or databases... Tables are used to store lots of different pieces of data in one easy to access location. Tables can store any kind of data including other tables... the number of things you can do with tables is too large for me to cover so we will focus on a few of the basics. Loops are an essential part of managing most tables so we will be using them together,
In many cases you will need to initialize an empty table before you begin,
function init()
tableA = {}
end
Won't need to initialize the empty table if we begin by initializing it with some data inside, table data is listed in brackets like so,
function init()
tableA = { this:read(0) * 10, 10, 100, 1000, math.pi * 2 }
end
Each of the positions in the table can be identified by several methods, first is with a position number[1],
function step()
if tableA[1] < tableA[2] then
my_var = true
else
my_var = false
end
if my_var == true then
game:show_numfeed(tableA[3])
end
end
Now we've created a variable on the fly called my_var
, it uses true/false instead of numbers. Like the numbers it needs to be initialized unless you give it special instructions. With the code above my_var
will always be either true or false if the first two table positions are numbers, so it does not need to be initialized. The my_var
will always equal true unless the signal to IN0 equals 1.0. then we use the true/false state to activate the number feed showing tableA[3]
... 100.
If your code changes one of the initial values of either tableA[1]
or tableA[2]
to something that isn't a number, an error will occur. An error will also occur if you ask for the true/false state of my_var
, before it is defined. Here are some codes which will produce errors,
function step()
if my_var == false and tableA[1] < tableA[2] then
my_var = true
end
end
function step()
if tableA[1] < tableA[2] then
my_var = true
else
my_var = false
end
tableA[1] = "my random non-number text string"
tableA[1] = true
end
As a maker of games I use random numbers like they are going out of style. Even if you don't want to make games, knowing how to customize random numbers is a must. There are two methods, math.random()
and math.random(lowvalue, highvalue)
. The first method is generic and creates a number from 0.0-1.0 by .01, the second method is custom and will only make values in increments of one within the range you specify.
-- a random value from 0.00-1.00 by increments of .01
math.random()
-- random between either 0.0 or 1.0
math.random(0, 1)
-- a whole number from -100 to 25
math.random(-100, 25)
-- a number from 1.25-10.25 by increments of 1, any value over 9.25 returns the next highest number. Will always start at the first number and increase by 1.
math.random(1.25, 10)
-- Two dice simulator, 36 combinations from 2-12 weighted towards 7
math.random(1, 6) + math.random(1, 6)
As you can see this is somewhat restrictive, to make your randoms more versatile you need to modify the result.
-- a random value from 0.0 to 10.0 by .1
math.random() * 10
-- random between either 0.0, 0.5 or 1.0
math.random(0, 2) / 2
-- a random value from 0.000 to 1.000 by increments of .001
math.random(1, 1000) / 1000
-- a random even number from 0 to 100
math.random(0, 50) * 2
-- a random odd number from 1 to 99
(math.random(1, 50) * 2) - 1
-- a random number from 1 to 298 by increments of 3, (1,4,7,10...etc.)
(math.random(1, 100) * 3) - 2
To move things randomly right/left or up/down you will need a number that is random in the negative or positive direction, here are a few methods.
-- a random value from -1.00 to 1.00 by increments of .01
math.random(-100, 100) / 100
-- a random value either -1.0 or 1.0
(math.random(0, 1) * 2) - 1
The last example above is a great multi-purpose tool you can multiply it by a second random number or this:read(0)
to create a positive to negative scale.
Finally we make a random number between four completely unrelated values with a simple table,
(SETUP: RC with a sparsified button sent to IN0... RC->sparsifier->luaIN0)
function init()
randoValues = {0.12, 100, 999 * 999, math.min(1, math.max(3, math.pi))}
A = randoValues[math.random(1, #randoValues)]
end
function step()
if this:read(0) == 1 then
A = randoValues[math.random(1, #randoValues)]
end
game:show_numfeed(A)
end
Every time the button is pressed a random number between 1 and the #length of the table is created, we can use the table length #randoValues
instead of 1-4 so if we decide to add more entries to the table the random will automatically adjust. (see part IV) It then finds the number at the table position of the random number.
In this next example we want a table listing the first 100 numbers and the square roots of each number to follow after each one,
T = {1, math.sqrt(1), 2, math.sqrt(2), 3,...}
...but we don't want to have to write out each entry one at a time, so we'll need to coordinate a for/do loop to do it for us.
function init()
T = {}
for value = 1, 100 do
T[{value * 2} - 1] = value
T[value * 2] = math.sqrt(value)
this:listen_on_frequency(value)
end
end
Now every other entry starting with T[1]
is a whole number counting up from 1 to 100. and every even numbered table entry is the square root of that number. While we're at it we listen on 100 frequencies from 1-100.
Let's now make use of the data in the program.
function step()
for v = 1, 100 do
if this:read_frequency(v) >= 0.5 then
this:write_frequency(v * 2, T[value * 2] / 100)
end
end
end
Now we have 100 LuaScript if-gates that will send a square root value/100 to frequencies 101-200 if the corresponding 1-100 frequency is greater than or equal to 0.5.
The length of a table is often the most useful number for sorting things out, every code requirement is a little bit different and you will need to be able to rearrange your data sometimes to fit with the other codes you have already written.
function init()
tableA = {1,2,3,4,5}
tableB = {6,7,8,9,10,11,12}
end
To find the length of a table use a # sign in front of the table name. It's common to need the length of the table so you can count off each position to compare the values or change them in some way. Putting the table length as the end point for a for/do loop is one use and another is if you need to add on to the end of a table with new entries you can use the 'current table length"+1 (#table+1)
This example will add all of the entries from the second table at the end of the first and then get rid of the second table entries when the count reaches 8 seconds(1000 bits),
function step(count)
if count = 1000 then
for v = 1, #tableB do
tableA[#tableA+v] = tableB[v]
tableB = {}
end
end
end
The length of tableB
is 7 so we tell it to make 7 new table entries to the end of tableA
. The length of tableA is 5 so we need it to number the new entries at #tableA + v
or 5 + (1 - 7)
and each is assigned the value of tableB[entries 1-7] or (6-12). Last we clear all entries from tableB, we could make tableB 'nil' but we plan on using it again.
Now I'm going to change up the previous code so that it can accept new entries to tableB, wait 8 more seconds and then again add them to the end of tableA.
(SETUP: RC(button) -> sparsifier -> lua IN0
)
function init()
tableA = {10,20,30,40,50}
tableB = {60,70,80,90,100,110,120}
bitNumber = 0
end
function step(count)
if count = bitNumber + 1000 then
for v = 1, #tableB do
tableA[#tableA+v] = tableB[v]
tableB = {}
end
end
if this:read(0) == 1 and tableB[1] = nil then
tableB[1] = (#tableA + 1) * 10
bitNumber = count
end
end
Now you have a button that does nothing until 8 seconds has passed, after that time TableB adds itself to the end of tableA. Then the button is unlocked since tableB[1] is now nil, pressing the button creates a new tableB[1] with the correct value based on the formula needed. Finally when you press the button the 'bitNumber' variable becomes the current bit count#, restarting the 8 second clock automatically for another 1000 bits.
Now we are going to make a table called MySprites where each position is filled with another table containing four numbers.
function init()
Sprites = {}
Sprites[1] = {0,0,2,0.3}
Sprites[2] = {4,0,1,1}
Sprites[3] = {2,5,10,0.5}
end
This can also be written as,
function init()
Sprites = { {0,0,2,0.3}, {4,0,1,1}, {2,5,10,0.5} }
end
The first two numbers in each sub-table are the X and Y locations I want to draw my sprites at. The 3rd and 4th numbers are the X-size (width) and Y-size (height) of the sprites. To retrieve one of the values of the sub tables you add another [] bracket to the end of the table name. MySprite[3][3]
will pull the third number in the MySprite[3]
table ... the 10.
Now to draw some generic sprites at the location and size we want by combining all we've learned so far!
function step()
for sNum = 1, #Sprites do
this:draw_sprite(Sprites[sNum][1], Sprites{sNum][2], 0, Sprites[sNum][3], Sprites[sNum][4],0,0,1,1)
end
end
Each LuaScript has a sprite sheet which defaults to 128x128 pixels(texels) You can set the individual texels in the init()
or do many all at the same time with equations or patterns... The following method is the easiest for managing a picture since it can easily be modified if a texel is out of place or needs a color change. We start by defining some colors we want to use in a new type of table set-up.
function init()
a = {R = 0, G = 0, B = 0, A = 0} --black(100% transparent)
b = {R = 1, G = 0, B = 0, A = 2} --red solid color
MyPattern={
a,a,a,a,a,a,a,a,a,a,a,a,a,
a,a,b,b,b,a,a,b,b,b,b,a,a,
a,b,a,a,a,b,a,b,a,a,a,b,a,
a,b,a,a,a,b,a,b,a,a,a,b,a,
a,b,b,b,b,b,a,b,b,b,b,a,a,
a,b,a,a,a,b,a,b,a,a,a,b,a,
a,b,a,a,a,b,a,b,a,a,a,b,a,
a,b,a,a,a,b,a,b,b,b,b,a,a,
a,a,a,a,a,a,a,a,a,a,a,a,a}
ResolutionX=13
ResolutionY=9
So we've made a table with 117 positions in it. It's been formatted into a grid array so a visual pattern can be seen by the user and customized. The pattern is intended to show two letters A and B in red. The computer is going to read the table as one long line, it does not care one bit that we've made a fancy pattern... What we need is a grid management equation that will tell the computer to sort the table like our eyeballs do.
This is pretty complex and you do not need to understand the following code completely to make good use of it.
for y = 0, ResolutionY - 1 do
for x = 0, ResolutionX - 1 do
color = MyPattern[1 + y * ResolutionX + x]
this:set_sprite_texel(x, ResolutionY - y - 1, color.R, color.G,color.B, color.A)
end
end
end --{end of init()}
this:set_sprite_texel()
is used to set each individually colored texel by it's x/y coordinate location. The first two numbers are the x and y location of the texel you want to set. The sprite sheet begins at location 0x,0y in the lower left corner up to 127x/127y in the top right. This double for/do loop begins at y=0 and then cycles through the inner loop for all x values. After completing this first row, y becomes 1 and the x loop begins again for the row 2 texels. Since we begin the loop at 0 the loop must end at our desired resolution minus 1.
b = {R=1,G=0,B=0,A=2}
, this is the second method for calling for data from a table... the first table position is no longer a plain number but the variable 'R', to retrieve this data point you can use the shortcut b.R.
Above the for/do loop creates a special type of temporary table, since the 'color' line will produce one result for every table position it can be used as a table for the remainder of the loop. So instead of calling for b.R we call for color.R.
y begins at 0 and x begins at 0, if you input those values into the equation:1 + y * ResolutionX + x
you get [1+0*13+0] = [1+[0*13]+0] = 1
, so the very first position in our new color table = 1.
y=0 x=0 = [1+[0*13]+0] = 1
y=0 x=12 = [1+[0*13]+12] = 13
y=1 x=0 = [1+[1*13]+0] = 14 --start row 2
y=9 x=12 = [1+[8*13]+12] = 117 --last position
so.. color=MyPattern[1] through MyPattern(117]
Now when the set texel calls for color it is cycling through all 117 table positions and finding the .R, .G, .B, .A for each position. The data still needs to be sorted to the correct texel position.
this:set_sprite_texel(x,ResolutionY-y-1...
So x will match the current loop x value as it cycles through, y is more special it begins at 13 since 13 is the top row in our pattern and the first position in the table is the top right texel of the pattern, 12y 0x.
You will notice I've left a single layer of transparent texels surrounding each image. this is so I can use the sprite_filtering()
function.
this:set_sprite_filtering(0) --this will draw all texels as solid squares, very 8-bit. No transparent edges are needed if you are using this.
this:set_sprite_filtering(1) --this setting blends all texel colors with the texels surrounding it.
If you tried to show a transparent texel with a solid texel next to it in the pattern, it will appear as a transparent smear of the adjacent colors.
By leaving a gap between the edges and each pattern image you can draw a pattern without any edge blending problems. Usually seen as a thin line on one or more of the edges.
When making a pattern sprite I usually create a big temporary sprite which will display the whole sheet so you can see how things are looking,
function step()
this:draw_sprite(0,0,0,10,10,0,0,127,127)
end
Currently our pattern is drawn in the lower left of the sheet, if we want to move it from that spot you simply add to the x/y set_texel locations like so,
function init()
this:set_sprite_texel(x + 40, ResolutionY - y - 1 + 20, color.R, color.G, color.B, color.A)
end
Now its drawing from 40x and 20y instead of 0x and 0y. to display only the two letters and not the rest of the blank sprite sheet we have to isolate a section of the sheet with the last four numbers in the draw_sprite()
.
function step()
this:draw_sprite(0,0,0,2,2,1,1,12,8)
end
Now we are drawing a rectangle section of the grid from texel x1/21 to x1/y7. It should have no lines around the edges and display AB at the 0x,0y location designated.
Now we also need to adjust the size of the sprite to account for the fact that our letters are 5x by 7y, any attempt to draw a square sprite will squish the height of the picture...
this:draw_sprite(0,0,0,2,2*(7/5),1,1,12,8)
Lastly Ill show the entire code but with the two letter images split into separate sprites drawn at 0x/0y and 3x/0y,
function init()
this:set_sprite_filtering(1)
a = {R = 0, G = 0, B = 0, A = 0} --black(100% transparent)
b = {R = 1, G = 0, B = 0, A = 2} --red solid color
MyPattern={
a,a,a,a,a,a,a,a,a,a,a,a,a,
a,a,b,b,b,a,a,b,b,b,b,a,a,
a,b,a,a,a,b,a,b,a,a,a,b,a,
a,b,a,a,a,b,a,b,a,a,a,b,a,
a,b,b,b,b,b,a,b,b,b,b,a,a,
a,b,a,a,a,b,a,b,a,a,a,b,a,
a,b,a,a,a,b,a,b,a,a,a,b,a,
a,b,a,a,a,b,a,b,b,b,b,a,a,
a,a,a,a,a,a,a,a,a,a,a,a,a}
ResolutionX=13
ResolutionY=9
for y=0,ResolutionY-1 do
for x=0,ResolutionX-1 do
color=MyPattern[1+y*ResolutionX+x]
this:set_sprite_texel(x,ResolutionY-y-1,color.R,color.G,color.B,color.A)
end
end
this:set_sprite_texel(x,ResolutionY-y-1,color.R,color.G,color.B,color.A)
end
function step()
this:draw_sprite(0,0,0,2,2*(7/5),1,1,6,8) --draw the letter A
this:draw_sprite(3,0,0,2,2*(7/5),7,1,11,8) --draw the letter B
end
With the information in these four lessons you can create almost anything, you will even be able to look at other programming languages and not be completely lost. I do plan on adding more info for the while/do loop and repeat/until, however they are so rarely actually needed you can get by without them for now.
I would recommend moving on to my 20 part Lua Sprite Mega Tutorial level or Lua School Basics on Principia. They cover almost everything you need to know about drawing sprites, angles, creating graphics and the codes for the existing parts. I also have a Lua Line Draw Mega Tutorial and a Lua 101 series of levels with lots of stuff on it... #7 has most of the math shortcuts like math.min(), but I made that when I didn't know so much. I also recommend "SinCos and you!" for understanding circles better...I also have 100+ other Lua based levels you can look over.
I may add more lessons with stuff on circles/curves, grid mastery, text strings and other tricks of the trade if I'm feeling up to it. I wanted to keep this lesson mostly number based since it is the universal language and Principia doesn't use a lot of text... if you understand how to use these techniques with numbers using them to manipulate words will be easier.
Good Luck, be patient, determine where to start and take it one step at a time, playtest other levels, ask questions, keep at it, learn 100's of ways not to do things, change a number, press play, see what happens... you shall prevail!
Thanks to Team Bithack for an awesome product and everyone who helped me learn!
--zardOz