1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 23:05:37 +08:00

Merge branch 'master' into skin-editor-ux-impovements

This commit is contained in:
Bartłomiej Dach 2023-02-02 18:16:18 +01:00 committed by GitHub
commit 779c58f9c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 274 additions and 65 deletions

View File

@ -5,11 +5,11 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon namespace osu.Game.Rulesets.Mania.Skinning.Argon
@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>(); private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly IBindable<Color4> accentColour = new Bindable<Color4>(); private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
private readonly Box colouredBox; private readonly Box shadeBackground;
private readonly Box shadow; private readonly Box shadeForeground;
public ArgonHoldNoteTailPiece() public ArgonHoldNoteTailPiece()
{ {
@ -32,32 +32,25 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
shadow = new Box shadeBackground = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
new Container new Container
{ {
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Height = 0.82f, Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
Masking = true, Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
CornerRadius = ArgonNotePiece.CORNER_RADIUS, CornerRadius = ArgonNotePiece.CORNER_RADIUS,
Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
colouredBox = new Box shadeForeground = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
} },
} },
},
new Circle
{
RelativeSizeAxes = Axes.X,
Height = ArgonNotePiece.CORNER_RADIUS * 2,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
}, },
}; };
} }
@ -77,19 +70,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction) private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{ {
colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
? Anchor.TopCentre
: Anchor.BottomCentre;
} }
private void onAccentChanged(ValueChangedEvent<Color4> accent) private void onAccentChanged(ValueChangedEvent<Color4> accent)
{ {
colouredBox.Colour = ColourInfo.GradientVertical( shadeBackground.Colour = accent.NewValue.Darken(1.7f);
accent.NewValue, shadeForeground.Colour = accent.NewValue.Darken(1.1f);
accent.NewValue.Darken(0.1f)
);
shadow.Colour = accent.NewValue.Darken(0.5f);
} }
} }
} }

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
internal partial class ArgonNotePiece : CompositeDrawable internal partial class ArgonNotePiece : CompositeDrawable
{ {
public const float NOTE_HEIGHT = 42; public const float NOTE_HEIGHT = 42;
public const float NOTE_ACCENT_RATIO = 0.82f;
public const float CORNER_RADIUS = 3.4f; public const float CORNER_RADIUS = 3.4f;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>(); private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Height = 0.82f, Height = NOTE_ACCENT_RATIO,
Masking = true, Masking = true,
CornerRadius = CORNER_RADIUS, CornerRadius = CORNER_RADIUS,
Children = new Drawable[] Children = new Drawable[]
@ -95,6 +95,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
? Anchor.TopCentre ? Anchor.TopCentre
: Anchor.BottomCentre; : Anchor.BottomCentre;
Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
} }
private void onAccentChanged(ValueChangedEvent<Color4> accent) private void onAccentChanged(ValueChangedEvent<Color4> accent)

View File

@ -0,0 +1,88 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Editing.Checks
{
public class CheckPreviewTimeTest
{
private CheckPreviewTime check = null!;
private IBeatmap beatmap = null!;
[SetUp]
public void Setup()
{
check = new CheckPreviewTime();
}
[Test]
public void TestPreviewTimeNotSet()
{
setNoPreviewTimeBeatmap();
var content = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(content).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckPreviewTime.IssueTemplateHasNoPreviewTime);
}
[Test]
public void TestPreviewTimeconflict()
{
setPreviewTimeConflictBeatmap();
var content = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(content).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckPreviewTime.IssueTemplatePreviewTimeConflict);
Assert.That(issues.Single().Arguments.FirstOrDefault()?.ToString() == "Test1");
}
private void setNoPreviewTimeBeatmap()
{
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata { PreviewTime = -1 },
}
};
}
private void setPreviewTimeConflictBeatmap()
{
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata { PreviewTime = 10 },
BeatmapSet = new BeatmapSetInfo(new List<BeatmapInfo>
{
new BeatmapInfo
{
DifficultyName = "Test1",
Metadata = new BeatmapMetadata { PreviewTime = 5 },
},
new BeatmapInfo
{
DifficultyName = "Test2",
Metadata = new BeatmapMetadata { PreviewTime = 10 },
},
})
}
};
}
}
}

