diff --git a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs index dceb7a9cff..8c913ae95e 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs @@ -129,8 +129,6 @@ namespace osu.Desktop.VisualTests.Tests }; Add(clockAdjustContainer); - - load(mode); } private int depth; diff --git a/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs new file mode 100644 index 0000000000..381e24c84c --- /dev/null +++ b/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs @@ -0,0 +1,92 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Rulesets.Mania.MathUtils +{ + /// + /// A PRNG specified in http://heliosphan.org/fastrandom.html. + /// + internal class FastRandom + { + private const double uint_to_real = 1.0 / (int.MaxValue + 1.0); + private const uint int_mask = 0x7FFFFFFF; + private const uint y = 842502087; + private const uint z = 3579807591; + private const uint w = 273326509; + private uint _x, _y = y, _z = z, _w = w; + + public FastRandom(int seed) + { + _x = (uint)seed; + } + + public FastRandom() + : this(Environment.TickCount) + { + } + + /// + /// Generates a random unsigned integer within the range [, ). + /// + /// The random value. + public uint NextUInt() + { + uint t = _x ^ _x << 11; + _x = _y; + _y = _z; + _z = _w; + return _w = _w ^ _w >> 19 ^ t ^ t >> 8; + } + + /// + /// Generates a random integer value within the range [0, ). + /// + /// The random value. + public int Next() => (int)(int_mask & NextUInt()); + + /// + /// Generates a random integer value within the range [0, ). + /// + /// The upper bound. + /// The random value. + public int Next(int upperBound) => (int)(NextDouble() * upperBound); + + /// + /// Generates a random integer value within the range [, ). + /// + /// The lower bound of the range. + /// The upper bound of the range. + /// The random value. + public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound)); + + /// + /// Generates a random double value within the range [0, 1). + /// + /// The random value. + public double NextDouble() => uint_to_real * NextUInt(); + + private uint bitBuffer; + private int bitIndex = 32; + + /// + /// Generates a reandom boolean value. Cached such that a random value is only generated once in every 32 calls. + /// + /// The random value. + public bool NextBool() + { + if (bitIndex == 32) + { + bitBuffer = NextUInt(); + bitIndex = 1; + + return (bitBuffer & 1) == 1; + } + + bitIndex++; + return ((bitBuffer >>= 1) & 1) == 1; + } + + } +} diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 00deaba85d..52396debf5 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -48,6 +48,7 @@ + diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a8ff231cc7..854a9b5f49 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -1,15 +1,16 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using OpenTK; using OpenTK.Graphics; -using osu.Game.Rulesets.Osu.UI; +using osu.Game.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Allocation; +using osu.Game.Screens.Ranking; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -18,9 +19,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly Spinner spinner; private readonly SpinnerDisc disc; + private readonly SpinnerTicks ticks; + + private readonly Container mainContainer; + private readonly SpinnerBackground background; private readonly Container circleContainer; - private readonly DrawableHitCircle circle; + private readonly CirclePiece circle; + private readonly GlowPiece glow; + + private readonly TextAwesome symbol; + + private Color4 normalColour; + private Color4 completeColour; public DrawableSpinner(Spinner s) : base(s) { @@ -29,57 +40,91 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; Position = s.Position; - //take up full playfield. - Size = new Vector2(OsuPlayfield.BASE_SIZE.X); + RelativeSizeAxes = Axes.Both; + + // we are slightly bigger than our parent, to clip the top and bottom of the circle + Height = 1.3f; spinner = s; Children = new Drawable[] { - background = new SpinnerBackground - { - Alpha = 0, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - DiscColour = Color4.Black - }, - disc = new SpinnerDisc - { - Alpha = 0, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - DiscColour = AccentColour - }, circleContainer = new Container { AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Children = new [] + Children = new Drawable[] { - circle = new DrawableHitCircle(s) + glow = new GlowPiece(), + circle = new CirclePiece { - Interactive = false, Position = Vector2.Zero, Anchor = Anchor.Centre, - } + }, + new RingPiece(), + symbol = new TextAwesome + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + UseFullGlyphHeight = true, + TextSize = 48, + Icon = FontAwesome.fa_asterisk, + Shadow = false, + }, } - } + }, + mainContainer = new AspectContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + background = new SpinnerBackground + { + Alpha = 0.6f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + disc = new SpinnerDisc(spinner) + { + Scale = Vector2.Zero, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + circleContainer.CreateProxy(), + ticks = new SpinnerTicks + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, }; - - background.Scale = scaleToCircle; - disc.Scale = scaleToCircle; } + public float Progress => MathHelper.Clamp(disc.RotationAbsolute / 360 / spinner.SpinsRequired, 0, 1); + protected override void CheckJudgement(bool userTriggered) { if (Time.Current < HitObject.StartTime) return; - disc.ScaleTo(Interpolation.ValueAt(Math.Sqrt(Progress), scaleToCircle, Vector2.One, 0, 1), 100); - - if (Progress >= 1) + if (Progress >= 1 && !disc.Complete) + { disc.Complete = true; + const float duration = 200; + + disc.FadeAccent(completeColour, duration); + + background.FadeAccent(completeColour, duration); + background.FadeOut(duration); + + circle.FadeColour(completeColour, duration); + glow.FadeColour(completeColour, duration); + } + if (!userTriggered && Time.Current >= spinner.EndTime) { if (Progress >= 1) @@ -106,26 +151,48 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - private Vector2 scaleToCircle => circle.Scale * circle.DrawWidth / DrawWidth * 0.95f; + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + normalColour = colours.SpinnerBase; - public float Progress => MathHelper.Clamp(disc.RotationAbsolute / 360 / spinner.SpinsRequired, 0, 1); + background.AccentColour = normalColour; + + completeColour = colours.YellowLight.Opacity(0.6f); + + disc.AccentColour = colours.SpinnerFill; + circle.Colour = colours.BlueDark; + glow.Colour = colours.BlueDark; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + circle.Rotation = disc.Rotation; + ticks.Rotation = disc.Rotation; + + float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; + disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, EasingTypes.OutQuint); + + symbol.RotateTo(disc.Rotation / 2, 500, EasingTypes.OutQuint); + } protected override void UpdatePreemptState() { base.UpdatePreemptState(); - circleContainer.ScaleTo(1, 400, EasingTypes.OutElastic); + circleContainer.ScaleTo(spinner.Scale * 0.3f); + circleContainer.ScaleTo(spinner.Scale, TIME_PREEMPT / 1.4f, EasingTypes.OutQuint); - background.Delay(TIME_PREEMPT - 500); + disc.RotateTo(-720); + symbol.RotateTo(-720); - background.ScaleTo(scaleToCircle * 1.2f, 400, EasingTypes.OutQuint); - background.FadeIn(200); + mainContainer.ScaleTo(0); + mainContainer.ScaleTo(spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, TIME_PREEMPT - 150, EasingTypes.OutQuint); - background.Delay(400); - background.ScaleTo(1, 250, EasingTypes.OutQuint); - - disc.Delay(TIME_PREEMPT - 50); - disc.FadeIn(200); + mainContainer.Delay(TIME_PREEMPT - 150); + mainContainer.ScaleTo(1, 500, EasingTypes.OutQuint); } protected override void UpdateCurrentState(ArmedState state) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index 9a90c07517..3004dafda7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { private readonly Sprite disc; - public Func Hit; public CirclePiece() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs index 72024bbe99..1c54f9f893 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs @@ -1,10 +1,55 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; + namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { - public class SpinnerBackground : SpinnerDisc + public class SpinnerBackground : CircularContainer, IHasAccentColour { public override bool HandleInput => false; + + protected Box Disc; + + public Color4 AccentColour + { + get + { + return Disc.Colour; + } + set + { + Disc.Colour = value; + + EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Glow, + Radius = 14, + Colour = value.Opacity(0.3f), + }; + } + } + + public SpinnerBackground() + { + RelativeSizeAxes = Axes.Both; + Masking = true; + + Children = new Drawable[] + { + Disc = new Box + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Alpha = 1, + }, + }; + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 71adba74c7..4e4d4e30b9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -2,13 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Transforms; using osu.Framework.Input; using osu.Game.Graphics; @@ -17,104 +12,31 @@ using OpenTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { - public class SpinnerDisc : CircularContainer + public class SpinnerDisc : CircularContainer, IHasAccentColour { - protected Sprite Disc; + private readonly Spinner spinner; - public SRGBColour DiscColour + public Color4 AccentColour { - get { return Disc.Colour; } - set { Disc.Colour = value; } + get { return background.AccentColour; } + set { background.AccentColour = value; } } - private Color4 completeColour; + private readonly SpinnerBackground background; - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private const float idle_alpha = 0.2f; + private const float tracking_alpha = 0.4f; + + public SpinnerDisc(Spinner s) { - completeColour = colours.YellowLight.Opacity(0.8f); - Masking = true; - } + spinner = s; - private class SpinnerBorder : Container - { - public SpinnerBorder() - { - Origin = Anchor.Centre; - Anchor = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - - layout(); - } - - private int lastLayoutDotCount; - private void layout() - { - int count = (int)(MathHelper.Pi * ScreenSpaceDrawQuad.Width / 9); - - if (count == lastLayoutDotCount) return; - - lastLayoutDotCount = count; - - while (Children.Count() < count) - { - Add(new CircularContainer - { - Colour = Color4.White, - RelativePositionAxes = Axes.Both, - Masking = true, - Origin = Anchor.Centre, - Size = new Vector2(1 / ScreenSpaceDrawQuad.Width * 2000), - Children = new[] - { - new Box - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - } - } - }); - } - - var size = new Vector2(1 / ScreenSpaceDrawQuad.Width * 2000); - - int i = 0; - foreach (var d in Children) - { - d.Size = size; - d.Position = new Vector2( - 0.5f + (float)Math.Sin((float)i / count * 2 * MathHelper.Pi) / 2, - 0.5f + (float)Math.Cos((float)i / count * 2 * MathHelper.Pi) / 2 - ); - - i++; - } - } - - protected override void Update() - { - base.Update(); - layout(); - } - } - - public SpinnerDisc() - { AlwaysReceiveInput = true; - RelativeSizeAxes = Axes.Both; Children = new Drawable[] { - Disc = new Box - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, - }, - new SpinnerBorder() + background = new SpinnerBackground { Alpha = idle_alpha }, }; } @@ -125,10 +47,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces set { if (value == tracking) return; - tracking = value; - Disc.FadeTo(tracking ? 0.5f : 0.2f, 100); + background.FadeTo(tracking ? tracking_alpha : idle_alpha, 100); } } @@ -139,31 +60,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces set { if (value == complete) return; - complete = value; - Disc.FadeColour(completeColour, 200); - updateCompleteTick(); } } protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - Tracking = true; + Tracking |= state.Mouse.HasMainButtonPressed; return base.OnMouseDown(state, args); } protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) { - Tracking = false; + Tracking &= state.Mouse.HasMainButtonPressed; return base.OnMouseUp(state, args); } protected override bool OnMouseMove(InputState state) { Tracking |= state.Mouse.HasMainButtonPressed; - mousePosition = state.Mouse.Position; + mousePosition = Parent.ToLocalSpace(state.Mouse.NativeState.Position); return base.OnMouseMove(state); } @@ -177,13 +95,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360)); + private bool rotationTransferred; + protected override void Update() { base.Update(); var thisAngle = -(float)MathHelper.RadiansToDegrees(Math.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2)); - if (tracking) + + bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; + + if (validAndTracking) { + if (!rotationTransferred) + { + currentRotation = Rotation * 2; + rotationTransferred = true; + } + if (thisAngle - lastAngle > 180) lastAngle += 360; else if (lastAngle - thisAngle > 180) @@ -192,17 +121,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces currentRotation += thisAngle - lastAngle; RotationAbsolute += Math.Abs(thisAngle - lastAngle); } + lastAngle = thisAngle; if (Complete && updateCompleteTick()) { - Disc.Flush(flushType: typeof(TransformAlpha)); - Disc.FadeTo(0.75f, 30, EasingTypes.OutExpo); - Disc.Delay(30); - Disc.FadeTo(0.5f, 250, EasingTypes.OutQuint); + background.Flush(flushType: typeof(TransformAlpha)); + background.FadeTo(tracking_alpha + 0.4f, 60, EasingTypes.OutExpo); + background.Delay(60); + background.FadeTo(tracking_alpha, 250, EasingTypes.OutQuint); } - RotateTo(currentRotation, 100, EasingTypes.OutExpo); + RotateTo(currentRotation / 2, validAndTracking ? 500 : 1500, EasingTypes.OutExpo); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs new file mode 100644 index 0000000000..dc3d18d40a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs @@ -0,0 +1,71 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class SpinnerTicks : Container + { + private Color4 glowColour; + + public SpinnerTicks() + { + Origin = Anchor.Centre; + Anchor = Anchor.Centre; + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + glowColour = colours.BlueDarker.Opacity(0.4f); + layout(); + } + + private void layout() + { + const int count = 18; + + for (int i = 0; i < count; i++) + { + Add(new Container + { + Colour = Color4.Black, + Alpha = 0.4f, + EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Glow, + Radius = 20, + Colour = glowColour, + }, + RelativePositionAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + Size = new Vector2(60, 10), + Origin = Anchor.Centre, + Position = new Vector2( + 0.5f + (float)Math.Sin((float)i / count * 2 * MathHelper.Pi) / 2 * 0.86f, + 0.5f + (float)Math.Cos((float)i / count * 2 * MathHelper.Pi) / 2 * 0.86f + ), + Rotation = -(float)i / count * 360 + 90, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + } + } + }); + } + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 8974b1bcbd..b91bdc6a78 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -64,6 +64,7 @@ + @@ -103,9 +104,7 @@ - - - +