1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-31 05:23:21 +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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
@ -17,6 +18,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Testing.Input; using osu.Framework.Testing.Input;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Osu.Skinning.Legacy;
using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -103,6 +105,23 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("contract", () => this.ChildrenOfType<CursorTrail>().Single().NewPartScale = Vector2.One); 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", () => private void createTest(Func<Drawable> createContent) => AddStep("create trail", () =>
{ {
Clear(); Clear();
@ -121,12 +140,14 @@ namespace osu.Game.Rulesets.Osu.Tests
private readonly IRenderer renderer; private readonly IRenderer renderer;
private readonly bool provideMiddle; private readonly bool provideMiddle;
private readonly bool provideCursor; 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.renderer = renderer;
this.provideMiddle = provideMiddle; this.provideMiddle = provideMiddle;
this.provideCursor = provideCursor; this.provideCursor = provideCursor;
this.enableRotation = enableRotation;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
} }
@ -152,7 +173,19 @@ namespace osu.Game.Rulesets.Osu.Tests
public ISample GetSample(ISampleInfo sampleInfo) => null; 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; 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)); 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 partial class LegacyCursor : SkinnableCursor
{ {
public static readonly int REVOLUTION_DURATION = 10000;
private const float pressed_scale = 1.3f; private const float pressed_scale = 1.3f;
private const float released_scale = 1f; private const float released_scale = 1f;
@ -52,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
protected override void LoadComplete() protected override void LoadComplete()
{ {
if (spin) if (spin)
ExpandTarget.Spin(10000, RotationDirection.Clockwise); ExpandTarget.Spin(REVOLUTION_DURATION, RotationDirection.Clockwise);
} }
public override void Expand() public override void Expand()

View File

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

View File

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

View File

@ -34,19 +34,24 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
/// </summary> /// </summary>
protected virtual float FadeExponent => 1.7f; 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> /// <summary>
/// The scale used on creation of a new trail part. /// The scale used on creation of a new trail part.
/// </summary> /// </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 protected Anchor TrailOrigin
{ {
get => 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() public CursorTrail()
{ {
// as we are currently very dependent on having a running clock, let's make our own clock for the time being. // 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 time;
private float fadeExponent; private float fadeExponent;
private float angle;
private readonly TrailPart[] parts = new TrailPart[max_sprites]; private readonly TrailPart[] parts = new TrailPart[max_sprites];
private Vector2 originPosition; private Vector2 originPosition;
@ -239,6 +252,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
texture = Source.texture; texture = Source.texture;
time = Source.time; time = Source.time;
fadeExponent = Source.FadeExponent; fadeExponent = Source.FadeExponent;
angle = Source.AllowPartRotation ? float.DegreesToRadians(Source.PartRotation) : 0;
originPosition = Vector2.Zero; originPosition = Vector2.Zero;
@ -279,6 +293,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
renderer.PushLocalMatrix(DrawInfo.Matrix); renderer.PushLocalMatrix(DrawInfo.Matrix);
float sin = MathF.Sin(angle);
float cos = MathF.Cos(angle);
foreach (var part in parts) foreach (var part in parts)
{ {
if (part.InvalidationID == -1) if (part.InvalidationID == -1)
@ -289,7 +306,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex 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, TexturePosition = textureRect.BottomLeft,
TextureRect = new Vector4(0, 0, 1, 1), TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomLeft.Linear, Colour = DrawColourInfo.Colour.BottomLeft.Linear,
@ -298,7 +317,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex 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, TexturePosition = textureRect.BottomRight,
TextureRect = new Vector4(0, 0, 1, 1), TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomRight.Linear, Colour = DrawColourInfo.Colour.BottomRight.Linear,
@ -307,7 +328,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex 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, TexturePosition = textureRect.TopRight,
TextureRect = new Vector4(0, 0, 1, 1), TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.TopRight.Linear, Colour = DrawColourInfo.Colour.TopRight.Linear,
@ -316,7 +339,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex 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, TexturePosition = textureRect.TopLeft,
TextureRect = new Vector4(0, 0, 1, 1), TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.TopLeft.Linear, Colour = DrawColourInfo.Colour.TopLeft.Linear,
@ -330,6 +355,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
shader.Unbind(); 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) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);

View File

@ -36,6 +36,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
/// </summary> /// </summary>
public Vector2 CurrentExpandedScale => skinnableCursor.ExpandTarget?.Scale ?? Vector2.One; 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; public IBindable<float> CursorScale => cursorScale;
/// <summary> /// <summary>

View File

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