mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 15:47:26 +08:00
Merge pull request #10924 from ekrctb/on-drawable-hitobject-added
Add method of initialising a `DrawableHitObject` from `Playfield` regardless of pooling
This commit is contained in:
commit
dcfd9517b7
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
switch (hitObject)
|
||||
{
|
||||
case Banana banana:
|
||||
return createDrawableRepresentation?.Invoke(banana)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
|
||||
return createDrawableRepresentation?.Invoke(banana);
|
||||
}
|
||||
|
||||
return base.CreateNestedHitObject(hitObject);
|
||||
|
@ -47,8 +47,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
switch (hitObject)
|
||||
{
|
||||
case CatchHitObject catchObject:
|
||||
return createDrawableRepresentation?.Invoke(catchObject)?.With(o =>
|
||||
((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
|
||||
return createDrawableRepresentation?.Invoke(catchObject);
|
||||
}
|
||||
|
||||
throw new ArgumentException($"{nameof(hitObject)} must be of type {nameof(CatchHitObject)}.");
|
||||
|
@ -55,21 +55,18 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
HitObjectContainer,
|
||||
CatcherArea,
|
||||
};
|
||||
|
||||
NewResult += onNewResult;
|
||||
RevertResult += onRevertResult;
|
||||
}
|
||||
|
||||
public bool CheckIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj);
|
||||
|
||||
public override void Add(DrawableHitObject h)
|
||||
protected override void OnNewDrawableHitObject(DrawableHitObject d)
|
||||
{
|
||||
h.OnNewResult += onNewResult;
|
||||
h.OnRevertResult += onRevertResult;
|
||||
|
||||
base.Add(h);
|
||||
|
||||
var fruit = (DrawableCatchHitObject)h;
|
||||
fruit.CheckPosition = CheckIfWeCanCatch;
|
||||
((DrawableCatchHitObject)d).CheckPosition = checkIfWeCanCatch;
|
||||
}
|
||||
|
||||
private bool checkIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj);
|
||||
|
||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
|
||||
|
||||
|
@ -1,63 +0,0 @@
|
||||
// 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,9 +2,12 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
@ -26,8 +29,51 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
protected override GameplayCursorContainer CreateCursor() => null;
|
||||
|
||||
protected override DrawablePool<TDrawable> CreatePool<TDrawable>(int initialSize, int? maximumSize = null)
|
||||
=> new DrawableOsuEditPool<TDrawable>(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize);
|
||||
protected override void OnNewDrawableHitObject(DrawableHitObject d)
|
||||
{
|
||||
d.ApplyCustomUpdateState += updateState;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
|
||||
public class DrawableHitCircle : DrawableOsuHitObject
|
||||
{
|
||||
public OsuAction? HitAction => HitArea.HitAction;
|
||||
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
|
||||
|
@ -1,32 +0,0 @@
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
|
||||
public class DrawableSlider : DrawableOsuHitObject
|
||||
{
|
||||
public new Slider HitObject => (Slider)base.HitObject;
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -25,8 +26,6 @@ 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;
|
||||
@ -56,7 +55,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
};
|
||||
|
||||
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
||||
CheckHittable = hitPolicy.IsHittable;
|
||||
|
||||
var hitWindows = new OsuHitWindows();
|
||||
|
||||
@ -68,6 +66,29 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
NewResult += onNewResult;
|
||||
}
|
||||
|
||||
protected override void OnNewDrawableHitObject(DrawableHitObject drawable)
|
||||
{
|
||||
((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.IsHittable;
|
||||
|
||||
Debug.Assert(!drawable.IsLoaded, $"Already loaded {nameof(DrawableHitObject)} is added to {nameof(OsuPlayfield)}");
|
||||
drawable.OnLoadComplete += onDrawableHitObjectLoaded;
|
||||
}
|
||||
|
||||
private void onDrawableHitObjectLoaded(Drawable drawable)
|
||||
{
|
||||
// note: `Slider`'s `ProxiedLayer` is added when its nested `DrawableHitCircle` is loaded.
|
||||
switch (drawable)
|
||||
{
|
||||
case DrawableSpinner _:
|
||||
spinnerProxies.Add(drawable.CreateProxy());
|
||||
break;
|
||||
|
||||
case DrawableHitCircle hitCircle:
|
||||
approachCircles.Add(hitCircle.ProxiedLayer.CreateProxy());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onJudgmentLoaded(DrawableOsuJudgement judgement)
|
||||
{
|
||||
judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent());
|
||||
@ -78,28 +99,19 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle);
|
||||
|
||||
registerPool<HitCircle, DrawableHitCircle>(10, 100);
|
||||
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<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);
|
||||
RegisterPool<Spinner, DrawableSpinner>(2, 20);
|
||||
RegisterPool<SpinnerTick, DrawableSpinnerTick>(10, 100);
|
||||
RegisterPool<SpinnerBonusTick, DrawableSpinnerBonusTick>(10, 100);
|
||||
}
|
||||
|
||||
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)
|
||||
@ -114,27 +126,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
followPoints.RemoveFollowPoints((OsuHitObject)hitObject);
|
||||
}
|
||||
|
||||
public void OnHitObjectLoaded(Drawable drawable)
|
||||
{
|
||||
switch (drawable)
|
||||
{
|
||||
case DrawableSliderHead _:
|
||||
case DrawableSliderTail _:
|
||||
case DrawableSliderTick _:
|
||||
case DrawableSliderRepeat _:
|
||||
case DrawableSpinnerTick _:
|
||||
break;
|
||||
|
||||
case DrawableSpinner _:
|
||||
spinnerProxies.Add(drawable.CreateProxy());
|
||||
break;
|
||||
|
||||
case IDrawableHitObjectWithProxiedApproach approach:
|
||||
approachCircles.Add(approach.ProxiedLayer.CreateProxy());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
{
|
||||
// Hitobjects that block future hits should miss previous hitobjects if they're hit out-of-order.
|
||||
|
@ -74,6 +74,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
/// </summary>
|
||||
public event Action<DrawableHitObject, JudgementResult> OnRevertResult;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a new nested hit object is created by <see cref="CreateNestedHitObject" />.
|
||||
/// </summary>
|
||||
internal event Action<DrawableHitObject> OnNestedDrawableCreated;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a visual indicator should be displayed when a scoring result occurs.
|
||||
/// </summary>
|
||||
@ -141,6 +146,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
private Container<PausableSkinnableSound> samplesContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the initialization logic in <see cref="Playfield" /> has applied.
|
||||
/// </summary>
|
||||
internal bool IsInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DrawableHitObject"/>.
|
||||
/// </summary>
|
||||
@ -214,10 +224,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
foreach (var h in HitObject.NestedHitObjects)
|
||||
{
|
||||
var drawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h)
|
||||
var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h);
|
||||
var drawableNested = pooledDrawableNested
|
||||
?? CreateNestedHitObject(h)
|
||||
?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
|
||||
|
||||
// Invoke the event only if this nested object is just created by `CreateNestedHitObject`.
|
||||
if (pooledDrawableNested == null)
|
||||
OnNestedDrawableCreated?.Invoke(drawableNested);
|
||||
|
||||
drawableNested.OnNewResult += onNewResult;
|
||||
drawableNested.OnRevertResult += onRevertResult;
|
||||
drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState;
|
||||
|
@ -1,12 +0,0 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Drawables
|
||||
{
|
||||
public interface IDrawableHitObjectWithProxiedApproach
|
||||
{
|
||||
Drawable ProxiedLayer { get; }
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osuTK;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
@ -113,6 +114,16 @@ namespace osu.Game.Rulesets.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void onNewDrawableHitObject(DrawableHitObject d)
|
||||
{
|
||||
d.OnNestedDrawableCreated += onNewDrawableHitObject;
|
||||
|
||||
OnNewDrawableHitObject(d);
|
||||
|
||||
Debug.Assert(!d.IsInitialized);
|
||||
d.IsInitialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield.
|
||||
/// </summary>
|
||||
@ -124,6 +135,11 @@ namespace osu.Game.Rulesets.UI
|
||||
/// <param name="h">The DrawableHitObject to add.</param>
|
||||
public virtual void Add(DrawableHitObject h)
|
||||
{
|
||||
if (h.IsInitialized)
|
||||
throw new InvalidOperationException($"{nameof(Add)} doesn't support {nameof(DrawableHitObject)} reuse. Use pooling instead.");
|
||||
|
||||
onNewDrawableHitObject(h);
|
||||
|
||||
HitObjectContainer.Add(h);
|
||||
OnHitObjectAdded(h.HitObject);
|
||||
}
|
||||
@ -157,6 +173,17 @@ namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked before a new <see cref="DrawableHitObject"/> is added to this <see cref="Playfield"/>.
|
||||
/// It is invoked only once even if the drawable is pooled and used multiple times for different <see cref="HitObject"/>s.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is also invoked for nested <see cref="DrawableHitObject"/>s.
|
||||
/// </remarks>
|
||||
protected virtual void OnNewDrawableHitObject(DrawableHitObject drawableHitObject)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The cursor currently being used by this <see cref="Playfield"/>. May be null if no cursor is provided.
|
||||
/// </summary>
|
||||
@ -321,10 +348,12 @@ namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
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)
|
||||
if (!dho.IsInitialized)
|
||||
{
|
||||
onNewDrawableHitObject(dho);
|
||||
|
||||
// If this is the first time this DHO is being used, then apply the DHO mods.
|
||||
// This is done before Apply() so that the state is updated once when the hitobject is applied.
|
||||
foreach (var m in mods.OfType<IApplicableToDrawableHitObjects>())
|
||||
m.ApplyToDrawableHitObjects(dho.Yield());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user