Object Oriented Programming in Lua (Part 3 – Examples)

In this section, we will apply the information from the last two sections into an actual example. Let’s implement a Car class in which we will make Car objects. Cars have a state (like their color or speed) as well as behavior like accelerating or honking the horn. We will create fields and methods for both of these.

We start by defining a car metatable and a table of car methods. We set the __index  metamethod so when a new car object is given this metatable, we can access our methods through it.

Next, we write a function to create a new car of a certain color and set the metatable properly. Additionally, we can initialize the speed field to zero. A function like this is called a constructor.

Excellent, now write some methods for the car, like acceleration and honking the horn. Store these in the CarMethods  table. Remember that we’re going to call these methods with colon syntax, so the first argument is going to be the car we’re working with. Let’s call this first parameter  self .

An important thing to mention here is that the following function definitions are all equivalent:

For the last one, we are defining the function with a colon! This tells Lua that we’re going to implicitly create a local variable called self  which refers to the first argument to the function. Conveniently, when we call the method with the colon syntax, self is automatically filled with the car in question.

And this is all we need to have something workable. Create a new car with the newCar  function and use some of its methods (don’t forget to use a colon instead of a period)!

In the next article, we’ll talk about ways to clean up this code more.

Click here for Part 4 of this tutorial series.

Object Oriented Programming in Lua (Part 2 – Metatables)

Part 2! This is all about metatables, which will be the main tool to implement OOP in Lua. Metatables, put simply, are Lua tables that describe the behavior of other Lua tables. You can use getmetatable(tab)  and setmetatable(tab, meta)  to manage a table’s metatable:

Now that you’ve set a table’s metatable, you can now set metamethods. These are special keys in the metatable that define specific behavior when set to a function, for example: __index , __add , __sub , __tostring , __eq and others. Notice how their names all being with two underscores (_). These metamethod functions are called when you do things with the table in question. It’s important to take time and read up on the behavior of each metamethod on your own, like how __add  is called when you add two tables with the same metatable.

We’ll be using __index to implement classes and objects. This metamethod is called when you try to index a key (doing  tab[key] or tab.key, remember these are equivalent) that hasn’t been set in a table yet:

In the above example, the goodbye print statement attempts to index world in my_tab . Since that key hasn’t been set in  my_tab  (it is nil), Lua calls our  __index  function with  my_tab  and  world as arguments. The function returns the string  cruel world , which is returned to the goodbye print statement. This code will output:

This example is pretty crucial to understand what’s going on behind the scenes and why. A key feature of __index is that you don’t have to set it to a function. You can also set it to another table instead! The behavior is now as follows: if you index something that isn’t in the table, it checks the table in __index instead (like a backup table).

Why is this important? For our objects, we will be creating a table of the functions (methods) that every object of one kind should have. We’ll set the metatable of the objects to equal this table of functions so that we don’t have to copy functions over to every instance of an object when it is created.

However, before we get to that, we have to understand colon syntax for method calling (remember that a method and a function are essentially the same, except that the word method is used in the context of OOP). In Roblox Lua, you are required to call methods like Destroy by using a colon. How is this different from using a period? When you use a colon in a method call, Lua sends the table itself (the left of the colon) as the first argument to the function. For example, if you had a table foo  with a function bar , then calling foo:bar()  is the same as foo.bar(foo) . More info about this behavior can be found in Chapter 16 of Programming in Lua. It’s syntax sugar.

When you use a colon in a method call, Lua sends the table itself (the left of the colon) as the first argument to the function.

Let’s put this all together: we can store our object’s methods (functions) inside a table. We set this table as the __index for the metatable of newly created objects. We call these methods using colon syntax so our functions can access the object we’re talking about. In the next article, we will take all these abstract topics and apply the concepts to concrete examples.

Click here for Part 3 of this tutorial series.

Object Oriented Programming in Lua (Part 1 – Concepts)

