diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs index 4005c94b5a..7aeb75ef8d 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs @@ -1,12 +1,11 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using OpenTK; using OpenTK.Graphics; -using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Game.Graphics; using osu.Game.Modes.Taiko.Objects.Drawable.Pieces; namespace osu.Desktop.VisualTests.Tests @@ -24,7 +23,7 @@ namespace osu.Desktop.VisualTests.Tests AddToggleStep("Kiai", b => { kiai = !kiai; - Reset(); + updateKiaiState(); }); Add(new CirclePiece @@ -87,37 +86,27 @@ namespace osu.Desktop.VisualTests.Tests } }); - Add(new DrumRollCircle(new CirclePiece + Add(new CirclePiece { - KiaiMode = kiai - }) - { - Width = 250, - Position = new Vector2(575, 100) + Position = new Vector2(575, 100), + Width = 0.25f, + AccentColour = Color4.Orange, + KiaiMode = kiai, }); - Add(new DrumRollCircle(new StrongCirclePiece + Add(new StrongCirclePiece { + Position = new Vector2(575, 300), + Width = 0.25f, + AccentColour = Color4.Orange, KiaiMode = kiai - }) - { - Width = 250, - Position = new Vector2(575, 300) }); } - private class DrumRollCircle : BaseCircle + private void updateKiaiState() { - public DrumRollCircle(CirclePiece piece) - : base(piece) - { - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Piece.AccentColour = colours.YellowDark; - } + foreach (var c in Children.OfType()) + c.KiaiMode = kiai; } private abstract class BaseCircle : Container diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs index c0aa3af176..ecd6fe813e 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; using osu.Framework.MathUtils; using osu.Framework.Testing; using osu.Game.Modes.Objects.Drawables; @@ -26,6 +25,8 @@ namespace osu.Desktop.VisualTests.Tests AddStep("Hit!", addHitJudgement); AddStep("Miss :(", addMissJudgement); + AddStep("DrumRoll", () => addDrumRoll(false)); + AddStep("Strong DrumRoll", () => addDrumRoll(true)); AddStep("Swell", addSwell); AddStep("Centre", () => addCentreHit(false)); AddStep("Strong Centre", () => addCentreHit(true)); @@ -36,7 +37,6 @@ namespace osu.Desktop.VisualTests.Tests { RelativeSizeAxes = Axes.X, Y = 200, - Padding = new MarginPadding { Left = 200 }, Children = new[] { playfield = new TaikoPlayfield() @@ -82,6 +82,18 @@ namespace osu.Desktop.VisualTests.Tests PreEmpt = 1000 })); } + + private void addDrumRoll(bool strong) + { + var d = new DrumRoll + { + StartTime = Time.Current + 1000, + Distance = 20000, + PreEmpt = 1000, + }; + + playfield.Add(strong ? new DrawableStrongDrumRoll(d) : new DrawableDrumRoll(d)); + } private void addCentreHit(bool strong) { diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRoll.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRoll.cs index 3551538fe7..4697625c5e 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRoll.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRoll.cs @@ -1,38 +1,90 @@ // 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.MathUtils; +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.Linq; namespace osu.Game.Modes.Taiko.Objects.Drawable { public class DrawableDrumRoll : DrawableTaikoHitObject { + /// + /// Number of rolling hits required to reach the dark/final accent colour. + /// + private const int rolling_hits_for_dark_accent = 5; + private readonly DrumRoll drumRoll; + private readonly CirclePiece circle; + + private Color4 accentDarkColour; + + /// + /// Rolling number of tick hits. This increases for hits and decreases for misses. + /// + private int rollingHits; + public DrawableDrumRoll(DrumRoll drumRoll) : base(drumRoll) { this.drumRoll = drumRoll; - int tickIndex = 0; + RelativeSizeAxes = Axes.X; + Width = (float)(drumRoll.Duration / drumRoll.PreEmpt); + + Add(circle = CreateCirclePiece()); + foreach (var tick in drumRoll.Ticks) { var newTick = new DrawableDrumRollTick(tick) { - Depth = tickIndex, X = (float)((tick.StartTime - HitObject.StartTime) / drumRoll.Duration) }; - AddNested(newTick); + newTick.OnJudgement += onTickJudgement; - tickIndex++; + AddNested(newTick); + Add(newTick); } } - protected override void UpdateState(ArmedState state) + [BackgroundDependencyLoader] + private void load(OsuColour colours) { + circle.AccentColour = AccentColour = colours.YellowDark; + accentDarkColour = colours.YellowDarker; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // This is naive, however it's based on the reasoning that the hit target + // is further than mid point of the play field, so the time taken to scroll in should always + // be greater than the time taken to scroll out to the left of the screen. + // Thus, using PreEmpt here is enough for the drum roll to completely scroll out. + LifetimeEnd = drumRoll.EndTime + drumRoll.PreEmpt; + } + + private void onTickJudgement(DrawableHitObject obj) + { + if (obj.Judgement.Result == HitResult.Hit) + rollingHits++; + else + rollingHits--; + + rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_dark_accent); + + Color4 newAccent = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_dark_accent, AccentColour, accentDarkColour, 0, 1); + circle.FadeAccent(newAccent, 100); } protected override void CheckJudgement(bool userTriggered) @@ -53,5 +105,11 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable else Judgement.Result = HitResult.Miss; } + + protected override void UpdateState(ArmedState state) + { + } + + protected virtual CirclePiece CreateCirclePiece() => new CirclePiece(); } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRollTick.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRollTick.cs index 5217fd9085..b7509bc51d 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRollTick.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRollTick.cs @@ -5,20 +5,66 @@ using OpenTK.Input; using osu.Game.Modes.Taiko.Judgements; using System; using osu.Game.Modes.Objects.Drawables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Modes.Taiko.Objects.Drawable { public class DrawableDrumRollTick : DrawableTaikoHitObject { + /// + /// The size of a tick. + /// + private const float tick_size = TaikoHitObject.CIRCLE_RADIUS / 2; + + /// + /// Any tick that is not the first for a drumroll is not filled, but is instead displayed + /// as a hollow circle. This is what controls the border width of that circle. + /// + private const float tick_border_width = tick_size / 4; + private readonly DrumRollTick tick; + private readonly CircularContainer bodyContainer; + public DrawableDrumRollTick(DrumRollTick tick) : base(tick) { this.tick = tick; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.Centre; + + RelativePositionAxes = Axes.X; + Size = new Vector2(tick_size); + + Children = new[] + { + bodyContainer = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = tick_border_width, + BorderColour = Color4.White, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = tick.FirstTick ? 1 : 0, + AlwaysPresent = true + } + } + } + }; } - protected override TaikoJudgement CreateJudgement() => new TaikoDrumRollTickJudgement(); + protected override TaikoJudgement CreateJudgement() => new TaikoDrumRollTickJudgement { SecondHit = tick.IsStrong }; protected override void CheckJudgement(bool userTriggered) { @@ -38,11 +84,17 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable protected override void UpdateState(ArmedState state) { + switch (state) + { + case ArmedState.Hit: + bodyContainer.ScaleTo(0, 100, EasingTypes.OutQuint); + break; + } } protected override void UpdateScrollPosition(double time) { - // Drum roll ticks shouldn't move + // Ticks don't move } protected override bool HandleKeyPress(Key key) diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongDrumRoll.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongDrumRoll.cs new file mode 100644 index 0000000000..e9723a0162 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongDrumRoll.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Modes.Taiko.Objects.Drawable.Pieces; + +namespace osu.Game.Modes.Taiko.Objects.Drawable +{ + public class DrawableStrongDrumRoll : DrawableDrumRoll + { + public DrawableStrongDrumRoll(DrumRoll drumRoll) + : base(drumRoll) + { + } + + protected override TaikoJudgement CreateJudgement() => new TaikoJudgement { SecondHit = true }; + + protected override CirclePiece CreateCirclePiece() => new StrongCirclePiece(); + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs index 5d6d669dc1..8da05d8bed 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs @@ -22,7 +22,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable : base(hitObject) { Anchor = Anchor.CentreLeft; - Origin = Anchor.Centre; + Origin = Anchor.CentreLeft; RelativePositionAxes = Axes.X; } diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs b/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs index 2321ad30ee..d51c06bcad 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs @@ -36,10 +36,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces { accentColour = value; - innerBackground.Colour = AccentColour; - - triangles.ColourLight = AccentColour; - triangles.ColourDark = AccentColour.Darken(0.1f); + background.Colour = AccentColour; resetEdgeEffects(); } @@ -69,10 +66,8 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces protected override Container Content => SymbolContainer; protected readonly Container SymbolContainer; + private readonly Container background; private readonly Container innerLayer; - private readonly Container innerCircleContainer; - private readonly Box innerBackground; - private readonly Triangles triangles; public CirclePiece() { @@ -88,26 +83,28 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces RelativeSizeAxes = Axes.Y, Children = new Framework.Graphics.Drawable[] { - innerCircleContainer = new CircularContainer + background = new CircularContainer { - Name = "Inner Circle", + Name = "Background", Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Masking = true, Children = new Framework.Graphics.Drawable[] { - innerBackground = new Box + new Box { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, }, - triangles = new Triangles + new Triangles { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, + ColourLight = Color4.White, + ColourDark = Color4.White.Darken(0.1f) } } }, @@ -150,7 +147,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces private void resetEdgeEffects() { - innerCircleContainer.EdgeEffect = new EdgeEffect + background.EdgeEffect = new EdgeEffect { Type = EdgeEffectType.Glow, Colour = AccentColour, diff --git a/osu.Game.Modes.Taiko/Objects/DrumRoll.cs b/osu.Game.Modes.Taiko/Objects/DrumRoll.cs index 40277e18fb..ccf86654b5 100644 --- a/osu.Game.Modes.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Modes.Taiko/Objects/DrumRoll.cs @@ -30,13 +30,13 @@ namespace osu.Game.Modes.Taiko.Objects /// /// Velocity of the drum roll in positional length units per millisecond. /// - public double Velocity { get; protected set; } + public double Velocity { get; protected set; } = 5; /// /// The distance between ticks of this drumroll. /// Half of this value is the hit window of the ticks. /// - public double TickTimeDistance { get; protected set; } + public double TickTimeDistance { get; protected set; } = 100; /// /// Number of drum roll ticks required for a "Good" hit. @@ -93,6 +93,7 @@ namespace osu.Game.Modes.Taiko.Objects PreEmpt = PreEmpt, TickTimeDistance = TickTimeDistance, StartTime = t, + IsStrong = IsStrong, Sample = new HitSampleInfo { Type = SampleType.None, diff --git a/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs b/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs index e70e2d3811..5640e1df30 100644 --- a/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs +++ b/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs @@ -2,15 +2,17 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; +using osu.Framework.Graphics; using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Replays; using osu.Game.Modes.Scoring; using osu.Game.Modes.Taiko.Beatmaps; using osu.Game.Modes.Taiko.Judgements; using osu.Game.Modes.Taiko.Objects; -using osu.Game.Modes.Taiko.Replays; +using osu.Game.Modes.Taiko.Objects.Drawable; using osu.Game.Modes.Taiko.Scoring; using osu.Game.Modes.UI; +using osu.Game.Modes.Replays; +using osu.Game.Modes.Taiko.Replays; namespace osu.Game.Modes.Taiko.UI { @@ -27,9 +29,44 @@ namespace osu.Game.Modes.Taiko.UI protected override IBeatmapProcessor CreateBeatmapProcessor() => new TaikoBeatmapProcessor(); - protected override Playfield CreatePlayfield() => new TaikoPlayfield(); + protected override Playfield CreatePlayfield() => new TaikoPlayfield + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }; - protected override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) => null; + protected override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) + { + var centreHit = h as CentreHit; + if (centreHit != null) + { + if (h.IsStrong) + return new DrawableStrongCentreHit(centreHit); + return new DrawableCentreHit(centreHit); + } + + var rimHit = h as RimHit; + if (rimHit != null) + { + if (h.IsStrong) + return new DrawableStrongRimHit(rimHit); + return new DrawableRimHit(rimHit); + } + + var drumRoll = h as DrumRoll; + if (drumRoll != null) + { + if (h.IsStrong) + return new DrawableStrongDrumRoll(drumRoll); + return new DrawableDrumRoll(drumRoll); + } + + var swell = h as Swell; + if (swell != null) + return new DrawableSwell(swell); + + return null; + } protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay); } diff --git a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj index 917be7a084..f3f93d720a 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 @@ + diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5bdb629393..c678e9bf37 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Play { var beatmap = Beatmap.Beatmap; - if (beatmap.BeatmapInfo?.Mode > PlayMode.Osu) + if (beatmap.BeatmapInfo?.Mode > PlayMode.Taiko) { //we only support osu! mode for now because the hitobject parsing is crappy and needs a refactor. Exit();