From 6dd80589dd65d420b749bce9a0a2045c6b975c00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2024 18:00:00 +0900 Subject: [PATCH] Add seasonal lighting Replaces kiai fountains for now. --- .../TestSceneMainMenuSeasonalLighting.cs | 46 +++++ osu.Game/Screens/Menu/MainMenu.cs | 4 +- .../Screens/Menu/MainMenuSeasonalLighting.cs | 188 ++++++++++++++++++ 3 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs create mode 100644 osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs new file mode 100644 index 0000000000..bfdc07fba6 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs @@ -0,0 +1,46 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + public partial class TestSceneMainMenuSeasonalLighting : OsuTestScene + { + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + + [Resolved] + private RealmAccess realm { get; set; } = null!; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("prepare beatmap", () => + { + var setInfo = beatmaps.QueryBeatmapSet(b => b.Protected && b.Hash == "7e26183e72a496f672c3a21292e6b469fdecd084d31c259ea10a31df5b46cd77"); + + Beatmap.Value = beatmaps.GetWorkingBeatmap(setInfo!.Value.Beatmaps.First()); + }); + + AddStep("create lighting", () => Child = new MainMenuSeasonalLighting()); + + AddStep("restart beatmap", () => + { + Beatmap.Value.Track.Start(); + Beatmap.Value.Track.Seek(4000); + }); + } + + [Test] + public void TestBasic() + { + } + } +} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 0630b9612e..42aa2342da 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -124,6 +124,7 @@ namespace osu.Game.Screens.Menu AddRangeInternal(new[] { + SeasonalUI.ENABLED ? new MainMenuSeasonalLighting() : Empty(), buttonsContainer = new ParallaxContainer { ParallaxAmount = 0.01f, @@ -166,7 +167,8 @@ namespace osu.Game.Screens.Menu Origin = Anchor.TopRight, Margin = new MarginPadding { Right = 15, Top = 5 } }, - new KiaiMenuFountains(), + // For now, this is too much alongside the seasonal lighting. + SeasonalUI.ENABLED ? Empty() : new KiaiMenuFountains(), bottomElementsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs b/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs new file mode 100644 index 0000000000..7ba4e998d2 --- /dev/null +++ b/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs @@ -0,0 +1,188 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Menu +{ + public partial class MainMenuSeasonalLighting : CompositeDrawable + { + private IBindable working = null!; + + private InterpolatingFramedClock beatmapClock = null!; + + private List hitObjects = null!; + + [Resolved] + private RulesetStore rulesets { get; set; } = null!; + + public MainMenuSeasonalLighting() + { + RelativeChildSize = new Vector2(512, 384); + + RelativeSizeAxes = Axes.X; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load(IBindable working) + { + this.working = working.GetBoundCopy(); + this.working.BindValueChanged(_ => Scheduler.AddOnce(updateBeatmap), true); + } + + private void updateBeatmap() + { + lastObjectIndex = null; + beatmapClock = new InterpolatingFramedClock(new FramedClock(working.Value.Track)); + hitObjects = working.Value.GetPlayableBeatmap(rulesets.GetRuleset(0)).HitObjects.SelectMany(h => h.NestedHitObjects.Prepend(h)) + .OrderBy(h => h.StartTime) + .ToList(); + } + + private int? lastObjectIndex; + + protected override void Update() + { + base.Update(); + + Height = DrawWidth / 16 * 10; + + beatmapClock.ProcessFrame(); + + // intentionally slightly early since we are doing fades on the lighting. + double time = beatmapClock.CurrentTime + 50; + + // handle seeks or OOB by skipping to current. + if (lastObjectIndex == null || lastObjectIndex >= hitObjects.Count || (lastObjectIndex >= 0 && hitObjects[lastObjectIndex.Value].StartTime > time) + || Math.Abs(beatmapClock.ElapsedFrameTime) > 500) + lastObjectIndex = hitObjects.Count(h => h.StartTime < time) - 1; + + while (lastObjectIndex < hitObjects.Count - 1) + { + var h = hitObjects[lastObjectIndex.Value + 1]; + + if (h.StartTime > time) + break; + + // Don't add lighting if the game is running too slow. + if (Clock.ElapsedFrameTime < 20) + addLight(h); + + lastObjectIndex++; + } + } + + private void addLight(HitObject h) + { + var light = new Light + { + RelativePositionAxes = Axes.Both, + Position = ((IHasPosition)h).Position + }; + + AddInternal(light); + + if (h.GetType().Name.Contains("Tick")) + { + light.Colour = SeasonalUI.AMBIENT_COLOUR_1; + light.Scale = new Vector2(0.5f); + light + .FadeInFromZero(250) + .Then() + .FadeOutFromOne(1000, Easing.Out); + + light.MoveToOffset(new Vector2(RNG.Next(-20, 20), RNG.Next(-20, 20)), 1400, Easing.Out); + } + else + { + // default green + Color4 col = SeasonalUI.PRIMARY_COLOUR_2; + + // whistle red + if (h.Samples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE)) + col = SeasonalUI.PRIMARY_COLOUR_1; + // clap is third colour + else if (h.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)) + col = SeasonalUI.AMBIENT_COLOUR_1; + + light.Colour = col; + + // finish larger lighting + if (h.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH)) + light.Scale = new Vector2(3); + + light + .FadeInFromZero(150) + .Then() + .FadeOutFromOne(1000, Easing.In); + + light.Expire(); + } + } + + public partial class Light : CompositeDrawable + { + private readonly Circle circle; + + public new Color4 Colour + { + set + { + circle.Colour = value.Darken(0.8f); + circle.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = value, + Radius = 80, + }; + } + } + + public Light() + { + InternalChildren = new Drawable[] + { + circle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(12), + Colour = SeasonalUI.AMBIENT_COLOUR_1, + Blending = BlendingParameters.Additive, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = SeasonalUI.AMBIENT_COLOUR_2, + Radius = 80, + } + } + }; + + Origin = Anchor.Centre; + Alpha = 0.5f; + } + } + } +}