mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 10:07:52 +08:00
Merge pull request #10826 from smoogipoo/osu-hitobject-pooling-playfield
Change osu! ruleset to use pooled hitobjects
This commit is contained in:
commit
aae59dc3cf
@ -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)
|
||||
{
|
||||
DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)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));
|
||||
|
||||
h.OnNewResult += onNewResult;
|
||||
h.OnLoadComplete += d =>
|
||||
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)
|
||||
{
|
||||
base.OnHitObjectAdded(hitObject);
|
||||
followPoints.AddFollowPoints((OsuHitObject)hitObject);
|
||||
}
|
||||
|
||||
protected override void OnHitObjectRemoved(HitObject hitObject)
|
||||
{
|
||||
base.OnHitObjectRemoved(hitObject);
|
||||
followPoints.RemoveFollowPoints((OsuHitObject)hitObject);
|
||||
}
|
||||
|
||||
public void OnHitObjectLoaded(Drawable drawable)
|
||||
{
|
||||
switch (drawable)
|
||||
{
|
||||
if (d is DrawableSpinner)
|
||||
spinnerProxies.Add(d.CreateProxy());
|
||||
case DrawableSliderHead _:
|
||||
case DrawableSliderTail _:
|
||||
case DrawableSliderTick _:
|
||||
case DrawableSliderRepeat _:
|
||||
case DrawableSpinnerTick _:
|
||||
break;
|
||||
|
||||
if (d is IDrawableHitObjectWithProxiedApproach c)
|
||||
approachCircles.Add(c.ProxiedLayer.CreateProxy());
|
||||
};
|
||||
case DrawableSpinner _:
|
||||
spinnerProxies.Add(drawable.CreateProxy());
|
||||
break;
|
||||
|
||||
base.Add(h);
|
||||
|
||||
osuHitObject.CheckHittable = hitPolicy.IsHittable;
|
||||
|
||||
followPoints.AddFollowPoints(osuHitObject.HitObject);
|
||||
}
|
||||
|
||||
public override bool Remove(DrawableHitObject h)
|
||||
{
|
||||
DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)h;
|
||||
|
||||
bool result = base.Remove(h);
|
||||
|
||||
if (result)
|
||||
followPoints.RemoveFollowPoints(osuHitObject.HitObject);
|
||||
|
||||
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", () =>
|
||||
|
@ -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