View File

@ -8,12 +8,14 @@ using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Tests.Visual.Background namespace osu.Game.Tests.Visual.Background
{ {
public partial class TestSceneTrianglesV2Background : OsuTestScene public partial class TestSceneTrianglesV2Background : OsuTestScene
{ {
private readonly TrianglesV2 triangles; private readonly TrianglesV2 triangles;
private readonly TrianglesV2 maskedTriangles;
private readonly Box box; private readonly Box box;
public TestSceneTrianglesV2Background() public TestSceneTrianglesV2Background()
@ -31,12 +33,20 @@ namespace osu.Game.Tests.Visual.Background
Origin = Anchor.Centre, Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5), Spacing = new Vector2(0, 10),
Children = new Drawable[] Children = new Drawable[]
{ {
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Masked"
},
new Container new Container
{ {
Size = new Vector2(500, 100), Size = new Vector2(500, 100),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true, Masking = true,
CornerRadius = 40, CornerRadius = 40,
Children = new Drawable[] Children = new Drawable[]
@ -54,9 +64,43 @@ namespace osu.Game.Tests.Visual.Background
} }
} }
}, },
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Non-masked"
},
new Container new Container
{ {
Size = new Vector2(500, 100), Size = new Vector2(500, 100),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Red
},
maskedTriangles = new TrianglesV2
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both
}
}
},
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Gradient comparison box"
},
new Container
{
Size = new Vector2(500, 100),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true, Masking = true,
CornerRadius = 40, CornerRadius = 40,
Child = box = new Box Child = box = new Box
@ -75,14 +119,16 @@ namespace osu.Game.Tests.Visual.Background
AddSliderStep("Spawn ratio", 0f, 10f, 1f, s => AddSliderStep("Spawn ratio", 0f, 10f, 1f, s =>
{ {
triangles.SpawnRatio = s; triangles.SpawnRatio = maskedTriangles.SpawnRatio = s;
triangles.Reset(1234); triangles.Reset(1234);
maskedTriangles.Reset(1234);
}); });
AddSliderStep("Thickness", 0f, 1f, 0.02f, t => triangles.Thickness = t); AddSliderStep("Thickness", 0f, 1f, 0.02f, t => triangles.Thickness = maskedTriangles.Thickness = t);
AddStep("White colour", () => box.Colour = triangles.Colour = Color4.White); AddStep("White colour", () => box.Colour = triangles.Colour = maskedTriangles.Colour = Color4.White);
AddStep("Vertical gradient", () => box.Colour = triangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red)); AddStep("Vertical gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red));
AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red)); AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red));
AddToggleStep("Masking", m => maskedTriangles.Masking = m);
} }
} }
} }

View File

