I made a small example that makes the object move to the destination, after it is noted that it has been reached
partial struct BotMovementSystem : ISystem{
[BurstCompile]
public void OnUpdate(ref SystemState state){
var ecbSingleton = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();
var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
var botMovementJob = new BotMovementJob{
DeltaTime = SystemAPI.Time.fixedDeltaTime
};
botMovementJob.ScheduleParallel();
}
}
[BurstCompile]
partial struct BotMovementJob : IJobEntity{
public float DeltaTime;
void Execute(ref BotMovementAspect bot){
if(!bot.TargetReached){
float3 dir = math.normalize(bot.TargetPosition - bot.Position);
bot.Position += dir * bot.Speed * DeltaTime;
if(math.distancesq(bot.TargetPosition, bot.Position) < 0.01f){
bot.Position = bot.TargetPosition;
bot.TargetReached = true;
}
}
}
}
now I need to place further movement logic, that is, in this logic, where the object is located is considered, then, based on the needs, a decision is made on the next destination. question - where this logic should be carried out?
in the same place in the aspect? or for this it is better to make a separate system that will process only those objects with TargetReached = true; ?
And by the way, is it possible to iterate over objects that have bot.TargetReached = false?
I think the better way should be making TargetReached an IEnableableComponent tag. There you can query only entities which have this tag enabled and process them accordingly. Tags as a grouping mechanism would be much better and do not waste any precious byte.
For me I would split your BotMovementSystem into various systems. One to only move your bots, and another to check if the target is reached. Because your movement logic and target checking logic might be changed to something more complicated later, you might want to work on them separately.
One other thing I think you should do is to avoid using Aspects as much as possible. I’m seeing Aspects as an easy way out for OOP programmers to feel comfortable. But it can dampen your progress of praticing DOD. You should always specify explicit components in your queries and IJobEntity to instil DOD in your mind as much as possible.
For me it is better to split your monolith logic into smaller, specialized systems. Your current BotMovementSystem might seem to be simple now. But it can become something bigger. Like you might want to add avoidance logic to your bots?
public readonly struct ReachTargetTag : IComponentData, IEnableableComponent { }
EntityManager.SetComponentEnabled<ReachTargetTag>(prefabEntity, false);
// and in your reach-target system job:
EntityCommandBuffer.SetComponentEnabled<ReachTargetTag>(entity, true);
// Query for entities that has this tag disabled
_query = QueryBuilder()
.WithNone<ReachTargetTag>()
.Build();
// Query for entities that has this tag enabled
_query = QueryBuilder()
.WithAll<ReachTargetTag>()
.Build();
Remember that enableable components are enabled by default. You must turn them off after spawning your entities. Or turn them off on the prefab entity before using it to spawn your entities.
I understood the essence, but the syntax is not there, I can’t find examples that would fit my needs, plus I can’t figure out where it’s better for me to use system ISystem or SystemBase.
My bot looks like this
class BotAuthoring : UnityEngine.MonoBehaviour{
public float Speed;
public float3 TargetPosition;
class BotBaker : Baker<BotAuthoring>{
public override void Bake(BotAuthoring authoring){
var e = GetEntity(authoring, TransformUsageFlags.NonUniformScale | TransformUsageFlags.Dynamic);
AddComponent(e, new Bot{
Speed = authoring.Speed,
TargetPosition = authoring.TargetPosition
});
AddComponent(e, new MovementNeedTag{});
}
}
}
struct Bot : IComponentData{
public float Speed;
public float3 TargetPosition;
}
public readonly struct MovementNeedTag : IComponentData, IEnableableComponent{}
how can I correctly create a system so that it is selected by tag, moves as in aspect, and turns off the tag when it reaches the position?
I can’t figure out the syntax…
Use ISystem if you want your code fully bursted. Use SystemBase when you have to use managed objects. ISystem can be partially bursted if you really need it.
Here is a sample I’ve just whisked up to show you the idea. I haven’t tested the code so it might not work as expected. But this is how my systems are implemented atm.
public readonly struct MovementNeedTag : IComponentData, IEnableableComponent { }
public struct MoveSpeed : IComponentData
{
public float value;
}
public struct TargetLocation : IComponentData
{
public float3 value;
}
public partial struct MoveToTargetSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var moveJob = new MoveJob {
timeDelta = SystemAPI.Time.DeltaTime,
};
state.Dependency = moveJob.ScheduleParallelByRef(state.Dependency);
}
[WithAll(typeof(MovementNeedTag))]
private partial struct MoveJob : IJobEntity
{
public float timeDelta;
public void Execute(
in TargetLocation target
, in MoveSpeed speed
, ref LocalTransform transform
)
{
var direction = math.normalize(target.value - transform.Position);
transform.Position += direction * speed.value * timeDelta;
}
}
}
public partial struct CheckTargetReachedSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var ecbSingleton = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();
var checkJob = new CheckJob {
ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter(),
};
state.Dependency = checkJob.ScheduleParallelByRef(state.Dependency);
}
[WithAll(typeof(MovementNeedTag))]
private partial struct CheckJob : IJobEntity
{
public EntityCommandBuffer.ParallelWriter ecb;
public void Execute(
[ChunkIndexInQuery] int chunkIndex
, in Entity entity
, in TargetLocation target
, in LocalTransform transform
)
{
if (math.distancesq(transform.Position, target.value) < 0.01f)
{
ecb.SetComponentEnabled<MovementNeedTag>(chunkIndex, entity, false);
}
}
}
}