G’day,
I’m having to duplicate most of the code in my game because we can’t use Entities.WithAll, which is getting really frustrating. My games has lots of units from 2 sides/teams, and a lot of systems that do something for each team (often using both ‘my’ team and the ‘enemy’ team).
Here’s one of the smaller examples from my game:
public class ShieldDamageSystem : SystemBase
{
private EntityQuery side1DamageQuery;
private EntityQuery side1ShieldStrengthQuery;
private EntityQuery side2DamageQuery;
private EntityQuery side2ShieldStrengthQuery;
protected override void OnCreate()
{
side1ShieldStrengthQuery = EntityManager.CreateEntityQuery(
ComponentType.ReadOnly<Side1>(),
ComponentType.ReadOnly<ShieldStrength>()
);
side2ShieldStrengthQuery = EntityManager.CreateEntityQuery(
ComponentType.ReadOnly<Side2>(),
ComponentType.ReadOnly<ShieldStrength>()
);
base.OnCreate();
}
protected override void OnUpdate()
{
UpdateSide1();
UpdateSide2();
}
void UpdateSide1()
{
float totalDamage = 0;
Entities
.WithAll<Side1>()
.WithStoreEntityQueryInField(ref side1DamageQuery)
.ForEach((ref ShieldDamage shieldDamage) =>
{
totalDamage += shieldDamage.Value;
}).Run();
var shieldStrength = side1ShieldStrengthQuery.GetSingleton<ShieldStrength>();
shieldStrength.Value -= totalDamage;
side1ShieldStrengthQuery.SetSingleton<ShieldStrength>(shieldStrength);
EntityManager.DestroyEntity(side1DamageQuery);
}
void UpdateSide2()
{
float totalDamage = 0;
Entities
.WithAll<Side2>()
.WithStoreEntityQueryInField(ref side2DamageQuery)
.ForEach((ref ShieldDamage shieldDamage) =>
{
totalDamage += shieldDamage.Value;
}).Run();
var shieldStrength = side2ShieldStrengthQuery.GetSingleton<ShieldStrength>();
shieldStrength.Value -= totalDamage;
side2ShieldStrengthQuery.SetSingleton<ShieldStrength>(shieldStrength);
EntityManager.DestroyEntity(side2DamageQuery);
}
}
If we could use generic functions, this could be reduced to at most:
public class ShieldDamageSystem : SystemBase
{
private EntityQuery side1DamageQuery;
private EntityQuery side1ShieldStrengthQuery;
private EntityQuery side2DamageQuery;
private EntityQuery side2ShieldStrengthQuery;
protected override void OnCreate()
{
side1ShieldStrengthQuery = EntityManager.CreateEntityQuery(
ComponentType.ReadOnly<Side1>(),
ComponentType.ReadOnly<ShieldStrength>()
);
side2ShieldStrengthQuery = EntityManager.CreateEntityQuery(
ComponentType.ReadOnly<Side2>(),
ComponentType.ReadOnly<ShieldStrength>()
);
base.OnCreate();
}
protected override void OnUpdate()
{
UpdateSide<Side1>(ref side1DamageQuery, ref side1ShieldStrengthQuery);
UpdateSide<Side2>(ref side2DamageQuery, ref side2ShieldStrengthQuery);
}
void UpdateSide<TSide>(ref EntityQuery damageQuery, ref EntityQuery shieldQuery)
{
float totalDamage = 0;
Entities
.WithAll<TSide>()
.WithStoreEntityQueryInField(ref damageQuery)
.ForEach((ref ShieldDamage shieldDamage) =>
{
totalDamage += shieldDamage.Value;
}).Run();
var shieldStrength = shieldQuery.GetSingleton<ShieldStrength>();
shieldStrength.Value -= totalDamage;
shieldQuery.SetSingleton<ShieldStrength>(shieldStrength);
EntityManager.DestroyEntity(damageQuery);
}
}
If we could use generic base systems, it could be further reduced to:
public class Side1ShieldDamageSystem : ShieldDamageSystem<Side1> { }
public class Side2ShieldDamageSystem : ShieldDamageSystem<Side2> { }
public abstract class ShieldDamageSystem<TSide> : SystemBase
{
private EntityQuery damageQuery;
private EntityQuery shieldStrengthQuery;
protected override void OnCreate()
{
shieldStrengthQuery = EntityManager.CreateEntityQuery(
ComponentType.ReadOnly<TSide>(),
ComponentType.ReadOnly<ShieldStrength>()
);
base.OnCreate();
}
protected override void OnUpdate()
{
float totalDamage = 0;
Entities
.WithAll<TSide>()
.WithStoreEntityQueryInField(ref damageQuery)
.ForEach((ref ShieldDamage shieldDamage) =>
{
totalDamage += shieldDamage.Value;
}).Run();
var shieldStrength = shieldStrengthQuery.GetSingleton<ShieldStrength>();
shieldStrength.Value -= totalDamage;
shieldStrengthQuery.SetSingleton<ShieldStrength>(shieldStrength);
EntityManager.DestroyEntity(damageQuery);
}
}
It’s not too bad in this case, but some systems like finding nearest enemies have 100+ lines of duplicate code with 10 queries containing both Side1 and Side2 (which if they were generic would be TSide and TEnemySide). I’m using the Side1 and Side2 tags to separate everything because it’s much faster for a lot of queries.
Any chance we’ll ever be able to do either of these? Or does anyone know a current way I can avoid duplicating code in this situation?