2 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
12 private const float MinValue =
int.MinValue,
13 MaxValue =
int.MaxValue;
17 private float angleMin, angleMax;
29 AngleMinRad = MathHelper.ToRadians(MathHelper.Clamp(value, -360.0f, 360.0f));
40 AngleMaxRad = MathHelper.ToRadians(MathHelper.Clamp(value, -360.0f, 360.0f));
69 [
Editable(ValueStep = 1, MinValueInt = 0, MaxValueInt = 1000),
Serialize(0,
IsPropertySaveable.Yes, description:
"The number of particles to spawn per frame, or every x seconds if EmitInterval is set.")]
75 [
Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 10.0f, MinValueFloat = 0.0f),
Serialize(0f,
IsPropertySaveable.Yes, description:
"If larger than 0, a particle is spawned every x pixels across the ray cast by a hitscan weapon.")]
78 [
Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 100.0f, MinValueFloat = 0.0f),
Serialize(0f,
IsPropertySaveable.Yes, description:
"Delay before the emitter becomes active after being created.")]
87 [
Editable,
Serialize(
true,
IsPropertySaveable.Yes, description:
"Should the entity heading direction be applied to the particle rotation? Only affects after flipping the texture and when CopyEntityAngle is true.")]
90 [
Editable,
Serialize(
false,
IsPropertySaveable.Yes, description:
"Only relevant for status effects. Makes the emitter copy the angle from the target of the effect instead of the entity applying the effect.")]
93 [
Editable,
Serialize(
false,
IsPropertySaveable.Yes, description:
"Only relevant for particles spawned by another particle. Makes the emitter copy the scale of the parent particle.")]
133 if (element.GetAttributeBool(
"drawontop",
false))
142 private float emitTimer;
143 private float burstEmitTimer;
144 private float initialDelay;
155 System.Diagnostics.Debug.Assert(prefab !=
null,
"The prefab of a particle emitter cannot be null");
159 public void Emit(
float deltaTime, Vector2 position,
Hull? hullGuess =
null,
float angle = 0.0f,
float particleRotation = 0.0f,
float velocityMultiplier = 1.0f,
float sizeMultiplier = 1.0f,
float amountMultiplier = 1.0f, Color? colorMultiplier =
null,
ParticlePrefab? overrideParticle =
null,
bool mirrorAngle =
false, Tuple<Vector2, Vector2>? tracerPoints =
null)
163 if (initialDelay <
Prefab.Properties.InitialDelay)
165 initialDelay += deltaTime;
169 emitTimer += deltaTime * amountMultiplier;
170 burstEmitTimer -= deltaTime;
172 if (
Prefab.Properties.EmitAcrossRayInterval > 0.0f && tracerPoints !=
null)
174 Vector2 dir = tracerPoints.Item2 - tracerPoints.Item1;
175 if (dir.LengthSquared() > 0.001f)
177 float dist = dir.Length();
179 for (
float z = 0.0f; z < dist; z +=
Prefab.Properties.EmitAcrossRayInterval)
181 Vector2 pos = tracerPoints.Item1 + dir * z;
182 Emit(pos, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints:
null);
187 if (
Prefab.Properties.ParticlesPerSecond > 0)
189 float emitInterval = 1.0f /
Prefab.Properties.ParticlesPerSecond;
190 while (emitTimer > emitInterval)
192 Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: tracerPoints);
193 emitTimer -= emitInterval;
197 if (burstEmitTimer > 0.0f) {
return; }
199 burstEmitTimer =
Prefab.Properties.EmitInterval;
200 for (
int i = 0; i <
Prefab.Properties.ParticleAmount * amountMultiplier; i++)
202 Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: tracerPoints);
206 private void Emit(Vector2 position,
Hull? hullGuess,
float angle,
float particleRotation,
float velocityMultiplier,
float sizeMultiplier, Color? colorMultiplier =
null,
ParticlePrefab? overrideParticle =
null,
bool mirrorAngle =
false, Tuple<Vector2, Vector2>? tracerPoints =
null)
208 var particlePrefab = overrideParticle ??
Prefab.ParticlePrefab;
209 if (particlePrefab ==
null)
211 DebugConsole.AddWarning($
"Could not find the particle prefab \"{Prefab.ParticlePrefabName}\".",
216 Vector2 velocity = Vector2.Zero;
217 if (!MathUtils.NearlyEqual(
Prefab.Properties.VelocityMax * velocityMultiplier, 0.0f) || !MathUtils.NearlyEqual(
Prefab.Properties.DistanceMax, 0.0f))
219 angle += Rand.Range(
Prefab.Properties.AngleMinRad,
Prefab.Properties.AngleMaxRad) * (mirrorAngle ? -1 : 1);
220 Vector2 dir =
new Vector2((
float)Math.Cos(angle), (
float)Math.Sin(angle));
221 velocity = dir * Rand.Range(
Prefab.Properties.VelocityMin,
Prefab.Properties.VelocityMax) * velocityMultiplier;
222 position += dir * Rand.Range(
Prefab.Properties.DistanceMin,
Prefab.Properties.DistanceMax);
225 var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess,
229 if (particle !=
null)
234 if (colorMultiplier.HasValue)
236 particle.ColorMultiplier = colorMultiplier.Value.ToVector4();
247 Rectangle bounds =
new Rectangle((
int)startPosition.X, (
int)startPosition.Y, (
int)startPosition.X, (
int)startPosition.Y);
248 if (
Prefab.ParticlePrefab ==
null) {
return bounds; }
250 for (
float angle =
Prefab.Properties.AngleMinRad; angle <=
Prefab.Properties.AngleMaxRad; angle += 0.1f)
252 Vector2 velocity =
new Vector2((
float)Math.Cos(angle), (
float)Math.Sin(angle)) *
Prefab.Properties.VelocityMax;
253 Vector2 endPosition =
Prefab.ParticlePrefab.CalculateEndPosition(startPosition, velocity);
255 Vector2 endSize =
Prefab.ParticlePrefab.CalculateEndSize();
256 float spriteExtent = 0.0f;
257 foreach (
Sprite sprite
in Prefab.ParticlePrefab.Sprites)
261 spriteExtent = Math.Max(spriteExtent, Math.Max(spriteSheet.FrameSize.X * endSize.X, spriteSheet.FrameSize.Y * endSize.Y));
265 spriteExtent = Math.Max(spriteExtent, Math.Max(sprite.
size.X * endSize.X, sprite.
size.Y * endSize.Y));
270 (
int)Math.Min(bounds.X, endPosition.X -
Prefab.Properties.DistanceMax - spriteExtent / 2),
271 (
int)Math.Min(bounds.Y, endPosition.Y -
Prefab.Properties.DistanceMax - spriteExtent / 2),
272 (
int)Math.Max(bounds.X, endPosition.X +
Prefab.Properties.DistanceMax + spriteExtent / 2),
273 (
int)Math.Max(bounds.Y, endPosition.Y +
Prefab.Properties.DistanceMax + spriteExtent / 2));
276 bounds =
new Rectangle(bounds.X, bounds.Y, bounds.Width - bounds.X, bounds.Height - bounds.Y);
304 if (element ==
null) {
throw new ArgumentNullException(nameof(element)); }
ContentPackage? ContentPackage
Identifier GetAttributeIdentifier(string key, string def)
readonly ParticleEmitterPrefab Prefab
void Emit(float deltaTime, Vector2 position, Hull? hullGuess=null, float angle=0.0f, float particleRotation=0.0f, float velocityMultiplier=1.0f, float sizeMultiplier=1.0f, float amountMultiplier=1.0f, Color? colorMultiplier=null, ParticlePrefab? overrideParticle=null, bool mirrorAngle=false, Tuple< Vector2, Vector2 >? tracerPoints=null)
ParticleEmitter(ContentXElement element)
Rectangle CalculateParticleBounds(Vector2 startPosition)
ParticleEmitter(ParticleEmitterPrefab prefab)
readonly? ContentPackage ContentPackage
ParticleDrawOrder DrawOrder
ParticleEmitterPrefab(ParticlePrefab prefab, ParticleEmitterProperties properties)
ParticleEmitterPrefab(ContentXElement element)
readonly ParticleEmitterProperties Properties
readonly Identifier ParticlePrefabName
Dictionary< Identifier, SerializableProperty > SerializableProperties
ParticleDrawOrder DrawOrder
bool HighQualityCollisionDetection
float EmitAcrossRayInterval
bool CopyParentParticleScale
ParticleEmitterProperties(XElement element)
ParticleDrawOrder DrawOrder
static readonly PrefabCollection< ParticlePrefab > Prefabs
ContentPackage? ContentPackage
readonly Identifier Identifier
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)