- Home /
ECS design problem
Hello, I need to implement parabolic like movement in Unity using ECS. However, I feel kind of stuck to implement it. I already came up with two solutions, but I do not know which one I should prefer over the other.
This is the first implementation I came up with:
[GenerateAuthoringComponent]
public struct SinusMovement : IComponentData
{
public bool3 axes;
public float3 lerpSpeed;
public float3 amplitude;
public float3 period;
public float3 height;
public float3 lerpValue;
}
public class SinusMovementSystem : JobComponentSystem
{
private struct SinusMovementJob : IJobForEach<Translation, SinusMovement>
{
public float deltaTime;
public void Execute(ref Translation translation, ref SinusMovement sinusMovement)
{
if (sinusMovement.axes.x)
{
translation.Value.x = math.sin(math.PI * sinusMovement.period.x * math.clamp(sinusMovement.lerpValue.x, 0, 1)) * sinusMovement.amplitude.x + sinusMovement.height.x;
sinusMovement.lerpValue.x += deltaTime * sinusMovement.lerpSpeed.x;
if (sinusMovement.lerpValue.x >= 1.0f)
{
sinusMovement.lerpValue.x = 0.0f;
}
}
if (sinusMovement.axes.y)
{
translation.Value.y = math.sin(math.PI * sinusMovement.period.y * math.clamp(sinusMovement.lerpValue.y, 0, 1)) * sinusMovement.amplitude.y + sinusMovement.height.y;
sinusMovement.lerpValue.y += deltaTime * sinusMovement.lerpSpeed.y;
if (sinusMovement.lerpValue.y >= 1.0f)
{
sinusMovement.lerpValue.y = 0.0f;
}
}
if (sinusMovement.axes.z)
{
translation.Value.z = math.sin(math.PI * sinusMovement.period.z * math.clamp(sinusMovement.lerpValue.z, 0, 1)) * sinusMovement.amplitude.z + sinusMovement.height.z;
sinusMovement.lerpValue.z += deltaTime * sinusMovement.lerpSpeed.z;
if (sinusMovement.lerpValue.z >= 1.0f)
{
sinusMovement.lerpValue.z = 0.0f;
}
}
}
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
SinusMovementJob sinusMovementJob = new SinusMovementJob
{
deltaTime = Time.DeltaTime
};
return sinusMovementJob.Schedule(this, inputDeps);
}
}
But I realized that if I just want to move an object along with a parabola on, for example, the x-axis only I waste a lot of memory because I also store data for movement on the y and z-axis. This is why I came up with my second solution:
[GenerateAuthoringComponent]
public struct SinusMovementX : IComponentData
{
public float lerpSpeed;
public float amplitude;
public float period;
public float height;
public float lerpValue;
}
[GenerateAuthoringComponent]
public struct SinusMovementY : IComponentData
{
public float lerpSpeed;
public float amplitude;
public float period;
public float height;
public float lerpValue;
}
[GenerateAuthoringComponent]
public struct SinusMovementZ : IComponentData
{
public float lerpSpeed;
public float amplitude;
public float period;
public float height;
public float lerpValue;
}
public class SinusMovementSystem : JobComponentSystem
{
[BurstCompile]
private struct SinusMovementXJob : IJobForEach<Translation, SinusMovementX>
{
public float deltaTime;
public void Execute(ref Translation translation, ref SinusMovementX sinusMovementX)
{
translation.Value.x = math.sin(math.PI * sinusMovementX.period * math.clamp(sinusMovementX.lerpValue, 0, 1)) * sinusMovementX.amplitude + sinusMovementX.height;
sinusMovementX.lerpValue += sinusMovementX.lerpSpeed * deltaTime;
if (sinusMovementX.lerpValue >= 1.0f)
{
sinusMovementX.lerpValue = 0.0f;
}
}
}
[BurstCompile]
private struct SinusMovementYJob : IJobForEach<Translation, SinusMovementY>
{
public float deltaTime;
public void Execute(ref Translation translation, ref SinusMovementY sinusMovementY)
{
translation.Value.x = math.sin(math.PI * sinusMovementY.period * math.clamp(sinusMovementY.lerpValue, 0, 1)) * sinusMovementY.amplitude + sinusMovementY.height;
sinusMovementY.lerpValue += sinusMovementY.lerpSpeed * deltaTime;
if (sinusMovementY.lerpValue >= 1.0f)
{
sinusMovementY.lerpValue = 0.0f;
}
}
}
[BurstCompile]
private struct SinusMovementZJob : IJobForEach<Translation, SinusMovementZ>
{
public float deltaTime;
public void Execute(ref Translation translation, ref SinusMovementZ sinusMovementZ)
{
translation.Value.x = math.sin(math.PI * sinusMovementZ.period * math.clamp(sinusMovementZ.lerpValue, 0, 1)) * sinusMovementZ.amplitude + sinusMovementZ.height;
sinusMovementZ.lerpValue += sinusMovementZ.lerpSpeed * deltaTime;
if (sinusMovementZ.lerpValue >= 1.0f)
{
sinusMovementZ.lerpValue = 0.0f;
}
}
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
SinusMovementXJob sinusMovementXJob = new SinusMovementXJob
{
deltaTime = Time.DeltaTime
};
JobHandle sinusMovementXJobHandle = sinusMovementXJob.Schedule(this, inputDeps);
sinusMovementXJobHandle.Complete();
SinusMovementYJob sinusMovementYJob = new SinusMovementYJob
{
deltaTime = Time.DeltaTime
};
JobHandle sinusMovementYJobHandle = sinusMovementYJob.Schedule(this, inputDeps);
sinusMovementYJobHandle.Complete();
SinusMovementZJob sinusMovementZJob = new SinusMovementZJob
{
deltaTime = Time.DeltaTime
};
JobHandle sinusMovementZJobHandle = sinusMovementZJob.Schedule(this, inputDeps);
sinusMovementZJobHandle.Complete();
return JobHandle.CombineDependencies(sinusMovementXJobHandle, sinusMovementYJobHandle, sinusMovementZJobHandle);
}
}
In this approach, I only store data I need to move the object along a parabola on one axis. I do not waste data I do not need, and if there is the need to move an object along a parabola on one, two or three axes I simply add the corresponding component. The problem now is however that I need three jobs to update the whole thing and I can not even run these systems in parallel since they all write to the Translation component. This is why I get much more milliseconds on the JobSystem here compared to the System shown in my previous approach. Which approach is better and should be used?