forked from Unity-Technologies/EntityComponentSystemSamples
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGrenadeSystem.cs
More file actions
164 lines (149 loc) · 7.48 KB
/
Copy pathGrenadeSystem.cs
File metadata and controls
164 lines (149 loc) · 7.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
using Unity.Burst;
using Unity.Entities;
using Unity.NetCode;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
namespace Samples.HelloNetcode
{
// Timer to track when to destroy the explosion effect left by a grenade
public struct ExplosionData : IComponentData
{
public float Timer;
}
// Track the position of grenades on clients here, to play the explosion effect when they are destroyed (this
// will also delete the transform data and thus it needs recording separately)
public struct GrenadeClientCleanupData : ICleanupComponentData
{
public float3 Position;
}
// Server only system which handles the grenade behaviour, destroy when timer runs out and push close physics objects away
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
[UpdateInGroup(typeof(HelloNetcodePredictedSystemGroup))]
[UpdateAfter(typeof(GrenadeLauncherSystem))]
[BurstCompile]
public partial struct GrenadeSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<EnablePredictedSpawning>();
}
[BurstCompile]
public void OnDestroy(ref SystemState state) { }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var commandBuffer = new EntityCommandBuffer(state.WorldUpdateAllocator);
var time = state.WorldUnmanaged.Time.ElapsedTime;
var config = SystemAPI.GetSingleton<GrenadeConfig>();
foreach (var (data, grenadeTransform, entity) in SystemAPI.Query<RefRO<GrenadeData>, TransformAspect>().WithAll<Simulate>().WithEntityAccess())
{
// Destroy the grenade when it reaches the end of it's timer
if (time > data.ValueRO.DestroyTimer)
{
// Calculate which objects are within the blast radius of the grenade and apply explosion effect on
// them based on distance. The further away the grenade the less affected by the blast.
foreach (var (velocity, transform) in SystemAPI.Query<RefRW<PhysicsVelocity>, TransformAspect>().WithAll<Simulate>())
{
var diff = transform.WorldPosition - grenadeTransform.WorldPosition;
var distanceSqrt = math.lengthsq(diff);
if (distanceSqrt < config.BlastRadius && distanceSqrt != 0)
{
var scaledPower = 1.0f - distanceSqrt / config.BlastRadius;
velocity.ValueRW.Linear = config.BlastPower * scaledPower * (diff / math.sqrt(distanceSqrt));
}
}
commandBuffer.DestroyEntity(entity);
}
}
commandBuffer.Playback(state.EntityManager);
}
}
[UpdateInGroup(typeof(HelloNetcodePredictedSystemGroup))]
// Handle the rotation (up/down) of the grenade launcher, runs on both client and server as this is required
// to figure out the spawn point of the grenade
[BurstCompile]
public partial struct GrenadeLauncherSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<EnablePredictedSpawning>();
state.RequireForUpdate<NetworkIdComponent>();
}
[BurstCompile]
public void OnDestroy(ref SystemState state) {}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var commandBuffer = new EntityCommandBuffer(state.WorldUpdateAllocator);
foreach (var (character, anchorPoint) in SystemAPI.Query<CharacterAspect, RefRO<AnchorPoint>>().WithAll<Simulate>())
{
// This is the weapon slot and rotating that will make the launcher move correctly (it's anchored on the end)
var grenadeLauncher = anchorPoint.ValueRO.WeaponSlot;
var followCameraRotation = quaternion.RotateX(-character.Input.Pitch);
#if !ENABLE_TRANSFORM_V1
var transform = state.EntityManager.GetComponentData<LocalTransform>(grenadeLauncher);
commandBuffer.SetComponent(grenadeLauncher, transform.WithRotation(followCameraRotation));
#else
commandBuffer.SetComponent(grenadeLauncher, new Rotation { Value = followCameraRotation});
#endif
}
commandBuffer.Playback(state.EntityManager);
}
}
// Handle client only behaviour needed to support the grenade explosions
[UpdateInGroup(typeof(HelloNetcodeSystemGroup))]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[BurstCompile]
public partial struct ExplosionSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<EnablePredictedSpawning>();
}
[BurstCompile]
public void OnDestroy(ref SystemState state) {}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var commandBuffer = new EntityCommandBuffer(state.WorldUpdateAllocator);
var time = state.WorldUnmanaged.Time.ElapsedTime;
var explosionPrefab = SystemAPI.GetSingleton<GrenadeSpawner>().Explosion;
var config = SystemAPI.GetSingleton<GrenadeConfig>();
// Add the grenade cleanup component to all grenades when they've arrived in a ghost snapshot (so server has also spawned
// it), otherwise we might track invalid mis-predicted ghost spawns
foreach (var (grenadeData,entity) in SystemAPI.Query<RefRO<GrenadeData>>().WithNone<PredictedGhostSpawnRequestComponent>().WithNone<GrenadeClientCleanupData>().WithEntityAccess())
{
commandBuffer.AddComponent<GrenadeClientCleanupData>(entity);
}
// Record the grenade position every frame so it can be used when instantiating the explosion effect
foreach (var (transform, grenadeData) in SystemAPI.Query<TransformAspect, RefRW<GrenadeClientCleanupData>>())
{
grenadeData.ValueRW.Position = transform.WorldPosition;
}
// When a grenade is destroyed (GrenadeData deleted) we'll still see the system component and spawn an explosion
// effect based on that
foreach (var (grenade, entity) in SystemAPI.Query<RefRO<GrenadeClientCleanupData>>().WithNone<GrenadeData>().WithEntityAccess())
{
var explosion = commandBuffer.Instantiate(explosionPrefab);
#if !ENABLE_TRANSFORM_V1
commandBuffer.SetComponent(explosion, LocalTransform.FromPosition(grenade.ValueRO.Position));
#else
commandBuffer.SetComponent(explosion, new Translation {Value = grenade.ValueRO.Position});
#endif
commandBuffer.AddComponent(explosion, new ExplosionData(){Timer = (float)time + config.ExplosionTimer});
commandBuffer.RemoveComponent<GrenadeClientCleanupData>(entity);
}
// The explosion particle systems need to be destroyed manually when one loop has finished
foreach (var (explosionData, entity) in SystemAPI.Query<RefRO<ExplosionData>>().WithAll<Simulate>().WithEntityAccess())
{
if (explosionData.ValueRO.Timer < time)
commandBuffer.DestroyEntity(entity);
}
commandBuffer.Playback(state.EntityManager);
}
}
}