Menu
Lumberyard
Developer Guide (Version 1.11)

Writing Lua Scripts for the Component Entity System

You can use Lua in Lumberyard to facilitate quick iteration of your game project. Lua is a powerful, fast, lightweight, embeddable scripting language. When you construct new gameplay and game systems, you can run your changes immediately, without compiling your source code.

Learning Lua

For learning the Lua language itself, the lua.org website is a good place to start.

Learning Lua in Lumberyard

After you read through this tutorial on writing Lua scripts for the component entity system, learn more about using Lua in Lumberyard by consulting the following resources.

  • For information on Lumberyard's built-in Lua editor, see Lua Editor.

  • For sample Lua scripts, see the Lumberyard \dev\SamplesProject\Scripts directory and its subdirectories.

  • For information about Lua API operations in Lumberyard, see the Component Entity Lua API Reference.

  • For information about the Lumberyard EBus, see Event Bus (EBus).

Note

Starting with Lumberyard 1.8, Lua scripts use the new behavior context that replaces the legacy script context. Scripts that were written before the integration of the behavior context no longer work in Lumberyard versions 1.8 and later. For information on updating code from legacy script context to the new behavior context, see the migration notes for Lumberyard 1.8. For information on the behavior context, see Behavior Context.

Adding Lua Scripts to Component Entities

Lumberyard makes it easy for you to add script functionality to your game entities by using the Lua Script component. The following steps show you how to do this in Lumberyard Editor.

To add a Lua script to a component entity in Lumberyard Editor

  1. With the Entity Inspector view pane visible, select the entity in the viewport.

  2. Click Add Component, and then open Scripting, Lua Script.

    
          Click Add Component
  3. Scroll down to the Scripting section, and then click Lua Script.

    
            Click Lua Script
  4. A Lua Script component appears in the inspector. Use the file selection button (...) to select the Lua script from the file hierarchy that you want to use.

    
          Lua Script component

    You can select either a .lua file (a text copy of the original), or a .luac file (a precompiled version of the script). The functionality should be the same. The precompiled version is preferable because it loads faster and is usually smaller. However, you can use.lua files if you experience any issues.

  5. After the script is loaded, click Edit Script ({}) to launch the Lua IDE and make changes to your script.

Basic Structure of a Component Entity Lua Script

Scripts to be used as components contain a table (referred to as the script table), which provides the functionality for the script. In Lua, this table is treated like a class. The script table generally consists of the following:

  • An optional Properties table within the script table. The Properties table provides an interface that you can use to customize the script behavior from the editor.

  • An OnActivate() function that the engine calls when the entity that has the script is activated.

  • An OnDeactivate() function called by the engine when the entity that has the script is deactivated.

The following example shows a skeleton script.

Copy
-- ScriptName.lua local ScriptName = { Properties = { -- Property definitions } } function ScriptName:OnActivate() -- Activation Code end function ScriptName:OnDeactivate() -- Deactivation Code end return ScriptName

For each Lua script component, Lumberyard creates a table called the entity table. The script table in the referenced script is the metatable for the entity table. Because of this relationship, when any method in the script is called, the self parameter (implicit in most cases) refers to the entity table.

The entity table then has the following properties and methods available to it:

  • A Properties table, copied from the script table's Properties table. Default values are provided where appropriate.

  • An entityId property, which contains an object of type EntityId that refers to the current entity.

  • An IsMaster function, callable by the script, to check whether the currently executing script is on the master node or a proxy node. This function is available only if the script component is network enabled.

Built-in Types and Methods

The Lumberyard engine provides a number of types and methods that are useful for making games. Many of the types and methods available are listed in the class view available in Lumberyard's Lua IDE. For more information on the class view, see Lua Editor.

Properties

The Properties table configures the editor interface for customizing the behavior of a script. With the properties table, users can modify numeric values, select states, and turn flags on and off. The table can even provide a reference to entities that your script can interact with.

