1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-26 16:12:54 +08:00

Merge pull request #11055 from ekrctb/catch-stateless-rng

This commit is contained in:
Dean Herbert 2020-12-04 13:08:30 +09:00 committed by GitHub
commit 0134ac94a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 159 additions and 32 deletions

View File

@ -3,11 +3,12 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Objects.Drawables;
using osuTK;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
@ -37,39 +38,50 @@ namespace osu.Game.Rulesets.Catch.Tests
} }
private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) => private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) =>
SetProperties(new DrawableFruit(new Fruit new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
{ {
IndexInBeatmap = indexInBeatmap, IndexInBeatmap = indexInBeatmap,
HyperDashBindable = { Value = hyperdash } HyperDashBindable = { Value = hyperdash }
})); }));
private Drawable createDrawableBanana() => private Drawable createDrawableBanana() =>
SetProperties(new DrawableBanana(new Banana())); new TestDrawableCatchHitObjectSpecimen(new DrawableBanana(new Banana()));
private Drawable createDrawableDroplet(bool hyperdash = false) => private Drawable createDrawableDroplet(bool hyperdash = false) =>
SetProperties(new DrawableDroplet(new Droplet new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
{ {
HyperDashBindable = { Value = hyperdash } HyperDashBindable = { Value = hyperdash }
})); }));
private Drawable createDrawableTinyDroplet() => SetProperties(new DrawableTinyDroplet(new TinyDroplet())); private Drawable createDrawableTinyDroplet() => new TestDrawableCatchHitObjectSpecimen(new DrawableTinyDroplet(new TinyDroplet()));
}
protected virtual DrawableCatchHitObject SetProperties(DrawableCatchHitObject d) public class TestDrawableCatchHitObjectSpecimen : CompositeDrawable
{ {
public readonly ManualClock ManualClock;
public TestDrawableCatchHitObjectSpecimen(DrawableCatchHitObject d)
{
AutoSizeAxes = Axes.Both;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
ManualClock = new ManualClock();
Clock = new FramedClock(ManualClock);
var hitObject = d.HitObject; var hitObject = d.HitObject;
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 0 }); hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
hitObject.StartTime = 1000000000000;
hitObject.Scale = 1.5f; hitObject.Scale = 1.5f;
hitObject.StartTime = 500;
d.Anchor = Anchor.Centre; d.Anchor = Anchor.Centre;
d.RelativePositionAxes = Axes.None;
d.Position = Vector2.Zero;
d.HitObjectApplied += _ => d.HitObjectApplied += _ =>
{ {
d.LifetimeStart = double.NegativeInfinity; d.LifetimeStart = double.NegativeInfinity;
d.LifetimeEnd = double.PositiveInfinity; d.LifetimeEnd = double.PositiveInfinity;
}; };
return d;
InternalChild = d;
} }
} }
} }

View File

@ -0,0 +1,96 @@
// 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 NUnit.Framework;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Tests
{
public class TestSceneFruitRandomness : OsuTestScene
{
private readonly TestDrawableFruit drawableFruit;
private readonly TestDrawableBanana drawableBanana;
public TestSceneFruitRandomness()
{
drawableFruit = new TestDrawableFruit(new Fruit());
drawableBanana = new TestDrawableBanana(new Banana());
Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 });
Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana));
AddSliderStep("start time", 500, 600, 0, x =>
{
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = x;
});
}
[Test]
public void TestFruitRandomness()
{
// Use values such that the banana colour changes (2/3 of the integers are okay)
const int initial_start_time = 500;
const int another_start_time = 501;
float fruitRotation = 0;
float bananaRotation = 0;
float bananaScale = 0;
Color4 bananaColour = new Color4();
AddStep("Initialize start time", () =>
{
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
fruitRotation = drawableFruit.InnerRotation;
bananaRotation = drawableBanana.InnerRotation;
bananaScale = drawableBanana.InnerScale;
bananaColour = drawableBanana.AccentColour.Value;
});
AddStep("change start time", () =>
{
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
});
AddAssert("fruit rotation is changed", () => drawableFruit.InnerRotation != fruitRotation);
AddAssert("banana rotation is changed", () => drawableBanana.InnerRotation != bananaRotation);
AddAssert("banana scale is changed", () => drawableBanana.InnerScale != bananaScale);
AddAssert("banana colour is changed", () => drawableBanana.AccentColour.Value != bananaColour);
AddStep("reset start time", () =>
{
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
});
AddAssert("rotation and scale restored", () =>
drawableFruit.InnerRotation == fruitRotation &&
drawableBanana.InnerRotation == bananaRotation &&
drawableBanana.InnerScale == bananaScale &&
drawableBanana.AccentColour.Value == bananaColour);
}
private class TestDrawableFruit : DrawableFruit
{
public float InnerRotation => ScaleContainer.Rotation;
public TestDrawableFruit(Fruit h)
: base(h)
{
}
}
private class TestDrawableBanana : DrawableBanana
{
public float InnerRotation => ScaleContainer.Rotation;
public float InnerScale => ScaleContainer.Scale.X;
public TestDrawableBanana(Banana h)
: base(h)
{
}
}
}
}

View File

