mirror of
https://github.com/ppy/osu.git
synced 2024-12-27 13:53:10 +08:00
149 lines
5.0 KiB
C#
149 lines
5.0 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
#nullable disable
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Graphics.Primitives;
|
|
using osu.Framework.Graphics.Rendering;
|
|
using osu.Framework.Graphics.Sprites;
|
|
using osu.Framework.Graphics.Textures;
|
|
using osu.Framework.Utils;
|
|
using osuTK;
|
|
|
|
namespace osu.Game.Graphics
|
|
{
|
|
/// <summary>
|
|
/// An explosion of textured particles based on how osu-stable randomises the explosion pattern.
|
|
/// </summary>
|
|
public class ParticleExplosion : Sprite
|
|
{
|
|
private readonly int particleCount;
|
|
private readonly double duration;
|
|
private double startTime;
|
|
|
|
private readonly List<ParticlePart> parts = new List<ParticlePart>();
|
|
|
|
public ParticleExplosion(Texture texture, int particleCount, double duration)
|
|
{
|
|
Texture = texture;
|
|
this.particleCount = particleCount;
|
|
this.duration = duration;
|
|
Blending = BlendingParameters.Additive;
|
|
}
|
|
|
|
protected override void LoadComplete()
|
|
{
|
|
base.LoadComplete();
|
|
Restart();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restart the animation from the current point in time.
|
|
/// Supports transform time offset chaining.
|
|
/// </summary>
|
|
public void Restart()
|
|
{
|
|
startTime = TransformStartTime;
|
|
this.FadeOutFromOne(duration);
|
|
|
|
parts.Clear();
|
|
for (int i = 0; i < particleCount; i++)
|
|
parts.Add(new ParticlePart(duration));
|
|
}
|
|
|
|
protected override void Update()
|
|
{
|
|
base.Update();
|
|
Invalidate(Invalidation.DrawNode);
|
|
}
|
|
|
|
protected override DrawNode CreateDrawNode() => new ParticleExplosionDrawNode(this);
|
|
|
|
private class ParticleExplosionDrawNode : SpriteDrawNode
|
|
{
|
|
private readonly List<ParticlePart> parts = new List<ParticlePart>();
|
|
|
|
private ParticleExplosion source => (ParticleExplosion)Source;
|
|
|
|
private double startTime;
|
|
private double currentTime;
|
|
private Vector2 sourceSize;
|
|
|
|
public ParticleExplosionDrawNode(Sprite source)
|
|
: base(source)
|
|
{
|
|
}
|
|
|
|
public override void ApplyState()
|
|
{
|
|
base.ApplyState();
|
|
|
|
parts.Clear();
|
|
parts.AddRange(source.parts);
|
|
|
|
sourceSize = source.Size;
|
|
startTime = source.startTime;
|
|
currentTime = source.Time.Current;
|
|
}
|
|
|
|
protected override void Blit(IRenderer renderer)
|
|
{
|
|
double time = currentTime - startTime;
|
|
|
|
foreach (var p in parts)
|
|
{
|
|
Vector2 pos = p.PositionAtTime(time);
|
|
float alpha = p.AlphaAtTime(time);
|
|
|
|
var rect = new RectangleF(
|
|
pos.X * sourceSize.X - Texture.DisplayWidth / 2,
|
|
pos.Y * sourceSize.Y - Texture.DisplayHeight / 2,
|
|
Texture.DisplayWidth,
|
|
Texture.DisplayHeight);
|
|
|
|
// convert to screen space.
|
|
var quad = new Quad(
|
|
Vector2Extensions.Transform(rect.TopLeft, DrawInfo.Matrix),
|
|
Vector2Extensions.Transform(rect.TopRight, DrawInfo.Matrix),
|
|
Vector2Extensions.Transform(rect.BottomLeft, DrawInfo.Matrix),
|
|
Vector2Extensions.Transform(rect.BottomRight, DrawInfo.Matrix)
|
|
);
|
|
|
|
renderer.DrawQuad(Texture, quad, DrawColourInfo.Colour.MultiplyAlpha(alpha),
|
|
inflationPercentage: new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height),
|
|
textureCoords: TextureCoords);
|
|
}
|
|
}
|
|
|
|
protected override bool CanDrawOpaqueInterior => false;
|
|
}
|
|
|
|
private readonly struct ParticlePart
|
|
{
|
|
private readonly double duration;
|
|
private readonly float direction;
|
|
private readonly float distance;
|
|
|
|
public ParticlePart(double availableDuration)
|
|
{
|
|
distance = RNG.NextSingle(0.5f);
|
|
duration = RNG.NextDouble(availableDuration / 3, availableDuration);
|
|
direction = RNG.NextSingle(0, MathF.PI * 2);
|
|
}
|
|
|
|
public float AlphaAtTime(double time) => 1 - progressAtTime(time);
|
|
|
|
public Vector2 PositionAtTime(double time)
|
|
{
|
|
float travelledDistance = distance * progressAtTime(time);
|
|
return new Vector2(0.5f) + travelledDistance * new Vector2(MathF.Sin(direction), MathF.Cos(direction));
|
|
}
|
|
|
|
private float progressAtTime(double time) => (float)Math.Clamp(time / duration, 0, 1);
|
|
}
|
|
}
|
|
}
|