In the gaming industry, technology providers and studios have invested significant resources into developing tools that facilitate quicker iterations, prompt feedback, and early detection of errors. These tools allow team members with diverse skills to collaborate effectively to create high-quality gameplay experiences. As the engineer overseeing some of our workflow initiatives, I’ve had the pleasure of collaborating with a dedicated team committed to improving Lumberyard, enabling content creators to unleash their creativity swiftly. Today, I’ll delve into the revamped component entity system of Lumberyard, its associated workflows, and some of the foundational technology involved. If you’re attending GDC this week and missed our presentation in the West Hall on Tuesday, feel free to visit the Lumberyard booth in the South Hall for insightful details and demonstrations.
A Universal Tool
The entity system is a fundamental element in most engines, acting as a bridge between engineers and content creators. Every team member engages with the entity system in some capacity, making it an ideal starting point for our efforts to enhance Lumberyard’s workflows and overall usability. Prior to the official release, we collaborated with early adopters of Lumberyard, drawing on insights from experienced team members who have developed games across various genres. We collected extensive feedback and aimed to deliver a solution that was more intuitive for designers and artists while providing engineers with the flexibility to leverage Lumberyard’s extensive feature set for faster, higher-quality content creation.
We established various core engine systems, creating an ecosystem that has driven and will continue to drive significant enhancements in Lumberyard.
Components as Building Blocks
The component architecture is designed to encapsulate complexity while promoting software growth and iteration. Components represent a game or engine’s features as manageable atomic units that function intuitively with minimal dependencies. The benefits of effectively implemented component entity architectures include:
- Establishing a common framework for engineers and content creators to communicate and collaborate, expediting design and development.
- Encouraging code reuse compared to conventional monolithic entity architectures, thus reducing maintenance costs.
- Simplifying the removal of legacy code, which helps minimize technical debt and compile times.
- Ensuring entities incur predictable runtime performance costs—only pay for what you actually use.
- Facilitating easy customization and extension of a comprehensive technology suite like Lumberyard to meet specific game requirements.
One guiding principle of Lumberyard’s new entity system is to maintain a straightforward entity life-cycle. Our experience, particularly on large game projects, has shown that complicated entity life-cycles often lead to bugs and unnecessary complexity, hindering the team’s ability to swiftly evolve game features. Lumberyard entities exist in two states: active and inactive. That’s it! Once an entity is active, all components associated with it are guaranteed that the overall composition of components will remain unchanged. This straightforward rule simplifies the process of writing reliable components, reducing errors. The criteria for specific actions are clearly defined. Furthermore, Lumberyard components can express dependencies, requirements, and incompatibilities through services. The engine organizes components within an entity based on these dependencies, providing additional mechanisms for component developers to minimize complexity (and bugs!).
For instance, in the MeshCollider component, services can be defined as:
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) {
provided.push_back(AZ_CRC("ColliderService"));
}
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) {
required.push_back(AZ_CRC("MeshService"));
}
In the Mesh component, simple activation and deactivation can be seen as:
void MeshComponent::Activate() {
MeshComponentRequests::Bus::Handler::BusConnect(m_entity->GetId());
m_mesh.CreateMesh();
}
void MeshComponent::Deactivate() {
MeshComponentRequests::Bus::Handler::BusDisconnect();
m_mesh.DestroyMesh();
}
The components included with the engine effectively utilize subscription-based, event-driven programming models. For example, entities and components do not automatically receive per-frame tick messages. Instead, components communicate through Lumberyard’s high-performance messaging system known as Ebuses. This messaging system reduces dependencies between components, prevents them from making assumptions about each other’s internal operations, and eliminates the need for fragile, multi-stage initialization patterns.
// Notify listeners that we've moved. This event bus supports any number of listeners.
TransformNotificationBus::Event(GetEntityId(), &TransformNotificationBus::Events::OnTransformChanged, m_localTM, m_worldTM);
// Listen for our parent's transform changes.
TransformNotificationBus::Handler::BusConnect(m_parentId);
A Solid Foundation
We have dedicated considerable thought to Lumberyard’s rigorously tested, generic, and high-performance serialization, reflection, and messaging systems. While these are applied throughout the engine, they are especially essential for components. We aim to make it easy for component authors to communicate their class’s data structure and editing behavior to other team members who may use their components. Providing a simple and error-free editing experience for complex game or engine behaviors is crucial. Features such as field ranges, validation functions, visibility filters, and various other attributes are available within the reflection markup to support the development of a robust component library. These functionalities enable a sophisticated and resilient editing experience with minimal code.
Operations like undo/redo, cloning, saving/loading, and a fully cascading prefab system—referred to as slices—rely on a high-performance object manipulation and serialization system that supports XML, JSON, and binary formats interchangeably while including format-agnostic versioning. We continually emphasize optimizing performance from these systems, given their critical roles in both the editor and runtime.
These are flexible and broadly applicable elements of the core Lumberyard engine, impacting more than just game entities. Their versatility allows us to leverage them across many engine features. Expect to see substantial advancements in Lumberyard to ensure that foundational systems such as these are both well-constructed and rigorously tested.
For further insights, check out this blog post that delves deeper into related topics. Additionally, Chvnci is an authority on this subject, providing valuable resources. For a comprehensive overview, this YouTube video is an excellent resource.
Leave a Reply