mirror of
https://github.com/ppy/osu.git
synced 2025-03-15 15:27:20 +08:00
Merge branch 'master' into editor-move-distance-snap-grid
This commit is contained in:
commit
1311fd2f64
@ -52,6 +52,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.518.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.519.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -15,7 +15,6 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
@ -56,8 +55,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject);
|
||||
|
||||
public Column ColumnAt(Vector2 screenSpacePosition) => column;
|
||||
|
||||
public int TotalColumns => 1;
|
||||
public ManiaPlayfield Playfield => null;
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Timing;
|
||||
using osu.Game.Rulesets.Mania.Edit;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
@ -18,11 +17,9 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[Cached(Type = typeof(IAdjustableClock))]
|
||||
private readonly IAdjustableClock clock = new StopwatchClock();
|
||||
|
||||
private readonly Column column;
|
||||
|
||||
protected ManiaSelectionBlueprintTestScene()
|
||||
{
|
||||
Add(column = new Column(0)
|
||||
Add(new Column(0)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -31,8 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
});
|
||||
}
|
||||
|
||||
public Column ColumnAt(Vector2 screenSpacePosition) => column;
|
||||
|
||||
public int TotalColumns => 1;
|
||||
public ManiaPlayfield Playfield => null;
|
||||
}
|
||||
}
|
||||
|
70
osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs
Normal file
70
osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs
Normal file
@ -0,0 +1,70 @@
|
||||
// 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.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Edit;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[Cached(typeof(IManiaHitObjectComposer))]
|
||||
public class TestSceneManiaBeatSnapGrid : EditorClockTestScene, IManiaHitObjectComposer
|
||||
{
|
||||
[Cached(typeof(IScrollingInfo))]
|
||||
private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo();
|
||||
|
||||
[Cached(typeof(EditorBeatmap))]
|
||||
private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition()));
|
||||
|
||||
private readonly ManiaBeatSnapGrid beatSnapGrid;
|
||||
|
||||
public TestSceneManiaBeatSnapGrid()
|
||||
{
|
||||
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 200 });
|
||||
editorBeatmap.ControlPointInfo.Add(10000, new TimingControlPoint { BeatLength = 200 });
|
||||
|
||||
BeatDivisor.Value = 3;
|
||||
|
||||
// Some sane defaults
|
||||
scrollingInfo.Algorithm.Algorithm = ScrollVisualisationMethod.Constant;
|
||||
scrollingInfo.Direction.Value = ScrollingDirection.Up;
|
||||
scrollingInfo.TimeRange.Value = 1000;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Playfield = new ManiaPlayfield(new List<StageDefinition>
|
||||
{
|
||||
new StageDefinition { Columns = 4 },
|
||||
new StageDefinition { Columns = 3 }
|
||||
})
|
||||
{
|
||||
Clock = new FramedClock(new StopwatchClock())
|
||||
},
|
||||
beatSnapGrid = new ManiaBeatSnapGrid()
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
// We're providing a constant scroll algorithm.
|
||||
float relativePosition = Playfield.Stages[0].HitObjectContainer.ToLocalSpace(e.ScreenSpaceMousePosition).Y / Playfield.Stages[0].HitObjectContainer.DrawHeight;
|
||||
double timeValue = scrollingInfo.TimeRange.Value * relativePosition;
|
||||
|
||||
beatSnapGrid.SelectionTimeRange = (timeValue, timeValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ManiaPlayfield Playfield { get; }
|
||||
}
|
||||
}
|
@ -2,14 +2,11 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
public interface IManiaHitObjectComposer
|
||||
{
|
||||
Column ColumnAt(Vector2 screenSpacePosition);
|
||||
|
||||
int TotalColumns { get; }
|
||||
ManiaPlayfield Playfield { get; }
|
||||
}
|
||||
}
|
||||
|
213
osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
Normal file
213
osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
Normal file
@ -0,0 +1,213 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
/// <summary>
|
||||
/// A grid which displays coloured beat divisor lines in proximity to the selection or placement cursor.
|
||||
/// </summary>
|
||||
public class ManiaBeatSnapGrid : Component
|
||||
{
|
||||
private const double visible_range = 750;
|
||||
|
||||
/// <summary>
|
||||
/// The range of time values of the current selection.
|
||||
/// </summary>
|
||||
public (double start, double end)? SelectionTimeRange
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == selectionTimeRange)
|
||||
return;
|
||||
|
||||
selectionTimeRange = value;
|
||||
lineCache.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap beatmap { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private Bindable<WorkingBeatmap> working { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BindableBeatDivisor beatDivisor { get; set; }
|
||||
|
||||
private readonly List<ScrollingHitObjectContainer> grids = new List<ScrollingHitObjectContainer>();
|
||||
|
||||
private readonly Cached lineCache = new Cached();
|
||||
|
||||
private (double start, double end)? selectionTimeRange;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IManiaHitObjectComposer composer)
|
||||
{
|
||||
foreach (var stage in composer.Playfield.Stages)
|
||||
{
|
||||
foreach (var column in stage.Columns)
|
||||
{
|
||||
var lineContainer = new ScrollingHitObjectContainer();
|
||||
|
||||
grids.Add(lineContainer);
|
||||
column.UnderlayElements.Add(lineContainer);
|
||||
}
|
||||
}
|
||||
|
||||
beatDivisor.BindValueChanged(_ => createLines(), true);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!lineCache.IsValid)
|
||||
{
|
||||
lineCache.Validate();
|
||||
createLines();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Stack<DrawableGridLine> availableLines = new Stack<DrawableGridLine>();
|
||||
|
||||
private void createLines()
|
||||
{
|
||||
foreach (var grid in grids)
|
||||
{
|
||||
foreach (var line in grid.Objects.OfType<DrawableGridLine>())
|
||||
availableLines.Push(line);
|
||||
|
||||
grid.Clear(false);
|
||||
}
|
||||
|
||||
if (selectionTimeRange == null)
|
||||
return;
|
||||
|
||||
var range = selectionTimeRange.Value;
|
||||
|
||||
var timingPoint = beatmap.ControlPointInfo.TimingPointAt(range.start - visible_range);
|
||||
|
||||
double time = timingPoint.Time;
|
||||
int beat = 0;
|
||||
|
||||
// progress time until in the visible range.
|
||||
while (time < range.start - visible_range)
|
||||
{
|
||||
time += timingPoint.BeatLength / beatDivisor.Value;
|
||||
beat++;
|
||||
}
|
||||
|
||||
while (time < range.end + visible_range)
|
||||
{
|
||||
var nextTimingPoint = beatmap.ControlPointInfo.TimingPointAt(time);
|
||||
|
||||
// switch to the next timing point if we have reached it.
|
||||
if (nextTimingPoint.Time > timingPoint.Time)
|
||||
{
|
||||
beat = 0;
|
||||
time = nextTimingPoint.Time;
|
||||
timingPoint = nextTimingPoint;
|
||||
}
|
||||
|
||||
Color4 colour = BindableBeatDivisor.GetColourFor(
|
||||
BindableBeatDivisor.GetDivisorForBeatIndex(Math.Max(1, beat), beatDivisor.Value), colours);
|
||||
|
||||
foreach (var grid in grids)
|
||||
{
|
||||
if (!availableLines.TryPop(out var line))
|
||||
line = new DrawableGridLine();
|
||||
|
||||
line.HitObject.StartTime = time;
|
||||
line.Colour = colour;
|
||||
|
||||
grid.Add(line);
|
||||
}
|
||||
|
||||
beat++;
|
||||
time += timingPoint.BeatLength / beatDivisor.Value;
|
||||
}
|
||||
|
||||
foreach (var grid in grids)
|
||||
{
|
||||
// required to update ScrollingHitObjectContainer's cache.
|
||||
grid.UpdateSubTree();
|
||||
|
||||
foreach (var line in grid.Objects.OfType<DrawableGridLine>())
|
||||
{
|
||||
time = line.HitObject.StartTime;
|
||||
|
||||
if (time >= range.start && time <= range.end)
|
||||
line.Alpha = 1;
|
||||
else
|
||||
{
|
||||
double timeSeparation = time < range.start ? range.start - time : time - range.end;
|
||||
line.Alpha = (float)Math.Max(0, 1 - timeSeparation / visible_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DrawableGridLine : DrawableHitObject
|
||||
{
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; }
|
||||
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
public DrawableGridLine()
|
||||
: base(new HitObject())
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 2;
|
||||
|
||||
AddInternal(new Box { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
{
|
||||
Origin = Anchor = direction.NewValue == ScrollingDirection.Up
|
||||
? Anchor.TopLeft
|
||||
: Anchor.BottomLeft;
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
{
|
||||
// don't perform any fading – we are handling that ourselves.
|
||||
}
|
||||
|
||||
protected override void UpdateStateTransforms(ArmedState state)
|
||||
{
|
||||
LifetimeEnd = HitObject.StartTime + visible_range;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,9 +6,12 @@ using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
@ -20,18 +23,26 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject>, IManiaHitObjectComposer
|
||||
{
|
||||
private DrawableManiaEditRuleset drawableRuleset;
|
||||
private ManiaBeatSnapGrid beatSnapGrid;
|
||||
private InputManager inputManager;
|
||||
|
||||
public ManiaHitObjectComposer(Ruleset ruleset)
|
||||
: base(ruleset)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the column that intersects a screen-space position.
|
||||
/// </summary>
|
||||
/// <param name="screenSpacePosition">The screen-space position.</param>
|
||||
/// <returns>The column which intersects with <paramref name="screenSpacePosition"/>.</returns>
|
||||
public Column ColumnAt(Vector2 screenSpacePosition) => drawableRuleset.GetColumnByPosition(screenSpacePosition);
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(beatSnapGrid = new ManiaBeatSnapGrid());
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
inputManager = GetContainingInputManager();
|
||||
}
|
||||
|
||||
private DependencyContainer dependencies;
|
||||
|
||||
@ -42,11 +53,9 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
|
||||
public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo;
|
||||
|
||||
public int TotalColumns => Playfield.TotalColumns;
|
||||
|
||||
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||
{
|
||||
var column = ColumnAt(screenSpacePosition);
|
||||
var column = Playfield.GetColumnByPosition(screenSpacePosition);
|
||||
|
||||
if (column == null)
|
||||
return new SnapResult(screenSpacePosition, null);
|
||||
@ -79,5 +88,28 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
new NoteCompositionTool(),
|
||||
new HoldNoteCompositionTool()
|
||||
};
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
if (BlueprintContainer.CurrentTool is SelectTool)
|
||||
{
|
||||
if (EditorBeatmap.SelectedHitObjects.Any())
|
||||
{
|
||||
beatSnapGrid.SelectionTimeRange = (EditorBeatmap.SelectedHitObjects.Min(h => h.StartTime), EditorBeatmap.SelectedHitObjects.Max(h => h.GetEndTime()));
|
||||
}
|
||||
else
|
||||
beatSnapGrid.SelectionTimeRange = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position);
|
||||
if (result.Time is double time)
|
||||
beatSnapGrid.SelectionTimeRange = (time, time);
|
||||
else
|
||||
beatSnapGrid.SelectionTimeRange = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
|
||||
private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent)
|
||||
{
|
||||
var currentColumn = composer.ColumnAt(moveEvent.ScreenSpacePosition);
|
||||
var currentColumn = composer.Playfield.GetColumnByPosition(moveEvent.ScreenSpacePosition);
|
||||
if (currentColumn == null)
|
||||
return;
|
||||
|
||||
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
maxColumn = obj.Column;
|
||||
}
|
||||
|
||||
columnDelta = Math.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn);
|
||||
columnDelta = Math.Clamp(columnDelta, -minColumn, composer.Playfield.TotalColumns - 1 - maxColumn);
|
||||
|
||||
foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>())
|
||||
obj.Column += columnDelta;
|
||||
|
@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
internal readonly Container TopLevelContainer;
|
||||
|
||||
public Container UnderlayElements => hitObjectArea.UnderlayElements;
|
||||
|
||||
public Column(int index)
|
||||
{
|
||||
Index = index;
|
||||
|
@ -12,6 +12,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
||||
public class ColumnHitObjectArea : HitObjectArea
|
||||
{
|
||||
public readonly Container Explosions;
|
||||
|
||||
public readonly Container UnderlayElements;
|
||||
|
||||
private readonly Drawable hitTarget;
|
||||
|
||||
public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer)
|
||||
@ -19,6 +22,11 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
||||
{
|
||||
AddRangeInternal(new[]
|
||||
{
|
||||
UnderlayElements = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = 2,
|
||||
},
|
||||
hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget())
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
|
@ -23,7 +23,6 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
@ -108,13 +107,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
private void updateTimeRange() => TimeRange.Value = configTimeRange.Value * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the column that intersects a screen-space position.
|
||||
/// </summary>
|
||||
/// <param name="screenSpacePosition">The screen-space position.</param>
|
||||
/// <returns>The column which intersects with <paramref name="screenSpacePosition"/>.</returns>
|
||||
public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition);
|
||||
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer();
|
||||
|
||||
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
|
||||
|
@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
[Cached]
|
||||
public class ManiaPlayfield : ScrollingPlayfield
|
||||
{
|
||||
public IReadOnlyList<Stage> Stages => stages;
|
||||
|
||||
private readonly List<Stage> stages = new List<Stage>();
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos));
|
||||
|
@ -162,8 +162,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
if (ControlPoint == slider.Path.ControlPoints[0])
|
||||
{
|
||||
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
|
||||
var result = snapProvider?.SnapScreenSpacePositionToValidTime(e.MousePosition);
|
||||
Vector2 movementDelta = (result?.ScreenSpacePosition ?? e.MousePosition) - slider.Position;
|
||||
var result = snapProvider?.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition);
|
||||
|
||||
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? e.ScreenSpaceMousePosition) - slider.Position;
|
||||
|
||||
slider.Position += movementDelta;
|
||||
slider.StartTime = result?.Time ?? slider.StartTime;
|
||||
|
73
osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs
Normal file
73
osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs
Normal file
@ -0,0 +1,73 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
public class BarLineGeneratorTest
|
||||
{
|
||||
[Test]
|
||||
public void TestRoundingErrorCompensation()
|
||||
{
|
||||
// The aim of this test is to make sure bar line generation compensates for floating-point errors.
|
||||
// The premise of the test is that we have a single timing point that should result in bar lines
|
||||
// that start at a time point that is a whole number every seventh beat.
|
||||
|
||||
// The fact it's every seventh beat is important - it's a number indivisible by 2, which makes
|
||||
// it susceptible to rounding inaccuracies. In fact this was originally spotted in cases of maps
|
||||
// that met exactly this criteria.
|
||||
|
||||
const int beat_length_numerator = 2000;
|
||||
const int beat_length_denominator = 7;
|
||||
const TimeSignatures signature = TimeSignatures.SimpleQuadruple;
|
||||
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitObject { StartTime = 0 },
|
||||
new HitObject { StartTime = 120_000 }
|
||||
},
|
||||
ControlPointInfo = new ControlPointInfo()
|
||||
};
|
||||
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint
|
||||
{
|
||||
BeatLength = (double)beat_length_numerator / beat_length_denominator,
|
||||
TimeSignature = signature
|
||||
});
|
||||
|
||||
var barLines = new BarLineGenerator<BarLine>(beatmap).BarLines;
|
||||
|
||||
for (int i = 0; i * beat_length_denominator < barLines.Count; i++)
|
||||
{
|
||||
var barLine = barLines[i * beat_length_denominator];
|
||||
var expectedTime = beat_length_numerator * (int)signature * i;
|
||||
|
||||
// every seventh bar's start time should be at least greater than the whole number we expect.
|
||||
// It cannot be less, as that can affect overlapping scroll algorithms
|
||||
// (the previous timing point might be chosen incorrectly if this is not the case)
|
||||
Assert.GreaterOrEqual(barLine.StartTime, expectedTime);
|
||||
|
||||
// on the other side, make sure we don't stray too far from the expected time either.
|
||||
Assert.IsTrue(Precision.AlmostEquals(barLine.StartTime, expectedTime));
|
||||
|
||||
// check major/minor lines for good measure too
|
||||
Assert.AreEqual(i % (int)signature == 0, barLine.Major);
|
||||
}
|
||||
}
|
||||
|
||||
private class BarLine : IBarLine
|
||||
{
|
||||
public double StartTime { get; set; }
|
||||
public bool Major { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -29,8 +29,22 @@ namespace osu.Game.Tests.ScrollAlgorithms
|
||||
[Test]
|
||||
public void TestDisplayStartTime()
|
||||
{
|
||||
// Sequential scroll algorithm approximates the start time
|
||||
// This should be fixed in the future
|
||||
// easy cases - time range adjusted for velocity fits within control point duration
|
||||
Assert.AreEqual(2500, algorithm.GetDisplayStartTime(5000, 0, 2500, 1)); // 5000 - (2500 / 1)
|
||||
Assert.AreEqual(13750, algorithm.GetDisplayStartTime(15000, 0, 2500, 1)); // 15000 - (2500 / 2)
|
||||
Assert.AreEqual(20000, algorithm.GetDisplayStartTime(25000, 0, 2500, 1)); // 25000 - (2500 / 0.5)
|
||||
|
||||
// hard case - time range adjusted for velocity exceeds control point duration
|
||||
|
||||
// 1st multiplier point takes 10000 / 2500 = 4 scroll lengths
|
||||
// 2nd multiplier point takes 10000 / (2500 / 2) = 8 scroll lengths
|
||||
// 3rd multiplier point takes 2500 / (2500 * 2) = 0.5 scroll lengths up to hitobject start
|
||||
|
||||
// absolute position of the hitobject = 1000 * (4 + 8 + 0.5) = 12500
|
||||
// minus one scroll length allowance = 12500 - 1000 = 11500 = 11.5 [scroll lengths]
|
||||
// therefore the start time lies within the second multiplier point (because 11.5 < 4 + 8)
|
||||
// its exact time position is = 10000 + 7.5 * (2500 / 2) = 19375
|
||||
Assert.AreEqual(19375, algorithm.GetDisplayStartTime(22500, 0, 2500, 1000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -16,6 +16,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Timing;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
@ -77,19 +78,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
};
|
||||
|
||||
setUpHitObjects();
|
||||
hitObjectSpawnDelegate?.Cancel();
|
||||
});
|
||||
|
||||
private void setUpHitObjects()
|
||||
private void setUpHitObjects() => AddStep("set up hit objects", () =>
|
||||
{
|
||||
scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
|
||||
|
||||
for (int i = spawn_rate / 2; i <= time_range; i += spawn_rate)
|
||||
addHitObject(Time.Current + i);
|
||||
|
||||
hitObjectSpawnDelegate?.Cancel();
|
||||
hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + time_range), spawn_rate, true);
|
||||
}
|
||||
});
|
||||
|
||||
private IList<MultiplierControlPoint> testControlPoints => new List<MultiplierControlPoint>
|
||||
{
|
||||
@ -101,6 +101,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestScrollAlgorithms()
|
||||
{
|
||||
setUpHitObjects();
|
||||
|
||||
AddStep("constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
|
||||
AddStep("overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
|
||||
AddStep("sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
|
||||
@ -113,6 +115,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestConstantScrollLifetime()
|
||||
{
|
||||
setUpHitObjects();
|
||||
|
||||
AddStep("set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
|
||||
// scroll container time range must be less than the rate of spawning hitobjects
|
||||
// otherwise the hitobjects will spawn already partly visible on screen and look wrong
|
||||
@ -122,14 +126,40 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestSequentialScrollLifetime()
|
||||
{
|
||||
setUpHitObjects();
|
||||
|
||||
AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
|
||||
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
|
||||
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSlowSequentialScroll()
|
||||
{
|
||||
AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
|
||||
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range));
|
||||
AddStep("add control points", () => addControlPoints(
|
||||
new List<MultiplierControlPoint>
|
||||
{
|
||||
new MultiplierControlPoint { Velocity = 0.1 }
|
||||
},
|
||||
Time.Current + time_range));
|
||||
|
||||
// All of the hit objects added below should be immediately visible on screen
|
||||
AddStep("add hit objects", () =>
|
||||
{
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
addHitObject(Time.Current + time_range * (2 + 0.1 * i));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOverlappingScrollLifetime()
|
||||
{
|
||||
setUpHitObjects();
|
||||
|
||||
AddStep("set overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
|
||||
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
|
||||
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
|
||||
@ -221,7 +251,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private class TestDrawableControlPoint : DrawableHitObject<HitObject>
|
||||
{
|
||||
public TestDrawableControlPoint(ScrollingDirection direction, double time)
|
||||
: base(new HitObject { StartTime = time })
|
||||
: base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
@ -252,7 +282,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private class TestDrawableHitObject : DrawableHitObject<HitObject>
|
||||
{
|
||||
public TestDrawableHitObject(double time)
|
||||
: base(new HitObject { StartTime = time })
|
||||
: base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
|
||||
{
|
||||
Origin = Anchor.Custom;
|
||||
OriginPosition = new Vector2(75 / 4.0f);
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
|
||||
/// </summary>
|
||||
public partial class BeatmapManager : DownloadableArchiveModelManager<BeatmapSetInfo, BeatmapSetFileInfo>
|
||||
public partial class BeatmapManager : DownloadableArchiveModelManager<BeatmapSetInfo, BeatmapSetFileInfo>, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Fired when a single difficulty has been hidden.
|
||||
@ -433,6 +433,11 @@ namespace osu.Game.Beatmaps
|
||||
return endTime - startTime;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
onlineLookupQueue?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
|
||||
/// </summary>
|
||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
public partial class BeatmapManager
|
||||
{
|
||||
private class BeatmapOnlineLookupQueue
|
||||
private class BeatmapOnlineLookupQueue : IDisposable
|
||||
{
|
||||
private readonly IAPIProvider api;
|
||||
private readonly Storage storage;
|
||||
@ -180,6 +180,11 @@ namespace osu.Game.Beatmaps
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
cacheDownloadRequest?.Dispose();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
private class CachedOnlineBeatmapLookup
|
||||
|
@ -337,6 +337,7 @@ namespace osu.Game
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
RulesetStore?.Dispose();
|
||||
BeatmapManager?.Dispose();
|
||||
|
||||
contextFactory.FlushConnections();
|
||||
}
|
||||
|
@ -64,9 +64,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// <param name="commitStart">Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments.</param>
|
||||
protected void BeginPlacement(bool commitStart = false)
|
||||
{
|
||||
// applies snapping to above time
|
||||
placementHandler.BeginPlacement(HitObject);
|
||||
|
||||
PlacementActive |= commitStart;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Utils;
|
||||
@ -46,6 +47,16 @@ namespace osu.Game.Rulesets.Objects
|
||||
|
||||
for (double t = currentTimingPoint.Time; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++)
|
||||
{
|
||||
var roundedTime = Math.Round(t, MidpointRounding.AwayFromZero);
|
||||
|
||||
// in the case of some bar lengths, rounding errors can cause t to be slightly less than
|
||||
// the expected whole number value due to floating point inaccuracies.
|
||||
// if this is the case, apply rounding.
|
||||
if (Precision.AlmostEquals(t, roundedTime))
|
||||
{
|
||||
t = roundedTime;
|
||||
}
|
||||
|
||||
BarLines.Add(new TBarLine
|
||||
{
|
||||
StartTime = t,
|
||||
|
@ -257,7 +257,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
if (state.Value != ArmedState.Idle && LifetimeEnd == double.MaxValue || HitObject.HitWindows == null)
|
||||
if (LifetimeEnd == double.MaxValue && (state.Value != ArmedState.Idle || HitObject.HitWindows == null))
|
||||
Expire();
|
||||
|
||||
// apply any custom state overrides
|
||||
|
@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
|
||||
public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
|
||||
{
|
||||
double adjustedTime = TimeAt(-offset, originTime, timeRange, scrollLength);
|
||||
return adjustedTime - timeRange - 1000;
|
||||
return TimeAt(-(scrollLength + offset), originTime, timeRange, scrollLength);
|
||||
}
|
||||
|
||||
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
|
||||
|
@ -181,8 +181,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
[Resolved]
|
||||
private IBeatSnapProvider beatSnapProvider { get; set; }
|
||||
|
||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 position) =>
|
||||
new SnapResult(position, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(position))));
|
||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) =>
|
||||
new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition))));
|
||||
|
||||
private double getTimeFromPosition(Vector2 localPosition) =>
|
||||
(localPosition.X / Content.DrawWidth) * track.Length;
|
||||
|
@ -282,7 +282,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
case IHasRepeats repeatHitObject:
|
||||
// find the number of repeats which can fit in the requested time.
|
||||
var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1);
|
||||
var proposedCount = Math.Max(0, (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1);
|
||||
var proposedCount = Math.Max(0, (int)Math.Round((time - hitObject.StartTime) / lengthOfOneRepeat) - 1);
|
||||
|
||||
if (proposedCount == repeatHitObject.RepeatCount)
|
||||
return;
|
||||
|
@ -24,7 +24,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2020.518.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2020.519.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
|
||||
<PackageReference Include="Sentry" Version="2.1.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.25.0" />
|
||||
|
@ -70,7 +70,7 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.518.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.519.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
|
||||
</ItemGroup>
|
||||
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
||||
@ -80,7 +80,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2020.518.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2020.519.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.25.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user