Event System
Events are a crucial part of any game, this includes the Massive Loop. The majority of events can be traced back to player interactions. However, the scripts also can raise events based on some criteria.
The Lua Event System in Massive Loop is a versatile tool used to manage and communicate not only with other parts of the game but it can also be used to synchronize the game between all players over the network.
It's all about the handlers
Handlers: functions that will get executed when an event triggers.
In Massive Loop, the only way to create an Event is to attach handlers to it.
Attaching handlers: Specifying the function that will execute when an event triggers.
When attaching a handler to an Event, the system keeps a record of the specified event name and the functions to execute. Adding more handlers to that event just adds them to that record.
Raising an event is pointless if there are no handlers to handle the event.
Adding Handlers:
To add a handler to an event, use the following method (from API)
- C#
- Lua
EventToken MLEvents.AddHandler(string eventName, Action<object[]> handler);
LuaEvents:Add(eventName, handler);
The eventName
is a string that defines the name of the event.
handler
is a function that will be executed when the event triggers.
- C#
- Lua
using ML.SDK;
using UnityEngine;
public class Script : MonoBehaviour
{
private void Handler(object[] arguments)
{
// code to run
}
void Start()
{
MLEvents.AddHandler("AnEventName", Handler);
}
}
function handler()
Debug.log("The event triggered");
end
LuaEvents.Add("AnEventName", handler)
Note that both in Lua and C#, like many other programming languages, you can pass the reference to a function by typing its name without parenthesis.
You can add many handlers to same event:
- C#
- Lua
using ML.SDK;
using UnityEngine;
public class Script : MonoBehaviour
{
private void Handler1(object[] arguments)
{
// code to run for handler 1
}
private void Handler2(object[] arguments)
{
// code to run for handler 2
}
void Start()
{
MLEvents.AddHandler("AnEventName", Handler1);
MLEvents.AddHandler("AnEventName", Handler2);
}
}
function handler1()
Debug.log("Handler 1");
end
function handler2()
Debug.log("Handler 2");
end
LuaEvents.Add("EventName", handler1);
LuaEvents.Add("EventName", handler2);
Keep handlers lightweight in terms of computation. If there are 1000 handlers attached to an event, all those handlers will get executed in one single frame when the event triggers. This may cause performance issues.
It is recommended to keep the number of handlers per event as low as possible.
If a handler requires you to initiate a costly computation, you can have a bool value in your script in which the handler will set to true. Then, in update(), check for that variable and if it's true, start a coroutine to perform the calculation.
- C#
- Lua
using ML.SDK;
using System.Collections;
using UnityEngine;
public class Script : MonoBehaviour
{
private void Handler(object[] arguments)
{
StartCoroutine(CoroutineFunction());
}
private IEnumerator CoroutineFunction()
{
// perform heavy computations here
// don't forget to call yield
yield return null;
}
void Start()
{
MLEvents.AddHandler("AnEventName", Handler);
}
}
do -- entity
local entityScript = LUA.script;
local startComputation = false;
function handler()
startComputation = true;
end
function entityScript.Start()
LuaEvents.Add("anEvent", handler);
end
function entityScript.Update()
if startComputation then
entityScript.startCoroutine(heavyComputation);
end
end
local function heavyComputation()
startComputation = false;
-- perform heavy computations here
-- don't forget to call yield
-- ...
coroutine.yield();
-- ...
end
end
Raising the events
An event can be raised in different ways.
One way to raise an event is by its name. You can call the Invoke
method (From any script) to raise an event. For example:
- C#
- Lua
- C#
- Lua
MLEvents.Invoke("anEventName");
LuaEvents:Invoke("AnEventName");
You can pass optional parameters in the invoke function which will get passed to a handler function.
For example, you can define the following handler:
- C#
- Lua
using ML.SDK;
using UnityEngine;
public class Script : MonoBehaviour
{
private void Handler(object[] arguments)
{
Debug.Log($"I received the message {arguments[0]}");
}
void Start()
{
MLEvents.AddHandler("AnEventName", Handler);
}
}
function handler(message)
Debug.Log("I received the message"..message);
end
LuaEvents.Add("onMessageReceived", handler);
And invoke it like this:
- C#
- Lua
MLEvents.Invoke("AnEventName", "Hello there!");
LuaEvents.Invoke("onMessageReceived", "Hello there");
You can raise an event over the network as well.
Every handler that is attached to an event can be raised over the network. You don't need anything else.
It is important to remember that all of the clients (players who joined the room) will have access to all Lua scripts.
It's important that the handler must be registered before raising an event.
To raise an event over the network, you should call InvokeNetwork
of these methods instead of plain invoke()
.
For more detail about networking events, please check out this tutorial on multiplayer and networking.
Removing handlers
To remove a handler, you can call the LuaEvents.remove()
function. All registered handlers can be traced by their LuaEventId
's. The LuaEventId
is a unique identifier for a handler that is attached to a specific function.
The Add function returns a token for the attached handler which can be used to remove it later.
- C#
- Lua
using ML.SDK;
using UnityEngine;
public class Script : MonoBehaviour
{
EventToken eventToken;
private void Handler(object[] arguments)
{
Debug.Log($"I received the message {arguments[0]}");
}
void Start()
{
eventToken = MLEvents.AddHandler("AnEventName", Handler);
}
private void OnDisable()
{
MLEvents.RemoveHandler(eventToken);
}
}
function handler()
Debug.Log("event");
end
myHandlerId = LuaEvents.Add("eventName", handler);
To remove the handler define above,
LuaEvents.Remove(myHandlerId);
Although small, all registered handlers occupy a space in memory. Remove unnecessary handlers.
For the Lua scripts, try to add all your event handlers in the start function.
Local events
A Local event only defined with in the script and the game object. So, for example lets imagine a local event called testEvent
within a Lua script test.lua
attached to game object A
. The scope of this event is only the combination of the object A
and test.lua
. Same event name in same script attached to a different object will be different from this event. Or a local event with same name in object A
with in a different script would in a separate scope.
Local Events over the network!
Ironically, the local events can be raised over the network! In this case, the scope of the local event is within the same game object + Lua script combination that existed in the scene during the build time, or the instantiated prefab instance with MLSynchronizer.
Handling UI Events (Lua Only)
The Lua Event System can be used to register events from UI. The concept of registering a handler for UI events is exactly the same. In any script, you can register a handler for the events.
For example, consider following sample UI Canvas with a panel and a Button.
Registering UI event handlers
To add the event handler, like previous scenarios, we add the event handler first (in any script)
do -- UI
local UIscript = LUA.script;
function onButtonClick()
Debug.Log("Button clicked");
end
function UIscript.Start()
LuaEvents.Add("onMyButtonClick", onButtonClick);
end
end
Again, so far it is the exact process.
Raising UI events
We need to connect the Canvas element to the Massive Loop Lua Event system. In order for the UI to register the user interactions in VR, we must add the SDKUI
component to our canvas.
To do this, Select your canvas from the scene objects hierarchy, and in the inspector click on Add Component
then select MassiveLoop > UI > SDKUI
.
As you may notice, adding SDKUI
component also adds a LuaUIEvent
component automatically. This component can be used to raise UI events.
In our example, we want to register the click from the button, and raise the event we named "onMyButtonClick"
.
In order to do that, first, choose the Button from the scene hierarchy and find the On Click () box in the inspector.
Click on the +
.
Here, we need to drag the Canvas object (Which has the LuaUIEvent
component on it) and drop it to the empty field.
Now, from the function drop down, select LuaUIEvents > CallLuaEvent(string)
.
Now on the empty field that opened, we can write the name of the event we want this button to trigger.
Done! Now whenever a user clicks this button, all the handlers registered under name of "onMyButtonClick"
will be executed.
A word on event names
As we saw in previous sections, the event names are strings, which technically allows the event names have a little more flexibility. Although it is allowed to go wild on naming the event names, we recommend selecting a naming convention similar to camelCase, snake_case or, etc. that suits your project.
Under the hood, the event names are stored in a hash table. There is no string comparison used.