-
-
Notifications
You must be signed in to change notification settings - Fork 13
Blueprints
A Blueprint
offer's a developer a way to design groups of components that they can apply to an entity at will. Blueprints can be configured as a typical MonoBehaviour
-derived component or a ScriptableObject
.

Each blueprint can contain a single instance of a distinct component type that context is associated with. When applied to an entity, a blueprint copies over all of the data from its local components to the passed entity as new components that either replace existing components if already present or added as entirely new components This allows composition of an entity's data to be potentially constructed from one or more blueprints if desired.
Example Blueprint API Usage
using UnityEngine;
namespace ExampleContent.Blueprints
{
internal sealed class SceneBlueprintExampleController : MonoBehaviour
{
[SerializeField]
private VisualDebugBlueprint _blueprint;
private void Awake()
{
// Get the relevant context for this type of blueprint, spin up ten entities and apply the blueprint to
// them. This results in all of those entities having the same components from the blueprint.
var context = Contexts.SharedInstance.VisualDebug;
for (var i = 0; i < 10; i++)
{
var entity = context.CreateEntity();
_blueprint.ApplyToEntity(entity);
}
}
}
}
When creating a scriptable blueprint, a context-specific create menu item is available when right-clicking in the project folder at Create => EntitasRedux => _ContextName_ => Blueprint
. A similar [AddComponent]
menu path exists for adding a blueprint behaviour component to a GameObject
.

Each defined-context has their own blueprint classes generated via code-generation. For example, a context named 'VisualDebug` will generate the following code for blueprint support.
-
VisualDebugBlueprintBehaviour: A blueprint behaviour is a
Monobehaviour
-derived component useful for creating a blueprint as part of a scene or prefab. -
VisualDebugBlueprint: A blueprint is a
ScriptableObject
-derived type useful for creating a blueprint as a scriptable asset in the project. - IVisualDebugBlueperint: An interface containing the methods that both of the above types share.
Blueprints take advantage of the native Unity [SerializeReference]
attribute to enable polymorphic serialization of components as a list of IComponent
instances. What this means is that Unity ultimately is doing the work to serialize components on a blueprint and all of the usual rules around Unity serialization also apply here (the list of constraints below is not exhaustive, but illustrates some of the common gotchas around this topic).
- Components must be decorated with
[Serializable]
in order to be used on a blueprint for both serialization as well as the inspector UI which filters out all non-serializable components. - Components with non-serializable members such as native C#
Dictionary<T,TV>
,HashSet<T>
, etc... are not supported for serialization on blueprints, even if marked as serializable and show up in the UI as additional custom serialization work is needed from the developer to make those types support native Unity serialization.
An additional constraint for components to be usable by blueprints is that they must have a default constructor; this enables the blueprint inspector to instantiate them on demand from a developer.
When components are copied from a blueprint to an entity, each one is copied based on it's CopyXTo
method where X is the name of the component. The logic used in this method for copying each member differs based on the type's implementation. In general, copying each member follows these rules.
- At a minimum and assuming none of the rules below apply, all members will be shallow-copied, meaning they will be directly assigned to the same fields on the new component. All value types, structs, and strings will always be copied by assignment, whereas reference-types will be copied this way only if none of the rules below apply.
- To support deep-copying of reference types on a component, if any of the reference type public members of a component implement
ICloneable
, it'sClone
method will be called in place of a shallow assignment. In this way, a developer using EntitasRedux is responsible for adding deep-copy support for the reference types used as members on components they implement. - If a member type is a
List<T>
and theT
element type of the list does not implementICloneable
, a shallow copy of the list and contents are created and assigned to the copy component. If theT
element type does implementICloneable
, a deep-copy of the list and it's elements are created and assigned to the copy component. - If a member type is a
Dictionary<T, TV>
and theTV
value element type of the list does not implementICloneable
, a shallow copy of theDictionary<T, TV>
and contents are created and assigned to the copy component. If theT
value element type does implementICloneable
, a deep-copy of theDictionary<T, TV>
and it's contents are created and assigned to the copy component. In both cases, theT
type keys are always shallow-copied as these are considered immutable for the purpose of this copy logic. - If a member type is an
T[]
and theT
element type of the array does not implementICloneable
, a shallow copy of the array and it's contents are created and assigned to the copy component. If it does implementICloneable
, the array is copied and then each element is deep-copied via it'sClone
method and assigned to the same index in the new array. Deep-copying of an element is supported for arrays up to four dimensions.
The intent of this logic is to place the primary responsibility for whether a reference type member on a component should be shallow copied versus deep copied onto the developer as they're likely to know best whether a particular type should be copied as shallow or deep. This also avoids needing to support creating overly complex deep-copying support via reflection or other methods. With that in mind, there are edge cases to note for this copying logic.
-
Collections of collections: When creating components that have members that are collections of other collections, it's likely that the child collections will always be shallow copied as
Systems.Collections.Generic
and other native collection types often implementICloneable
which returns a shallow copy of the collection and contents. If deep-copying of these kinds of structures are needed, it's recommended to create a new reference type that can wrapper the collections of collections member and have it implementICloneable
. In this way, the developer can take ownership of how this nested collection structure is deep-copied.
Bad Nested Collection Member Example
[Serializable]
public class SomeCustomObject()
{
public string name;
}
[Serializable]
public class BadNestedListComponent : IComponent
{
// This will always be shallow-copied.
public List<List<SomeCustomObject>> value;
}
Good Nested Collection Member Example
[Serializable]
public class SomeCustomObject()
{
public string name;
}
[Serializable]
public class SomeCustomObjectList : ICloneable
{
public List<List<SomeCustomObject>> value;
public SomeCustomObjectList()
{
value = new List<List<SomeCustomObject>>();
}
public object Clone()
{
var newList = new SomeCustomObjectList();
foreach(var list in value)
{
var innerList = new List<SomeCustomObject>();
foreach(var element in list)
{
innerList.Add(new SomeCustomObject { name = element.name});
}
newList.Add(innerList);
}
return newList;
}
}
[Serializable]
public class GoodNestedListComponent : IComponent
{
// This will be deep-copied via SomeCustomObjectList's Clone method.
public SomeCustomObjectList value;
}