1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 11:20:04 +08:00

Remove abstract from ParticleSpewer

This commit is contained in:
Opelkuh 2021-09-19 03:19:16 +02:00
parent ef530ed87c
commit 3f8454cb76
3 changed files with 144 additions and 184 deletions

View File

@ -1,11 +1,11 @@
// 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.
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
@ -20,12 +20,17 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
public class LegacyCursorParticles : CompositeDrawable, IKeyBindingHandler<OsuAction>
public class LegacyCursorParticles : CompositeDrawable, IKeyBindingHandler<OsuAction>, IRequireHighFrequencyMousePosition
{
private const int particle_lifetime_min = 300;
private const int particle_lifetime_max = 1000;
private const float particle_gravity = 240;
public bool Active => breakSpewer?.Active.Value == true || kiaiSpewer?.Active.Value == true;
private LegacyCursorParticleSpewer breakSpewer;
private LegacyCursorParticleSpewer kiaiSpewer;
private Vector2 cursorVelocity;
private ParticleSpewer breakSpewer;
private ParticleSpewer kiaiSpewer;
[Resolved(canBeNull: true)]
private Player player { get; set; }
@ -45,21 +50,25 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
texture.ScaleAdjust *= 1.6f;
}
RelativeSizeAxes = Axes.Both;
Anchor = Anchor.Centre;
InternalChildren = new[]
{
breakSpewer = new LegacyCursorParticleSpewer(texture, 20)
breakSpewer = new ParticleSpewer(texture, 20, particle_lifetime_max)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = Vector2.One,
Colour = starBreakAdditive,
Direction = SpewDirection.None,
ParticleGravity = particle_gravity,
CreateParticle = createBreakParticle,
},
kiaiSpewer = new LegacyCursorParticleSpewer(texture, 60)
kiaiSpewer = new ParticleSpewer(texture, 60, particle_lifetime_max)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = Vector2.One,
Colour = starBreakAdditive,
Direction = SpewDirection.None,
ParticleGravity = particle_gravity,
CreateParticle = createParticle,
},
};
@ -85,6 +94,39 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
kiaiSpewer.Active.Value = kiaiHitObject != null;
}
private Vector2? cursorScreenPosition;
private const double max_velocity_frame_length = 15;
private double velocityFrameLength;
private Vector2 totalPosDifference;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (cursorScreenPosition == null)
{
cursorScreenPosition = e.ScreenSpaceMousePosition;
return base.OnMouseMove(e);
}
// calculate cursor velocity.
totalPosDifference += e.ScreenSpaceMousePosition - cursorScreenPosition.Value;
cursorScreenPosition = e.ScreenSpaceMousePosition;
velocityFrameLength += Math.Abs(Clock.ElapsedFrameTime);
if (velocityFrameLength > max_velocity_frame_length)
{
cursorVelocity = totalPosDifference / (float)velocityFrameLength;
totalPosDifference = Vector2.Zero;
velocityFrameLength = 0;
}
return base.OnMouseMove(e);
}
public bool OnPressed(OsuAction action)
{
handleInput(action, true);
@ -111,125 +153,53 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
rightPressed = pressed;
break;
}
}
private ParticleSpewer.FallingParticle? createParticle()
{
if (!cursorScreenPosition.HasValue) return null;
return new ParticleSpewer.FallingParticle
{
StartPosition = ToLocalSpace(cursorScreenPosition.Value),
Duration = RNG.NextSingle(particle_lifetime_min, particle_lifetime_max),
StartAngle = (float)(RNG.NextDouble() * 4 - 2),
EndAngle = RNG.NextSingle(-2f, 2f),
EndScale = RNG.NextSingle(2f),
Velocity = cursorVelocity * 40,
};
}
private ParticleSpewer.FallingParticle? createBreakParticle()
{
var baseParticle = createParticle();
if (!baseParticle.HasValue) return null;
var p = baseParticle.Value;
if (leftPressed && rightPressed)
breakSpewer.Direction = SpewDirection.Omni;
{
p.Velocity += new Vector2(
RNG.NextSingle(-460f, 460f),
RNG.NextSingle(-160f, 160f)
);
}
else if (leftPressed)
breakSpewer.Direction = SpewDirection.Left;
{
p.Velocity += new Vector2(
RNG.NextSingle(-460f, 0),
RNG.NextSingle(-40f, 40f)
);
}
else if (rightPressed)
breakSpewer.Direction = SpewDirection.Right;
else
breakSpewer.Direction = SpewDirection.None;
}
private class LegacyCursorParticleSpewer : ParticleSpewer, IRequireHighFrequencyMousePosition
{
private const int particle_lifetime_min = 300;
private const int particle_lifetime_max = 1000;
public SpewDirection Direction { get; set; }
protected override bool CanSpawnParticles => base.CanSpawnParticles && cursorScreenPosition.HasValue;
protected override float ParticleGravity => 240;
public LegacyCursorParticleSpewer(Texture texture, int perSecond)
: base(texture, perSecond, particle_lifetime_max)
{
Active.BindValueChanged(_ => resetVelocityCalculation());
p.Velocity += new Vector2(
RNG.NextSingle(0, 460f),
RNG.NextSingle(-40f, 40f)
);
}
private Vector2? cursorScreenPosition;
private Vector2 cursorVelocity;
private const double max_velocity_frame_length = 15;
private double velocityFrameLength;
private Vector2 totalPosDifference;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (cursorScreenPosition == null)
{
cursorScreenPosition = e.ScreenSpaceMousePosition;
return base.OnMouseMove(e);
}
// calculate cursor velocity.
totalPosDifference += e.ScreenSpaceMousePosition - cursorScreenPosition.Value;
cursorScreenPosition = e.ScreenSpaceMousePosition;
velocityFrameLength += Clock.ElapsedFrameTime;
if (velocityFrameLength > max_velocity_frame_length)
{
cursorVelocity = totalPosDifference / (float)velocityFrameLength;
totalPosDifference = Vector2.Zero;
velocityFrameLength = 0;
}
return base.OnMouseMove(e);
}
private void resetVelocityCalculation()
{
cursorScreenPosition = null;
totalPosDifference = Vector2.Zero;
velocityFrameLength = 0;
}
protected override FallingParticle CreateParticle() =>
new FallingParticle
{
StartPosition = ToLocalSpace(cursorScreenPosition ?? Vector2.Zero),
Duration = RNG.NextSingle(particle_lifetime_min, particle_lifetime_max),
StartAngle = (float)(RNG.NextDouble() * 4 - 2),
EndAngle = RNG.NextSingle(-2f, 2f),
EndScale = RNG.NextSingle(2f),
Velocity = getVelocity(),
};
private Vector2 getVelocity()
{
Vector2 velocity = Vector2.Zero;
switch (Direction)
{
case SpewDirection.Left:
velocity = new Vector2(
RNG.NextSingle(-460f, 0),
RNG.NextSingle(-40f, 40f)
);
break;
case SpewDirection.Right:
velocity = new Vector2(
RNG.NextSingle(0, 460f),
RNG.NextSingle(-40f, 40f)
);
break;
case SpewDirection.Omni:
velocity = new Vector2(
RNG.NextSingle(-460f, 460f),
RNG.NextSingle(-160f, 160f)
);
break;
}
velocity += cursorVelocity * 40;
return velocity;
}
}
private enum SpewDirection
{
None,
Left,
Right,
Omni,
return p;
}
}
}

