Added explosion

This commit is contained in:
Michel Fedde 2023-05-14 19:54:50 +02:00
parent 59ae0bc41a
commit 7f7a21c110
8 changed files with 345 additions and 0 deletions

View file

@ -11,12 +11,18 @@
<Compile Remove="Assemblies\**" />
<EmbeddedResource Remove="Assemblies\**" />
<None Remove="Assemblies\**" />
<None Remove="Classes\Rendering\Particles\explosion.cfx" />
<EmbeddedResource Include="Classes\Rendering\Particles\explosion.cfx" />
<None Update="Resources\NoiseTexture.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="AStarLite" Version="1.1.0" />
<PackageReference Include="NAudio" Version="2.1.0" />
<PackageReference Include="NAudio.Lame" Version="2.0.1" />
<PackageReference Include="NETEasing" Version="1.0.1" />
<PackageReference Include="SimplexNoise" Version="2.0.0" />
</ItemGroup>
@ -162,4 +168,8 @@
</Reference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Classes\Rendering\Particles\explosion.fx" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using CityGame.Classes.Rendering.Shaders;
using CityGame.Classes.World;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using OrpticonGameHelper;
using OrpticonGameHelper.Classes.Particles;
using OrpticonGameHelper.Classes.Utils;
using static CityGame.MainWindow;
namespace CityGame.Classes.Rendering.Particles;
public sealed class Explosion : ParticleEmitter, IParticleHandler
{
internal const string SHADER_PATH = "CityGame.Classes.Rendering.Particles.explosion.cfx";
private const float PIXEL_TO_TILE_RATIO = 1 / 2f;
private static Effect? _explosionShader = null;
public Color Color = Color.Yellow;
public Explosion()
: base(Explosion.GetShader())
{
this.ParticleHandler = this;
}
private static Effect GetShader()
{
if (Explosion._explosionShader == null)
{
Explosion._explosionShader = ShaderLoader.Get(Explosion.SHADER_PATH);
}
return Explosion._explosionShader;
}
public ICollection<IParticle> Generate()
{
List<IParticle> particles = new List<IParticle>();
for (int i = 0; i < 8; 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;
particle.LifeTime = EmissionTime;
particle.LifeTimeScale = Random.Shared.NextSingle(1, 1.5f);
particle.UVOffset = new Vector2(Random.Shared.NextSingle(1), Random.Shared.NextSingle(1));
particles.Add(particle);
}
return particles;
}
public void Move(ICollection<IParticle> particles, float deltatime, ParticleEmitter emitter)
{
foreach (IParticle particle in particles)
{
float time = emitter.NormalizedTime;
if (particle is ExplosionParticle explParticle)
{
explParticle.ReduceLifeTime(deltatime);
time = ParticleEmitter.CalculateNormalizedReversedTime(explParticle.LifeTime, this.EmissionTime);
}
float distanceCalc = Easing.Easing.OutExpo(time);
float distance = distanceCalc * emitter.MaxParticleDistance;
particle.Position = particle.OriginalPosition + particle.Direction * distance;
}
}
public void Draw(ref ParticleDrawContext context)
{
context.Effect.Parameters["view_projection"].SetValue(context.View * context.Projection);
context.Effect.Parameters["color"].SetValue(this.Color.ToVector3());
context.SpriteBatch.Begin(
effect: Effect,
blendState: BlendState.NonPremultiplied,
sortMode: SpriteSortMode.Immediate,
samplerState: SamplerState.LinearWrap
);
float pixelSize = MainWindow.TileSize * Explosion.PIXEL_TO_TILE_RATIO;
float sizeToTileRatio = (float)context.Size / MainWindow.TileSize;
int resultingPixelSize = (int)Math.Ceiling(pixelSize * sizeToTileRatio);
context.Effect.Parameters["size"].SetValue(context.Size / 2);
context.Effect.Parameters["pixelSize"].SetValue(resultingPixelSize / 2);
foreach (IParticle particle in context.Particles)
{
if (!particle.IsActive)
{
continue;
}
float normTime = NormalizedTime;
if (particle is ExplosionParticle explParticle)
{
normTime = ParticleEmitter.CalculateNormalizedReversedTime(explParticle.LifeTime, this.EmissionTime);
context.Effect.Parameters["noiseOffset"].SetValue(explParticle.UVOffset);
}
float time = Easing.Easing.OutExpo(normTime);
context.Effect.Parameters["timing"].SetValue(time);
context.Effect.Parameters["position"].SetValue(context.EmitterCenterPosition + particle.Position);
int x = (int)(context.EmitterRenderPosition.X + particle.Position.X);
int y = (int)(context.EmitterRenderPosition.Y + particle.Position.Y);
context.SpriteBatch.Draw(
Window._textures[Environment.CurrentDirectory + "\\Resources\\NoiseTexture.png"],
new Rectangle(x, y, context.Size, context.Size),
Color.White
);
}
context.SpriteBatch.End();
}
}

View file

@ -0,0 +1,21 @@
using Microsoft.Xna.Framework;
using OrpticonGameHelper.Classes.Particles;
namespace CityGame.Classes.Rendering.Particles;
public sealed class ExplosionParticle: Particle
{
public float LifeTimeScale = 1;
public float LifeTime = 1;
public Vector2 UVOffset = Vector2.Zero;
public void ReduceLifeTime(float deltatime)
{
this.LifeTime -= deltatime * this.LifeTimeScale;
if (this.LifeTime <= 0)
{
this.IsActive = false;
}
}
}

Binary file not shown.

View file

