osu/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs

279 lines
11 KiB
C#

// 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.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning.Argon
{
public partial class ArgonSpinnerDisc : CompositeDrawable
{
private const float initial_scale = 1f;
private const float idle_alpha = 0.2f;
private const float tracking_alpha = 0.4f;
private const float idle_centre_size = 80f;
private const float tracking_centre_size = 40f;
private DrawableSpinner drawableSpinner = null!;
private readonly BindableBool complete = new BindableBool();
private int wholeRotationCount;
private bool checkNewRotationCount
{
get
{
int rotations = (int)(drawableSpinner.Result.TotalRotation / 360);
if (wholeRotationCount == rotations) return false;
wholeRotationCount = rotations;
return true;
}
}
private Container disc = null!;
private Container centre = null!;
private CircularContainer fill = null!;
private Container ticksContainer = null!;
private ArgonSpinnerTicks ticks = null!;
[BackgroundDependencyLoader]
private void load(DrawableHitObject drawableHitObject)
{
drawableSpinner = (DrawableSpinner)drawableHitObject;
// 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(initial_scale);
InternalChildren = new Drawable[]
{
disc = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(8f),
Children = new[]
{
fill = new CircularContainer
{
Name = @"Fill",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Colour4.FromHex("FC618F").Opacity(1f),
Radius = 40,
},
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0f,
AlwaysPresent = true,
}
},
ticksContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Child = ticks = new ArgonSpinnerTicks(),
}
},
},
new Container
{
Name = @"Ring",
Masking = true,
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(8f),
Children = new[]
{
new ArgonSpinnerRingArc
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Name = "Top Arc",
},
new ArgonSpinnerRingArc
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Name = "Bottom Arc",
Scale = new Vector2(1, -1),
},
}
},
new Container
{
Name = @"Sides",
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new ArgonSpinnerProgressArc
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Name = "Left Bar"
},
new ArgonSpinnerProgressArc
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Name = "Right Bar",
Scale = new Vector2(-1, 1),
},
}
},
}
},
centre = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(idle_centre_size),
Children = new[]
{
new RingPiece(10)
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f),
},
new RingPiece(3)
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1f),
}
},
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
}
private float trackingElementInterpolation;
protected override void Update()
{
base.Update();
complete.Value = Time.Current >= drawableSpinner.Result.TimeCompleted;
if (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
{
trackingElementInterpolation =
(float)Interpolation.Damp(trackingElementInterpolation, drawableSpinner.RotationTracker.Tracking ? 1 : 0, 0.985f, (float)Math.Abs(Clock.ElapsedFrameTime));
fill.Alpha = trackingElementInterpolation * (tracking_alpha - idle_alpha) + idle_alpha;
centre.Size = new Vector2(trackingElementInterpolation * (tracking_centre_size - idle_centre_size) + idle_centre_size);
}
const float initial_fill_scale = 0.1f;
float targetScale = initial_fill_scale + (0.98f - initial_fill_scale) * drawableSpinner.Progress;
fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1)));
ticks.Rotation = drawableSpinner.RotationTracker.Rotation;
}
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{
if (!(drawableHitObject is DrawableSpinner))
return;
Spinner spinner = drawableSpinner.HitObject;
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
{
this.ScaleTo(initial_scale);
ticksContainer.RotateTo(0);
centre.ScaleTo(0);
disc.ScaleTo(0);
using (BeginDelayedSequence(spinner.TimePreempt / 2))
{
// constant ambient rotation to give the spinner "spinning" character.
ticksContainer.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration);
}
using (BeginDelayedSequence(spinner.TimePreempt / 2))
{
centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint);
disc.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint);
using (BeginDelayedSequence(spinner.TimePreempt / 2))
{
centre.ScaleTo(0.8f, spinner.TimePreempt / 2, Easing.OutQuint);
disc.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint);
}
}
using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset))
{
switch (state)
{
case ArmedState.Hit:
disc.ScaleTo(initial_scale * 1.2f, 320, Easing.Out);
ticksContainer.RotateTo(ticksContainer.Rotation + 180, 320);
break;
case ArmedState.Miss:
disc.ScaleTo(initial_scale * 0.8f, 320, Easing.In);
break;
}
}
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (drawableSpinner.IsNotNull())
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
}
}
}