View File

@ -5,7 +5,6 @@ using System;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Graphics;
@ -17,7 +16,12 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneParticleSpewer : OsuTestScene
{
private TestParticleSpewer spewer;
private const int max_particle_duration = 1500;
private float particleMaxVelocity = 0.5f;
private Vector2 particleSpawnPosition = new Vector2(0.5f);
private ParticleSpewer spewer;
[Resolved]
private SkinManager skinManager { get; set; }
@ -28,11 +32,11 @@ namespace osu.Game.Tests.Visual.Gameplay
Child = spewer = createSpewer();
AddToggleStep("toggle spawning", value => spewer.Active.Value = value);
AddSliderStep("particle gravity", 0f, 1f, 0f, value => spewer.Gravity = value);
AddSliderStep("particle velocity", 0f, 1f, 0.5f, value => spewer.MaxVelocity = value);
AddSliderStep("particle velocity", 0f, 1f, 0.5f, value => particleMaxVelocity = value);
AddSliderStep("particle gravity", 0f, 1f, 0f, value => spewer.ParticleGravity = value);
AddStep("move to new location", () =>
{
spewer.TransformTo(nameof(spewer.SpawnPosition), new Vector2(RNG.NextSingle(), RNG.NextSingle()), 1000, Easing.Out);
this.TransformTo(nameof(particleSpawnPosition), new Vector2(RNG.NextSingle(), RNG.NextSingle()), 1000, Easing.Out);
});
}
@ -55,47 +59,29 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("is not present", () => !spewer.IsPresent);
}
private TestParticleSpewer createSpewer() =>
new TestParticleSpewer(skinManager.DefaultLegacySkin.GetTexture("star2"))
private ParticleSpewer createSpewer() =>
new ParticleSpewer(skinManager.DefaultLegacySkin.GetTexture("star2"), 1500, max_particle_duration)
{
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
Position = new Vector2(0.5f),
Size = new Vector2(0.5f),
CreateParticle = createParticle,
};
private class TestParticleSpewer : ParticleSpewer
{
private const int lifetime = 1500;
private const int rate = 250;
public float Gravity;
public float MaxVelocity = 0.25f;
public Vector2 SpawnPosition { get; set; } = new Vector2(0.5f);
protected override float ParticleGravity => Gravity;
public TestParticleSpewer(Texture texture)
: base(texture, rate, lifetime)
private ParticleSpewer.FallingParticle? createParticle() =>
new ParticleSpewer.FallingParticle
{
}
protected override FallingParticle CreateParticle() =>
new FallingParticle
{
Velocity = new Vector2(
RNG.NextSingle(-MaxVelocity, MaxVelocity),
RNG.NextSingle(-MaxVelocity, MaxVelocity)
),
StartPosition = SpawnPosition,
Duration = RNG.NextSingle(lifetime),
StartAngle = RNG.NextSingle(MathF.PI * 2),
EndAngle = RNG.NextSingle(MathF.PI * 2),
EndScale = RNG.NextSingle(0.5f, 1.5f)
};
}
Velocity = new Vector2(
RNG.NextSingle(-particleMaxVelocity, particleMaxVelocity),
RNG.NextSingle(-particleMaxVelocity, particleMaxVelocity)
),
StartPosition = particleSpawnPosition,
Duration = RNG.NextSingle(max_particle_duration),
StartAngle = RNG.NextSingle(MathF.PI * 2),
EndAngle = RNG.NextSingle(MathF.PI * 2),
EndScale = RNG.NextSingle(0.5f, 1.5f)
};
}
}

