ContextExtensions.CloneEntity creates entity with shallow copied components
public static TEntity CloneEntity<TEntity>(this IContext<TEntity> context, IEntity entity, bool replaceExisting = false, params int[] indices) where TEntity : class, IEntity
{
TEntity entity1 = context.CreateEntity();
entity.CopyTo((IEntity) entity1, replaceExisting, indices);
return entity1;
}
I'm using prototype entities, which are cloned at runtime. So far I stored behaviour classes with readonly data inside component fields, so shallow copying kinda worked. But recently added BehaviourTreeComponent for AI and it needs to store some data.
What could be a solution to get entity deep clone? Or can I get out with storing POD components?
I solved it by changing IComponent, adding Component class and each component inherits from Component.
public interface IComponent {
void AfterCopyPublicMemberValues();
}
public class Component : IComponent {
public virtual void AfterCopyPublicMemberValues() {
}
}
Non POD members would implement ICloneable interface called in AfterCopyPublicMemberValues
public interface IBtNode : ICloneable<IBtNode> {
BtStatus Execute(Contexts contexts, Entity entity);
}
[BtC]
public class BehaviourTreeComp : Entitas.Component {
public IBtNode Root;
public override void AfterCopyPublicMemberValues() {
Root = Root.Clone();
}
}
--edited to use ```csharp code block and spaces
clonedComponent.AfterCopyPublicMemberValues() is called in PublicMemberInfoEntityExtension.CopyTo
I don't tnink it's optimal solution as it doesn't utilize Entitas caching capabilities. If only all behaviourTree data could be cleverly stored in components. Any Ideas?
@sschmid could you possible weigh in on a general solution for deep-copy of entities and components?
Hi, 2 thoughts, one on deep copy and one on an alternative solution for your problem:
Deep Copy
A generic solution for deep copying is probably not a good idea. Deep cloning a component with complex objects is probably way more complicated and more overhead compared to simply construct new objects manually. Deep copying can make sense if you know what objects you are dealing with, but I wouldn't strive for a general solution. Objects might have references to other objects that have to be cloned that have references to objects that have to be cloned that have other ref..... and so on, which leads me to:
Configuration / Entity Factory
If I understand correctly you basically trying to create some template / blueprint entities that you will clone at runtime. If you break it down, this is basically configuration. I worked on many games with lots of external configuration. Configs are simple first and can grow in complexity. Chances are that game designers and level designer will take over the responsibility to maintain configurations. I recommend having tools for working with configs like google sheets, custom Unity inspectors or sth like that which output a versioned config in structure independent from your game architecture, e.g. Text formats like JSON or binary formats like Flax Buffers / protobuf or similar.
We created a class, e.g. CharacterSetupService, BuildingSetupService that know how to construct entities based on the configuration, e.g.
public virtual GameEntity CreateCharacter(Contexts contexts, string id, string type, Gender gender, int positionX, int positionY, string state) {
var e = contexts.core.CreateEntity();
// Add components every character needs
SetupEntitySpecificComponents(e, id, type, gender, positionX, positionY, state);
// Add components based on config
AddComponentsFromConfig(contexts, e);
return e;
}
var character = characterSetupService.CreateCharacter(contexts, id, type, gender, 0, 0, CharacterState.Idle);
This approach also allows you to simply write unit tests because you can mock the CharacterSetupService and have config independent tests
I did what Simon suggests for one of our games and would do it again.
For behaviour trees we use BehaviourDesiger. We have an excel spreadsheet that defines all enemies types. One of the fields is the name of the behaviourTree asset.
In runtime our "blueprints" read this config and provide entities built from it.
That sounds similar to how I had it initially (I just wrote functions inside an EntityFactory class) and it's where I've been leaning recently too - my hope was to have something like a single ScriptableObject class that had a nice drop down for context and from there, drop-downs for components. From what you all say there is no nice way to do this in all generality. Thanks very much for the insights @sschmid and @kdrzymala.
Well, we do have scriptable objects that define game entity types :) They are just high-level, and designer friendly like: enemy, pickup, static geometry and so on. The designers can build levels and gameplay effortlesly from those blocks.
For instance: enemy generator is parametrized just with the enemyId (which corresponds to the config entry). Designers get to see/tweak only what is meaningful to them.
The difference is that we don't allow designers to compose entities from components - in my opinion they shouldn't be aware of that, it's an implementation detail. The composition is done in code.
At first I was reluctant to keep it in code, but it's not that much work. When we need a new generator type - coding one is like 2 minutes of work. Generator does only one thing, so it's clean too.
Thanks for this - if I understand correctly, your SOs expose the specific things for specific entities they are responsible for (e.g. enemy config might need health, attack types, etc) then under the hood you have a method that translates the SO's fields into .AddComponent() calls.
So you have many SOs each defined specifically for certain types of "things" in your game. Then some way of managing instances of these SOs so they can be passed around at runtime?
More-less, yes, though it is far more complicated than that (I wanted to describe our setup, but stopped half way, as I have to go back to work ;-)) The big picture is to expose simple interface to the designers and keep the complexity hidden.
As for runtime - no I don't. I treat those SO's just like Unity treats prefabs. Those are editor thingies and the runtime code has not idea where the entities came from, just like Unity doesn't know if a gameObject was spawned dynamically or from prefab.
Ok, thanks. Do come back and describe the setup if you have the time, otherwise I appreciate what you've shared so far.
I will, after gamescom most probably, though ;-)
Agree with all statements, thanks guys.
I decided to try both approaches and see what fits best in the long run. Configs are very good as they are implementation independent, and data won't disappear after implementation is rewritten. I will add them at some point.
For deep-copy solution I ended up using modified copy of PublicMemberInfoEntityExtension.CopyTo
public interface IAfterCopy
{
void AfterCopy();
}
public static class CloneExtensions
{
public static TEntity CloneEntityExt<TEntity>(this IContext<TEntity> context, IEntity entity, bool replaceExisting = false, params int[] indices) where TEntity : class, IEntity
{
TEntity entity1 = context.CreateEntity();
entity.CopyToExt((IEntity) entity1, replaceExisting, indices);
return entity1;
}
public static void CopyToExt(this IEntity entity, IEntity target, bool replaceExisting = false, params int[] indices)
{
foreach (int index in indices.Length == 0 ? entity.GetComponentIndices() : indices)
{
IComponent component1 = entity.GetComponent(index);
IComponent component2 = target.CreateComponent(index, component1.GetType());
component1.CopyPublicMemberValues((object) component2);
(component2 as IAfterCopy)?.AfterCopy( );
if (replaceExisting)
target.ReplaceComponent(index, component2);
else
target.AddComponent(index, component2);
}
}
}
entity.CopyToExt() calls (component2 as IAfterCopy)?.AfterCopy( ) after CopyPublicMemberValues. Components can implement IAfterCopy to react. All cloning should go through CloneEntityExt() and CopyToExt().
It doesn't require changes to Entitas src.
What do you put in your AfterCopy methods for your components? Is this the bit you put in to handle deep-copying reference types?
objects implement ICloneable and in AfterCopy I call something like Value = Value.Clone();. Followed this video https://www.youtube.com/watch?v=Ct70N_8TcTU
After some time, I think it should be inside Entitas framework.
Either:
CopyTo() should provide deep copyingCopyToShallow(), context.CloneEntityShallow() and CopyToDeep(), context.CloneEntityDeep(), to avoid ICloneable.Clone reputation for not stating clone typeentity.CopyTo(), context.CloneEntity to entity.CopyToShallow(), context.CloneEntityShallow()I see too many cases where deep copying doesn't make sense and therefore can't be included in Entitas as a general solution. If you have specific cases where you need it, you have to implement it yourself, which I think would be the better way anyway as you have oversight on what objects you actually plan to deep copy.
Most helpful comment
Hi, 2 thoughts, one on deep copy and one on an alternative solution for your problem:
Deep Copy
A generic solution for deep copying is probably not a good idea. Deep cloning a component with complex objects is probably way more complicated and more overhead compared to simply construct new objects manually. Deep copying can make sense if you know what objects you are dealing with, but I wouldn't strive for a general solution. Objects might have references to other objects that have to be cloned that have references to objects that have to be cloned that have other ref..... and so on, which leads me to:
Configuration / Entity Factory
If I understand correctly you basically trying to create some template / blueprint entities that you will clone at runtime. If you break it down, this is basically configuration. I worked on many games with lots of external configuration. Configs are simple first and can grow in complexity. Chances are that game designers and level designer will take over the responsibility to maintain configurations. I recommend having tools for working with configs like google sheets, custom Unity inspectors or sth like that which output a versioned config in structure independent from your game architecture, e.g. Text formats like JSON or binary formats like Flax Buffers / protobuf or similar.
We created a class, e.g. CharacterSetupService, BuildingSetupService that know how to construct entities based on the configuration, e.g.
This approach also allows you to simply write unit tests because you can mock the CharacterSetupService and have config independent tests