Here is an example: on Physics Collision, filter damage, and send damage event to the target.
There are three Optional Components in my damage system.
DamageFilter, DamageParameter, and the third is ComponentDisable.
When Damage Component is Disabled, No damage event will be sent.
When ComponentDisable dose not exist, Damage is considered Enabled.
When DamageFilter dose not exist. I want to use the default filter.
When DamageParameterdose not exist. I want to use the default parameter.
Here’s the code with my Disable and Optional Approach.
public class ApplyDamageSystem : SystemBase
{
HPChangeEventSystem m_HPSystem;
ComponentDisableInfoSystem DisableInfo;
EntityQuery CoreQuery;
protected override void OnCreate()
{
m_HPSystem = World.GetOrCreateSystem<HPChangeEventSystem>();
DisableInfo = World.GetOrCreateSystem<ComponentDisableInfoSystem>();
DisableInfo.RegisterTypeForDisable<Damage>();
}
protected override void OnUpdate()
{
var writer = m_HPSystem.GetStreamWriter();
var dmgDisableHandle = DisableInfo.GetDisableHandle<Damage>();
var dependency = Dependency;
//Collect Component from Query that target component is not required
//Use default if not found on entity
//This is done in one IJobChunk
(var disables, var damageFilters, var damageParams) =
CoreQuery.ToOptionalDataArrayAsync<ComponentDisable, DamageFilter, DamageParameter>(
this, Allocator.TempJob, ref dependency,
default,
DamageFilter.Default,
DamageParameter.Default);
var dmgJob =
Entities.WithName("ApplyDamage")
.WithAll<CoreTag>()
.WithChangeFilter<PhysicsContactState2D>()
.WithStoreEntityQueryInField(ref CoreQuery)
.ForEach((Entity coreEntity,
int entityInQueryIndex,
in Damage damage,
in DynamicBuffer<PhysicsContactState2D> contacts) =>
{
var disable = disables[entityInQueryIndex];
var damageFilter = damageFilters[entityInQueryIndex];
var parameter = damageParams[entityInQueryIndex];
var handle = writer.BeginBatch();
for (int i = 0, len = contacts.Length; i < len; i++)
{
var contact = contacts[i];
var enemyEntity = contact.Other;
if ( contact.Type == ContactType.Collider &
contact.State == ContactState.Enter &
HasComponent<FoeTag>(enemyEntity) &
disable.GetEnabled(dmgDisableHandle))
{
handle.WriteDamage(enemyEntity, damageFilter.FilterDamage(damage, parameter), coreEntity);
}
}
handle.EndBatch();
}).Schedule(JobHandle.CombineDependencies(Dependency, m_HPSystem.WaiteFroEventProcess));
m_HPSystem.WaiteFroStreamAccess = dmgJob;
disables.Dispose(dmgJob);
damageFilters.Dispose(dmgJob);
damageParams.Dispose(dmgJob);
Dependency = dmgJob;
}
}
Without Disable and Optional. I would need to have 8 ForEach to match every case(too much copy-paste).
Or use 3 HasCompoent and GetComponent inside ForEach(not prefered).
And one extra ComponentTag to mark disable
Even with IJobChunk. I will end up with massive branches filtering component compositions.
This is a common pattern that happens everywhere in game logics.
Keeping it reasonably code/review friendly should be a hight priority Task.
I am trying to keep everything in the Dots style.
The Drawback is that ToOptionalDataArrayAsync+Entitise.ForEach will use a large chunk of memory and use one extra job. Trading back much cleaner source code and no branching in job. But it can be supported by Entites.ForEach CodeGen without this Drawback.
A possible way is in here
https://discussions.unity.com/t/809424
Hope Optional component can be supported by ECS as well.