diff --git a/osu-framework b/osu-framework index 3c074a0981..db625dc65f 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 3c074a0981844fbaa9f2ecbf879c542f07e2b94d +Subproject commit db625dc65fb7ae9be154b03a0968b2f8cedb036d diff --git a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs b/osu.Game.Tests/Visual/TestCaseIntroSequence.cs new file mode 100644 index 0000000000..ba5cf8ef46 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseIntroSequence.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseIntroSequence : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuLogo), + }; + + public TestCaseIntroSequence() + { + OsuLogo logo; + + var rateAdjustClock = new StopwatchClock(true); + var framedClock = new FramedClock(rateAdjustClock); + framedClock.ProcessFrame(); + + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Clock = framedClock, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + logo = new OsuLogo + { + Anchor = Anchor.Centre, + } + } + }); + + AddStep(@"Restart", logo.PlayIntro); + AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseOsuGame.cs b/osu.Game.Tests/Visual/TestCaseOsuGame.cs new file mode 100644 index 0000000000..3f869e7378 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseOsuGame.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Game.Screens; +using osu.Game.Screens.Menu; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseOsuGame : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuLogo), + }; + + public TestCaseOsuGame() + { + var rateAdjustClock = new StopwatchClock(true); + var framedClock = new FramedClock(rateAdjustClock); + framedClock.ProcessFrame(); + + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }); + + Add(new Loader()); + + AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v); + } + } +} diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index e0e9fb7b5e..b1081890c8 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -110,6 +110,7 @@ + @@ -121,6 +122,7 @@ + diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index b000f08369..9598372d2d 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -16,12 +16,12 @@ namespace osu.Game.Configuration Set(OsuSetting.Ruleset, 0, 0, int.MaxValue); Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details); - Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10); - Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10); + Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); + Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1); Set(OsuSetting.SelectionRandomType, SelectionRandomType.RandomPermutation); - Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1); + Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1, 0.01); // Online settings Set(OsuSetting.Username, string.Empty); @@ -41,11 +41,11 @@ namespace osu.Game.Configuration Set(OsuSetting.MenuVoice, true); Set(OsuSetting.MenuMusic, true); - Set(OsuSetting.AudioOffset, 0, -500.0, 500.0); + Set(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1); // Input - Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2); - Set(OsuSetting.GameplayCursorSize, 1.0, 0.5f, 2); + Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2, 0.01); + Set(OsuSetting.GameplayCursorSize, 1.0, 0.5f, 2, 0.01); Set(OsuSetting.AutoCursorSize, false); Set(OsuSetting.MouseDisableButtons, false); @@ -63,13 +63,13 @@ namespace osu.Game.Configuration Set(OsuSetting.SnakingOutSliders, true); // Gameplay - Set(OsuSetting.DimLevel, 0.3, 0, 1); + Set(OsuSetting.DimLevel, 0.3, 0, 1, 0.01); Set(OsuSetting.ShowInterface, true); Set(OsuSetting.KeyOverlay, false); Set(OsuSetting.FloatingComments, false); - Set(OsuSetting.PlaybackSpeed, 1.0, 0.5f, 2); + Set(OsuSetting.PlaybackSpeed, 1.0, 0.5f, 2, 0.01); // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 295b3603be..faa8b7e40a 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -2,11 +2,13 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Screens.Menu; +using OpenTK; namespace osu.Game.Screens { - internal class Loader : OsuScreen + public class Loader : OsuScreen { public override bool ShowOverlays => false; @@ -15,8 +17,20 @@ namespace osu.Game.Screens ValidForResume = false; } + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + logo.RelativePositionAxes = Axes.Both; + logo.Triangles = false; + logo.Position = new Vector2(0.9f); + logo.Scale = new Vector2(0.2f); + + logo.FadeInFromZero(5000, Easing.OutQuint); + } + [BackgroundDependencyLoader] - private void load(OsuGame game) + private void load(OsuGameBase game) { if (game.IsDeployedBuild) LoadComponentAsync(new Disclaimer(), d => Push(d)); diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index e4dbe00a80..33d118d12e 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -39,12 +39,25 @@ namespace osu.Game.Screens.Menu //todo: make these non-internal somehow. internal const float BUTTON_AREA_HEIGHT = 100; + internal const float BUTTON_WIDTH = 140f; internal const float WEDGE_WIDTH = 20; - public const int EXIT_DELAY = 3000; + private OsuLogo logo; + + public void SetOsuLogo(OsuLogo logo) + { + this.logo = logo; + + if (this.logo != null) + { + this.logo.Action = onOsuLogo; + + // osuLogo.SizeForFlow relies on loading to be complete. + buttonFlow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0); + } + } - private readonly OsuLogo osuLogo; private readonly Drawable iconFacade; private readonly Container buttonArea; private readonly Box buttonAreaBackground; @@ -99,12 +112,6 @@ namespace osu.Game.Screens.Menu } } }, - osuLogo = new OsuLogo - { - Action = onOsuLogo, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - } }; buttonsPlay.Add(new Button(@"solo", @"select-6", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); @@ -127,14 +134,6 @@ namespace osu.Game.Screens.Menu sampleBack = audio.Sample.Get(@"Menu/select-4"); } - protected override void LoadComplete() - { - base.LoadComplete(); - - // osuLogo.SizeForFlow relies on loading to be complete. - buttonFlow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + osuLogo.SizeForFlow / 4), 0); - } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { if (args.Repeat) return false; @@ -142,7 +141,7 @@ namespace osu.Game.Screens.Menu switch (args.Key) { case Key.Space: - osuLogo.TriggerOnClick(state); + logo?.TriggerOnClick(state); return true; case Key.Escape: switch (State) @@ -215,24 +214,31 @@ namespace osu.Game.Screens.Menu backButton.ContractStyle = 0; settingsButton.ContractStyle = 0; - bool fromInitial = lastState == MenuState.Initial; - if (state == MenuState.TopLevel) buttonArea.FinishTransforms(true); - using (buttonArea.BeginDelayedSequence(fromInitial ? 150 : 0, true)) + using (buttonArea.BeginDelayedSequence(lastState == MenuState.Initial ? 150 : 0, true)) { switch (state) { case MenuState.Exit: case MenuState.Initial: + trackingPosition = false; + buttonAreaBackground.ScaleTo(Vector2.One, 500, Easing.Out); buttonArea.FadeOut(300); - osuLogo.Delay(150) - .Schedule(() => toolbar?.Hide()) - .ScaleTo(1, 800, Easing.OutExpo) - .MoveTo(Vector2.Zero, 800, Easing.OutExpo); + logo?.Delay(150) + .Schedule(() => + { + toolbar?.Hide(); + + logo.ClearTransforms(targetMember: nameof(Position)); + logo.RelativePositionAxes = Axes.Both; + + logo.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); + logo.ScaleTo(1, 800, Easing.OutExpo); + }); foreach (Button b in buttonsTopLevel) b.State = ButtonState.Contracted; @@ -240,27 +246,40 @@ namespace osu.Game.Screens.Menu foreach (Button b in buttonsPlay) b.State = ButtonState.Contracted; - if (state == MenuState.Exit) - { - osuLogo.RotateTo(20, EXIT_DELAY * 1.5f); - osuLogo.FadeOut(EXIT_DELAY); - } - else if (lastState == MenuState.TopLevel) + if (state != MenuState.Exit && lastState == MenuState.TopLevel) sampleBack?.Play(); break; case MenuState.TopLevel: buttonAreaBackground.ScaleTo(Vector2.One, 200, Easing.Out); - var sequence = osuLogo - .ScaleTo(0.5f, 200, Easing.In) - .MoveTo(buttonFlow.DrawPosition, 200, Easing.In); + logo.ClearTransforms(targetMember: nameof(Position)); + logo.RelativePositionAxes = Axes.None; - if (fromInitial && osuLogo.Scale.X > 0.5f) - sequence.OnComplete(o => - { - o.Impact(); - toolbar?.Show(); - }); + trackingPosition = true; + + switch (lastState) + { + case MenuState.Initial: + logo.ScaleTo(0.5f, 200, Easing.In); + + trackingPosition = false; + logo + .MoveTo(iconTrackingPosition, lastState == MenuState.EnteringMode ? 0 : 200, Easing.In) + .OnComplete(o => + { + trackingPosition = true; + + if (logo.Scale.X > 0.5f) + { + o.Impact(); + toolbar?.Show(); + } + }); + break; + default: + logo.ScaleTo(0.5f, 200, Easing.OutQuint); + break; + } buttonArea.FadeIn(300); @@ -280,6 +299,8 @@ namespace osu.Game.Screens.Menu case MenuState.EnteringMode: buttonAreaBackground.ScaleTo(new Vector2(2, 0), 300, Easing.InSine); + trackingPosition = true; + buttonsTopLevel.ForEach(b => b.ContractStyle = 1); buttonsPlay.ForEach(b => b.ContractStyle = 1); backButton.ContractStyle = 1; @@ -301,15 +322,24 @@ namespace osu.Game.Screens.Menu } } + private Vector2 iconTrackingPosition => logo.Parent.ToLocalSpace(iconFacade.ScreenSpaceDrawQuad.Centre); + + private bool trackingPosition; + protected override void Update() { //if (OsuGame.IdleTime > 6000 && State != MenuState.Exit) // State = MenuState.Initial; - osuLogo.Interactive = Alpha > 0.2f; - - iconFacade.Width = osuLogo.SizeForFlow * 0.5f; base.Update(); + + if (logo != null) + { + if (trackingPosition) + logo.Position = iconTrackingPosition; + + iconFacade.Width = logo.SizeForFlow * 0.5f; + } } } diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index d767d4eb12..0445733b23 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -14,13 +14,14 @@ using osu.Game.Beatmaps.IO; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; +using OpenTK; using OpenTK.Graphics; namespace osu.Game.Screens.Menu { public class Intro : OsuScreen { - private readonly OsuLogo logo; + private readonly IntroSequence introSequence; private const string menu_music_beatmap_hash = "3c8b1fcc9434dbb29e2fb613d3b9eada9d7bb6c125ceb32396c3b53437280c83"; @@ -39,32 +40,10 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => new BackgroundScreenEmpty(); - public Intro() - { - Children = new Drawable[] - { - new ParallaxContainer - { - ParallaxAmount = 0.01f, - Children = new Drawable[] - { - logo = new OsuLogo - { - Alpha = 0, - Triangles = false, - Blending = BlendingMode.Additive, - Interactive = false, - Colour = Color4.DarkGray, - Ripple = false - } - } - } - }; - } - private Bindable menuVoice; private Bindable menuMusic; private Track track; + private readonly ParallaxContainer parallax; [BackgroundDependencyLoader] private void load(AudioManager audio, OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) @@ -123,14 +102,48 @@ namespace osu.Game.Screens.Menu { DidLoadMenu = true; Push(mainMenu); - }, 2300); - }, 600); + }, delay_step_one); + }, delay_step_two); + } - logo.ScaleTo(0.4f); - logo.FadeOut(); + private const double delay_step_one = 2300; + private const double delay_step_two = 600; - logo.ScaleTo(1, 4400, Easing.OutQuint); - logo.FadeIn(20000, Easing.OutQuint); + public const int EXIT_DELAY = 3000; + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + logo.RelativePositionAxes = Axes.Both; + logo.Colour = Color4.White; + logo.Ripple = false; + + const int quick_appear = 350; + + int initialMovementTime = logo.Alpha > 0.2f ? quick_appear : 0; + + logo.MoveTo(new Vector2(0.5f), initialMovementTime, Easing.OutQuint); + + if (!resuming) + { + logo.Triangles = true; + + logo.ScaleTo(1); + logo.FadeIn(); + logo.PlayIntro(); + } + else + { + logo.Triangles = false; + + logo + .ScaleTo(1, initialMovementTime, Easing.OutQuint) + .FadeIn(quick_appear, Easing.OutQuint) + .Then() + .RotateTo(20, EXIT_DELAY * 1.5f) + .FadeOut(EXIT_DELAY); + } } protected override void OnSuspending(Screen next) @@ -150,7 +163,7 @@ namespace osu.Game.Screens.Menu if (!(last is MainMenu)) Content.FadeIn(300); - double fadeOutTime = 2000; + double fadeOutTime = EXIT_DELAY; //we also handle the exit transition. if (menuVoice) seeya.Play(); diff --git a/osu.Game/Screens/Menu/IntroSequence.cs b/osu.Game/Screens/Menu/IntroSequence.cs new file mode 100644 index 0000000000..5fca389708 --- /dev/null +++ b/osu.Game/Screens/Menu/IntroSequence.cs @@ -0,0 +1,297 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Menu +{ + public class IntroSequence : Container + { + private const float logo_size = 460; //todo: this should probably be 480 + + private OsuSpriteText welcomeText; + + private Container lines; + + private Box lineTopLeft; + private Box lineBottomLeft; + private Box lineTopRight; + private Box lineBottomRight; + + private Ring smallRing; + private Ring mediumRing; + private Ring bigRing; + + private Box backgroundFill; + private Box foregroundFill; + + private CircularContainer pinkCircle; + private CircularContainer blueCircle; + private CircularContainer yellowCircle; + private CircularContainer purpleCircle; + + public IntroSequence() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + const int line_offset = 80; + const int circle_offset = 250; + + Children = new Drawable[] + { + lines = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new [] + { + lineTopLeft = new Box + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.Centre, + Position = new Vector2(-line_offset, -line_offset), + Rotation = 45, + Colour = Color4.White.Opacity(180), + }, + lineTopRight = new Box + { + Origin = Anchor.CentreRight, + Anchor = Anchor.Centre, + Position = new Vector2(line_offset, -line_offset), + Rotation = -45, + Colour = Color4.White.Opacity(80), + }, + lineBottomLeft = new Box + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.Centre, + Position = new Vector2(-line_offset, line_offset), + Rotation = -45, + Colour = Color4.White.Opacity(230), + }, + lineBottomRight = new Box + { + Origin = Anchor.CentreRight, + Anchor = Anchor.Centre, + Position = new Vector2(line_offset, line_offset), + Rotation = 45, + Colour = Color4.White.Opacity(130), + }, + } + }, + bigRing = new Ring(OsuColour.FromHex(@"B6C5E9"), 0.85f), + mediumRing = new Ring(Color4.White.Opacity(130), 0.7f), + smallRing = new Ring(Color4.White, 0.6f), + welcomeText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "welcome", + Padding = new MarginPadding { Bottom = 10 }, + Font = @"Exo2.0-Light", + Alpha = 0, + TextSize = 42, + Spacing = new Vector2(5), + }, + new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(logo_size), + Masking = true, + Children = new Drawable[] + { + backgroundFill = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Height = 0, + Colour = OsuColour.FromHex(@"C6D8FF").Opacity(160), + }, + foregroundFill = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = Vector2.Zero, + RelativeSizeAxes = Axes.Both, + Width = 0, + Colour = Color4.White, + }, + } + }, + purpleCircle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Position = new Vector2(0, circle_offset), + Colour = OsuColour.FromHex(@"AA92FF"), + }, + blueCircle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + Position = new Vector2(-circle_offset, 0), + Colour = OsuColour.FromHex(@"8FE5FE"), + }, + yellowCircle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + Position = new Vector2(0, -circle_offset), + Colour = OsuColour.FromHex(@"FFD64C"), + }, + pinkCircle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + Position = new Vector2(circle_offset, 0), + Colour = OsuColour.FromHex(@"e967a1"), + }, + }; + + foreach (var line in lines) + { + line.Size = new Vector2(105, 1.5f); + line.Alpha = 0; + } + + Scale = new Vector2(0.5f); + } + + public void Start(double length) + { + if (Children.Any()) + { + // restart if we were already run previously. + FinishTransforms(true); + load(); + } + + smallRing.ResizeTo(logo_size * 0.086f, 400, Easing.InOutQuint); + + mediumRing.ResizeTo(130, 340, Easing.OutQuad); + mediumRing.Foreground.ResizeTo(1, 880, Easing.Out); + + Func remainingTime = () => length - TransformDelay; + + using (BeginDelayedSequence(250, true)) + { + welcomeText.FadeIn(700); + welcomeText.TransformSpacingTo(new Vector2(20, 0), remainingTime(), Easing.Out); + + const int line_duration = 700; + const int line_resize = 150; + + foreach (var line in lines) + { + line.FadeIn(40).ResizeWidthTo(0, line_duration - line_resize, Easing.OutQuint); + } + + const int line_end_offset = 120; + + smallRing.Foreground.ResizeTo(1, line_duration, Easing.OutQuint); + + lineTopLeft.MoveTo(new Vector2(-line_end_offset, -line_end_offset), line_duration, Easing.OutQuint); + lineTopRight.MoveTo(new Vector2(line_end_offset, -line_end_offset), line_duration, Easing.OutQuint); + lineBottomLeft.MoveTo(new Vector2(-line_end_offset, line_end_offset), line_duration, Easing.OutQuint); + lineBottomRight.MoveTo(new Vector2(line_end_offset, line_end_offset), line_duration, Easing.OutQuint); + + using (BeginDelayedSequence(length * 0.56, true)) + { + bigRing.ResizeTo(logo_size, 500, Easing.InOutQuint); + bigRing.Foreground.Delay(250).ResizeTo(1, 850, Easing.OutQuint); + + using (BeginDelayedSequence(250, true)) + { + backgroundFill.ResizeHeightTo(1, remainingTime(), Easing.InOutQuart); + backgroundFill.RotateTo(-90, remainingTime(), Easing.InOutQuart); + + using (BeginDelayedSequence(50, true)) + { + foregroundFill.ResizeWidthTo(1, remainingTime(), Easing.InOutQuart); + foregroundFill.RotateTo(-90, remainingTime(), Easing.InOutQuart); + } + + this.ScaleTo(1, remainingTime(), Easing.InOutCubic); + + const float circle_size = logo_size * 0.9f; + + const int rotation_delay = 110; + const int appear_delay = 80; + + purpleCircle.MoveToY(circle_size / 2, remainingTime(), Easing.InOutQuart); + purpleCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); + purpleCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); + + using (BeginDelayedSequence(appear_delay, true)) + { + yellowCircle.MoveToY(-circle_size / 2, remainingTime(), Easing.InOutQuart); + yellowCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); + yellowCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); + + using (BeginDelayedSequence(appear_delay, true)) + { + blueCircle.MoveToX(-circle_size / 2, remainingTime(), Easing.InOutQuart); + blueCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); + blueCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); + + using (BeginDelayedSequence(appear_delay, true)) + { + pinkCircle.MoveToX(circle_size / 2, remainingTime(), Easing.InOutQuart); + pinkCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); + pinkCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); + } + } + } + } + } + } + } + + private class Ring : Container + { + public readonly Circle Foreground; + + public Ring(Color4 ringColour, float foregroundSize) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Children = new[] + { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.98f), + Colour = ringColour, + }, + Foreground = new Circle + { + Size = new Vector2(foregroundSize), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + } + }; + } + } + } +} diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 4b8942349d..3e7662a441 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -19,7 +19,7 @@ using osu.Framework.Allocation; namespace osu.Game.Screens.Menu { - internal class LogoVisualisation : Drawable, IHasAccentColour + public class LogoVisualisation : Drawable, IHasAccentColour { private readonly Bindable beatmap = new Bindable(); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index ff902bf28b..691e8eab04 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; +using OpenTK.Graphics; using OpenTK.Input; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -58,13 +59,16 @@ namespace osu.Game.Screens.Menu }; } - [BackgroundDependencyLoader] - private void load(OsuGame game) + [BackgroundDependencyLoader(true)] + private void load(OsuGame game = null) { LoadComponentAsync(background); - buttons.OnSettings = game.ToggleSettings; - buttons.OnDirect = game.ToggleDirect; + if (game != null) + { + buttons.OnSettings = game.ToggleSettings; + buttons.OnDirect = game.ToggleDirect; + } preloadSongSelect(); } @@ -102,6 +106,29 @@ namespace osu.Game.Screens.Menu Beatmap.ValueChanged += beatmap_ValueChanged; } + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + buttons.SetOsuLogo(logo); + + logo.Triangles = true; + logo.Ripple = false; + + logo.FadeColour(Color4.White, 100, Easing.OutQuint); + logo.FadeIn(100, Easing.OutQuint); + + if (resuming) + buttons.State = MenuState.TopLevel; + } + + protected override void LogoSuspending(OsuLogo logo) + { + logo.FadeOut(300, Easing.InSine) + .ScaleTo(0.2f, 300, Easing.InSine) + .OnComplete(l => buttons.SetOsuLogo(null)); + } + private void beatmap_ValueChanged(WorkingBeatmap newValue) { if (!IsCurrentScreen) @@ -135,8 +162,6 @@ namespace osu.Game.Screens.Menu const float length = 300; - buttons.State = MenuState.TopLevel; - Content.FadeIn(length, Easing.OutQuint); Content.MoveTo(new Vector2(0, 0), length, Easing.OutQuint); diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 6f4a46b10b..dccb910d86 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -29,6 +29,8 @@ namespace osu.Game.Screens.Menu { public readonly Color4 OsuPink = OsuColour.FromHex(@"e967a1"); + private const double transition_length = 300; + private readonly Sprite logo; private readonly CircularContainer logoContainer; private readonly Container logoBounceContainer; @@ -37,6 +39,8 @@ namespace osu.Game.Screens.Menu private readonly Container logoHoverContainer; private readonly LogoVisualisation visualizer; + private readonly IntroSequence intro; + private SampleChannel sampleClick; private SampleChannel sampleBeat; @@ -54,7 +58,7 @@ namespace osu.Game.Screens.Menu public bool Triangles { - set { colourAndTriangles.Alpha = value ? 1 : 0; } + set { colourAndTriangles.FadeTo(value ? 1 : 0, transition_length, Easing.OutQuint); } } public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => logoContainer.ReceiveMouseInputAt(screenSpacePos); @@ -62,10 +66,9 @@ namespace osu.Game.Screens.Menu public bool Ripple { get { return rippleContainer.Alpha > 0; } - set { rippleContainer.Alpha = value ? 1 : 0; } + set { rippleContainer.FadeTo(value ? 1 : 0, transition_length, Easing.OutQuint); } } - public bool Interactive = true; private readonly Box flashLayer; private readonly Container impactContainer; @@ -76,17 +79,23 @@ namespace osu.Game.Screens.Menu public OsuLogo() { + // Required to make Schedule calls run in OsuScreen even when we are not visible. + AlwaysPresent = true; + EarlyActivationMilliseconds = early_activation; Size = new Vector2(default_size); - Anchor = Anchor.Centre; Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; Children = new Drawable[] { + intro = new IntroSequence + { + RelativeSizeAxes = Axes.Both, + }, logoHoverContainer = new Container { AutoSizeAxes = Axes.Both, @@ -266,6 +275,17 @@ namespace osu.Game.Screens.Menu } } + public void PlayIntro() + { + const double length = 3150; + const double fade = 200; + + logoHoverContainer.FadeOut().Delay(length).FadeIn(fade); + intro.Show(); + intro.Start(length); + intro.Delay(length + fade).FadeOut(); + } + protected override void Update() { base.Update(); @@ -290,9 +310,11 @@ namespace osu.Game.Screens.Menu } } + private bool interactive => Action != null && Alpha > 0.2f; + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - if (!Interactive) return false; + if (!interactive) return false; logoBounceContainer.ScaleTo(0.9f, 1000, Easing.Out); return true; @@ -306,7 +328,7 @@ namespace osu.Game.Screens.Menu protected override bool OnClick(InputState state) { - if (!Interactive) return false; + if (!interactive) return false; sampleClick.Play(); @@ -320,7 +342,7 @@ namespace osu.Game.Screens.Menu protected override bool OnHover(InputState state) { - if (!Interactive) return false; + if (!interactive) return false; logoHoverContainer.ScaleTo(1.1f, 500, Easing.OutElastic); return true; diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 2a3cba0d49..3dd175ca88 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -10,7 +10,9 @@ using osu.Game.Graphics.Containers; using OpenTK; using osu.Framework.Audio.Sample; using osu.Framework.Audio; +using osu.Framework.Graphics; using osu.Game.Rulesets; +using osu.Game.Screens.Menu; namespace osu.Game.Screens { @@ -30,6 +32,8 @@ namespace osu.Game.Screens public virtual bool HasLocalCursorDisplayed => false; + private OsuLogo logo; + /// /// Whether the beatmap or ruleset should be allowed to be changed by the user or game. /// Used to mark exclusive areas where this is strongly prohibited, like gameplay. @@ -72,9 +76,16 @@ namespace osu.Game.Screens protected override void OnResuming(Screen last) { base.OnResuming(last); + logo.DelayUntilTransformsFinished().Schedule(() => LogoArriving(logo, true)); sampleExit?.Play(); } + protected override void OnSuspending(Screen next) + { + base.OnSuspending(next); + onSuspendingLogo(); + } + protected override void OnEntering(Screen last) { OsuScreen lastOsu = last as OsuScreen; @@ -106,11 +117,19 @@ namespace osu.Game.Screens }); } + if ((logo = lastOsu?.logo) == null) + AddInternal(logo = new OsuLogo()); + base.OnEntering(last); + + logo.DelayUntilTransformsFinished().Schedule(() => LogoArriving(logo, false)); } protected override bool OnExiting(Screen next) { + if (ValidForResume && logo != null) + onExitingLogo(); + OsuScreen nextOsu = next as OsuScreen; if (Background != null && !Background.Equals(nextOsu?.Background)) @@ -128,5 +147,40 @@ namespace osu.Game.Screens Beatmap.UnbindAll(); return false; } + + /// + /// Fired when this screen was entered or resumed and the logo state is required to be adjusted. + /// + protected virtual void LogoArriving(OsuLogo logo, bool resuming) + { + logo.Action = null; + logo.FadeOut(300, Easing.OutQuint); + } + + private void onExitingLogo() + { + logo.ClearTransforms(); + LogoExiting(logo); + } + + /// + /// Fired when this screen was exited to add any outwards transition to the logo. + /// + protected virtual void LogoExiting(OsuLogo logo) + { + } + + private void onSuspendingLogo() + { + logo.ClearTransforms(); + LogoSuspending(logo); + } + + /// + /// Fired when this screen was suspended to add any outwards transition to the logo. + /// + protected virtual void LogoSuspending(OsuLogo logo) + { + } } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 71c2ec9a6d..53a2dcc41f 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -10,9 +10,9 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Backgrounds; -using osu.Game.Screens.Menu; using OpenTK; using osu.Framework.Localisation; +using osu.Game.Screens.Menu; namespace osu.Game.Screens.Play { @@ -20,7 +20,6 @@ namespace osu.Game.Screens.Play { private Player player; - private readonly OsuLogo logo; private BeatmapMetadataDisplay info; private bool showOverlays = true; @@ -39,15 +38,6 @@ namespace osu.Game.Screens.Play showOverlays = false; ValidForResume = true; }; - - Children = new Drawable[] - { - logo = new OsuLogo - { - Scale = new Vector2(0.15f), - Interactive = false, - }, - }; } [BackgroundDependencyLoader] @@ -101,11 +91,24 @@ namespace osu.Game.Screens.Play contentIn(); - logo.Delay(500).MoveToOffset(new Vector2(0, -180), 500, Easing.InOutExpo); info.Delay(750).FadeIn(500); this.Delay(2150).Schedule(pushWhenLoaded); } + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + logo.ClearTransforms(targetMember: nameof(Position)); + logo.RelativePositionAxes = Axes.Both; + + logo.ScaleTo(new Vector2(0.15f), 300, Easing.In); + logo.MoveTo(new Vector2(0.5f), 300, Easing.In); + logo.FadeIn(350); + + logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo); + } + private void pushWhenLoaded() { if (player.LoadState != LoadState.Ready) diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 00f311e522..40c3cf0fd4 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Menu; namespace osu.Game.Screens.Select { @@ -31,12 +30,9 @@ namespace osu.Game.Screens.Select private const float padding = 80; public Action OnBack; - public Action OnStart; private readonly FillFlowContainer buttons; - public OsuLogo StartButton; - /// Text on the button. /// Colour of the button. /// Hotkey of the button. @@ -106,13 +102,6 @@ namespace osu.Game.Screens.Select Height = 3, Position = new Vector2(0, -3), }, - StartButton = new OsuLogo - { - Anchor = Anchor.BottomRight, - Scale = new Vector2(0.4f), - Position = new Vector2(-70, -25), - Action = () => OnStart?.Invoke() - }, new BackButton { Anchor = Anchor.BottomLeft, @@ -143,8 +132,6 @@ namespace osu.Game.Screens.Select updateModeLight(); } - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => base.ReceiveMouseInputAt(screenSpacePos) || StartButton.ReceiveMouseInputAt(screenSpacePos); - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; protected override bool OnClick(InputState state) => true; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e11eed7040..987fef2541 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -20,6 +20,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; +using osu.Game.Screens.Menu; using osu.Game.Screens.Select.Options; namespace osu.Game.Screens.Select @@ -153,7 +154,6 @@ namespace osu.Game.Screens.Select Add(Footer = new Footer { OnBack = Exit, - OnStart = () => carouselRaisedStart(), }); FooterPanels.Add(BeatmapOptions = new BeatmapOptionsOverlay()); @@ -309,6 +309,41 @@ namespace osu.Game.Screens.Select FilterControl.Activate(); } + private const double logo_transition = 250; + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + logo.ClearTransforms(); + logo.RelativePositionAxes = Axes.Both; + + Vector2 position = new Vector2(0.95f, 0.96f); + + if (logo.Alpha > 0.8f) + { + logo.MoveTo(position, 500, Easing.OutQuint); + } + else + { + logo.Hide(); + logo.ScaleTo(0.2f); + logo.MoveTo(position); + } + + logo.FadeIn(logo_transition, Easing.OutQuint); + logo.ScaleTo(0.4f, logo_transition, Easing.OutQuint); + + logo.Action = () => carouselRaisedStart(); + } + + protected override void LogoExiting(OsuLogo logo) + { + base.LogoExiting(logo); + logo.ScaleTo(0.2f, logo_transition, Easing.OutQuint); + logo.FadeOut(logo_transition, Easing.OutQuint); + } + private void beatmap_ValueChanged(WorkingBeatmap beatmap) { if (!IsCurrentScreen) return; @@ -350,6 +385,7 @@ namespace osu.Game.Screens.Select Content.FadeOut(100); FilterControl.Deactivate(); + return base.OnExiting(next); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ef2e573443..ab8112504d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -661,6 +661,7 @@ +