1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 16:02:55 +08:00

Implement basic hold note + tick input.

This commit is contained in:
smoogipooo 2017-05-24 20:45:01 +09:00
parent d0e280dbef
commit dcf879687d
11 changed files with 406 additions and 34 deletions

View File

@ -11,6 +11,9 @@ using OpenTK;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Timing; using osu.Game.Rulesets.Mania.Timing;
using osu.Framework.Configuration;
using OpenTK.Input;
using osu.Framework.Timing;
namespace osu.Desktop.VisualTests.Tests namespace osu.Desktop.VisualTests.Tests
{ {
@ -59,6 +62,48 @@ namespace osu.Desktop.VisualTests.Tests
} }
}; };
Action createPlayfieldWithNotesAcceptingInput = () =>
{
Clear();
var rateAdjustClock = new StopwatchClock(true) { Rate = 0.2 };
ManiaPlayfield playField;
Add(playField = new ManiaPlayfield(4, new List<TimingChange> { new TimingChange { BeatLength = 200 } })
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(1, -1),
Clock = new FramedClock(rateAdjustClock)
});
playField.Add(new DrawableNote(new Note
{
StartTime = 1000,
Column = 0
}, new Bindable<Key>(Key.D)));
playField.Add(new DrawableHoldNote(new HoldNote
{
StartTime = 1000,
Duration = 2000,
Column = 1
}, new Bindable<Key>(Key.F)));
playField.Add(new DrawableHoldNote(new HoldNote
{
StartTime = 1000,
Duration = 2000,
Column = 2
}, new Bindable<Key>(Key.J)));
playField.Add(new DrawableNote(new Note
{
StartTime = 1000,
Column = 3
}, new Bindable<Key>(Key.K)));
};
AddStep("1 column", () => createPlayfield(1, SpecialColumnPosition.Normal)); AddStep("1 column", () => createPlayfield(1, SpecialColumnPosition.Normal));
AddStep("4 columns", () => createPlayfield(4, SpecialColumnPosition.Normal)); AddStep("4 columns", () => createPlayfield(4, SpecialColumnPosition.Normal));
AddStep("Left special style", () => createPlayfield(4, SpecialColumnPosition.Left)); AddStep("Left special style", () => createPlayfield(4, SpecialColumnPosition.Left));
@ -76,6 +121,8 @@ namespace osu.Desktop.VisualTests.Tests
AddWaitStep(10); AddWaitStep(10);
AddStep("Right special style", () => createPlayfieldWithNotes(4, SpecialColumnPosition.Right)); AddStep("Right special style", () => createPlayfieldWithNotes(4, SpecialColumnPosition.Right));
AddWaitStep(10); AddWaitStep(10);
AddStep("Test", () => createPlayfieldWithNotesAcceptingInput());
} }
private void triggerKeyDown(Column column) private void triggerKeyDown(Column column)

View File

@ -471,14 +471,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
} }
else else
{ {
newObject = new HoldNote var holdNote = new HoldNote
{ {
StartTime = startTime, StartTime = startTime,
Samples = sampleInfoListAt(startTime),
EndSamples = sampleInfoListAt(endTime),
Column = column, Column = column,
Duration = endTime - startTime Duration = endTime - startTime
}; };
holdNote.HeadNote.Samples = sampleInfoListAt(startTime);
holdNote.TailNote.Samples = sampleInfoListAt(endTime);
newObject = holdNote;
} }
pattern.Add(newObject); pattern.Add(newObject);

View File

