Programming
Programming
Lua vs. C++
In getting comfortable with Lumberyard it required investigating the programming languages available to developers. The engine’s scripting language is Lua, and is available from the Editor. However, the other language available to developers is C++, which is embedded in the Lumberyard Gem system and engine code. Lua scripting has functionality exposed to it through the C++ code. Any game should be able to be developed with either or both languages, based on development needs.
Lua scripting is included within the Lumberyard Editor. Being an interpretive language, it’s great for prototyping and iteration, more so because it doesn’t need to be recompiled every time a change is made, and can be loaded in real time. It is easier to share with other developers because it only has one source file within a specific project. Lua is where the majority of your game logic should be located, because the engine shouldn’t be aware of your specific project. Developing in Lua is ideal for a small team, with a rapid turnaround time. The downfalls of Lua is that not everything is fully exposed or well documented at this time, especially because systems and documentation are being deprecated in every subsequent release. The language also isn’t as powerful/optimized as C++ so can cause performance issues in cases where C++ wouldn’t.
C++ isn’t included in Lumberyard’s Express Install and requires the option of compiling the game code to be checked and the engine recompiled. However, the language is the industry standard because it is so performant and can be well optimized. C++ source code can’t be created from the Editor, it requires you to create Gems and enable them through the Project Configurator. The C++ classes have access to all the engine code, since it is coupled with the source code, which can cause problems when game logic is entwined with the engine. However, one thing to keep in mind during programming with C++ is that Lumberyard currently does not have native support for hot reloading of C++ code. The way it is integrated into Lumberyard isn’t set for quick iteration, it requires the game code to be recompiled for each gem. This can also slow your team members down if the binaries aren’t shared and your team member needs to recompile the code/game as well. Mistakes made in your C++ won’t be determined until compilation, which isn’t ideal for rapid changes. While there are external tools available (both paid and unpaid) for hot reloading of C++ code, the integration is something the developers will have to take the burden of.
The option of using both is viable, such as using Lua scripts to contain all game logic and C++ to expose user-defined data structures and functionalities, and to optimize any Lua bottlenecks. In either case, a program creates a “component” (not identical, but similar to Lumberyard defined components) which can be attached with an entity in game. For Lua, the entity object which will be using the script must first be assigned a Lua Script Component and the script required attached to the component. For C++, the game needs to be compiled and the Gem activated for the project, which can then be associated with an entity.
Resources
- Recode
- Lumberyard Runtime Compiled Components
- Live++ C++ Live Coding
Event Bus / Gameplay Bus
The gameplay bus is the main way of communicating between scripts and entities through Lua. Entities cannot be found with a specific Lua script attached to them, so to communicate to the Lua script the script must know the EntityId that the Lua script is attached to. With the EntityId, messages can be sent through the Event Bus. The gameplay bus is the most generic Event Bus (EBus) to pass information around your game, however to have more specific events create EBus events in a C++ gem. In the case, the “Compile game code” option isn’t selected in the Setup Assistant, EBuses can’t be created so the gameplay bus is the only option.
Our experience with creating C++ EBuses is limited because at the time of learning Lumberyard there were limited resources available for creating these. In addition, there wasn’t a clear explanation of why these EBus events were necessary, which after creating a game we realized that Lua is more error prone to using the generic gameplay bus rather having contexts for each type of event.
For any event bus you are required to have an identifier, which for the gameplay bus is the gameplay notification id. The gameplay notification id requires three parameters: an EntityId (sending/receiving entity), a string with the event name, and the type of parameter to be passed. Prior to version 1.12 the parameter type wasn’t needed, in version 1.13 the typeid() function assisted in finding the id of a parameter; however, the parameter type doesn’t seem to influence the actual values that can be send or received the bus (it seems like a needless parameter in the current version). For a script to receive an event from another script/entity all 3 parameters must match (including the EntityId), otherwise the event will be sent to the wrong place or not recognized properly.
Resources
Serialization/Reflection
The reflection function will expose a C++ struct/class to the editor and/or Lua. Our knowledge of serialization is slightly superficial, and was mainly used to create structs for more compact Lua code. This can include variables and functions coded in C++ to be reused in Lua. The “Compile game code” option must be selected in the Setup Assistant to be able to create classes on the C++ side.
Suggested File Structure
To add a new class into the engine, the code can either be placed into the “Gems” folder of the project or in a engine gem. Within the “Code” folder, create a folder named “Components” which is where class files will be located. Each class category component needs a “.cpp” and “.h” file (for best practice). The class/struct are located within the “.h” file. Each class/struct needs an “AZ_TYPE_INFO” and “AZ_CLASS_ALLOCATOR” to serialize/reflect, however the class/struct doesn’t need its own “Reflect” function. The “.cpp” file has the “GetUuid” and “Reflect” functions, see Starter Game/other Gems for examples.
The other files to edit are: [gem_name].waf_files located in the “Code” directory and [gem_name]SystemComponent.cpp. In the waf file, create a components array with the header and cpp files of the created files.
"Components": [
"Components/[file].cpp",
"Components/[file].h",
],
In the System Component file, include the header files of the newly created classes. In the “Reflect” function, call the reflect function of the classes passing in the serialize context and/or behavior contexts.
Serialize and Edit Context
The serialize and edit context are required to expose parameters to the inspector in the engine. For the serialize context, use the function call “Field” to serialize class parameters. The edit context is more complicated/customizable, see the Lumberyard documentation for more details.
Behavior Context
The behavior context is how functions and parameters are exposed to Lua scripting. Functions can include constructors with different number of parameters, however cannot include the same number of constructor arguments. We have found, however, constructors don’t always work within the Lua Editor and reassigning the parameters after calling the constructor is needed.
Resources
Lua Scripting
Outlined is the structure of Lua script, which connects to a single EBus, the Tick Bus. Lua is fairly relaxed with regards to syntax, so it is recommended to have a style when creating a project and stay consistent. Amazon outlines the structure and format of a Lua script, with the links listed below, however examples can be more useful which can be found within StarterGame and SamplesProject. StarterGame shows how code can be integrated into a game project, where SamplesProject provides smaller, specific examples for a lot of functionality.
local required = require("scripts.path.to.file");
local ScriptName =
{
-- variables exposed to the editor
Properties =
{
},
-- table holding the connected event buses
Handlers =
{
},
}
-- called when the entity is spawned or game is started
function ScriptName:OnActivate()
-- connecting to an event bus, passing self as the object of reference
self.Handlers.Tick = TickBus.Connect(self);
end
-- called when the entity is deleted or game is ended/quit
function ScriptName:OnDeactivate()
end
-- called each tick (frame) when connected to the Tick Bus
function ScriptName:OnTick(deltaTime, scriptPointTime)
end
-- to expose the script to the editor
return ScriptName;
Occasionally, after creating a new script, the script won’t be available to select in the Lua Script component from the editor. This can happen because there is a syntax error in the script, or Lumberyard didn’t process/recognize the file. If the problem is due to the latter, to solve the issue, either duplicate the script through copy/pasting in the window explorer or close and reopen the editor. It is recommended that once you have the basic structure of the script, as outlined above, save the script and add it to the Lua component. This will not only stop the script from not showing up, but also be easier to debug since an error message will appear on the Lua component if there is one.
Resources
Lua Editor
The Lua Editor is a built-into Lumberyard but can also be docked to the start bar of your OS. It is opened in a new window, which can cause issues when left open and switching projects. We had run into the problem that if the Lua Editor was left open when the editor closed or crashes, the editor wouldn’t open again unless the Lua Editor was closed.
The Lua Editor is Lumberyard’s default editor, and recommended to program Lua scripts in because of the built-in functionality. The editor has debugging functionality, like most IDEs, but also has the panel labeled “Class Reference”. When the Lua Editor is connected to the Lumberyard Editor, the target is set to the Project Configurator, the Class Reference has classes, EBuses, and globals exposed to the Lua script for that specific project. Because of the lack of online documentation about Lua scripting, the Class Reference panel was invaluable in finding functions and understanding the expected parameters.
The Lua Editor is also is in beta so it has its flaws. The Lua Editor was sometimes not able to detect the Project Configurator as the target which removed most functionality of the editor, and made it a glorified text editor. The autocomplete functionality of the editor has weird behavior when the cursor is not at the end of the word to autocomplete, it will insert the remaining characters at the cursor rather than the end of the word.