Skip to content
This repository was archived by the owner on Jan 18, 2022. It is now read-only.

Commit 9c0b93f

Browse files
author
Paul Balaji
authored
Better gameobject initialization (#1333)
1 parent 96e691b commit 9c0b93f

File tree

9 files changed

+219
-66
lines changed

9 files changed

+219
-66
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
- `CustomSpatialOSSendSystem` is no longer available. [#1308](https://github.com/spatialos/gdk-for-unity/pull/1308)
1313
- The Player Lifecycle feature module now provides an `EntityId` in its `CreatePlayerEntityTemplate` callback. [#1315](https://github.com/spatialos/gdk-for-unity/pull/1315)
1414
- You will have to change your callback from `(string clientWorkerId, byte[] serializedArguments)` to `(EntityId entityId, string clientWorkerId, byte[] serializedArguments)`.
15-
- Added the `ComponentType[] MiniumComponentTypes { get; }` property to `IEntityGameObjectCreator`. [#1330](https://github.com/spatialos/gdk-for-unity/pull/1330)
16-
- You will have to define the minimum set of components required on an entity to trigger the `OnEntityCreated` method on your custom GameObject creator.
15+
- Added the `PopulateEntityTypeExpectations` method to `IEntityGameObjectCreator`. [#1333](https://github.com/spatialos/gdk-for-unity/pull/1333)
16+
- Use this method to define the set of components expected on an entity to be able create GameObjects for a given entity type.
17+
- Added `string entityType` as an argument to `IEntityGameObjectCreator.OnEntityCreated`. [#1333](https://github.com/spatialos/gdk-for-unity/pull/1333)
18+
- This means that your entities must have the `Metadata` component to use the GameObject Creation Feature Module.
1719

1820
### Added
1921

UPGRADE_GUIDE.md

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,65 @@ public static EntityTemplate Player(EntityId entityId, string workerId, byte[] a
2929
}
3030
```
3131

32-
### The `IEntityGameObjectCreator` now requires the `ComponentType[] MiniumComponentTypes { get; }` property
32+
### `IEntityGameObjectCreator` changes
3333

34-
If you have written custom GameObject creators implementing `IEntityGameObjectCreator`, you will have to define the minimum set of components required on an entity to trigger the `OnEntityCreated` method.
34+
#### Implement `PopulateEntityTypeExpectations` method
3535

36-
For example, the following has been added to the `GameObjectCreatorFromMetadata` class:
36+
If you have written custom GameObject creators implementing `IEntityGameObjectCreator`, you will now have to implement a `void PopulateEntityTypeExpectations(EntityTypeExpectations entityTypeExpectations)` method.
37+
38+
The `EntityTypeExpectations` class provides two public methods for defining a set of components expected on an entity to be able create GameObjects for a given entity type:
39+
40+
- `void RegisterDefault(IEnumerable<Type> defaultComponentTypes = null)`
41+
- `void RegisterEntityType(string entityType, IEnumerable<Type> expectedComponentTypes = null)`
42+
43+
For example, the `GameObjectCreatorFromMetadata` class implements the method like so:
3744

3845
```csharp
39-
public ComponentType[] MinimumComponentTypes { get; } =
46+
public void PopulateEntityTypeExpectations(EntityTypeExpectations entityTypeExpectations)
4047
{
41-
ComponentType.ReadOnly<Metadata.Component>(),
42-
ComponentType.ReadOnly<Position.Component>()
43-
};
48+
entityTypeExpectations.RegisterDefault(new[]
49+
{
50+
typeof(Position.Component)
51+
});
52+
}
4453
```
4554

46-
> You will need to add `using Unity.Entities;` to the top of the file and reference it in the assembly that your custom GameObject creator is in.
55+
The `AdvancedEntityPipeline` in the FPS Starter Project makes use of both public methods on the `EntityTypeExpectations` class. This is done in order to wait for specific components when creating "Player" GameObjects, and a different set of components for any other entity type:
56+
57+
```csharp
58+
public void PopulateEntityTypeExpectations(EntityTypeExpectations entityTypeExpectations)
59+
{
60+
entityTypeExpectations.RegisterEntityType(PlayerEntityType, new[]
61+
{
62+
typeof(OwningWorker.Component), typeof(ServerMovement.Component)
63+
});
64+
65+
fallback.PopulateEntityTypeExpectations(entityTypeExpectations);
66+
}
67+
``
68+
69+
#### Add `string entityType` as argument to `OnEntityCreated`
70+
71+
The `EntityType` available the `Metadata` component is now provided as an argument when calling `OnEntityCreated` on your GameObject creator.
72+
73+
> This means that your entities must have the `Metadata` component to use the GameObject Creation Feature Module.
74+
75+
The `AdvancedEntityPipeline` in the FPS Starter Project makes use of it like so:
76+
77+
```csharp
78+
public void OnEntityCreated(string entityType, SpatialOSEntity entity, EntityGameObjectLinker linker)
79+
{
80+
switch (entityType)
81+
{
82+
case PlayerEntityType:
83+
CreatePlayerGameObject(entity, linker);
84+
break;
85+
default:
86+
fallback.OnEntityCreated(entityType, entity, linker);
87+
break;
88+
}
89+
}
90+
```
4791

4892
## From `0.3.2` to `0.3.3`
4993

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Unity.Entities;
5+
6+
namespace Improbable.Gdk.GameObjectCreation
7+
{
8+
/// <summary>
9+
/// Holds mappings from entity types to the set of components expected before creating their GameObjects.
10+
/// </summary>
11+
public class EntityTypeExpectations
12+
{
13+
private ComponentType[] defaultExpectation = new ComponentType[] { };
14+
15+
private readonly Dictionary<string, ComponentType[]> entityExpectations
16+
= new Dictionary<string, ComponentType[]>();
17+
18+
public void RegisterDefault(IEnumerable<Type> defaultComponentTypes = null)
19+
{
20+
if (defaultComponentTypes == null)
21+
{
22+
defaultExpectation = new ComponentType[] { };
23+
}
24+
else
25+
{
26+
defaultExpectation = defaultComponentTypes
27+
.Select(type => new ComponentType(type, ComponentType.AccessMode.ReadOnly))
28+
.ToArray();
29+
}
30+
}
31+
32+
public void RegisterEntityType(string entityType, IEnumerable<Type> expectedComponentTypes = null)
33+
{
34+
ComponentType[] expectedTypes;
35+
36+
if (expectedComponentTypes == null)
37+
{
38+
expectedTypes = new ComponentType[] { };
39+
}
40+
else
41+
{
42+
expectedTypes = expectedComponentTypes
43+
.Select(type => new ComponentType(type, ComponentType.AccessMode.ReadOnly))
44+
.ToArray();
45+
}
46+
47+
entityExpectations.Add(entityType, expectedTypes);
48+
}
49+
50+
internal ComponentType[] GetExpectedTypes(string entityType)
51+
{
52+
if (!entityExpectations.TryGetValue(entityType, out var types))
53+
{
54+
return defaultExpectation;
55+
}
56+
57+
return types;
58+
}
59+
}
60+
}

workers/unity/Packages/io.improbable.gdk.gameobjectcreation/EntityTypeExpectations.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

workers/unity/Packages/io.improbable.gdk.gameobjectcreation/GameObjectCreatorFromMetadata.cs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.IO;
44
using Improbable.Gdk.Core;
55
using Improbable.Gdk.Subscriptions;
6-
using Unity.Entities;
76
using UnityEngine;
87
using Object = UnityEngine.Object;
98

@@ -17,39 +16,32 @@ private readonly Dictionary<string, GameObject> cachedPrefabs
1716
private readonly string workerType;
1817
private readonly Vector3 workerOrigin;
1918

20-
private readonly ILogDispatcher logger;
21-
2219
private readonly Dictionary<EntityId, GameObject> entityIdToGameObject = new Dictionary<EntityId, GameObject>();
2320

2421
private readonly Type[] componentsToAdd =
2522
{
26-
typeof(Transform),
27-
typeof(Rigidbody),
28-
typeof(MeshRenderer)
29-
};
30-
31-
public ComponentType[] MinimumComponentTypes { get; } =
32-
{
33-
ComponentType.ReadOnly<Metadata.Component>(),
34-
ComponentType.ReadOnly<Position.Component>()
23+
typeof(Transform), typeof(Rigidbody), typeof(MeshRenderer)
3524
};
3625

3726
public GameObjectCreatorFromMetadata(string workerType, Vector3 workerOrigin, ILogDispatcher logger)
3827
{
3928
this.workerType = workerType;
4029
this.workerOrigin = workerOrigin;
41-
this.logger = logger;
4230
}
4331

44-
public void OnEntityCreated(SpatialOSEntity entity, EntityGameObjectLinker linker)
32+
public void PopulateEntityTypeExpectations(EntityTypeExpectations entityTypeExpectations)
4533
{
46-
if (!entity.TryGetComponent<Metadata.Component>(out var metadata) ||
47-
!entity.TryGetComponent<Position.Component>(out var spatialOSPosition))
34+
entityTypeExpectations.RegisterDefault(new[]
4835
{
49-
return;
50-
}
36+
typeof(Position.Component)
37+
});
38+
}
39+
40+
public void OnEntityCreated(string entityType, SpatialOSEntity entity, EntityGameObjectLinker linker)
41+
{
42+
var spatialOSPosition = entity.GetComponent<Position.Component>();
5143

52-
var prefabName = metadata.EntityType;
44+
var prefabName = entityType;
5345
var position = spatialOSPosition.Coords.ToUnityVector() + workerOrigin;
5446

5547
if (!cachedPrefabs.TryGetValue(prefabName, out var prefab))

workers/unity/Packages/io.improbable.gdk.gameobjectcreation/GameObjectInitializationSystem.cs

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Linq;
1+
using System;
22
using Improbable.Gdk.Core;
33
using Improbable.Gdk.Subscriptions;
44
using Unity.Entities;
@@ -26,23 +26,12 @@ internal class GameObjectInitializationSystem : ComponentSystem
2626
private EntityQuery newEntitiesQuery;
2727
private EntityQuery removedEntitiesQuery;
2828

29-
private ComponentType[] minimumComponentSet = new[]
30-
{
31-
ComponentType.ReadOnly<SpatialEntityId>()
32-
};
29+
private readonly EntityTypeExpectations entityTypeExpectations = new EntityTypeExpectations();
3330

3431
public GameObjectInitializationSystem(IEntityGameObjectCreator gameObjectCreator, GameObject workerGameObject)
3532
{
3633
this.gameObjectCreator = gameObjectCreator;
3734
this.workerGameObject = workerGameObject;
38-
39-
var minCreatorComponentSet = gameObjectCreator.MinimumComponentTypes;
40-
if (minCreatorComponentSet != null)
41-
{
42-
minimumComponentSet = minimumComponentSet
43-
.Concat(minCreatorComponentSet)
44-
.ToArray();
45-
}
4635
}
4736

4837
protected override void OnCreate()
@@ -58,6 +47,11 @@ protected override void OnCreate()
5847
Linker.LinkGameObjectToSpatialOSEntity(new EntityId(0), workerGameObject);
5948
}
6049

50+
var minimumComponentSet = new[]
51+
{
52+
ComponentType.ReadOnly<SpatialEntityId>(), ComponentType.ReadOnly<Metadata.Component>()
53+
};
54+
6155
newEntitiesQuery = GetEntityQuery(new EntityQueryDesc()
6256
{
6357
All = minimumComponentSet,
@@ -69,6 +63,8 @@ protected override void OnCreate()
6963
All = new[] { ComponentType.ReadOnly<GameObjectInitSystemStateComponent>() },
7064
None = minimumComponentSet
7165
});
66+
67+
gameObjectCreator.PopulateEntityTypeExpectations(entityTypeExpectations);
7268
}
7369

7470
protected override void OnDestroy()
@@ -87,24 +83,68 @@ protected override void OnDestroy()
8783

8884
protected override void OnUpdate()
8985
{
90-
Entities.With(newEntitiesQuery).ForEach((Entity entity, ref SpatialEntityId spatialEntityId) =>
86+
if (!newEntitiesQuery.IsEmptyIgnoreFilter)
9187
{
92-
gameObjectCreator.OnEntityCreated(new SpatialOSEntity(entity, EntityManager), Linker);
93-
PostUpdateCommands.AddComponent(entity, new GameObjectInitSystemStateComponent
94-
{
95-
EntityId = spatialEntityId.EntityId
96-
});
97-
});
88+
ProcessNewEntities();
89+
}
90+
91+
if (!removedEntitiesQuery.IsEmptyIgnoreFilter)
92+
{
93+
ProcessRemovedEntities();
94+
}
9895

96+
Linker.FlushCommandBuffer();
97+
}
98+
99+
private void ProcessRemovedEntities()
100+
{
99101
Entities.With(removedEntitiesQuery).ForEach((ref GameObjectInitSystemStateComponent state) =>
100102
{
101103
Linker.UnlinkAllGameObjectsFromEntityId(state.EntityId);
102-
gameObjectCreator.OnEntityRemoved(state.EntityId);
103-
});
104104

105-
Linker.FlushCommandBuffer();
105+
try
106+
{
107+
gameObjectCreator.OnEntityRemoved(state.EntityId);
108+
}
109+
catch (Exception e)
110+
{
111+
Debug.LogException(e);
112+
}
113+
});
106114

107115
EntityManager.RemoveComponent<GameObjectInitSystemStateComponent>(removedEntitiesQuery);
108116
}
117+
118+
private void ProcessNewEntities()
119+
{
120+
Entities.With(newEntitiesQuery).ForEach(
121+
(Entity entity, ref SpatialEntityId spatialEntityId, ref Metadata.Component metadata) =>
122+
{
123+
var entityType = metadata.EntityType;
124+
var expectedTypes = entityTypeExpectations.GetExpectedTypes(entityType);
125+
126+
foreach (var expectedType in expectedTypes)
127+
{
128+
if (!EntityManager.HasComponent(entity, expectedType))
129+
{
130+
return;
131+
}
132+
}
133+
134+
try
135+
{
136+
gameObjectCreator.OnEntityCreated(entityType, new SpatialOSEntity(entity, EntityManager), Linker);
137+
}
138+
catch (Exception e)
139+
{
140+
Debug.LogException(e);
141+
}
142+
143+
PostUpdateCommands.AddComponent(entity, new GameObjectInitSystemStateComponent
144+
{
145+
EntityId = spatialEntityId.EntityId
146+
});
147+
});
148+
}
109149
}
110150
}