View File

@ -13,14 +13,14 @@ using osuTK;
namespace osu.Game.Graphics
{
public abstract class ParticleSpewer : Sprite
public class ParticleSpewer : Sprite
{
private readonly FallingParticle[] particles;
private int currentIndex;
private double lastParticleAdded;
private readonly double cooldown;
private readonly double maxLifetime;
private readonly double maxDuration;
/// <summary>
/// Determines whether particles are being spawned.
@ -29,20 +29,24 @@ namespace osu.Game.Graphics
public override bool IsPresent => base.IsPresent && hasActiveParticles;
protected virtual bool CanSpawnParticles => true;
protected virtual float ParticleGravity => 0;
/// <summary>
/// Called each time a new particle should be spawned.
/// </summary>
public Func<FallingParticle?> CreateParticle = () => new FallingParticle();
private bool hasActiveParticles => Active.Value || (lastParticleAdded + maxLifetime) > Time.Current;
public float ParticleGravity;
protected ParticleSpewer(Texture texture, int perSecond, double maxLifetime)
private bool hasActiveParticles => Active.Value || (lastParticleAdded + maxDuration) > Time.Current;
public ParticleSpewer(Texture texture, int perSecond, double maxDuration)
{
Texture = texture;
Blending = BlendingParameters.Additive;
particles = new FallingParticle[perSecond * (int)Math.Ceiling(maxLifetime / 1000)];
particles = new FallingParticle[perSecond * (int)Math.Ceiling(maxDuration / 1000)];
cooldown = 1000f / perSecond;
this.maxLifetime = maxLifetime;
this.maxDuration = maxDuration;
}
protected override void Update()
@ -53,25 +57,25 @@ namespace osu.Game.Graphics
// this can happen when seeking in replays.
if (lastParticleAdded > Time.Current) lastParticleAdded = 0;
if (Active.Value && CanSpawnParticles && Time.Current > lastParticleAdded + cooldown)
if (Active.Value && Time.Current > lastParticleAdded + cooldown)
{
var newParticle = CreateParticle();
newParticle.StartTime = (float)Time.Current;
particles[currentIndex] = newParticle;
if (newParticle.HasValue)
{
var particle = newParticle.Value;
particle.StartTime = (float)Time.Current;
currentIndex = (currentIndex + 1) % particles.Length;
lastParticleAdded = Time.Current;
particles[currentIndex] = particle;
currentIndex = (currentIndex + 1) % particles.Length;
lastParticleAdded = Time.Current;
}
}
Invalidate(Invalidation.DrawNode);
}
/// <summary>
/// Called each time a new particle should be spawned.
/// </summary>
protected virtual FallingParticle CreateParticle() => new FallingParticle();
protected override DrawNode CreateDrawNode() => new ParticleSpewerDrawNode(this);
# region DrawNode
@ -82,7 +86,7 @@ namespace osu.Game.Graphics
protected new ParticleSpewer Source => (ParticleSpewer)base.Source;
private readonly float maxLifetime;
private readonly float maxDuration;
private float currentTime;
private float gravity;
@ -93,7 +97,7 @@ namespace osu.Game.Graphics
: base(source)
{
particles = new FallingParticle[Source.particles.Length];
maxLifetime = (float)Source.maxLifetime;
maxDuration = (float)Source.maxDuration;
}
public override void ApplyState()
@ -124,7 +128,7 @@ namespace osu.Game.Graphics
var alpha = p.AlphaAtTime(timeSinceStart);
if (alpha <= 0) continue;
var pos = p.PositionAtTime(timeSinceStart, gravity, maxLifetime);
var pos = p.PositionAtTime(timeSinceStart, gravity, maxDuration);
var scale = p.ScaleAtTime(timeSinceStart);
var angle = p.AngleAtTime(timeSinceStart);
@ -174,7 +178,7 @@ namespace osu.Game.Graphics
#endregion
protected struct FallingParticle
public struct FallingParticle
{
public float StartTime;
public Vector2 StartPosition;