Browse Source

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.

readme
Nico de Poel 5 years ago
parent
commit
612dbca12a
  1. 31
      Assets/Scripts/Game/Entity.cs
  2. 6
      Assets/Scripts/Modules/GameModule.Interop.cs
  3. 13
      Assets/Scripts/Modules/GameModule.cs
  4. 22
      Assets/Scripts/Support/ParticleTrailController.cs
  5. 11
      Assets/Scripts/VisualStyle.cs
  6. 6
      Assets/Styles/Original/Particles/ParticleTrail.prefab
  7. 14
      engine/Quake/cl_main.c
  8. 4
      engine/Quake/r_part.c
  9. 4
      engine/Quake/render.h
  10. 6
      engine/UniQuake/game_uniquake.c

31
Assets/Scripts/Game/Entity.cs

@ -19,6 +19,8 @@ public class Entity
private GameObject brushModel;
private GameObject worldModel;
private ParticleTrailController particleTrail;
public Entity(int entityNum, VisualStyle visualStyle, Layers layer)
{
this.entityNum = entityNum;
@ -41,6 +43,7 @@ public class Entity
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;
}
}
}

6
Assets/Scripts/Modules/GameModule.Interop.cs

@ -126,13 +126,13 @@ public partial class GameModule : CallbackHandler<GameModule>
}
[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();
}
}

13
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)
@ -110,6 +110,15 @@ public partial class GameModule
type -= 128;
}
uq.CurrentStyle.Particles.CreateParticleTrail((ParticleTrail)type, start, end, dec, uq.GameLayer);
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;
entity.SetParticleTrail(particleTrail);
}
}

22
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)

11
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<ParticleTrailController>();
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)

6
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

14
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;

4
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;

4
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 */

6
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);
}
Loading…
Cancel
Save