diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs index 4eba9e6a96..dddc487bd6 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs @@ -62,7 +62,7 @@ namespace osu.Desktop.VisualTests.Tests Position = new Vector2(350, 300) }); - Add(new SwellCircle(new CirclePiece() + Add(new SwellCirclePiece(new CirclePiece { KiaiMode = kiai }) @@ -70,7 +70,7 @@ namespace osu.Desktop.VisualTests.Tests Position = new Vector2(100, 500) }); - Add(new SwellCircle(new StrongCirclePiece() + Add(new SwellCirclePiece(new StrongCirclePiece { KiaiMode = kiai }) @@ -97,28 +97,6 @@ namespace osu.Desktop.VisualTests.Tests }); } - private class SwellCircle : BaseCircle - { - public SwellCircle(CirclePiece piece) - : base(piece) - { - Piece.Add(new TextAwesome - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - TextSize = SYMBOL_INNER_SIZE, - Icon = FontAwesome.fa_asterisk, - Shadow = false - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Piece.AccentColour = colours.YellowDark; - } - } - private class DrumRollCircle : BaseCircle { public DrumRollCircle(CirclePiece piece) diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs index 5a988c54e1..2d30f6802b 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs @@ -6,6 +6,7 @@ using osu.Framework.Testing; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Taiko.Judgements; using osu.Game.Modes.Taiko.Objects; +using osu.Game.Modes.Taiko.Objects.Drawable; using osu.Game.Modes.Taiko.UI; namespace osu.Desktop.VisualTests.Tests @@ -22,6 +23,7 @@ namespace osu.Desktop.VisualTests.Tests AddButton("Hit!", addHitJudgement); AddButton("Miss :(", addMissJudgement); + AddButton("Swell", addSwell); Add(playfield = new TaikoPlayfield { @@ -60,6 +62,16 @@ namespace osu.Desktop.VisualTests.Tests }); } + private void addSwell() + { + playfield.Add(new DrawableSwell(new Swell + { + StartTime = Time.Current + 1000, + EndTime = Time.Current + 5000, + PreEmpt = 1000 + })); + } + private class DrawableTestHit : DrawableHitObject { public DrawableTestHit(TaikoHitObject hitObject) diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs index 15584ac73f..b5246321da 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs @@ -1,15 +1,35 @@ // 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 OpenTK.Input; +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 osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Modes.Taiko.Objects.Drawable.Pieces; using System; namespace osu.Game.Modes.Taiko.Objects.Drawable { public class DrawableSwell : DrawableTaikoHitObject { + /// + /// Invoked when the swell has reached the hit target, i.e. when CurrentTime >= StartTime. + /// This is only ever invoked once. + /// + public event Action OnStart; + + private const float target_ring_thick_border = 4f; + private const float target_ring_thin_border = 1f; + private const float target_ring_scale = 5f; + private const float inner_ring_alpha = 0.35f; + /// /// The amount of times the user has hit this swell. /// @@ -17,10 +37,94 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable private readonly Swell swell; + private readonly Container bodyContainer; + private readonly CircularContainer targetRing; + private readonly CircularContainer innerRing; + + private bool hasStarted; + public DrawableSwell(Swell swell) : base(swell) { this.swell = swell; + + Children = new Framework.Graphics.Drawable[] + { + bodyContainer = new Container + { + Children = new Framework.Graphics.Drawable[] + { + innerRing = new CircularContainer + { + Name = "Inner ring", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), + Masking = true, + Children = new [] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = inner_ring_alpha, + } + } + }, + targetRing = new CircularContainer + { + Name = "Target ring (thick border)", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), + Masking = true, + BorderThickness = target_ring_thick_border, + Children = new Framework.Graphics.Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }, + new CircularContainer + { + Name = "Target ring (thin border)", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = target_ring_thin_border, + BorderColour = Color4.White, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + } + } + }, + new SwellCirclePiece(new CirclePiece()) + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + innerRing.Colour = colours.YellowDark; + targetRing.BorderColour = colours.YellowDark.Opacity(0.25f); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + targetRing.Delay(HitObject.StartTime - Time.Current).ScaleTo(target_ring_scale, 600, EasingTypes.OutQuint); } protected override void CheckJudgement(bool userTriggered) @@ -32,6 +136,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable userHits++; + innerRing.FadeTo(1); + innerRing.FadeTo(inner_ring_alpha, 500, EasingTypes.OutQuint); + innerRing.ScaleTo(1f + (target_ring_scale - 1) * userHits / swell.RequiredHits, 1200, EasingTypes.OutElastic); + if (userHits == swell.RequiredHits) { Judgement.Result = HitResult.Hit; @@ -55,11 +163,35 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable protected override void UpdateState(ArmedState state) { + switch (state) + { + case ArmedState.Idle: + break; + case ArmedState.Miss: + FadeOut(100); + Expire(); + break; + case ArmedState.Hit: + bodyContainer.ScaleTo(1.2f, 400, EasingTypes.OutQuad); + + FadeOut(600); + Expire(); + break; + } } protected override void UpdateScrollPosition(double time) { - base.UpdateScrollPosition(Math.Min(time, HitObject.StartTime)); + // Make the swell stop at the hit target + double t = Math.Min(HitObject.StartTime, time); + + if (t == HitObject.StartTime && !hasStarted) + { + OnStart?.Invoke(); + hasStarted = true; + } + + base.UpdateScrollPosition(t); } protected override bool HandleKeyPress(Key key) diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs index c14dc6d7b0..5d6d669dc1 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs @@ -30,7 +30,6 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable protected override void LoadComplete() { LifetimeStart = HitObject.StartTime - HitObject.PreEmpt * 2; - LifetimeEnd = HitObject.StartTime + HitObject.PreEmpt; base.LoadComplete(); } diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/SwellCirclePiece.cs b/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/SwellCirclePiece.cs new file mode 100644 index 0000000000..8274a2bff0 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/SwellCirclePiece.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; + +namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces +{ + public class SwellCirclePiece : Container + { + private readonly CirclePiece circle; + + public SwellCirclePiece(CirclePiece piece) + { + Add(circle = piece); + + circle.Add(new TextAwesome + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextSize = CirclePiece.SYMBOL_INNER_SIZE, + Icon = FontAwesome.fa_asterisk, + Shadow = false + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + circle.AccentColour = colours.YellowDark; + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Swell.cs b/osu.Game.Modes.Taiko/Objects/Swell.cs index 20b9a6effb..4cbb5904c7 100644 --- a/osu.Game.Modes.Taiko/Objects/Swell.cs +++ b/osu.Game.Modes.Taiko/Objects/Swell.cs @@ -17,7 +17,7 @@ namespace osu.Game.Modes.Taiko.Objects /// /// The number of hits required to complete the swell successfully. /// - public int RequiredHits { get; protected set; } + public int RequiredHits { get; protected set; } = 10; public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) { diff --git a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs index 9bc75a55f5..1dce4a5c7b 100644 --- a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Primitives; +using osu.Game.Modes.Taiko.Objects.Drawable; namespace osu.Game.Modes.Taiko.UI { @@ -52,7 +53,7 @@ namespace osu.Game.Modes.Taiko.UI private readonly Container judgementContainer; private readonly Container hitObjectContainer; - //private Container topLevelHitContainer; + private readonly Container topLevelHitContainer; private readonly Container leftBackgroundContainer; private readonly Container rightBackgroundContainer; private readonly Box leftBackground; @@ -154,10 +155,10 @@ namespace osu.Game.Modes.Taiko.UI }, } }, - //topLevelHitContainer = new Container - //{ - // RelativeSizeAxes = Axes.Both, - //} + topLevelHitContainer = new Container + { + RelativeSizeAxes = Axes.Both, + } }); } @@ -177,6 +178,11 @@ namespace osu.Game.Modes.Taiko.UI h.Scale = new Vector2(PLAYFIELD_SCALE); base.Add(h); + + // Swells should be moved at the very top of the playfield when they reach the hit target + var swell = h as DrawableSwell; + if (swell != null) + swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy()); } public override void OnJudgement(DrawableHitObject judgedObject) diff --git a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj index d2aecc8d2e..2e9034839f 100644 --- a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj +++ b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj @@ -58,6 +58,7 @@ +