diff --git a/osu.Desktop.VisualTests/Tests/TestCaseMedalOverlay.cs b/osu.Desktop.VisualTests/Tests/TestCaseMedalOverlay.cs new file mode 100644 index 0000000000..1533f2141e --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseMedalOverlay.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Testing; +using osu.Game.Overlays; +using osu.Game.Users; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseMedalOverlay : TestCase + { + public override string Description => @"medal get!"; + + public TestCaseMedalOverlay() + { + AddStep(@"display", () => + { + LoadComponentAsync(new MedalOverlay(new Medal + { + Name = @"Animations", + InternalName = @"all-intro-doubletime", + Description = @"More complex than you think.", + }), Add); + }); + } + } +} diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index c11d79192e..b65bab0d17 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -231,6 +231,7 @@ + diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs new file mode 100644 index 0000000000..5f85474ede --- /dev/null +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -0,0 +1,304 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Sprites; +using osu.Game.Users; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Overlays.MedalSplash; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Audio; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input; +using OpenTK.Input; +using System.Linq; +using osu.Framework.Graphics.Shapes; +using System; +using osu.Framework.MathUtils; + +namespace osu.Game.Overlays +{ + public class MedalOverlay : FocusedOverlayContainer + { + public const float DISC_SIZE = 400; + + private const float border_width = 5; + + private readonly Medal medal; + private readonly Box background; + private readonly Container backgroundStrip, particleContainer; + private readonly BackgroundStrip leftStrip, rightStrip; + private readonly CircularContainer disc; + private readonly Sprite innerSpin, outerSpin; + private DrawableMedal drawableMedal; + + private SampleChannel getSample; + + public MedalOverlay(Medal medal) + { + this.medal = medal; + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(60), + }, + outerSpin = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(DISC_SIZE + 500), + Alpha = 0f, + }, + backgroundStrip = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = border_width, + Alpha = 0f, + Children = new[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + Width = 0.5f, + Padding = new MarginPadding { Right = DISC_SIZE / 2 }, + Children = new[] + { + leftStrip = new BackgroundStrip(0f, 1f) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + Width = 0.5f, + Padding = new MarginPadding { Left = DISC_SIZE / 2 }, + Children = new[] + { + rightStrip = new BackgroundStrip(1f, 0f), + }, + }, + }, + }, + particleContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Alpha = 0f, + }, + disc = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0f, + Masking = true, + AlwaysPresent = true, + BorderColour = Color4.White, + BorderThickness = border_width, + Size = new Vector2(DISC_SIZE), + Scale = new Vector2(0.8f), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"05262f"), + }, + new Triangles + { + RelativeSizeAxes = Axes.Both, + TriangleScale = 2, + ColourDark = OsuColour.FromHex(@"04222b"), + ColourLight = OsuColour.FromHex(@"052933"), + }, + innerSpin = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1.05f), + Alpha = 0.25f, + }, + }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures, AudioManager audio) + { + getSample = audio.Sample.Get(@"MedalSplash/medal-get"); + innerSpin.Texture = outerSpin.Texture = textures.Get(@"MedalSplash/disc-spin"); + + disc.EdgeEffect = leftStrip.EdgeEffect = rightStrip.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = colours.Blue.Opacity(0.5f), + Radius = 50, + }; + + disc.Add(drawableMedal = new DrawableMedal(medal) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Show(); + } + + protected override void Update() + { + base.Update(); + + particleContainer.Add(new MedalParticle(RNG.Next(0, 359))); + } + + protected override bool OnClick(InputState state) + { + dismiss(); + return true; + } + + protected override void OnFocusLost(InputState state) + { + if (state.Keyboard.Keys.Contains(Key.Escape)) dismiss(); + } + + private const double initial_duration = 400; + private const double step_duration = 900; + + protected override void PopIn() + { + base.PopIn(); + + FadeIn(200); + background.FlashColour(Color4.White.Opacity(0.25f), 400); + + getSample.Play(); + + using (innerSpin.BeginLoopedSequence()) + innerSpin.RotateTo(360, 20000); + + using (outerSpin.BeginLoopedSequence()) + outerSpin.RotateTo(360, 40000); + + using (BeginDelayedSequence(200, true)) + { + disc.FadeIn(initial_duration); + particleContainer.FadeIn(initial_duration); + outerSpin.FadeTo(0.1f, initial_duration * 2); + disc.ScaleTo(1f, initial_duration * 2, EasingTypes.OutElastic); + + using (BeginDelayedSequence(initial_duration + 200, true)) + { + backgroundStrip.FadeIn(step_duration); + leftStrip.ResizeWidthTo(1f, step_duration, EasingTypes.OutQuint); + rightStrip.ResizeWidthTo(1f, step_duration, EasingTypes.OutQuint); + Schedule(() => { if (drawableMedal.State != DisplayState.Full) drawableMedal.State = DisplayState.Icon; }); + + using (BeginDelayedSequence(step_duration, true)) + { + Schedule(() => { if (drawableMedal.State != DisplayState.Full) drawableMedal.State = DisplayState.MedalUnlocked; }); + + using (BeginDelayedSequence(step_duration, true)) + Schedule(() => { if (drawableMedal.State != DisplayState.Full) drawableMedal.State = DisplayState.Full; }); + } + } + } + } + + protected override void PopOut() + { + base.PopOut(); + FadeOut(200); + } + + private void dismiss() + { + if (drawableMedal.State != DisplayState.Full) + { + // if we haven't yet, play out the animation fully + drawableMedal.State = DisplayState.Full; + Flush(true); + return; + } + + Hide(); + Expire(); + } + + private class BackgroundStrip : Container + { + public BackgroundStrip(float start, float end) + { + RelativeSizeAxes = Axes.Both; + Width = 0f; + ColourInfo = ColourInfo.GradientHorizontal(Color4.White.Opacity(start), Color4.White.Opacity(end)); + Masking = true; + + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + } + }; + } + } + + private class MedalParticle : CircularContainer + { + private readonly float direction; + + private Vector2 positionForOffset(float offset) => new Vector2((float)(offset * Math.Sin(direction)), (float)(offset * Math.Cos(direction))); + + public MedalParticle(float direction) + { + this.direction = direction; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Position = positionForOffset(DISC_SIZE / 2); + Masking = true; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = colours.Blue.Opacity(0.5f), + Radius = 5, + }; + + MoveTo(positionForOffset(DISC_SIZE / 2 + 200), 500); + FadeOut(500); + Expire(); + } + } + } +} diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs new file mode 100644 index 0000000000..7d7ffbd12a --- /dev/null +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -0,0 +1,183 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework; +using OpenTK; +using osu.Framework.Allocation; +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; +using osu.Game.Users; + +namespace osu.Game.Overlays.MedalSplash +{ + public class DrawableMedal : Container, IStateful + { + private const float scale_when_unlocked = 0.76f; + private const float scale_when_full = 0.6f; + + private readonly Medal medal; + private readonly Container medalContainer; + private readonly Sprite medalSprite, medalGlow; + private readonly OsuSpriteText unlocked, name; + private readonly TextFlowContainer description; + private readonly FillFlowContainer infoFlow; + private DisplayState state; + public DrawableMedal(Medal medal) + { + this.medal = medal; + Position = new Vector2(0f, MedalOverlay.DISC_SIZE / 2); + + Children = new Drawable[] + { + medalContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Alpha = 0f, + Children = new Drawable[] + { + medalSprite = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.81f), + }, + medalGlow = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }, + }, + unlocked = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Medal Unlocked".ToUpper(), + TextSize = 24, + Font = @"Exo2.0-Light", + Alpha = 0f, + Scale = new Vector2(1f / scale_when_unlocked), + }, + infoFlow = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.6f, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 5f), + Children = new Drawable[] + { + name = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = medal.Name, + TextSize = 20, + Font = @"Exo2.0-Bold", + Alpha = 0f, + Scale = new Vector2(1f / scale_when_full), + }, + description = new TextFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0f, + Scale = new Vector2(1f / scale_when_full), + }, + }, + }, + }; + + description.AddText(medal.Description, s => + { + s.Anchor = Anchor.TopCentre; + s.Origin = Anchor.TopCentre; + s.TextSize = 16; + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures) + { + medalSprite.Texture = textures.Get(medal.ImageUrl); + medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); + description.Colour = colours.BlueLight; + + unlocked.Position = new Vector2(0f, medalContainer.Size.Y / 2 + 10); + infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); + } + + public DisplayState State + { + get { return state; } + set + { + if (state == value) return; + + state = value; + updateState(); + } + } + + private void updateState() + { + if (!IsLoaded) return; + + const double duration = 900; + + switch (state) + { + case DisplayState.None: + medalContainer.ScaleTo(0); + break; + case DisplayState.Icon: + medalContainer.ScaleTo(1, duration, EasingTypes.OutElastic); + medalContainer.FadeIn(duration); + break; + case DisplayState.MedalUnlocked: + medalContainer.ScaleTo(1); + medalContainer.Show(); + + ScaleTo(scale_when_unlocked, duration, EasingTypes.OutExpo); + MoveToY(MedalOverlay.DISC_SIZE / 2 - 30, duration, EasingTypes.OutExpo); + unlocked.FadeInFromZero(duration); + break; + case DisplayState.Full: + medalContainer.ScaleTo(1); + medalContainer.Show(); + + ScaleTo(scale_when_full, duration, EasingTypes.OutExpo); + MoveToY(MedalOverlay.DISC_SIZE / 2 - 60, duration, EasingTypes.OutExpo); + name.FadeInFromZero(duration + 100); + description.FadeInFromZero(duration * 2); + break; + } + + + } + } + + public enum DisplayState + { + None, + Icon, + MedalUnlocked, + Full, + } +} diff --git a/osu.Game/Users/Medal.cs b/osu.Game/Users/Medal.cs new file mode 100644 index 0000000000..aa7382b457 --- /dev/null +++ b/osu.Game/Users/Medal.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Users +{ + public class Medal + { + public string Name { get; set; } + public string InternalName { get; set; } + public string ImageUrl => $@"https://s.ppy.sh/images/medals-client/{InternalName}@2x.png"; + public string Description { get; set; } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 959642ce8b..f9ef7c864d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -489,6 +489,9 @@ + + +