@ -0,0 +1,121 @@
#if OPENGL
#define VS_SHADERMODEL vs_3_0
#define PS_SHADERMODEL ps_3_0
#else
#define VS_SHADERMODEL vs_4_0_level_9_1
#define PS_SHADERMODEL ps_4_0_level_9_1
#endif
sampler TextureSampler : register(s0);
float4x4 view_projection;
float2 position;
int size;
int pixelSize;
float timing;
float2 noiseOffset;
float3 color;
#define BLUR_KERNEL_X_MIN -0.5
#define BLUR_KERNEL_X_MAX 0.5
#define BLUR_KERNEL_X_JUMP 0.5
#define BLUR_KERNEL_Y_MIN -0.5
#define BLUR_KERNEL_Y_MAX 0.5
#define BLUR_KERNEL_Y_JUMP 0.5
struct VertexInput {
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
};
struct PixelInput {
float4 Position : SV_Position0;
float2 RelativePos : TEXCOORD0;
float2 NoiseTextureCoord : TEXCOORD1;
};
float2 pixelizeCoord(float2 coord)
{
float2 uv = coord / size;
float2 scaledUV = uv * pixelSize;
return scaledUV;
}
float2 scalePixelCoordBack(float2 coord)
{
return round(coord) / pixelSize;
}
PixelInput SpriteVertexShader(VertexInput v) {
PixelInput output;
output.RelativePos = pixelizeCoord(v.Position.xy - position);
output.Position = mul(v.Position, view_projection);
output.NoiseTextureCoord = (v.TexCoord + noiseOffset) * (pixelSize * 2);
return output;
}
float2 linear_mix(float t, float2 val1, float2 val2)
{
float2 outval = val1;
if (val2[0] > 0.5)
outval[0] = val1[0] + t * (2.0 * (val2[0] - 0.5));
else
outval[0] = val1[0] + t * (2.0 * (val2[0]) - 1.0);
if (val2[1] > 0.5)
outval[1] = val1[1] + t * (2.0 * (val2[1] - 0.5));
else
outval[1] = val1[1] + t * (2.0 * (val2[1]) - 1.0);
return outval;
}
float2 getNoise(float2 uv)
{
return tex2D(TextureSampler, uv).rg;
}
float calculateAlpha(float2 uv, float2 noiseUV)
{
float2 actualUV = scalePixelCoordBack(uv);
float2 lengthUV = linear_mix(0.5, actualUV, getNoise(actualUV + noiseOffset));
return step(length(lengthUV), timing);
}
float4 SpritePixelShader(PixelInput p) : SV_TARGET {
float2 noiseCoord = round(p.NoiseTextureCoord) / (pixelSize * 2);
float2 relPos = abs(p.RelativePos);
int sampleAmount = 0;
float val = 0;
for(float x = BLUR_KERNEL_X_MIN; x <= BLUR_KERNEL_X_MAX; x += BLUR_KERNEL_X_JUMP)
{
for(float y = BLUR_KERNEL_X_MIN; y <= BLUR_KERNEL_X_MAX; y += BLUR_KERNEL_Y_JUMP)
{
float2 alphaUV = relPos + float2(x,y);
val += calculateAlpha(alphaUV, noiseCoord);
sampleAmount++;
}
}
val = val / sampleAmount;
val = val + calculateAlpha(relPos, noiseCoord);
float noiseResult = getNoise(noiseCoord).r;
float3 resultColor = color * lerp(0.5, 1, noiseResult);
return float4(resultColor,val);
}
technique SpriteBatch {
pass {
VertexShader = compile VS_SHADERMODEL SpriteVertexShader();
PixelShader = compile PS_SHADERMODEL SpritePixelShader();
}
}

View file

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using CityGame.Classes.Rendering.Particles;
using Microsoft.Xna.Framework.Graphics;
using OrpticonGameHelper;
using OrpticonGameHelper.Classes;
using OrpticonGameHelper.Classes.Utils;
namespace CityGame.Classes.Rendering.Shaders;
public sealed class ShaderLoader : ILoadable
{
private static string[] _shaderPaths = new[]
{
Explosion.SHADER_PATH
};
private static ShaderLoader _instance;
private Dictionary<string, Effect> _effects = new Dictionary<string, Effect>();
private ShaderLoader() {}
public static ShaderLoader Create()
{
if (ShaderLoader._instance != null)
{
throw new Exception("There can only be one ShaderLoader.");
}
ShaderLoader._instance = new ShaderLoader();
return ShaderLoader._instance;
}
public void Load(Window window)
{
foreach (string shaderPath in ShaderLoader._shaderPaths)
{
byte[] shaderCode = AssemblyUtility.ReadAssemblyFileAsByte(shaderPath);
Effect shader = new Effect(window.GraphicsDevice, shaderCode);
this._effects[shaderPath] = shader;
}
}
public static Effect Get(string path)
{
if (!ShaderLoader._instance._effects.ContainsKey(path))
{
throw new Exception("No effect loaded with the path '" + path + "'.");
}
return ShaderLoader._instance._effects[path];
}
}

View file

@ -10,6 +10,8 @@ using SimplexNoise;
using System;
using System.Collections.Generic;
using System.Linq;
using CityGame.Classes.Rendering.Particles;
using CityGame.Classes.Rendering.Shaders;
using OrpticonGameHelper;
using OrpticonGameHelper.Classes.Elements;
using static CityGame.Classes.Entities.Car;
@ -161,6 +163,8 @@ namespace CityGame
ImageConverter.ChangeColor("Car", "PoliceCar", ColorConversionMaps.CarToPoliceCar);
#endregion
LoadableContent.Add(ShaderLoader.Create());
int seed = 8;
Noise.Seed = seed;

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB