1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 22:07:25 +08:00

Implement legacy slider body for classic skins (#7243)

Implement legacy slider body for classic skins

Co-authored-by: Dean Herbert <pe@ppy.sh>
This commit is contained in:
Dean Herbert 2019-12-18 01:15:00 +09:00 committed by GitHub
commit 726abaddc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 244 additions and 129 deletions

View File

@ -11,6 +11,7 @@ using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Mods
{
@ -54,13 +55,8 @@ namespace osu.Game.Rulesets.Osu.Mods
break;
case DrawableSlider slider:
slider.AccentColour.BindValueChanged(_ =>
{
//will trigger on skin change.
slider.Body.AccentColour = slider.AccentColour.Value.Opacity(0);
slider.Body.BorderColour = slider.AccentColour.Value;
}, true);
slider.Body.OnSkinChanged += () => applySliderState(slider);
applySliderState(slider);
break;
case DrawableSpinner spinner:
@ -69,5 +65,11 @@ namespace osu.Game.Rulesets.Osu.Mods
break;
}
}
private void applySliderState(DrawableSlider slider)
{
((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0);
((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value;
}
}
}

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring;
using osuTK;
using osu.Game.Skinning;
@ -98,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public void UpdateSnakingPosition(Vector2 start, Vector2 end)
{
bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0;
List<Vector2> curve = drawableSlider.Body.CurrentCurve;
List<Vector2> curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
Position = isRepeatAtEnd ? end : start;

View File

@ -11,7 +11,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
@ -24,8 +23,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public DrawableSliderHead HeadCircle => headContainer.Child;
public DrawableSliderTail TailCircle => tailContainer.Child;
public readonly SnakingSliderBody Body;
public readonly SliderBall Ball;
public readonly SkinnableDrawable Body;
private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody;
private readonly Container<DrawableSliderHead> headContainer;
private readonly Container<DrawableSliderTail> tailContainer;
@ -37,10 +38,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
private readonly IBindable<float> scaleBindable = new Bindable<float>();
private readonly IBindable<int> pathVersion = new Bindable<int>();
[Resolved(CanBeNull = true)]
private OsuRulesetConfigManager config { get; set; }
public DrawableSlider(Slider s)
: base(s)
@ -51,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[]
{
Body = new SnakingSliderBody(s),
Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
repeatContainer = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s, this)
@ -70,28 +67,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
[BackgroundDependencyLoader]
private void load()
{
config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn);
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut);
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
scaleBindable.BindValueChanged(scale =>
{
updatePathRadius();
Ball.Scale = new Vector2(scale.NewValue);
});
scaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue));
positionBindable.BindTo(HitObject.PositionBindable);
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
scaleBindable.BindTo(HitObject.ScaleBindable);
pathVersion.BindTo(slider.Path.Version);
pathVersion.BindValueChanged(_ => Body.Refresh());
AccentColour.BindValueChanged(colour =>
{
Body.AccentColour = colour.NewValue;
foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour.Value = colour.NewValue;
}, true);
@ -169,16 +154,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
Ball.UpdateProgress(completionProgress);
Body.UpdateProgress(completionProgress);
sliderBody?.UpdateProgress(completionProgress);
foreach (DrawableHitObject hitObject in NestedHitObjects)
{
if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0));
if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(sliderBody?.SnakedStart ?? 0), slider.Path.PositionAt(sliderBody?.SnakedEnd ?? 0));
if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking;
}
Size = Body.Size;
OriginPosition = Body.PathOffset;
Size = sliderBody?.Size ?? Vector2.Zero;
OriginPosition = sliderBody?.PathOffset ?? Vector2.Zero;
if (DrawSize != Vector2.Zero)
{
@ -192,28 +177,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override void OnKilled()
{
base.OnKilled();
Body.RecyclePath();
sliderBody?.RecyclePath();
}
private float sliderPathRadius;
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
{
base.ApplySkin(skin, allowFallback);
Body.BorderSize = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
sliderPathRadius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
updatePathRadius();
Body.AccentColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
Body.BorderColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
}
private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (userTriggered || Time.Current < slider.EndTime)
@ -264,6 +238,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Drawable ProxiedLayer => HeadCircle.ProxiedLayer;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos);
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
private class DefaultSliderBody : PlaySliderBody
{
}
}
}

View File

@ -0,0 +1,70 @@
// 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.Lines;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public abstract class DrawableSliderPath : SmoothPath
{
protected const float BORDER_PORTION = 0.128f;
protected const float GRADIENT_PORTION = 1 - BORDER_PORTION;
private const float border_max_size = 8f;
private const float border_min_size = 0f;
private Color4 borderColour = Color4.White;
public Color4 BorderColour
{
get => borderColour;
set
{
if (borderColour == value)
return;
borderColour = value;
InvalidateTexture();
}
}
private Color4 accentColour = Color4.White;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
InvalidateTexture();
}
}
private float borderSize = 1;
public float BorderSize
{
get => borderSize;
set
{
if (borderSize == value)
return;
if (value < border_min_size || value > border_max_size)
return;
borderSize = value;
InvalidateTexture();
}
}
protected float CalculatedBorderPortion => BorderSize * BORDER_PORTION;
}
}

View File

@ -0,0 +1,57 @@
// 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.Allocation;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Skinning;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public abstract class PlaySliderBody : SnakingSliderBody
{
private IBindable<float> scaleBindable;
private IBindable<int> pathVersion;
private IBindable<Color4> accentColour;
[Resolved]
private DrawableHitObject drawableObject { get; set; }
[Resolved(CanBeNull = true)]
private OsuRulesetConfigManager config { get; set; }
private Slider slider;
private float defaultPathRadius;
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
slider = (Slider)drawableObject.HitObject;
defaultPathRadius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
scaleBindable = slider.ScaleBindable.GetBoundCopy();
scaleBindable.BindValueChanged(_ => updatePathRadius(), true);
pathVersion = slider.Path.Version.GetBoundCopy();
pathVersion.BindValueChanged(_ => Refresh());
accentColour = drawableObject.AccentColour.GetBoundCopy();
accentColour.BindValueChanged(accent => updateAccentColour(skin, accent.NewValue), true);
config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn);
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, SnakingOut);
BorderSize = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1;
BorderColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
}
private void updatePathRadius()
=> PathRadius = defaultPathRadius * scaleBindable.Value;
private void updateAccentColour(ISkinSource skin, Color4 defaultAccentColour)
=> AccentColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? defaultAccentColour;
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
using osuTK;
@ -12,9 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public abstract class SliderBody : CompositeDrawable
{
public const float DEFAULT_BORDER_SIZE = 1;
private SliderPath path;
private DrawableSliderPath path;
protected Path Path => path;
@ -80,19 +79,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
/// <summary>
/// Initialises a new <see cref="SliderPath"/>, releasing all resources retained by the old one.
/// Initialises a new <see cref="DrawableSliderPath"/>, releasing all resources retained by the old one.
/// </summary>
public virtual void RecyclePath()
{
InternalChild = path = new SliderPath
InternalChild = path = CreateSliderPath().With(p =>
{
Position = path?.Position ?? Vector2.Zero,
PathRadius = path?.PathRadius ?? 10,
AccentColour = path?.AccentColour ?? Color4.White,
BorderColour = path?.BorderColour ?? Color4.White,
BorderSize = path?.BorderSize ?? DEFAULT_BORDER_SIZE,
Vertices = path?.Vertices ?? Array.Empty<Vector2>()
};
p.Position = path?.Position ?? Vector2.Zero;
p.PathRadius = path?.PathRadius ?? 10;
p.AccentColour = path?.AccentColour ?? Color4.White;
p.BorderColour = path?.BorderColour ?? Color4.White;
p.BorderSize = path?.BorderSize ?? 1;
p.Vertices = path?.Vertices ?? Array.Empty<Vector2>();
});
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos);
@ -103,77 +102,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
/// <param name="vertices">The vertices</param>
protected void SetVertices(IReadOnlyList<Vector2> vertices) => path.Vertices = vertices;
private class SliderPath : SmoothPath
protected virtual DrawableSliderPath CreateSliderPath() => new DefaultDrawableSliderPath();
private class DefaultDrawableSliderPath : DrawableSliderPath
{
private const float border_max_size = 8f;
private const float border_min_size = 0f;
private const float border_portion = 0.128f;
private const float gradient_portion = 1 - border_portion;
private const float opacity_at_centre = 0.3f;
private const float opacity_at_edge = 0.8f;
private Color4 borderColour = Color4.White;
public Color4 BorderColour
{
get => borderColour;
set
{
if (borderColour == value)
return;
borderColour = value;
InvalidateTexture();
}
}
private Color4 accentColour = Color4.White;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
InvalidateTexture();
}
}
private float borderSize = DEFAULT_BORDER_SIZE;
public float BorderSize
{
get => borderSize;
set
{
if (borderSize == value)
return;
if (value < border_min_size || value > border_max_size)
return;
borderSize = value;
InvalidateTexture();
}
}
private float calculatedBorderPortion => BorderSize * border_portion;
protected override Color4 ColourAt(float position)
{
if (calculatedBorderPortion != 0f && position <= calculatedBorderPortion)
if (CalculatedBorderPortion != 0f && position <= CalculatedBorderPortion)
return BorderColour;
position -= calculatedBorderPortion;
return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / gradient_portion) * AccentColour.A);
position -= CalculatedBorderPortion;
return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / GRADIENT_PORTION) * AccentColour.A);
}
}
}

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
@ -50,16 +51,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
/// </summary>
private Vector2 snakedPathOffset;
private readonly Slider slider;
public SnakingSliderBody(Slider slider)
{
this.slider = slider;
}
private Slider slider;
[BackgroundDependencyLoader]
private void load()
private void load(DrawableHitObject drawableObject)
{
slider = (Slider)drawableObject.HitObject;
Refresh();
}

