mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 19:22:54 +08:00
Merge remote-tracking branch 'refs/remotes/ppy/master' into profile-overlay-graph-new
This commit is contained in:
commit
c2a7f2f356
@ -249,6 +249,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
if (action != Action.Value)
|
||||
return false;
|
||||
|
||||
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
|
||||
if (Time.Elapsed < 0)
|
||||
return false;
|
||||
|
||||
if (CheckHittable?.Invoke(this, Time.Current) == false)
|
||||
return false;
|
||||
|
||||
@ -281,6 +285,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
if (action != Action.Value)
|
||||
return;
|
||||
|
||||
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
|
||||
if (Time.Elapsed < 0)
|
||||
return;
|
||||
|
||||
// Make sure a hold was started
|
||||
if (HoldStartTime == null)
|
||||
return;
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Mod = new OsuModSpunOut(),
|
||||
Autoplay = false,
|
||||
Beatmap = singleSpinnerBeatmap,
|
||||
PassCondition = () => Player.ChildrenOfType<DrawableSpinner>().Single().Progress >= 1
|
||||
PassCondition = () => Player.ChildrenOfType<DrawableSpinner>().SingleOrDefault()?.Progress >= 1
|
||||
});
|
||||
|
||||
[TestCase(null)]
|
||||
@ -45,7 +45,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Mods = mods,
|
||||
Autoplay = false,
|
||||
Beatmap = singleSpinnerBeatmap,
|
||||
PassCondition = () => Precision.AlmostEquals(Player.ChildrenOfType<SpinnerSpmCounter>().Single().SpinsPerMinute, 286, 1)
|
||||
PassCondition = () =>
|
||||
{
|
||||
var counter = Player.ChildrenOfType<SpinnerSpmCounter>().SingleOrDefault();
|
||||
return counter != null && Precision.AlmostEquals(counter.SpinsPerMinute, 286, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut);
|
||||
}
|
||||
|
||||
private DrawableSlider slider;
|
||||
private DrawableSlider drawableSlider;
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
@ -68,7 +68,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
|
||||
double startTime = hitObjects[sliderIndex].StartTime;
|
||||
retrieveDrawableSlider(sliderIndex);
|
||||
addSeekStep(startTime);
|
||||
retrieveDrawableSlider((Slider)hitObjects[sliderIndex]);
|
||||
setSnaking(true);
|
||||
|
||||
ensureSnakingIn(startTime + fade_in_modifier);
|
||||
@ -93,7 +94,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
|
||||
double startTime = hitObjects[sliderIndex].StartTime;
|
||||
retrieveDrawableSlider(sliderIndex);
|
||||
addSeekStep(startTime);
|
||||
retrieveDrawableSlider((Slider)hitObjects[sliderIndex]);
|
||||
setSnaking(false);
|
||||
|
||||
ensureNoSnakingIn(startTime + fade_in_modifier);
|
||||
@ -127,9 +129,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
checkPositionChange(16600, sliderRepeat, positionDecreased);
|
||||
}
|
||||
|
||||
private void retrieveDrawableSlider(int index) =>
|
||||
AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () =>
|
||||
slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index));
|
||||
private void retrieveDrawableSlider(Slider slider) => AddUntilStep($"retrieve slider @ {slider.StartTime}", () =>
|
||||
(drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null);
|
||||
|
||||
private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased);
|
||||
private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame);
|
||||
@ -150,13 +151,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex;
|
||||
private Func<Vector2> positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func<Vector2>)sliderStart : sliderEnd;
|
||||
|
||||
private List<Vector2> sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve;
|
||||
private List<Vector2> sliderCurve => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
|
||||
private Vector2 sliderStart() => sliderCurve.First();
|
||||
private Vector2 sliderEnd() => sliderCurve.Last();
|
||||
|
||||
private Vector2 sliderRepeat()
|
||||
{
|
||||
var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1);
|
||||
var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == hitObjects[1]);
|
||||
var repeat = drawable.ChildrenOfType<Container<DrawableSliderRepeat>>().First().Children.First();
|
||||
return repeat.Position;
|
||||
}
|
||||
|
63
osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs
Normal file
63
osu.Game.Rulesets.Osu/Edit/DrawableOsuEditPool.cs
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public class DrawableOsuEditPool<T> : DrawableOsuPool<T>
|
||||
where T : DrawableHitObject, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
|
||||
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
|
||||
/// </summary>
|
||||
private const double editor_hit_object_fade_out_extension = 700;
|
||||
|
||||
public DrawableOsuEditPool(Func<DrawableHitObject, double, bool> checkHittable, Action<Drawable> onLoaded, int initialSize, int? maximumSize = null)
|
||||
: base(checkHittable, onLoaded, initialSize, maximumSize)
|
||||
{
|
||||
}
|
||||
|
||||
protected override T CreateNewDrawable() => base.CreateNewDrawable().With(d => d.ApplyCustomUpdateState += updateState);
|
||||
|
||||
private void updateState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
if (state == ArmedState.Idle)
|
||||
return;
|
||||
|
||||
// adjust the visuals of certain object types to make them stay on screen for longer than usual.
|
||||
switch (hitObject)
|
||||
{
|
||||
default:
|
||||
// there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.)
|
||||
return;
|
||||
|
||||
case DrawableSlider _:
|
||||
// no specifics to sliders but let them fade slower below.
|
||||
break;
|
||||
|
||||
case DrawableHitCircle circle: // also handles slider heads
|
||||
circle.ApproachCircle
|
||||
.FadeOutFromOne(editor_hit_object_fade_out_extension)
|
||||
.Expire();
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the existing fade out transform
|
||||
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
|
||||
|
||||
if (existing == null)
|
||||
return;
|
||||
|
||||
hitObject.RemoveTransform(existing);
|
||||
|
||||
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
|
||||
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
|
||||
}
|
||||
}
|
||||
}
|
@ -2,13 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
@ -17,62 +13,21 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public class DrawableOsuEditRuleset : DrawableOsuRuleset
|
||||
{
|
||||
/// <summary>
|
||||
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
|
||||
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
|
||||
/// </summary>
|
||||
private const double editor_hit_object_fade_out_extension = 700;
|
||||
|
||||
public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||
: base(ruleset, beatmap, mods)
|
||||
{
|
||||
}
|
||||
|
||||
public override DrawableHitObject<OsuHitObject> CreateDrawableRepresentation(OsuHitObject h)
|
||||
=> base.CreateDrawableRepresentation(h)?.With(d => d.ApplyCustomUpdateState += updateState);
|
||||
|
||||
private void updateState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
if (state == ArmedState.Idle)
|
||||
return;
|
||||
|
||||
// adjust the visuals of certain object types to make them stay on screen for longer than usual.
|
||||
switch (hitObject)
|
||||
{
|
||||
default:
|
||||
// there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.)
|
||||
return;
|
||||
|
||||
case DrawableSlider _:
|
||||
// no specifics to sliders but let them fade slower below.
|
||||
break;
|
||||
|
||||
case DrawableHitCircle circle: // also handles slider heads
|
||||
circle.ApproachCircle
|
||||
.FadeOutFromOne(editor_hit_object_fade_out_extension)
|
||||
.Expire();
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the existing fade out transform
|
||||
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
|
||||
|
||||
if (existing == null)
|
||||
return;
|
||||
|
||||
hitObject.RemoveTransform(existing);
|
||||
|
||||
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
|
||||
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
|
||||
}
|
||||
|
||||
protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor();
|
||||
protected override Playfield CreatePlayfield() => new OsuEditPlayfield();
|
||||
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { Size = Vector2.One };
|
||||
|
||||
private class OsuPlayfieldNoCursor : OsuPlayfield
|
||||
private class OsuEditPlayfield : OsuPlayfield
|
||||
{
|
||||
protected override GameplayCursorContainer CreateCursor() => null;
|
||||
|
||||
protected override DrawablePool<TDrawable> CreatePool<TDrawable>(int initialSize, int? maximumSize = null)
|
||||
=> new DrawableOsuEditPool<TDrawable>(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||
{
|
||||
foreach (var d in drawables)
|
||||
{
|
||||
d.HitObjectApplied += applyFadeInAdjustment;
|
||||
applyFadeInAdjustment(d);
|
||||
}
|
||||
|
||||
base.ApplyToDrawableHitObjects(drawables);
|
||||
}
|
||||
|
@ -31,6 +31,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
private Container scaleContainer;
|
||||
private InputManager inputManager;
|
||||
|
||||
public DrawableHitCircle()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableHitCircle([CanBeNull] HitCircle h = null)
|
||||
: base(h)
|
||||
{
|
||||
|
@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
private OsuInputManager osuActionInputManager;
|
||||
internal OsuInputManager OsuActionInputManager => osuActionInputManager ??= GetContainingInputManager() as OsuInputManager;
|
||||
|
||||
protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
|
||||
public virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
{
|
||||
|
32
osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuPool.cs
Normal file
32
osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuPool.cs
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableOsuPool<T> : DrawablePool<T>
|
||||
where T : DrawableHitObject, new()
|
||||
{
|
||||
private readonly Func<DrawableHitObject, double, bool> checkHittable;
|
||||
private readonly Action<Drawable> onLoaded;
|
||||
|
||||
public DrawableOsuPool(Func<DrawableHitObject, double, bool> checkHittable, Action<Drawable> onLoaded, int initialSize, int? maximumSize = null)
|
||||
: base(initialSize, maximumSize)
|
||||
{
|
||||
this.checkHittable = checkHittable;
|
||||
this.onLoaded = onLoaded;
|
||||
}
|
||||
|
||||
protected override T CreateNewDrawable() => base.CreateNewDrawable().With(o =>
|
||||
{
|
||||
var osuObject = (DrawableOsuHitObject)(object)o;
|
||||
|
||||
osuObject.CheckHittable = checkHittable;
|
||||
osuObject.OnLoadComplete += onLoaded;
|
||||
});
|
||||
}
|
||||
}
|
@ -40,6 +40,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
private Container<DrawableSliderTail> tailContainer;
|
||||
private Container<DrawableSliderTick> tickContainer;
|
||||
private Container<DrawableSliderRepeat> repeatContainer;
|
||||
private Container<PausableSkinnableSound> samplesContainer;
|
||||
|
||||
public DrawableSlider()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableSlider([CanBeNull] Slider s = null)
|
||||
: base(s)
|
||||
@ -63,6 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
Alpha = 0
|
||||
},
|
||||
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
|
||||
samplesContainer = new Container<PausableSkinnableSound> { RelativeSizeAxes = Axes.Both }
|
||||
};
|
||||
|
||||
PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||
@ -100,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
base.LoadSamples();
|
||||
|
||||
slidingSample?.Expire();
|
||||
samplesContainer.Clear();
|
||||
slidingSample = null;
|
||||
|
||||
var firstSample = HitObject.Samples.FirstOrDefault();
|
||||
@ -110,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
|
||||
clone.Name = "sliderslide";
|
||||
|
||||
AddInternal(slidingSample = new PausableSkinnableSound(clone)
|
||||
samplesContainer.Add(slidingSample = new PausableSkinnableSound(clone)
|
||||
{
|
||||
Looping = true
|
||||
});
|
||||
@ -159,10 +166,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
base.ClearNestedHitObjects();
|
||||
|
||||
headContainer.Clear();
|
||||
tailContainer.Clear();
|
||||
repeatContainer.Clear();
|
||||
tickContainer.Clear();
|
||||
headContainer.Clear(false);
|
||||
tailContainer.Clear(false);
|
||||
repeatContainer.Clear(false);
|
||||
tickContainer.Clear(false);
|
||||
}
|
||||
|
||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||
@ -173,17 +180,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
return new DrawableSliderTail(tail);
|
||||
|
||||
case SliderHeadCircle head:
|
||||
return new DrawableSliderHead(HitObject, head)
|
||||
{
|
||||
OnShake = Shake,
|
||||
CheckHittable = (d, t) => CheckHittable?.Invoke(d, t) ?? true
|
||||
};
|
||||
return new DrawableSliderHead(head);
|
||||
|
||||
case SliderTick tick:
|
||||
return new DrawableSliderTick(tick) { Position = tick.Position - HitObject.Position };
|
||||
return new DrawableSliderTick(tick);
|
||||
|
||||
case SliderRepeat repeat:
|
||||
return new DrawableSliderRepeat(repeat, this) { Position = repeat.Position - HitObject.Position };
|
||||
return new DrawableSliderRepeat(repeat);
|
||||
}
|
||||
|
||||
return base.CreateNestedHitObject(hitObject);
|
||||
|
@ -4,6 +4,8 @@
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
@ -14,21 +16,43 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle;
|
||||
|
||||
private readonly Slider slider;
|
||||
private DrawableSlider drawableSlider;
|
||||
|
||||
public DrawableSliderHead(Slider slider, SliderHeadCircle h)
|
||||
private Slider slider => drawableSlider?.HitObject;
|
||||
|
||||
public DrawableSliderHead()
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableSliderHead(SliderHeadCircle h)
|
||||
: base(h)
|
||||
{
|
||||
this.slider = slider;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
pathVersion.BindTo(slider.Path.Version);
|
||||
|
||||
PositionBindable.BindValueChanged(_ => updatePosition());
|
||||
pathVersion.BindValueChanged(_ => updatePosition(), true);
|
||||
pathVersion.BindValueChanged(_ => updatePosition());
|
||||
}
|
||||
|
||||
protected override void OnFree(HitObject hitObject)
|
||||
{
|
||||
base.OnFree(hitObject);
|
||||
|
||||
pathVersion.UnbindFrom(drawableSlider.PathVersion);
|
||||
}
|
||||
|
||||
protected override void OnParentReceived(DrawableHitObject parent)
|
||||
{
|
||||
base.OnParentReceived(parent);
|
||||
|
||||
drawableSlider = (DrawableSlider)parent;
|
||||
|
||||
pathVersion.BindTo(drawableSlider.PathVersion);
|
||||
|
||||
OnShake = drawableSlider.Shake;
|
||||
CheckHittable = (d, t) => drawableSlider.CheckHittable?.Invoke(d, t) ?? true;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -44,8 +68,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
public Action<double> OnShake;
|
||||
|
||||
protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength);
|
||||
public override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength);
|
||||
|
||||
private void updatePosition() => Position = HitObject.Position - slider.Position;
|
||||
private void updatePosition()
|
||||
{
|
||||
if (slider != null)
|
||||
Position = HitObject.Position - slider.Position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking
|
||||
{
|
||||
private readonly SliderRepeat sliderRepeat;
|
||||
private readonly DrawableSlider drawableSlider;
|
||||
public new SliderRepeat HitObject => (SliderRepeat)base.HitObject;
|
||||
|
||||
private double animDuration;
|
||||
|
||||
@ -27,11 +26,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider)
|
||||
private DrawableSlider drawableSlider;
|
||||
|
||||
public DrawableSliderRepeat()
|
||||
: base(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableSliderRepeat(SliderRepeat sliderRepeat)
|
||||
: base(sliderRepeat)
|
||||
{
|
||||
this.sliderRepeat = sliderRepeat;
|
||||
this.drawableSlider = drawableSlider;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -53,18 +57,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
}
|
||||
};
|
||||
|
||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true);
|
||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||
}
|
||||
|
||||
protected override void OnParentReceived(DrawableHitObject parent)
|
||||
{
|
||||
base.OnParentReceived(parent);
|
||||
|
||||
drawableSlider = (DrawableSlider)parent;
|
||||
|
||||
Position = HitObject.Position - drawableSlider.Position;
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (sliderRepeat.StartTime <= Time.Current)
|
||||
if (HitObject.StartTime <= Time.Current)
|
||||
ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
{
|
||||
animDuration = Math.Min(300, sliderRepeat.SpanDuration);
|
||||
animDuration = Math.Min(300, HitObject.SpanDuration);
|
||||
|
||||
this.Animate(
|
||||
d => d.FadeIn(animDuration),
|
||||
@ -100,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
// When the repeat is hit, the arrow should fade out on spot rather than following the slider
|
||||
if (IsHit) return;
|
||||
|
||||
bool isRepeatAtEnd = sliderRepeat.RepeatIndex % 2 == 0;
|
||||
bool isRepeatAtEnd = HitObject.RepeatIndex % 2 == 0;
|
||||
List<Vector2> curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
|
||||
|
||||
Position = isRepeatAtEnd ? end : start;
|
||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking
|
||||
{
|
||||
private readonly SliderTailCircle tailCircle;
|
||||
public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject;
|
||||
|
||||
/// <summary>
|
||||
/// The judgement text is provided by the <see cref="DrawableSlider"/>.
|
||||
@ -25,10 +25,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
private SkinnableDrawable circlePiece;
|
||||
private Container scaleContainer;
|
||||
|
||||
public DrawableSliderTail()
|
||||
: base(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableSliderTail(SliderTailCircle tailCircle)
|
||||
: base(tailCircle)
|
||||
{
|
||||
this.tailCircle = tailCircle;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -52,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
},
|
||||
};
|
||||
|
||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true);
|
||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
@ -92,6 +96,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
}
|
||||
|
||||
public void UpdateSnakingPosition(Vector2 start, Vector2 end) =>
|
||||
Position = tailCircle.RepeatIndex % 2 == 0 ? end : start;
|
||||
Position = HitObject.RepeatIndex % 2 == 0 ? end : start;
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
private SkinnableDrawable scaleContainer;
|
||||
|
||||
public DrawableSliderTick()
|
||||
: base(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableSliderTick(SliderTick sliderTick)
|
||||
: base(sliderTick)
|
||||
{
|
||||
@ -54,7 +59,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
|
||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true);
|
||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||
}
|
||||
|
||||
protected override void OnParentReceived(DrawableHitObject parent)
|
||||
{
|
||||
base.OnParentReceived(parent);
|
||||
|
||||
Position = HitObject.Position - ((DrawableSlider)parent).HitObject.Position;
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
|
@ -29,10 +29,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
private Container<DrawableSpinnerTick> ticks;
|
||||
private SpinnerBonusDisplay bonusDisplay;
|
||||
private Container<PausableSkinnableSound> samplesContainer;
|
||||
|
||||
private Bindable<bool> isSpinning;
|
||||
private bool spinnerFrequencyModulate;
|
||||
|
||||
public DrawableSpinner()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableSpinner([CanBeNull] Spinner s = null)
|
||||
: base(s)
|
||||
{
|
||||
@ -70,7 +76,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Y = -120,
|
||||
}
|
||||
},
|
||||
samplesContainer = new Container<PausableSkinnableSound> { RelativeSizeAxes = Axes.Both }
|
||||
};
|
||||
|
||||
PositionBindable.BindValueChanged(pos => Position = pos.NewValue);
|
||||
@ -92,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
base.LoadSamples();
|
||||
|
||||
spinningSample?.Expire();
|
||||
samplesContainer.Clear();
|
||||
spinningSample = null;
|
||||
|
||||
var firstSample = HitObject.Samples.FirstOrDefault();
|
||||
@ -102,7 +109,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
|
||||
clone.Name = "spinnerspin";
|
||||
|
||||
AddInternal(spinningSample = new PausableSkinnableSound(clone)
|
||||
samplesContainer.Add(spinningSample = new PausableSkinnableSound(clone)
|
||||
{
|
||||
Volume = { Value = 0 },
|
||||
Looping = true,
|
||||
@ -155,7 +162,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
protected override void ClearNestedHitObjects()
|
||||
{
|
||||
base.ClearNestedHitObjects();
|
||||
ticks.Clear();
|
||||
ticks.Clear(false);
|
||||
}
|
||||
|
||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||
|
@ -5,6 +5,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableSpinnerBonusTick : DrawableSpinnerTick
|
||||
{
|
||||
public DrawableSpinnerBonusTick()
|
||||
: base(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableSpinnerBonusTick(SpinnerBonusTick spinnerTick)
|
||||
: base(spinnerTick)
|
||||
{
|
||||
|
@ -7,6 +7,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
public DrawableSpinnerTick()
|
||||
: base(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableSpinnerTick(SpinnerTick spinnerTick)
|
||||
: base(spinnerTick)
|
||||
{
|
||||
|
@ -12,7 +12,6 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -24,11 +23,15 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config;
|
||||
|
||||
public new OsuPlayfield Playfield => (OsuPlayfield)base.Playfield;
|
||||
|
||||
public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||
: base(ruleset, beatmap, mods)
|
||||
{
|
||||
}
|
||||
|
||||
public override DrawableHitObject<OsuHitObject> CreateDrawableRepresentation(OsuHitObject h) => null;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor
|
||||
|
||||
protected override Playfield CreatePlayfield() => new OsuPlayfield();
|
||||
@ -39,23 +42,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
protected override ResumeOverlay CreateResumeOverlay() => new OsuResumeOverlay();
|
||||
|
||||
public override DrawableHitObject<OsuHitObject> CreateDrawableRepresentation(OsuHitObject h)
|
||||
{
|
||||
switch (h)
|
||||
{
|
||||
case HitCircle circle:
|
||||
return new DrawableHitCircle(circle);
|
||||
|
||||
case Slider slider:
|
||||
return new DrawableSlider(slider);
|
||||
|
||||
case Spinner spinner:
|
||||
return new DrawableSpinner(spinner);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay);
|
||||
|
||||
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new OsuReplayRecorder(replay);
|
||||
|
@ -13,6 +13,7 @@ using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
@ -26,6 +27,8 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
public class OsuPlayfield : Playfield
|
||||
{
|
||||
public readonly Func<DrawableHitObject, double, bool> CheckHittable;
|
||||
|
||||
private readonly PlayfieldBorder playfieldBorder;
|
||||
private readonly ProxyContainer approachCircles;
|
||||
private readonly ProxyContainer spinnerProxies;
|
||||
@ -78,6 +81,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
};
|
||||
|
||||
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
||||
CheckHittable = hitPolicy.IsHittable;
|
||||
|
||||
var hitWindows = new OsuHitWindows();
|
||||
|
||||
@ -85,45 +89,70 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
poolDictionary.Add(result, new DrawableJudgementPool(result));
|
||||
|
||||
AddRangeInternal(poolDictionary.Values);
|
||||
|
||||
NewResult += onNewResult;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuRulesetConfigManager config)
|
||||
{
|
||||
config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle);
|
||||
|
||||
registerPool<HitCircle, DrawableHitCircle>(10, 100);
|
||||
|
||||
registerPool<Slider, DrawableSlider>(10, 100);
|
||||
registerPool<SliderHeadCircle, DrawableSliderHead>(10, 100);
|
||||
registerPool<SliderTailCircle, DrawableSliderTail>(10, 100);
|
||||
registerPool<SliderTick, DrawableSliderTick>(10, 100);
|
||||
registerPool<SliderRepeat, DrawableSliderRepeat>(5, 50);
|
||||
|
||||
registerPool<Spinner, DrawableSpinner>(2, 20);
|
||||
registerPool<SpinnerTick, DrawableSpinnerTick>(10, 100);
|
||||
registerPool<SpinnerBonusTick, DrawableSpinnerBonusTick>(10, 100);
|
||||
}
|
||||
|
||||
public override void Add(DrawableHitObject h)
|
||||
private void registerPool<TObject, TDrawable>(int initialSize, int? maximumSize = null)
|
||||
where TObject : HitObject
|
||||
where TDrawable : DrawableHitObject, new()
|
||||
=> RegisterPool<TObject, TDrawable>(CreatePool<TDrawable>(initialSize, maximumSize));
|
||||
|
||||
protected virtual DrawablePool<TDrawable> CreatePool<TDrawable>(int initialSize, int? maximumSize = null)
|
||||
where TDrawable : DrawableHitObject, new()
|
||||
=> new DrawableOsuPool<TDrawable>(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize);
|
||||
|
||||
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject);
|
||||
|
||||
protected override void OnHitObjectAdded(HitObject hitObject)
|
||||
{
|
||||
DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)h;
|
||||
|
||||
h.OnNewResult += onNewResult;
|
||||
h.OnLoadComplete += d =>
|
||||
{
|
||||
if (d is DrawableSpinner)
|
||||
spinnerProxies.Add(d.CreateProxy());
|
||||
|
||||
if (d is IDrawableHitObjectWithProxiedApproach c)
|
||||
approachCircles.Add(c.ProxiedLayer.CreateProxy());
|
||||
};
|
||||
|
||||
base.Add(h);
|
||||
|
||||
osuHitObject.CheckHittable = hitPolicy.IsHittable;
|
||||
|
||||
followPoints.AddFollowPoints(osuHitObject.HitObject);
|
||||
base.OnHitObjectAdded(hitObject);
|
||||
followPoints.AddFollowPoints((OsuHitObject)hitObject);
|
||||
}
|
||||
|
||||
public override bool Remove(DrawableHitObject h)
|
||||
protected override void OnHitObjectRemoved(HitObject hitObject)
|
||||
{
|
||||
DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)h;
|
||||
base.OnHitObjectRemoved(hitObject);
|
||||
followPoints.RemoveFollowPoints((OsuHitObject)hitObject);
|
||||
}
|
||||
|
||||
bool result = base.Remove(h);
|
||||
public void OnHitObjectLoaded(Drawable drawable)
|
||||
{
|
||||
switch (drawable)
|
||||
{
|
||||
case DrawableSliderHead _:
|
||||
case DrawableSliderTail _:
|
||||
case DrawableSliderTick _:
|
||||
case DrawableSliderRepeat _:
|
||||
case DrawableSpinnerTick _:
|
||||
break;
|
||||
|
||||
if (result)
|
||||
followPoints.RemoveFollowPoints(osuHitObject.HitObject);
|
||||
case DrawableSpinner _:
|
||||
spinnerProxies.Add(drawable.CreateProxy());
|
||||
break;
|
||||
|
||||
return result;
|
||||
case IDrawableHitObjectWithProxiedApproach approach:
|
||||
approachCircles.Add(approach.ProxiedLayer.CreateProxy());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
@ -166,5 +195,15 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
return judgement;
|
||||
}
|
||||
}
|
||||
|
||||
private class OsuHitObjectLifetimeEntry : HitObjectLifetimeEntry
|
||||
{
|
||||
public OsuHitObjectLifetimeEntry(HitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
}
|
||||
|
||||
protected override double InitialLifetimeOffset => ((OsuHitObject)HitObject).TimePreempt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
@ -12,7 +11,6 @@ using static osu.Game.Skinning.LegacySkinConfiguration;
|
||||
|
||||
namespace osu.Game.Tests.Gameplay
|
||||
{
|
||||
[HeadlessTest]
|
||||
public class TestSceneHitObjectSamples : HitObjectSampleTest
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
|
@ -23,11 +23,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
DrawableSample[] samples = null;
|
||||
ISamplePlaybackDisabler sampleDisabler = null;
|
||||
|
||||
AddStep("get variables", () =>
|
||||
AddUntilStep("get variables", () =>
|
||||
{
|
||||
sampleDisabler = Player;
|
||||
slider = Player.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).First();
|
||||
samples = slider.ChildrenOfType<DrawableSample>().ToArray();
|
||||
slider = Player.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).FirstOrDefault();
|
||||
samples = slider?.ChildrenOfType<DrawableSample>().ToArray();
|
||||
|
||||
return slider != null;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for slider sliding then seek", () =>
|
||||
|
@ -134,19 +134,33 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RegisterPool<TestHitObject, DrawableTestHitObject>(PoolSize);
|
||||
}
|
||||
|
||||
protected override HitObjectLifetimeEntry CreateLifetimeEntry(TestHitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
|
||||
|
||||
public override DrawableHitObject<TestHitObject> CreateDrawableRepresentation(TestHitObject h) => null;
|
||||
|
||||
protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
|
||||
|
||||
protected override Playfield CreatePlayfield() => new TestPlayfield();
|
||||
protected override Playfield CreatePlayfield() => new TestPlayfield(PoolSize);
|
||||
}
|
||||
|
||||
private class TestPlayfield : Playfield
|
||||
{
|
||||
private readonly int poolSize;
|
||||
|
||||
public TestPlayfield(int poolSize)
|
||||
{
|
||||
this.poolSize = poolSize;
|
||||
AddInternal(HitObjectContainer);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RegisterPool<TestHitObject, DrawableTestHitObject>(poolSize);
|
||||
}
|
||||
|
||||
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
|
||||
|
||||
protected override GameplayCursorContainer CreateCursor() => null;
|
||||
}
|
||||
|
||||
private class TestHitObjectLifetimeEntry : HitObjectLifetimeEntry
|
||||
{
|
||||
@ -157,17 +171,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
protected override double InitialLifetimeOffset => 0;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestPlayfield : Playfield
|
||||
{
|
||||
public TestPlayfield()
|
||||
{
|
||||
AddInternal(HitObjectContainer);
|
||||
}
|
||||
|
||||
protected override GameplayCursorContainer CreateCursor() => null;
|
||||
}
|
||||
|
||||
private class TestBeatmapConverter : BeatmapConverter<TestHitObject>
|
||||
{
|
||||
|
@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
private HitObjectLifetimeEntry lifetimeEntry;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private DrawableRuleset drawableRuleset { get; set; }
|
||||
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
|
||||
|
||||
private Container<PausableSkinnableSound> samplesContainer;
|
||||
|
||||
@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
foreach (var h in HitObject.NestedHitObjects)
|
||||
{
|
||||
var drawableNested = drawableRuleset?.GetPooledDrawableRepresentation(h)
|
||||
var drawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h)
|
||||
?? CreateNestedHitObject(h)
|
||||
?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
|
||||
|
||||
|
@ -15,10 +15,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
@ -248,7 +245,7 @@ namespace osu.Game.Rulesets.UI
|
||||
if (drawableRepresentation != null)
|
||||
Playfield.Add(drawableRepresentation);
|
||||
else
|
||||
Playfield.Add(GetLifetimeEntry(hitObject));
|
||||
Playfield.Add(hitObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -260,15 +257,10 @@ namespace osu.Game.Rulesets.UI
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> to remove.</param>
|
||||
public bool RemoveHitObject(TObject hitObject)
|
||||
{
|
||||
var entry = GetLifetimeEntry(hitObject);
|
||||
|
||||
// May have been newly-created by the above call - remove it anyway.
|
||||
RemoveLifetimeEntry(hitObject);
|
||||
|
||||
if (Playfield.Remove(entry))
|
||||
if (Playfield.Remove(hitObject))
|
||||
return true;
|
||||
|
||||
// If the entry was not removed from the playfield, assume the hitobject is not being pooled and attempt a direct removal.
|
||||
// If the entry was not removed from the playfield, assume the hitobject is not being pooled and attempt a direct drawable removal.
|
||||
var drawableObject = Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == hitObject);
|
||||
if (drawableObject != null)
|
||||
return Playfield.Remove(drawableObject);
|
||||
@ -276,16 +268,6 @@ namespace osu.Game.Rulesets.UI
|
||||
return false;
|
||||
}
|
||||
|
||||
protected sealed override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject)
|
||||
{
|
||||
if (!(hitObject is TObject tHitObject))
|
||||
throw new InvalidOperationException($"Unexpected hitobject type: {hitObject.GetType().ReadableName()}");
|
||||
|
||||
return CreateLifetimeEntry(tHitObject);
|
||||
}
|
||||
|
||||
protected virtual HitObjectLifetimeEntry CreateLifetimeEntry(TObject hitObject) => new HitObjectLifetimeEntry(hitObject);
|
||||
|
||||
public override void SetRecordTarget(Replay recordingReplay)
|
||||
{
|
||||
if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputManager))
|
||||
@ -327,9 +309,8 @@ namespace osu.Game.Rulesets.UI
|
||||
/// Creates a <see cref="DrawableHitObject{TObject}"/> to represent a <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this method returns <c>null</c>, then this <see cref="DrawableRuleset"/> will assume the requested <see cref="HitObject"/> type is being pooled,
|
||||
/// and will instead attempt to retrieve the <see cref="DrawableHitObject"/>s at the point they should become alive via pools registered through
|
||||
/// <see cref="DrawableRuleset.RegisterPool{TObject, TDrawable}(int, int?)"/> or <see cref="DrawableRuleset.RegisterPool{TObject, TDrawable}(DrawablePool{TDrawable})"/>.
|
||||
/// If this method returns <c>null</c>, then this <see cref="DrawableRuleset"/> will assume the requested <see cref="HitObject"/> type is being pooled inside the <see cref="Playfield"/>,
|
||||
/// and will instead attempt to retrieve the <see cref="DrawableHitObject"/>s at the point they should become alive via pools registered in the <see cref="Playfield"/>.
|
||||
/// </remarks>
|
||||
/// <param name="h">The <see cref="HitObject"/> to represent.</param>
|
||||
/// <returns>The representing <see cref="DrawableHitObject{TObject}"/>.</returns>
|
||||
@ -549,99 +530,6 @@ namespace osu.Game.Rulesets.UI
|
||||
/// Invoked when the user requests to pause while the resume overlay is active.
|
||||
/// </summary>
|
||||
public abstract void CancelResume();
|
||||
|
||||
private readonly Dictionary<Type, IDrawablePool> pools = new Dictionary<Type, IDrawablePool>();
|
||||
private readonly Dictionary<HitObject, HitObjectLifetimeEntry> lifetimeEntries = new Dictionary<HitObject, HitObjectLifetimeEntry>();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a default <see cref="DrawableHitObject"/> pool with this <see cref="DrawableRuleset"/> which is to be used whenever
|
||||
/// <see cref="DrawableHitObject"/> representations are requested for the given <typeparamref name="TObject"/> type (via <see cref="GetPooledDrawableRepresentation"/>).
|
||||
/// </summary>
|
||||
/// <param name="initialSize">The number of <see cref="DrawableHitObject"/>s to be initially stored in the pool.</param>
|
||||
/// <param name="maximumSize">
|
||||
/// The maximum number of <see cref="DrawableHitObject"/>s that can be stored in the pool.
|
||||
/// If this limit is exceeded, every subsequent <see cref="DrawableHitObject"/> will be created anew instead of being retrieved from the pool,
|
||||
/// until some of the existing <see cref="DrawableHitObject"/>s are returned to the pool.
|
||||
/// </param>
|
||||
/// <typeparam name="TObject">The <see cref="HitObject"/> type.</typeparam>
|
||||
/// <typeparam name="TDrawable">The <see cref="DrawableHitObject"/> receiver for <typeparamref name="TObject"/>s.</typeparam>
|
||||
protected void RegisterPool<TObject, TDrawable>(int initialSize, int? maximumSize = null)
|
||||
where TObject : HitObject
|
||||
where TDrawable : DrawableHitObject, new()
|
||||
=> RegisterPool<TObject, TDrawable>(new DrawablePool<TDrawable>(initialSize, maximumSize));
|
||||
|
||||
/// <summary>
|
||||
/// Registers a custom <see cref="DrawableHitObject"/> pool with this <see cref="DrawableRuleset"/> which is to be used whenever
|
||||
/// <see cref="DrawableHitObject"/> representations are requested for the given <typeparamref name="TObject"/> type (via <see cref="GetPooledDrawableRepresentation"/>).
|
||||
/// </summary>
|
||||
/// <param name="pool">The <see cref="DrawablePool{T}"/> to register.</param>
|
||||
/// <typeparam name="TObject">The <see cref="HitObject"/> type.</typeparam>
|
||||
/// <typeparam name="TDrawable">The <see cref="DrawableHitObject"/> receiver for <typeparamref name="TObject"/>s.</typeparam>
|
||||
protected void RegisterPool<TObject, TDrawable>([NotNull] DrawablePool<TDrawable> pool)
|
||||
where TObject : HitObject
|
||||
where TDrawable : DrawableHitObject, new()
|
||||
{
|
||||
pools[typeof(TObject)] = pool;
|
||||
AddInternal(pool);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the poolable <see cref="DrawableHitObject"/> representation of a <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> to retrieve the <see cref="DrawableHitObject"/> representation of.</param>
|
||||
/// <returns>The <see cref="DrawableHitObject"/> representing <see cref="HitObject"/>, or <c>null</c> if no poolable representation exists.</returns>
|
||||
[CanBeNull]
|
||||
public DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject)
|
||||
{
|
||||
if (!pools.TryGetValue(hitObject.GetType(), out var pool))
|
||||
return null;
|
||||
|
||||
return (DrawableHitObject)pool.Get(d =>
|
||||
{
|
||||
var dho = (DrawableHitObject)d;
|
||||
|
||||
// If this is the first time this DHO is being used (not loaded), then apply the DHO mods.
|
||||
// This is done before Apply() so that the state is updated once when the hitobject is applied.
|
||||
if (!dho.IsLoaded)
|
||||
{
|
||||
foreach (var m in Mods.OfType<IApplicableToDrawableHitObjects>())
|
||||
m.ApplyToDrawableHitObjects(dho.Yield());
|
||||
}
|
||||
|
||||
dho.Apply(hitObject, GetLifetimeEntry(hitObject));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <see cref="HitObjectLifetimeEntry"/> for a given <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This may be overridden to provide custom lifetime control (e.g. via <see cref="HitObjectLifetimeEntry.InitialLifetimeOffset"/>.
|
||||
/// </remarks>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> to create the entry for.</param>
|
||||
/// <returns>The <see cref="HitObjectLifetimeEntry"/>.</returns>
|
||||
[NotNull]
|
||||
protected abstract HitObjectLifetimeEntry CreateLifetimeEntry([NotNull] HitObject hitObject);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves or creates the <see cref="HitObjectLifetimeEntry"/> for a given <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> to retrieve or create the <see cref="HitObjectLifetimeEntry"/> for.</param>
|
||||
/// <returns>The <see cref="HitObjectLifetimeEntry"/> for <paramref name="hitObject"/>.</returns>
|
||||
[NotNull]
|
||||
protected HitObjectLifetimeEntry GetLifetimeEntry([NotNull] HitObject hitObject)
|
||||
{
|
||||
if (lifetimeEntries.TryGetValue(hitObject, out var entry))
|
||||
return entry;
|
||||
|
||||
return lifetimeEntries[hitObject] = CreateLifetimeEntry(hitObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the <see cref="HitObjectLifetimeEntry"/> for a <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> to remove the <see cref="HitObjectLifetimeEntry"/> for.</param>
|
||||
internal void RemoveLifetimeEntry([NotNull] HitObject hitObject) => lifetimeEntries.Remove(hitObject);
|
||||
}
|
||||
|
||||
public class BeatmapInvalidForRulesetException : ArgumentException
|
||||
|
@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.UI
|
||||
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private DrawableRuleset drawableRuleset { get; set; }
|
||||
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
|
||||
|
||||
public HitObjectContainer()
|
||||
{
|
||||
@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
Debug.Assert(!drawableMap.ContainsKey(entry));
|
||||
|
||||
var drawable = drawableRuleset.GetPooledDrawableRepresentation(entry.HitObject);
|
||||
var drawable = pooledObjectProvider.GetPooledDrawableRepresentation(entry.HitObject);
|
||||
if (drawable == null)
|
||||
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
||||
|
||||
|
20
osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs
Normal file
20
osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
internal interface IPooledHitObjectProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the poolable <see cref="DrawableHitObject"/> representation of a <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> to retrieve the <see cref="DrawableHitObject"/> representation of.</param>
|
||||
/// <returns>The <see cref="DrawableHitObject"/> representing <see cref="HitObject"/>, or <c>null</c> if no poolable representation exists.</returns>
|
||||
[CanBeNull]
|
||||
DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject);
|
||||
}
|
||||
}
|
@ -4,12 +4,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -17,7 +19,8 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
public abstract class Playfield : CompositeDrawable
|
||||
[Cached(typeof(IPooledHitObjectProvider))]
|
||||
public abstract class Playfield : CompositeDrawable, IPooledHitObjectProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="DrawableHitObject"/> is judged.
|
||||
@ -138,39 +141,6 @@ namespace osu.Game.Rulesets.UI
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="HitObjectLifetimeEntry"/> for a pooled <see cref="HitObject"/> to this <see cref="Playfield"/>.
|
||||
/// </summary>
|
||||
/// <param name="entry">The <see cref="HitObjectLifetimeEntry"/> controlling the lifetime of the <see cref="HitObject"/>.</param>
|
||||
public virtual void Add(HitObjectLifetimeEntry entry)
|
||||
{
|
||||
HitObjectContainer.Add(entry);
|
||||
lifetimeEntryMap[entry.HitObject] = entry;
|
||||
OnHitObjectAdded(entry.HitObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a <see cref="HitObjectLifetimeEntry"/> for a pooled <see cref="HitObject"/> from this <see cref="Playfield"/>.
|
||||
/// </summary>
|
||||
/// <param name="entry">The <see cref="HitObjectLifetimeEntry"/> controlling the lifetime of the <see cref="HitObject"/>.</param>
|
||||
/// <returns>Whether the <see cref="HitObject"/> was successfully removed.</returns>
|
||||
public virtual bool Remove(HitObjectLifetimeEntry entry)
|
||||
{
|
||||
if (HitObjectContainer.Remove(entry))
|
||||
{
|
||||
lifetimeEntryMap.Remove(entry.HitObject);
|
||||
OnHitObjectRemoved(entry.HitObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool removedFromNested = false;
|
||||
|
||||
if (nestedPlayfields.IsValueCreated)
|
||||
removedFromNested = nestedPlayfields.Value.Any(p => p.Remove(entry));
|
||||
|
||||
return removedFromNested;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="HitObject"/> is added to this <see cref="Playfield"/>.
|
||||
/// </summary>
|
||||
@ -246,6 +216,134 @@ namespace osu.Game.Rulesets.UI
|
||||
/// </summary>
|
||||
protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer();
|
||||
|
||||
#region Pooling support
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IPooledHitObjectProvider parentPooledObjectProvider { get; set; }
|
||||
|
||||
private readonly Dictionary<Type, IDrawablePool> pools = new Dictionary<Type, IDrawablePool>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="HitObjectLifetimeEntry"/> for a pooled <see cref="HitObject"/> to this <see cref="Playfield"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObject"></param>
|
||||
public virtual void Add(HitObject hitObject)
|
||||
{
|
||||
var entry = CreateLifetimeEntry(hitObject);
|
||||
lifetimeEntryMap[entry.HitObject] = entry;
|
||||
|
||||
HitObjectContainer.Add(entry);
|
||||
OnHitObjectAdded(entry.HitObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a <see cref="HitObjectLifetimeEntry"/> for a pooled <see cref="HitObject"/> from this <see cref="Playfield"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObject"></param>
|
||||
/// <returns>Whether the <see cref="HitObject"/> was successfully removed.</returns>
|
||||
public virtual bool Remove(HitObject hitObject)
|
||||
{
|
||||
if (lifetimeEntryMap.Remove(hitObject, out var entry))
|
||||
{
|
||||
HitObjectContainer.Remove(entry);
|
||||
OnHitObjectRemoved(hitObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool removedFromNested = false;
|
||||
|
||||
if (nestedPlayfields.IsValueCreated)
|
||||
removedFromNested = nestedPlayfields.Value.Any(p => p.Remove(hitObject));
|
||||
|
||||
return removedFromNested;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <see cref="HitObjectLifetimeEntry"/> for a given <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This may be overridden to provide custom lifetime control (e.g. via <see cref="HitObjectLifetimeEntry.InitialLifetimeOffset"/>.
|
||||
/// </remarks>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> to create the entry for.</param>
|
||||
/// <returns>The <see cref="HitObjectLifetimeEntry"/>.</returns>
|
||||
[NotNull]
|
||||
protected virtual HitObjectLifetimeEntry CreateLifetimeEntry([NotNull] HitObject hitObject) => new HitObjectLifetimeEntry(hitObject);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a default <see cref="DrawableHitObject"/> pool with this <see cref="DrawableRuleset"/> which is to be used whenever
|
||||
/// <see cref="DrawableHitObject"/> representations are requested for the given <typeparamref name="TObject"/> type.
|
||||
/// </summary>
|
||||
/// <param name="initialSize">The number of <see cref="DrawableHitObject"/>s to be initially stored in the pool.</param>
|
||||
/// <param name="maximumSize">
|
||||
/// The maximum number of <see cref="DrawableHitObject"/>s that can be stored in the pool.
|
||||
/// If this limit is exceeded, every subsequent <see cref="DrawableHitObject"/> will be created anew instead of being retrieved from the pool,
|
||||
/// until some of the existing <see cref="DrawableHitObject"/>s are returned to the pool.
|
||||
/// </param>
|
||||
/// <typeparam name="TObject">The <see cref="HitObject"/> type.</typeparam>
|
||||
/// <typeparam name="TDrawable">The <see cref="DrawableHitObject"/> receiver for <typeparamref name="TObject"/>s.</typeparam>
|
||||
protected void RegisterPool<TObject, TDrawable>(int initialSize, int? maximumSize = null)
|
||||
where TObject : HitObject
|
||||
where TDrawable : DrawableHitObject, new()
|
||||
=> RegisterPool<TObject, TDrawable>(new DrawablePool<TDrawable>(initialSize, maximumSize));
|
||||
|
||||
/// <summary>
|
||||
/// Registers a custom <see cref="DrawableHitObject"/> pool with this <see cref="DrawableRuleset"/> which is to be used whenever
|
||||
/// <see cref="DrawableHitObject"/> representations are requested for the given <typeparamref name="TObject"/> type.
|
||||
/// </summary>
|
||||
/// <param name="pool">The <see cref="DrawablePool{T}"/> to register.</param>
|
||||
/// <typeparam name="TObject">The <see cref="HitObject"/> type.</typeparam>
|
||||
/// <typeparam name="TDrawable">The <see cref="DrawableHitObject"/> receiver for <typeparamref name="TObject"/>s.</typeparam>
|
||||
protected void RegisterPool<TObject, TDrawable>([NotNull] DrawablePool<TDrawable> pool)
|
||||
where TObject : HitObject
|
||||
where TDrawable : DrawableHitObject, new()
|
||||
{
|
||||
pools[typeof(TObject)] = pool;
|
||||
AddInternal(pool);
|
||||
}
|
||||
|
||||
DrawableHitObject IPooledHitObjectProvider.GetPooledDrawableRepresentation(HitObject hitObject)
|
||||
{
|
||||
var lookupType = hitObject.GetType();
|
||||
|
||||
IDrawablePool pool;
|
||||
|
||||
// Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists.
|
||||
if (!pools.TryGetValue(lookupType, out pool))
|
||||
{
|
||||
foreach (var (t, p) in pools)
|
||||
{
|
||||
if (!t.IsInstanceOfType(hitObject))
|
||||
continue;
|
||||
|
||||
pools[lookupType] = pool = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pool == null)
|
||||
return parentPooledObjectProvider?.GetPooledDrawableRepresentation(hitObject);
|
||||
|
||||
return (DrawableHitObject)pool.Get(d =>
|
||||
{
|
||||
var dho = (DrawableHitObject)d;
|
||||
|
||||
// If this is the first time this DHO is being used (not loaded), then apply the DHO mods.
|
||||
// This is done before Apply() so that the state is updated once when the hitobject is applied.
|
||||
if (!dho.IsLoaded)
|
||||
{
|
||||
foreach (var m in mods.OfType<IApplicableToDrawableHitObjects>())
|
||||
m.ApplyToDrawableHitObjects(dho.Yield());
|
||||
}
|
||||
|
||||
if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry))
|
||||
lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject);
|
||||
|
||||
dho.Apply(hitObject, entry);
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Editor logic
|
||||
|
||||
/// <summary>
|
||||
|
@ -9,11 +9,14 @@ using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Tests.Visual;
|
||||
@ -21,6 +24,7 @@ using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps
|
||||
{
|
||||
[HeadlessTest]
|
||||
public abstract class HitObjectSampleTest : PlayerTestScene
|
||||
{
|
||||
protected abstract IResourceStore<byte[]> Resources { get; }
|
||||
@ -44,7 +48,9 @@ namespace osu.Game.Tests.Beatmaps
|
||||
private readonly TestResourceStore beatmapSkinResourceStore = new TestResourceStore();
|
||||
private SkinSourceDependencyContainer dependencies;
|
||||
private IBeatmap currentTestBeatmap;
|
||||
|
||||
protected sealed override bool HasCustomSteps => true;
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
protected sealed override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
=> new DependencyContainer(dependencies = new SkinSourceDependencyContainer(base.CreateChildDependencies(parent)));
|
||||
@ -54,6 +60,8 @@ namespace osu.Game.Tests.Beatmaps
|
||||
protected sealed override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
=> new TestWorkingBeatmap(beatmapInfo, beatmapSkinResourceStore, beatmap, storyboard, Clock, Audio);
|
||||
|
||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false);
|
||||
|
||||
protected void CreateTestWithBeatmap(string filename)
|
||||
{
|
||||
CreateTest(() =>
|
||||
@ -73,6 +81,9 @@ namespace osu.Game.Tests.Beatmaps
|
||||
currentTestBeatmap.BeatmapInfo.Ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.RulesetID);
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
|
||||
AddUntilStep("results displayed", () => Stack.CurrentScreen is ResultsScreen);
|
||||
}
|
||||
|
||||
protected void SetupSkins(string beatmapFile, string userFile)
|
||||
|
Loading…
Reference in New Issue
Block a user