From 612dbca12a880328cf650f1fa4df0de413ad2dcc Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Fri, 6 Aug 2021 16:35:08 +0200 Subject: [PATCH] Improved particle trail management: instead of instantiating many tiny particle effects for each segment of the trail, instantiate a single particle trail effect and attach it to the entity. The particle system is made looping and simulates in world space, to have it create a proper trail. Particle trails are destroyed with a delay when detached from their entity, so that the particles have the time to finish their simulation. --- Assets/Scripts/Game/Entity.cs | 33 ++++++++++++++++++- Assets/Scripts/Modules/GameModule.Interop.cs | 6 ++-- Assets/Scripts/Modules/GameModule.cs | 13 ++++++-- .../Support/ParticleTrailController.cs | 22 ++++++++----- Assets/Scripts/VisualStyle.cs | 11 ++++--- .../Original/Particles/ParticleTrail.prefab | 6 ++-- engine/Quake/cl_main.c | 14 ++++---- engine/Quake/r_part.c | 4 +-- engine/Quake/render.h | 4 +-- engine/UniQuake/game_uniquake.c | 6 ++-- 10 files changed, 83 insertions(+), 36 deletions(-) diff --git a/Assets/Scripts/Game/Entity.cs b/Assets/Scripts/Game/Entity.cs index f1c60dd..08d6550 100644 --- a/Assets/Scripts/Game/Entity.cs +++ b/Assets/Scripts/Game/Entity.cs @@ -18,6 +18,8 @@ public class Entity private AliasModel aliasModel; private GameObject brushModel; private GameObject worldModel; + + private ParticleTrailController particleTrail; public Entity(int entityNum, VisualStyle visualStyle, Layers layer) { @@ -38,9 +40,10 @@ public class Entity meshRenderer = gameObject.AddComponent(); meshRenderer.enabled = false; } - + public void Destroy() { + ClearParticleTrail(); Object.Destroy(gameObject); } @@ -131,6 +134,13 @@ public class Entity { gameObject.transform.position = position; gameObject.transform.rotation = rotation; + + // Note: we can't parent the particle trail to the entity's game object, + // since that will instantly destroy all particles when the entity is destroyed or disabled. + if (particleTrail != null) + { + particleTrail.transform.position = position; + } } public void SetSkin(int skinNum) @@ -168,4 +178,25 @@ public class Entity SetSkin(skinNumber); } + + public bool HasParticleTrail(ParticleTrail type) + { + return particleTrail != null && particleTrail.Type == type; + } + + public void SetParticleTrail(ParticleTrailController trail) + { + ClearParticleTrail(); + + particleTrail = trail; + } + + public void ClearParticleTrail() + { + if (particleTrail != null) + { + particleTrail.Stop(); + particleTrail = null; + } + } } diff --git a/Assets/Scripts/Modules/GameModule.Interop.cs b/Assets/Scripts/Modules/GameModule.Interop.cs index 6920181..0b1d369 100644 --- a/Assets/Scripts/Modules/GameModule.Interop.cs +++ b/Assets/Scripts/Modules/GameModule.Interop.cs @@ -126,13 +126,13 @@ public partial class GameModule : CallbackHandler } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void CreateParticleTrailCallback(IntPtr context, int type, ref QVec3 start, ref QVec3 end); + private delegate void CreateParticleTrailCallback(IntPtr context, int entNum, int type, ref QVec3 origin); [MonoPInvokeCallback(typeof(CreateParticleTrailCallback))] - private static void Callback_CreateParticleTrail(IntPtr context, int type, ref QVec3 start, ref QVec3 end) + private static void Callback_CreateParticleTrail(IntPtr context, int entNum, int type, ref QVec3 origin) { Profiler.BeginSample("CreateParticleTrail"); - GetSelf(context).CreateParticleTrail(type, start.ToUnityPosition(), end.ToUnityPosition()); + GetSelf(context).CreateParticleTrail(entNum, type, origin.ToUnityPosition()); Profiler.EndSample(); } } diff --git a/Assets/Scripts/Modules/GameModule.cs b/Assets/Scripts/Modules/GameModule.cs index 9bd05f5..03f7dd7 100644 --- a/Assets/Scripts/Modules/GameModule.cs +++ b/Assets/Scripts/Modules/GameModule.cs @@ -97,7 +97,7 @@ public partial class GameModule uq.CurrentStyle.Particles.CreateRogueExplosion(position, colorMin, colorMax, uq.GameLayer); } - private void CreateParticleTrail(int type, Vector3 start, Vector3 end) + private void CreateParticleTrail(int entityNum, int type, Vector3 position) { int dec; if (type < 128) @@ -109,7 +109,16 @@ public partial class GameModule dec = 1; type -= 128; } + + var trailType = (ParticleTrail)type; + var entity = uq.GameState.GetEntity(entityNum); + if (entity.HasParticleTrail(trailType)) + return; + + var particleTrail = uq.CurrentStyle.Particles.CreateParticleTrail(trailType, position, dec, uq.GameLayer); + if (particleTrail == null) + return; - uq.CurrentStyle.Particles.CreateParticleTrail((ParticleTrail)type, start, end, dec, uq.GameLayer); + entity.SetParticleTrail(particleTrail); } } diff --git a/Assets/Scripts/Support/ParticleTrailController.cs b/Assets/Scripts/Support/ParticleTrailController.cs index 6b0565c..028890f 100644 --- a/Assets/Scripts/Support/ParticleTrailController.cs +++ b/Assets/Scripts/Support/ParticleTrailController.cs @@ -1,13 +1,17 @@ -using System.Collections; -using UnityEngine; +using UnityEngine; public class ParticleTrailController : MonoBehaviour { - private Vector3 endPosition; + [SerializeField] + private ParticleSystem effect; - public void Initialize(ParticleSystem effect, ParticleTrail type, Vector3 end, int interval) + [SerializeField] + private ParticleTrail type; + public ParticleTrail Type => type; + + public void Initialize(ParticleSystem effect, ParticleTrail type, int interval) { - endPosition = end; + this.type = type; var main = effect.main; var shape = effect.shape; @@ -78,11 +82,11 @@ public class ParticleTrailController : MonoBehaviour } } - IEnumerator Start() + public void Stop() { - // Instantly move the particle emitter to its end position, to force it to emit all particles at once - yield return null; - transform.position = endPosition; + // Allow the current particles to finish simulation, then auto-destroy the particle effect + var main = effect.main; + Destroy(gameObject, main.startLifetime.constantMax); } private static ParticleSystem.MinMaxGradient SetupGradients(GradientColorKey[] minKeys, GradientColorKey[] maxKeys) diff --git a/Assets/Scripts/VisualStyle.cs b/Assets/Scripts/VisualStyle.cs index 179bd33..9691354 100644 --- a/Assets/Scripts/VisualStyle.cs +++ b/Assets/Scripts/VisualStyle.cs @@ -259,17 +259,18 @@ public class ParticleSystems InstantiateEffect(lavaSplash, position, layer); } - public virtual void CreateParticleTrail(ParticleTrail type, Vector3 start, Vector3 end, int interval, Layers layer) + public virtual ParticleTrailController CreateParticleTrail(ParticleTrail type, Vector3 position, int interval, Layers layer) { - var effect = InstantiateEffect(particleTrail, start, layer); + var effect = InstantiateEffect(particleTrail, position, layer); if (effect == null) - return; + return null; var controller = effect.gameObject.GetComponent(); if (controller == null) - return; + return null; - controller.Initialize(effect, type, end, interval); + controller.Initialize(effect, type, interval); + return controller; } protected virtual ParticleSystem InstantiateEffect(ParticleSystem template, Vector3 position, Layers layer) diff --git a/Assets/Styles/Original/Particles/ParticleTrail.prefab b/Assets/Styles/Original/Particles/ParticleTrail.prefab index ca9a4ae..9d98f19 100644 --- a/Assets/Styles/Original/Particles/ParticleTrail.prefab +++ b/Assets/Styles/Original/Particles/ParticleTrail.prefab @@ -47,7 +47,7 @@ ParticleSystem: cullingMode: 0 ringBufferMode: 0 ringBufferLoopRange: {x: 0, y: 1} - looping: 0 + looping: 1 prewarm: 0 playOnAwake: 1 useUnscaledTime: 0 @@ -106,7 +106,7 @@ ParticleSystem: m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4 - moveWithTransform: 0 + moveWithTransform: 1 moveWithCustomTransform: {fileID: 0} scalingMode: 1 randomSeed: 0 @@ -4817,3 +4817,5 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 7220fa57562e4a48b6fc17e6bfc0a945, type: 3} m_Name: m_EditorClassIdentifier: + effect: {fileID: 0} + type: 0 diff --git a/engine/Quake/cl_main.c b/engine/Quake/cl_main.c index f184bef..d1672ba 100644 --- a/engine/Quake/cl_main.c +++ b/engine/Quake/cl_main.c @@ -557,25 +557,25 @@ void CL_RelinkEntities (void) } if (ent->model->flags & EF_GIB) - R_RocketTrail (oldorg, ent->origin, 2); + R_RocketTrail (ent->num, oldorg, ent->origin, 2); else if (ent->model->flags & EF_ZOMGIB) - R_RocketTrail (oldorg, ent->origin, 4); + R_RocketTrail (ent->num, oldorg, ent->origin, 4); else if (ent->model->flags & EF_TRACER) - R_RocketTrail (oldorg, ent->origin, 3); + R_RocketTrail (ent->num, oldorg, ent->origin, 3); else if (ent->model->flags & EF_TRACER2) - R_RocketTrail (oldorg, ent->origin, 5); + R_RocketTrail (ent->num, oldorg, ent->origin, 5); else if (ent->model->flags & EF_ROCKET) { - R_RocketTrail (oldorg, ent->origin, 0); + R_RocketTrail (ent->num, oldorg, ent->origin, 0); dl = CL_AllocDlight (i); VectorCopy (ent->origin, dl->origin); dl->radius = 200; dl->die = cl.time + 0.01; } else if (ent->model->flags & EF_GRENADE) - R_RocketTrail (oldorg, ent->origin, 1); + R_RocketTrail (ent->num, oldorg, ent->origin, 1); else if (ent->model->flags & EF_TRACER3) - R_RocketTrail (oldorg, ent->origin, 6); + R_RocketTrail (ent->num, oldorg, ent->origin, 6); ent->forcelink = false; diff --git a/engine/Quake/r_part.c b/engine/Quake/r_part.c index 4b4e42e..4990c16 100644 --- a/engine/Quake/r_part.c +++ b/engine/Quake/r_part.c @@ -647,9 +647,9 @@ R_RocketTrail FIXME -- rename function and use #defined types instead of numbers =============== */ -void R_RocketTrail (vec3_t start, vec3_t end, int type) +void R_RocketTrail (int entnum, vec3_t start, vec3_t end, int type) { - UQ_Game_ParticleTrail(type, start, end); + UQ_Game_ParticleTrail(entnum, type, start); #ifdef USE_OPENGL vec3_t vec; diff --git a/engine/Quake/render.h b/engine/Quake/render.h index 071ca54..73ed61a 100644 --- a/engine/Quake/render.h +++ b/engine/Quake/render.h @@ -152,7 +152,7 @@ void R_NewMap (void); void R_ParseParticleEffect (void); void R_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count); -void R_RocketTrail (vec3_t start, vec3_t end, int type); +void R_RocketTrail (int entnum, vec3_t start, vec3_t end, int type); void R_EntityParticles (entity_t *ent); void R_BlobExplosion (vec3_t org); void R_ParticleExplosion (vec3_t org); @@ -184,6 +184,6 @@ void UQ_Game_ParticleExplosion2(vec3_t origin, int colorStart, int colorLength); void UQ_Game_BlobExplosion(vec3_t origin); void UQ_Game_TeleportSplash(vec3_t origin); void UQ_Game_LavaSplash(vec3_t origin); -void UQ_Game_ParticleTrail(int type, vec3_t start, vec3_t end); +void UQ_Game_ParticleTrail(int entnum, int type, vec3_t origin); #endif /* _QUAKE_RENDER_H */ diff --git a/engine/UniQuake/game_uniquake.c b/engine/UniQuake/game_uniquake.c index 9e7d87f..1f90d67 100644 --- a/engine/UniQuake/game_uniquake.c +++ b/engine/UniQuake/game_uniquake.c @@ -12,7 +12,7 @@ typedef struct unity_gamecalls_s void(*RunParticleEffect)(void *context, vec3_t origin, vec3_t direction, unsigned int colorMin, unsigned int colorMax, int count); void(*CreateParticleEffect)(void *context, int type, vec3_t origin, unsigned int colorMin, unsigned int colorMax); - void(*CreateParticleTrail)(void *context, int type, vec3_t start, vec3_t end); + void(*CreateParticleTrail)(void *context, int entnum, int type, vec3_t origin); } unity_gamecalls_t; static void *unity_context; @@ -94,7 +94,7 @@ void UQ_Game_LavaSplash(vec3_t origin) unity_gamecalls->CreateParticleEffect(unity_context, PARTFX_LAVA_SPLASH, origin, 0, 0); } -void UQ_Game_ParticleTrail(int type, vec3_t start, vec3_t end) +void UQ_Game_ParticleTrail(int entnum, int type, vec3_t origin) { - unity_gamecalls->CreateParticleTrail(unity_context, type, start, end); + unity_gamecalls->CreateParticleTrail(unity_context, entnum, type, origin); }