View File

@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu
ReverseArrow,
HitCircleText,
SliderFollowCircle,
SliderBall
SliderBall,
SliderBody,
}
}

View File

@ -0,0 +1,47 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Skinning
{
public class LegacySliderBody : PlaySliderBody
{
protected override DrawableSliderPath CreateSliderPath() => new LegacyDrawableSliderPath();
private class LegacyDrawableSliderPath : DrawableSliderPath
{
public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f);
protected override Color4 ColourAt(float position)
{
if (CalculatedBorderPortion != 0f && position <= CalculatedBorderPortion)
return BorderColour;
position -= BORDER_PORTION;
Color4 outerColour = AccentColour.Darken(0.1f);
Color4 innerColour = lighten(AccentColour, 0.5f);
return Interpolation.ValueAt(position / GRADIENT_PORTION, outerColour, innerColour, 0, 1);
}
/// <summary>
/// Lightens a colour in a way more friendly to dark or strong colours.
/// </summary>
private static Color4 lighten(Color4 color, float amount)
{
amount *= 0.5f;
return new Color4(
Math.Min(1, color.R * (1 + 0.5f * amount) + 1 * amount),
Math.Min(1, color.G * (1 + 0.5f * amount) + 1 * amount),
Math.Min(1, color.B * (1 + 0.5f * amount) + 1 * amount),
color.A);
}
}
}
}

View File

@ -73,6 +73,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
case OsuSkinComponents.SliderBody:
if (hasHitCircle.Value)
return new LegacySliderBody();
return null;
case OsuSkinComponents.HitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece();

View File

@ -12,6 +12,11 @@ namespace osu.Game.Skinning
/// </summary>
public abstract class SkinReloadableDrawable : CompositeDrawable
{
/// <summary>
/// Invoked when <see cref="CurrentSkin"/> has changed.
/// </summary>
public event Action OnSkinChanged;
/// <summary>
/// The current skin source.
/// </summary>
@ -43,12 +48,18 @@ namespace osu.Game.Skinning
private void onChange() =>
// schedule required to avoid calls after disposed.
// note that this has the side-effect of components only performing a skin change when they are alive.
Scheduler.AddOnce(() => SkinChanged(CurrentSkin, allowDefaultFallback));
Scheduler.AddOnce(skinChanged);
protected override void LoadAsyncComplete()
{
base.LoadAsyncComplete();
skinChanged();
}
private void skinChanged()
{
SkinChanged(CurrentSkin, allowDefaultFallback);
OnSkinChanged?.Invoke();
}
/// <summary>
@ -66,6 +77,8 @@ namespace osu.Game.Skinning
if (CurrentSkin != null)
CurrentSkin.SourceChanged -= onChange;
OnSkinChanged = null;
}
}
}