@ -69,18 +69,21 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (holdNote) if (holdNote)
{ {
newObject = new HoldNote var hold = new HoldNote
{ {
StartTime = HitObject.StartTime, StartTime = HitObject.StartTime,
EndSamples = HitObject.Samples,
Column = column, Column = column,
Duration = endTime - HitObject.StartTime Duration = endTime - HitObject.StartTime
}; };
newObject.Samples.Add(new SampleInfo hold.HeadNote.Samples.Add(new SampleInfo
{ {
Name = SampleInfo.HIT_NORMAL Name = SampleInfo.HIT_NORMAL
}); });
hold.TailNote.Samples = HitObject.Samples;
newObject = hold;
} }
else else
{ {

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Mania.Judgements
{
public class HoldNoteTailJudgement : ManiaJudgement
{
/// <summary>
/// Whether the hold note has been released too early and shouldn't give full score for the release.
/// </summary>
public bool HasBroken;
}
}

View File

@ -0,0 +1,9 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Mania.Judgements
{
public class HoldNoteTickJudgement : ManiaJudgement
{
}
}

View File

@ -7,14 +7,47 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using OpenTK.Input; using OpenTK.Input;
using osu.Framework.Input;
using OpenTK;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
public class DrawableHoldNote : DrawableManiaHitObject<HoldNote> public class DrawableHoldNote : DrawableManiaHitObject<HoldNote>
{ {
private readonly NotePiece headPiece; private readonly DrawableNote headNote;
private readonly DrawableNote tailNote;
private readonly BodyPiece bodyPiece; private readonly BodyPiece bodyPiece;
private readonly NotePiece tailPiece; private readonly Container<DrawableHoldNoteTick> tickContainer;
/// <summary>
/// Relative time at which the user started holding this note.
/// </summary>
private double holdStartTime;
/// <summary>
/// Whether the hold note has been released too early and shouldn't give full score for the release.
/// </summary>
private bool hasBroken;
private bool _holding;
/// <summary>
/// Whether the user is holding the hold note.
/// </summary>
private bool holding
{
get { return _holding; }
set
{
if (_holding == value)
return;
_holding = value;
if (holding)
holdStartTime = Time.Current;
}
}
public DrawableHoldNote(HoldNote hitObject, Bindable<Key> key = null) public DrawableHoldNote(HoldNote hitObject, Bindable<Key> key = null)
: base(hitObject, key) : base(hitObject, key)
@ -32,17 +65,39 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
}, },
headPiece = new NotePiece tickContainer = new Container<DrawableHoldNoteTick>
{
RelativeSizeAxes = Axes.Both,
RelativeCoordinateSpace = new Vector2(1, (float)HitObject.Duration)
},
headNote = new DrawableHoldNoteHead(this, key)
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre Origin = Anchor.TopCentre
}, },
tailPiece = new NotePiece tailNote = new DrawableHoldNoteTail(this, key)
{ {
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre Origin = Anchor.TopCentre
} }
}); });
foreach (var tick in HitObject.Ticks)
{
var drawableTick = new DrawableHoldNoteTick(tick)
{
IsHolding = () => holding,
HoldStartTime = () => holdStartTime
};
drawableTick.Y -= (float)HitObject.StartTime;
tickContainer.Add(drawableTick);
AddNested(drawableTick);
}
AddNested(headNote);
AddNested(tailNote);
} }
public override Color4 AccentColour public override Color4 AccentColour
@ -54,9 +109,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
return; return;
base.AccentColour = value; base.AccentColour = value;
headPiece.AccentColour = value;
bodyPiece.AccentColour = value; bodyPiece.AccentColour = value;
tailPiece.AccentColour = value; headNote.AccentColour = value;
tailNote.AccentColour = value;
} }
} }
@ -64,14 +119,133 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
} }
protected override void Update() /// <summary>
/// Handles key down events on the body of the hold note.
/// </summary>
/// <param name="state">The input state.</param>
/// <param name="args">The key down args.</param>
/// <returns>Whether the key press was handled.</returns>
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{ {
if (Time.Current > HitObject.StartTime) // Make sure the keypress happened within reasonable bounds of the hold note
headPiece.Colour = Color4.Green; if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime)
if (Time.Current > HitObject.EndTime) return false;
if (args.Key != Key)
return false;
if (args.Repeat)
return false;
holding = true;
return true;
}
/// <summary>
/// Handles key up events on the body of the hold note.
/// </summary>
/// <param name="state">The input state.</param>
/// <param name="args">The key down args.</param>
/// <returns>Whether the key press was handled.</returns>
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
{
// Make sure that the user started holding the key during the hold note
if (!holding)
return false;
if (args.Key != Key)
return false;
holding = false;
// If the key has been released too early, they should not receive full score for the release
if (!tailNote.Judged)
hasBroken = true;
return true;
}
private class DrawableHoldNoteHead : DrawableNote
{
private readonly DrawableHoldNote holdNote;
public DrawableHoldNoteHead(DrawableHoldNote holdNote, Bindable<Key> key = null)
: base(holdNote.HitObject.HeadNote, key)
{ {
bodyPiece.Colour = Color4.Green; this.holdNote = holdNote;
tailPiece.Colour = Color4.Green;
RelativePositionAxes = Axes.None;
Y = 0;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (!base.OnKeyDown(state, args))
return false;
// We only want to trigger a holding state from the head if the head has received a judgement
if (Judgement.Result == HitResult.None)
return false;
// If the head has been missed, make sure the user also can't receive a full score for the release
if (Judgement.Result == HitResult.Miss)
holdNote.hasBroken = true;
holdNote.holding = true;
return true;
}
}
private class DrawableHoldNoteTail : DrawableNote
{
private readonly DrawableHoldNote holdNote;
public DrawableHoldNoteTail(DrawableHoldNote holdNote, Bindable<Key> key = null)
: base(holdNote.HitObject.TailNote, key)
{
this.holdNote = holdNote;
RelativePositionAxes = Axes.None;
Y = 0;
}
protected override ManiaJudgement CreateJudgement() => new HoldNoteTailJudgement();
protected override void CheckJudgement(bool userTriggered)
{
base.CheckJudgement(userTriggered);
var tailJudgement = Judgement as HoldNoteTailJudgement;
if (tailJudgement == null)
return;
tailJudgement.HasBroken = holdNote.hasBroken;
}
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
{
// Make sure that the user started holding the key during the hold note
if (!holdNote.holding)
return false;
if (Judgement.Result != HitResult.None)
return false;
if (args.Key != Key)
return false;
UpdateJudgement(true);
// Handled by the hold note, which will set holding = false
return false;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
// Tail doesn't handle key down
return false;
} }
} }
} }

