Intro
OK, so till now my game have some decent functinality but now I wanted to reward player even more when we will be able to collect all pellets in the level within specified time frame. So my Idea was to destroy all wall(s) in the level make the level clear (no walls at all) with some destroy effect + then there could be spawn some bonus pellets that player might collect if he likes. So let’s get started.
Design
So my Idea was to create new ECS System GameWonSystem
that will be responsible to identify when player reach the goal (collect all pellets within time) and at that exact moment will identify all object(s) that should be destroyed and spawned some collectable on their place.
Systems
Lets first create GameWonSystem
and look at the OnCreate
method.
we will create first the field called query_GameStats
this will contains command to Find all entities .WithAll<GameStats>()
that contains GameStats
component type. In this component I have stored all calculated game statistics like remainingLive, remainingPellets, remainingTime and so on. So the task is to find this object validate its statistics and then create Tag component CollectedAllPelletsWithinTimeLimitTag
on top of it to mark it with information that game is already won so does not need to be calculated again or marked again.
This is also the reason we wanted to filter out all GameStats that already have this Tag componnent .WithNone<CollectedAllPelletsWithinTimeLimitTag>()
.
So the final query look like this:
query_GameStats = SystemAPI.QueryBuilder() .WithAll<GameStats>() //only one GameStats component in the game .WithNone<CollectedAllPelletsWithinTimeLimitTag>() //only if not already won .Build();
Second part of the onCreate method contains declarations when we wanted that Ecs will call our OnUpdate method.
state.RequireForUpdate(query_GameStats); state.RequireForUpdate<EndSimulationEntityCommandBufferSystem.Singleton>();
in our case we requere that our system is called every frame only in case that our query_GameStats
returns something and also we would like to use EndSimulationEntityCommandBufferSystem.Singleton
for modification of ECS Components (create new or modify existing) at the end of frame we need also this Ecs system to be available. It is created by Unity Ecs System at the start of the game.
Final version of OnCreate method looks like this:
[BurstCompile] public void OnCreate(ref SystemState state) { query_GameStats = SystemAPI.QueryBuilder() .WithAll<GameStats>() //only one GameStats component in the game .WithNone<CollectedAllPelletsWithinTimeLimitTag>() //only if not already won .Build(); state.RequireForUpdate(query_GameStats); state.RequireForUpdate<EndSimulationEntityCommandBufferSystem.Singleton>(); }
Finally here is the full System struct:
GameWonSystem.cs
/** * System that checks if the game is won (all pellets collected within defined time) * and if yes, it will create special component that will be used by GameWonSystem to show the game won behaviour */ [UpdateInGroup(typeof(SimulationSystemGroup))] public partial struct GameWonSystem : ISystem { private EntityQuery query_GameStats; [BurstCompile] public void OnCreate(ref SystemState state) { query_GameStats = SystemAPI.QueryBuilder() .WithAll<GameStats>() //only one GameStats component in the game .WithNone<CollectedAllPelletsWithinTimeLimitTag>() //only if not already won .Build(); state.RequireForUpdate(query_GameStats); state.RequireForUpdate<EndSimulationEntityCommandBufferSystem.Singleton>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { if (!SystemAPI.TryGetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>(out var ecb)) return; var ecp = ecb.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter(); new GameWonJob() { ecb = ecp, }.ScheduleParallel(query_GameStats, state.Dependency).Complete(); } }
GameWonJob
We would like to use the ECS system as it should be so now we need to implement GameWonJob to implement validation logic in separate Thread. Job is really simple like this.
GameWonJob.cs
[BurstCompile] public partial struct GameWonJob : IJobEntity { public EntityCommandBuffer.ParallelWriter ecb; public void Execute(ref GameStats gameStats, in Entity e, [EntityIndexInChunk] int id) { if (!gameStats.IsCollectedAllWithinTimeLimit) return; gameStats.timeLimitForStar1 += 30; //add 30 seconds for star 1 to be able to collect extra bonus pellets // store new time limit for star 1 ecb.SetComponent(id, e, gameStats); // add component that game is won ecb.AddComponent(id, e, new CollectedAllPelletsWithinTimeLimitTag()); // fire event that player is collected all pellets within time limit ecb.AddComponent(id, e, new GamePlayEvent() { eventPosition = new float3(), eventType = GamePlayEvent.VfxEventType.OnCollectedAllPelletsWithinTimeLimit }); } }
Now lets explain what is in it:
if (!gameStats.IsCollectedAllWithinTimeLimit) return;
This part is the Magic Validation. As I have implemented check as a field all I have to do now is to call this IsCollectedAllWithinTimeLimit and based on the anser I know if the level is finished as expected or not finished.
Now next importat thing is to set CollectedAllPelletsWithinTimeLimitTag
to be sure that this will not be executed second time. And also based on this Tag coponent I can implement other system that will follow in the game play. More on that in the next devlog.
// add component that game is won ecb.AddComponent(id, e, new CollectedAllPelletsWithinTimeLimitTag());
Conclusion
So this is the end of this little description about how the end level is done in my game. If you have any questions or comment feel free to let me know. Hope that something in this dev log will help you with your jurney.