@ -34,6 +34,12 @@ namespace osu.Game.Graphics.Backgrounds
/// </summary> /// </summary>
protected virtual bool CreateNewTriangles => true; protected virtual bool CreateNewTriangles => true;
/// <summary>
/// If enabled, only the portion of triangles that falls within this <see cref="Drawable"/>'s
/// shape is drawn to the screen.
/// </summary>
public bool Masking { get; set; }
private readonly BindableFloat spawnRatio = new BindableFloat(1f); private readonly BindableFloat spawnRatio = new BindableFloat(1f);
/// <summary> /// <summary>
@ -189,6 +195,7 @@ namespace osu.Game.Graphics.Backgrounds
private Vector2 size; private Vector2 size;
private float thickness; private float thickness;
private float texelSize; private float texelSize;
private bool masking;
private IVertexBatch<TexturedVertex2D>? vertexBatch; private IVertexBatch<TexturedVertex2D>? vertexBatch;
@ -205,6 +212,7 @@ namespace osu.Game.Graphics.Backgrounds
texture = Source.texture; texture = Source.texture;
size = Source.DrawSize; size = Source.DrawSize;
thickness = Source.Thickness; thickness = Source.Thickness;
masking = Source.Masking;
Quad triangleQuad = new Quad( Quad triangleQuad = new Quad(
Vector2Extensions.Transform(Vector2.Zero, DrawInfo.Matrix), Vector2Extensions.Transform(Vector2.Zero, DrawInfo.Matrix),
@ -236,26 +244,31 @@ namespace osu.Game.Graphics.Backgrounds
shader.GetUniform<float>("thickness").UpdateValue(ref thickness); shader.GetUniform<float>("thickness").UpdateValue(ref thickness);
shader.GetUniform<float>("texelSize").UpdateValue(ref texelSize); shader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
float relativeHeight = triangleSize.Y / size.Y; Vector2 relativeSize = Vector2.Divide(triangleSize, size);
float relativeWidth = triangleSize.X / size.X;
foreach (TriangleParticle particle in parts) foreach (TriangleParticle particle in parts)
{ {
Vector2 topLeft = particle.Position - new Vector2(relativeWidth * 0.5f, 0f); Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f);
Vector2 topRight = topLeft + new Vector2(relativeWidth, 0f);
Vector2 bottomLeft = topLeft + new Vector2(0f, relativeHeight); Quad triangleQuad = masking ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y);
Vector2 bottomRight = bottomLeft + new Vector2(relativeWidth, 0f);
var drawQuad = new Quad( var drawQuad = new Quad(
Vector2Extensions.Transform(topLeft * size, DrawInfo.Matrix), Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix),
Vector2Extensions.Transform(topRight * size, DrawInfo.Matrix), Vector2Extensions.Transform(triangleQuad.TopRight * size, DrawInfo.Matrix),
Vector2Extensions.Transform(bottomLeft * size, DrawInfo.Matrix), Vector2Extensions.Transform(triangleQuad.BottomLeft * size, DrawInfo.Matrix),
Vector2Extensions.Transform(bottomRight * size, DrawInfo.Matrix) Vector2Extensions.Transform(triangleQuad.BottomRight * size, DrawInfo.Matrix)
); );
ColourInfo colourInfo = triangleColourInfo(DrawColourInfo.Colour, new Quad(topLeft, topRight, bottomLeft, bottomRight)); ColourInfo colourInfo = triangleColourInfo(DrawColourInfo.Colour, triangleQuad);
renderer.DrawQuad(texture, drawQuad, colourInfo, vertexAction: vertexBatch.AddAction); RectangleF textureCoords = new RectangleF(
triangleQuad.TopLeft.X - topLeft.X,
triangleQuad.TopLeft.Y - topLeft.Y,
triangleQuad.Width,
triangleQuad.Height
) / relativeSize;
renderer.DrawQuad(texture, drawQuad, colourInfo, new RectangleF(0, 0, 1, 1), vertexBatch.AddAction, textureCoords: textureCoords);
} }
shader.Unbind(); shader.Unbind();
@ -272,6 +285,19 @@ namespace osu.Game.Graphics.Backgrounds
}; };
} }
private static Quad clampToDrawable(Vector2 topLeft, Vector2 size)
{
float leftClamped = Math.Clamp(topLeft.X, 0f, 1f);
float topClamped = Math.Clamp(topLeft.Y, 0f, 1f);
return new Quad(
leftClamped,
topClamped,
Math.Clamp(topLeft.X + size.X, 0f, 1f) - leftClamped,
Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - topClamped
);
}
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);

View File

@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Edit
new CheckUnsnappedObjects(), new CheckUnsnappedObjects(),
new CheckConcurrentObjects(), new CheckConcurrentObjects(),
new CheckZeroLengthObjects(), new CheckZeroLengthObjects(),
// Timing
new CheckPreviewTime(),
}; };
public IEnumerable<Issue> Run(BeatmapVerifierContext context) public IEnumerable<Issue> Run(BeatmapVerifierContext context)

View File