@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Catch.Tests
protected override void LoadComplete() protected override void LoadComplete()
{ {
AddStep("fruit changes visual and hyper", () => SetContents(() => SetProperties(new DrawableFruit(new Fruit AddStep("fruit changes visual and hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
{ {
IndexInBeatmapBindable = { BindTarget = indexInBeatmap }, IndexInBeatmapBindable = { BindTarget = indexInBeatmap },
HyperDashBindable = { BindTarget = hyperDash }, HyperDashBindable = { BindTarget = hyperDash },
})))); }))));
AddStep("droplet changes hyper", () => SetContents(() => SetProperties(new DrawableDroplet(new Droplet AddStep("droplet changes hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
{ {
HyperDashBindable = { BindTarget = hyperDash }, HyperDashBindable = { BindTarget = hyperDash },
})))); }))));

View File

@ -5,7 +5,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Utils;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
@ -31,17 +30,12 @@ namespace osu.Game.Rulesets.Catch.Objects
Samples = samples; Samples = samples;
} }
private Color4? colour;
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours)
{
// override any external colour changes with banananana // override any external colour changes with banananana
return colour ??= getBananaColour(); Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours) => getBananaColour();
}
private Color4 getBananaColour() private Color4 getBananaColour()
{ {
switch (RNG.Next(0, 3)) switch (StatelessRNG.NextInt(3, RandomSeed))
{ {
default: default:
return new Color4(255, 240, 0, 255); return new Color4(255, 240, 0, 255);

View File

@ -97,6 +97,12 @@ namespace osu.Game.Rulesets.Catch.Objects
set => ScaleBindable.Value = value; set => ScaleBindable.Value = value;
} }
/// <summary>
/// The seed value used for visual randomness such as fruit rotation.
/// The value is <see cref="HitObject.StartTime"/> truncated to an integer.
/// </summary>
public int RandomSeed => (int)StartTime;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{ {
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);

View File

@ -3,7 +3,6 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Utils;
namespace osu.Game.Rulesets.Catch.Objects.Drawables namespace osu.Game.Rulesets.Catch.Objects.Drawables
{ {
@ -21,6 +20,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{ {
} }
protected override void LoadComplete()
{
base.LoadComplete();
// start time affects the random seed which is used to determine the banana colour
StartTimeBindable.BindValueChanged(_ => UpdateComboColour());
}
protected override void UpdateInitialTransforms() protected override void UpdateInitialTransforms()
{ {
base.UpdateInitialTransforms(); base.UpdateInitialTransforms();
@ -28,14 +35,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
const float end_scale = 0.6f; const float end_scale = 0.6f;
const float random_scale_range = 1.6f; const float random_scale_range = 1.6f;
ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RNG.NextSingle())) ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3)))
.Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt); .Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
ScaleContainer.RotateTo(getRandomAngle()) ScaleContainer.RotateTo(getRandomAngle(1))
.Then() .Then()
.RotateTo(getRandomAngle(), HitObject.TimePreempt); .RotateTo(getRandomAngle(2), HitObject.TimePreempt);
float getRandomAngle() => 180 * (RNG.NextSingle() * 2 - 1); float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1);
} }
public override void PlaySamples() public override void PlaySamples()

View File

@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Utils;
namespace osu.Game.Rulesets.Catch.Objects.Drawables namespace osu.Game.Rulesets.Catch.Objects.Drawables
{ {
@ -20,12 +21,19 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH; protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
public int RandomSeed => HitObject?.RandomSeed ?? 0;
protected DrawableCatchHitObject([CanBeNull] CatchHitObject hitObject) protected DrawableCatchHitObject([CanBeNull] CatchHitObject hitObject)
: base(hitObject) : base(hitObject)
{ {
Anchor = Anchor.BottomLeft; Anchor = Anchor.BottomLeft;
} }
/// <summary>
/// Get a random number in range [0,1) based on seed <see cref="RandomSeed"/>.
/// </summary>
public float RandomSingle(int series) => StatelessRNG.NextSingle(RandomSeed, series);
protected override void OnApply() protected override void OnApply()
{ {
base.OnApply(); base.OnApply();

View File

@ -4,7 +4,6 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -45,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
base.UpdateInitialTransforms(); base.UpdateInitialTransforms();
// roughly matches osu-stable // roughly matches osu-stable
float startRotation = RNG.NextSingle() * 20; float startRotation = RandomSingle(1) * 20;
double duration = HitObject.TimePreempt + 2000; double duration = HitObject.TimePreempt + 2000;
ScaleContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration); ScaleContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);

View File

@ -5,7 +5,7 @@ using System;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Utils; using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -30,8 +30,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
ScaleContainer.Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
IndexInBeatmap.BindValueChanged(change => IndexInBeatmap.BindValueChanged(change =>
{ {
VisualRepresentation.Value = GetVisualRepresentation(change.NewValue); VisualRepresentation.Value = GetVisualRepresentation(change.NewValue);
@ -41,6 +39,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
HyperDash.BindValueChanged(_ => updatePiece(), true); HyperDash.BindValueChanged(_ => updatePiece(), true);
} }
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
ScaleContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
}
private void updatePiece() private void updatePiece()
{ {
ScaleContainer.Child = new SkinnableDrawable( ScaleContainer.Child = new SkinnableDrawable(