Disclaimer: There will be lots of code
TL;DR: Im very confused about correct System/Jobs syntax.
Hi, I just started a test project to learn about DOTS.
I decided to go with a Tower Defense Prototype and started working on basic concepts.
Questions (Architecture/Syntax):
- What is the correct architecture for this, Am I doing this correctly or is Jobs something completely unrelated and should not be used in that way?
- What is the correct way to reference “external” entities, for example, a tower that would want to know which enemy is aiming at? And yes, there’s no need to actually do that instead of just calculating the target when its going to attack, BUT. Its not an uncommon case**. Should I add a public Entity to the TargetingComponent and store the entity there?**
Questions(Jobs):
- Are the 3 different ways to write the code inside the system all the same? does foreach generate a job inside burst compiling?
- Code inside systemcan use SystemAPI.Query, and Entities.ForEach had their own way to reference objects, how can I add something like I mentioned previously (all towers, all enemies) like diferent aspects inside a jobEntity.
- Is it the correct behaviour to iterate all things that much? or is the logic to do things like “cooldown system” and when its ready it adds a tag (empty component), so the “next” job only searches for that tag?
So guiding me with a Code Monkey video I started by adding an enemy that does a simple move.
For that I added a MovementComponent which initially only has speed.
public struct MovementComponent : IComponentData
{
public float speed;
}
For that to be added to the Entity I understood that I have to add a Baker and an Authoring (Im not entirely sure if thats only for a visual feedback)
public class MovementAuthoring : MonoBehaviour
{
public float speed;
}
public class MovementSpeedBaker : Baker<MovementAuthoring>
{
public override void Bake(MovementAuthoring authoring)
{
AddComponent(new MovementComponent { speed = authoring.speed });
}
}
Then I need to start with a System
The system would be something simple like this:
[BurstCompile]
public partial struct MovementSystem : ISystem
{
public void OnCreate(ref SystemState state)
{}
public void OnDestroy(ref SystemState state) { }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
new ProcessMovementJob { deltaTime = SystemAPI.Time.DeltaTime}.ScheduleParallel();
}
}
And finally the job would go something like this:
[BurstCompile]
public partial struct ProcessMovementJob : IJobEntity
{
public float deltaTime;
private void Execute(EnemyAspect enemy)
{
if (!enemy.localTransform.IsValid) return;
enemy.localTransform.ValueRW.Position += new float3(deltaTime*enemy.Movement.ValueRO.speed, 0, 0);
}
}
But then I went to a targetting system with multiple jobs
[BurstCompile]
public partial struct TargetingSystem : ISystem
{
public void OnCreate(ref SystemState state) { }
public void OnDestroy(ref SystemState state) { }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
new ProcessClosestTargetStrategyJob().ScheduleParallel();
new ProcessMostHealthTargetStrategyJob().ScheduleParallel();
}
}
}
and I was not able to understand how to pass multiple aspects, for example, the closest strategy would be implemented in something like these.
[BurstCompile]
public partial struct TargetingSystem : ISystem
{
public void OnCreate(ref SystemState state) { }
public void OnDestroy(ref SystemState state) { }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var tower in SystemAPI.Query<TowerAspect>())
{
var towerPosition = tower.localTransform.ValueRO.Position;
var currentTarget = tower.targetingComponent.ValueRO.target;
var closestDistance = 1000000f;
if (currentTarget.HasValue)
{
var targetTransform = SystemAPI.GetComponent<LocalTransform>(currentTarget.Value);
closestDistance = math.distancesq(towerPosition, targetTransform.Position);
}
foreach (var enemy in SystemAPI.Query<EnemyAspect>())
{
if (!enemy.localTransform.IsValid)
continue;
var distance = math.distancesq(towerPosition, enemy.localTransform.ValueRO.Position);
if (distance > tower.attackComponent.ValueRO.attackRange)
continue;
if (distance > closestDistance)
continue;
closestDistance = distance;
currentTarget = enemy.Entity;
}
tower.targetingComponent.ValueRW.target = currentTarget;
}
}
And I would like to move that logic to a job but it seems that I cannot use
SystemAPI, I tried to get that list and pass it as a parameter to the job but it also dont seem to work.
I understood that you can pass parameters in the execute method like this
private void Execute(TowerAspect tower)
but I was not able to pass something like 1 tower, all enemies.
Thus I ended up with multiple targeting systems.
Another thing I noticed mostly on older videos (more than 6 months)
Is that systems can use 3 different ways to write jobs, and i didnt understand what difference on that.
it seems that
foreach (var tower in SystemAPI.Query<TowerAspect>()){}
Entities.ForEach(()=>{}).ScheduleParallel();
And the job interface IJobEntity are exactly the same