From 7bb984eb8da4e0390ad9671330a0bb61d2a8920f Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 21 Dec 2019 20:47:34 +0800 Subject: [PATCH 01/28] Basic kiai flash implementation --- .../Objects/Drawables/Pieces/CirclePiece.cs | 4 ++ .../Objects/Drawables/Pieces/KiaiFlash.cs | 40 +++++++++++++++++++ .../Skinning/LegacyMainCirclePiece.cs | 12 ++++++ 3 files changed, 56 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index aab01f45d4..84470d7f30 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -35,6 +35,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Origin = Anchor.Centre, Texture = textures.Get(@"Gameplay/osu/disc"), }, + new KiaiFlash + { + RelativeSizeAxes = Axes.Both, + }, new TrianglesPiece { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs new file mode 100644 index 0000000000..9f9ca42d69 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class KiaiFlash : BeatSyncedContainer + { + public Drawable FlashComponent { get; set; } + + public KiaiFlash() + { + Blending = BlendingParameters.Additive; + + Child = FlashComponent = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(1f), + Alpha = 0f, + }; + } + + protected new double EarlyActivationMilliseconds = 80; + + protected override void OnNewBeat(int beatIndex, Game.Beatmaps.ControlPoints.TimingControlPoint timingPoint, Game.Beatmaps.ControlPoints.EffectControlPoint effectPoint, Framework.Audio.Track.TrackAmplitudes amplitudes) + { + if (effectPoint.KiaiMode) + { + FlashComponent + .FadeTo(0.25f, EarlyActivationMilliseconds, Easing.OutQuint) + .Then() + .FadeOut(timingPoint.BeatLength - 80, Easing.OutSine); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 93ae0371df..97541d171b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -10,6 +10,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -44,6 +45,17 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, }, + new KiaiFlash + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.921875f), + + Masking = true, + CornerRadius = Size.X / 2, + CornerExponent = 2, + }, hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40), From e4eccb86ba62985bbae398a5b5697269f01df72f Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 21 Dec 2019 20:53:02 +0800 Subject: [PATCH 02/28] Add property Intensity --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs | 1 + osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs | 4 +++- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index 84470d7f30..e6b193b00f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces new KiaiFlash { RelativeSizeAxes = Axes.Both, + Intensity = 0.25f, }, new TrianglesPiece { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs index 9f9ca42d69..e7c880c3c1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs @@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public Drawable FlashComponent { get; set; } + public float Intensity { get; set; } + public KiaiFlash() { Blending = BlendingParameters.Additive; @@ -31,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (effectPoint.KiaiMode) { FlashComponent - .FadeTo(0.25f, EarlyActivationMilliseconds, Easing.OutQuint) + .FadeTo(Intensity, EarlyActivationMilliseconds, Easing.OutQuint) .Then() .FadeOut(timingPoint.BeatLength - 80, Easing.OutSine); } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 97541d171b..ce89fac345 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -55,6 +55,8 @@ namespace osu.Game.Rulesets.Osu.Skinning Masking = true, CornerRadius = Size.X / 2, CornerExponent = 2, + + Intensity = 0.1f }, hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { From 76a895f34830a8389920cedb1d7db47458c7c2df Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 21 Dec 2019 22:44:52 +0800 Subject: [PATCH 03/28] improve code quality --- .../Objects/Drawables/Pieces/CirclePiece.cs | 8 ++++++++ .../Objects/Drawables/Pieces/KiaiFlash.cs | 14 ++------------ .../Skinning/LegacyMainCirclePiece.cs | 11 +++++++++-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index e6b193b00f..eba3a02376 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -6,7 +6,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Shapes; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -39,6 +41,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { RelativeSizeAxes = Axes.Both, Intensity = 0.25f, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Alpha = 0f, + }, }, new TrianglesPiece { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs index e7c880c3c1..379deff8bf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs @@ -10,29 +10,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class KiaiFlash : BeatSyncedContainer { - public Drawable FlashComponent { get; set; } - public float Intensity { get; set; } public KiaiFlash() { + EarlyActivationMilliseconds = 80; Blending = BlendingParameters.Additive; - - Child = FlashComponent = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(1f), - Alpha = 0f, - }; } - protected new double EarlyActivationMilliseconds = 80; - protected override void OnNewBeat(int beatIndex, Game.Beatmaps.ControlPoints.TimingControlPoint timingPoint, Game.Beatmaps.ControlPoints.EffectControlPoint effectPoint, Framework.Audio.Track.TrackAmplitudes amplitudes) { if (effectPoint.KiaiMode) { - FlashComponent + Child .FadeTo(Intensity, EarlyActivationMilliseconds, Easing.OutQuint) .Then() .FadeOut(timingPoint.BeatLength - 80, Easing.OutSine); diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index ce89fac345..27f9e008ed 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; @@ -50,13 +51,19 @@ namespace osu.Game.Rulesets.Osu.Skinning RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(0.921875f), + Size = new Vector2(OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / 64), Masking = true, CornerRadius = Size.X / 2, CornerExponent = 2, - Intensity = 0.1f + Intensity = 0.1f, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Alpha = 0f, + } }, hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { From 4b3cfe3baec4d44d7a5f04d5398199ca1d95eb00 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 22 Dec 2019 20:01:58 +0800 Subject: [PATCH 04/28] temporarily remove kiai flash for legacy circles --- .../Objects/Drawables/Pieces/CirclePiece.cs | 10 +------- .../Objects/Drawables/Pieces/KiaiFlash.cs | 23 +++++++++++++------ .../Skinning/LegacyMainCirclePiece.cs | 21 ----------------- 3 files changed, 17 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index eba3a02376..8d68714f9a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -6,9 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Shapes; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -40,13 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces new KiaiFlash { RelativeSizeAxes = Axes.Both, - Intensity = 0.25f, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - Alpha = 0f, - }, + FlashOpacity = 0.25f, }, new TrianglesPiece { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs index 379deff8bf..62ea028b11 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/KiaiFlash.cs @@ -3,30 +3,39 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class KiaiFlash : BeatSyncedContainer { - public float Intensity { get; set; } + public float FlashOpacity = 1f; public KiaiFlash() { EarlyActivationMilliseconds = 80; Blending = BlendingParameters.Additive; + + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Alpha = 0f, + }; } protected override void OnNewBeat(int beatIndex, Game.Beatmaps.ControlPoints.TimingControlPoint timingPoint, Game.Beatmaps.ControlPoints.EffectControlPoint effectPoint, Framework.Audio.Track.TrackAmplitudes amplitudes) { - if (effectPoint.KiaiMode) + if (!effectPoint.KiaiMode) { - Child - .FadeTo(Intensity, EarlyActivationMilliseconds, Easing.OutQuint) - .Then() - .FadeOut(timingPoint.BeatLength - 80, Easing.OutSine); + return; } + + Child + .FadeTo(FlashOpacity, EarlyActivationMilliseconds, Easing.OutQuint) + .Then() + .FadeOut(timingPoint.BeatLength - 80, Easing.OutSine); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 27f9e008ed..93ae0371df 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -6,12 +6,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -46,25 +44,6 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - new KiaiFlash - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / 64), - - Masking = true, - CornerRadius = Size.X / 2, - CornerExponent = 2, - - Intensity = 0.1f, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - Alpha = 0f, - } - }, hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40), From 5b436ee436a58d3a8d83898f5613fcdf18222268 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 2 Jun 2021 15:50:33 +0800 Subject: [PATCH 05/28] add beatmap with storyboard source --- osu.Game/Configuration/BackgroundSource.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/BackgroundSource.cs b/osu.Game/Configuration/BackgroundSource.cs index 5726e96eb1..18e0603860 100644 --- a/osu.Game/Configuration/BackgroundSource.cs +++ b/osu.Game/Configuration/BackgroundSource.cs @@ -1,11 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; + namespace osu.Game.Configuration { public enum BackgroundSource { Skin, - Beatmap + Beatmap, + + [Description("Beatmap (with storyboard / video)")] + BeatmapWithStoryboard, } } From dec18ef826defe08cce89cc5b92b01b30c4fef81 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 2 Jun 2021 15:50:58 +0800 Subject: [PATCH 06/28] implement BeatmapBackgroundWithStoryboard --- .../BeatmapBackgroundWithStoryboard.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs new file mode 100644 index 0000000000..20bf15af22 --- /dev/null +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Storyboards.Drawables; + +namespace osu.Game.Graphics.Backgrounds +{ + public class BeatmapBackgroundWithStoryboard : BeatmapBackground + { + private DrawableStoryboard storyboard; + + public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1") + : base(beatmap, fallbackTextureName) + { + } + + [BackgroundDependencyLoader] + private void load() + { + var clock = new InterpolatingFramedClock(Beatmap.Track); + LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) + { + Alpha = 0, + Clock = clock, + }, + loaded => + { + AddInternal(storyboard = loaded); + storyboard.FadeIn(300, Easing.OutQuint); + + if (Beatmap.Storyboard.ReplacesBackground) + Sprite.FadeOut(300, Easing.OutQuint); + }); + } + } +} From dde64adcb5102d0ea358e9eae8d9106a4b33b316 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 2 Jun 2021 15:51:43 +0800 Subject: [PATCH 07/28] add new background type in BackgroundScreenDefault --- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index bd4577fd57..81968db320 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -110,6 +110,10 @@ namespace osu.Game.Screens.Backgrounds newBackground = new BeatmapBackground(beatmap.Value, backgroundName); break; + case BackgroundSource.BeatmapWithStoryboard: + newBackground = new BeatmapBackgroundWithStoryboard(beatmap.Value, backgroundName); + break; + default: newBackground = new SkinnedBackground(skin.Value, backgroundName); break; From e66f6e8f91cd6783544d2bb1ea501d3f297e39aa Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 2 Jun 2021 16:12:41 +0800 Subject: [PATCH 08/28] fix inspect code issues and cleanup code --- .../BeatmapBackgroundWithStoryboard.cs | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 20bf15af22..0afdc52e49 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -11,8 +11,6 @@ namespace osu.Game.Graphics.Backgrounds { public class BeatmapBackgroundWithStoryboard : BeatmapBackground { - private DrawableStoryboard storyboard; - public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1") : base(beatmap, fallbackTextureName) { @@ -21,20 +19,19 @@ namespace osu.Game.Graphics.Backgrounds [BackgroundDependencyLoader] private void load() { - var clock = new InterpolatingFramedClock(Beatmap.Track); LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) - { - Alpha = 0, - Clock = clock, - }, - loaded => - { - AddInternal(storyboard = loaded); - storyboard.FadeIn(300, Easing.OutQuint); + { + Alpha = 0, + Clock = new InterpolatingFramedClock(Beatmap.Track), + }, + loaded => + { + AddInternal(loaded); + loaded.FadeIn(300, Easing.OutQuint); - if (Beatmap.Storyboard.ReplacesBackground) - Sprite.FadeOut(300, Easing.OutQuint); - }); + if (Beatmap.Storyboard.ReplacesBackground) + Sprite.FadeOut(300, Easing.OutQuint); + }); } } } From 3c3ef1363209db1ac261f37f35f5fba7f5a041ba Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 2 Jun 2021 16:28:22 +0800 Subject: [PATCH 09/28] remove fade --- .../Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 0afdc52e49..21c536ee30 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -21,14 +21,11 @@ namespace osu.Game.Graphics.Backgrounds { LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) { - Alpha = 0, Clock = new InterpolatingFramedClock(Beatmap.Track), }, loaded => { AddInternal(loaded); - loaded.FadeIn(300, Easing.OutQuint); - if (Beatmap.Storyboard.ReplacesBackground) Sprite.FadeOut(300, Easing.OutQuint); }); From 277545bb0652c8e2e56c474293200b5b2c1755da Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 2 Jun 2021 20:27:12 +0800 Subject: [PATCH 10/28] refactor BeatmapBackgroundWithStoryboard to reduce overhead This avoids loading the sprite if its not needed and instead of hiding it, it is removed when the storyboard replaces the background or there is a video. This also only initializes DrawableStoryboard if there are any elements in any layer. --- .../Graphics/Backgrounds/BeatmapBackground.cs | 11 +++++-- .../BeatmapBackgroundWithStoryboard.cs | 29 ++++++++++--------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs index 058d2ed0f9..2a4c5e194b 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs @@ -13,16 +13,21 @@ namespace osu.Game.Graphics.Backgrounds private readonly string fallbackTextureName; + [Resolved] + private LargeTextureStore textures { get; set; } + public BeatmapBackground(WorkingBeatmap beatmap, string fallbackTextureName = @"Backgrounds/bg1") { Beatmap = beatmap; this.fallbackTextureName = fallbackTextureName; } - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) + protected override void LoadComplete() { - Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName); + base.LoadComplete(); + Initialize(); } + + protected virtual void Initialize() => Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName); } } diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 21c536ee30..1925cb927e 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Framework.Graphics; +using System.Linq; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Storyboards.Drawables; @@ -16,19 +15,21 @@ namespace osu.Game.Graphics.Backgrounds { } - [BackgroundDependencyLoader] - private void load() + protected override void Initialize() { - LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) - { - Clock = new InterpolatingFramedClock(Beatmap.Track), - }, - loaded => - { - AddInternal(loaded); - if (Beatmap.Storyboard.ReplacesBackground) - Sprite.FadeOut(300, Easing.OutQuint); - }); + if (Beatmap.Storyboard.HasDrawable) + { + LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }, AddInternal); + } + + if (Beatmap.Storyboard.ReplacesBackground || Beatmap.Storyboard.Layers.First(l => l.Name == "Video").Elements.Any()) + { + Sprite.Expire(); + } + else + { + base.Initialize(); + } } } } From a62dd7cca089b65990f273d185bd561699552a2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Jun 2021 12:33:16 +0900 Subject: [PATCH 11/28] Revert "refactor BeatmapBackgroundWithStoryboard to reduce overhead" This reverts commit 277545bb0652c8e2e56c474293200b5b2c1755da. --- .../Graphics/Backgrounds/BeatmapBackground.cs | 11 ++----- .../BeatmapBackgroundWithStoryboard.cs | 29 +++++++++---------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs index 2a4c5e194b..058d2ed0f9 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs @@ -13,21 +13,16 @@ namespace osu.Game.Graphics.Backgrounds private readonly string fallbackTextureName; - [Resolved] - private LargeTextureStore textures { get; set; } - public BeatmapBackground(WorkingBeatmap beatmap, string fallbackTextureName = @"Backgrounds/bg1") { Beatmap = beatmap; this.fallbackTextureName = fallbackTextureName; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) { - base.LoadComplete(); - Initialize(); + Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName); } - - protected virtual void Initialize() => Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName); } } diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 1925cb927e..21c536ee30 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -1,7 +1,8 @@ // 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.Timing; using osu.Game.Beatmaps; using osu.Game.Storyboards.Drawables; @@ -15,21 +16,19 @@ namespace osu.Game.Graphics.Backgrounds { } - protected override void Initialize() + [BackgroundDependencyLoader] + private void load() { - if (Beatmap.Storyboard.HasDrawable) - { - LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }, AddInternal); - } - - if (Beatmap.Storyboard.ReplacesBackground || Beatmap.Storyboard.Layers.First(l => l.Name == "Video").Elements.Any()) - { - Sprite.Expire(); - } - else - { - base.Initialize(); - } + LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) + { + Clock = new InterpolatingFramedClock(Beatmap.Track), + }, + loaded => + { + AddInternal(loaded); + if (Beatmap.Storyboard.ReplacesBackground) + Sprite.FadeOut(300, Easing.OutQuint); + }); } } } From d00fb2118840e324cae17f52b481d81e0a7b9012 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 3 Jun 2021 13:24:21 +0800 Subject: [PATCH 12/28] prevent scaling container from creating a storyboard background --- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 ++ osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 2488fd14d0..d2b1e5e523 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -172,6 +172,8 @@ namespace osu.Game.Graphics.Containers private class ScalingBackgroundScreen : BackgroundScreenDefault { + protected override bool AllowStoryboardBackground => false; + public override void OnEntering(IScreen last) { this.FadeInFromZero(4000, Easing.OutQuint); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 81968db320..b02e7ddb0d 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -31,6 +31,8 @@ namespace osu.Game.Screens.Backgrounds [Resolved] private IBindable beatmap { get; set; } + protected virtual bool AllowStoryboardBackground => true; + public BackgroundScreenDefault(bool animateOnEnter = true) : base(animateOnEnter) { @@ -111,7 +113,9 @@ namespace osu.Game.Screens.Backgrounds break; case BackgroundSource.BeatmapWithStoryboard: - newBackground = new BeatmapBackgroundWithStoryboard(beatmap.Value, backgroundName); + newBackground = AllowStoryboardBackground + ? new BeatmapBackgroundWithStoryboard(beatmap.Value, backgroundName) + : new BeatmapBackground(beatmap.Value, backgroundName); break; default: From 62b07fb9ceadace33eb2801e0ff7461089d4ebea Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 3 Jun 2021 13:27:00 +0800 Subject: [PATCH 13/28] apply suggestions - Replace the sprite with a solid black box when a storyboard requests it. - Create a new storyboard instance and exclude the fail layer as well as strip all samples from it - Do not attempt in creating the storyboard when it isn't needed --- .../BeatmapBackgroundWithStoryboard.cs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 21c536ee30..1936482c90 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -1,10 +1,13 @@ // 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.Timing; using osu.Game.Beatmaps; +using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; namespace osu.Game.Graphics.Backgrounds @@ -19,16 +22,23 @@ namespace osu.Game.Graphics.Backgrounds [BackgroundDependencyLoader] private void load() { - LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) - { - Clock = new InterpolatingFramedClock(Beatmap.Track), - }, - loaded => - { - AddInternal(loaded); - if (Beatmap.Storyboard.ReplacesBackground) - Sprite.FadeOut(300, Easing.OutQuint); - }); + if (!Beatmap.Storyboard.HasDrawable) + return; + + var storyboard = new Storyboard { BeatmapInfo = Beatmap.BeatmapInfo }; + foreach (var layer in storyboard.Layers) + { + if (layer.Name != "Fail") + layer.Elements = Beatmap.Storyboard.GetLayer(layer.Name).Elements.Where(e => !(e is StoryboardSampleInfo)).ToList(); + } + + if (storyboard.ReplacesBackground) + { + Sprite.Texture = Texture.WhitePixel; + Sprite.Colour = Colour4.Black; + } + + LoadComponentAsync(new DrawableStoryboard(storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }, AddInternal); } } } From d7d0dde5d27c84cfb2e7380158d1c4e26b1cd40a Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 3 Jun 2021 13:56:14 +0800 Subject: [PATCH 14/28] use created storyboard to check for drawables instead --- .../Backgrounds/BeatmapBackgroundWithStoryboard.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 1936482c90..9695e93f5d 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -22,16 +22,17 @@ namespace osu.Game.Graphics.Backgrounds [BackgroundDependencyLoader] private void load() { - if (!Beatmap.Storyboard.HasDrawable) - return; - var storyboard = new Storyboard { BeatmapInfo = Beatmap.BeatmapInfo }; + foreach (var layer in storyboard.Layers) { if (layer.Name != "Fail") layer.Elements = Beatmap.Storyboard.GetLayer(layer.Name).Elements.Where(e => !(e is StoryboardSampleInfo)).ToList(); } + if (!storyboard.HasDrawable) + return; + if (storyboard.ReplacesBackground) { Sprite.Texture = Texture.WhitePixel; From fe2934db1daa6681bae7342782fc8b7635b4cec8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 31 May 2021 22:57:43 +0900 Subject: [PATCH 15/28] Factor out lifetime management logic of HitObjectContainer --- .../PooledDrawableWithLifetimeContainer.cs | 157 ++++++++++++++++++ osu.Game/Rulesets/UI/HitObjectContainer.cs | 94 ++--------- 2 files changed, 175 insertions(+), 76 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs new file mode 100644 index 0000000000..656e4a9dd6 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs @@ -0,0 +1,157 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; + +namespace osu.Game.Rulesets.Objects.Pooling +{ + /// + /// A container of s dynamically added/removed by model s. + /// When an entry became alive, a drawable corresponding to the entry is obtained (potentially pooled), and added to this container. + /// The drawable is removed when the entry became dead. + /// + /// The type of entries managed by this container. + /// The type of drawables corresponding to the entries. + public abstract class PooledDrawableWithLifetimeContainer : CompositeDrawable + where TEntry : LifetimeEntry + where TDrawable : Drawable + { + /// + /// All entries added to this container, including dead entries. + /// + /// + /// The enumeration order is undefined. + /// + public IEnumerable Entries => allEntries; + + /// + /// All alive entries and drawables corresponding to the entries. + /// + /// + /// The enumeration order is undefined. + /// + public IEnumerable<(TEntry Entry, TDrawable Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value)); + + /// + /// The amount of time prior to the current time within which entries should be considered alive. + /// + internal double PastLifetimeExtension { get; set; } + + /// + /// The amount of time after the current time within which entries should be considered alive. + /// + internal double FutureLifetimeExtension { get; set; } + + private readonly Dictionary aliveDrawableMap = new Dictionary(); + private readonly HashSet allEntries = new HashSet(); + + private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); + + protected PooledDrawableWithLifetimeContainer() + { + lifetimeManager.EntryBecameAlive += entryBecameAlive; + lifetimeManager.EntryBecameDead += entryBecameDead; + lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; + } + + /// + /// Add a to be managed by this container. + /// + /// + /// The aliveness of the entry is not updated until . + /// + public virtual void Add(TEntry entry) + { + allEntries.Add(entry); + lifetimeManager.AddEntry(entry); + } + + /// + /// Remove a from this container. + /// + /// + /// If the entry was alive, the corresponding drawable is removed. + /// + /// Whether the entry was in this container. + public virtual bool Remove(TEntry entry) + { + if (!lifetimeManager.RemoveEntry(entry)) return false; + + allEntries.Remove(entry); + return true; + } + + /// + /// Initialize new corresponding . + /// + /// The corresponding to the entry. + protected abstract TDrawable GetDrawable(TEntry entry); + + private void entryBecameAlive(LifetimeEntry lifetimeEntry) + { + var entry = (TEntry)lifetimeEntry; + Debug.Assert(!aliveDrawableMap.ContainsKey(entry)); + + TDrawable drawable = GetDrawable(entry); + aliveDrawableMap[entry] = drawable; + AddDrawable(entry, drawable); + } + + /// + /// Add a corresponding to to this container. + /// + /// + /// Invoked when the entry became alive and a is obtained by . + /// + protected virtual void AddDrawable(TEntry entry, TDrawable drawable) => AddInternal(drawable); + + private void entryBecameDead(LifetimeEntry lifetimeEntry) + { + var entry = (TEntry)lifetimeEntry; + Debug.Assert(aliveDrawableMap.ContainsKey(entry)); + + TDrawable drawable = aliveDrawableMap[entry]; + aliveDrawableMap.Remove(entry); + RemoveDrawable(entry, drawable); + } + + /// + /// Remove a corresponding to from this container. + /// + /// + /// Invoked when the entry became dead. + /// + protected virtual void RemoveDrawable(TEntry entry, TDrawable drawable) => RemoveInternal(drawable); + + private void entryCrossedBoundary(LifetimeEntry lifetimeEntry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) => + OnEntryCrossedBoundary((TEntry)lifetimeEntry, kind, direction); + + protected virtual void OnEntryCrossedBoundary(TEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) + { + } + + /// + /// Remove all s. + /// + public virtual void Clear() + { + foreach (var entry in Entries.ToArray()) + Remove(entry); + Debug.Assert(aliveDrawableMap.Count == 0); + } + + protected override bool CheckChildrenLife() + { + bool aliveChanged = base.CheckChildrenLife(); + aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension); + return aliveChanged; + } + } +} diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 83033b2dd5..ff7a368c0c 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -14,24 +14,15 @@ using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Pooling; namespace osu.Game.Rulesets.UI { - public class HitObjectContainer : CompositeDrawable, IHitObjectContainer + public class HitObjectContainer : PooledDrawableWithLifetimeContainer, IHitObjectContainer { - /// - /// All entries in this including dead entries. - /// - public IEnumerable Entries => allEntries; - - /// - /// All alive entries and s used by the entries. - /// - public IEnumerable<(HitObjectLifetimeEntry Entry, DrawableHitObject Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value)); - public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); - public IEnumerable AliveObjects => AliveInternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); + public IEnumerable AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime); /// /// Invoked when a is judged. @@ -59,34 +50,16 @@ namespace osu.Game.Rulesets.UI /// internal event Action HitObjectUsageFinished; - /// - /// The amount of time prior to the current time within which s should be considered alive. - /// - internal double PastLifetimeExtension { get; set; } - - /// - /// The amount of time after the current time within which s should be considered alive. - /// - internal double FutureLifetimeExtension { get; set; } - private readonly Dictionary startTimeMap = new Dictionary(); - private readonly Dictionary aliveDrawableMap = new Dictionary(); private readonly Dictionary nonPooledDrawableMap = new Dictionary(); - private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); - private readonly HashSet allEntries = new HashSet(); - [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } public HitObjectContainer() { RelativeSizeAxes = Axes.Both; - - lifetimeManager.EntryBecameAlive += entryBecameAlive; - lifetimeManager.EntryBecameDead += entryBecameDead; - lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; } protected override void LoadAsyncComplete() @@ -99,37 +72,29 @@ namespace osu.Game.Rulesets.UI #region Pooling support - public void Add(HitObjectLifetimeEntry entry) + public override bool Remove(HitObjectLifetimeEntry entry) { - allEntries.Add(entry); - lifetimeManager.AddEntry(entry); - } - - public bool Remove(HitObjectLifetimeEntry entry) - { - if (!lifetimeManager.RemoveEntry(entry)) return false; + if (!base.Remove(entry)) return false; // This logic is not in `Remove(DrawableHitObject)` because a non-pooled drawable may be removed by specifying its entry. if (nonPooledDrawableMap.Remove(entry, out var drawable)) removeDrawable(drawable); - allEntries.Remove(entry); return true; } - private void entryBecameAlive(LifetimeEntry lifetimeEntry) + protected sealed override DrawableHitObject GetDrawable(HitObjectLifetimeEntry entry) { - var entry = (HitObjectLifetimeEntry)lifetimeEntry; - Debug.Assert(!aliveDrawableMap.ContainsKey(entry)); + if (nonPooledDrawableMap.TryGetValue(entry, out var drawable)) + return drawable; - bool isPooled = !nonPooledDrawableMap.TryGetValue(entry, out var drawable); - drawable ??= pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null); - if (drawable == null) - throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}."); + return pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null) ?? + throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}."); + } - aliveDrawableMap[entry] = drawable; - - if (isPooled) + protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) + { + if (!nonPooledDrawableMap.ContainsKey(entry)) { addDrawable(drawable); HitObjectUsageBegan?.Invoke(entry.HitObject); @@ -138,18 +103,11 @@ namespace osu.Game.Rulesets.UI OnAdd(drawable); } - private void entryBecameDead(LifetimeEntry lifetimeEntry) + protected override void RemoveDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) { - var entry = (HitObjectLifetimeEntry)lifetimeEntry; - Debug.Assert(aliveDrawableMap.ContainsKey(entry)); - - var drawable = aliveDrawableMap[entry]; - bool isPooled = !nonPooledDrawableMap.ContainsKey(entry); - drawable.OnKilled(); - aliveDrawableMap.Remove(entry); - if (isPooled) + if (!nonPooledDrawableMap.ContainsKey(entry)) { removeDrawable(drawable); HitObjectUsageFinished?.Invoke(entry.HitObject); @@ -201,9 +159,9 @@ namespace osu.Game.Rulesets.UI public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject); - private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) + protected override void OnEntryCrossedBoundary(HitObjectLifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) { - if (nonPooledDrawableMap.TryGetValue((HitObjectLifetimeEntry)entry, out var drawable)) + if (nonPooledDrawableMap.TryGetValue(entry, out var drawable)) OnChildLifetimeBoundaryCrossed(new LifetimeBoundaryCrossedEvent(drawable, kind, direction)); } @@ -228,22 +186,6 @@ namespace osu.Game.Rulesets.UI { } - public virtual void Clear() - { - lifetimeManager.ClearEntries(); - foreach (var drawable in nonPooledDrawableMap.Values) - removeDrawable(drawable); - nonPooledDrawableMap.Clear(); - Debug.Assert(InternalChildren.Count == 0 && startTimeMap.Count == 0 && aliveDrawableMap.Count == 0, "All hit objects should have been removed"); - } - - protected override bool CheckChildrenLife() - { - bool aliveChanged = base.CheckChildrenLife(); - aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension); - return aliveChanged; - } - private void onNewResult(DrawableHitObject d, JudgementResult r) => NewResult?.Invoke(d, r); private void onRevertResult(DrawableHitObject d, JudgementResult r) => RevertResult?.Invoke(d, r); From 2c9e5b6c7e7b965ac08325b76345522777442b49 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 31 May 2021 23:00:12 +0900 Subject: [PATCH 16/28] Replace `EntryCrossedBoundary` with more useful `RemoveRewoundEntry` property It can be used for dynamically added entries. --- .../UI/DrumRollHitContainer.cs | 17 +++++------------ .../PooledDrawableWithLifetimeContainer.cs | 13 +++++++++---- osu.Game/Rulesets/UI/HitObjectContainer.cs | 12 ------------ 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs index 9bfb6aa839..263454c78a 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; @@ -11,6 +8,11 @@ namespace osu.Game.Rulesets.Taiko.UI { internal class DrumRollHitContainer : ScrollingHitObjectContainer { + // TODO: this usage is buggy. + // Because `LifetimeStart` is set based on scrolling, lifetime is not same as the time when the object is created. + // If the `Update` override is removed, it breaks in an obscure way. + protected override bool RemoveRewoundEntry => true; + protected override void Update() { base.Update(); @@ -23,14 +25,5 @@ namespace osu.Game.Rulesets.Taiko.UI Remove(flyingHit); } } - - protected override void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e) - { - base.OnChildLifetimeBoundaryCrossed(e); - - // ensure all old hits are removed on becoming alive (may miss being in the AliveInternalChildren list above). - if (e.Kind == LifetimeBoundaryKind.Start && e.Direction == LifetimeBoundaryCrossingDirection.Backward) - Remove((DrawableHitObject)e.Child); - } } } diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs index 656e4a9dd6..5fe9d889a1 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs @@ -39,6 +39,12 @@ namespace osu.Game.Rulesets.Objects.Pooling /// public IEnumerable<(TEntry Entry, TDrawable Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value)); + /// + /// Whether to remove an entry when clock goes backward and crossed its . + /// Used when entries are dynamically added at its to prevent duplicated entries. + /// + protected virtual bool RemoveRewoundEntry => false; + /// /// The amount of time prior to the current time within which entries should be considered alive. /// @@ -130,11 +136,10 @@ namespace osu.Game.Rulesets.Objects.Pooling /// protected virtual void RemoveDrawable(TEntry entry, TDrawable drawable) => RemoveInternal(drawable); - private void entryCrossedBoundary(LifetimeEntry lifetimeEntry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) => - OnEntryCrossedBoundary((TEntry)lifetimeEntry, kind, direction); - - protected virtual void OnEntryCrossedBoundary(TEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) + private void entryCrossedBoundary(LifetimeEntry lifetimeEntry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) { + if (RemoveRewoundEntry && kind == LifetimeBoundaryKind.Start && direction == LifetimeBoundaryCrossingDirection.Backward) + Remove((TEntry)lifetimeEntry); } /// diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index ff7a368c0c..917aa54673 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -9,8 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -159,16 +157,6 @@ namespace osu.Game.Rulesets.UI public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject); - protected override void OnEntryCrossedBoundary(HitObjectLifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) - { - if (nonPooledDrawableMap.TryGetValue(entry, out var drawable)) - OnChildLifetimeBoundaryCrossed(new LifetimeBoundaryCrossedEvent(drawable, kind, direction)); - } - - protected virtual void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e) - { - } - #endregion /// From 0ce7baa3f3e80b640cf6c8d07400bc3465509368 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 31 May 2021 23:01:54 +0900 Subject: [PATCH 17/28] Make `HitObjectContainer.Clear` non-virtual It just call `Remove` for all entries. --- .../Objects/Pooling/PooledDrawableWithLifetimeContainer.cs | 3 ++- .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 7 ------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs index 5fe9d889a1..d35933dba8 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs @@ -145,10 +145,11 @@ namespace osu.Game.Rulesets.Objects.Pooling /// /// Remove all s. /// - public virtual void Clear() + public void Clear() { foreach (var entry in Entries.ToArray()) Remove(entry); + Debug.Assert(aliveDrawableMap.Count == 0); } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index cde4182f2d..b60aabe0e6 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -45,13 +45,6 @@ namespace osu.Game.Rulesets.UI.Scrolling timeRange.ValueChanged += _ => layoutCache.Invalidate(); } - public override void Clear() - { - base.Clear(); - - layoutComputed.Clear(); - } - /// /// Given a position in screen space, return the time within this column. /// From b321b20e9d5087bd5b0a0a208d616d751dc9abc6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 31 May 2021 23:07:32 +0900 Subject: [PATCH 18/28] Remove `OnAdd`/`OnRemove` of `HitObjectContainer` Instead, override `AddDrawable`/`RemoveDrawable`. --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 35 ++++--------------- .../Scrolling/ScrollingHitObjectContainer.cs | 20 +++++++---- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 917aa54673..fee77af0ba 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -92,26 +91,19 @@ namespace osu.Game.Rulesets.UI protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) { - if (!nonPooledDrawableMap.ContainsKey(entry)) - { - addDrawable(drawable); - HitObjectUsageBegan?.Invoke(entry.HitObject); - } + if (nonPooledDrawableMap.ContainsKey(entry)) return; - OnAdd(drawable); + addDrawable(drawable); + HitObjectUsageBegan?.Invoke(entry.HitObject); } protected override void RemoveDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) { drawable.OnKilled(); + if (nonPooledDrawableMap.ContainsKey(entry)) return; - if (!nonPooledDrawableMap.ContainsKey(entry)) - { - removeDrawable(drawable); - HitObjectUsageFinished?.Invoke(entry.HitObject); - } - - OnRemove(drawable); + removeDrawable(drawable); + HitObjectUsageFinished?.Invoke(entry.HitObject); } private void addDrawable(DrawableHitObject drawable) @@ -159,21 +151,6 @@ namespace osu.Game.Rulesets.UI #endregion - /// - /// Invoked after a is added to this container. - /// - protected virtual void OnAdd(DrawableHitObject drawableHitObject) - { - Debug.Assert(drawableHitObject.LoadState >= LoadState.Ready); - } - - /// - /// Invoked after a is removed from this container. - /// - protected virtual void OnRemove(DrawableHitObject drawableHitObject) - { - } - private void onNewResult(DrawableHitObject d, JudgementResult r) => NewResult?.Invoke(d, r); private void onRevertResult(DrawableHitObject d, JudgementResult r) => RevertResult?.Invoke(d, r); diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index b60aabe0e6..f478e37e3e 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Layout; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -140,17 +142,20 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - protected override void OnAdd(DrawableHitObject drawableHitObject) + protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) { - invalidateHitObject(drawableHitObject); - drawableHitObject.DefaultsApplied += invalidateHitObject; + base.AddDrawable(entry, drawable); + + invalidateHitObject(drawable); + drawable.DefaultsApplied += invalidateHitObject; } - protected override void OnRemove(DrawableHitObject drawableHitObject) + protected override void RemoveDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) { - layoutComputed.Remove(drawableHitObject); + base.RemoveDrawable(entry, drawable); - drawableHitObject.DefaultsApplied -= invalidateHitObject; + drawable.DefaultsApplied -= invalidateHitObject; + layoutComputed.Remove(drawable); } private void invalidateHitObject(DrawableHitObject hitObject) @@ -199,6 +204,9 @@ namespace osu.Game.Rulesets.UI.Scrolling private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject) { + // Origin position may be relative to the parent size + Debug.Assert(hitObject.Parent != null); + float originAdjustment = 0.0f; // calculate the dimension of the part of the hitobject that should already be visible From 59536747373395b413d4fa79a43184a4ef0391ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 14:06:08 +0900 Subject: [PATCH 19/28] Tidy up constants --- osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs | 1 - osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs index 0b43704439..cb68d4b7a7 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs @@ -45,7 +45,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default new KiaiFlash { RelativeSizeAxes = Axes.Both, - FlashOpacity = 0.25f, }, triangles = new TrianglesPiece { diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs index a13bcba4c7..d49b1713f6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs @@ -12,7 +12,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public class KiaiFlash : BeatSyncedContainer { - public float FlashOpacity = 1f; + private const double fade_length = 80; + + private const float flash_opacity = 0.25f; public KiaiFlash() { @@ -30,14 +32,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { if (!effectPoint.KiaiMode) - { return; - } Child - .FadeTo(FlashOpacity, EarlyActivationMilliseconds, Easing.OutQuint) + .FadeTo(flash_opacity, EarlyActivationMilliseconds, Easing.OutQuint) .Then() - .FadeOut(timingPoint.BeatLength - 80, Easing.OutSine); + .FadeOut(timingPoint.BeatLength - fade_length, Easing.OutSine); } } } From 52557da335b1fd0374deb8c2447666d1a4153ef8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 14:06:23 +0900 Subject: [PATCH 20/28] Add test coverage --- .../TestSceneHitCircleKiai.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs new file mode 100644 index 0000000000..2bce8fa7f2 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs @@ -0,0 +1,30 @@ +// 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.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneHitCircleKiai : TestSceneHitCircle + { + [SetUp] + public void SetUp() => Schedule(() => + { + var controlPointInfo = new ControlPointInfo(); + + controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 }); + controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); + + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + ControlPointInfo = controlPointInfo + }); + + // track needs to be playing for BeatSyncedContainer to work. + Beatmap.Value.Track.Start(); + }); + } +} From f62a2747f67721cc333ca37c93bb8721287cdd7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 14:51:35 +0900 Subject: [PATCH 21/28] Add legacy implementation --- .../Skinning/Legacy/KiaiFlashingSprite.cs | 61 +++++++++++++++++++ .../Skinning/Legacy/LegacyMainCirclePiece.cs | 13 ++-- 2 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.cs new file mode 100644 index 0000000000..4a1d69ad41 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/KiaiFlashingSprite.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 osu.Framework.Audio.Track; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Rulesets.Osu.Skinning.Legacy +{ + internal class KiaiFlashingSprite : BeatSyncedContainer + { + private readonly Sprite mainSprite; + private readonly Sprite flashingSprite; + + public Texture Texture + { + set + { + mainSprite.Texture = value; + flashingSprite.Texture = value; + } + } + + private const float flash_opacity = 0.3f; + + public KiaiFlashingSprite() + { + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + mainSprite = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + flashingSprite = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + Blending = BlendingParameters.Additive, + } + }; + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + if (!effectPoint.KiaiMode) + return; + + flashingSprite + .FadeTo(flash_opacity) + .Then() + .FadeOut(timingPoint.BeatLength * 0.75f); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index ffbeea5e0e..822dad8523 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -32,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); } - private Container circleSprites; - private Sprite hitCircleSprite; - private Sprite hitCircleOverlay; + private Container circleSprites; + private Drawable hitCircleSprite; + private Drawable hitCircleOverlay; private SkinnableSpriteText hitCircleText; @@ -72,20 +71,20 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy InternalChildren = new Drawable[] { - circleSprites = new Container + circleSprites = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Children = new[] { - hitCircleSprite = new Sprite + hitCircleSprite = new KiaiFlashingSprite { Texture = baseTexture, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - hitCircleOverlay = new Sprite + hitCircleOverlay = new KiaiFlashingSprite { Texture = overlayTexture, Anchor = Anchor.Centre, From 996c15610668fedb30df9dd4d885b648a6fb82dc Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 4 Jun 2021 13:56:10 +0800 Subject: [PATCH 22/28] apply suggestions - apply 0 alpha to beatmap background if storyboard replaces it - use an AudioContainer to mute all samples coming from the storyboard --- .../BeatmapBackgroundWithStoryboard.cs | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 9695e93f5d..b9e5b3c08f 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -1,13 +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.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; namespace osu.Game.Graphics.Backgrounds @@ -22,24 +20,17 @@ namespace osu.Game.Graphics.Backgrounds [BackgroundDependencyLoader] private void load() { - var storyboard = new Storyboard { BeatmapInfo = Beatmap.BeatmapInfo }; - - foreach (var layer in storyboard.Layers) - { - if (layer.Name != "Fail") - layer.Elements = Beatmap.Storyboard.GetLayer(layer.Name).Elements.Where(e => !(e is StoryboardSampleInfo)).ToList(); - } - - if (!storyboard.HasDrawable) + if (!Beatmap.Storyboard.HasDrawable) return; - if (storyboard.ReplacesBackground) - { - Sprite.Texture = Texture.WhitePixel; - Sprite.Colour = Colour4.Black; - } + if (Beatmap.Storyboard.ReplacesBackground) + Sprite.Alpha = 0; - LoadComponentAsync(new DrawableStoryboard(storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }, AddInternal); + var audio = new AudioContainer { RelativeSizeAxes = Axes.Both }; + audio.Volume.Value = 0; + + AddInternal(audio); + LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }, audio.Add); } } } From 071c07586a42846447f135219f6f037e66de841c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 15:00:53 +0900 Subject: [PATCH 23/28] Increase music volume back to 80% for the time being --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7935815f38..9c3adba342 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -58,7 +58,7 @@ namespace osu.Game /// /// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects. /// - internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.5; + internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.8; public bool UseDevelopmentServer { get; } From 19a44d65c5df7a1c83dfe9ca3b73ee0fbeb63d7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Jun 2021 15:18:16 +0900 Subject: [PATCH 24/28] Tidy up code --- .../Backgrounds/BeatmapBackgroundWithStoryboard.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index b9e5b3c08f..6a42e83305 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -26,11 +26,12 @@ namespace osu.Game.Graphics.Backgrounds if (Beatmap.Storyboard.ReplacesBackground) Sprite.Alpha = 0; - var audio = new AudioContainer { RelativeSizeAxes = Axes.Both }; - audio.Volume.Value = 0; - - AddInternal(audio); - LoadComponentAsync(new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }, audio.Add); + LoadComponentAsync(new AudioContainer + { + RelativeSizeAxes = Axes.Both, + Volume = { Value = 0 }, + Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) } + }, AddInternal); } } } From b373b120ff090d633863d312bc0b2cadf24d7255 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Jun 2021 16:31:50 +0900 Subject: [PATCH 25/28] Use general lifetime container for follow point container --- .../Connections/FollowPointConnection.cs | 22 +++---- .../Connections/FollowPointRenderer.cs | 64 ++++--------------- 2 files changed, 20 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index cda4715280..7effc4cfa7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Pooling; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections @@ -12,38 +13,31 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// Visualises the s between two s. /// - public class FollowPointConnection : PoolableDrawable + public class FollowPointConnection : PoolableDrawableWithLifetime { // Todo: These shouldn't be constants public const int SPACING = 32; public const double PREEMPT = 800; - public FollowPointLifetimeEntry Entry; public DrawablePool Pool; - protected override void PrepareForUse() + protected override void OnApply(FollowPointLifetimeEntry entry) { - base.PrepareForUse(); - - Entry.Invalidated += onEntryInvalidated; + base.OnApply(entry); + entry.Invalidated += refreshPoints; refreshPoints(); } - protected override void FreeAfterUse() + protected override void OnFree(FollowPointLifetimeEntry entry) { - base.FreeAfterUse(); - - Entry.Invalidated -= onEntryInvalidated; + base.OnFree(entry); + entry.Invalidated -= refreshPoints; // Return points to the pool. ClearInternal(false); - - Entry = null; } - private void onEntryInvalidated() => Scheduler.AddOnce(refreshPoints); - private void refreshPoints() { ClearInternal(false); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 3e85e528e8..21e6619444 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -6,43 +6,32 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Pooling; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { /// /// Visualises connections between s. /// - public class FollowPointRenderer : CompositeDrawable + public class FollowPointRenderer : PooledDrawableWithLifetimeContainer { - public override bool RemoveCompletedTransforms => false; - - public IReadOnlyList Entries => lifetimeEntries; + public new IReadOnlyList Entries => lifetimeEntries; private DrawablePool connectionPool; private DrawablePool pointPool; private readonly List lifetimeEntries = new List(); - private readonly Dictionary connectionsInUse = new Dictionary(); private readonly Dictionary startTimeMap = new Dictionary(); - private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); - - public FollowPointRenderer() - { - lifetimeManager.EntryBecameAlive += onEntryBecameAlive; - lifetimeManager.EntryBecameDead += onEntryBecameDead; - } [BackgroundDependencyLoader] private void load() { InternalChildren = new Drawable[] { - connectionPool = new DrawablePoolNoLifetime(1, 200), - pointPool = new DrawablePoolNoLifetime(50, 1000) + connectionPool = new DrawablePool(1, 200), + pointPool = new DrawablePool(50, 1000) }; } @@ -107,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections previousEntry.End = newEntry.Start; } - lifetimeManager.AddEntry(newEntry); + Add(newEntry); } private void removeEntry(OsuHitObject hitObject) @@ -118,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections entry.UnbindEvents(); lifetimeEntries.RemoveAt(index); - lifetimeManager.RemoveEntry(entry); + Remove(entry); if (index > 0) { @@ -131,30 +120,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } } - protected override bool CheckChildrenLife() + protected override FollowPointConnection GetDrawable(FollowPointLifetimeEntry entry) { - bool anyAliveChanged = base.CheckChildrenLife(); - anyAliveChanged |= lifetimeManager.Update(Time.Current); - return anyAliveChanged; - } - - private void onEntryBecameAlive(LifetimeEntry entry) - { - var connection = connectionPool.Get(c => - { - c.Entry = (FollowPointLifetimeEntry)entry; - c.Pool = pointPool; - }); - - connectionsInUse[entry] = connection; - - AddInternal(connection); - } - - private void onEntryBecameDead(LifetimeEntry entry) - { - RemoveInternal(connectionsInUse[entry]); - connectionsInUse.Remove(entry); + var connection = connectionPool.Get(); + connection.Pool = pointPool; + connection.Apply(entry); + return connection; } private void onStartTimeChanged(OsuHitObject hitObject) @@ -171,16 +142,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections entry.UnbindEvents(); lifetimeEntries.Clear(); } - - private class DrawablePoolNoLifetime : DrawablePool - where T : PoolableDrawable, new() - { - public override bool RemoveWhenNotAlive => false; - - public DrawablePoolNoLifetime(int initialSize, int? maximumSize = null) - : base(initialSize, maximumSize) - { - } - } } } From bc892086fea7f5dedb1ada1ae396e744502e98e2 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Jun 2021 16:41:56 +0900 Subject: [PATCH 26/28] Resolve nullable inspection, enable nullable for `FollowPointLifetimeEntry` --- .../Connections/FollowPointConnection.cs | 12 +++++++----- .../Connections/FollowPointLifetimeEntry.cs | 17 ++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 7effc4cfa7..55f494d2f9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects; @@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections base.OnApply(entry); entry.Invalidated += refreshPoints; - refreshPoints(); + refreshPoints(entry); } protected override void OnFree(FollowPointLifetimeEntry entry) @@ -38,12 +39,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections ClearInternal(false); } - private void refreshPoints() + private void refreshPoints(FollowPointLifetimeEntry entry) { ClearInternal(false); - OsuHitObject start = Entry.Start; - OsuHitObject end = Entry.End; + OsuHitObject start = entry.Start; + OsuHitObject end = entry.End; + Debug.Assert(end != null, $"{nameof(FollowPointLifetimeEntry)} without end hit object should never be alive"); double startTime = start.GetEndTime(); @@ -88,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } // todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed. - Entry.LifetimeEnd = finalTransformEndTime; + entry.LifetimeEnd = finalTransformEndTime; } /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs index a167cb2f0f..a44d07d92c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using osu.Framework.Bindables; using osu.Framework.Graphics.Performance; @@ -11,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointLifetimeEntry : LifetimeEntry { - public event Action Invalidated; + public event Action? Invalidated; public readonly OsuHitObject Start; public FollowPointLifetimeEntry(OsuHitObject start) @@ -22,9 +24,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections bindEvents(); } - private OsuHitObject end; + private OsuHitObject? end; - public OsuHitObject End + public OsuHitObject? End { get => end; set @@ -56,11 +58,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public void UnbindEvents() { - if (Start != null) - { - Start.DefaultsApplied -= onDefaultsApplied; - Start.PositionBindable.ValueChanged -= onPositionChanged; - } + Start.DefaultsApplied -= onDefaultsApplied; + Start.PositionBindable.ValueChanged -= onPositionChanged; if (End != null) { @@ -92,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections LifetimeStart = fadeInTime; LifetimeEnd = double.MaxValue; // This will be set by the connection. - Invalidated?.Invoke(); + Invalidated?.Invoke(this); } } } From 0098ac276090076b85faf01c8e934a358d9ee1f5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Jun 2021 16:53:27 +0900 Subject: [PATCH 27/28] Remove one TODO It can be removed because pooled drawables are always ready, and `FollowPointConnection` is also ready when applied. --- .../Objects/Drawables/Connections/FollowPointConnection.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 55f494d2f9..36a4d1abe3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -83,13 +83,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections fp.FadeIn(end.TimeFadeIn); fp.ScaleTo(end.Scale, end.TimeFadeIn, Easing.Out); fp.MoveTo(pointEndPosition, end.TimeFadeIn, Easing.Out); - fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn).Expire(); - finalTransformEndTime = fadeOutTime + end.TimeFadeIn; + finalTransformEndTime = fp.LifetimeEnd; } } - // todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed. entry.LifetimeEnd = finalTransformEndTime; } From d7da66d876ec5a99b2effc438dabbba9dbd446bf Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 4 Jun 2021 18:41:02 +0900 Subject: [PATCH 28/28] Bring back scheduling of follow point update --- .../Connections/FollowPointConnection.cs | 15 +++++++++------ .../Connections/FollowPointLifetimeEntry.cs | 4 ++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 36a4d1abe3..001ea6c4ad 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects; @@ -26,26 +25,30 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { base.OnApply(entry); - entry.Invalidated += refreshPoints; - refreshPoints(entry); + entry.Invalidated += onEntryInvalidated; + refreshPoints(); } protected override void OnFree(FollowPointLifetimeEntry entry) { base.OnFree(entry); - entry.Invalidated -= refreshPoints; + entry.Invalidated -= onEntryInvalidated; // Return points to the pool. ClearInternal(false); } - private void refreshPoints(FollowPointLifetimeEntry entry) + private void onEntryInvalidated() => Scheduler.AddOnce(refreshPoints); + + private void refreshPoints() { ClearInternal(false); + var entry = Entry; + if (entry?.End == null) return; + OsuHitObject start = entry.Start; OsuHitObject end = entry.End; - Debug.Assert(end != null, $"{nameof(FollowPointLifetimeEntry)} without end hit object should never be alive"); double startTime = start.GetEndTime(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs index a44d07d92c..82bca0a4e2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointLifetimeEntry : LifetimeEntry { - public event Action? Invalidated; + public event Action? Invalidated; public readonly OsuHitObject Start; public FollowPointLifetimeEntry(OsuHitObject start) @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections LifetimeStart = fadeInTime; LifetimeEnd = double.MaxValue; // This will be set by the connection. - Invalidated?.Invoke(this); + Invalidated?.Invoke(); } } }