View File

@ -0,0 +1,75 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
public class DrawableHoldNoteTick : DrawableManiaHitObject<HoldNoteTick>
{
public Func<double> HoldStartTime;
public Func<bool> IsHolding;
public DrawableHoldNoteTick(HoldNoteTick hitObject)
: base(hitObject, null)
{
RelativeSizeAxes = Axes.X;
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.X,
Height = 1
}
};
}
protected override ManiaJudgement CreateJudgement() => new HoldNoteTickJudgement();
protected override void CheckJudgement(bool userTriggered)
{
if (!userTriggered)
return;
if (Time.Current < HitObject.StartTime)
return;
if (HoldStartTime?.Invoke() > HitObject.StartTime)
return;
Judgement.ManiaResult = ManiaHitResult.Perfect;
Judgement.Result = HitResult.Hit;
}
protected override void UpdateState(ArmedState state)
{
switch (State)
{
case ArmedState.Hit:
Colour = Color4.Green;
break;
}
}
protected override void Update()
{
if (Judgement.Result != HitResult.None)
return;
if (IsHolding?.Invoke() != true)
return;
UpdateJudgement(true);
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database; using osu.Game.Database;
@ -12,32 +13,41 @@ namespace osu.Game.Rulesets.Mania.Objects
/// <summary> /// <summary>
/// Represents a hit object which requires pressing, holding, and releasing a key. /// Represents a hit object which requires pressing, holding, and releasing a key.
/// </summary> /// </summary>
public class HoldNote : Note, IHasEndTime public class HoldNote : ManiaHitObject, IHasEndTime
{ {
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// </summary>
private const double release_window_lenience = 1.5;
public double Duration { get; set; } public double Duration { get; set; }
public double EndTime => StartTime + Duration; public double EndTime => StartTime + Duration;
/// <summary> private Note headNote;
/// The samples to be played when this hold note is released. public Note HeadNote => headNote ?? (headNote = new Note { StartTime = StartTime });
/// </summary>
public SampleInfoList EndSamples = new SampleInfoList(); private Note tailNote;
public Note TailNote => tailNote ?? (tailNote = new HoldNoteTail { StartTime = EndTime });
/// <summary> /// <summary>
/// The key-release hit windows for this hold note. /// The length (in milliseconds) between ticks of this hold.
/// </summary> /// </summary>
public HitWindows ReleaseHitWindows { get; protected set; } = new HitWindows(); private double tickSpacing = 50;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) public IEnumerable<HoldNoteTick> Ticks => ticks ?? (ticks = createTicks());
private List<HoldNoteTick> ticks;
private List<HoldNoteTick> createTicks()
{ {
base.ApplyDefaults(controlPointInfo, difficulty); var ret = new List<HoldNoteTick>();
ReleaseHitWindows = HitWindows * release_window_lenience; if (tickSpacing == 0)
return ret;
for (double t = StartTime + HeadNote.HitWindows.Great / 2; t <= EndTime - TailNote.HitWindows.Great / 2; t+= tickSpacing)
{
ret.Add(new HoldNoteTick
{
StartTime = t
});
}
return ret;
} }
} }
} }

