Discord: Start an Activity
How to level up the team spirit in your company!
Game production, Technology
In this article we share our experiences with Unity's Data Oriented Technology Stack (DOTS) while working on our DOTS project “Recursive Reckoning”. We also show some simple code examples from our project to give a first insight into the newly released features of Unity.
Gameplay
The idea of Recursive Reckoning was to create a tower defense game with a top-down perspective fixed in the middle, potentially offering infinite zoom across multiple levels, although we have currently limited the zoom to 5 levels. The player can also rotate the camera 360° and tilt it downwards up to 45°.
Due to the infinity aspect, it is not suitable to build each level ten times the size of the previous one. This approach would very quickly reach the limits of the game engine. A better option is to separate the visual representation from the simulation by building all levels with a scale of 1 next to each other in the world. Then the visualizations are moved and resized to fit their presentation layer. For example, the first level is displayed with a scale of 0.1, the second with a scale of 1 and the third with 10 times its simulated scale. In this way, the player can see up to 3 levels at the same time.
To allow the player to zoom out seamlessly, the displayed levels are swapped to show the next three levels. In the previous example, layers 0, 1 and 2 would be replaced by layers 1, 2 and 3. And of course, the camera position would be scaled down by a factor of 10 to provide a seamless experience. To hide the layer change perfectly, timing is crucial. Everything has to happen in the exact same frame. Thanks to the efficiency of DOTS, there is no noticeable drop in performance.
Because we use Unity's Entity Component System (ECS), the more than doubled amount of objects (entities) for the visual copies is not a big problem. ECS is very efficient when it comes to modifying the data of many entities. It breaks a game down into entities, components, and systems. Entities are nothing more than identifiers or containers that represent individual game objects. Components, on the other hand, contain the data and behavior associated with specific aspects of an entity, such as position, speed, or health. Finally, systems are responsible for processing and manipulating entities based on their components. The principles of ECS revolve around data-oriented design and separation of concerns. By coherently storing similar data in memory, ECS allows for efficient data processing, as the CPU can work with larger blocks of data simultaneously. This approach results in fewer cache misses and reduces latency in memory access. A key difference between ECS and object-oriented programming (OOP) lies in their fundamental design philosophy. OOP focuses on encapsulating behavior within objects, while ECS prioritizes data and divides behavior into systems. This separation allows for better performance optimization, scalability, and parallelism in game development. In implementation, ECS offers significant advantages over the traditional MonoBehaviour approach. By using ECS, it is possible to achieve better performance due to reduced memory fragmentation and improved cache utilization. Additionally, ECS facilitates parallel processing, enabling efficient use of multi-core CPUs. However, implementing ECS requires a shift in mindset and a different approach to organizing and managing game logic compared to the familiar MonoBehaviour-based implementation.
In the past few months, we have been working on a game project based on Unity's Data Oriented Technology Stack. The goal of the project was to experiment with and learn about DOTS, and perhaps even create a fun little game out of it. We mainly focused on the Entities Package (ECS), but we also utilized the Job System and the Burst Compiler wherever possible. Here are our top 5 aspects that we enjoyed the most while working with DOTS, based on our tower defense project 'Recursive Reckoning'.
It's not necessary to optimize every line of code to achieve a significant performance boost compared to mono-scripting. If the core principles of the ECS concept are followed, there won't be many performance issues in most games.
The ECS architecture organizes similar data in memory as contiguous blocks, allowing the CPU to easily access and process them as a whole without many memory jumps.
Here are two interesting videos that compare the performance of MonoBehaviour and DOTS in scripts:
Unity DOTS: Comparing performance and physics: How many Rigidbodies can Unity support ? [2020 DOTS Edition]
Unity DOTS encourages developers to apply clean and efficient programming practices. Unlike traditional approaches, where developers may rely on high-level abstractions, DOTS forces them to think about how they design and implement their systems. The data-oriented mindset promotes a more explicit and organized code structure, leading to improved readability and maintainability.
By breaking down game logic into smaller, reusable components, a clear separation of concerns can be ensured. This modularity enables better code understanding and facilitates the identification and resolution of emerging issues. Additionally, DOTS prevents unnecessary object allocations and promotes the use of fixed-size arrays, reducing memory fragmentation and improving overall performance.
Due to the ECS architecture, game mechanics will be highly reusable for other projects. For example, the health mechanic we created for our DOTS project, Recursive Reckoning, can easily be copied into a new project and is almost plug-and-play: it only requires attaching the authoring script to an entity, and it will have a health value that can take damage.
The health module consists of the following parts:
A major concern regarding DOTS is the fear that it will eventually replace the old Mono-scripting backend. However, the opposite is true. Unity's current plans for DOTS involve expanding the toolset available in the editor. It is already possible to use a hybrid approach, combining the strengths of both worlds.
By using a SystemBase class as an interface, it is possible to receive inputs from our user interface and input system, and, for example, create a new tower entity at the cursor position when the player presses the left mouse button.
The hybrid approach also uses authoring and baker scripts to convert GameObjects within subscenes into entities.
Here is an example of an authoring MonoBehaviour with its baker class:
public class HealthAuthoring : MonoBehaviour { public float value; } public class HealthBaker : Baker { public override void Bake(HealthAuthoring authoring) { var entity = GetEntity(TransformUsageFlags.Dynamic); AddComponent(entity, new HealthComponent {Value = authoring.value, Max = authoring.value}); AddBuffer(entity); } }
As one of the cornerstones of DOTS, the job system is also designed for efficiency, both in processing and programming. Among other things, it enables easy parallelization of tasks, making optimal use of modern multicore CPUs.
A special type of job is the Entity Job. These jobs process data for many entities efficiently. It is simply necessary to create a function that should be executed once for each entity and queue that function as a job for execution. The job system handles dependencies and execution order to avoid common issues in parallel programming, such as race conditions or deadlocks.
Here is a small example of a job:
[BurstCompile] public partial struct DestroyOnNoHealthJob : IJobEntity { public EntityCommandBuffer.ParallelWriter Ecb; private void Execute([EntityIndexInQuery] int sortKey, HealthAspect health) { if (!health.isReadyToDie) return; Ecb.DestroyEntity(sortKey, health.Entity); } }
[BurstCompile] public partial struct DestroyOnNoHealthSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate(); } [BurstCompile] public void OnUpdate(ref SystemState state) { var ecbSingleton = SystemAPI.GetSingleton(); new DestroyOnNoHealthJob { Ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter(), }.ScheduleParallel(); }
Unity has created some debugging tools for the editor to assist with DOTS development. They are available under Window > Entities. The "System" window, in particular, has been very helpful, as it shows the execution order of all predefined and custom systems. Additionally, the inspector has been upgraded to allow debugging of entities and their attached components.
All in all, I am very satisfied with the Data-Oriented Technology Stack, which was released in version 1.0 this year. I see myself playing around with DOTS, Unity Physics, and all the new features in the future, just because I enjoyed it this time. This technology offers great potential, and I am excited to see what will come next as it gains more attention and more developers take the opportunity to leverage the powerful tools it offers.
We hope you like our article and would like to invite you to share your thoughts and questions on the topic with us. If you have any questions, comments or feedback on the content of this article, please don't hesitate to let us know in the comments section. We're always happy to hear from our readers and engage in meaningful discussions about game development.
Just ask us anything you want to know and we'll do our best to provide the answers you're looking for. Thank you for your support and we look forward to hearing from you!
How to level up the team spirit in your company!
VR attractions are special facilities where visitors can have immersive virtual reality experiences. In this article, we will focus primarily on…
Unity modernizes the user interfaces and functionalities for developers. UIToolkit is intended to be a simplified solution for UI element creation and…
Write comment