diff --git a/CityGame/Classes/Entities/Entity.cs b/CityGame/Classes/Entities/Entity.cs index d16304b..a9154e4 100644 --- a/CityGame/Classes/Entities/Entity.cs +++ b/CityGame/Classes/Entities/Entity.cs @@ -1,11 +1,14 @@ using CityGame.Classes.Rendering; +using CityGame.Classes.Rendering.Particles; using CityGame.Classes.World; using OrpticonGameHelper.Classes.Effects; +using OrpticonGameHelper.Classes.Elements; namespace CityGame.Classes.Entities { public abstract class Entity : ISelectable { + public bool IsSingleSelect() => SingleSelect; public float X { get; set; } public float Y { get; set; } protected float visualX; @@ -38,6 +41,21 @@ namespace CityGame.Classes.Entities { car.Path = null; car.Target = target; + return true; + } + if(this is GasPipe pipe) + { + if (pipe.Exploded > 0) return false; + Explosion x = new Explosion(); + pipe.canvas.Children.Add(x); + Canvas.SetLeft(x, pipe.GetParticleOrigin().X); + Canvas.SetTop(x, pipe.GetParticleOrigin().Y); + x.RotationOrigin = new Microsoft.Xna.Framework.Point(MainWindow.TileSize / 2); + x.Size = 16; + x.MaxParticleDistance = 32; + x.Emit(); + pipe.Exploded = 1; + return true; } return false; } diff --git a/CityGame/Classes/Entities/GasPipe.cs b/CityGame/Classes/Entities/GasPipe.cs index 2e20886..4b97e8f 100644 --- a/CityGame/Classes/Entities/GasPipe.cs +++ b/CityGame/Classes/Entities/GasPipe.cs @@ -1,25 +1,62 @@ using CityGame.Classes.Rendering; +using CityGame.Classes.Rendering.Particles; +using Microsoft.Xna.Framework; using OrpticonGameHelper.Classes.Elements; namespace CityGame.Classes.Entities { public class GasPipe : Entity { - public int Exploded { get; set; } + public long Exploded { get; set; } + public Image image { get; set; } + public OCanvas canvas { get; set; } + public Explosion Smoke { get; set; } public GasPipe() { if (MainWindow.random.Next(0, 2) == 0) Rotation += 180; SingleSelect = true; } - + public Vector2 GetParticleOrigin() + { + return new Vector2(MainWindow.TileSize / 2); + } public override OCanvas Render() { - return new SourcedImage("ManholeCover.png") { ZIndex = 98, Effects = { selectedEffect } }; + image = new SourcedImage("ManholeCover.png") { ZIndex = 98, Effects = { selectedEffect } }; + canvas = image; + return canvas; } public override void Tick(long deltaTime) { - + if (Exploded > 0) + { + Exploded += deltaTime; + if(Smoke is null) + { + CreateSmoke(); + } + } + } + void CreateSmoke() + { + Explosion smoke = new Explosion(); + smoke.DirectionTendency = MainWindow.Wind; + smoke.DirectionVariance = 22.5f; + smoke.MinColor = Color.LightGray; + smoke.MaxColor = Color.DarkGray; + smoke.MaxParticleDistance = MainWindow.TileSize * 3; + smoke.EmissionTime = 3; + smoke.ParticleCountMin = 1; + smoke.ParticleCountMax = 2; + smoke.Size = 16; + smoke.RotationOrigin = new Microsoft.Xna.Framework.Point(MainWindow.TileSize / 2); + smoke.EaseMovement = false; + Smoke = smoke; + Object.Children.Add(Smoke); + Canvas.SetLeft(Smoke, GetParticleOrigin().X); + Canvas.SetTop(Smoke, GetParticleOrigin().Y); + smoke.Emit().ContinueWith(t => CreateSmoke()); } } } diff --git a/CityGame/Classes/Misc/Extensions.cs b/CityGame/Classes/Misc/Extensions.cs index f3172e6..6fc534c 100644 --- a/CityGame/Classes/Misc/Extensions.cs +++ b/CityGame/Classes/Misc/Extensions.cs @@ -1,5 +1,7 @@ using Microsoft.Xna.Framework; using System; +using System.Numerics; +using System.Reflection.Metadata; namespace CityGame { @@ -25,5 +27,23 @@ namespace CityGame { return A == B; } + public static Microsoft.Xna.Framework.Vector2 RotateBy(this Microsoft.Xna.Framework.Vector2 v, float angleInDegrees) + { + float radians = (float)(angleInDegrees * Math.PI / 180f); + float sin = (float)Math.Sin(radians); + float cos = (float)Math.Cos(radians); + float rotatedX = v.X * cos - v.Y * sin; + float rotatedY = v.X * sin + v.Y * cos; + return new Microsoft.Xna.Framework.Vector2(rotatedX, rotatedY); + } + public static System.Numerics.Vector2 RotateBy(this System.Numerics.Vector2 v, float angleInDegrees) + { + float radians = (float)(angleInDegrees * Math.PI / 180f); + float sin = (float)Math.Sin(radians); + float cos = (float)Math.Cos(radians); + float rotatedX = v.X * cos - v.Y * sin; + float rotatedY = v.X * sin + v.Y * cos; + return new System.Numerics.Vector2(rotatedX, rotatedY); + } } } \ No newline at end of file diff --git a/CityGame/Classes/Rendering/Particles/Explosion.cs b/CityGame/Classes/Rendering/Particles/Explosion.cs index 55b9656..9df06da 100644 --- a/CityGame/Classes/Rendering/Particles/Explosion.cs +++ b/CityGame/Classes/Rendering/Particles/Explosion.cs @@ -20,7 +20,12 @@ public sealed class Explosion : ParticleEmitter, IParticleHandler private static Effect? _explosionShader = null; - public Color Color = Color.Yellow; + public Color MinColor = Color.Yellow; + public Color MaxColor = Color.OrangeRed; + public Vector2 DirectionTendency = Vector2.UnitX; + public float DirectionVariance = 180; + public int ParticleCountMin = 8; + public int ParticleCountMax = 8; public Explosion() : base(Explosion.GetShader()) @@ -41,13 +46,16 @@ public sealed class Explosion : ParticleEmitter, IParticleHandler public ICollection Generate() { List particles = new List(); - for (int i = 0; i < 8; i++) + for (int i = 0; i < Random.Shared.Next(ParticleCountMin, ParticleCountMax + 1); i++) { ExplosionParticle particle = new ExplosionParticle(); particle.OriginalPosition = particle.Position = Vector2.Zero; - Vector2 direction = new Vector2(Random.Shared.NextSingle(-1, 1), Random.Shared.NextSingle(-1,1)); - particle.Direction = direction; + float variance = ((float)Random.Shared.NextSingle() * 2 - 1) * DirectionVariance; + Vector2 direction = DirectionTendency.RotateBy(variance); + particle.Direction = direction * Random.Shared.NextSingle(); + + particle.color = Color.Lerp(MinColor, MaxColor, Random.Shared.NextSingle()); particle.LifeTime = EmissionTime; particle.LifeTimeScale = Random.Shared.NextSingle(1, 1.5f); @@ -59,7 +67,7 @@ public sealed class Explosion : ParticleEmitter, IParticleHandler return particles; } - + public bool EaseMovement { get; set; } = true; public void Move(ICollection particles, float deltatime, ParticleEmitter emitter) { @@ -72,7 +80,7 @@ public sealed class Explosion : ParticleEmitter, IParticleHandler time = ParticleEmitter.CalculateNormalizedReversedTime(explParticle.LifeTime, this.EmissionTime); } - float distanceCalc = Easing.Easing.OutExpo(time); + float distanceCalc = EaseMovement ? Easing.Easing.OutExpo(time) : time; float distance = distanceCalc * emitter.MaxParticleDistance; particle.Position = particle.OriginalPosition + particle.Direction * distance; @@ -82,10 +90,9 @@ public sealed class Explosion : ParticleEmitter, IParticleHandler public void Draw(ref ParticleDrawContext context) { context.Effect.Parameters["view_projection"].SetValue(context.View * context.Projection); - context.Effect.Parameters["color"].SetValue(this.Color.ToVector4()); + context.Effect.Parameters["color"].SetValue(this.MinColor.ToVector4()); + - - context.SpriteBatch.Begin( effect: Effect, blendState: BlendState.NonPremultiplied, @@ -110,6 +117,7 @@ public sealed class Explosion : ParticleEmitter, IParticleHandler if (particle is ExplosionParticle explParticle) { normTime = ParticleEmitter.CalculateNormalizedReversedTime(explParticle.LifeTime, this.EmissionTime); + context.Effect.Parameters["color"].SetValue(explParticle.color.ToVector4()); context.Effect.Parameters["noiseOffset"].SetValue(explParticle.UVOffset); } float time = Easing.Easing.OutExpo(normTime); diff --git a/CityGame/Classes/Rendering/Particles/ExplosionParticle.cs b/CityGame/Classes/Rendering/Particles/ExplosionParticle.cs index 391cb2f..b4a362e 100644 --- a/CityGame/Classes/Rendering/Particles/ExplosionParticle.cs +++ b/CityGame/Classes/Rendering/Particles/ExplosionParticle.cs @@ -8,6 +8,7 @@ public sealed class ExplosionParticle: Particle public float LifeTimeScale = 1; public float LifeTime = 1; public Vector2 UVOffset = Vector2.Zero; + public Color color; public void ReduceLifeTime(float deltatime) { diff --git a/CityGame/Classes/World/ISelectable.cs b/CityGame/Classes/World/ISelectable.cs index b51872d..e6e3ab0 100644 --- a/CityGame/Classes/World/ISelectable.cs +++ b/CityGame/Classes/World/ISelectable.cs @@ -8,5 +8,6 @@ namespace CityGame.Classes.World public bool RunAction(ISelectable target); public int X(); public int Y(); + public bool IsSingleSelect(); } } \ No newline at end of file diff --git a/CityGame/Classes/World/Tile.cs b/CityGame/Classes/World/Tile.cs index 22fe192..e575834 100644 --- a/CityGame/Classes/World/Tile.cs +++ b/CityGame/Classes/World/Tile.cs @@ -5,6 +5,7 @@ namespace CityGame.Classes.World { public struct Tile : ISelectable { + public bool IsSingleSelect() => false; public int BlockID; public TileType Type; public Pattern Pattern; diff --git a/CityGame/MainWindow.cs b/CityGame/MainWindow.cs index 536375d..4d71a1d 100644 --- a/CityGame/MainWindow.cs +++ b/CityGame/MainWindow.cs @@ -71,6 +71,7 @@ namespace CityGame public static Tile[,] Grid; public static ISelectable Selected; public static OCanvas[,] ImageGrid; + public static Vector2 Wind = new Vector2(0.5f, -0.5f); Canvas MainCanvas = new OCanvas(); Canvas BGCanvas = new OCanvas(); @@ -509,7 +510,8 @@ namespace CityGame ISelectable select = GetSelectableFromClick(state); if (select is not null) { - Selected = select; + if (select.IsSingleSelect()) select.RunAction(select); + else Selected = select; } } else if (state.RightButton == ButtonState.Pressed)