From 1a60ce164e31c5cf3706e25d4b7e42aa70f359da Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Tue, 24 Aug 2021 00:15:16 +0200 Subject: [PATCH 01/36] Add `ParticleJet` --- .../Visual/Gameplay/TestSceneParticleJet.cs | 61 +++++++ osu.Game/Graphics/Particles/ParticleJet.cs | 49 +++++ osu.Game/Graphics/Particles/ParticleSpewer.cs | 172 ++++++++++++++++++ 3 files changed, 282 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneParticleJet.cs create mode 100644 osu.Game/Graphics/Particles/ParticleJet.cs create mode 100644 osu.Game/Graphics/Particles/ParticleSpewer.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleJet.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleJet.cs new file mode 100644 index 0000000000..6438ba0b22 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleJet.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Graphics.Particles; +using osu.Game.Skinning; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [TestFixture] + public class TestSceneParticleJet : OsuTestScene + { + private ParticleJet jet; + + [Resolved] + private SkinManager skinManager { get; set; } + + public TestSceneParticleJet() + { + AddStep("create", () => + { + Child = jet = createJet(); + }); + + AddToggleStep("toggle spawning", value => jet.Active = value); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create jet", () => Child = jet = createJet()); + } + + [Test] + public void TestPresence() + { + AddStep("start jet", () => jet.Active = true); + AddAssert("is present", () => jet.IsPresent); + + AddWaitStep("wait for some particles", 3); + AddStep("stop jet", () => jet.Active = false); + + AddWaitStep("wait for clean screen", 5); + AddAssert("is not present", () => !jet.IsPresent); + } + + private ParticleJet createJet() + { + return new ParticleJet(skinManager.DefaultLegacySkin.GetTexture("star2"), 180) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativePositionAxes = Axes.Y, + Y = -0.1f, + }; + } + } +} diff --git a/osu.Game/Graphics/Particles/ParticleJet.cs b/osu.Game/Graphics/Particles/ParticleJet.cs new file mode 100644 index 0000000000..039dd36ddc --- /dev/null +++ b/osu.Game/Graphics/Particles/ParticleJet.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics.Textures; +using osu.Framework.Utils; +using osuTK; + +namespace osu.Game.Graphics.Particles +{ + public class ParticleJet : ParticleSpewer + { + private const int particles_per_second = 80; + private const double particle_lifetime = 500; + private const float angular_velocity = 3f; + private const int angle_spread = 10; + private const float velocity_min = 1.3f; + private const float velocity_max = 1.5f; + + private readonly int angle; + + protected override float ParticleGravity => 0.25f; + + public ParticleJet(Texture texture, int angle) + : base(texture, particles_per_second, particle_lifetime) + { + this.angle = angle; + } + + protected override FallingParticle SpawnParticle() + { + var directionRads = MathUtils.DegreesToRadians( + RNG.NextSingle(angle - angle_spread / 2, angle + angle_spread / 2) + ); + var direction = new Vector2(MathF.Sin(directionRads), MathF.Cos(directionRads)); + + return new FallingParticle + { + StartTime = (float)Time.Current, + Position = OriginPosition, + Duration = RNG.NextSingle((float)particle_lifetime * 0.8f, (float)particle_lifetime), + Velocity = direction * new Vector2(RNG.NextSingle(velocity_min, velocity_max)), + AngularVelocity = RNG.NextSingle(-angular_velocity, angular_velocity), + StartScale = 1f, + EndScale = 2f, + }; + } + } +} diff --git a/osu.Game/Graphics/Particles/ParticleSpewer.cs b/osu.Game/Graphics/Particles/ParticleSpewer.cs new file mode 100644 index 0000000000..7196727ca1 --- /dev/null +++ b/osu.Game/Graphics/Particles/ParticleSpewer.cs @@ -0,0 +1,172 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osuTK; + +namespace osu.Game.Graphics.Particles +{ + public abstract class ParticleSpewer : Sprite + { + private readonly FallingParticle[] particles; + private int currentIndex; + private double lastParticleAdded; + + private readonly double cooldown; + private readonly double maxLifetime; + + /// + /// Determines whether particles are being spawned. + /// + public bool Active { get; set; } + + public bool HasActiveParticles => Active || (lastParticleAdded + maxLifetime) > Time.Current; + public override bool IsPresent => base.IsPresent && HasActiveParticles; + + protected virtual float ParticleGravity => 0.5f; + + protected ParticleSpewer(Texture texture, int perSecond, double maxLifetime) + { + Texture = texture; + Blending = BlendingParameters.Additive; + + particles = new FallingParticle[perSecond * (int)Math.Ceiling(maxLifetime / 1000)]; + + cooldown = 1000f / perSecond; + this.maxLifetime = maxLifetime; + } + + protected override void Update() + { + base.Update(); + + if (Active && Time.Current > lastParticleAdded + cooldown) + { + addParticle(SpawnParticle()); + } + + Invalidate(Invalidation.DrawNode); + } + + /// + /// Called each time a new particle should be spawned. + /// + protected abstract FallingParticle SpawnParticle(); + + private void addParticle(FallingParticle fallingParticle) + { + particles[currentIndex] = fallingParticle; + + currentIndex = (currentIndex + 1) % particles.Length; + lastParticleAdded = Time.Current; + } + + protected override DrawNode CreateDrawNode() => new ParticleSpewerDrawNode(this); + + private class ParticleSpewerDrawNode : SpriteDrawNode + { + private readonly FallingParticle[] particles; + + protected new ParticleSpewer Source => (ParticleSpewer)base.Source; + + private float currentTime; + private float gravity; + + public ParticleSpewerDrawNode(Sprite source) + : base(source) + { + particles = new FallingParticle[Source.particles.Length]; + } + + public override void ApplyState() + { + base.ApplyState(); + + Source.particles.CopyTo(particles, 0); + + currentTime = (float)Source.Time.Current; + gravity = Source.ParticleGravity; + } + + protected override void Blit(Action vertexAction) + { + foreach (var p in particles) + { + // ignore particles that weren't initialized. + if (p.StartTime <= 0) continue; + + var timeSinceStart = currentTime - p.StartTime; + + var alpha = p.AlphaAtTime(timeSinceStart); + if (alpha <= 0) continue; + + var scale = p.ScaleAtTime(timeSinceStart); + var pos = p.PositionAtTime(timeSinceStart, gravity); + var angle = p.AngleAtTime(timeSinceStart); + + var rect = new RectangleF( + pos.X - Texture.DisplayWidth * scale / 2, + pos.Y - Texture.DisplayHeight * scale / 2, + Texture.DisplayWidth * scale, + Texture.DisplayHeight * scale); + + var quad = new Quad( + transformPosition(rect.TopLeft, rect.Centre, angle), + transformPosition(rect.TopRight, rect.Centre, angle), + transformPosition(rect.BottomLeft, rect.Centre, angle), + transformPosition(rect.BottomRight, rect.Centre, angle) + ); + + DrawQuad(Texture, quad, DrawColourInfo.Colour.MultiplyAlpha(alpha), null, vertexAction, + new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height), + null, TextureCoords); + } + } + + private Vector2 transformPosition(Vector2 pos, Vector2 centre, float angle) + { + // rotate point around centre. + float cos = MathF.Cos(angle); + float sin = MathF.Sin(angle); + + float x = centre.X + (pos.X - centre.X) * cos + (pos.Y - centre.Y) * sin; + float y = centre.Y + (pos.Y - centre.Y) * cos - (pos.X - centre.X) * sin; + + // convert to screen space. + return Vector2Extensions.Transform(new Vector2(x, y), DrawInfo.Matrix); + } + } + + protected struct FallingParticle + { + public float StartTime; + public Vector2 Position; + public Vector2 Velocity; + public float Duration; + public float AngularVelocity; + public float StartScale; + public float EndScale; + + public float AlphaAtTime(float timeSinceStart) => 1 - progressAtTime(timeSinceStart); + + public float ScaleAtTime(float timeSinceStart) => StartScale + (EndScale - StartScale) * progressAtTime(timeSinceStart); + + public float AngleAtTime(float timeSinceStart) => AngularVelocity / 1000 * timeSinceStart; + + public Vector2 PositionAtTime(float timeSinceStart, float gravity) + { + var progress = progressAtTime(timeSinceStart); + var grav = new Vector2(0, -gravity) * progress; + + return Position + (Velocity - grav) * timeSinceStart; + } + + private float progressAtTime(float timeSinceStart) => Math.Clamp(timeSinceStart / Duration, 0, 1); + } + } +} From 714cf33aac1b94ce27d6411b4f0efdc3cab92c8b Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 29 Aug 2021 21:41:47 +0200 Subject: [PATCH 02/36] Change `ParticleSpewer` to use screen space --- osu.Game/Graphics/Particles/ParticleJet.cs | 19 ++++--- osu.Game/Graphics/Particles/ParticleSpewer.cs | 51 +++++++++++++------ 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/osu.Game/Graphics/Particles/ParticleJet.cs b/osu.Game/Graphics/Particles/ParticleJet.cs index 039dd36ddc..eb7a49abc3 100644 --- a/osu.Game/Graphics/Particles/ParticleJet.cs +++ b/osu.Game/Graphics/Particles/ParticleJet.cs @@ -29,21 +29,20 @@ namespace osu.Game.Graphics.Particles protected override FallingParticle SpawnParticle() { + var p = base.SpawnParticle(); + var directionRads = MathUtils.DegreesToRadians( RNG.NextSingle(angle - angle_spread / 2, angle + angle_spread / 2) ); var direction = new Vector2(MathF.Sin(directionRads), MathF.Cos(directionRads)); - return new FallingParticle - { - StartTime = (float)Time.Current, - Position = OriginPosition, - Duration = RNG.NextSingle((float)particle_lifetime * 0.8f, (float)particle_lifetime), - Velocity = direction * new Vector2(RNG.NextSingle(velocity_min, velocity_max)), - AngularVelocity = RNG.NextSingle(-angular_velocity, angular_velocity), - StartScale = 1f, - EndScale = 2f, - }; + p.Duration = RNG.NextSingle((float)particle_lifetime * 0.8f, (float)particle_lifetime); + p.Velocity = direction * new Vector2(RNG.NextSingle(velocity_min, velocity_max)); + p.AngularVelocity = RNG.NextSingle(-angular_velocity, angular_velocity); + p.StartScale = 1f; + p.EndScale = 2f; + + return p; } } } diff --git a/osu.Game/Graphics/Particles/ParticleSpewer.cs b/osu.Game/Graphics/Particles/ParticleSpewer.cs index 7196727ca1..f2c358bd96 100644 --- a/osu.Game/Graphics/Particles/ParticleSpewer.cs +++ b/osu.Game/Graphics/Particles/ParticleSpewer.cs @@ -45,6 +45,10 @@ namespace osu.Game.Graphics.Particles { base.Update(); + // reset cooldown if the clock was rewound. + // this can happen when seeking in replays. + if (lastParticleAdded > Time.Current) lastParticleAdded = 0; + if (Active && Time.Current > lastParticleAdded + cooldown) { addParticle(SpawnParticle()); @@ -56,7 +60,14 @@ namespace osu.Game.Graphics.Particles /// /// Called each time a new particle should be spawned. /// - protected abstract FallingParticle SpawnParticle(); + protected virtual FallingParticle SpawnParticle() + { + return new FallingParticle + { + StartTime = (float)Time.Current, + StartPosition = ToScreenSpace(OriginPosition), + }; + } private void addParticle(FallingParticle fallingParticle) { @@ -68,6 +79,8 @@ namespace osu.Game.Graphics.Particles protected override DrawNode CreateDrawNode() => new ParticleSpewerDrawNode(this); + # region DrawNode + private class ParticleSpewerDrawNode : SpriteDrawNode { private readonly FallingParticle[] particles; @@ -102,6 +115,10 @@ namespace osu.Game.Graphics.Particles var timeSinceStart = currentTime - p.StartTime; + // ignore particles from the future. + // these can appear when seeking in replays. + if (timeSinceStart < 0) continue; + var alpha = p.AlphaAtTime(timeSinceStart); if (alpha <= 0) continue; @@ -109,17 +126,21 @@ namespace osu.Game.Graphics.Particles var pos = p.PositionAtTime(timeSinceStart, gravity); var angle = p.AngleAtTime(timeSinceStart); + var matrixScale = DrawInfo.Matrix.ExtractScale(); + var width = Texture.DisplayWidth * scale * matrixScale.X; + var height = Texture.DisplayHeight * scale * matrixScale.Y; + var rect = new RectangleF( - pos.X - Texture.DisplayWidth * scale / 2, - pos.Y - Texture.DisplayHeight * scale / 2, - Texture.DisplayWidth * scale, - Texture.DisplayHeight * scale); + pos.X - width / 2, + pos.Y - height / 2, + width, + height); var quad = new Quad( - transformPosition(rect.TopLeft, rect.Centre, angle), - transformPosition(rect.TopRight, rect.Centre, angle), - transformPosition(rect.BottomLeft, rect.Centre, angle), - transformPosition(rect.BottomRight, rect.Centre, angle) + rotatePosition(rect.TopLeft, rect.Centre, angle), + rotatePosition(rect.TopRight, rect.Centre, angle), + rotatePosition(rect.BottomLeft, rect.Centre, angle), + rotatePosition(rect.BottomRight, rect.Centre, angle) ); DrawQuad(Texture, quad, DrawColourInfo.Colour.MultiplyAlpha(alpha), null, vertexAction, @@ -128,24 +149,24 @@ namespace osu.Game.Graphics.Particles } } - private Vector2 transformPosition(Vector2 pos, Vector2 centre, float angle) + private Vector2 rotatePosition(Vector2 pos, Vector2 centre, float angle) { - // rotate point around centre. float cos = MathF.Cos(angle); float sin = MathF.Sin(angle); float x = centre.X + (pos.X - centre.X) * cos + (pos.Y - centre.Y) * sin; float y = centre.Y + (pos.Y - centre.Y) * cos - (pos.X - centre.X) * sin; - // convert to screen space. - return Vector2Extensions.Transform(new Vector2(x, y), DrawInfo.Matrix); + return new Vector2(x, y); } } + #endregion + protected struct FallingParticle { public float StartTime; - public Vector2 Position; + public Vector2 StartPosition; public Vector2 Velocity; public float Duration; public float AngularVelocity; @@ -163,7 +184,7 @@ namespace osu.Game.Graphics.Particles var progress = progressAtTime(timeSinceStart); var grav = new Vector2(0, -gravity) * progress; - return Position + (Velocity - grav) * timeSinceStart; + return StartPosition + (Velocity - grav) * timeSinceStart; } private float progressAtTime(float timeSinceStart) => Math.Clamp(timeSinceStart / Duration, 0, 1); From 4c753420d3859df95a3eed7751df08b0f83a6747 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sat, 4 Sep 2021 16:47:12 +0200 Subject: [PATCH 03/36] Fix `ParticleSpewer` gravity calculation --- osu.Game/Graphics/Particles/ParticleJet.cs | 6 +++--- osu.Game/Graphics/Particles/ParticleSpewer.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Particles/ParticleJet.cs b/osu.Game/Graphics/Particles/ParticleJet.cs index eb7a49abc3..6bdde44a2c 100644 --- a/osu.Game/Graphics/Particles/ParticleJet.cs +++ b/osu.Game/Graphics/Particles/ParticleJet.cs @@ -14,12 +14,12 @@ namespace osu.Game.Graphics.Particles private const double particle_lifetime = 500; private const float angular_velocity = 3f; private const int angle_spread = 10; - private const float velocity_min = 1.3f; - private const float velocity_max = 1.5f; + private const float velocity_min = 1300f; + private const float velocity_max = 1500f; private readonly int angle; - protected override float ParticleGravity => 0.25f; + protected override float ParticleGravity => 750f; public ParticleJet(Texture texture, int angle) : base(texture, particles_per_second, particle_lifetime) diff --git a/osu.Game/Graphics/Particles/ParticleSpewer.cs b/osu.Game/Graphics/Particles/ParticleSpewer.cs index f2c358bd96..52e089fcca 100644 --- a/osu.Game/Graphics/Particles/ParticleSpewer.cs +++ b/osu.Game/Graphics/Particles/ParticleSpewer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Graphics.Particles public bool HasActiveParticles => Active || (lastParticleAdded + maxLifetime) > Time.Current; public override bool IsPresent => base.IsPresent && HasActiveParticles; - protected virtual float ParticleGravity => 0.5f; + protected virtual float ParticleGravity => 0; protected ParticleSpewer(Texture texture, int perSecond, double maxLifetime) { @@ -182,9 +182,9 @@ namespace osu.Game.Graphics.Particles public Vector2 PositionAtTime(float timeSinceStart, float gravity) { var progress = progressAtTime(timeSinceStart); - var grav = new Vector2(0, -gravity) * progress; + var currentGravity = new Vector2(0, gravity * Duration / 1000 * progress); - return StartPosition + (Velocity - grav) * timeSinceStart; + return StartPosition + (Velocity + currentGravity) * timeSinceStart / 1000; } private float progressAtTime(float timeSinceStart) => Math.Clamp(timeSinceStart / Duration, 0, 1); From 328c9a5dd038d9882ebd11d8f0ba068b16826252 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sat, 4 Sep 2021 20:50:30 +0200 Subject: [PATCH 04/36] Change `ParticleSpewer.Active` to a Bindable --- osu.Game.Tests/Visual/Gameplay/TestSceneParticleJet.cs | 6 +++--- osu.Game/Graphics/Particles/ParticleSpewer.cs | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleJet.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleJet.cs index 6438ba0b22..e570abcf88 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleJet.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleJet.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Gameplay Child = jet = createJet(); }); - AddToggleStep("toggle spawning", value => jet.Active = value); + AddToggleStep("toggle spawning", value => jet.Active.Value = value); } [SetUpSteps] @@ -37,11 +37,11 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestPresence() { - AddStep("start jet", () => jet.Active = true); + AddStep("start jet", () => jet.Active.Value = true); AddAssert("is present", () => jet.IsPresent); AddWaitStep("wait for some particles", 3); - AddStep("stop jet", () => jet.Active = false); + AddStep("stop jet", () => jet.Active.Value = false); AddWaitStep("wait for clean screen", 5); AddAssert("is not present", () => !jet.IsPresent); diff --git a/osu.Game/Graphics/Particles/ParticleSpewer.cs b/osu.Game/Graphics/Particles/ParticleSpewer.cs index 52e089fcca..bc25206311 100644 --- a/osu.Game/Graphics/Particles/ParticleSpewer.cs +++ b/osu.Game/Graphics/Particles/ParticleSpewer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; @@ -23,9 +24,9 @@ namespace osu.Game.Graphics.Particles /// /// Determines whether particles are being spawned. /// - public bool Active { get; set; } + public readonly BindableBool Active = new BindableBool(); - public bool HasActiveParticles => Active || (lastParticleAdded + maxLifetime) > Time.Current; + public bool HasActiveParticles => Active.Value || (lastParticleAdded + maxLifetime) > Time.Current; public override bool IsPresent => base.IsPresent && HasActiveParticles; protected virtual float ParticleGravity => 0; @@ -49,7 +50,7 @@ namespace osu.Game.Graphics.Particles // this can happen when seeking in replays. if (lastParticleAdded > Time.Current) lastParticleAdded = 0; - if (Active && Time.Current > lastParticleAdded + cooldown) + if (Active.Value && Time.Current > lastParticleAdded + cooldown) { addParticle(SpawnParticle()); } From ee4006f3d73389eddfcc382de288bb041d4f1989 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sat, 4 Sep 2021 21:49:05 +0200 Subject: [PATCH 05/36] Add legacy cursor star particles --- .../Skinning/Legacy/LegacyCursor.cs | 5 + .../Legacy/LegacyCursorStarParticles.cs | 185 ++++++++++++++++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 1 + 3 files changed, 191 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs index b2ffc171be..cd7954a8d6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs @@ -31,6 +31,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy InternalChildren = new[] { + new LegacyCursorStarParticles + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, ExpandTarget = new NonPlayfieldSprite { Texture = skin.GetTexture("cursor"), diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs new file mode 100644 index 0000000000..f10d9a0fa9 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs @@ -0,0 +1,185 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Bindings; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Particles; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Play; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Skinning.Legacy +{ + public class LegacyCursorStarParticles : BeatSyncedContainer, IKeyBindingHandler + { + private StarParticleSpewer breakSpewer; + private StarParticleSpewer kiaiSpewer; + + [Resolved(canBeNull: true)] + private Player player { get; set; } + + [Resolved(canBeNull: true)] + private OsuPlayfield osuPlayfield { get; set; } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, OsuColour colour) + { + var texture = skin.GetTexture("star2"); + + InternalChildren = new[] + { + breakSpewer = new StarParticleSpewer(texture, 20) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colour.PinkLighter, + Direction = SpewDirection.None, + Active = + { + Value = true, + } + }, + kiaiSpewer = new StarParticleSpewer(texture, 60) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colour.PinkLighter, + Direction = SpewDirection.None, + Active = + { + Value = false, + } + }, + }; + + if (player != null) + { + breakSpewer.Active.BindTarget = player.IsBreakTime; + } + } + + protected override void Update() + { + if (osuPlayfield == null) return; + + // find active kiai slider or spinner. + var kiaiHitObject = osuPlayfield.HitObjectContainer.AliveObjects.FirstOrDefault(h => + h.HitObject.Kiai && + ( + (h is DrawableSlider slider && slider.Tracking.Value) || + (h is DrawableSpinner spinner && spinner.RotationTracker.Tracking) + ) + ); + + kiaiSpewer.Active.Value = kiaiHitObject != null; + } + + public bool OnPressed(OsuAction action) + { + handleInput(action, true); + return false; + } + + public void OnReleased(OsuAction action) + { + handleInput(action, false); + } + + private bool leftPressed; + private bool rightPressed; + + private void handleInput(OsuAction action, bool pressed) + { + switch (action) + { + case OsuAction.LeftButton: + leftPressed = pressed; + break; + + case OsuAction.RightButton: + rightPressed = pressed; + break; + } + + if (leftPressed && rightPressed) + breakSpewer.Direction = SpewDirection.Both; + else if (leftPressed) + breakSpewer.Direction = SpewDirection.Left; + else if (rightPressed) + breakSpewer.Direction = SpewDirection.Right; + else + breakSpewer.Direction = SpewDirection.None; + } + + private class StarParticleSpewer : ParticleSpewer + { + private const int particle_lifetime_min = 300; + private const int particle_lifetime_max = 1000; + + public SpewDirection Direction { get; set; } + + protected override float ParticleGravity => 460; + + public StarParticleSpewer(Texture texture, int perSecond) + : base(texture, perSecond, particle_lifetime_max) + { + } + + protected override FallingParticle SpawnParticle() + { + var p = base.SpawnParticle(); + + p.Duration = RNG.NextSingle(particle_lifetime_min, particle_lifetime_max); + p.AngularVelocity = RNG.NextSingle(-3f, 3f); + p.StartScale = RNG.NextSingle(0.5f, 1f); + p.EndScale = RNG.NextSingle(2f); + + switch (Direction) + { + case SpewDirection.None: + p.Velocity = Vector2.Zero; + break; + + case SpewDirection.Left: + p.Velocity = new Vector2( + RNG.NextSingle(-460f, 0) * 2, + RNG.NextSingle(-40f, 40f) * 2 + ); + break; + + case SpewDirection.Right: + p.Velocity = new Vector2( + RNG.NextSingle(0, 460f) * 2, + RNG.NextSingle(-40f, 40f) * 2 + ); + break; + + case SpewDirection.Both: + p.Velocity = new Vector2( + RNG.NextSingle(-460f, 460f) * 2, + RNG.NextSingle(-160f, 160f) * 2 + ); + break; + } + + return p; + } + } + + private enum SpewDirection + { + None, + Left, + Right, + Both, + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index c1c2ea2299..2233a547b9 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -24,6 +24,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.UI { + [Cached] public class OsuPlayfield : Playfield { private readonly PlayfieldBorder playfieldBorder; From 5b1b36436f0ec4ccf95f9dbdf45c28f5a87c3fbc Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 5 Sep 2021 01:01:49 +0200 Subject: [PATCH 06/36] Add cursor velocity to star particles --- .../Legacy/LegacyCursorStarParticles.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs index f10d9a0fa9..52d4eedf42 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs @@ -131,6 +131,44 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy public StarParticleSpewer(Texture texture, int perSecond) : base(texture, perSecond, particle_lifetime_max) { + Active.BindValueChanged(_ => resetVelocityCalculation()); + } + + private Vector2 screenPosition => ToScreenSpace(OriginPosition); + + private Vector2 screenVelocity; + + private const double velocity_calculation_delay = 15; + private double lastVelocityCalculation; + private Vector2 positionDifference; + private Vector2? lastPosition; + + protected override void Update() + { + base.Update(); + + if (lastPosition != null) + { + positionDifference += (screenPosition - lastPosition.Value); + lastVelocityCalculation += Clock.ElapsedFrameTime; + } + + lastPosition = screenPosition; + + if (lastVelocityCalculation > velocity_calculation_delay) + { + screenVelocity = positionDifference / (float)lastVelocityCalculation; + + positionDifference = Vector2.Zero; + lastVelocityCalculation = 0; + } + } + + private void resetVelocityCalculation() + { + positionDifference = Vector2.Zero; + lastVelocityCalculation = 0; + lastPosition = null; } protected override FallingParticle SpawnParticle() @@ -170,6 +208,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy break; } + p.Velocity += screenVelocity * 50; + return p; } } From db662f8c5c9ae2b7b35a0da4317e1e9ee94ab9dd Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 5 Sep 2021 16:08:22 +0200 Subject: [PATCH 07/36] Add `ParticleParent` option to `ParticleSpewer` --- .../Legacy/LegacyCursorStarParticles.cs | 31 ++++++++++++------- osu.Game/Graphics/Particles/ParticleJet.cs | 1 + osu.Game/Graphics/Particles/ParticleSpewer.cs | 17 +++++++--- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs index 52d4eedf42..58fe4f9b7c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Direction = SpewDirection.None, Active = { - Value = true, + Value = false, } }, kiaiSpewer = new StarParticleSpewer(texture, 60) @@ -64,6 +64,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { breakSpewer.Active.BindTarget = player.IsBreakTime; } + + if (osuPlayfield != null) + { + breakSpewer.ParticleParent = osuPlayfield; + kiaiSpewer.ParticleParent = osuPlayfield; + } } protected override void Update() @@ -126,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy public SpewDirection Direction { get; set; } - protected override float ParticleGravity => 460; + protected override float ParticleGravity => 240; public StarParticleSpewer(Texture texture, int perSecond) : base(texture, perSecond, particle_lifetime_max) @@ -134,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Active.BindValueChanged(_ => resetVelocityCalculation()); } - private Vector2 screenPosition => ToScreenSpace(OriginPosition); + private Vector2 positionInParent => ToSpaceOfOtherDrawable(OriginPosition, ParticleParent); private Vector2 screenVelocity; @@ -149,11 +155,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (lastPosition != null) { - positionDifference += (screenPosition - lastPosition.Value); + positionDifference += (positionInParent - lastPosition.Value); lastVelocityCalculation += Clock.ElapsedFrameTime; } - lastPosition = screenPosition; + lastPosition = positionInParent; if (lastVelocityCalculation > velocity_calculation_delay) { @@ -175,6 +181,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { var p = base.SpawnParticle(); + p.StartPosition = positionInParent; p.Duration = RNG.NextSingle(particle_lifetime_min, particle_lifetime_max); p.AngularVelocity = RNG.NextSingle(-3f, 3f); p.StartScale = RNG.NextSingle(0.5f, 1f); @@ -188,27 +195,27 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy case SpewDirection.Left: p.Velocity = new Vector2( - RNG.NextSingle(-460f, 0) * 2, - RNG.NextSingle(-40f, 40f) * 2 + RNG.NextSingle(-460f, 0), + RNG.NextSingle(-40f, 40f) ); break; case SpewDirection.Right: p.Velocity = new Vector2( - RNG.NextSingle(0, 460f) * 2, - RNG.NextSingle(-40f, 40f) * 2 + RNG.NextSingle(0, 460f), + RNG.NextSingle(-40f, 40f) ); break; case SpewDirection.Both: p.Velocity = new Vector2( - RNG.NextSingle(-460f, 460f) * 2, - RNG.NextSingle(-160f, 160f) * 2 + RNG.NextSingle(-460f, 460f), + RNG.NextSingle(-160f, 160f) ); break; } - p.Velocity += screenVelocity * 50; + p.Velocity += screenVelocity * 40; return p; } diff --git a/osu.Game/Graphics/Particles/ParticleJet.cs b/osu.Game/Graphics/Particles/ParticleJet.cs index 6bdde44a2c..763f8d0a9e 100644 --- a/osu.Game/Graphics/Particles/ParticleJet.cs +++ b/osu.Game/Graphics/Particles/ParticleJet.cs @@ -36,6 +36,7 @@ namespace osu.Game.Graphics.Particles ); var direction = new Vector2(MathF.Sin(directionRads), MathF.Cos(directionRads)); + p.StartPosition = OriginPosition; p.Duration = RNG.NextSingle((float)particle_lifetime * 0.8f, (float)particle_lifetime); p.Velocity = direction * new Vector2(RNG.NextSingle(velocity_min, velocity_max)); p.AngularVelocity = RNG.NextSingle(-angular_velocity, angular_velocity); diff --git a/osu.Game/Graphics/Particles/ParticleSpewer.cs b/osu.Game/Graphics/Particles/ParticleSpewer.cs index bc25206311..2251d9590d 100644 --- a/osu.Game/Graphics/Particles/ParticleSpewer.cs +++ b/osu.Game/Graphics/Particles/ParticleSpewer.cs @@ -26,6 +26,12 @@ namespace osu.Game.Graphics.Particles /// public readonly BindableBool Active = new BindableBool(); + /// + /// whose DrawInfo will be used to draw each particle. + /// Defaults to the itself. + /// + public IDrawable ParticleParent; + public bool HasActiveParticles => Active.Value || (lastParticleAdded + maxLifetime) > Time.Current; public override bool IsPresent => base.IsPresent && HasActiveParticles; @@ -35,6 +41,7 @@ namespace osu.Game.Graphics.Particles { Texture = texture; Blending = BlendingParameters.Additive; + ParticleParent = this; particles = new FallingParticle[perSecond * (int)Math.Ceiling(maxLifetime / 1000)]; @@ -66,7 +73,6 @@ namespace osu.Game.Graphics.Particles return new FallingParticle { StartTime = (float)Time.Current, - StartPosition = ToScreenSpace(OriginPosition), }; } @@ -90,6 +96,7 @@ namespace osu.Game.Graphics.Particles private float currentTime; private float gravity; + private Matrix3 particleDrawMatrix; public ParticleSpewerDrawNode(Sprite source) : base(source) @@ -105,6 +112,7 @@ namespace osu.Game.Graphics.Particles currentTime = (float)Source.Time.Current; gravity = Source.ParticleGravity; + particleDrawMatrix = Source.ParticleParent.DrawInfo.Matrix; } protected override void Blit(Action vertexAction) @@ -127,9 +135,8 @@ namespace osu.Game.Graphics.Particles var pos = p.PositionAtTime(timeSinceStart, gravity); var angle = p.AngleAtTime(timeSinceStart); - var matrixScale = DrawInfo.Matrix.ExtractScale(); - var width = Texture.DisplayWidth * scale * matrixScale.X; - var height = Texture.DisplayHeight * scale * matrixScale.Y; + var width = Texture.DisplayWidth * scale; + var height = Texture.DisplayHeight * scale; var rect = new RectangleF( pos.X - width / 2, @@ -158,7 +165,7 @@ namespace osu.Game.Graphics.Particles float x = centre.X + (pos.X - centre.X) * cos + (pos.Y - centre.Y) * sin; float y = centre.Y + (pos.Y - centre.Y) * cos - (pos.X - centre.X) * sin; - return new Vector2(x, y); + return Vector2Extensions.Transform(new Vector2(x, y), particleDrawMatrix); } } From 6d68da8ff00229bfcbccd68f7b6d73c80c4da70d Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 5 Sep 2021 16:25:24 +0200 Subject: [PATCH 08/36] Remove `StartScale` from `ParticleSpewer` particles --- .../Skinning/Legacy/LegacyCursorStarParticles.cs | 1 - osu.Game/Graphics/Particles/ParticleJet.cs | 1 - osu.Game/Graphics/Particles/ParticleSpewer.cs | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs index 58fe4f9b7c..d6c4d9f92a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs @@ -184,7 +184,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy p.StartPosition = positionInParent; p.Duration = RNG.NextSingle(particle_lifetime_min, particle_lifetime_max); p.AngularVelocity = RNG.NextSingle(-3f, 3f); - p.StartScale = RNG.NextSingle(0.5f, 1f); p.EndScale = RNG.NextSingle(2f); switch (Direction) diff --git a/osu.Game/Graphics/Particles/ParticleJet.cs b/osu.Game/Graphics/Particles/ParticleJet.cs index 763f8d0a9e..2d70a7f697 100644 --- a/osu.Game/Graphics/Particles/ParticleJet.cs +++ b/osu.Game/Graphics/Particles/ParticleJet.cs @@ -40,7 +40,6 @@ namespace osu.Game.Graphics.Particles p.Duration = RNG.NextSingle((float)particle_lifetime * 0.8f, (float)particle_lifetime); p.Velocity = direction * new Vector2(RNG.NextSingle(velocity_min, velocity_max)); p.AngularVelocity = RNG.NextSingle(-angular_velocity, angular_velocity); - p.StartScale = 1f; p.EndScale = 2f; return p; diff --git a/osu.Game/Graphics/Particles/ParticleSpewer.cs b/osu.Game/Graphics/Particles/ParticleSpewer.cs index 2251d9590d..f9fb30abdc 100644 --- a/osu.Game/Graphics/Particles/ParticleSpewer.cs +++ b/osu.Game/Graphics/Particles/ParticleSpewer.cs @@ -178,12 +178,11 @@ namespace osu.Game.Graphics.Particles public Vector2 Velocity; public float Duration; public float AngularVelocity; - public float StartScale; public float EndScale; public float AlphaAtTime(float timeSinceStart) => 1 - progressAtTime(timeSinceStart); - public float ScaleAtTime(float timeSinceStart) => StartScale + (EndScale - StartScale) * progressAtTime(timeSinceStart); + public float ScaleAtTime(float timeSinceStart) => 1 + (EndScale - 1) * progressAtTime(timeSinceStart); public float AngleAtTime(float timeSinceStart) => AngularVelocity / 1000 * timeSinceStart; From c2f7b01ca400b8286365877c3f363f247897c857 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 5 Sep 2021 17:08:00 +0200 Subject: [PATCH 09/36] Change particle `AngularVelocity` into `StartAngle` and `EndAngle` --- .../Skinning/Legacy/LegacyCursorStarParticles.cs | 3 ++- osu.Game/Graphics/Particles/ParticleJet.cs | 4 ++-- osu.Game/Graphics/Particles/ParticleSpewer.cs | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs index d6c4d9f92a..9c901a7de5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs @@ -183,7 +183,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy p.StartPosition = positionInParent; p.Duration = RNG.NextSingle(particle_lifetime_min, particle_lifetime_max); - p.AngularVelocity = RNG.NextSingle(-3f, 3f); + p.StartAngle = (float)(RNG.NextDouble() * 4 - 2); + p.EndAngle = RNG.NextSingle(-2f, 2f); p.EndScale = RNG.NextSingle(2f); switch (Direction) diff --git a/osu.Game/Graphics/Particles/ParticleJet.cs b/osu.Game/Graphics/Particles/ParticleJet.cs index 2d70a7f697..a76aa6b75f 100644 --- a/osu.Game/Graphics/Particles/ParticleJet.cs +++ b/osu.Game/Graphics/Particles/ParticleJet.cs @@ -12,7 +12,6 @@ namespace osu.Game.Graphics.Particles { private const int particles_per_second = 80; private const double particle_lifetime = 500; - private const float angular_velocity = 3f; private const int angle_spread = 10; private const float velocity_min = 1300f; private const float velocity_max = 1500f; @@ -39,7 +38,8 @@ namespace osu.Game.Graphics.Particles p.StartPosition = OriginPosition; p.Duration = RNG.NextSingle((float)particle_lifetime * 0.8f, (float)particle_lifetime); p.Velocity = direction * new Vector2(RNG.NextSingle(velocity_min, velocity_max)); - p.AngularVelocity = RNG.NextSingle(-angular_velocity, angular_velocity); + p.StartAngle = RNG.NextSingle(-2f, 2f); + p.EndAngle = RNG.NextSingle(-2f, 2f); p.EndScale = 2f; return p; diff --git a/osu.Game/Graphics/Particles/ParticleSpewer.cs b/osu.Game/Graphics/Particles/ParticleSpewer.cs index f9fb30abdc..3d2225d382 100644 --- a/osu.Game/Graphics/Particles/ParticleSpewer.cs +++ b/osu.Game/Graphics/Particles/ParticleSpewer.cs @@ -177,14 +177,15 @@ namespace osu.Game.Graphics.Particles public Vector2 StartPosition; public Vector2 Velocity; public float Duration; - public float AngularVelocity; + public float StartAngle; + public float EndAngle; public float EndScale; public float AlphaAtTime(float timeSinceStart) => 1 - progressAtTime(timeSinceStart); public float ScaleAtTime(float timeSinceStart) => 1 + (EndScale - 1) * progressAtTime(timeSinceStart); - public float AngleAtTime(float timeSinceStart) => AngularVelocity / 1000 * timeSinceStart; + public float AngleAtTime(float timeSinceStart) => StartAngle + (EndAngle - StartAngle) * progressAtTime(timeSinceStart); public Vector2 PositionAtTime(float timeSinceStart, float gravity) { From 99eff4f41f5e5d126fc165fed86309e98ed05cfd Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Thu, 9 Sep 2021 23:18:19 +0200 Subject: [PATCH 10/36] Move cursor particles under `OsuCursorContainer` --- .../Skinning/Legacy/LegacyCursor.cs | 5 -- .../Legacy/LegacyCursorStarParticles.cs | 71 ++++++++++--------- .../UI/Cursor/OsuCursorContainer.cs | 11 ++- osu.Game/Graphics/Particles/ParticleSpewer.cs | 27 ++----- 4 files changed, 53 insertions(+), 61 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs index cd7954a8d6..b2ffc171be 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs @@ -31,11 +31,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy InternalChildren = new[] { - new LegacyCursorStarParticles - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, ExpandTarget = new NonPlayfieldSprite { Texture = skin.GetTexture("cursor"), diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs index 9c901a7de5..89ffa4ca15 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs @@ -4,11 +4,13 @@ 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; using osu.Framework.Utils; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Particles; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; @@ -18,7 +20,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacyCursorStarParticles : BeatSyncedContainer, IKeyBindingHandler + public class LegacyCursorStarParticles : CompositeDrawable, IKeyBindingHandler { private StarParticleSpewer breakSpewer; private StarParticleSpewer kiaiSpewer; @@ -64,12 +66,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { breakSpewer.Active.BindTarget = player.IsBreakTime; } - - if (osuPlayfield != null) - { - breakSpewer.ParticleParent = osuPlayfield; - kiaiSpewer.ParticleParent = osuPlayfield; - } } protected override void Update() @@ -116,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } if (leftPressed && rightPressed) - breakSpewer.Direction = SpewDirection.Both; + breakSpewer.Direction = SpewDirection.Omni; else if (leftPressed) breakSpewer.Direction = SpewDirection.Left; else if (rightPressed) @@ -125,13 +121,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy breakSpewer.Direction = SpewDirection.None; } - private class StarParticleSpewer : ParticleSpewer + private class StarParticleSpewer : 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 StarParticleSpewer(Texture texture, int perSecond) @@ -140,48 +137,52 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Active.BindValueChanged(_ => resetVelocityCalculation()); } - private Vector2 positionInParent => ToSpaceOfOtherDrawable(OriginPosition, ParticleParent); + private Vector2? cursorScreenPosition; + private Vector2 cursorVelocity; - private Vector2 screenVelocity; + private const double max_velocity_frame_length = 15; + private double velocityFrameLength; + private Vector2 totalPosDifference; - private const double velocity_calculation_delay = 15; - private double lastVelocityCalculation; - private Vector2 positionDifference; - private Vector2? lastPosition; - - protected override void Update() + protected override bool OnMouseMove(MouseMoveEvent e) { - base.Update(); - - if (lastPosition != null) + if (cursorScreenPosition == null) { - positionDifference += (positionInParent - lastPosition.Value); - lastVelocityCalculation += Clock.ElapsedFrameTime; + cursorScreenPosition = e.ScreenSpaceMousePosition; + return base.OnMouseMove(e); } - lastPosition = positionInParent; + // calculate cursor velocity. + totalPosDifference += e.ScreenSpaceMousePosition - cursorScreenPosition.Value; + cursorScreenPosition = e.ScreenSpaceMousePosition; - if (lastVelocityCalculation > velocity_calculation_delay) + velocityFrameLength += Clock.ElapsedFrameTime; + + if (velocityFrameLength > max_velocity_frame_length) { - screenVelocity = positionDifference / (float)lastVelocityCalculation; + cursorVelocity = totalPosDifference / (float)velocityFrameLength; - positionDifference = Vector2.Zero; - lastVelocityCalculation = 0; + totalPosDifference = Vector2.Zero; + velocityFrameLength = 0; } + + return base.OnMouseMove(e); } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + private void resetVelocityCalculation() { - positionDifference = Vector2.Zero; - lastVelocityCalculation = 0; - lastPosition = null; + cursorScreenPosition = null; + totalPosDifference = Vector2.Zero; + velocityFrameLength = 0; } protected override FallingParticle SpawnParticle() { var p = base.SpawnParticle(); - p.StartPosition = positionInParent; + p.StartPosition = ToLocalSpace(cursorScreenPosition ?? Vector2.Zero); p.Duration = RNG.NextSingle(particle_lifetime_min, particle_lifetime_max); p.StartAngle = (float)(RNG.NextDouble() * 4 - 2); p.EndAngle = RNG.NextSingle(-2f, 2f); @@ -207,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy ); break; - case SpewDirection.Both: + case SpewDirection.Omni: p.Velocity = new Vector2( RNG.NextSingle(-460f, 460f), RNG.NextSingle(-160f, 160f) @@ -215,7 +216,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy break; } - p.Velocity += screenVelocity * 40; + p.Velocity += cursorVelocity * 40; return p; } @@ -226,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy None, Left, Right, - Both, + Omni, } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 5812e8cf75..fbb7bfd7b1 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -42,7 +43,15 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor InternalChild = fadeContainer = new Container { RelativeSizeAxes = Axes.Both, - Child = cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling) + Children = new[] + { + cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling), + new LegacyCursorStarParticles() + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } }; } diff --git a/osu.Game/Graphics/Particles/ParticleSpewer.cs b/osu.Game/Graphics/Particles/ParticleSpewer.cs index 3d2225d382..f0c9dff239 100644 --- a/osu.Game/Graphics/Particles/ParticleSpewer.cs +++ b/osu.Game/Graphics/Particles/ParticleSpewer.cs @@ -26,22 +26,16 @@ namespace osu.Game.Graphics.Particles /// public readonly BindableBool Active = new BindableBool(); - /// - /// whose DrawInfo will be used to draw each particle. - /// Defaults to the itself. - /// - public IDrawable ParticleParent; - public bool HasActiveParticles => Active.Value || (lastParticleAdded + maxLifetime) > Time.Current; public override bool IsPresent => base.IsPresent && HasActiveParticles; + protected virtual bool CanSpawnParticles => true; protected virtual float ParticleGravity => 0; protected ParticleSpewer(Texture texture, int perSecond, double maxLifetime) { Texture = texture; Blending = BlendingParameters.Additive; - ParticleParent = this; particles = new FallingParticle[perSecond * (int)Math.Ceiling(maxLifetime / 1000)]; @@ -57,9 +51,12 @@ namespace osu.Game.Graphics.Particles // this can happen when seeking in replays. if (lastParticleAdded > Time.Current) lastParticleAdded = 0; - if (Active.Value && Time.Current > lastParticleAdded + cooldown) + if (Active.Value && CanSpawnParticles && Time.Current > lastParticleAdded + cooldown) { - addParticle(SpawnParticle()); + particles[currentIndex] = SpawnParticle(); + + currentIndex = (currentIndex + 1) % particles.Length; + lastParticleAdded = Time.Current; } Invalidate(Invalidation.DrawNode); @@ -76,14 +73,6 @@ namespace osu.Game.Graphics.Particles }; } - private void addParticle(FallingParticle fallingParticle) - { - particles[currentIndex] = fallingParticle; - - currentIndex = (currentIndex + 1) % particles.Length; - lastParticleAdded = Time.Current; - } - protected override DrawNode CreateDrawNode() => new ParticleSpewerDrawNode(this); # region DrawNode @@ -96,7 +85,6 @@ namespace osu.Game.Graphics.Particles private float currentTime; private float gravity; - private Matrix3 particleDrawMatrix; public ParticleSpewerDrawNode(Sprite source) : base(source) @@ -112,7 +100,6 @@ namespace osu.Game.Graphics.Particles currentTime = (float)Source.Time.Current; gravity = Source.ParticleGravity; - particleDrawMatrix = Source.ParticleParent.DrawInfo.Matrix; } protected override void Blit(Action vertexAction) @@ -165,7 +152,7 @@ namespace osu.Game.Graphics.Particles float x = centre.X + (pos.X - centre.X) * cos + (pos.Y - centre.Y) * sin; float y = centre.Y + (pos.Y - centre.Y) * cos - (pos.X - centre.X) * sin; - return Vector2Extensions.Transform(new Vector2(x, y), particleDrawMatrix); + return Vector2Extensions.Transform(new Vector2(x, y), DrawInfo.Matrix); } } From cfcb46034c51bc4a95186f4e304e8b84294706fa Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Fri, 10 Sep 2021 00:02:37 +0200 Subject: [PATCH 11/36] Remove `ParticleJet` --- .../Visual/Gameplay/TestSceneParticleJet.cs | 61 ------------ .../Gameplay/TestSceneParticleSpewer.cs | 94 +++++++++++++++++++ osu.Game/Graphics/Particles/ParticleJet.cs | 48 ---------- 3 files changed, 94 insertions(+), 109 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneParticleJet.cs create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs delete mode 100644 osu.Game/Graphics/Particles/ParticleJet.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleJet.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleJet.cs deleted file mode 100644 index e570abcf88..0000000000 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleJet.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Testing; -using osu.Game.Graphics.Particles; -using osu.Game.Skinning; - -namespace osu.Game.Tests.Visual.Gameplay -{ - [TestFixture] - public class TestSceneParticleJet : OsuTestScene - { - private ParticleJet jet; - - [Resolved] - private SkinManager skinManager { get; set; } - - public TestSceneParticleJet() - { - AddStep("create", () => - { - Child = jet = createJet(); - }); - - AddToggleStep("toggle spawning", value => jet.Active.Value = value); - } - - [SetUpSteps] - public void SetUpSteps() - { - AddStep("create jet", () => Child = jet = createJet()); - } - - [Test] - public void TestPresence() - { - AddStep("start jet", () => jet.Active.Value = true); - AddAssert("is present", () => jet.IsPresent); - - AddWaitStep("wait for some particles", 3); - AddStep("stop jet", () => jet.Active.Value = false); - - AddWaitStep("wait for clean screen", 5); - AddAssert("is not present", () => !jet.IsPresent); - } - - private ParticleJet createJet() - { - return new ParticleJet(skinManager.DefaultLegacySkin.GetTexture("star2"), 180) - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativePositionAxes = Axes.Y, - Y = -0.1f, - }; - } - } -} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs new file mode 100644 index 0000000000..3a59374c98 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +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.Particles; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [TestFixture] + public class TestSceneParticleSpewer : OsuTestScene + { + private TestParticleSpewer spewer; + + [Resolved] + private SkinManager skinManager { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + Child = spewer = createSpewer(); + + AddToggleStep("toggle spawning", value => spewer.Active.Value = value); + AddSliderStep("particle gravity", 0f, 250f, 0f, value => spewer.Gravity = value); + AddSliderStep("particle velocity", 0f, 500f, 250f, value => spewer.MaxVelocity = value); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create jet", () => Child = spewer = createSpewer()); + } + + [Test] + public void TestPresence() + { + AddStep("start jet", () => spewer.Active.Value = true); + AddAssert("is present", () => spewer.IsPresent); + + AddWaitStep("wait for some particles", 3); + AddStep("stop jet", () => spewer.Active.Value = false); + + AddWaitStep("wait for clean screen", 8); + AddAssert("is not present", () => !spewer.IsPresent); + } + + private TestParticleSpewer createSpewer() + { + return new TestParticleSpewer(skinManager.DefaultLegacySkin.GetTexture("star2")) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + + private class TestParticleSpewer : ParticleSpewer + { + private const int lifetime = 1500; + private const int rate = 250; + + public float Gravity = 0; + public float MaxVelocity = 250; + + protected override float ParticleGravity => Gravity; + + public TestParticleSpewer(Texture texture) + : base(texture, rate, lifetime) + { + } + + protected override FallingParticle SpawnParticle() + { + var p = base.SpawnParticle(); + p.Velocity = new Vector2( + RNG.NextSingle(-MaxVelocity, MaxVelocity), + RNG.NextSingle(-MaxVelocity, MaxVelocity) + ); + p.Duration = RNG.NextSingle(lifetime); + p.StartAngle = RNG.NextSingle(MathF.PI * 2); + p.EndAngle = RNG.NextSingle(MathF.PI * 2); + p.EndScale = RNG.NextSingle(0.5f, 1.5f); + + return p; + } + } + } +} diff --git a/osu.Game/Graphics/Particles/ParticleJet.cs b/osu.Game/Graphics/Particles/ParticleJet.cs deleted file mode 100644 index a76aa6b75f..0000000000 --- a/osu.Game/Graphics/Particles/ParticleJet.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Graphics.Textures; -using osu.Framework.Utils; -using osuTK; - -namespace osu.Game.Graphics.Particles -{ - public class ParticleJet : ParticleSpewer - { - private const int particles_per_second = 80; - private const double particle_lifetime = 500; - private const int angle_spread = 10; - private const float velocity_min = 1300f; - private const float velocity_max = 1500f; - - private readonly int angle; - - protected override float ParticleGravity => 750f; - - public ParticleJet(Texture texture, int angle) - : base(texture, particles_per_second, particle_lifetime) - { - this.angle = angle; - } - - protected override FallingParticle SpawnParticle() - { - var p = base.SpawnParticle(); - - var directionRads = MathUtils.DegreesToRadians( - RNG.NextSingle(angle - angle_spread / 2, angle + angle_spread / 2) - ); - var direction = new Vector2(MathF.Sin(directionRads), MathF.Cos(directionRads)); - - p.StartPosition = OriginPosition; - p.Duration = RNG.NextSingle((float)particle_lifetime * 0.8f, (float)particle_lifetime); - p.Velocity = direction * new Vector2(RNG.NextSingle(velocity_min, velocity_max)); - p.StartAngle = RNG.NextSingle(-2f, 2f); - p.EndAngle = RNG.NextSingle(-2f, 2f); - p.EndScale = 2f; - - return p; - } - } -} From 8862d3fa1ea67d42c54dda8710206f036aed6436 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Fri, 10 Sep 2021 00:29:05 +0200 Subject: [PATCH 12/36] Add `OsuSkinComponents.CursorParticles` --- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 1 + .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 3 +++ osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs | 7 +------ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 46e501758b..484d1c6753 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu FollowPoint, Cursor, CursorTrail, + CursorParticles, SliderScorePoint, ReverseArrow, HitCircleText, diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 41b0a88f11..0887e4d1d3 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -89,6 +89,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; + case OsuSkinComponents.CursorParticles: + return new LegacyCursorStarParticles(); + case OsuSkinComponents.HitCircleText: if (!this.HasFont(LegacyFont.HitCircle)) return null; diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index fbb7bfd7b1..463b1f4eaf 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -11,7 +11,6 @@ using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Configuration; -using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -46,11 +45,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Children = new[] { cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling), - new LegacyCursorStarParticles() - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, + new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorParticles), confineMode: ConfineMode.NoScaling), } }; } From 911282234e9a0944caec1af51c6c544e28927edf Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Fri, 10 Sep 2021 00:30:58 +0200 Subject: [PATCH 13/36] Rename legacy cursor particle classes --- ...orStarParticles.cs => LegacyCursorParticles.cs} | 14 +++++++------- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game.Rulesets.Osu/Skinning/Legacy/{LegacyCursorStarParticles.cs => LegacyCursorParticles.cs} (93%) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs similarity index 93% rename from osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs rename to osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index 89ffa4ca15..876af29e88 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorStarParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -20,10 +20,10 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacyCursorStarParticles : CompositeDrawable, IKeyBindingHandler + public class LegacyCursorParticles : CompositeDrawable, IKeyBindingHandler { - private StarParticleSpewer breakSpewer; - private StarParticleSpewer kiaiSpewer; + private LegacyCursorParticleSpewer breakSpewer; + private LegacyCursorParticleSpewer kiaiSpewer; [Resolved(canBeNull: true)] private Player player { get; set; } @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy InternalChildren = new[] { - breakSpewer = new StarParticleSpewer(texture, 20) + breakSpewer = new LegacyCursorParticleSpewer(texture, 20) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Value = false, } }, - kiaiSpewer = new StarParticleSpewer(texture, 60) + kiaiSpewer = new LegacyCursorParticleSpewer(texture, 60) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy breakSpewer.Direction = SpewDirection.None; } - private class StarParticleSpewer : ParticleSpewer, IRequireHighFrequencyMousePosition + private class LegacyCursorParticleSpewer : ParticleSpewer, IRequireHighFrequencyMousePosition { private const int particle_lifetime_min = 300; private const int particle_lifetime_max = 1000; @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected override bool CanSpawnParticles => base.CanSpawnParticles && cursorScreenPosition.HasValue; protected override float ParticleGravity => 240; - public StarParticleSpewer(Texture texture, int perSecond) + public LegacyCursorParticleSpewer(Texture texture, int perSecond) : base(texture, perSecond, particle_lifetime_max) { Active.BindValueChanged(_ => resetVelocityCalculation()); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 0887e4d1d3..20c432b298 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; case OsuSkinComponents.CursorParticles: - return new LegacyCursorStarParticles(); + return new LegacyCursorParticles(); case OsuSkinComponents.HitCircleText: if (!this.HasFont(LegacyFont.HitCircle)) From a688e69859262529d668ad6db5758d58d29aa8f6 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sat, 11 Sep 2021 01:55:16 +0200 Subject: [PATCH 14/36] Scale down cursor particles --- .../Skinning/Legacy/LegacyCursorParticles.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index 876af29e88..f8b5ab97df 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -35,6 +35,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void load(ISkinSource skin, OsuColour colour) { var texture = skin.GetTexture("star2"); + if (texture == null) + return; + + texture.ScaleAdjust = 3.2f; InternalChildren = new[] { From 82d16ab394e747a333b47a9ac725d8152b94ce07 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 12 Sep 2021 23:45:50 +0200 Subject: [PATCH 15/36] Fix `LegacyCursorParticles` texture null reference --- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 20c432b298..02a67ea44f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -90,7 +90,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; case OsuSkinComponents.CursorParticles: - return new LegacyCursorParticles(); + if (GetTexture("star2") != null) + return new LegacyCursorParticles(); + + return null; case OsuSkinComponents.HitCircleText: if (!this.HasFont(LegacyFont.HitCircle)) From 16f98357e621260b5a7e9107055e391e2c453a62 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 12 Sep 2021 23:53:41 +0200 Subject: [PATCH 16/36] Add cursor particles tests --- .../TestSceneCursorParticles.cs | 174 ++++++++++++++++++ .../Skinning/Legacy/LegacyCursorParticles.cs | 8 +- 2 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs new file mode 100644 index 0000000000..11b1f5b2af --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs @@ -0,0 +1,174 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Legacy; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Skinning; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneCursorParticles : TestSceneOsuPlayer + { + protected override bool Autoplay => autoplay; + protected override bool HasCustomSteps => true; + + private bool autoplay; + private IBeatmap currentBeatmap; + + [Resolved] + private SkinManager skinManager { get; set; } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentBeatmap ?? base.CreateBeatmap(ruleset); + + [Test] + public void TestLegacyBreakParticles() + { + LegacyCursorParticles cursorParticles = null; + + createLegacyTest(false, () => new Beatmap + { + Breaks = + { + new BreakPeriod(8500, 10000), + }, + HitObjects = + { + new HitCircle + { + StartTime = 8000, + Position = OsuPlayfield.BASE_SIZE / 2, + }, + new HitCircle + { + StartTime = 11000, + Position = OsuPlayfield.BASE_SIZE / 2, + }, + } + }); + + AddUntilStep("fetch cursor particles", () => + { + cursorParticles = this.ChildrenOfType().SingleOrDefault(); + return cursorParticles != null; + }); + + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre)); + + AddAssert("particles are being spawned", () => cursorParticles.Active); + + AddStep("press left mouse button", () => InputManager.PressButton(MouseButton.Left)); + AddWaitStep("wait a bit", 5); + AddStep("press right mouse button", () => InputManager.PressButton(MouseButton.Right)); + AddWaitStep("wait a bit", 5); + AddStep("release left mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); + AddWaitStep("wait a bit", 5); + AddStep("release right mouse button", () => InputManager.ReleaseButton(MouseButton.Right)); + + AddUntilStep("wait for beatmap start", () => !Player.IsBreakTime.Value); + AddAssert("particle spawning stopped", () => !cursorParticles.Active); + + AddUntilStep("wait for break", () => Player.IsBreakTime.Value); + AddAssert("particles are being spawned", () => cursorParticles.Active); + + AddUntilStep("wait for break end", () => !Player.IsBreakTime.Value); + AddAssert("particle spawning stopped", () => !cursorParticles.Active); + } + + [Test] + public void TestLegacyKiaiParticles() + { + LegacyCursorParticles cursorParticles = null; + DrawableSpinner spinner = null; + DrawableSlider slider = null; + + createLegacyTest(true, () => + { + var controlPointInfo = new ControlPointInfo(); + controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); + + return new Beatmap + { + ControlPointInfo = controlPointInfo, + HitObjects = + { + new Spinner + { + StartTime = 0, + Duration = 1000, + Position = OsuPlayfield.BASE_SIZE / 2, + }, + new Slider + { + StartTime = 2500, + RepeatCount = 0, + Position = OsuPlayfield.BASE_SIZE / 2, + Path = new SliderPath(new[] + { + new PathControlPoint(Vector2.Zero), + new PathControlPoint(new Vector2(100, 0)), + }) + }, + new HitCircle + { + StartTime = 4500, + Position = OsuPlayfield.BASE_SIZE / 2, + }, + }, + }; + } + ); + + AddUntilStep("fetch cursor particles", () => + { + cursorParticles = this.ChildrenOfType().SingleOrDefault(); + return cursorParticles != null; + }); + + AddUntilStep("wait for spinner tracking", () => + { + spinner = this.ChildrenOfType().SingleOrDefault(); + return spinner?.RotationTracker.Tracking == true; + }); + AddAssert("particles are being spawned", () => cursorParticles.Active); + + AddUntilStep("spinner tracking stopped", () => !spinner.RotationTracker.Tracking); + AddAssert("particle spawning stopped", () => !cursorParticles.Active); + + AddUntilStep("wait for slider tracking", () => + { + slider = this.ChildrenOfType().SingleOrDefault(); + return slider?.Tracking.Value == true; + }); + AddAssert("particles are being spawned", () => cursorParticles.Active); + + AddUntilStep("slider tracking stopped", () => !slider.Tracking.Value); + AddAssert("particle spawning stopped", () => !cursorParticles.Active); + } + + private void createLegacyTest(bool autoplay, Func beatmap) => CreateTest(() => + { + AddStep("set beatmap", () => + { + this.autoplay = autoplay; + currentBeatmap = beatmap(); + }); + AddStep("setup default legacy skin", () => + { + skinManager.CurrentSkinInfo.Value = skinManager.DefaultLegacySkin.SkinInfo; + }); + }); + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index f8b5ab97df..2080b1a3ce 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class LegacyCursorParticles : CompositeDrawable, IKeyBindingHandler { + public bool Active => breakSpewer?.Active.Value == true || kiaiSpewer?.Active.Value == true; + private LegacyCursorParticleSpewer breakSpewer; private LegacyCursorParticleSpewer kiaiSpewer; @@ -32,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private OsuPlayfield osuPlayfield { get; set; } [BackgroundDependencyLoader] - private void load(ISkinSource skin, OsuColour colour) + private void load(ISkinSource skin, OsuColour colours) { var texture = skin.GetTexture("star2"); if (texture == null) @@ -46,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = colour.PinkLighter, + Colour = colours.PinkLighter, Direction = SpewDirection.None, Active = { @@ -57,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = colour.PinkLighter, + Colour = colours.PinkLighter, Direction = SpewDirection.None, Active = { From 7327603fc834de3cbb32b29e5a511d1df8061107 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Mon, 13 Sep 2021 00:05:49 +0200 Subject: [PATCH 17/36] Fix outdated test step description --- osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs index 3a59374c98..e58bbb737e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs @@ -35,17 +35,17 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUpSteps] public void SetUpSteps() { - AddStep("create jet", () => Child = spewer = createSpewer()); + AddStep("create spewer", () => Child = spewer = createSpewer()); } [Test] public void TestPresence() { - AddStep("start jet", () => spewer.Active.Value = true); + AddStep("start spewer", () => spewer.Active.Value = true); AddAssert("is present", () => spewer.IsPresent); AddWaitStep("wait for some particles", 3); - AddStep("stop jet", () => spewer.Active.Value = false); + AddStep("stop spewer", () => spewer.Active.Value = false); AddWaitStep("wait for clean screen", 8); AddAssert("is not present", () => !spewer.IsPresent); From 224244801f35ed2b031c9be907cd891181b5da97 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Mon, 13 Sep 2021 00:35:13 +0200 Subject: [PATCH 18/36] Remove Particles namespace --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs | 1 - osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs | 2 +- osu.Game/Graphics/{Particles => }/ParticleSpewer.cs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) rename osu.Game/Graphics/{Particles => }/ParticleSpewer.cs (99%) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index 2080b1a3ce..95c2a6b930 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -11,7 +11,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics; -using osu.Game.Graphics.Particles; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Play; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs index e58bbb737e..fe14869e9a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.Testing; using osu.Framework.Utils; -using osu.Game.Graphics.Particles; +using osu.Game.Graphics; using osu.Game.Skinning; using osuTK; diff --git a/osu.Game/Graphics/Particles/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs similarity index 99% rename from osu.Game/Graphics/Particles/ParticleSpewer.cs rename to osu.Game/Graphics/ParticleSpewer.cs index f0c9dff239..544b2bd2ed 100644 --- a/osu.Game/Graphics/Particles/ParticleSpewer.cs +++ b/osu.Game/Graphics/ParticleSpewer.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osuTK; -namespace osu.Game.Graphics.Particles +namespace osu.Game.Graphics { public abstract class ParticleSpewer : Sprite { From b009772206a4ffeebb2b7d36dad890ec655c2ff6 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Mon, 13 Sep 2021 00:45:36 +0200 Subject: [PATCH 19/36] Fix code inspect failure --- osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs index fe14869e9a..e6b5763f2c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.Gameplay private const int lifetime = 1500; private const int rate = 250; - public float Gravity = 0; + public float Gravity; public float MaxVelocity = 250; protected override float ParticleGravity => Gravity; From 9fd616c578107ab375fa102ab313a6d5f40a34cf Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Tue, 14 Sep 2021 00:16:42 +0200 Subject: [PATCH 20/36] Tiny refactor --- .../Skinning/Legacy/LegacyCursorParticles.cs | 4 ++-- osu.Game/Graphics/ParticleSpewer.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index 95c2a6b930..8dc486285b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -149,6 +149,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private double velocityFrameLength; private Vector2 totalPosDifference; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + protected override bool OnMouseMove(MouseMoveEvent e) { if (cursorScreenPosition == null) @@ -174,8 +176,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return base.OnMouseMove(e); } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - private void resetVelocityCalculation() { cursorScreenPosition = null; diff --git a/osu.Game/Graphics/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs index 544b2bd2ed..8b82dfb7c6 100644 --- a/osu.Game/Graphics/ParticleSpewer.cs +++ b/osu.Game/Graphics/ParticleSpewer.cs @@ -132,10 +132,10 @@ namespace osu.Game.Graphics height); var quad = new Quad( - rotatePosition(rect.TopLeft, rect.Centre, angle), - rotatePosition(rect.TopRight, rect.Centre, angle), - rotatePosition(rect.BottomLeft, rect.Centre, angle), - rotatePosition(rect.BottomRight, rect.Centre, angle) + transformPosition(rect.TopLeft, rect.Centre, angle), + transformPosition(rect.TopRight, rect.Centre, angle), + transformPosition(rect.BottomLeft, rect.Centre, angle), + transformPosition(rect.BottomRight, rect.Centre, angle) ); DrawQuad(Texture, quad, DrawColourInfo.Colour.MultiplyAlpha(alpha), null, vertexAction, @@ -144,7 +144,7 @@ namespace osu.Game.Graphics } } - private Vector2 rotatePosition(Vector2 pos, Vector2 centre, float angle) + private Vector2 transformPosition(Vector2 pos, Vector2 centre, float angle) { float cos = MathF.Cos(angle); float sin = MathF.Sin(angle); From c4886be7e1132ed189dbc400db3930cb3c218b11 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Tue, 14 Sep 2021 00:36:01 +0200 Subject: [PATCH 21/36] Add `StarBreakAdditive` config support --- .../Skinning/Legacy/LegacyCursorParticles.cs | 7 +++++-- osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index 8dc486285b..a2ee9afff1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -36,6 +37,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void load(ISkinSource skin, OsuColour colours) { var texture = skin.GetTexture("star2"); + var starBreakAdditive = skin.GetConfig(OsuSkinColour.StarBreakAdditive)?.Value ?? new Color4(255, 182, 193, 255); + if (texture == null) return; @@ -47,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = colours.PinkLighter, + Colour = starBreakAdditive, Direction = SpewDirection.None, Active = { @@ -58,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = colours.PinkLighter, + Colour = starBreakAdditive, Direction = SpewDirection.None, Active = { diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs index f7ba8b9fc4..24f9217a5f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs @@ -9,5 +9,6 @@ namespace osu.Game.Rulesets.Osu.Skinning SliderBorder, SliderBall, SpinnerBackground, + StarBreakAdditive, } } From d13ff12a3e4cb0218f1aa1560b9439354aea0353 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Tue, 14 Sep 2021 00:36:52 +0200 Subject: [PATCH 22/36] Remove hardcoded particle scale --- .../Skinning/Legacy/LegacyCursorParticles.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index a2ee9afff1..ccccd1810c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -39,11 +39,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy var texture = skin.GetTexture("star2"); var starBreakAdditive = skin.GetConfig(OsuSkinColour.StarBreakAdditive)?.Value ?? new Color4(255, 182, 193, 255); - if (texture == null) - return; - - texture.ScaleAdjust = 3.2f; - InternalChildren = new[] { breakSpewer = new LegacyCursorParticleSpewer(texture, 20) From 32d65adb35c225dac77a43604012f0d426293508 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Wed, 15 Sep 2021 21:22:37 +0200 Subject: [PATCH 23/36] Fix cursor particle scale --- .../Skinning/Legacy/LegacyCursorParticles.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index ccccd1810c..73820b8df9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -39,6 +39,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy var texture = skin.GetTexture("star2"); var starBreakAdditive = skin.GetConfig(OsuSkinColour.StarBreakAdditive)?.Value ?? new Color4(255, 182, 193, 255); + if (texture != null) + { + // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation. + texture.ScaleAdjust *= 1.6f; + } + InternalChildren = new[] { breakSpewer = new LegacyCursorParticleSpewer(texture, 20) From 29ce2f05bdfeaeb317220383f608b081f39195f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Sep 2021 16:44:46 +0900 Subject: [PATCH 24/36] Remove implied defaults --- .../Skinning/Legacy/LegacyCursorParticles.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index 73820b8df9..3357508d24 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -53,10 +53,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Origin = Anchor.Centre, Colour = starBreakAdditive, Direction = SpewDirection.None, - Active = - { - Value = false, - } }, kiaiSpewer = new LegacyCursorParticleSpewer(texture, 60) { @@ -64,10 +60,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Origin = Anchor.Centre, Colour = starBreakAdditive, Direction = SpewDirection.None, - Active = - { - Value = false, - } }, }; From 2df4073946bed60dc96fe5f3e44b925af63a0a13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Sep 2021 16:50:03 +0900 Subject: [PATCH 25/36] `SpawnParticle` -> `CreateParticle` (and set time outside of `virtual` call) Allows easier overriding (no need to call the `base.CreateParticle` call and worry about overwriting the time value. --- .../Skinning/Legacy/LegacyCursorParticles.cs | 35 ++++++++++--------- .../Gameplay/TestSceneParticleSpewer.cs | 26 +++++++------- osu.Game/Graphics/ParticleSpewer.cs | 13 +++---- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index 3357508d24..858ba98b86 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -179,47 +179,48 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy velocityFrameLength = 0; } - protected override FallingParticle SpawnParticle() - { - var p = base.SpawnParticle(); + 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(), + }; - p.StartPosition = ToLocalSpace(cursorScreenPosition ?? Vector2.Zero); - p.Duration = RNG.NextSingle(particle_lifetime_min, particle_lifetime_max); - p.StartAngle = (float)(RNG.NextDouble() * 4 - 2); - p.EndAngle = RNG.NextSingle(-2f, 2f); - p.EndScale = RNG.NextSingle(2f); + private Vector2 getVelocity() + { + Vector2 velocity = Vector2.Zero; switch (Direction) { - case SpewDirection.None: - p.Velocity = Vector2.Zero; - break; - case SpewDirection.Left: - p.Velocity = new Vector2( + velocity = new Vector2( RNG.NextSingle(-460f, 0), RNG.NextSingle(-40f, 40f) ); break; case SpewDirection.Right: - p.Velocity = new Vector2( + velocity = new Vector2( RNG.NextSingle(0, 460f), RNG.NextSingle(-40f, 40f) ); break; case SpewDirection.Omni: - p.Velocity = new Vector2( + velocity = new Vector2( RNG.NextSingle(-460f, 460f), RNG.NextSingle(-160f, 160f) ); break; } - p.Velocity += cursorVelocity * 40; + velocity += cursorVelocity * 40; - return p; + return velocity; } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs index e6b5763f2c..c259718a7a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs @@ -75,20 +75,18 @@ namespace osu.Game.Tests.Visual.Gameplay { } - protected override FallingParticle SpawnParticle() - { - var p = base.SpawnParticle(); - p.Velocity = new Vector2( - RNG.NextSingle(-MaxVelocity, MaxVelocity), - RNG.NextSingle(-MaxVelocity, MaxVelocity) - ); - p.Duration = RNG.NextSingle(lifetime); - p.StartAngle = RNG.NextSingle(MathF.PI * 2); - p.EndAngle = RNG.NextSingle(MathF.PI * 2); - p.EndScale = RNG.NextSingle(0.5f, 1.5f); - - return p; - } + protected override FallingParticle CreateParticle() => + new FallingParticle + { + Velocity = new Vector2( + RNG.NextSingle(-MaxVelocity, MaxVelocity), + RNG.NextSingle(-MaxVelocity, MaxVelocity) + ), + Duration = RNG.NextSingle(lifetime), + StartAngle = RNG.NextSingle(MathF.PI * 2), + EndAngle = RNG.NextSingle(MathF.PI * 2), + EndScale = RNG.NextSingle(0.5f, 1.5f) + }; } } } diff --git a/osu.Game/Graphics/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs index 8b82dfb7c6..fc119b3ea2 100644 --- a/osu.Game/Graphics/ParticleSpewer.cs +++ b/osu.Game/Graphics/ParticleSpewer.cs @@ -53,7 +53,10 @@ namespace osu.Game.Graphics if (Active.Value && CanSpawnParticles && Time.Current > lastParticleAdded + cooldown) { - particles[currentIndex] = SpawnParticle(); + var newParticle = CreateParticle(); + newParticle.StartTime = (float)Time.Current; + + particles[currentIndex] = newParticle; currentIndex = (currentIndex + 1) % particles.Length; lastParticleAdded = Time.Current; @@ -65,13 +68,7 @@ namespace osu.Game.Graphics /// /// Called each time a new particle should be spawned. /// - protected virtual FallingParticle SpawnParticle() - { - return new FallingParticle - { - StartTime = (float)Time.Current, - }; - } + protected virtual FallingParticle CreateParticle() => new FallingParticle(); protected override DrawNode CreateDrawNode() => new ParticleSpewerDrawNode(this); From 9127a706ac2444daf504ae60fffb0dc38754fd0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Sep 2021 16:54:22 +0900 Subject: [PATCH 26/36] Use `private` for internally used property --- osu.Game/Graphics/ParticleSpewer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs index fc119b3ea2..c022fd4598 100644 --- a/osu.Game/Graphics/ParticleSpewer.cs +++ b/osu.Game/Graphics/ParticleSpewer.cs @@ -26,12 +26,13 @@ namespace osu.Game.Graphics /// public readonly BindableBool Active = new BindableBool(); - public bool HasActiveParticles => Active.Value || (lastParticleAdded + maxLifetime) > Time.Current; - public override bool IsPresent => base.IsPresent && HasActiveParticles; + public override bool IsPresent => base.IsPresent && hasActiveParticles; protected virtual bool CanSpawnParticles => true; protected virtual float ParticleGravity => 0; + private bool hasActiveParticles => Active.Value || (lastParticleAdded + maxLifetime) > Time.Current; + protected ParticleSpewer(Texture texture, int perSecond, double maxLifetime) { Texture = texture; From 846cde53b3dd7c2dd79034cb0ef2e214228c976a Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sat, 18 Sep 2021 22:54:12 +0200 Subject: [PATCH 27/36] Add `RelativePositionAxes` support --- .../Gameplay/TestSceneParticleSpewer.cs | 25 +++++++++----- osu.Game/Graphics/ParticleSpewer.cs | 33 ++++++++++++++----- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs index c259718a7a..31cc505a0d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs @@ -28,8 +28,12 @@ namespace osu.Game.Tests.Visual.Gameplay Child = spewer = createSpewer(); AddToggleStep("toggle spawning", value => spewer.Active.Value = value); - AddSliderStep("particle gravity", 0f, 250f, 0f, value => spewer.Gravity = value); - AddSliderStep("particle velocity", 0f, 500f, 250f, value => spewer.MaxVelocity = value); + AddSliderStep("particle gravity", 0f, 1f, 0f, value => spewer.Gravity = value); + AddSliderStep("particle velocity", 0f, 1f, 0.25f, value => spewer.MaxVelocity = value); + AddStep("move to new location", () => + { + spewer.TransformTo(nameof(spewer.SpawnPosition), new Vector2(RNG.NextSingle(), RNG.NextSingle()), 1000, Easing.Out); + }); } [SetUpSteps] @@ -51,14 +55,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("is not present", () => !spewer.IsPresent); } - private TestParticleSpewer createSpewer() - { - return new TestParticleSpewer(skinManager.DefaultLegacySkin.GetTexture("star2")) + private TestParticleSpewer createSpewer() => + new TestParticleSpewer(skinManager.DefaultLegacySkin.GetTexture("star2")) { - Anchor = Anchor.Centre, Origin = Anchor.Centre, + RelativePositionAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, + Position = new Vector2(0.5f), + Size = new Vector2(0.5f), }; - } private class TestParticleSpewer : ParticleSpewer { @@ -66,7 +71,10 @@ namespace osu.Game.Tests.Visual.Gameplay private const int rate = 250; public float Gravity; - public float MaxVelocity = 250; + + public float MaxVelocity = 0.25f; + + public Vector2 SpawnPosition { get; set; } = new Vector2(0.5f); protected override float ParticleGravity => Gravity; @@ -82,6 +90,7 @@ namespace osu.Game.Tests.Visual.Gameplay 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), diff --git a/osu.Game/Graphics/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs index c022fd4598..6bf9bff05a 100644 --- a/osu.Game/Graphics/ParticleSpewer.cs +++ b/osu.Game/Graphics/ParticleSpewer.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Bindables; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; @@ -83,6 +84,8 @@ namespace osu.Game.Graphics private float currentTime; private float gravity; + private Axes relativePositionAxes; + private Vector2 sourceSize; public ParticleSpewerDrawNode(Sprite source) : base(source) @@ -98,6 +101,8 @@ namespace osu.Game.Graphics currentTime = (float)Source.Time.Current; gravity = Source.ParticleGravity; + relativePositionAxes = Source.RelativePositionAxes; + sourceSize = Source.DrawSize; } protected override void Blit(Action vertexAction) @@ -116,18 +121,11 @@ namespace osu.Game.Graphics var alpha = p.AlphaAtTime(timeSinceStart); if (alpha <= 0) continue; - var scale = p.ScaleAtTime(timeSinceStart); var pos = p.PositionAtTime(timeSinceStart, gravity); + var scale = p.ScaleAtTime(timeSinceStart); var angle = p.AngleAtTime(timeSinceStart); - var width = Texture.DisplayWidth * scale; - var height = Texture.DisplayHeight * scale; - - var rect = new RectangleF( - pos.X - width / 2, - pos.Y - height / 2, - width, - height); + var rect = createDrawRect(pos, scale); var quad = new Quad( transformPosition(rect.TopLeft, rect.Centre, angle), @@ -142,6 +140,23 @@ namespace osu.Game.Graphics } } + private RectangleF createDrawRect(Vector2 position, float scale) + { + var width = Texture.DisplayWidth * scale; + var height = Texture.DisplayHeight * scale; + + if (relativePositionAxes.HasFlagFast(Axes.X)) + position.X *= sourceSize.X; + if (relativePositionAxes.HasFlagFast(Axes.Y)) + position.Y *= sourceSize.Y; + + return new RectangleF( + position.X - width / 2, + position.Y - height / 2, + width, + height); + } + private Vector2 transformPosition(Vector2 pos, Vector2 centre, float angle) { float cos = MathF.Cos(angle); From ef530ed87cc984d2d0b23e8f78ae3c464d04c984 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sat, 18 Sep 2021 23:45:58 +0200 Subject: [PATCH 28/36] Normalize particle velocity based on max duration --- .../Visual/Gameplay/TestSceneParticleSpewer.cs | 2 +- osu.Game/Graphics/ParticleSpewer.cs | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs index 31cc505a0d..086b1c2ac2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddToggleStep("toggle spawning", value => spewer.Active.Value = value); AddSliderStep("particle gravity", 0f, 1f, 0f, value => spewer.Gravity = value); - AddSliderStep("particle velocity", 0f, 1f, 0.25f, value => spewer.MaxVelocity = value); + AddSliderStep("particle velocity", 0f, 1f, 0.5f, value => spewer.MaxVelocity = value); AddStep("move to new location", () => { spewer.TransformTo(nameof(spewer.SpawnPosition), new Vector2(RNG.NextSingle(), RNG.NextSingle()), 1000, Easing.Out); diff --git a/osu.Game/Graphics/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs index 6bf9bff05a..1ad4672238 100644 --- a/osu.Game/Graphics/ParticleSpewer.cs +++ b/osu.Game/Graphics/ParticleSpewer.cs @@ -82,6 +82,8 @@ namespace osu.Game.Graphics protected new ParticleSpewer Source => (ParticleSpewer)base.Source; + private readonly float maxLifetime; + private float currentTime; private float gravity; private Axes relativePositionAxes; @@ -91,6 +93,7 @@ namespace osu.Game.Graphics : base(source) { particles = new FallingParticle[Source.particles.Length]; + maxLifetime = (float)Source.maxLifetime; } public override void ApplyState() @@ -121,7 +124,7 @@ namespace osu.Game.Graphics var alpha = p.AlphaAtTime(timeSinceStart); if (alpha <= 0) continue; - var pos = p.PositionAtTime(timeSinceStart, gravity); + var pos = p.PositionAtTime(timeSinceStart, gravity, maxLifetime); var scale = p.ScaleAtTime(timeSinceStart); var angle = p.AngleAtTime(timeSinceStart); @@ -187,12 +190,12 @@ namespace osu.Game.Graphics public float AngleAtTime(float timeSinceStart) => StartAngle + (EndAngle - StartAngle) * progressAtTime(timeSinceStart); - public Vector2 PositionAtTime(float timeSinceStart, float gravity) + public Vector2 PositionAtTime(float timeSinceStart, float gravity, float maxDuration) { var progress = progressAtTime(timeSinceStart); - var currentGravity = new Vector2(0, gravity * Duration / 1000 * progress); + var currentGravity = new Vector2(0, gravity * Duration / maxDuration * progress); - return StartPosition + (Velocity + currentGravity) * timeSinceStart / 1000; + return StartPosition + (Velocity + currentGravity) * timeSinceStart / maxDuration; } private float progressAtTime(float timeSinceStart) => Math.Clamp(timeSinceStart / Duration, 0, 1); From 3f8454cb76cc5f2d0c52f8270a27a843bab366eb Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 19 Sep 2021 03:19:16 +0200 Subject: [PATCH 29/36] Remove abstract from `ParticleSpewer` --- .../Skinning/Legacy/LegacyCursorParticles.cs | 218 ++++++++---------- .../Gameplay/TestSceneParticleSpewer.cs | 62 ++--- osu.Game/Graphics/ParticleSpewer.cs | 48 ++-- 3 files changed, 144 insertions(+), 184 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index 858ba98b86..d4e5bdd46f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -1,11 +1,11 @@ // Copyright (c) ppy Pty Ltd . 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 + public class LegacyCursorParticles : CompositeDrawable, IKeyBindingHandler, 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; } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs index 086b1c2ac2..390534745d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs @@ -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) + }; } } diff --git a/osu.Game/Graphics/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs index 1ad4672238..911f5894e7 100644 --- a/osu.Game/Graphics/ParticleSpewer.cs +++ b/osu.Game/Graphics/ParticleSpewer.cs @@ -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; /// /// 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; + /// + /// Called each time a new particle should be spawned. + /// + public Func 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); } - /// - /// Called each time a new particle should be spawned. - /// - 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; From af4c3727d77a16e2534df9bbf452336b5c544342 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 19 Sep 2021 04:39:35 +0200 Subject: [PATCH 30/36] Fix build errors --- .../Skinning/Legacy/LegacyCursorParticles.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index d4e5bdd46f..ba5a03c1f1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -127,15 +127,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return base.OnMouseMove(e); } - public bool OnPressed(OsuAction action) + public bool OnPressed(KeyBindingPressEvent e) { - handleInput(action, true); + handleInput(e.Action, true); return false; } - public void OnReleased(OsuAction action) + public void OnReleased(KeyBindingReleaseEvent e) { - handleInput(action, false); + handleInput(e.Action, false); } private bool leftPressed; From 761da45f6a41b30d90931819e168ea1204571d99 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 19 Sep 2021 14:00:56 +0200 Subject: [PATCH 31/36] Revert `af4c3727d77a16e2534df9bbf452336b5c544342` --- .idea/.idea.osu/.idea/indexLayout.xml | 2 +- .../Skinning/Legacy/LegacyCursorParticles.cs | 217 ++++++++++-------- .../Gameplay/TestSceneParticleSpewer.cs | 62 +++-- osu.Game/Graphics/ParticleSpewer.cs | 34 ++- 4 files changed, 178 insertions(+), 137 deletions(-) diff --git a/.idea/.idea.osu/.idea/indexLayout.xml b/.idea/.idea.osu/.idea/indexLayout.xml index 27ba142e96..7b08163ceb 100644 --- a/.idea/.idea.osu/.idea/indexLayout.xml +++ b/.idea/.idea.osu/.idea/indexLayout.xml @@ -1,6 +1,6 @@ - + diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index ba5a03c1f1..e1b7dbc3e3 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -6,6 +6,7 @@ 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,17 +21,12 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacyCursorParticles : CompositeDrawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition + public class LegacyCursorParticles : CompositeDrawable, IKeyBindingHandler { - 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 Vector2 cursorVelocity; - private ParticleSpewer breakSpewer; - private ParticleSpewer kiaiSpewer; + private LegacyCursorParticleSpewer breakSpewer; + private LegacyCursorParticleSpewer kiaiSpewer; [Resolved(canBeNull: true)] private Player player { get; set; } @@ -50,25 +46,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy texture.ScaleAdjust *= 1.6f; } - RelativeSizeAxes = Axes.Both; - Anchor = Anchor.Centre; InternalChildren = new[] { - breakSpewer = new ParticleSpewer(texture, 20, particle_lifetime_max) + breakSpewer = new LegacyCursorParticleSpewer(texture, 20) { - RelativeSizeAxes = Axes.Both, - Size = Vector2.One, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Colour = starBreakAdditive, - ParticleGravity = particle_gravity, - CreateParticle = createBreakParticle, + Direction = SpewDirection.None, }, - kiaiSpewer = new ParticleSpewer(texture, 60, particle_lifetime_max) + kiaiSpewer = new LegacyCursorParticleSpewer(texture, 60) { - RelativeSizeAxes = Axes.Both, - Size = Vector2.One, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Colour = starBreakAdditive, - ParticleGravity = particle_gravity, - CreateParticle = createParticle, + Direction = SpewDirection.None, }, }; @@ -94,39 +86,6 @@ 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(KeyBindingPressEvent e) { handleInput(e.Action, true); @@ -153,53 +112,125 @@ 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) - { - p.Velocity += new Vector2( - RNG.NextSingle(-460f, 460f), - RNG.NextSingle(-160f, 160f) - ); - } + breakSpewer.Direction = SpewDirection.Omni; else if (leftPressed) - { - p.Velocity += new Vector2( - RNG.NextSingle(-460f, 0), - RNG.NextSingle(-40f, 40f) - ); - } + breakSpewer.Direction = SpewDirection.Left; else if (rightPressed) + breakSpewer.Direction = SpewDirection.Right; + else + breakSpewer.Direction = SpewDirection.None; + } + + private class LegacyCursorParticleSpewer : ParticleSpewer, IRequireHighFrequencyMousePosition + { + private const int particle_duration_min = 300; + private const int particle_duration_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_duration_max) { - p.Velocity += new Vector2( - RNG.NextSingle(0, 460f), - RNG.NextSingle(-40f, 40f) - ); + Active.BindValueChanged(_ => resetVelocityCalculation()); } - return p; + 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 += Math.Abs(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_duration_min, particle_duration_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, } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs index 390534745d..2f107c8300 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs @@ -5,6 +5,7 @@ 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; @@ -16,12 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneParticleSpewer : OsuTestScene { - private const int max_particle_duration = 1500; - - private float particleMaxVelocity = 0.5f; - private Vector2 particleSpawnPosition = new Vector2(0.5f); - - private ParticleSpewer spewer; + private TestParticleSpewer spewer; [Resolved] private SkinManager skinManager { get; set; } @@ -32,11 +28,11 @@ namespace osu.Game.Tests.Visual.Gameplay Child = spewer = createSpewer(); AddToggleStep("toggle spawning", value => spewer.Active.Value = value); - AddSliderStep("particle velocity", 0f, 1f, 0.5f, value => particleMaxVelocity = value); - AddSliderStep("particle gravity", 0f, 1f, 0f, value => spewer.ParticleGravity = value); + AddSliderStep("particle gravity", 0f, 1f, 0f, value => spewer.Gravity = value); + AddSliderStep("particle velocity", 0f, 1f, 0.5f, value => spewer.MaxVelocity = value); AddStep("move to new location", () => { - this.TransformTo(nameof(particleSpawnPosition), new Vector2(RNG.NextSingle(), RNG.NextSingle()), 1000, Easing.Out); + spewer.TransformTo(nameof(spewer.SpawnPosition), new Vector2(RNG.NextSingle(), RNG.NextSingle()), 1000, Easing.Out); }); } @@ -59,29 +55,47 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("is not present", () => !spewer.IsPresent); } - private ParticleSpewer createSpewer() => - new ParticleSpewer(skinManager.DefaultLegacySkin.GetTexture("star2"), 1500, max_particle_duration) + private TestParticleSpewer createSpewer() => + new TestParticleSpewer(skinManager.DefaultLegacySkin.GetTexture("star2")) { Origin = Anchor.Centre, RelativePositionAxes = Axes.Both, RelativeSizeAxes = Axes.Both, Position = new Vector2(0.5f), Size = new Vector2(0.5f), - CreateParticle = createParticle, }; - private ParticleSpewer.FallingParticle? createParticle() => - new ParticleSpewer.FallingParticle + private class TestParticleSpewer : ParticleSpewer + { + private const int max_duration = 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, max_duration) { - 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) - }; + } + + protected override FallingParticle CreateParticle() => + new FallingParticle + { + Velocity = new Vector2( + RNG.NextSingle(-MaxVelocity, MaxVelocity), + RNG.NextSingle(-MaxVelocity, MaxVelocity) + ), + StartPosition = SpawnPosition, + Duration = RNG.NextSingle(max_duration), + StartAngle = RNG.NextSingle(MathF.PI * 2), + EndAngle = RNG.NextSingle(MathF.PI * 2), + EndScale = RNG.NextSingle(0.5f, 1.5f) + }; + } } } diff --git a/osu.Game/Graphics/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs index 911f5894e7..492e439352 100644 --- a/osu.Game/Graphics/ParticleSpewer.cs +++ b/osu.Game/Graphics/ParticleSpewer.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Graphics { - public class ParticleSpewer : Sprite + public abstract class ParticleSpewer : Sprite { private readonly FallingParticle[] particles; private int currentIndex; @@ -29,16 +29,12 @@ namespace osu.Game.Graphics public override bool IsPresent => base.IsPresent && hasActiveParticles; - /// - /// Called each time a new particle should be spawned. - /// - public Func CreateParticle = () => new FallingParticle(); - - public float ParticleGravity; + protected virtual bool CanSpawnParticles => true; + protected virtual float ParticleGravity => 0; private bool hasActiveParticles => Active.Value || (lastParticleAdded + maxDuration) > Time.Current; - public ParticleSpewer(Texture texture, int perSecond, double maxDuration) + protected ParticleSpewer(Texture texture, int perSecond, double maxDuration) { Texture = texture; Blending = BlendingParameters.Additive; @@ -57,25 +53,25 @@ namespace osu.Game.Graphics // this can happen when seeking in replays. if (lastParticleAdded > Time.Current) lastParticleAdded = 0; - if (Active.Value && Time.Current > lastParticleAdded + cooldown) + if (Active.Value && CanSpawnParticles && Time.Current > lastParticleAdded + cooldown) { var newParticle = CreateParticle(); + newParticle.StartTime = (float)Time.Current; - if (newParticle.HasValue) - { - var particle = newParticle.Value; - particle.StartTime = (float)Time.Current; + particles[currentIndex] = newParticle; - particles[currentIndex] = particle; - - currentIndex = (currentIndex + 1) % particles.Length; - lastParticleAdded = Time.Current; - } + currentIndex = (currentIndex + 1) % particles.Length; + lastParticleAdded = Time.Current; } Invalidate(Invalidation.DrawNode); } + /// + /// Called each time a new particle should be spawned. + /// + protected virtual FallingParticle CreateParticle() => new FallingParticle(); + protected override DrawNode CreateDrawNode() => new ParticleSpewerDrawNode(this); # region DrawNode @@ -178,7 +174,7 @@ namespace osu.Game.Graphics #endregion - public struct FallingParticle + protected struct FallingParticle { public float StartTime; public Vector2 StartPosition; From d5a10e922177f9cf7c7bc7f465f7c3e1d9a9807e Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 19 Sep 2021 14:47:20 +0200 Subject: [PATCH 32/36] Fix particles not spawning if `Time.Current` is negative --- osu.Game/Graphics/ParticleSpewer.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Graphics/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs index 492e439352..a52f749f4a 100644 --- a/osu.Game/Graphics/ParticleSpewer.cs +++ b/osu.Game/Graphics/ParticleSpewer.cs @@ -49,11 +49,7 @@ namespace osu.Game.Graphics { base.Update(); - // reset cooldown if the clock was rewound. - // this can happen when seeking in replays. - if (lastParticleAdded > Time.Current) lastParticleAdded = 0; - - if (Active.Value && CanSpawnParticles && Time.Current > lastParticleAdded + cooldown) + if (Active.Value && CanSpawnParticles && Math.Abs(Time.Current - lastParticleAdded) > cooldown) { var newParticle = CreateParticle(); newParticle.StartTime = (float)Time.Current; @@ -112,9 +108,6 @@ namespace osu.Game.Graphics { foreach (var p in particles) { - // ignore particles that weren't initialized. - if (p.StartTime <= 0) continue; - var timeSinceStart = currentTime - p.StartTime; // ignore particles from the future. From 0b593fac5c243438d2f0c9200f74eb7d2abdddf3 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 19 Sep 2021 14:49:09 +0200 Subject: [PATCH 33/36] Scope down DrawNode's `source` parameter --- osu.Game/Graphics/ParticleSpewer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs index a52f749f4a..90216da85a 100644 --- a/osu.Game/Graphics/ParticleSpewer.cs +++ b/osu.Game/Graphics/ParticleSpewer.cs @@ -85,7 +85,7 @@ namespace osu.Game.Graphics private Axes relativePositionAxes; private Vector2 sourceSize; - public ParticleSpewerDrawNode(Sprite source) + public ParticleSpewerDrawNode(ParticleSpewer source) : base(source) { particles = new FallingParticle[Source.particles.Length]; From 9c90dd539f3e6842cc03027cad368da5a9bb5d57 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 19 Sep 2021 15:06:15 +0200 Subject: [PATCH 34/36] Use `Interpolation.Lerp` --- osu.Game/Graphics/ParticleSpewer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs index 90216da85a..466bf04369 100644 --- a/osu.Game/Graphics/ParticleSpewer.cs +++ b/osu.Game/Graphics/ParticleSpewer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Utils; using osuTK; namespace osu.Game.Graphics @@ -179,9 +180,9 @@ namespace osu.Game.Graphics public float AlphaAtTime(float timeSinceStart) => 1 - progressAtTime(timeSinceStart); - public float ScaleAtTime(float timeSinceStart) => 1 + (EndScale - 1) * progressAtTime(timeSinceStart); + public float ScaleAtTime(float timeSinceStart) => (float)Interpolation.Lerp(1, EndScale, progressAtTime(timeSinceStart)); - public float AngleAtTime(float timeSinceStart) => StartAngle + (EndAngle - StartAngle) * progressAtTime(timeSinceStart); + public float AngleAtTime(float timeSinceStart) => (float)Interpolation.Lerp(StartAngle, EndAngle, progressAtTime(timeSinceStart)); public Vector2 PositionAtTime(float timeSinceStart, float gravity, float maxDuration) { From 366dbf5c3dbae9993b78f1f55c2ac73dd7806944 Mon Sep 17 00:00:00 2001 From: Opelkuh <25430283+Opelkuh@users.noreply.github.com> Date: Sun, 19 Sep 2021 15:35:03 +0200 Subject: [PATCH 35/36] Add test for time jumps --- .../Gameplay/TestSceneParticleSpewer.cs | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs index 2f107c8300..ce5cd629e0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.Testing; +using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Skinning; @@ -55,6 +56,26 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("is not present", () => !spewer.IsPresent); } + [Test] + public void TestTimeJumps() + { + ManualClock testClock = new ManualClock(); + + AddStep("prepare clock", () => + { + testClock.CurrentTime = TestParticleSpewer.MAX_DURATION * -3; + spewer.Clock = new FramedClock(testClock); + }); + AddStep("start spewer", () => spewer.Active.Value = true); + AddAssert("spawned first particle", () => spewer.TotalCreatedParticles == 1); + + AddStep("move clock forward", () => testClock.CurrentTime = TestParticleSpewer.MAX_DURATION * 3); + AddAssert("spawned second particle", () => spewer.TotalCreatedParticles == 2); + + AddStep("move clock backwards", () => testClock.CurrentTime = TestParticleSpewer.MAX_DURATION * -1); + AddAssert("spawned third particle", () => spewer.TotalCreatedParticles == 3); + } + private TestParticleSpewer createSpewer() => new TestParticleSpewer(skinManager.DefaultLegacySkin.GetTexture("star2")) { @@ -67,9 +88,11 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestParticleSpewer : ParticleSpewer { - private const int max_duration = 1500; + public const int MAX_DURATION = 1500; private const int rate = 250; + public int TotalCreatedParticles { get; private set; } + public float Gravity; public float MaxVelocity = 0.25f; @@ -79,23 +102,27 @@ namespace osu.Game.Tests.Visual.Gameplay protected override float ParticleGravity => Gravity; public TestParticleSpewer(Texture texture) - : base(texture, rate, max_duration) + : base(texture, rate, MAX_DURATION) { } - protected override FallingParticle CreateParticle() => - new FallingParticle + protected override FallingParticle CreateParticle() + { + TotalCreatedParticles++; + + return new FallingParticle { Velocity = new Vector2( RNG.NextSingle(-MaxVelocity, MaxVelocity), RNG.NextSingle(-MaxVelocity, MaxVelocity) ), StartPosition = SpawnPosition, - Duration = RNG.NextSingle(max_duration), + Duration = RNG.NextSingle(MAX_DURATION), StartAngle = RNG.NextSingle(MathF.PI * 2), EndAngle = RNG.NextSingle(MathF.PI * 2), EndScale = RNG.NextSingle(0.5f, 1.5f) }; + } } } } From 20eeb36567c2c2afb232b7580729c4c52f4d2ec6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Sep 2021 18:35:47 +0900 Subject: [PATCH 36/36] Avoid `AliveObject` enumeration when not in kiai section --- .../Skinning/Legacy/LegacyCursorParticles.cs | 46 +++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index e1b7dbc3e3..2b0dfba1dd 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; @@ -12,6 +13,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Play; @@ -32,7 +34,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private Player player { get; set; } [Resolved(canBeNull: true)] - private OsuPlayfield osuPlayfield { get; set; } + private OsuPlayfield playfield { get; set; } + + [Resolved(canBeNull: true)] + private GameplayBeatmap gameplayBeatmap { get; set; } + + [Resolved(canBeNull: true)] + private GameplayClock gameplayClock { get; set; } [BackgroundDependencyLoader] private void load(ISkinSource skin, OsuColour colours) @@ -65,27 +73,39 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy }; if (player != null) - { - breakSpewer.Active.BindTarget = player.IsBreakTime; - } + ((IBindable)breakSpewer.Active).BindTo(player.IsBreakTime); } protected override void Update() { - if (osuPlayfield == null) return; + if (playfield == null || gameplayBeatmap == null) return; - // find active kiai slider or spinner. - var kiaiHitObject = osuPlayfield.HitObjectContainer.AliveObjects.FirstOrDefault(h => - h.HitObject.Kiai && - ( - (h is DrawableSlider slider && slider.Tracking.Value) || - (h is DrawableSpinner spinner && spinner.RotationTracker.Tracking) - ) - ); + DrawableHitObject kiaiHitObject = null; + + // Check whether currently in a kiai section first. This is only done as an optimisation to avoid enumerating AliveObjects when not necessary. + if (gameplayBeatmap.ControlPointInfo.EffectPointAt(gameplayBeatmap.Time.Current).KiaiMode) + kiaiHitObject = playfield.HitObjectContainer.AliveObjects.FirstOrDefault(isTracking); kiaiSpewer.Active.Value = kiaiHitObject != null; } + private bool isTracking(DrawableHitObject h) + { + if (!h.HitObject.Kiai) + return false; + + switch (h) + { + case DrawableSlider slider: + return slider.Tracking.Value; + + case DrawableSpinner spinner: + return spinner.RotationTracker.Tracking; + } + + return false; + } + public bool OnPressed(KeyBindingPressEvent e) { handleInput(e.Action, true);