The properties inside the Properties table are exposed to the editor. Properties outside the Properties table are private and not displayed in the editor.

The following example is a properties table from the Controllable Chicken sample level.

Copy
-- Example Properties Table local ChickenMannequinControllerSM = { Properties = { MoveSpeed = { default = 3.0, description = "How fast the chicken moves.", suffix = " m/s" }, RotationSpeed = { default = 360.0, description = "How fast (in degrees per second) the chicken can turn.", suffix = " deg/sec"}, CameraFollowDistance = {default = 5.0, description = "Distance (in meters) from which camera follows character."}, CameraFollowHeight = {default = 1.0, description = "Height (in meters) from which camera follows character."}, CameraLerpSpeed = {default = 5.0, description = "Coefficient for how tightly camera follows character."}, Camera = { default = EntityId() }, InitialState = "Idle", DebugStateMachine = false, }, ...

The result is the following Properties user interface in Lumberyard Editor:


        Properties in Lumberyard Editor defined by the Properties table

The type that you provide as the default value determines how the property is appears in the editor user interface. You can further customize the representation of the property in the editor by specifying additional attributes in a table format. All property types support a description field that appears when you pause your mouse on the property name in the editor.

Supported Types

Properties can have the types described in this section.

Boolean Values (True, False)

The following examples are Boolean values.

Copy
DebugMovement = false, AllowMovement = { default = true, description = "Allow or restrict movement of the object." },

In Lumberyard Editor, Boolean values are represented by a check box.

Numeric Values (Integer or Floating Point Numbers)

The following examples are numeric values.

Copy
Count = 5, Velocity = { default = 1.0, suffix = "m/s", description = "Initial Velocity Of The Object" }, Distance = { default = 5.0, min = 2.0, max = 10.0, step = 2.0, suffix = "m", description = "The Distance An Object Can Travel In Meters" },

In Lumberyard Editor, numeric values are represented by an edit field with increase/decrease arrows. Numeric values can do the following:

  • Provide a custom suffix to indicate units.

  • Set minimum and maximum values.

  • Provide a step value (how much the value increases or decreases when the user clicks the arrows on the right side of the edit field).

Strings

The following examples are strings.

Copy
DebugPrefix = "d_", Name = { default = "Default Name", description = "The name of the entity" }, StartingState = { "Idle", description = "Specify the starting state. Valid starting states are Idle and Fidget" },

In Lumberyard Editor, string values are represented by a text edit box.

Reflected Classes

You can use any class that is reflected to both the BehaviorContext and the EditContext as a property. A good example of this is the EntityId type, which references other entities.

Copy
-- Entity Examples ParentEntity = { default = EntityId(), description = "The Entity that this one will follow"}, Target = EntityId()

The editor representation is the default editor for the type reflected. For example, for EntityId, it's the entity reference picker. For most reflected types, it is a tree of the type's properties.

Arrays

Properties can contain resizable arrays of any of the types mentioned. To create the array, declare the default value as a keyless table of values. For example, the property definitions in the following code produce the properties shown in the image that follows.

Copy
local ExampleScript = { Properties = { Speed = 4, ExampleArray = { default = { 1, 2, 3, 4 } }, } } return ExampleScript

            Property array

Attributes

You can add attributes to a property by placing them alongside the default value in a property table. Attribute keys are not case sensitive. The following common attributes can be added to any property.

Common Attributes

Attribute Description
Description A string that is the text of the tool tip for the property.
UI Specifies (overrides) the UI handler that the property uses.

Network Binding

For network binding features to function, you must have a Net Binding Component.

Properties

You can configure networking binding for properties by adding the netSynched table to the description of the variable inside of the Properties table.

Copy
local ExampleScript = { Properties = { Speed = { default = 0, -- Supports numbers, strings, booleans, and nils for net bindings. min = 0, max = 100, step = 1, description = "Speed in m/s for the ...", -- If this table is missing, it is assumed the value is not networked. netSynched = { -- Optional fields OnNewValue = <function> -- OnNewValue is called whenever the property has a -- new value. OnNewValue accepts one parameter,which -- is the entity table for the instance that changed. -- The following flags are mainly here for debugging and profiling convenience. Enabled = true -- Controls whether the field is network enabled. If -- missing, assumes true. ForceIndex = [1..32] -- Profiling helper tool to force a property to use a -- specific DataSet to make understanding what data is -- being used where easier. }`` } } } return ExampleScript

After you add networking to a property, any changes to the property are reflected across the network.

RPCs

Exposing RPCs to scripts involves creating a new table inside of the component table, but outside of the properties table, as shown in the following example.

Copy
local ExampleScript = { Properties = { -- ... }, -- Table of remote procedure calls (RPCs) that the script wants to implement. NetRPCs = { RPCNoParam = { OnMaster = <function> -- The function to be called on the Master Script. -- The function should return a bool value that -- indicates whether or not proxy components can -- execute the RPC on themselves. Required. OnProxy = <function> -- The function to be called on the Proxy Script. -- This function is optional and can be excluded if -- the master never allows proxies to execute the function call. } } } return ExampleScript

You can invoke the RPC just like any other function. There is no need to specify the OnMaster/OnProxy from the calling script. For example, you can call RPCs as in the following example.

Copy
self.NetRPCs.RPCNoParam() self.NetRPCs.RPCParam(1.0)

Communicating with Components

Components provide interfaces that allow scripts to send them information and receive notifications when certain actions take place. Communication is established by creating two different objects in Lua: senders and handlers. A sender or a handler is an interface to an Event Bus (EBus), a communication system used extensively in the Lumberyard Engine. When a sender is created, it can call functions, which in turn send information to a component. When a handler is created, the component calls certain functions that the Lua script defines. These senders and handlers are created with an entity ID. You can use the entity ID to communicate with components that are attached to entities other than the one the script itself is running on. The main script table always provides a field called entityId that contains the ID of the entity to which the script is attached. Other entity IDs can be passed to the script through the Properties interface.

Order of Component Activation

Keep in mind the following points regarding the order of activation of Lua components:

  • Lua components are activated after all C++ components have been activated.

  • If an entity has multiple Lua components, there is no guarantee regarding which Lua component is activated first.

Registering with a Component to Receive Notifications

When a Lua script creates a handler object, it notifies a component attached to an entity that it should call the script handler functions when certain events occur. For example, in the first sample below, the script creates a Spawner Component notification bus handler when OnActivate() is called. This tells the spawner component attached to the entity that has the script to call the OnSpawnBegin(), OnSpawnEnd(), and OnEntitySpawned() functions when the spawner instantiates a new dynamic slice. Subsequently, the handler is explicitly disconnected and set back to nil in the OnDeactivate function. This ensures that processing time is not wasted when the entity attached to the script isn't active. As long as the entity is active, these functions are called by the spawner component at the appropriate time.

The following code example shows a spawner component handler.

Copy
local SpawnerScriptSample = { } function SpawnerScriptSample:OnActivate() -- Register our handlers to receive notification from the spawner attached to this entity. if( self.spawnerNotiBusHandler == nil ) then self.spawnerNotiBusHandler = SpawnerComponentNotificationBus.CreateHandler(self, self.entityId) end end -- This handler is called when we start spawning a slice. function SpawnerScriptSample:OnSpawnBegin(sliceTicket) -- Do something so we know if/when this is being called Debug.Log("Slice Spawn Begin") end -- This handler is called when we're finished spawning a slice. function SpawnerScriptSample:OnSpawnEnd(sliceTicket) -- Do something so we know if/when this is being called Debug.Log("Slice Spawn End") end -- This handler is called whenever an entity is spawned. function SpawnerScriptSample:OnEntitySpawned(sliceTicket, entityId) -- Do something so we know if/when this is being called Debug.Log("Entity Spawned: " .. tostring(entityId) ) end function SpawnerScriptSample:OnDeactivate() -- Disconnect our spawner notificaton if self.spawnerNotiBusHandler ~= nil then self.spawnerNotiBusHandler:Disconnect() self.spawnerNotiBusHandler = nil end end return SpawnerScriptSample

Noncomponent Notifications

There are event buses that are available to Lua that are not associated with components. For example, a script can create a handler to receive notifications from the system's tick bus whenever the engine ticks. It provides both the amount of time that has passed since the last tick and the current time point. To gain access to this information, the script simply implements the OnTick() function and creates the handler.

The following example shows how to register an EBus.

Copy
local TestScript = { } function TestScript:OnActivate() -- Inform the tick bus that you want to receive event notifications self.tickBusHandler = TickBus.CreateHandler(self) self.tickBusHandler:Connect() end -- This callback is called every frame by the tick bus after this entity activates function TestScript:OnTick(deltaTime, timePoint) -- Add script to be executed every frame here... end function TestScript:OnDeactivate() -- Inform the tick bus that you no longer want to receive notifications self.tickBusHandler:Disconnect() end return TestScript

Sending Events to a Component

In addition to receiving notifications from components, a script must sometimes exercise control over components. Control is accomplished by sending events to components using the Event table and calling the functions implemented on it. In the example script that follows, the spawner component is sent an event that tells the component to spawn a dynamic slice by calling the Spawn() function. The first argument to an Event function is always the ID of the listener that you send the event to; the remaining arguments follow.

The following example shows how to send EBus events.

Copy
local SpawnerScript = { } function SpawnerScript:OnActivate() SpawnerComponentRequestBus.Event.Spawn(self.entityId) end return SpawnerScript

You can request information from some event sending functions that return values. The next example script uses a TransformBus to get the current local transform of the entity and uses the GetLocalTM() function, which returns a transform object. This object is stored in a variable in the main script table. TransformBus is used again to reset the transform of the object to the identity.

The following example shows how to use the transform bus.

Copy
function samplescript:OnActivate() -- Retrieve the object's local transform and store it for later use self.myOldTransform = TransformBus.Event.GetLocalTM(self.entityId) -- Reset the object's local transform to the identity matrix TransformBus.Event.SetLocalTM(self.entityId, Transform.CreateIdentity()) end

Communicating with Components Attached to Other Entities

You can also send events and create handlers to communicate with components that are attached to other entities. The following example defines a parent entity in the properties table and requests its transform. This allows it to set its transform to that of another entity.

The following example code shows the use of a parent entity.

Copy
local ParentScriptSample= { Properties = { ParentEntity = { entity="" }, } } function ParentScriptSample:OnActivate() if self.Properties.ParentEntity.IsValid() then local parentTransform = TransformBus.Event.GetLocalTM(self.Properties.ParentEntity) TransformBus.Event.SetLocalTM(self.entityId, parentTransform) end end return ParentScriptSample

Using AZStd::vector and AZStd::array

Vectors and arrays in Lua behave very simarly to tables, with a few limitations. Both vector and array have the following features.

Length Operator #

You can obtain the length of a collection by prefixing the name of the collection with the length operator #, as in the following example.

Copy
#myCollection

Indexing []

To obtain the elements in a collection, use indexing in square brackets as the following syntax shows. Indexing is 1 based, just like Lua tables.

Copy
myCollection[index]

Vector also has the following methods for mutating the collection.

push_back

Use the push_back method to append elements to the vector, as in the following example.

Copy
myCollection:push_back(5)

pop_back

Use the pop_back method to remove the last element of the vector, as in the following example.

Copy
myCollection:pop_back()

clear

Use the clear method to remove all elements from the vector, as in the following example.

Copy
myCollection:clear()

Using AZStd::any

You can pass any Lua primitive type excluding tables to any bus or function that takes AZStd::any as a parameter (for example, GameplayNotificationBus::OnEventBegin). You can also pass any type reflected from C++ (for example, vectors or EntityId values). There is no syntax required to pass a value as an any—just call the bus or function.

The following example shows the use of AZStd::any.

Copy
GameplayNotificationBus.Broadcast.OnEventBegin(self.eventId, "The value I'd like to pass to the handler")

Debugging Scripts

Lumberyard provides Lua scripts with several functions to make debugging easier.

Logging to the Console

To print text to the Lumberyard Editor and game console, use the Debug.Log() function.

The following example shows the use of the Debug.Log() function.

Copy
local LoggingTest = { } function LoggingTest:OnActivate() componentName = "MyComponent" Debug.Log(ComponentName .. " has been activated.") end return LoggingTest

Using an Assert to Detect Potential Issues

You can use the assert() or Debug.Assert() functions to display an error message in the console when conditions are detected that might result in an execution fault. The assert functions take two arguments: a condition that evaluates to true or false, and a message to display if the condition is false.

The following example shows the use of the assert and Debug.Assert() functions.

Copy
function SampleScript:DoStuff() -- This value should never be negative assert( self.positiveValue >= 0, "Expected a positive value! Got: " .. self.positiveValue ) end -- Console output when the value of self.positiveValue is -5: -- [Error] Lua error (2 - [string "q:/lyengine/branches/systems/dev/samplespro..."]:61: Expected a positive value! Got: -5) during call samplescript:DoStuff -- ALTERNATIVE SYNTAX: function SampleScript:DoStuff() -- This value should never be negative Debug.Assert( self.positiveValue >= 0, "Expected a positive value! Got: " .. self.positiveValue ) end -- Console output when the value of self.positiveValue is -5: -- [Error] Assert on argument 0: Expected a positive value! Got: -5

Communicating Errors

You can use the Debug.Error() function to display an error in the console and halt execution of the current script function. This does not halt all execution of the script. If you have active handlers, they can still be called when the engine posts notifications. The Debug.Error() function takes arguments similar to the Debug.Assert function: a condition and a message. The message is displayed in bright red and execution halts only if the condition is false.

The following example shows the use of the Debug.Error() function.

Copy
function SampleScript:CheckAndError() -- This value should never be negative Debug.Error( self.positiveValue >= 0, "Detected a negative value: " .. self.positiveValue ) end -- Console output when the value of self.positiveValue is -5: -- [Error] Error on argument 0: Detected a negative value: -5

Displaying a Warning When User Attention Is Required

A script condition can occur that does not adversely affect the execution of the script but might be useful for the user to know about. The Debug.Warning() function uses arguments similar to those of the Error and Assert functions but just displays an orange warning message in the console. It does not halt execution.

The following example shows the use of the Debug.Warning() function.

Copy
function SampleScript:CheckValue() -- This value should probably never be negative Debug.Warning( self.positiveValue >= 0, "Detected a negative value: " .. self.positiveValue ) end -- Console output when the value of self.positiveValue is -5: -- [Warning] Warning on argument 0: Detected a negative value: -5

The Lua Environment (Advanced)

By default, the Lumberyard component entity Lua environment is a single Lua environment (or lua_State). This environment is bound to the BehaviorContext that is owned by the ComponentApplication. Because of this, it has access to all API operations that are reflected on startup.

Adding Other VMs

You may add more ScriptContext instances using the ScriptSystemBus (either call AddContextWithId, or create your own and call AddContext). If you want your new context to be available for debugging, you must register it with ScriptDebugAgentBus::RegisterContext.

Reusing Code

Lua provides the capability to load and execute scripts from other Lua files using the built-in Lua require function. It's important to note that this function requires a special path format. The file path is delimited by periods instead of slashes, has no .lua file name extension, and is relative to the Lumberyard assets directory. For example, if you want to use the require function to give your scripts some common functionality from the project's Scripts directory, you can use code similar to the following example.

Copy
local library = require("Scripts.MyLibraryFile")