1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-30 00:03:08 +08:00

Merge pull request #31519 from EVAST9919/trail-rotate

Add support for `CursorTrailRotate` skin command
This commit is contained in:
Dan Balasescu 2025-01-16 14:56:53 +09:00 committed by GitHub
commit 471180d947
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 107 additions and 15 deletions

View File

@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
@ -17,6 +18,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Framework.Testing.Input;
using osu.Game.Audio;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.Skinning.Legacy;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Skinning;
@ -103,6 +105,23 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("contract", () => this.ChildrenOfType<CursorTrail>().Single().NewPartScale = Vector2.One);
}
[Test]
public void TestRotation()
{
createTest(() =>
{
var skinContainer = new LegacySkinContainer(renderer, provideMiddle: true, enableRotation: true);
var legacyCursorTrail = new LegacyRotatingCursorTrail(skinContainer)
{
NewPartScale = new Vector2(10)
};
skinContainer.Child = legacyCursorTrail;
return skinContainer;
});
}
private void createTest(Func<Drawable> createContent) => AddStep("create trail", () =>
{
Clear();
@ -121,12 +140,14 @@ namespace osu.Game.Rulesets.Osu.Tests
private readonly IRenderer renderer;
private readonly bool provideMiddle;
private readonly bool provideCursor;
private readonly bool enableRotation;
public LegacySkinContainer(IRenderer renderer, bool provideMiddle, bool provideCursor = true)
public LegacySkinContainer(IRenderer renderer, bool provideMiddle, bool provideCursor = true, bool enableRotation = false)
{
this.renderer = renderer;
this.provideMiddle = provideMiddle;
this.provideCursor = provideCursor;
this.enableRotation = enableRotation;
RelativeSizeAxes = Axes.Both;
}
@ -152,7 +173,19 @@ namespace osu.Game.Rulesets.Osu.Tests
public ISample GetSample(ISampleInfo sampleInfo) => null;
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null;
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
switch (lookup)
{
case OsuSkinConfiguration osuLookup:
if (osuLookup == OsuSkinConfiguration.CursorTrailRotate)
return SkinUtils.As<TValue>(new BindableBool(enableRotation));
break;
}
return null;
}
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => lookupFunction(this) ? this : null;
@ -185,5 +218,19 @@ namespace osu.Game.Rulesets.Osu.Tests
MoveMouseTo(ToScreenSpace(DrawSize / 2 + DrawSize / 3 * rPos));
}
}
private partial class LegacyRotatingCursorTrail : LegacyCursorTrail
{
public LegacyRotatingCursorTrail([NotNull] ISkin skin)
: base(skin)
{
}
protected override void Update()
{
base.Update();
PartRotation += (float)(Time.Elapsed * 0.1);
}
}
}
}

View File

@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
public partial class LegacyCursor : SkinnableCursor
{
public static readonly int REVOLUTION_DURATION = 10000;
private const float pressed_scale = 1.3f;
private const float released_scale = 1f;
@ -52,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
protected override void LoadComplete()
{
if (spin)
ExpandTarget.Spin(10000, RotationDirection.Clockwise);
ExpandTarget.Spin(REVOLUTION_DURATION, RotationDirection.Clockwise);
}
public override void Expand()

View File

@ -34,6 +34,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private void load(OsuConfigManager config, ISkinSource skinSource)
{
cursorSize = config.GetBindable<float>(OsuSetting.GameplayCursorSize).GetBoundCopy();
AllowPartRotation = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorTrailRotate)?.Value ?? true;
Texture = skin.GetTexture("cursortrail");

View File

@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
CursorCentre,
CursorExpand,
CursorRotate,
CursorTrailRotate,
HitCircleOverlayAboveNumber,
// ReSharper disable once IdentifierTypo

View File

@ -34,19 +34,24 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
/// </summary>
protected virtual float FadeExponent => 1.7f;
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private int currentIndex;
private IShader shader;
private double timeOffset;
private float time;
/// <summary>
/// The scale used on creation of a new trail part.
/// </summary>
public Vector2 NewPartScale = Vector2.One;
public Vector2 NewPartScale { get; set; } = Vector2.One;
private Anchor trailOrigin = Anchor.Centre;
/// <summary>
/// The rotation (in degrees) to apply to trail parts when <see cref="AllowPartRotation"/> is <c>true</c>.
/// </summary>
public float PartRotation { get; set; }
/// <summary>
/// Whether to rotate trail parts based on the value of <see cref="PartRotation"/>.
/// </summary>
protected bool AllowPartRotation { get; set; }
/// <summary>
/// The trail part texture origin.
/// </summary>
protected Anchor TrailOrigin
{
get => trailOrigin;
@ -57,6 +62,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
}
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private Anchor trailOrigin = Anchor.Centre;
private int currentIndex;
private IShader shader;
private double timeOffset;
private float time;
public CursorTrail()
{
// as we are currently very dependent on having a running clock, let's make our own clock for the time being.
@ -220,6 +232,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private float time;
private float fadeExponent;
private float angle;
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private Vector2 originPosition;
@ -239,6 +252,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
texture = Source.texture;
time = Source.time;
fadeExponent = Source.FadeExponent;
angle = Source.AllowPartRotation ? float.DegreesToRadians(Source.PartRotation) : 0;
originPosition = Vector2.Zero;
@ -279,6 +293,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
renderer.PushLocalMatrix(DrawInfo.Matrix);
float sin = MathF.Sin(angle);
float cos = MathF.Cos(angle);
foreach (var part in parts)
{
if (part.InvalidationID == -1)
@ -289,7 +306,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex
{
Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y),
Position = rotateAround(
new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y),
part.Position, sin, cos),
TexturePosition = textureRect.BottomLeft,
TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
@ -298,7 +317,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex
{
Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y),
Position = rotateAround(
new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X,
part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y), part.Position, sin, cos),
TexturePosition = textureRect.BottomRight,
TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomRight.Linear,
@ -307,7 +328,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex
{
Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y),
Position = rotateAround(
new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y),
part.Position, sin, cos),
TexturePosition = textureRect.TopRight,
TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.TopRight.Linear,
@ -316,7 +339,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex
{
Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y),
Position = rotateAround(
new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y),
part.Position, sin, cos),
TexturePosition = textureRect.TopLeft,
TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.TopLeft.Linear,
@ -330,6 +355,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
shader.Unbind();
}
private static Vector2 rotateAround(Vector2 input, Vector2 origin, float sin, float cos)
{
float xTranslated = input.X - origin.X;
float yTranslated = input.Y - origin.Y;
return new Vector2(xTranslated * cos - yTranslated * sin, xTranslated * sin + yTranslated * cos) + origin;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);

View File

@ -36,6 +36,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
/// </summary>
public Vector2 CurrentExpandedScale => skinnableCursor.ExpandTarget?.Scale ?? Vector2.One;
/// <summary>
/// The current rotation of the cursor.
/// </summary>
public float CurrentRotation => skinnableCursor.ExpandTarget?.Rotation ?? 0;
public IBindable<float> CursorScale => cursorScale;
/// <summary>

View File

@ -83,7 +83,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
base.Update();
if (cursorTrail.Drawable is CursorTrail trail)
{
trail.NewPartScale = ActiveCursor.CurrentExpandedScale;
trail.PartRotation = ActiveCursor.CurrentRotation;
}
}
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)