Your Code Sucks: Improving Your Roblox Lua Coding Habits

You might have picked up one of these terrible coding habits when learning Roblox Lua. Let’s address that, shall we?

Listen… we gotta talk. You’ve got some really bad habits when it comes to scripting on Roblox. You clearly grew a lot since your days as a novice. You’ve been around the block enough times to know when it’s time to move past old habits and grow as a developer. Now’s the time to start improving your Roblox code.

OK, maybe not all of that first paragraph is completely true, but that bit about growing as a developer might interest you a whole ton. In this post, let’s talk about what you can do to clean up your coding habits.

A word of caution: this “advice” (if we’re calling it that) isn’t for the absolute novice. If you’re really-really new, just focus on making things work first and foremost. That’s more important. For the rest of you, let’s get into it!

Continue reading “Your Code Sucks: Improving Your Roblox Lua Coding Habits”

The Binder Pattern

Let’s learn about Binders, a useful pattern that links CollectionService and OOP.

In this post, I’m featuring the Binder pattern, its uses, and reasons you should start using them in your Roblox projects.

A binder connects a CollectionService tag on Instance with an object in Lua code. When a tag is applied to an Instance, an object is constructed from a class with this Instance. During its lifetime, the object might read from various attributes on the instance to modify how it behaves. Finally, if the tag or entire Instance is removed, the object is cleaned up or destroyed accordingly.

For the purposes of this article, I’ll be using this implementation (I recommend you keep this open while reading).

Continue reading “The Binder Pattern”

Fix: RBXL files don’t open Roblox Studio properly

Hey all, I fixed this issue the other day. It took a little know-how, so I figured I’d write about it here in case anyone else ran into this. Hello to anyone from Google or the great beyond.

The Problem: Opening RBXL files in Windows Explorer starts Roblox Studio, but fails to actually open the RBXL file. (May also happen with RBXLX files, the XML counterpart to the binary RBXL files)

Continue reading “Fix: RBXL files don’t open Roblox Studio properly”

Fast Distance Comparison Algorithm

Comparing distances doesn’t have to be slow! Learn a simple yet valuable shortcut that’ll make distance checks significantly faster in most languages.

Hey, this is a quick post about quickly comparing the distance of two points (2D or 3D). In this article, I’ll be using Roblox Lua for demonstration, but this method ought to work in many different languages. The goal is to find a fast way to answer the questions:

  • Is a point within a certain distance of another point?
  • Which of these two points are closest to this point?

Continue reading “Fast Distance Comparison Algorithm”

Object-Oriented Programming in Lua (Part 5 – Inheritance)

Inheritance is ubiquitous in the world of object-oriented programming. It makes your classes easy-to-maintain and allows you to reuse a lot of your software. In Part 4, we cleaned up some class code and packaged it all into one nice complete class. In this part, we’ll talk about how we can form a relationship between two classes in which a subclass inherits the state and behaviors of a superclass.

Continue reading “Object-Oriented Programming in Lua (Part 5 – Inheritance)”

Event Systems in Roblox Lua (Part 4 – Full Solution)

Click here for part 3 of this tutorial series, where we used a BindableEvent to take all the work out of creating our own event system. Ok, maybe not all the work, but still…The limitations are real with BindableEvents. Namely the arguments from firing them can’t include special tables, and that’s a problem when we’re working with our custom objects with metatables. Let’s fix it with a little bit of Lua magic.

First off, we need to save the original values when the event is fired. We don’t know what variables are sent to the fire  method, but we are able to save them with a table using the ...  operator. If you want to know the nitty-gritty, definitely read section 5.2 on variable arguments in Programming in Lua. Let’s add an argument table and an argument count fields in the event itself:

Note that we don’t have to add these here, by the way. Initializing them to nil does do anything except let us, the programmers, know that these are accessible fields that we’ll use in the future. It’s good practice, so you should get in the habit of showing these fields here.

Next, let’s save the arguments and argument count in these fields by revisiting our fire function:

Notice how we use ...  within a table to capture the values into argData . We can now use this table to inspect the possibly many arguments passed. Also, an important detail is the use of the select  function: if you give "#"  as the first argument, it returns the number of arguments passed after it. We just use   ...  again here to do that. Finally, we aren’t even sending anything to the BindableEvent anymore since we’re handling that ourselves. It’s best to remove it for efficiency’s sake.

Okay, now we have all the information saved! How do we use it? We have to get a bit creative with our connect function. Instead of connecting the function directly to the BindableEvent’s Event, we need to connect our own anonymous function. Inside, we’ll unpack  the arguments we saved while firing the event, and use that to call our function.

We use unpack  here in order to return each of the saved arguments from 1 to the number we saved. I’ll save the detailed explanation of unpack for section 5.1 in Programming in Lua. Just know that it’s like returning multiple values from a function, except for unpack  it’s the multiple values are all the indices of the table we give it.