Hi everyone! This multi-part tutorial is all about object-oriented programming (OOP for short) in Lua. I’ll be applying this information to Roblox’s flavor of Lua. For this tutorial, you should be fairly comfortable with tables (read this chapter from Programming in Lua if needed) as well as how ModuleScripts work. You’ll learn what metatables are and how they can be used to simulate OOP in Lua. Many of the concepts you’ll see here are applicable to other programming languages, namely Java or C#. If you’re going into computer science later in life, this is good to expose yourself to early.

Let’s first talk about what object-oriented programming means: programming involving the use of objects. Objects are distinctly identifiable entities. For example, a car, a button on-screen, a player in-game, or a tool/weapon. Objects have their own individual identities, states and behaviors:

  • The identity of an object just means that it is different from other objects of its kind.
  • The state of an object, such as the current speed of a car or player’s score, are represented by fields.
  • The behavior of an object is defined through methods which are actions the object can perform. A car can accelerate, a player can earn more points, and a button can be pressed.

This should sound familiar to Roblox scripters because every object with which you’ve worked follows this model! A Part has a BrickColor property (a field representing state); a ParticleEmitter can emit particles (a method representing behavior).

In this tutorial, we’re going to make our own blueprints for objects. These are called classes. These blueprints define all the fields and methods by which our objects’ state/behaviors will be identified.

The next article will talk about metamethods and some Lua syntax subtleties that will help us implement these behaviors.

Click here for Part 2 of this tutorial series.

Scripting Dice on ROBLOX

Hi everyone. This is going to be a tutorial on scripting dice using ROBLOX Studio. Let’s start by creating the dice model.

dice
The dice model we’re going to create.

Creating the Dice Model

If you’re already comfortable using ROBLOX Studio to build things, you can skip this part and grab the model off ROBLOX here.

  1. Create a 4×4 part.
  2. Color the dice white. Set the Top and Bottom surfaces to Smooth.
  3. Add the following decals: Top (3), Bottom (4), Front (1), Back (6), Left (2), Right (5). Credit to game-icons.net for the dice face images. Remember, opposite face values of a dice will add to 7.
  4. Add a new Script to the part, as well as a StringValue named Face and an IntValue named Roll. We’ll use these as the output for our script.

The dice’s hierarchy should look like this:

tree

Scripting the Dice

Now for the fun part. Let’s start by getting some variable references to the objects in the game hierarchy (visible in the Explorer window). Remember to open the Output window when scripting! Small note: Unless otherwise mentioned, the given code should be added from top to bottom in the script.

Next, let’s define a function called getHighestFace(part) which will determine the highest face of given part and return it.

What’s going on here: We’re going over each NormalId (Top, Bottom, Left, etc). For each of these, we get a Vector3 value from the NormalId using Vector3.FromNormalId so Top translates to (0, 1, 0), Bottom is (0, -1, 0), etc. Then we’re transforming that point, which is a point local to the given part’s CFrame, into world space using pointToWorldSpace. Finally, we’re getting the Y component (height) of the resulting world point.

The rest of the this function is simply finding the face that gives the highest world point. There’s the meat of the program – now let’s hook it up into something practical by writing an update function that uses the getHighestFace function.

This is simple enough: get the highest face of the dice, then stuff it into the Face StringValue. We’ll revisit this function later to determine what number is on what side of the dice. For now, we’ll use this line to connect the update function with the RunService‘s Stepped event, which fires about 30 times a second when the game is unpaused.

You could also add a while-true-do loop and update on your own frequency if you wanted. Test your script by pressing play, then rotating your dice. If the Face StringValue’s Value changes accurately based on the highest face, then you’ve done got the basis of your dice! If not, check the Output window for any red error messages or yellow warnings. One little typo can mess up the entire script!

If your dice is working properly, let’s go back and update the update function so that it determines which number is on what side of the dice. Add this to the top of your script:

This is a table we’ll use to look up values of the dice decals that are on whatever side of the part is highest. Update your update function to this:

This will stuff the current face’s value into the Roll IntValue. This way, another script in your game could read the current dice roll without having to do any math.

And that’s it! Go ahead and play around with your dice in Play mode and make sure each side corresponds to the correct value. If not, you might have to switch some values in the faceValues table. If none of your values are changing, check the Output for errors.

Here’s the completed dice model in case you got stuck or lost.

dice