View File

@ -0,0 +1,24 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database;
namespace osu.Game.Rulesets.Mania.Objects
{
public class HoldNoteTail : Note
{
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// </summary>
private const double release_window_lenience = 1.5;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
HitWindows *= release_window_lenience;
}
}
}

View File

@ -0,0 +1,9 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Mania.Objects
{
public class HoldNoteTick : ManiaHitObject
{
}
}

View File

@ -57,10 +57,13 @@
<Compile Include="Beatmaps\Patterns\Pattern.cs" /> <Compile Include="Beatmaps\Patterns\Pattern.cs" />
<Compile Include="MathUtils\FastRandom.cs" /> <Compile Include="MathUtils\FastRandom.cs" />
<Compile Include="Judgements\HitWindows.cs" /> <Compile Include="Judgements\HitWindows.cs" />
<Compile Include="Judgements\HoldNoteTailJudgement.cs" />
<Compile Include="Judgements\HoldNoteTickJudgement.cs" />
<Compile Include="Judgements\ManiaHitResult.cs" /> <Compile Include="Judgements\ManiaHitResult.cs" />
<Compile Include="Judgements\ManiaJudgement.cs" /> <Compile Include="Judgements\ManiaJudgement.cs" />
<Compile Include="ManiaDifficultyCalculator.cs" /> <Compile Include="ManiaDifficultyCalculator.cs" />
<Compile Include="Objects\Drawables\DrawableHoldNote.cs" /> <Compile Include="Objects\Drawables\DrawableHoldNote.cs" />
<Compile Include="Objects\Drawables\DrawableHoldNoteTick.cs" />
<Compile Include="Objects\Drawables\DrawableManiaHitObject.cs" /> <Compile Include="Objects\Drawables\DrawableManiaHitObject.cs" />
<Compile Include="Objects\Drawables\DrawableNote.cs" /> <Compile Include="Objects\Drawables\DrawableNote.cs" />
<Compile Include="Objects\Drawables\Pieces\BodyPiece.cs" /> <Compile Include="Objects\Drawables\Pieces\BodyPiece.cs" />
@ -68,6 +71,8 @@
<Compile Include="Objects\Types\IHasColumn.cs" /> <Compile Include="Objects\Types\IHasColumn.cs" />
<Compile Include="Scoring\ManiaScoreProcessor.cs" /> <Compile Include="Scoring\ManiaScoreProcessor.cs" />
<Compile Include="Objects\HoldNote.cs" /> <Compile Include="Objects\HoldNote.cs" />
<Compile Include="Objects\HoldNoteTail.cs" />
<Compile Include="Objects\HoldNoteTick.cs" />
<Compile Include="Objects\ManiaHitObject.cs" /> <Compile Include="Objects\ManiaHitObject.cs" />
<Compile Include="Objects\Note.cs" /> <Compile Include="Objects\Note.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />