Learn Lua in 15 minutes

Lua is a scripting language that is popular in game development. It can be used in applications to allow you to extend the application’s features. Lua is also frequently employed in computer developer engines or frameworks. Corona is a great example of this.

Here are a few helpful things you need to know to get started with Lua.

Comments

Comments are useful for documenting what’s happening in your program. They help you remember what you are trying to accomplish and they allow others reading your code to understand what’s happening as well. Lua supports two types of comments: single line and multi-line.

Variables

Variables are the main way to store data, be it a number, string, table, boolean or function. Unlike some languages, you don’t have to tell Lua what type it is. Lua simply figures it out based on the value.

In Lua, all numbers are double floats. Lua uses 64 bits of data and also knows how to represent integers.

Variables can be cleared by “nil-ing” them. This tells the Lua garbage collector to reclaim the memory used by the variable.

In Lua, you specifically use the booleans true and false. This is different than booleans in some other languages where 0 means false and anything other than 0 can mean true when used with conditions.

Operators

Lua supports math and logical operators. They include:

  • + — addition
  • - — subtraction
  • * — multiplication
  • / — division
  • ^ — raise to a power
  • % — modulus
  • .. — concatenate two strings together
  • == — equal to
  • ~= — not equal to
  • < -- less than
  • <= -- less than or equal to
  • > -- greater than
  • >= -- greater than or equal to
  • and -- connect two operations together logically; both operations have to be true for the whole condition to be true
  • or -- connects two operations together; only one of the conditions has to be true for the whole condition to be true
  • not -- takes a condition and flips its state, i.e. not true = false, not false = true)

Flow control

Sometimes your code simply needs to execute each line of code in order, one after the other. Other times you need to execute some of the code repeatedly. And yet other times you may only want to execute code if some condition is true. This is known as flow control. There are five primary structures to alter the flow of your program:

  • if - then - else statements
  • while loops
  • repeat loops
  • for loops
  • functions

if - then - else

An if - then - else statement allows your program to make a decision as the code flows, using logical conditional operators:

In this case, since myNumber is 12, it's greater than 10, so the "true" portion of the if statement will execute. Had myNumber been 5, the else block would have executed. Note that an if statement must always end with the keyword end.

The if statement does not necessarily need an else statement...

...but it can also have multiple tests (conditions) using the elseif clause:

Note that you can string multiple conditions together for more complex logic:

For the print() statement to execute, both conditions have to be true. With Lua, you do not necessarily need to use parentheses in if statement conditions, unless you need to alter precedence order.

Now consider this block:

If either condition is true, the print() will happen. In this case, myNumber is not outside the range of 10 to 20, so nothing prints.

Unlike other Languages, as soon as Lua gets a true condition, it stops evaluating the rest of the conditionals.

In this example, if player does not exist (is nil or false), the rest of the conditional is irrelevant and it's not checked. This is important to frameworks like Corona where testing player.x < 0 would generate an error if player did not exist.

Loops

You can loop over the same block of code using one of the three loop types.

The while loop tests the condition before the first time through the loop. If the condition is false before the loop starts, it will not run at all. As long as the condition is true, it will continue to run.

The repeat loop tests its condition after each pass:

Finally, for loops iterate over a value, counting either up or down until the end value is reached:

The loop will print 1 through 10. You can also count down or increment by some value other than 1 by specifying a third value:

This code will print values 10 to 1, counting down by 1. Or, as mentioned, you can increment or decrement by any value like -12 or 0.75 if desired.

Now consider this block of code:

Here, at first glance, something strange will happen. The for loop will execute and print the numbers 1 to 10 as expected. But the last print() statement will print 100. This is because of scope and a special condition that happens in for loops. The variable defined in the actual for loop statement is local to the for loop. Thus, when the for loop is over, that local version of the variable disappears and you are left with the original version.

Scope, blocks, and chunks

All variables have scope or visibility. Variables defined without a prefix are global variables:

Global variables can be "seen" everywhere in the program, but this flexibility comes at a huge cost: they are not efficient to process and they lead to hard-to-track bugs and memory leaks. Fortunately, Lua supports local variables as well -- these only have visibility to the block of code in which they are declared or in that block's child blocks.

Local variables are always prefixed by the local keyword:

Now consider this example:

When myNumber increments to 5, we define a new local variable named isFive and set it to true. It will print true since the number is equal to 5 at that point. However, when the code executes the print() statement two lines down, printing isFive will output nil. That's because isFive only exists inside the if statement. Once the if statement finishes, isFive goes away.

However, because myNumber was declared local outside of the while loop, it's visible to the while loop and the if statement.

Lua scope is very important to understand. Please read our tutorial on scope if you need further explanation.

Functions

Sometimes you'll need to repeat the same code at different times in your program. Functions help keep your code modular and allow you to follow the DRY principle (Don't Repeat Yourself).

Functions begin with the function keyword, followed by the name of the function along with any parameters in parentheses. Functions can be scoped either globally or locally, but as mentioned earlier, local is the preferable option.

In Lua, functions can return multiple values:

Lua also supports anonymous functions. These are useful for "temporary" functions, for functions needed in a parameter to another function, or when you need to use a function before you define it.

Tables

Tables are a Lua data type used to organize similar data together. Lua supports two types of tables:

  1. Tables indexed by numbers, much like a traditional one-dimensional array in other languages.
  2. Tables indexed by string keys, much like an associative array in PHP or a hash table in other languages.

Numerically indexed

Let's look at some tables indexed by numbers:

You can tell how many items are in a table using the # operator:

Note that the first entry in a numerically indexed table is 1, not 0.

Key indexed

Tables indexed by keys work in a similar manner, except you need to use key-value pairs:

In this case, you can access the members of the table interchangeably using their keys either in dot notation or with square brackets. If the key has special characters like the dash in body-style, you must use the square bracket method.

Lua does not support multi-dimensional arrays. It does, however, support tables as members of a table.

Or it can contain tables of key-value tables too:

Numeric arrays can be looped over using the for loop. Key-value tables, however, can not be indexed by numbers in the same manner. Thus, Lua has a special for loop type specifically for key-value tables.

Modules

Modules allow you to use a table to organize related functions and data together in a separate .lua file. In this example file named do_math.lua, create your module:

When you want to access the module from another Lua file, you need to first require() it as shown below. Then you can call the function that you added to the module as shown on the second line.

Note that modules are only loaded into memory once, even if you require() them a second time. However, if you have multiple Lua files and need to access a certain module in each of them, you must require() that module in each file.

Metatables and metamethods

Metatables are regular Lua tables that contain a set of methods used to override certain operators. For instance, you can't normally add two tables together as it doesn't seem to make sense -- but you can using a metatable. In this example we will generate a combined high score from two players:

The following operators can be overridden:

  • + : __add
  • - : __sub
  • * : __mul
  • / : __div
  • % : __mod
  • ^ : __pow
  • - : __unm -- unary operator, i.e. variable = -variable
  • .. : __concat -- concatenate strings
  • # : __len -- length operator
  • == : __eq -- equality operator
  • < : __lt -- less than operator
  • <= : __le -- less than or equal to operator

Lua also supports handling missing table keys using the __index operator. If you access a key in a table that doesn't exist, normally nil would be returned. By using the __index operator, you can provide default values to be returned for certain keys.

This is the basic form for a metatable...

...and this can even be shortened to:

Now if you try to access myTable.width, you will get a default value even though you have not set a width value in the main table. However, if you tried to access myTable.alpha, it would return nil since you have not set any default for .alpha.

__index can be set to be a function instead of a table:

That is great for getting values, but what about setting values? Well, Lua has an operator for that: __newindex. It's quite similar to __index in that it can take a table or a function:

Wait... why did it not print 13.333? Because __newindex only works on new keys. myTable.width was already set when you set .height. To experiment, don't assign a value to myTable.width and you will see that .width is set correctly.

If you set the keys without using rawset(), you risk running through the metatable over and over, creating a recursive loop. The function rawset() and its sibling rawget() for __index bypass the metatable.

Classes and inheritance

Lua doesn't support object-oriented programming (OOP) directly, but by using metatables along with tables, methods and attributes can produce a similar behavior. In this case, __index will be used to get the default properties of the Car object. Like a module, you can also add methods to the object.

Conclusion

Lua is an easy language to learn and it's not syntax-heavy, which makes it easier to read.

There are plenty of other resources to aid in learning Lua, including:

And, when you're ready to start making great games and apps with Corona, please see: