(61d00a474) v0.9.7.1
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Particles
|
||||
{
|
||||
class Decal
|
||||
{
|
||||
public readonly DecalPrefab Prefab;
|
||||
private Vector2 position;
|
||||
|
||||
public readonly Sprite Sprite;
|
||||
|
||||
private float fadeTimer;
|
||||
|
||||
public float FadeTimer
|
||||
{
|
||||
get { return fadeTimer; }
|
||||
set { fadeTimer = MathHelper.Clamp(value, 0.0f, LifeTime); }
|
||||
}
|
||||
|
||||
public float FadeInTime
|
||||
{
|
||||
get { return Prefab.FadeInTime; }
|
||||
}
|
||||
|
||||
public float FadeOutTime
|
||||
{
|
||||
get { return Prefab.FadeOutTime; }
|
||||
}
|
||||
|
||||
public float LifeTime
|
||||
{
|
||||
get { return Prefab.LifeTime; }
|
||||
}
|
||||
|
||||
public Color Color
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector2 worldPos = position
|
||||
+ clippedSourceRect.Size.ToVector2() / 2 * scale
|
||||
+ hull.Rect.Location.ToVector2();
|
||||
if (hull.Submarine != null) { worldPos += hull.Submarine.DrawPosition; }
|
||||
return worldPos;
|
||||
}
|
||||
}
|
||||
|
||||
private Hull hull;
|
||||
|
||||
private float scale;
|
||||
|
||||
private Rectangle clippedSourceRect;
|
||||
|
||||
public Decal(DecalPrefab prefab, float scale, Vector2 worldPosition, Hull hull)
|
||||
{
|
||||
Prefab = prefab;
|
||||
|
||||
this.hull = hull;
|
||||
|
||||
//transform to hull-relative coordinates so we don't have to worry about the hull moving
|
||||
position = worldPosition - hull.WorldRect.Location.ToVector2();
|
||||
|
||||
Vector2 drawPos = position + hull.Rect.Location.ToVector2();
|
||||
|
||||
Sprite = prefab.Sprites[Rand.Range(0, prefab.Sprites.Count, Rand.RandSync.Unsynced)];
|
||||
Color = prefab.Color;
|
||||
|
||||
Rectangle drawRect = new Rectangle(
|
||||
(int)(drawPos.X - Sprite.size.X / 2 * scale),
|
||||
(int)(drawPos.Y + Sprite.size.Y / 2 * scale),
|
||||
(int)(Sprite.size.X * scale),
|
||||
(int)(Sprite.size.Y * scale));
|
||||
|
||||
Rectangle overFlowAmount = new Rectangle(
|
||||
(int)Math.Max(hull.Rect.X - drawRect.X, 0.0f),
|
||||
(int)Math.Max(drawRect.Y - hull.Rect.Y, 0.0f),
|
||||
(int)Math.Max(drawRect.Right - hull.Rect.Right, 0.0f),
|
||||
(int)Math.Max((hull.Rect.Y - hull.Rect.Height) - (drawRect.Y - drawRect.Height), 0.0f));
|
||||
|
||||
clippedSourceRect = new Rectangle(
|
||||
Sprite.SourceRect.X + (int)(overFlowAmount.X / scale),
|
||||
Sprite.SourceRect.Y + (int)(overFlowAmount.Y / scale),
|
||||
Sprite.SourceRect.Width - (int)((overFlowAmount.X + overFlowAmount.Width) / scale),
|
||||
Sprite.SourceRect.Height - (int)((overFlowAmount.Y + overFlowAmount.Height) / scale));
|
||||
|
||||
position -= new Vector2(Sprite.size.X / 2 * scale - overFlowAmount.X, -Sprite.size.Y / 2 * scale + overFlowAmount.Y);
|
||||
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
fadeTimer += deltaTime;
|
||||
}
|
||||
|
||||
public void StopFadeIn()
|
||||
{
|
||||
Color *= GetAlpha();
|
||||
fadeTimer = Prefab.FadeInTime;
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, Hull hull, float depth)
|
||||
{
|
||||
Vector2 drawPos = position + hull.Rect.Location.ToVector2();
|
||||
if (hull.Submarine != null) { drawPos += hull.Submarine.DrawPosition; }
|
||||
drawPos.Y = -drawPos.Y;
|
||||
|
||||
spriteBatch.Draw(Sprite.Texture, drawPos, clippedSourceRect, Color * GetAlpha(), 0, Vector2.Zero, scale, SpriteEffects.None, depth);
|
||||
}
|
||||
|
||||
private float GetAlpha()
|
||||
{
|
||||
if (fadeTimer < Prefab.FadeInTime)
|
||||
{
|
||||
return fadeTimer / Prefab.FadeInTime;
|
||||
}
|
||||
else if (fadeTimer > Prefab.LifeTime - Prefab.FadeOutTime)
|
||||
{
|
||||
return (Prefab.LifeTime - fadeTimer) / Prefab.FadeOutTime;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Particles
|
||||
{
|
||||
class DecalManager
|
||||
{
|
||||
public PrefabCollection<DecalPrefab> Prefabs { get; private set; }
|
||||
|
||||
public DecalManager()
|
||||
{
|
||||
Prefabs = new PrefabCollection<DecalPrefab>();
|
||||
foreach (ContentFile configFile in GameMain.Instance.GetFilesOfType(ContentType.Decals))
|
||||
{
|
||||
LoadFromFile(configFile);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromFile(ContentFile configFile)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
|
||||
if (doc == null) { return; }
|
||||
|
||||
bool allowOverriding = false;
|
||||
var mainElement = doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
allowOverriding = true;
|
||||
}
|
||||
|
||||
foreach (XElement sourceElement in mainElement.Elements())
|
||||
{
|
||||
var element = sourceElement.IsOverride() ? sourceElement.FirstElement() : sourceElement;
|
||||
string name = element.Name.ToString().ToLowerInvariant();
|
||||
if (Prefabs.ContainsKey(name))
|
||||
{
|
||||
if (allowOverriding || sourceElement.IsOverride())
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the existing decal prefab '{name}' using the file '{configFile.Path}'", Color.Yellow);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{configFile.Path}': Duplicate decal prefab '{name}' found in '{configFile.Path}'! Each decal prefab must have a unique name. " +
|
||||
"Use <override></override> tags to override prefabs.");
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Prefabs.Add(new DecalPrefab(element, configFile), allowOverriding || sourceElement.IsOverride());
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveByFile(string filePath)
|
||||
{
|
||||
Prefabs.RemoveByFile(filePath);
|
||||
}
|
||||
|
||||
public Decal CreateDecal(string decalName, float scale, Vector2 worldPosition, Hull hull)
|
||||
{
|
||||
if (!Prefabs.ContainsKey(decalName.ToLowerInvariant()))
|
||||
{
|
||||
DebugConsole.ThrowError("Decal prefab " + decalName + " not found!");
|
||||
return null;
|
||||
}
|
||||
|
||||
DecalPrefab prefab = Prefabs[decalName];
|
||||
|
||||
return new Decal(prefab, scale, worldPosition, hull);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Particles
|
||||
{
|
||||
class DecalPrefab : IPrefab, IDisposable
|
||||
{
|
||||
public readonly string Name;
|
||||
|
||||
public string OriginalName { get { return Name; } }
|
||||
|
||||
private string _identifier;
|
||||
public string Identifier
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_identifier == null)
|
||||
{
|
||||
_identifier = Name.ToLowerInvariant();
|
||||
}
|
||||
return _identifier;
|
||||
}
|
||||
}
|
||||
|
||||
public string FilePath { get; private set; }
|
||||
|
||||
public ContentPackage ContentPackage { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (Sprite spr in Sprites)
|
||||
{
|
||||
spr.Remove();
|
||||
}
|
||||
Sprites.Clear();
|
||||
}
|
||||
|
||||
public readonly List<Sprite> Sprites;
|
||||
|
||||
public readonly Color Color;
|
||||
|
||||
public readonly float LifeTime;
|
||||
public readonly float FadeOutTime;
|
||||
public readonly float FadeInTime;
|
||||
|
||||
public DecalPrefab(XElement element, ContentFile file)
|
||||
{
|
||||
Name = element.Name.ToString();
|
||||
|
||||
FilePath = file.Path;
|
||||
|
||||
ContentPackage = file.ContentPackage;
|
||||
|
||||
Sprites = new List<Sprite>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("sprite", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Sprites.Add(new Sprite(subElement));
|
||||
}
|
||||
}
|
||||
|
||||
Color = new Color(element.GetAttributeVector4("color", Vector4.One));
|
||||
|
||||
LifeTime = element.GetAttributeFloat("lifetime", 10.0f);
|
||||
FadeOutTime = Math.Min(LifeTime, element.GetAttributeFloat("fadeouttime", 1.0f));
|
||||
FadeInTime = Math.Min(LifeTime - FadeOutTime, element.GetAttributeFloat("fadeintime", 0.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.Particles
|
||||
{
|
||||
class Particle
|
||||
{
|
||||
private ParticlePrefab prefab;
|
||||
|
||||
public delegate void OnChangeHullHandler(Vector2 position, Hull currentHull);
|
||||
public OnChangeHullHandler OnChangeHull;
|
||||
|
||||
private Vector2 position;
|
||||
private Vector2 prevPosition;
|
||||
|
||||
private Vector2 velocity;
|
||||
|
||||
private float rotation;
|
||||
private float prevRotation;
|
||||
|
||||
private float angularVelocity;
|
||||
|
||||
private Vector2 dragVec = Vector2.Zero;
|
||||
private int dragWait = 0;
|
||||
|
||||
private Vector2 size;
|
||||
private Vector2 sizeChange;
|
||||
|
||||
private Color color;
|
||||
private bool changeColor;
|
||||
|
||||
private int spriteIndex;
|
||||
|
||||
private float totalLifeTime;
|
||||
private float lifeTime;
|
||||
|
||||
private float startDelay;
|
||||
|
||||
private Vector2 velocityChange;
|
||||
private Vector2 velocityChangeWater;
|
||||
|
||||
private Vector2 drawPosition;
|
||||
private float drawRotation;
|
||||
|
||||
private Hull currentHull;
|
||||
|
||||
private List<Gap> hullGaps;
|
||||
|
||||
private bool hasSubEmitters;
|
||||
private List<ParticleEmitter> subEmitters = new List<ParticleEmitter>();
|
||||
|
||||
private float animState;
|
||||
private int animFrame;
|
||||
|
||||
private float collisionUpdateTimer;
|
||||
|
||||
public bool HighQualityCollisionDetection;
|
||||
|
||||
public bool DrawOnTop { get; private set; }
|
||||
|
||||
public ParticlePrefab.DrawTargetType DrawTarget
|
||||
{
|
||||
get { return prefab.DrawTarget; }
|
||||
}
|
||||
|
||||
public ParticleBlendState BlendState
|
||||
{
|
||||
get { return prefab.BlendState; }
|
||||
}
|
||||
|
||||
public float StartDelay
|
||||
{
|
||||
get { return startDelay; }
|
||||
set { startDelay = MathHelper.Clamp(value, Prefab.StartDelayMin, prefab.StartDelayMax); }
|
||||
}
|
||||
|
||||
public Vector2 Size
|
||||
{
|
||||
get { return size; }
|
||||
set { size = value; }
|
||||
}
|
||||
|
||||
public Hull CurrentHull
|
||||
{
|
||||
get { return currentHull; }
|
||||
}
|
||||
|
||||
public ParticlePrefab Prefab
|
||||
{
|
||||
get { return prefab; }
|
||||
}
|
||||
|
||||
public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false)
|
||||
{
|
||||
this.prefab = prefab;
|
||||
|
||||
spriteIndex = Rand.Int(prefab.Sprites.Count);
|
||||
|
||||
animState = 0;
|
||||
animFrame = 0;
|
||||
dragWait = 0;
|
||||
dragVec = Vector2.Zero;
|
||||
|
||||
currentHull = Hull.FindHull(position, hullGuess);
|
||||
|
||||
this.position = position;
|
||||
prevPosition = position;
|
||||
|
||||
drawPosition = position;
|
||||
|
||||
velocity = MathUtils.IsValid(speed) ? speed : Vector2.Zero;
|
||||
|
||||
if (currentHull?.Submarine != null)
|
||||
{
|
||||
velocity += ConvertUnits.ToDisplayUnits(currentHull.Submarine.Velocity);
|
||||
}
|
||||
|
||||
this.rotation = rotation + Rand.Range(prefab.StartRotationMinRad, prefab.StartRotationMaxRad);
|
||||
prevRotation = rotation;
|
||||
|
||||
angularVelocity = Rand.Range(prefab.AngularVelocityMinRad, prefab.AngularVelocityMaxRad);
|
||||
|
||||
totalLifeTime = prefab.LifeTime;
|
||||
lifeTime = prefab.LifeTime;
|
||||
startDelay = Rand.Range(prefab.StartDelayMin, prefab.StartDelayMax);
|
||||
|
||||
size = prefab.StartSizeMin + (prefab.StartSizeMax - prefab.StartSizeMin) * Rand.Range(0.0f, 1.0f);
|
||||
|
||||
sizeChange = prefab.SizeChangeMin + (prefab.SizeChangeMax - prefab.SizeChangeMin) * Rand.Range(0.0f, 1.0f);
|
||||
|
||||
color = prefab.StartColor;
|
||||
changeColor = prefab.StartColor != prefab.EndColor;
|
||||
|
||||
velocityChange = prefab.VelocityChangeDisplay;
|
||||
velocityChangeWater = prefab.VelocityChangeWaterDisplay;
|
||||
|
||||
HighQualityCollisionDetection = false;
|
||||
|
||||
OnChangeHull = null;
|
||||
|
||||
subEmitters.Clear();
|
||||
hasSubEmitters = false;
|
||||
foreach (ParticleEmitterPrefab emitterPrefab in prefab.SubEmitters)
|
||||
{
|
||||
subEmitters.Add(new ParticleEmitter(emitterPrefab));
|
||||
hasSubEmitters = true;
|
||||
}
|
||||
|
||||
if (prefab.UseCollision)
|
||||
{
|
||||
hullGaps = currentHull == null ? new List<Gap>() : currentHull.ConnectedGaps;
|
||||
}
|
||||
|
||||
if (prefab.RotateToDirection)
|
||||
{
|
||||
this.rotation = MathUtils.VectorToAngle(new Vector2(velocity.X, -velocity.Y));
|
||||
|
||||
prevRotation = rotation;
|
||||
}
|
||||
|
||||
DrawOnTop = drawOnTop;
|
||||
}
|
||||
|
||||
public bool Update(float deltaTime)
|
||||
{
|
||||
if (startDelay > 0.0f)
|
||||
{
|
||||
startDelay -= deltaTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
prevPosition = position;
|
||||
prevRotation = rotation;
|
||||
|
||||
//over 3 times faster than position += velocity * deltatime
|
||||
position.X += velocity.X * deltaTime;
|
||||
position.Y += velocity.Y * deltaTime;
|
||||
|
||||
if (prefab.RotateToDirection)
|
||||
{
|
||||
if (velocityChange != Vector2.Zero || angularVelocity != 0.0f)
|
||||
{
|
||||
Vector2 relativeVel = velocity;
|
||||
if (currentHull?.Submarine != null)
|
||||
{
|
||||
relativeVel -= ConvertUnits.ToDisplayUnits(currentHull.Submarine.Velocity);
|
||||
}
|
||||
rotation = MathUtils.VectorToAngle(new Vector2(relativeVel.X, -relativeVel.Y));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rotation += angularVelocity * deltaTime;
|
||||
}
|
||||
|
||||
bool inWater = (currentHull == null || (currentHull.Submarine != null && position.Y - currentHull.Submarine.DrawPosition.Y < currentHull.Surface));
|
||||
if (inWater)
|
||||
{
|
||||
velocity.X += velocityChangeWater.X * deltaTime;
|
||||
velocity.Y += velocityChangeWater.Y * deltaTime;
|
||||
if (prefab.WaterDrag > 0.0f)
|
||||
{
|
||||
ApplyDrag(prefab.WaterDrag, deltaTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
velocity.X += velocityChange.X * deltaTime;
|
||||
velocity.Y += velocityChange.Y * deltaTime;
|
||||
if (prefab.Drag > 0.0f)
|
||||
{
|
||||
ApplyDrag(prefab.Drag, deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
size.X += sizeChange.X * deltaTime;
|
||||
size.Y += sizeChange.Y * deltaTime;
|
||||
|
||||
if (changeColor)
|
||||
{
|
||||
color = Color.Lerp(prefab.EndColor, prefab.StartColor, lifeTime / prefab.LifeTime);
|
||||
}
|
||||
|
||||
if (prefab.Sprites[spriteIndex] is SpriteSheet)
|
||||
{
|
||||
animState += deltaTime;
|
||||
int frameCount = ((SpriteSheet)prefab.Sprites[spriteIndex]).FrameCount;
|
||||
animFrame = (int)Math.Min(Math.Floor(animState / prefab.AnimDuration * frameCount), frameCount - 1);
|
||||
}
|
||||
|
||||
lifeTime -= deltaTime;
|
||||
if (lifeTime <= 0.0f || color.A <= 0 || size.X <= 0.0f || size.Y <= 0.0f) { return false; }
|
||||
|
||||
if (hasSubEmitters)
|
||||
{
|
||||
foreach (ParticleEmitter emitter in subEmitters)
|
||||
{
|
||||
emitter.Emit(deltaTime, position, currentHull);
|
||||
}
|
||||
}
|
||||
|
||||
if (!prefab.UseCollision) { return true; }
|
||||
|
||||
if (HighQualityCollisionDetection)
|
||||
{
|
||||
return CollisionUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
collisionUpdateTimer -= deltaTime;
|
||||
if (collisionUpdateTimer <= 0.0f)
|
||||
{
|
||||
//more frequent collision updates if the particle is moving fast
|
||||
collisionUpdateTimer = 0.5f - Math.Min((Math.Abs(velocity.X) + Math.Abs(velocity.Y)) * 0.01f, 0.45f);
|
||||
return CollisionUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CollisionUpdate()
|
||||
{
|
||||
if (currentHull == null)
|
||||
{
|
||||
Hull collidedHull = Hull.FindHull(position);
|
||||
if (collidedHull != null)
|
||||
{
|
||||
if (prefab.DeleteOnCollision) return false;
|
||||
OnWallCollisionOutside(collidedHull);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rectangle hullRect = currentHull.WorldRect;
|
||||
Vector2 collisionNormal = Vector2.Zero;
|
||||
if (velocity.Y < 0.0f && position.Y - prefab.CollisionRadius * size.Y < hullRect.Y - hullRect.Height)
|
||||
{
|
||||
if (prefab.DeleteOnCollision) return false;
|
||||
collisionNormal = new Vector2(0.0f, 1.0f);
|
||||
}
|
||||
else if (velocity.Y > 0.0f && position.Y + prefab.CollisionRadius * size.Y > hullRect.Y)
|
||||
{
|
||||
if (prefab.DeleteOnCollision) return false;
|
||||
collisionNormal = new Vector2(0.0f, -1.0f);
|
||||
}
|
||||
else if (velocity.X < 0.0f && position.X - prefab.CollisionRadius * size.X < hullRect.X)
|
||||
{
|
||||
if (prefab.DeleteOnCollision) return false;
|
||||
collisionNormal = new Vector2(1.0f, 0.0f);
|
||||
}
|
||||
else if (velocity.X > 0.0f && position.X + prefab.CollisionRadius * size.X > hullRect.Right)
|
||||
{
|
||||
if (prefab.DeleteOnCollision) return false;
|
||||
collisionNormal = new Vector2(-1.0f, 0.0f);
|
||||
}
|
||||
|
||||
if (collisionNormal != Vector2.Zero)
|
||||
{
|
||||
bool gapFound = false;
|
||||
foreach (Gap gap in hullGaps)
|
||||
{
|
||||
if (gap.Open <= 0.9f || gap.IsHorizontal != (collisionNormal.X != 0.0f)) continue;
|
||||
|
||||
if (gap.IsHorizontal)
|
||||
{
|
||||
if (gap.WorldRect.Y < position.Y || gap.WorldRect.Y - gap.WorldRect.Height > position.Y) continue;
|
||||
int gapDir = Math.Sign(gap.WorldRect.Center.X - currentHull.WorldRect.Center.X);
|
||||
if (Math.Sign(velocity.X) != gapDir || Math.Sign(position.X - currentHull.WorldRect.Center.X) != gapDir) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gap.WorldRect.X > position.X || gap.WorldRect.Right < position.X) continue;
|
||||
float hullCenterY = currentHull.WorldRect.Y - currentHull.WorldRect.Height / 2;
|
||||
int gapDir = Math.Sign(gap.WorldRect.Y - hullCenterY);
|
||||
if (Math.Sign(velocity.Y) != gapDir || Math.Sign(position.Y - hullCenterY) != gapDir) continue;
|
||||
}
|
||||
|
||||
gapFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!gapFound)
|
||||
{
|
||||
OnWallCollisionInside(currentHull, collisionNormal);
|
||||
}
|
||||
else
|
||||
{
|
||||
Hull newHull = Hull.FindHull(position, currentHull);
|
||||
if (newHull != currentHull)
|
||||
{
|
||||
currentHull = newHull;
|
||||
hullGaps = currentHull == null ? new List<Gap>() : currentHull.ConnectedGaps;
|
||||
OnChangeHull?.Invoke(position, currentHull);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ApplyDrag(float dragCoefficient, float deltaTime)
|
||||
{
|
||||
if (velocity.LengthSquared() < dragVec.LengthSquared())
|
||||
{
|
||||
velocity = Vector2.Zero;
|
||||
return;
|
||||
}
|
||||
if (Math.Abs(velocity.X) < 0.0001f && Math.Abs(velocity.Y) < 0.0001f) return;
|
||||
|
||||
//TODO: some better way to handle particle drag
|
||||
//this doesn't work that well because the drag vector is only updated every 0.5 seconds, allowing the particle to accelerate way more than it should
|
||||
//(e.g. a falling particle can freely accelerate for 0.5 seconds before the drag takes effect)
|
||||
dragWait--;
|
||||
if (dragWait <= 0)
|
||||
{
|
||||
dragWait = 30;
|
||||
|
||||
float speed = velocity.Length();
|
||||
|
||||
dragVec = (velocity / speed) * Math.Min(speed * speed * dragCoefficient * deltaTime, 1.0f);
|
||||
}
|
||||
|
||||
velocity -= dragVec;
|
||||
}
|
||||
|
||||
private void OnWallCollisionInside(Hull prevHull, Vector2 collisionNormal)
|
||||
{
|
||||
Rectangle prevHullRect = prevHull.WorldRect;
|
||||
|
||||
Vector2 subVel = prevHull?.Submarine != null ? ConvertUnits.ToDisplayUnits(prevHull.Submarine.Velocity) : Vector2.Zero;
|
||||
velocity -= subVel;
|
||||
|
||||
if (Math.Abs(collisionNormal.X) > Math.Abs(collisionNormal.Y))
|
||||
{
|
||||
if (collisionNormal.X > 0.0f)
|
||||
{
|
||||
position.X = Math.Max(position.X, prevHullRect.X + prefab.CollisionRadius * size.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
position.X = Math.Min(position.X, prevHullRect.Right - prefab.CollisionRadius * size.X);
|
||||
}
|
||||
velocity.X = Math.Sign(collisionNormal.X) * Math.Abs(velocity.X) * prefab.Restitution;
|
||||
velocity.Y *= (1.0f - prefab.Friction);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (collisionNormal.Y > 0.0f)
|
||||
{
|
||||
position.Y = Math.Max(position.Y, prevHullRect.Y - prevHullRect.Height + prefab.CollisionRadius * size.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
position.Y = Math.Min(position.Y, prevHullRect.Y - prefab.CollisionRadius * size.Y);
|
||||
|
||||
}
|
||||
velocity.X *= (1.0f - prefab.Friction);
|
||||
velocity.Y = Math.Sign(collisionNormal.Y) * Math.Abs(velocity.Y) * prefab.Restitution;
|
||||
}
|
||||
|
||||
velocity += subVel;
|
||||
}
|
||||
|
||||
|
||||
private void OnWallCollisionOutside(Hull collisionHull)
|
||||
{
|
||||
Rectangle hullRect = collisionHull.WorldRect;
|
||||
|
||||
Vector2 center = new Vector2(hullRect.X + hullRect.Width /2, hullRect.Y - hullRect.Height / 2);
|
||||
|
||||
if (position.Y < center.Y)
|
||||
{
|
||||
position.Y = hullRect.Y - hullRect.Height - prefab.CollisionRadius;
|
||||
velocity.X *= (1.0f - prefab.Friction);
|
||||
velocity.Y = -velocity.Y * prefab.Restitution;
|
||||
}
|
||||
else if (position.Y > center.Y)
|
||||
{
|
||||
position.Y = hullRect.Y + prefab.CollisionRadius;
|
||||
velocity.X *= (1.0f - prefab.Friction);
|
||||
velocity.Y = -velocity.Y * prefab.Restitution;
|
||||
}
|
||||
|
||||
if (position.X < center.X)
|
||||
{
|
||||
position.X = hullRect.X - prefab.CollisionRadius;
|
||||
velocity.X = -velocity.X * prefab.Restitution;
|
||||
velocity.Y *= (1.0f - prefab.Friction);
|
||||
}
|
||||
else if (position.X > center.X)
|
||||
{
|
||||
position.X = hullRect.X + hullRect.Width + prefab.CollisionRadius;
|
||||
velocity.X = -velocity.X * prefab.Restitution;
|
||||
velocity.Y *= (1.0f - prefab.Friction);
|
||||
}
|
||||
|
||||
velocity *= prefab.Restitution;
|
||||
}
|
||||
|
||||
public void UpdateDrawPos()
|
||||
{
|
||||
drawPosition = Timing.Interpolate(prevPosition, position);
|
||||
drawRotation = Timing.Interpolate(prevRotation, rotation);
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
Vector2 drawSize = size;
|
||||
|
||||
if (prefab.GrowTime > 0.0f && totalLifeTime - lifeTime < prefab.GrowTime)
|
||||
{
|
||||
drawSize *= ((totalLifeTime - lifeTime) / prefab.GrowTime);
|
||||
}
|
||||
|
||||
if (prefab.Sprites[spriteIndex] is SpriteSheet)
|
||||
{
|
||||
((SpriteSheet)prefab.Sprites[spriteIndex]).Draw(
|
||||
spriteBatch, animFrame,
|
||||
new Vector2(drawPosition.X, -drawPosition.Y),
|
||||
color * (color.A / 255.0f),
|
||||
prefab.Sprites[spriteIndex].Origin, drawRotation,
|
||||
drawSize, SpriteEffects.None, prefab.Sprites[spriteIndex].Depth);
|
||||
}
|
||||
else
|
||||
{
|
||||
prefab.Sprites[spriteIndex].Draw(spriteBatch,
|
||||
new Vector2(drawPosition.X, -drawPosition.Y),
|
||||
color * (color.A / 255.0f),
|
||||
prefab.Sprites[spriteIndex].Origin, drawRotation,
|
||||
drawSize, SpriteEffects.None, prefab.Sprites[spriteIndex].Depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Particles
|
||||
{
|
||||
class ParticleEmitter
|
||||
{
|
||||
private float emitTimer;
|
||||
private float burstEmitTimer;
|
||||
|
||||
public readonly ParticleEmitterPrefab Prefab;
|
||||
|
||||
public ParticleEmitter(XElement element)
|
||||
{
|
||||
Prefab = new ParticleEmitterPrefab(element);
|
||||
}
|
||||
|
||||
public ParticleEmitter(ParticleEmitterPrefab prefab)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(prefab != null, "The prefab of a particle emitter cannot be null");
|
||||
Prefab = prefab;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
emitTimer += deltaTime * amountMultiplier;
|
||||
burstEmitTimer -= deltaTime;
|
||||
|
||||
if (Prefab.ParticlesPerSecond > 0)
|
||||
{
|
||||
float emitInterval = 1.0f / Prefab.ParticlesPerSecond;
|
||||
while (emitTimer > emitInterval)
|
||||
{
|
||||
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier);
|
||||
emitTimer -= emitInterval;
|
||||
}
|
||||
}
|
||||
|
||||
if (burstEmitTimer > 0.0f) { return; }
|
||||
|
||||
burstEmitTimer = Prefab.EmitInterval;
|
||||
for (int i = 0; i < Prefab.ParticleAmount * amountMultiplier; i++)
|
||||
{
|
||||
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier)
|
||||
{
|
||||
angle += Rand.Range(Prefab.AngleMin, Prefab.AngleMax);
|
||||
|
||||
Vector2 dir = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
|
||||
Vector2 velocity = dir * Rand.Range(Prefab.VelocityMin, Prefab.VelocityMax) * velocityMultiplier;
|
||||
position += dir * Rand.Range(Prefab.DistanceMin, Prefab.DistanceMax);
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle(Prefab.ParticlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop);
|
||||
|
||||
if (particle != null)
|
||||
{
|
||||
particle.Size *= Rand.Range(Prefab.ScaleMin, Prefab.ScaleMax) * sizeMultiplier;
|
||||
particle.HighQualityCollisionDetection = Prefab.HighQualityCollisionDetection;
|
||||
}
|
||||
}
|
||||
|
||||
public Rectangle CalculateParticleBounds(Vector2 startPosition)
|
||||
{
|
||||
Rectangle bounds = new Rectangle((int)startPosition.X, (int)startPosition.Y, (int)startPosition.X, (int)startPosition.Y);
|
||||
|
||||
for (float angle = Prefab.AngleMin; angle <= Prefab.AngleMax; angle += 0.1f)
|
||||
{
|
||||
Vector2 velocity = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) * Prefab.VelocityMax;
|
||||
Vector2 endPosition = Prefab.ParticlePrefab.CalculateEndPosition(startPosition, velocity);
|
||||
|
||||
bounds = new Rectangle(
|
||||
(int)Math.Min(bounds.X, endPosition.X - Prefab.DistanceMax),
|
||||
(int)Math.Min(bounds.Y, endPosition.Y - Prefab.DistanceMax),
|
||||
(int)Math.Max(bounds.X, endPosition.X + Prefab.DistanceMax),
|
||||
(int)Math.Max(bounds.Y, endPosition.Y + Prefab.DistanceMax));
|
||||
}
|
||||
|
||||
bounds = new Rectangle(bounds.X, bounds.Y, bounds.Width - bounds.X, bounds.Height - bounds.Y);
|
||||
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
class ParticleEmitterPrefab
|
||||
{
|
||||
public readonly string Name;
|
||||
|
||||
public readonly ParticlePrefab ParticlePrefab;
|
||||
|
||||
public readonly float AngleMin, AngleMax;
|
||||
|
||||
public readonly float DistanceMin, DistanceMax;
|
||||
|
||||
public readonly float VelocityMin, VelocityMax;
|
||||
|
||||
public readonly float ScaleMin, ScaleMax;
|
||||
|
||||
public readonly float EmitInterval;
|
||||
public readonly int ParticleAmount;
|
||||
|
||||
public readonly float ParticlesPerSecond;
|
||||
|
||||
public readonly bool HighQualityCollisionDetection;
|
||||
|
||||
public readonly bool CopyEntityAngle;
|
||||
|
||||
public readonly bool DrawOnTop;
|
||||
|
||||
public ParticleEmitterPrefab(XElement element)
|
||||
{
|
||||
Name = element.Name.ToString();
|
||||
|
||||
ParticlePrefab = GameMain.ParticleManager.FindPrefab(element.GetAttributeString("particle", ""));
|
||||
|
||||
if (element.Attribute("startrotation") == null)
|
||||
{
|
||||
AngleMin = element.GetAttributeFloat("anglemin", 0.0f);
|
||||
AngleMax = element.GetAttributeFloat("anglemax", 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
AngleMin = element.GetAttributeFloat("angle", 0.0f);
|
||||
AngleMax = AngleMin;
|
||||
}
|
||||
|
||||
AngleMin = MathHelper.ToRadians(MathHelper.Clamp(AngleMin, -360.0f, 360.0f));
|
||||
AngleMax = MathHelper.ToRadians(MathHelper.Clamp(AngleMax, -360.0f, 360.0f));
|
||||
|
||||
if (element.Attribute("scalemin") == null)
|
||||
{
|
||||
ScaleMin = 1.0f;
|
||||
ScaleMax = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
ScaleMin = element.GetAttributeFloat("scalemin", 1.0f);
|
||||
ScaleMax = Math.Max(ScaleMin, element.GetAttributeFloat("scalemax", 1.0f));
|
||||
}
|
||||
|
||||
if (element.Attribute("distance") == null)
|
||||
{
|
||||
DistanceMin = element.GetAttributeFloat("distancemin", 0.0f);
|
||||
DistanceMax = element.GetAttributeFloat("distancemax", 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
DistanceMin = DistanceMax = element.GetAttributeFloat("distance", 0.0f);
|
||||
}
|
||||
if (DistanceMax < DistanceMin)
|
||||
{
|
||||
var temp = DistanceMin;
|
||||
DistanceMin = DistanceMax;
|
||||
DistanceMax = temp;
|
||||
}
|
||||
|
||||
if (element.Attribute("velocity") == null)
|
||||
{
|
||||
VelocityMin = element.GetAttributeFloat("velocitymin", 0.0f);
|
||||
VelocityMax = element.GetAttributeFloat("velocitymax", 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
VelocityMin = VelocityMax = element.GetAttributeFloat("velocity", 0.0f);
|
||||
}
|
||||
if (VelocityMax < VelocityMin)
|
||||
{
|
||||
var temp = VelocityMin;
|
||||
VelocityMin = VelocityMax;
|
||||
VelocityMax = temp;
|
||||
}
|
||||
|
||||
EmitInterval = element.GetAttributeFloat("emitinterval", 0.0f);
|
||||
ParticlesPerSecond = element.GetAttributeInt("particlespersecond", 0);
|
||||
ParticleAmount = element.GetAttributeInt("particleamount", 0);
|
||||
HighQualityCollisionDetection = element.GetAttributeBool("highqualitycollisiondetection", false);
|
||||
CopyEntityAngle = element.GetAttributeBool("copyentityangle", false);
|
||||
DrawOnTop = element.GetAttributeBool("drawontop", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Particles
|
||||
{
|
||||
enum ParticleBlendState
|
||||
{
|
||||
AlphaBlend, Additive//, Distortion
|
||||
}
|
||||
|
||||
class ParticleManager
|
||||
{
|
||||
private const int MaxOutOfViewDist = 500;
|
||||
|
||||
private int particleCount;
|
||||
public int ParticleCount
|
||||
{
|
||||
get { return particleCount; }
|
||||
}
|
||||
|
||||
private int maxParticles;
|
||||
public int MaxParticles
|
||||
{
|
||||
get { return maxParticles; }
|
||||
set
|
||||
{
|
||||
if (maxParticles == value || value < 4) return;
|
||||
|
||||
Particle[] newParticles = new Particle[value];
|
||||
|
||||
for (int i = 0; i < Math.Min(maxParticles, value); i++)
|
||||
{
|
||||
newParticles[i] = particles[i];
|
||||
}
|
||||
|
||||
particleCount = Math.Min(particleCount, value);
|
||||
particles = newParticles;
|
||||
maxParticles = value;
|
||||
}
|
||||
}
|
||||
private Particle[] particles;
|
||||
|
||||
public readonly PrefabCollection<ParticlePrefab> Prefabs = new PrefabCollection<ParticlePrefab>();
|
||||
|
||||
private Camera cam;
|
||||
|
||||
public Camera Camera
|
||||
{
|
||||
get { return cam; }
|
||||
set { cam = value; }
|
||||
}
|
||||
|
||||
public ParticleManager(Camera cam)
|
||||
{
|
||||
this.cam = cam;
|
||||
|
||||
MaxParticles = GameMain.Config.ParticleLimit;
|
||||
}
|
||||
|
||||
public void LoadPrefabs()
|
||||
{
|
||||
foreach (ContentFile configFile in GameMain.Instance.GetFilesOfType(ContentType.Particles))
|
||||
{
|
||||
LoadPrefabsFromFile(configFile);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadPrefabsFromFile(ContentFile configFile)
|
||||
{
|
||||
var particleElements = new Dictionary<string, XElement>();
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
|
||||
if (doc == null) { return; }
|
||||
|
||||
bool allowOverriding = false;
|
||||
var mainElement = doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
allowOverriding = true;
|
||||
}
|
||||
|
||||
foreach (XElement sourceElement in mainElement.Elements())
|
||||
{
|
||||
var element = sourceElement.IsOverride() ? sourceElement.FirstElement() : sourceElement;
|
||||
string name = element.Name.ToString().ToLowerInvariant();
|
||||
if (Prefabs.ContainsKey(name) || particleElements.ContainsKey(name))
|
||||
{
|
||||
if (allowOverriding || sourceElement.IsOverride())
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the existing particle prefab '{name}' using the file '{configFile.Path}'", Color.Yellow);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{configFile.Path}': Duplicate particle prefab '{name}' found in '{configFile.Path}'! Each particle prefab must have a unique name. " +
|
||||
"Use <override></override> tags to override prefabs.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
particleElements.Add(name, element);
|
||||
}
|
||||
|
||||
foreach (var kvp in particleElements)
|
||||
{
|
||||
Prefabs.Add(new ParticlePrefab(kvp.Value, configFile), allowOverriding);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemovePrefabsByFile(string configFile)
|
||||
{
|
||||
Prefabs.RemoveByFile(configFile);
|
||||
}
|
||||
|
||||
public Particle CreateParticle(string prefabName, Vector2 position, float angle, float speed, Hull hullGuess = null)
|
||||
{
|
||||
return CreateParticle(prefabName, position, new Vector2((float)Math.Cos(angle), (float)-Math.Sin(angle)) * speed, angle, hullGuess);
|
||||
}
|
||||
|
||||
public Particle CreateParticle(string prefabName, Vector2 position, Vector2 velocity, float rotation=0.0f, Hull hullGuess = null)
|
||||
{
|
||||
ParticlePrefab prefab = FindPrefab(prefabName);
|
||||
|
||||
if (prefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Particle prefab \"" + prefabName+"\" not found!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return CreateParticle(prefab, position, velocity, rotation, hullGuess);
|
||||
}
|
||||
|
||||
public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, bool drawOnTop = false)
|
||||
{
|
||||
if (particleCount >= MaxParticles || prefab == null) return null;
|
||||
|
||||
Vector2 particleEndPos = prefab.CalculateEndPosition(position, velocity);
|
||||
|
||||
Vector2 minPos = new Vector2(Math.Min(position.X, particleEndPos.X), Math.Min(position.Y, particleEndPos.Y));
|
||||
Vector2 maxPos = new Vector2(Math.Max(position.X, particleEndPos.X), Math.Max(position.Y, particleEndPos.Y));
|
||||
|
||||
Rectangle expandedViewRect = MathUtils.ExpandRect(cam.WorldView, MaxOutOfViewDist);
|
||||
|
||||
if (minPos.X > expandedViewRect.Right || maxPos.X < expandedViewRect.X) return null;
|
||||
if (minPos.Y > expandedViewRect.Y || maxPos.Y < expandedViewRect.Y - expandedViewRect.Height) return null;
|
||||
|
||||
if (particles[particleCount] == null) particles[particleCount] = new Particle();
|
||||
|
||||
particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop);
|
||||
|
||||
particleCount++;
|
||||
|
||||
return particles[particleCount - 1];
|
||||
}
|
||||
|
||||
public List<ParticlePrefab> GetPrefabList()
|
||||
{
|
||||
return Prefabs.ToList();
|
||||
}
|
||||
|
||||
public ParticlePrefab FindPrefab(string prefabName)
|
||||
{
|
||||
return Prefabs.Find(p => p.Identifier == prefabName);
|
||||
}
|
||||
|
||||
private void RemoveParticle(int index)
|
||||
{
|
||||
particleCount--;
|
||||
|
||||
Particle swap = particles[index];
|
||||
particles[index] = particles[particleCount];
|
||||
particles[particleCount] = swap;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
MaxParticles = GameMain.Config.ParticleLimit;
|
||||
|
||||
for (int i = 0; i < particleCount; i++)
|
||||
{
|
||||
bool remove = false;
|
||||
try
|
||||
{
|
||||
remove = !particles[i].Update(deltaTime);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Particle update failed", e);
|
||||
remove = true;
|
||||
}
|
||||
|
||||
if (remove) RemoveParticle(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateTransforms()
|
||||
{
|
||||
for (int i = 0; i < particleCount; i++)
|
||||
{
|
||||
particles[i].UpdateDrawPos();
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<ParticlePrefab, int> CountActiveParticles()
|
||||
{
|
||||
Dictionary<ParticlePrefab, int> activeParticles = new Dictionary<ParticlePrefab, int>();
|
||||
for (int i = 0; i < particleCount; i++)
|
||||
{
|
||||
if (!activeParticles.ContainsKey(particles[i].Prefab)) activeParticles[particles[i].Prefab] = 0;
|
||||
activeParticles[particles[i].Prefab]++;
|
||||
}
|
||||
return activeParticles;
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool inWater, bool? inSub, ParticleBlendState blendState)
|
||||
{
|
||||
ParticlePrefab.DrawTargetType drawTarget = inWater ? ParticlePrefab.DrawTargetType.Water : ParticlePrefab.DrawTargetType.Air;
|
||||
|
||||
for (int i = 0; i < particleCount; i++)
|
||||
{
|
||||
var particle = particles[i];
|
||||
if (particle.BlendState != blendState) { continue; }
|
||||
//equivalent to !particles[i].DrawTarget.HasFlag(drawTarget) but garbage free and faster
|
||||
if ((particle.DrawTarget & drawTarget) == 0) { continue; }
|
||||
if (inSub.HasValue)
|
||||
{
|
||||
bool isOutside = particle.CurrentHull == null;
|
||||
if (particle.DrawOnTop)
|
||||
{
|
||||
if (isOutside != inSub.Value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (isOutside == inSub.Value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
particles[i].Draw(spriteBatch);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveByPrefab(ParticlePrefab prefab)
|
||||
{
|
||||
if (particles == null) { return; }
|
||||
for (int i = particles.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (particles[i]?.Prefab == prefab)
|
||||
{
|
||||
if (i < particleCount) { particleCount--; }
|
||||
|
||||
Particle swap = particles[particleCount];
|
||||
particles[particleCount] = null;
|
||||
particles[i] = swap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Particles
|
||||
{
|
||||
class ParticlePrefab : IPrefab, IDisposable, ISerializableEntity
|
||||
{
|
||||
public enum DrawTargetType { Air = 1, Water = 2, Both = 3 }
|
||||
|
||||
public readonly List<Sprite> Sprites;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GameMain.ParticleManager?.RemoveByPrefab(this);
|
||||
foreach (Sprite spr in Sprites)
|
||||
{
|
||||
spr.Remove();
|
||||
}
|
||||
Sprites.Clear();
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string FilePath
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string OriginalName { get { return Name; } }
|
||||
|
||||
public string Identifier { get { return Name; } }
|
||||
|
||||
public ContentPackage ContentPackage
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable(0.0f, float.MaxValue), Serialize(5.0f, false, description: "How many seconds the particle remains alive.")]
|
||||
public float LifeTime { get; private set; }
|
||||
|
||||
[Editable, Serialize(0.0f, false, description: "How long it takes for the particle to appear after spawning it.")]
|
||||
public float StartDelayMin { get; private set; }
|
||||
[Editable, Serialize(0.0f, false, description: "How long it takes for the particle to appear after spawning it.")]
|
||||
public float StartDelayMax { get; private set; }
|
||||
//movement -----------------------------------------
|
||||
|
||||
private float angularVelocityMin;
|
||||
public float AngularVelocityMinRad { get; private set; }
|
||||
|
||||
[Editable, Serialize(0.0f, false)]
|
||||
public float AngularVelocityMin
|
||||
{
|
||||
get { return angularVelocityMin; }
|
||||
private set
|
||||
{
|
||||
angularVelocityMin = value;
|
||||
AngularVelocityMinRad = MathHelper.ToRadians(value);
|
||||
}
|
||||
}
|
||||
|
||||
private float angularVelocityMax;
|
||||
public float AngularVelocityMaxRad { get; private set; }
|
||||
|
||||
[Editable, Serialize(0.0f, false)]
|
||||
public float AngularVelocityMax
|
||||
{
|
||||
get { return angularVelocityMax; }
|
||||
private set
|
||||
{
|
||||
angularVelocityMax = value;
|
||||
AngularVelocityMaxRad = MathHelper.ToRadians(value);
|
||||
}
|
||||
}
|
||||
|
||||
private float startRotationMin;
|
||||
public float StartRotationMinRad { get; private set; }
|
||||
|
||||
[Editable, Serialize(0.0f, false, description: "The minimum initial rotation of the particle (in degrees).")]
|
||||
public float StartRotationMin
|
||||
{
|
||||
get { return startRotationMin; }
|
||||
private set
|
||||
{
|
||||
startRotationMin = value;
|
||||
StartRotationMinRad = MathHelper.ToRadians(value);
|
||||
}
|
||||
}
|
||||
|
||||
private float startRotationMax;
|
||||
public float StartRotationMaxRad { get; private set; }
|
||||
|
||||
[Editable, Serialize(0.0f, false, description: "The maximum initial rotation of the particle (in degrees).")]
|
||||
public float StartRotationMax
|
||||
{
|
||||
get { return startRotationMax; }
|
||||
private set
|
||||
{
|
||||
startRotationMax = value;
|
||||
StartRotationMaxRad = MathHelper.ToRadians(value);
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, false, description: "Should the particle face the direction it's moving towards.")]
|
||||
public bool RotateToDirection { get; private set; }
|
||||
|
||||
[Editable, Serialize(0.0f, false, description: "Drag applied to the particle when it's moving through air.")]
|
||||
public float Drag { get; private set; }
|
||||
|
||||
[Editable, Serialize(0.0f, false, description: "Drag applied to the particle when it's moving through water.")]
|
||||
public float WaterDrag { get; private set; }
|
||||
|
||||
private Vector2 velocityChange;
|
||||
public Vector2 VelocityChangeDisplay { get; private set; }
|
||||
|
||||
[Editable, Serialize("0.0,0.0", false, description: "How much the velocity of the particle changes per second.")]
|
||||
public Vector2 VelocityChange
|
||||
{
|
||||
get { return velocityChange; }
|
||||
private set
|
||||
{
|
||||
velocityChange = value;
|
||||
VelocityChangeDisplay = ConvertUnits.ToDisplayUnits(value);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 velocityChangeWater;
|
||||
public Vector2 VelocityChangeWaterDisplay { get; private set; }
|
||||
|
||||
[Editable, Serialize("0.0,0.0", false, description: "How much the velocity of the particle changes per second when in water.")]
|
||||
public Vector2 VelocityChangeWater
|
||||
{
|
||||
get { return velocityChangeWater; }
|
||||
private set
|
||||
{
|
||||
velocityChangeWater = value;
|
||||
VelocityChangeWaterDisplay = ConvertUnits.ToDisplayUnits(value);
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(0.0f, 10000.0f), Serialize(0.0f, false, description: "Radius of the particle's collider. Only has an effect if UseCollision is set to true.")]
|
||||
public float CollisionRadius { get; private set; }
|
||||
|
||||
[Editable, Serialize(false, false, description: "Does the particle collide with the walls of the submarine and the level.")]
|
||||
public bool UseCollision { get; private set; }
|
||||
|
||||
[Editable, Serialize(false, false, description: "Does the particle disappear when it collides with something.")]
|
||||
public bool DeleteOnCollision { get; private set; }
|
||||
|
||||
[Editable(0.0f, 1.0f), Serialize(0.5f, false, description: "The friction coefficient of the particle, i.e. how much it slows down when it's sliding against a surface.")]
|
||||
public float Friction { get; private set; }
|
||||
|
||||
[Editable(0.0f, 1.0f)]
|
||||
[Serialize(0.5f, false, description: "How much of the particle's velocity is conserved when it collides with something, i.e. the \"bounciness\" of the particle. (1.0 = the particle stops completely).")]
|
||||
public float Restitution { get; private set; }
|
||||
|
||||
//size -----------------------------------------
|
||||
|
||||
[Editable, Serialize("1.0,1.0", false, description: "The minimum initial size of the particle.")]
|
||||
public Vector2 StartSizeMin { get; private set; }
|
||||
|
||||
[Editable, Serialize("1.0,1.0", false, description: "The maximum initial size of the particle.")]
|
||||
public Vector2 StartSizeMax { get; private set; }
|
||||
|
||||
[Editable]
|
||||
[Serialize("0.0,0.0", false, description: "How much the size of the particle changes per second. The rate of growth for each particle is randomize between SizeChangeMin and SizeChangeMax.")]
|
||||
public Vector2 SizeChangeMin { get; private set; }
|
||||
|
||||
[Editable]
|
||||
[Serialize("0.0,0.0", false, description: "How much the size of the particle changes per second. The rate of growth for each particle is randomize between SizeChangeMin and SizeChangeMax.")]
|
||||
public Vector2 SizeChangeMax { get; private set; }
|
||||
|
||||
[Editable]
|
||||
[Serialize(0.0f, false, description: "How many seconds it takes for the particle to grow to it's initial size.")]
|
||||
public float GrowTime { get; private set; }
|
||||
|
||||
//rendering -----------------------------------------
|
||||
|
||||
[Editable, Serialize("1.0,1.0,1.0,1.0", false, description: "The initial color of the particle.")]
|
||||
public Color StartColor { get; private set; }
|
||||
|
||||
[Editable, Serialize("1.0,1.0,1.0,1.0", false, description: "The color of the particle at the end of its lifetime.")]
|
||||
public Color EndColor { get; private set; }
|
||||
|
||||
[Editable, Serialize(DrawTargetType.Air, false, description: "Should the particle be rendered in air, water or both.")]
|
||||
public DrawTargetType DrawTarget { get; private set; }
|
||||
|
||||
[Editable, Serialize(ParticleBlendState.AlphaBlend, false, description: "The type of blending to use when rendering the particle.")]
|
||||
public ParticleBlendState BlendState { get; private set; }
|
||||
|
||||
//animation -----------------------------------------
|
||||
|
||||
[Editable(0.0f, float.MaxValue), Serialize(1.0f, false, description: "The duration of the particle's animation cycle (if it's animated).")]
|
||||
public float AnimDuration { get; private set; }
|
||||
|
||||
[Editable, Serialize(true, false, description: "Should the sprite animation be looped, or stay at the last frame when the animation finishes.")]
|
||||
public bool LoopAnim { get; private set; }
|
||||
|
||||
//----------------------------------------------------
|
||||
|
||||
public readonly List<ParticleEmitterPrefab> SubEmitters = new List<ParticleEmitterPrefab>();
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//----------------------------------------------------
|
||||
|
||||
public ParticlePrefab(XElement element, ContentFile file)
|
||||
{
|
||||
Name = element.Name.ToString();
|
||||
FilePath = file.Path;
|
||||
ContentPackage = file.ContentPackage;
|
||||
DisplayName = TextManager.Get("particle." + Name, true) ?? Name;
|
||||
|
||||
Sprites = new List<Sprite>();
|
||||
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
Sprites.Add(new Sprite(subElement));
|
||||
break;
|
||||
case "spritesheet":
|
||||
case "animatedsprite":
|
||||
Sprites.Add(new SpriteSheet(subElement));
|
||||
break;
|
||||
case "particleemitter":
|
||||
case "emitter":
|
||||
case "subemitter":
|
||||
SubEmitters.Add(new ParticleEmitterPrefab(subElement));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//if velocity change in water is not given, it defaults to the normal velocity change
|
||||
if (element.Attribute("velocitychangewater") == null)
|
||||
{
|
||||
VelocityChangeWater = VelocityChange;
|
||||
}
|
||||
|
||||
if (element.Attribute("angularvelocity") != null)
|
||||
{
|
||||
AngularVelocityMin = element.GetAttributeFloat("angularvelocity", 0.0f);
|
||||
AngularVelocityMax = AngularVelocityMin;
|
||||
}
|
||||
|
||||
if (element.Attribute("startsize") != null)
|
||||
{
|
||||
StartSizeMin = element.GetAttributeVector2("startsize", Vector2.One);
|
||||
StartSizeMax = StartSizeMin;
|
||||
}
|
||||
|
||||
if (element.Attribute("sizechange") != null)
|
||||
{
|
||||
SizeChangeMin = element.GetAttributeVector2("sizechange", Vector2.Zero);
|
||||
SizeChangeMax = SizeChangeMin;
|
||||
}
|
||||
|
||||
if (element.Attribute("startrotation") != null)
|
||||
{
|
||||
StartRotationMin = element.GetAttributeFloat("startrotation", 0.0f);
|
||||
StartRotationMax = StartRotationMin;
|
||||
}
|
||||
|
||||
if (CollisionRadius <= 0.0f) CollisionRadius = Sprites.Count > 0 ? 1 : Sprites[0].SourceRect.Width / 2.0f;
|
||||
}
|
||||
|
||||
public Vector2 CalculateEndPosition(Vector2 startPosition, Vector2 velocity)
|
||||
{
|
||||
//endPos = x + vt + 1/2 * at^2
|
||||
return startPosition + velocity * LifeTime + 0.5f * VelocityChangeDisplay * LifeTime * LifeTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user