mirror of
https://github.com/osukey/osukey.git
synced 2025-08-03 22:56:36 +09:00
Merge remote-tracking branch 'upstream/master' into expand-number-piece-on-old-skins
This commit is contained in:
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
/// <summary>
|
||||
/// A single follow point positioned between two adjacent <see cref="DrawableOsuHitObject"/>s.
|
||||
/// </summary>
|
||||
public class FollowPoint : Container
|
||||
public class FollowPoint : Container, IAnimationTimeReference
|
||||
{
|
||||
private const float width = 8;
|
||||
|
||||
@ -43,7 +43,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
Anchor = Anchor.Centre,
|
||||
Alpha = 0.5f,
|
||||
}
|
||||
}, confineMode: ConfineMode.NoScaling);
|
||||
});
|
||||
}
|
||||
|
||||
public double AnimationStartTime { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
private void bindEvents(DrawableOsuHitObject drawableObject)
|
||||
{
|
||||
drawableObject.HitObject.PositionBindable.BindValueChanged(_ => scheduleRefresh());
|
||||
drawableObject.HitObject.DefaultsApplied += scheduleRefresh;
|
||||
drawableObject.HitObject.DefaultsApplied += _ => scheduleRefresh();
|
||||
}
|
||||
|
||||
private void scheduleRefresh()
|
||||
@ -88,8 +88,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
|
||||
private void refresh()
|
||||
{
|
||||
ClearInternal();
|
||||
|
||||
OsuHitObject osuStart = Start.HitObject;
|
||||
double startTime = osuStart.GetEndTime();
|
||||
|
||||
@ -104,8 +102,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 startPosition = osuStart.EndPosition;
|
||||
Vector2 endPosition = osuEnd.Position;
|
||||
Vector2 startPosition = osuStart.StackedEndPosition;
|
||||
Vector2 endPosition = osuEnd.StackedPosition;
|
||||
double endTime = osuEnd.StartTime;
|
||||
|
||||
Vector2 distanceVector = endPosition - startPosition;
|
||||
@ -116,6 +114,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
double? firstTransformStartTime = null;
|
||||
double finalTransformEndTime = startTime;
|
||||
|
||||
int point = 0;
|
||||
|
||||
ClearInternal();
|
||||
|
||||
for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing)
|
||||
{
|
||||
float fraction = (float)d / distance;
|
||||
@ -126,16 +128,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
|
||||
FollowPoint fp;
|
||||
|
||||
AddInternal(fp = new FollowPoint
|
||||
{
|
||||
Position = pointStartPosition,
|
||||
Rotation = rotation,
|
||||
Alpha = 0,
|
||||
Scale = new Vector2(1.5f * osuEnd.Scale),
|
||||
});
|
||||
AddInternal(fp = new FollowPoint());
|
||||
|
||||
if (firstTransformStartTime == null)
|
||||
firstTransformStartTime = fadeInTime;
|
||||
fp.Position = pointStartPosition;
|
||||
fp.Rotation = rotation;
|
||||
fp.Alpha = 0;
|
||||
fp.Scale = new Vector2(1.5f * osuEnd.Scale);
|
||||
|
||||
firstTransformStartTime ??= fadeInTime;
|
||||
|
||||
fp.AnimationStartTime = fadeInTime;
|
||||
|
||||
using (fp.BeginAbsoluteSequence(fadeInTime))
|
||||
{
|
||||
@ -146,8 +148,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
|
||||
finalTransformEndTime = fadeOutTime + osuEnd.TimeFadeIn;
|
||||
}
|
||||
|
||||
point++;
|
||||
}
|
||||
|
||||
int excessPoints = InternalChildren.Count - point;
|
||||
for (int i = 0; i < excessPoints; i++)
|
||||
RemoveInternal(InternalChildren[^1]);
|
||||
|
||||
// todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed.
|
||||
LifetimeStart = firstTransformStartTime ?? startTime;
|
||||
LifetimeEnd = finalTransformEndTime;
|
||||
|
@ -7,8 +7,11 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
@ -30,6 +33,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
public readonly SkinnableDrawable CirclePiece;
|
||||
private readonly Container scaleContainer;
|
||||
|
||||
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
|
||||
|
||||
private InputManager inputManager;
|
||||
|
||||
public DrawableHitCircle(HitCircle h)
|
||||
: base(h)
|
||||
{
|
||||
@ -57,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
return true;
|
||||
},
|
||||
},
|
||||
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece()),
|
||||
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece()),
|
||||
ApproachCircle = new ApproachCircle
|
||||
{
|
||||
Alpha = 0,
|
||||
@ -84,6 +91,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue, true);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
inputManager = GetContainingInputManager();
|
||||
}
|
||||
|
||||
public override double LifetimeStart
|
||||
{
|
||||
get => base.LifetimeStart;
|
||||
@ -118,13 +132,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
var result = HitObject.HitWindows.ResultFor(timeOffset);
|
||||
|
||||
if (result == HitResult.None)
|
||||
if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false)
|
||||
{
|
||||
Shake(Math.Abs(timeOffset) - HitObject.HitWindows.WindowFor(HitResult.Miss));
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyResult(r => r.Type = result);
|
||||
ApplyResult(r =>
|
||||
{
|
||||
var circleResult = (OsuHitCircleJudgementResult)r;
|
||||
|
||||
// Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss.
|
||||
if (result != HitResult.Miss)
|
||||
{
|
||||
var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position);
|
||||
circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2);
|
||||
}
|
||||
|
||||
circleResult.Type = result;
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
@ -170,7 +196,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
public Drawable ProxiedLayer => ApproachCircle;
|
||||
|
||||
public class HitReceptor : Drawable, IKeyBindingHandler<OsuAction>
|
||||
protected override JudgementResult CreateResult(Judgement judgement) => new OsuHitCircleJudgementResult(HitObject, judgement);
|
||||
|
||||
public class HitReceptor : CompositeDrawable, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
// IsHovered is used
|
||||
public override bool HandlePositionalInput => true;
|
||||
@ -185,6 +213,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
CornerRadius = OsuHitObject.OBJECT_RADIUS;
|
||||
CornerExponent = 2;
|
||||
}
|
||||
|
||||
public bool OnPressed(OsuAction action)
|
||||
|
@ -1,11 +1,14 @@
|
||||
// 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.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
@ -16,6 +19,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
// Must be set to update IsHovered as it's used in relax mdo to detect osu hit objects.
|
||||
public override bool HandlePositionalInput => true;
|
||||
|
||||
protected override float SamplePlaybackPosition => HitObject.X / OsuPlayfield.BASE_SIZE.X;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="DrawableOsuHitObject"/> can be hit.
|
||||
/// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false.
|
||||
/// </summary>
|
||||
public Func<DrawableHitObject, double, bool> CheckHittable;
|
||||
|
||||
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
@ -54,6 +65,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
||||
/// </summary>
|
||||
public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss);
|
||||
|
||||
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement);
|
||||
}
|
||||
}
|
||||
|
@ -16,52 +16,87 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableOsuJudgement : DrawableJudgement
|
||||
{
|
||||
private SkinnableSprite lighting;
|
||||
protected SkinnableSprite Lighting;
|
||||
|
||||
private Bindable<Color4> lightingColour;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; }
|
||||
|
||||
public DrawableOsuJudgement(JudgementResult result, DrawableHitObject judgedObject)
|
||||
: base(result, judgedObject)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
public DrawableOsuJudgement()
|
||||
{
|
||||
if (config.Get<bool>(OsuSetting.HitLighting) && Result.Type != HitResult.Miss)
|
||||
{
|
||||
AddInternal(lighting = new SkinnableSprite("lighting")
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Depth = float.MaxValue
|
||||
});
|
||||
}
|
||||
|
||||
if (JudgedObject != null)
|
||||
{
|
||||
lightingColour = JudgedObject.AccentColour.GetBoundCopy();
|
||||
lightingColour.BindValueChanged(colour => lighting.Colour = colour.NewValue, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
lighting.Colour = Color4.White;
|
||||
}
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(Lighting = new SkinnableSprite("lighting")
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Depth = float.MaxValue,
|
||||
Alpha = 0
|
||||
});
|
||||
}
|
||||
|
||||
public override void Apply(JudgementResult result, DrawableHitObject judgedObject)
|
||||
{
|
||||
base.Apply(result, judgedObject);
|
||||
|
||||
if (judgedObject?.HitObject is OsuHitObject osuObject)
|
||||
{
|
||||
Position = osuObject.StackedPosition;
|
||||
Scale = new Vector2(osuObject.Scale);
|
||||
}
|
||||
}
|
||||
|
||||
protected override double FadeOutDelay => lighting == null ? base.FadeOutDelay : 1400;
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
lightingColour?.UnbindAll();
|
||||
|
||||
Lighting.ResetAnimation();
|
||||
|
||||
if (JudgedObject != null)
|
||||
{
|
||||
lightingColour = JudgedObject.AccentColour.GetBoundCopy();
|
||||
lightingColour.BindValueChanged(colour => Lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Lighting.Colour = Color4.White;
|
||||
}
|
||||
}
|
||||
|
||||
private double fadeOutDelay;
|
||||
protected override double FadeOutDelay => fadeOutDelay;
|
||||
|
||||
protected override void ApplyHitAnimations()
|
||||
{
|
||||
if (lighting != null)
|
||||
{
|
||||
JudgementBody.Delay(FadeInDuration).FadeOut(400);
|
||||
bool hitLightingEnabled = config.Get<bool>(OsuSetting.HitLighting);
|
||||
|
||||
lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out);
|
||||
lighting.FadeIn(200).Then().Delay(200).FadeOut(1000);
|
||||
if (hitLightingEnabled)
|
||||
{
|
||||
JudgementBody.FadeIn().Delay(FadeInDuration).FadeOut(400);
|
||||
|
||||
Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out);
|
||||
Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
JudgementBody.Alpha = 1;
|
||||
}
|
||||
|
||||
JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
|
||||
fadeOutDelay = hitLightingEnabled ? 1400 : base.FadeOutDelay;
|
||||
|
||||
JudgementText?.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
|
||||
base.ApplyHitAnimations();
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,17 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osuTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Skinning;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Skinning;
|
||||
@ -26,12 +27,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
public readonly SliderBall Ball;
|
||||
public readonly SkinnableDrawable Body;
|
||||
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody;
|
||||
|
||||
private readonly Container<DrawableSliderHead> headContainer;
|
||||
private readonly Container<DrawableSliderTail> tailContainer;
|
||||
private readonly Container<DrawableSliderTick> tickContainer;
|
||||
private readonly Container<DrawableRepeatPoint> repeatContainer;
|
||||
private readonly Container<DrawableSliderRepeat> repeatContainer;
|
||||
|
||||
private readonly Slider slider;
|
||||
|
||||
@ -50,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
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 },
|
||||
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
|
||||
Ball = new SliderBall(s, this)
|
||||
{
|
||||
GetInitialHitAction = () => HeadCircle.HitAction,
|
||||
@ -80,6 +83,42 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
foreach (var drawableHitObject in NestedHitObjects)
|
||||
drawableHitObject.AccentColour.Value = colour.NewValue;
|
||||
}, true);
|
||||
|
||||
Tracking.BindValueChanged(updateSlidingSample);
|
||||
}
|
||||
|
||||
private SkinnableSound slidingSample;
|
||||
|
||||
protected override void LoadSamples()
|
||||
{
|
||||
base.LoadSamples();
|
||||
|
||||
slidingSample?.Expire();
|
||||
slidingSample = null;
|
||||
|
||||
var firstSample = HitObject.Samples.FirstOrDefault();
|
||||
|
||||
if (firstSample != null)
|
||||
{
|
||||
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
|
||||
clone.Name = "sliderslide";
|
||||
|
||||
AddInternal(slidingSample = new SkinnableSound(clone)
|
||||
{
|
||||
Looping = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSlidingSample(ValueChangedEvent<bool> tracking)
|
||||
{
|
||||
// note that samples will not start playing if exiting a seek operation in the middle of a slider.
|
||||
// may be something we want to address at a later point, but not so easy to make happen right now
|
||||
// (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update).
|
||||
if (tracking.NewValue && ShouldPlaySamples)
|
||||
slidingSample?.Play();
|
||||
else
|
||||
slidingSample?.Stop();
|
||||
}
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||
@ -100,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
tickContainer.Add(tick);
|
||||
break;
|
||||
|
||||
case DrawableRepeatPoint repeat:
|
||||
case DrawableSliderRepeat repeat:
|
||||
repeatContainer.Add(repeat);
|
||||
break;
|
||||
}
|
||||
@ -123,14 +162,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
case SliderTailCircle tail:
|
||||
return new DrawableSliderTail(slider, tail);
|
||||
|
||||
case HitCircle head:
|
||||
return new DrawableSliderHead(slider, head) { OnShake = Shake };
|
||||
case SliderHeadCircle head:
|
||||
return new DrawableSliderHead(slider, head)
|
||||
{
|
||||
OnShake = Shake,
|
||||
CheckHittable = (d, t) => CheckHittable?.Invoke(d, t) ?? true
|
||||
};
|
||||
|
||||
case SliderTick tick:
|
||||
return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position };
|
||||
|
||||
case RepeatPoint repeat:
|
||||
return new DrawableRepeatPoint(repeat, this) { Position = repeat.Position - slider.Position };
|
||||
case SliderRepeat repeat:
|
||||
return new DrawableSliderRepeat(repeat, this) { Position = repeat.Position - slider.Position };
|
||||
}
|
||||
|
||||
return base.CreateNestedHitObject(hitObject);
|
||||
@ -151,6 +194,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
Tracking.Value = Ball.Tracking;
|
||||
|
||||
if (Tracking.Value && slidingSample != null)
|
||||
// keep the sliding sample playing at the current tracking position
|
||||
slidingSample.Balance.Value = CalculateSamplePlaybackBalance(Ball.X / OsuPlayfield.BASE_SIZE.X);
|
||||
|
||||
double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
|
||||
|
||||
Ball.UpdateProgress(completionProgress);
|
||||
@ -185,7 +232,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
base.ApplySkin(skin, allowFallback);
|
||||
|
||||
bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
|
||||
Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
|
||||
Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White;
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
@ -193,22 +240,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
if (userTriggered || Time.Current < slider.EndTime)
|
||||
return;
|
||||
|
||||
ApplyResult(r =>
|
||||
{
|
||||
var judgementsCount = NestedHitObjects.Count;
|
||||
var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
|
||||
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||
}
|
||||
|
||||
var hitFraction = (double)judgementsHit / judgementsCount;
|
||||
|
||||
if (hitFraction == 1 && HeadCircle.Result.Type == HitResult.Great)
|
||||
r.Type = HitResult.Great;
|
||||
else if (hitFraction >= 0.5 && HeadCircle.Result.Type >= HitResult.Good)
|
||||
r.Type = HitResult.Good;
|
||||
else if (hitFraction > 0)
|
||||
r.Type = HitResult.Meh;
|
||||
else
|
||||
r.Type = HitResult.Miss;
|
||||
});
|
||||
public override void PlaySamples()
|
||||
{
|
||||
// rather than doing it this way, we should probably attach the sample to the tail circle.
|
||||
// this can only be done after we stop using LegacyLastTick.
|
||||
if (TailCircle.Result.Type != HitResult.Miss)
|
||||
base.PlaySamples();
|
||||
}
|
||||
|
||||
protected override void UpdateStateTransforms(ArmedState state)
|
||||
|
@ -14,9 +14,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||
|
||||
protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle;
|
||||
|
||||
private readonly Slider slider;
|
||||
|
||||
public DrawableSliderHead(Slider slider, HitCircle h)
|
||||
public DrawableSliderHead(Slider slider, SliderHeadCircle h)
|
||||
: base(h)
|
||||
{
|
||||
this.slider = slider;
|
||||
|
@ -14,24 +14,25 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableRepeatPoint : DrawableOsuHitObject, ITrackSnaking
|
||||
public class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking
|
||||
{
|
||||
private readonly RepeatPoint repeatPoint;
|
||||
private readonly SliderRepeat sliderRepeat;
|
||||
private readonly DrawableSlider drawableSlider;
|
||||
|
||||
private double animDuration;
|
||||
|
||||
private readonly Drawable scaleContainer;
|
||||
|
||||
public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider)
|
||||
: base(repeatPoint)
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider)
|
||||
: base(sliderRepeat)
|
||||
{
|
||||
this.repeatPoint = repeatPoint;
|
||||
this.sliderRepeat = sliderRepeat;
|
||||
this.drawableSlider = drawableSlider;
|
||||
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
|
||||
Blending = BlendingParameters.Additive;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChild = scaleContainer = new ReverseArrowPiece();
|
||||
@ -48,13 +49,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (repeatPoint.StartTime <= Time.Current)
|
||||
if (sliderRepeat.StartTime <= Time.Current)
|
||||
ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss);
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
{
|
||||
animDuration = Math.Min(300, repeatPoint.SpanDuration);
|
||||
animDuration = Math.Min(300, sliderRepeat.SpanDuration);
|
||||
|
||||
this.Animate(
|
||||
d => d.FadeIn(animDuration),
|
||||
@ -87,7 +88,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
public void UpdateSnakingPosition(Vector2 start, Vector2 end)
|
||||
{
|
||||
bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0;
|
||||
// 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;
|
||||
List<Vector2> curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
|
||||
|
||||
Position = isRepeatAtEnd ? end : start;
|
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (!userTriggered && timeOffset >= 0)
|
||||
ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
|
||||
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss);
|
||||
}
|
||||
|
||||
private void updatePosition() => Position = HitObject.Position - slider.Position;
|
||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (timeOffset >= 0)
|
||||
ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
|
||||
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss);
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
|
@ -3,19 +3,18 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
@ -23,27 +22,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
protected readonly Spinner Spinner;
|
||||
|
||||
public readonly SpinnerDisc Disc;
|
||||
public readonly SpinnerTicks Ticks;
|
||||
private readonly Container<DrawableSpinnerTick> ticks;
|
||||
|
||||
public readonly SpinnerRotationTracker RotationTracker;
|
||||
public readonly SpinnerSpmCounter SpmCounter;
|
||||
|
||||
private readonly Container mainContainer;
|
||||
|
||||
public readonly SpinnerBackground Background;
|
||||
private readonly Container circleContainer;
|
||||
private readonly CirclePiece circle;
|
||||
private readonly GlowPiece glow;
|
||||
|
||||
private readonly SpriteIcon symbol;
|
||||
|
||||
private readonly Color4 baseColour = OsuColour.FromHex(@"002c3c");
|
||||
private readonly Color4 fillColour = OsuColour.FromHex(@"005b7c");
|
||||
private readonly SpinnerBonusDisplay bonusDisplay;
|
||||
|
||||
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
||||
|
||||
private Color4 normalColour;
|
||||
private Color4 completeColour;
|
||||
|
||||
public DrawableSpinner(Spinner s)
|
||||
: base(s)
|
||||
{
|
||||
@ -52,62 +38,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
// we are slightly bigger than our parent, to clip the top and bottom of the circle
|
||||
Height = 1.3f;
|
||||
|
||||
Spinner = s;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
circleContainer = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
glow = new GlowPiece(),
|
||||
circle = new CirclePiece
|
||||
{
|
||||
Position = Vector2.Zero,
|
||||
Anchor = Anchor.Centre,
|
||||
},
|
||||
new RingPiece(),
|
||||
symbol = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(48),
|
||||
Icon = FontAwesome.Solid.Asterisk,
|
||||
Shadow = false,
|
||||
},
|
||||
}
|
||||
},
|
||||
mainContainer = new AspectContainer
|
||||
ticks = new Container<DrawableSpinnerTick>(),
|
||||
new AspectContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Children = new[]
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Background = new SpinnerBackground
|
||||
{
|
||||
Alpha = 0.6f,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
Disc = new SpinnerDisc(Spinner)
|
||||
{
|
||||
Scale = Vector2.Zero,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
circleContainer.CreateProxy(),
|
||||
Ticks = new SpinnerTicks
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinnerDisc()),
|
||||
RotationTracker = new SpinnerRotationTracker(Spinner)
|
||||
}
|
||||
},
|
||||
SpmCounter = new SpinnerSpmCounter
|
||||
@ -116,51 +60,136 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
Origin = Anchor.Centre,
|
||||
Y = 120,
|
||||
Alpha = 0
|
||||
},
|
||||
bonusDisplay = new SpinnerBonusDisplay
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Y = -120,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Bindable<bool> isSpinning;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
isSpinning = RotationTracker.IsSpinning.GetBoundCopy();
|
||||
isSpinning.BindValueChanged(updateSpinningSample);
|
||||
}
|
||||
|
||||
private SkinnableSound spinningSample;
|
||||
|
||||
private const float minimum_volume = 0.0001f;
|
||||
|
||||
protected override void LoadSamples()
|
||||
{
|
||||
base.LoadSamples();
|
||||
|
||||
spinningSample?.Expire();
|
||||
spinningSample = null;
|
||||
|
||||
var firstSample = HitObject.Samples.FirstOrDefault();
|
||||
|
||||
if (firstSample != null)
|
||||
{
|
||||
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
|
||||
clone.Name = "spinnerspin";
|
||||
|
||||
AddInternal(spinningSample = new SkinnableSound(clone)
|
||||
{
|
||||
Volume = { Value = minimum_volume },
|
||||
Looping = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSpinningSample(ValueChangedEvent<bool> tracking)
|
||||
{
|
||||
// note that samples will not start playing if exiting a seek operation in the middle of a spinner.
|
||||
// may be something we want to address at a later point, but not so easy to make happen right now
|
||||
// (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update).
|
||||
if (tracking.NewValue && ShouldPlaySamples)
|
||||
{
|
||||
spinningSample?.Play();
|
||||
spinningSample?.VolumeTo(1, 200);
|
||||
}
|
||||
else
|
||||
{
|
||||
spinningSample?.VolumeTo(minimum_volume, 200).Finally(_ => spinningSample.Stop());
|
||||
}
|
||||
}
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||
{
|
||||
base.AddNestedHitObject(hitObject);
|
||||
|
||||
switch (hitObject)
|
||||
{
|
||||
case DrawableSpinnerTick tick:
|
||||
ticks.Add(tick);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateStateTransforms(ArmedState state)
|
||||
{
|
||||
base.UpdateStateTransforms(state);
|
||||
|
||||
using (BeginDelayedSequence(Spinner.Duration, true))
|
||||
this.FadeOut(160);
|
||||
|
||||
// skin change does a rewind of transforms, which will stop the spinning sound from playing if it's currently in playback.
|
||||
isSpinning?.TriggerChange();
|
||||
}
|
||||
|
||||
protected override void ClearNestedHitObjects()
|
||||
{
|
||||
base.ClearNestedHitObjects();
|
||||
ticks.Clear();
|
||||
}
|
||||
|
||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||
{
|
||||
switch (hitObject)
|
||||
{
|
||||
case SpinnerBonusTick bonusTick:
|
||||
return new DrawableSpinnerBonusTick(bonusTick);
|
||||
|
||||
case SpinnerTick tick:
|
||||
return new DrawableSpinnerTick(tick);
|
||||
}
|
||||
|
||||
return base.CreateNestedHitObject(hitObject);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
normalColour = baseColour;
|
||||
|
||||
Background.AccentColour = normalColour;
|
||||
|
||||
completeColour = colours.YellowLight.Opacity(0.75f);
|
||||
|
||||
Disc.AccentColour = fillColour;
|
||||
circle.Colour = colours.BlueDark;
|
||||
glow.Colour = colours.BlueDark;
|
||||
|
||||
positionBindable.BindValueChanged(pos => Position = pos.NewValue);
|
||||
positionBindable.BindTo(HitObject.PositionBindable);
|
||||
}
|
||||
|
||||
public float Progress => Math.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1);
|
||||
/// <summary>
|
||||
/// The completion progress of this spinner from 0..1 (clamped).
|
||||
/// </summary>
|
||||
public float Progress => Math.Clamp(RotationTracker.CumulativeRotation / 360 / Spinner.SpinsRequired, 0, 1);
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (Time.Current < HitObject.StartTime) return;
|
||||
|
||||
if (Progress >= 1 && !Disc.Complete)
|
||||
{
|
||||
Disc.Complete = true;
|
||||
|
||||
const float duration = 200;
|
||||
|
||||
Disc.FadeAccent(completeColour, duration);
|
||||
|
||||
Background.FadeAccent(completeColour, duration);
|
||||
Background.FadeOut(duration);
|
||||
|
||||
circle.FadeColour(completeColour, duration);
|
||||
glow.FadeColour(completeColour, duration);
|
||||
}
|
||||
RotationTracker.Complete.Value = Progress >= 1;
|
||||
|
||||
if (userTriggered || Time.Current < Spinner.EndTime)
|
||||
return;
|
||||
|
||||
// Trigger a miss result for remaining ticks to avoid infinite gameplay.
|
||||
foreach (var tick in ticks.Where(t => !t.IsHit))
|
||||
tick.TriggerResult(false);
|
||||
|
||||
ApplyResult(r =>
|
||||
{
|
||||
if (Progress >= 1)
|
||||
@ -176,59 +205,56 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
|
||||
if (!SpmCounter.IsPresent && Disc.Tracking)
|
||||
SpmCounter.FadeIn(HitObject.TimeFadeIn);
|
||||
|
||||
base.Update();
|
||||
|
||||
if (HandleUserInput)
|
||||
RotationTracker.Tracking = !Result.HasResult && (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
|
||||
|
||||
if (spinningSample != null)
|
||||
// todo: implement SpinnerFrequencyModulate
|
||||
spinningSample.Frequency.Value = 0.5f + Progress;
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
circle.Rotation = Disc.Rotation;
|
||||
Ticks.Rotation = Disc.Rotation;
|
||||
SpmCounter.SetRotation(Disc.RotationAbsolute);
|
||||
if (!SpmCounter.IsPresent && RotationTracker.Tracking)
|
||||
SpmCounter.FadeIn(HitObject.TimeFadeIn);
|
||||
SpmCounter.SetRotation(RotationTracker.CumulativeRotation);
|
||||
|
||||
float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight;
|
||||
Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);
|
||||
|
||||
symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint);
|
||||
updateBonusScore();
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
private int wholeSpins;
|
||||
|
||||
private void updateBonusScore()
|
||||
{
|
||||
base.UpdateInitialTransforms();
|
||||
if (ticks.Count == 0)
|
||||
return;
|
||||
|
||||
circleContainer.ScaleTo(Spinner.Scale * 0.3f);
|
||||
circleContainer.ScaleTo(Spinner.Scale, HitObject.TimePreempt / 1.4f, Easing.OutQuint);
|
||||
int spins = (int)(RotationTracker.CumulativeRotation / 360);
|
||||
|
||||
Disc.RotateTo(-720);
|
||||
symbol.RotateTo(-720);
|
||||
|
||||
mainContainer
|
||||
.ScaleTo(0)
|
||||
.ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, HitObject.TimePreempt - 150, Easing.OutQuint)
|
||||
.Then()
|
||||
.ScaleTo(1, 500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void UpdateStateTransforms(ArmedState state)
|
||||
{
|
||||
base.UpdateStateTransforms(state);
|
||||
|
||||
var sequence = this.Delay(Spinner.Duration).FadeOut(160);
|
||||
|
||||
switch (state)
|
||||
if (spins < wholeSpins)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out);
|
||||
break;
|
||||
// rewinding, silently handle
|
||||
wholeSpins = spins;
|
||||
return;
|
||||
}
|
||||
|
||||
case ArmedState.Miss:
|
||||
sequence.ScaleTo(Scale * 0.8f, 320, Easing.In);
|
||||
break;
|
||||
while (wholeSpins != spins)
|
||||
{
|
||||
var tick = ticks.FirstOrDefault(t => !t.IsHit);
|
||||
|
||||
// tick may be null if we've hit the spin limit.
|
||||
if (tick != null)
|
||||
{
|
||||
tick.TriggerResult(true);
|
||||
if (tick is DrawableSpinnerBonusTick)
|
||||
bonusDisplay.SetBonusCount(spins - Spinner.SpinsRequired);
|
||||
}
|
||||
|
||||
wholeSpins++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableSpinnerBonusTick : DrawableSpinnerTick
|
||||
{
|
||||
public DrawableSpinnerBonusTick(SpinnerBonusTick spinnerTick)
|
||||
: base(spinnerTick)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
// 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.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableSpinnerTick : DrawableOsuHitObject
|
||||
{
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
public DrawableSpinnerTick(SpinnerTick spinnerTick)
|
||||
: base(spinnerTick)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a judgement result.
|
||||
/// </summary>
|
||||
/// <param name="hit">Whether this tick was reached.</param>
|
||||
internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : HitResult.Miss);
|
||||
}
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
public class DefaultSpinnerDisc : CompositeDrawable
|
||||
{
|
||||
private DrawableSpinner drawableSpinner;
|
||||
|
||||
private Spinner spinner;
|
||||
|
||||
private const float idle_alpha = 0.2f;
|
||||
private const float tracking_alpha = 0.4f;
|
||||
|
||||
private Color4 normalColour;
|
||||
private Color4 completeColour;
|
||||
|
||||
private SpinnerTicks ticks;
|
||||
|
||||
private int wholeRotationCount;
|
||||
|
||||
private SpinnerFill fill;
|
||||
private Container mainContainer;
|
||||
private SpinnerCentreLayer centre;
|
||||
private SpinnerBackgroundLayer background;
|
||||
|
||||
public DefaultSpinnerDisc()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
// we are slightly bigger than our parent, to clip the top and bottom of the circle
|
||||
// this should probably be revisited when scaled spinners are a thing.
|
||||
Scale = new Vector2(1.3f);
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, DrawableHitObject drawableHitObject)
|
||||
{
|
||||
drawableSpinner = (DrawableSpinner)drawableHitObject;
|
||||
spinner = (Spinner)drawableSpinner.HitObject;
|
||||
|
||||
normalColour = colours.BlueDark;
|
||||
completeColour = colours.YellowLight;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
mainContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
background = new SpinnerBackgroundLayer(),
|
||||
fill = new SpinnerFill
|
||||
{
|
||||
Alpha = idle_alpha,
|
||||
AccentColour = normalColour
|
||||
},
|
||||
ticks = new SpinnerTicks
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AccentColour = normalColour
|
||||
},
|
||||
}
|
||||
},
|
||||
centre = new SpinnerCentreLayer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200));
|
||||
drawableSpinner.State.BindValueChanged(updateStateTransforms, true);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (drawableSpinner.RotationTracker.Complete.Value)
|
||||
{
|
||||
if (checkNewRotationCount)
|
||||
{
|
||||
fill.FinishTransforms(false, nameof(Alpha));
|
||||
fill
|
||||
.FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo)
|
||||
.Then()
|
||||
.FadeTo(tracking_alpha, 250, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Math.Abs(Clock.ElapsedFrameTime));
|
||||
}
|
||||
|
||||
const float initial_scale = 0.2f;
|
||||
float targetScale = initial_scale + (1 - initial_scale) * drawableSpinner.Progress;
|
||||
|
||||
fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1)));
|
||||
mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation;
|
||||
}
|
||||
|
||||
private void updateStateTransforms(ValueChangedEvent<ArmedState> state)
|
||||
{
|
||||
centre.ScaleTo(0);
|
||||
mainContainer.ScaleTo(0);
|
||||
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true))
|
||||
{
|
||||
// constant ambient rotation to give the spinner "spinning" character.
|
||||
this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration);
|
||||
|
||||
centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint);
|
||||
mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint);
|
||||
|
||||
using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
|
||||
{
|
||||
centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint);
|
||||
mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
// transforms we have from completing the spinner will be rolled back, so reapply immediately.
|
||||
updateComplete(state.NewValue == ArmedState.Hit, 0);
|
||||
|
||||
using (BeginDelayedSequence(spinner.Duration, true))
|
||||
{
|
||||
switch (state.NewValue)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
this.ScaleTo(Scale * 1.2f, 320, Easing.Out);
|
||||
this.RotateTo(mainContainer.Rotation + 180, 320);
|
||||
break;
|
||||
|
||||
case ArmedState.Miss:
|
||||
this.ScaleTo(Scale * 0.8f, 320, Easing.In);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateComplete(bool complete, double duration)
|
||||
{
|
||||
var colour = complete ? completeColour : normalColour;
|
||||
|
||||
ticks.FadeAccent(colour.Darken(1), duration);
|
||||
fill.FadeAccent(colour.Darken(1), duration);
|
||||
|
||||
background.FadeAccent(colour, duration);
|
||||
centre.FadeAccent(colour, duration);
|
||||
}
|
||||
|
||||
private bool checkNewRotationCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int rotations = (int)(drawableSpinner.RotationTracker.CumulativeRotation / 360);
|
||||
|
||||
if (wholeRotationCount == rotations) return false;
|
||||
|
||||
wholeRotationCount = rotations;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
|
||||
using (BeginDelayedSequence(flash_in, true))
|
||||
{
|
||||
//after the flash, we can hide some elements that were behind it
|
||||
// after the flash, we can hide some elements that were behind it
|
||||
ring.FadeOut();
|
||||
circle.FadeOut();
|
||||
number.FadeOut();
|
||||
|
@ -8,11 +8,16 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
public class ReverseArrowPiece : BeatSyncedContainer
|
||||
{
|
||||
[Resolved]
|
||||
private DrawableHitObject drawableRepeat { get; set; }
|
||||
|
||||
public ReverseArrowPiece()
|
||||
{
|
||||
Divisor = 2;
|
||||
@ -21,13 +26,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Blending = BlendingParameters.Additive;
|
||||
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
|
||||
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Icon = FontAwesome.Solid.ChevronRight,
|
||||
Size = new Vector2(0.35f)
|
||||
})
|
||||
@ -37,7 +41,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) =>
|
||||
Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out);
|
||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
||||
{
|
||||
if (!drawableRepeat.IsHit)
|
||||
Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,23 +16,30 @@ using osu.Game.Rulesets.Osu.Skinning;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition
|
||||
public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour
|
||||
{
|
||||
public Func<OsuAction?> GetInitialHitAction;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => ball.Colour;
|
||||
set => ball.Colour = value;
|
||||
}
|
||||
|
||||
private readonly Slider slider;
|
||||
private readonly Drawable followCircle;
|
||||
private readonly DrawableSlider drawableSlider;
|
||||
private readonly Drawable ball;
|
||||
|
||||
public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
|
||||
{
|
||||
this.drawableSlider = drawableSlider;
|
||||
this.slider = slider;
|
||||
|
||||
Blending = BlendingParameters.Additive;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
@ -47,19 +54,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
Alpha = 0,
|
||||
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()),
|
||||
},
|
||||
new CircularContainer
|
||||
ball = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall())
|
||||
{
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Alpha = 1,
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall()),
|
||||
}
|
||||
}
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -180,12 +179,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
return;
|
||||
|
||||
Position = newPos;
|
||||
Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI);
|
||||
ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI);
|
||||
|
||||
lastPosition = newPos;
|
||||
}
|
||||
|
||||
private class FollowCircleContainer : Container
|
||||
private class FollowCircleContainer : CircularContainer
|
||||
{
|
||||
public override bool HandlePositionalInput => true;
|
||||
}
|
||||
@ -233,6 +232,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Blending = BlendingParameters.Additive,
|
||||
BorderThickness = 10,
|
||||
BorderColour = Color4.White,
|
||||
Alpha = 1,
|
||||
|
@ -0,0 +1,44 @@
|
||||
// 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;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
/// <summary>
|
||||
/// Shows incremental bonus score achieved for a spinner.
|
||||
/// </summary>
|
||||
public class SpinnerBonusDisplay : CompositeDrawable
|
||||
{
|
||||
private readonly OsuSpriteText bonusCounter;
|
||||
|
||||
public SpinnerBonusDisplay()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = bonusCounter = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.Numeric.With(size: 24),
|
||||
Alpha = 0,
|
||||
};
|
||||
}
|
||||
|
||||
private int displayedCount;
|
||||
|
||||
public void SetBonusCount(int count)
|
||||
{
|
||||
if (displayedCount == count)
|
||||
return;
|
||||
|
||||
displayedCount = count;
|
||||
bonusCounter.Text = $"{SpinnerBonusTick.SCORE_PER_TICK * count}";
|
||||
bonusCounter.FadeOutFromOne(1500);
|
||||
bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,132 +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.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
public class SpinnerDisc : CircularContainer, IHasAccentColour
|
||||
{
|
||||
private readonly Spinner spinner;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => background.AccentColour;
|
||||
set => background.AccentColour = value;
|
||||
}
|
||||
|
||||
private readonly SpinnerBackground background;
|
||||
|
||||
private const float idle_alpha = 0.2f;
|
||||
private const float tracking_alpha = 0.4f;
|
||||
|
||||
public override bool IsPresent => true; // handle input when hidden
|
||||
|
||||
public SpinnerDisc(Spinner s)
|
||||
{
|
||||
spinner = s;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
background = new SpinnerBackground { Alpha = idle_alpha },
|
||||
};
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
private bool tracking;
|
||||
|
||||
public bool Tracking
|
||||
{
|
||||
get => tracking;
|
||||
set
|
||||
{
|
||||
if (value == tracking) return;
|
||||
|
||||
tracking = value;
|
||||
|
||||
background.FadeTo(tracking ? tracking_alpha : idle_alpha, 100);
|
||||
}
|
||||
}
|
||||
|
||||
private bool complete;
|
||||
|
||||
public bool Complete
|
||||
{
|
||||
get => complete;
|
||||
set
|
||||
{
|
||||
if (value == complete) return;
|
||||
|
||||
complete = value;
|
||||
|
||||
updateCompleteTick();
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
mousePosition = Parent.ToLocalSpace(e.ScreenSpaceMousePosition);
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
private Vector2 mousePosition;
|
||||
|
||||
private float lastAngle;
|
||||
private float currentRotation;
|
||||
public float RotationAbsolute;
|
||||
private int completeTick;
|
||||
|
||||
private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360));
|
||||
|
||||
private bool rotationTransferred;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2));
|
||||
|
||||
bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current;
|
||||
|
||||
if (validAndTracking)
|
||||
{
|
||||
if (!rotationTransferred)
|
||||
{
|
||||
currentRotation = Rotation * 2;
|
||||
rotationTransferred = true;
|
||||
}
|
||||
|
||||
if (thisAngle - lastAngle > 180)
|
||||
lastAngle += 360;
|
||||
else if (lastAngle - thisAngle > 180)
|
||||
lastAngle -= 360;
|
||||
|
||||
currentRotation += thisAngle - lastAngle;
|
||||
RotationAbsolute += Math.Abs(thisAngle - lastAngle) * Math.Sign(Clock.ElapsedFrameTime);
|
||||
}
|
||||
|
||||
lastAngle = thisAngle;
|
||||
|
||||
if (Complete && updateCompleteTick())
|
||||
{
|
||||
background.FinishTransforms(false, nameof(Alpha));
|
||||
background
|
||||
.FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo)
|
||||
.Then()
|
||||
.FadeTo(tracking_alpha, 250, Easing.OutQuint);
|
||||
}
|
||||
|
||||
this.RotateTo(currentRotation / 2, validAndTracking ? 500 : 1500, Easing.OutExpo);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,18 @@
|
||||
// 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 osuTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
public class SpinnerBackground : CircularContainer, IHasAccentColour
|
||||
public class SpinnerFill : CircularContainer, IHasAccentColour
|
||||
{
|
||||
protected Box Disc;
|
||||
public readonly Box Disc;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
@ -31,11 +31,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
}
|
||||
}
|
||||
|
||||
public SpinnerBackground()
|
||||
public SpinnerFill()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Masking = true;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Disc = new Box
|
@ -0,0 +1,119 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
public class SpinnerRotationTracker : CircularContainer
|
||||
{
|
||||
private readonly Spinner spinner;
|
||||
|
||||
public override bool IsPresent => true; // handle input when hidden
|
||||
|
||||
public SpinnerRotationTracker(Spinner s)
|
||||
{
|
||||
spinner = s;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
public bool Tracking { get; set; }
|
||||
|
||||
public readonly BindableBool Complete = new BindableBool();
|
||||
|
||||
/// <summary>
|
||||
/// The total rotation performed on the spinner disc, disregarding the spin direction.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This value is always non-negative and is monotonically increasing with time
|
||||
/// (i.e. will only increase if time is passing forward, but can decrease during rewind).
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// If the spinner is spun 360 degrees clockwise and then 360 degrees counter-clockwise,
|
||||
/// this property will return the value of 720 (as opposed to 0 for <see cref="Drawable.Rotation"/>).
|
||||
/// </example>
|
||||
public float CumulativeRotation { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the spinning is spinning at a reasonable speed to be considered visually spinning.
|
||||
/// </summary>
|
||||
public readonly BindableBool IsSpinning = new BindableBool();
|
||||
|
||||
/// <summary>
|
||||
/// Whether currently in the correct time range to allow spinning.
|
||||
/// </summary>
|
||||
private bool isSpinnableTime => spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current;
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
mousePosition = Parent.ToLocalSpace(e.ScreenSpaceMousePosition);
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
private Vector2 mousePosition;
|
||||
|
||||
private float lastAngle;
|
||||
private float currentRotation;
|
||||
|
||||
private bool rotationTransferred;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2));
|
||||
|
||||
var delta = thisAngle - lastAngle;
|
||||
|
||||
if (Tracking)
|
||||
AddRotation(delta);
|
||||
|
||||
lastAngle = thisAngle;
|
||||
|
||||
IsSpinning.Value = isSpinnableTime && Math.Abs(currentRotation / 2 - Rotation) > 5f;
|
||||
|
||||
Rotation = (float)Interpolation.Damp(Rotation, currentRotation / 2, 0.99, Math.Abs(Time.Elapsed));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotate the disc by the provided angle (in addition to any existing rotation).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Will be a no-op if not a valid time to spin.
|
||||
/// </remarks>
|
||||
/// <param name="angle">The delta angle.</param>
|
||||
public void AddRotation(float angle)
|
||||
{
|
||||
if (!isSpinnableTime)
|
||||
return;
|
||||
|
||||
if (!rotationTransferred)
|
||||
{
|
||||
currentRotation = Rotation * 2;
|
||||
rotationTransferred = true;
|
||||
}
|
||||
|
||||
if (angle > 180)
|
||||
{
|
||||
lastAngle += 360;
|
||||
angle -= 360;
|
||||
}
|
||||
else if (-angle > 180)
|
||||
{
|
||||
lastAngle -= 360;
|
||||
angle += 360;
|
||||
}
|
||||
|
||||
currentRotation += angle;
|
||||
CumulativeRotation += Math.Abs(angle) * Math.Sign(Clock.ElapsedFrameTime);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -9,10 +10,11 @@ using osu.Framework.Graphics.Effects;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
public class SpinnerTicks : Container
|
||||
public class SpinnerTicks : Container, IHasAccentColour
|
||||
{
|
||||
public SpinnerTicks()
|
||||
{
|
||||
@ -20,28 +22,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
Anchor = Anchor.Centre;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
const float count = 18;
|
||||
const float count = 8;
|
||||
|
||||
for (float i = 0; i < count; i++)
|
||||
{
|
||||
Add(new Container
|
||||
{
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.4f,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 10,
|
||||
Colour = Color4.Gray.Opacity(0.2f),
|
||||
},
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
Size = new Vector2(60, 10),
|
||||
Origin = Anchor.Centre,
|
||||
Position = new Vector2(
|
||||
0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.86f,
|
||||
0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.86f
|
||||
0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.83f,
|
||||
0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.83f
|
||||
),
|
||||
Rotation = -i / count * 360 + 90,
|
||||
Children = new[]
|
||||
@ -54,5 +50,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => Colour;
|
||||
set
|
||||
{
|
||||
Colour = value;
|
||||
|
||||
foreach (var c in Children.OfType<Container>())
|
||||
{
|
||||
c.EdgeEffect =
|
||||
new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 20,
|
||||
Colour = value.Opacity(0.8f),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
// 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.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class SpinnerBackgroundLayer : SpinnerFill
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, DrawableHitObject drawableHitObject)
|
||||
{
|
||||
Disc.Alpha = 0;
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class SpinnerCentreLayer : CompositeDrawable, IHasAccentColour
|
||||
{
|
||||
private DrawableSpinner spinner;
|
||||
|
||||
private CirclePiece circle;
|
||||
private GlowPiece glow;
|
||||
private SpriteIcon symbol;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableHitObject drawableHitObject)
|
||||
{
|
||||
spinner = (DrawableSpinner)drawableHitObject;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
glow = new GlowPiece(),
|
||||
circle = new CirclePiece
|
||||
{
|
||||
Position = Vector2.Zero,
|
||||
Anchor = Anchor.Centre,
|
||||
},
|
||||
new RingPiece(),
|
||||
symbol = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(48),
|
||||
Icon = FontAwesome.Solid.Asterisk,
|
||||
Shadow = false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, spinner.RotationTracker.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1));
|
||||
}
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
accentColour = value;
|
||||
|
||||
circle.Colour = accentColour;
|
||||
glow.Colour = accentColour;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user