@ -0,0 +1,62 @@
// 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.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Checks.Components;
namespace osu.Game.Rulesets.Edit.Checks
{
public class CheckPreviewTime : ICheck
{
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Timing, "Inconsistent or unset preview time");
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
{
new IssueTemplatePreviewTimeConflict(this),
new IssueTemplateHasNoPreviewTime(this),
};
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
{
var diffList = context.Beatmap.BeatmapInfo.BeatmapSet?.Beatmaps ?? new List<BeatmapInfo>();
int previewTime = context.Beatmap.BeatmapInfo.Metadata.PreviewTime;
if (previewTime == -1)
yield return new IssueTemplateHasNoPreviewTime(this).Create();
foreach (var diff in diffList)
{
if (diff.Equals(context.Beatmap.BeatmapInfo))
continue;
if (diff.Metadata.PreviewTime != previewTime)
yield return new IssueTemplatePreviewTimeConflict(this).Create(diff.DifficultyName, previewTime, diff.Metadata.PreviewTime);
}
}
public class IssueTemplatePreviewTimeConflict : IssueTemplate
{
public IssueTemplatePreviewTimeConflict(ICheck check)
: base(check, IssueType.Problem, "Audio preview time ({1}) doesn't match the time specified in \"{0}\" ({2})")
{
}
public Issue Create(string diffName, int originalTime, int conflictTime) =>
// preview time should show (not set) when it is not set.
new Issue(this, diffName,
originalTime != -1 ? $"{originalTime:N0} ms" : "not set",
conflictTime != -1 ? $"{conflictTime:N0} ms" : "not set");
}
public class IssueTemplateHasNoPreviewTime : IssueTemplate
{
public IssueTemplateHasNoPreviewTime(ICheck check)
: base(check, IssueType.Problem, "A preview point for this map is not set. Consider setting one from the Timing menu.")
{
}
public Issue Create() => new Issue(this);
}
}
}

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Utils;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -100,21 +99,6 @@ namespace osu.Game.Skinning
switch (result) switch (result)
{ {
case HitResult.Miss:
this.ScaleTo(1.6f);
this.ScaleTo(1, 100, Easing.In);
//todo: this only applies to osu! ruleset apparently.
this.MoveTo(new Vector2(0, -2));
this.MoveToOffset(new Vector2(0, 20), fade_out_delay + fade_out_length, Easing.In);
float rotation = RNG.NextSingle(-8.6f, 8.6f);
this.RotateTo(0);
this.RotateTo(rotation, fade_in_length)
.Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In);
break;
default: default:
mainPiece.ScaleTo(0.9f); mainPiece.ScaleTo(0.9f);
mainPiece.ScaleTo(1.05f, fade_out_delay + fade_out_length); mainPiece.ScaleTo(1.05f, fade_out_delay + fade_out_length);

View File

@ -1,15 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
@ -20,6 +20,9 @@ namespace osu.Game.Skinning
private readonly float finalScale; private readonly float finalScale;
private readonly bool forceTransforms; private readonly bool forceTransforms;
[Resolved]
private ISkinSource skin { get; set; } = null!;
public LegacyJudgementPieceOld(HitResult result, Func<Drawable> createMainDrawable, float finalScale = 1f, bool forceTransforms = false) public LegacyJudgementPieceOld(HitResult result, Func<Drawable> createMainDrawable, float finalScale = 1f, bool forceTransforms = false)
{ {
this.result = result; this.result = result;
@ -55,6 +58,14 @@ namespace osu.Game.Skinning
this.ScaleTo(1.6f); this.ScaleTo(1.6f);
this.ScaleTo(1, 100, Easing.In); this.ScaleTo(1, 100, Easing.In);
decimal? legacyVersion = skin.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value;
if (legacyVersion >= 2.0m)
{
this.MoveTo(new Vector2(0, -5));
this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In);
}
float rotation = RNG.NextSingle(-8.6f, 8.6f); float rotation = RNG.NextSingle(-8.6f, 8.6f);
this.RotateTo(0); this.RotateTo(0);