workers/unity/Packages/io.improbable.gdk.gameobjectcreation/IEntityGameObjectCreator.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@ namespace Improbable.Gdk.GameObjectCreation
1111
public interface IEntityGameObjectCreator
1212
{
1313
/// <summary>
14-
/// The minimum set of components required on an entity to create a GameObject.
14+
/// Called to register the components expected on an entity to create a GameObject for a given entity type.
1515
/// </summary>
16-
ComponentType[] MinimumComponentTypes { get; }
16+
void PopulateEntityTypeExpectations(EntityTypeExpectations entityTypeExpectations);
1717

1818
/// <summary>
1919
/// Called when a new SpatialOS Entity is checked out by the worker.
2020
/// </summary>
2121
/// <returns>
2222
/// A GameObject to be linked to the entity, or null if no GameObject should be linked.
2323
/// </returns>
24-
void OnEntityCreated(SpatialOSEntity entity, EntityGameObjectLinker linker);
24+
void OnEntityCreated(string entityType, SpatialOSEntity entity, EntityGameObjectLinker linker);
2525

2626
/// <summary>
2727
/// Called when a SpatialOS Entity is removed from the worker's view.

workers/unity/Packages/io.improbable.gdk.gameobjectcreation/Tests/Editmode/GameObjectCreationTests.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,18 +115,20 @@ private class TestGameObjectCreator : IEntityGameObjectCreator
115115

116116
private readonly Dictionary<EntityId, GameObject> entityIdToGameObject = new Dictionary<EntityId, GameObject>();
117117

118-
public ComponentType[] MinimumComponentTypes { get; } =
119-
{
120-
ComponentType.ReadOnly<Position.Component>(),
121-
ComponentType.ReadOnly<Metadata.Component>()
122-
};
123-
124118
public TestGameObjectCreator(string workerType)
125119
{
126120
this.workerType = workerType;
127121
}
128122

129-
public void OnEntityCreated(SpatialOSEntity entity, EntityGameObjectLinker linker)
123+
public void PopulateEntityTypeExpectations(EntityTypeExpectations entityTypeExpectations)
124+
{
125+
entityTypeExpectations.RegisterDefault(new[]
126+
{
127+
typeof(Metadata.Component), typeof(Position.Component)
128+
});
129+
}
130+
131+
public void OnEntityCreated(string entityType, SpatialOSEntity entity, EntityGameObjectLinker linker)
130132
{
131133
var gameObject = new GameObject();
132134
gameObject.transform.position = Vector3.one;

0 commit comments

Comments
 (0)