And that should solve our problems with the BindableEvent! We get all the nice threading that Roblox provides internally while working around the table limitations by saving and unpacking them ourselves. Try out the final product with the test code from part 3 and you’ll see the improvement.

And there you have it – just a few ways you can create and use your own custom events within your Lua code on Roblox. You can download the final Event class code here. I had to whittle down my examples to just these 3, so if there’s interest I’ll post others. If you have questions, send me a direct message on Twitter or a message on Roblox. Thanks!

Event Systems in Roblox Lua (Part 3 – Better Solution)

Click here for part 2 of this tutorial series. In part 2, we created a simple solution to our problem of creating an event system. We saw there are some problems with this, namely the synchronous execution. One after another, our connected functions are called when the event is fired.

Often times, when you’re using a game engine it’s best to let the engine do the heavy lifting for you. Roblox provides the BindableEvent object to create events in (or out of) the game hierarchy, however it isn’t often that you’ll want to instantiate one every time you want to create a custom event. Let’s create an Event class which uses BindableEvent internally intstead. Let’s re-start with the boilerplate from part 2:

We’re no longer keeping track of the connections ourselves with a table. We’ll merely be passing the connect and fire behaviors onto the already existing functionality of the object. This is quite simple, actually:

The key benefit here: since we’re delegating the responsibility of calling the connected functions to the BindableEvent, it can handle the threading as well. Connected functions that yield will not block other connected functions, so this is a primary benefit from the first simple solution. Note the use of ...  to represent “any and all variables passed to this function”. We’ll talk more about it in part 4.

There is still a problem with this, though. BindableEvents were originally created for inter-script communication. When it comes to tables, there’s some “gotchas”. From the Roblox Wiki article on BindableEvent:

If a Table is passed as an argument to a BindableEvent it must be an array without missing entries or have string keys, not a mixture, or else the string keys will be lost.

This is a big drawback to using a BindableEvent. Your tables will not be the same table as the one you fired the event with. Here’s an example demonstrating this drawback:

You can see that we are firing the event with my_table , then in the connected function comparing that table to (supposedly) itself. This shouldn’t ever throw an error, but it always does due to the described limitations of the BindableEvent.

You’ll also lose metatables, too! So if you created an object using our object-oriented tricks from the previous tutorial, you won’t have an object anymore. Just an empty shell of a table, no purpose, no meaning in life… definitely something you want to avoid.

You can see that the key benefit of letting Roblox handle the threading with the cost of the BindableEvent’s limitations is definitely a trade-off. You can download the finished version of this Event class here. Is it possible to work around this limitation? Maybe. Click here to find out in part 4.

Event Systems in Roblox Lua (Part 2 – Simple Solution)

Click here for part 1 of this tutorial series. You should be familiar with the content discussed in my Object-Oriented Programming tutorial.

In part 1, I discussed the pattern of an event system and the need for it in Roblox code. We frequently find ourselves needing to define our own gameplay defined events. Let’s dive into a simple way we can implement this pattern. Let’s use the OOP tricks we know in Lua to create the shell of an Event class.

So, at surface level we need a way to store some number of functions. We should be able to add/remove those functions. Crazy idea: let’s just use a numerically-indexed table of functions. We’ll use table.insert  to connect a function, and to disconnect we’ll iterate over each index and remove the first we find that matches. When we fire the event, just iterate over the list of functions, calling them in order. Let’s code it up.

First, let’s add a connections  table to our event. This will be our table of functions.

Next, let’s write the fire function. Simple iteration using a numeric for-loop:

That oughta do it! Finally, let’s write the connect function.

Notice how I added a line that will throw an error if we sent no function to connect. Finally, we need to add a means to disconnect. Let’s return a table with a disconnect function as a key:

And there we have it! We’ll soon find out that there are some issues with this. For simple uses cases, this works fine. However, if any connected functions yield (yielding essentially means waiting, like announceGameOver does to display the message), the next connected function will not run until the yielding function completes. This is a problem in the part 1 example because we might want our map to clean up first, then the game to announce the final score. It depends on the order in which we connected our events.

This event system calls connected functions in the order they were connected. In other words, first-in-first-out (FIFO). We could reverse this order (last-in-first-out, LIFO) by iterating backwards in the fire function:

Alternatively, we could just insert new functions at the beginning of the list in connect by specifying index 1 as the insertion point in the table.insert  call:

This is actually less efficient as Lua must move all the existing functions in the table over by 1 to make room for the one we’re inserting. For the computer science nerds in the audience, this is an O(n) operation. Simply inserting to the end of the list would be O(1). It goes without saying that making both of these changes (reverse iteration and insertion at the front of the list) would cancel each other out and we’d be back to original behavior of FIFO.

If this solves your problem, then I have good news: you can stop here. As for the rest of us, we have more options to explore in part 3. You can download the finished version of this Event class here.

Click here to advance to part 3 of this tutorial series.