mirror of
https://github.com/ppy/osu.git
synced 2024-11-14 06:17:44 +08:00
Merge branch 'master' into custom-ipc-location
This commit is contained in:
commit
a973a324ac
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
|
<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>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Edit;
|
using osu.Game.Rulesets.Mania.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
@ -14,7 +15,6 @@ using osu.Game.Rulesets.Mods;
|
|||||||
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 osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
@ -43,12 +43,18 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
|
||||||
|
{
|
||||||
|
var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
|
||||||
|
var pos = column.ScreenSpacePositionAtTime(time);
|
||||||
|
|
||||||
|
return new ManiaSnapResult(pos, time, column);
|
||||||
|
}
|
||||||
|
|
||||||
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
|
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject);
|
protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject);
|
||||||
|
|
||||||
public Column ColumnAt(Vector2 screenSpacePosition) => column;
|
public ManiaPlayfield Playfield => null;
|
||||||
|
|
||||||
public int TotalColumns => 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Timing;
|
|||||||
using osu.Game.Rulesets.Mania.Edit;
|
using osu.Game.Rulesets.Mania.Edit;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
@ -18,11 +17,9 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
[Cached(Type = typeof(IAdjustableClock))]
|
[Cached(Type = typeof(IAdjustableClock))]
|
||||||
private readonly IAdjustableClock clock = new StopwatchClock();
|
private readonly IAdjustableClock clock = new StopwatchClock();
|
||||||
|
|
||||||
private readonly Column column;
|
|
||||||
|
|
||||||
protected ManiaSelectionBlueprintTestScene()
|
protected ManiaSelectionBlueprintTestScene()
|
||||||
{
|
{
|
||||||
Add(column = new Column(0)
|
Add(new Column(0)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -31,8 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Column ColumnAt(Vector2 screenSpacePosition) => column;
|
public ManiaPlayfield Playfield => null;
|
||||||
|
|
||||||
public int TotalColumns => 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
public void TestDragOffscreenSelectionVerticallyUpScroll()
|
public void TestDragOffscreenSelectionVerticallyUpScroll()
|
||||||
{
|
{
|
||||||
DrawableHitObject lastObject = null;
|
DrawableHitObject lastObject = null;
|
||||||
|
double originalTime = 0;
|
||||||
Vector2 originalPosition = Vector2.Zero;
|
Vector2 originalPosition = Vector2.Zero;
|
||||||
|
|
||||||
setScrollStep(ScrollingDirection.Up);
|
setScrollStep(ScrollingDirection.Up);
|
||||||
@ -49,6 +50,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
AddStep("seek to last object", () =>
|
AddStep("seek to last object", () =>
|
||||||
{
|
{
|
||||||
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
||||||
|
originalTime = lastObject.HitObject.StartTime;
|
||||||
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -64,19 +66,20 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
AddStep("move mouse downwards", () =>
|
AddStep("move mouse downwards", () =>
|
||||||
{
|
{
|
||||||
InputManager.MoveMouseTo(lastObject, new Vector2(0, 20));
|
InputManager.MoveMouseTo(lastObject, new Vector2(0, lastObject.ScreenSpaceDrawQuad.Height * 4));
|
||||||
InputManager.ReleaseButton(MouseButton.Left);
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0));
|
AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0));
|
||||||
AddAssert("hitobjects moved downwards", () => lastObject.DrawPosition.Y - originalPosition.Y > 0);
|
AddAssert("hitobjects moved downwards", () => lastObject.DrawPosition.Y - originalPosition.Y > 0);
|
||||||
AddAssert("hitobjects not moved too far", () => lastObject.DrawPosition.Y - originalPosition.Y < 50);
|
AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDragOffscreenSelectionVerticallyDownScroll()
|
public void TestDragOffscreenSelectionVerticallyDownScroll()
|
||||||
{
|
{
|
||||||
DrawableHitObject lastObject = null;
|
DrawableHitObject lastObject = null;
|
||||||
|
double originalTime = 0;
|
||||||
Vector2 originalPosition = Vector2.Zero;
|
Vector2 originalPosition = Vector2.Zero;
|
||||||
|
|
||||||
setScrollStep(ScrollingDirection.Down);
|
setScrollStep(ScrollingDirection.Down);
|
||||||
@ -84,6 +87,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
AddStep("seek to last object", () =>
|
AddStep("seek to last object", () =>
|
||||||
{
|
{
|
||||||
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
||||||
|
originalTime = lastObject.HitObject.StartTime;
|
||||||
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -99,13 +103,13 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
AddStep("move mouse upwards", () =>
|
AddStep("move mouse upwards", () =>
|
||||||
{
|
{
|
||||||
InputManager.MoveMouseTo(lastObject, new Vector2(0, -20));
|
InputManager.MoveMouseTo(lastObject, new Vector2(0, -lastObject.ScreenSpaceDrawQuad.Height * 4));
|
||||||
InputManager.ReleaseButton(MouseButton.Left);
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0));
|
AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0));
|
||||||
AddAssert("hitobjects moved upwards", () => originalPosition.Y - lastObject.DrawPosition.Y > 0);
|
AddAssert("hitobjects moved upwards", () => originalPosition.Y - lastObject.DrawPosition.Y > 0);
|
||||||
AddAssert("hitobjects not moved too far", () => originalPosition.Y - lastObject.DrawPosition.Y < 50);
|
AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -207,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++)
|
for (int i = 0; i < 10; i++)
|
||||||
EditorBeatmap.Add(new Note { StartTime = 100 * i });
|
EditorBeatmap.Add(new Note { StartTime = 125 * i });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -17,6 +19,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
private readonly EditNotePiece headPiece;
|
private readonly EditNotePiece headPiece;
|
||||||
private readonly EditNotePiece tailPiece;
|
private readonly EditNotePiece tailPiece;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IManiaHitObjectComposer composer { get; set; }
|
||||||
|
|
||||||
public HoldNotePlacementBlueprint()
|
public HoldNotePlacementBlueprint()
|
||||||
: base(new HoldNote())
|
: base(new HoldNote())
|
||||||
{
|
{
|
||||||
@ -36,8 +41,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
|
|
||||||
if (Column != null)
|
if (Column != null)
|
||||||
{
|
{
|
||||||
headPiece.Y = PositionAt(HitObject.StartTime);
|
headPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.StartTime)).Y;
|
||||||
tailPiece.Y = PositionAt(HitObject.EndTime);
|
tailPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.EndTime)).Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y));
|
var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y));
|
||||||
@ -59,23 +64,28 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
|
|
||||||
private double originalStartTime;
|
private double originalStartTime;
|
||||||
|
|
||||||
public override void UpdatePosition(Vector2 screenSpacePosition)
|
public override void UpdatePosition(SnapResult result)
|
||||||
{
|
{
|
||||||
base.UpdatePosition(screenSpacePosition);
|
base.UpdatePosition(result);
|
||||||
|
|
||||||
if (PlacementActive)
|
if (PlacementActive)
|
||||||
{
|
{
|
||||||
var endTime = TimeAt(screenSpacePosition);
|
if (result.Time is double endTime)
|
||||||
|
{
|
||||||
HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
|
HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
|
||||||
HitObject.Duration = Math.Abs(endTime - originalStartTime);
|
HitObject.Duration = Math.Abs(endTime - originalStartTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
headPiece.Width = tailPiece.Width = SnappedWidth;
|
if (result is ManiaSnapResult maniaResult)
|
||||||
headPiece.X = tailPiece.X = SnappedMousePosition.X;
|
{
|
||||||
|
headPiece.Width = tailPiece.Width = maniaResult.Column.DrawWidth;
|
||||||
|
headPiece.X = tailPiece.X = ToLocalSpace(result.ScreenSpacePosition).X;
|
||||||
|
}
|
||||||
|
|
||||||
originalStartTime = HitObject.StartTime = TimeAt(screenSpacePosition);
|
if (result.Time is double startTime)
|
||||||
|
originalStartTime = HitObject.StartTime = startTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
|
|
||||||
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
|
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
public override Vector2 SelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre;
|
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,34 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
|
||||||
using osuTK;
|
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public abstract class ManiaPlacementBlueprint<T> : PlacementBlueprint,
|
public abstract class ManiaPlacementBlueprint<T> : PlacementBlueprint
|
||||||
IRequireHighFrequencyMousePosition // the playfield could be moving behind us
|
|
||||||
where T : ManiaHitObject
|
where T : ManiaHitObject
|
||||||
{
|
{
|
||||||
protected new T HitObject => (T)base.HitObject;
|
protected new T HitObject => (T)base.HitObject;
|
||||||
|
|
||||||
protected Column Column;
|
private Column column;
|
||||||
|
|
||||||
/// <summary>
|
public Column Column
|
||||||
/// The current mouse position, snapped to the closest column.
|
{
|
||||||
/// </summary>
|
get => column;
|
||||||
protected Vector2 SnappedMousePosition { get; private set; }
|
set
|
||||||
|
{
|
||||||
|
if (value == column)
|
||||||
|
return;
|
||||||
|
|
||||||
/// <summary>
|
column = value;
|
||||||
/// The width of the closest column to the current mouse position.
|
HitObject.Column = column.Index;
|
||||||
/// </summary>
|
}
|
||||||
protected float SnappedWidth { get; private set; }
|
}
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IManiaHitObjectComposer composer { get; set; }
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IScrollingInfo scrollingInfo { get; set; }
|
|
||||||
|
|
||||||
protected ManiaPlacementBlueprint(T hitObject)
|
protected ManiaPlacementBlueprint(T hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
@ -51,105 +42,18 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (Column == null)
|
if (Column == null)
|
||||||
return base.OnMouseDown(e);
|
return false;
|
||||||
|
|
||||||
HitObject.Column = Column.Index;
|
BeginPlacement(true);
|
||||||
BeginPlacement(TimeAt(e.ScreenSpaceMousePosition), true);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePosition(Vector2 screenSpacePosition)
|
public override void UpdatePosition(SnapResult result)
|
||||||
{
|
{
|
||||||
|
base.UpdatePosition(result);
|
||||||
|
|
||||||
if (!PlacementActive)
|
if (!PlacementActive)
|
||||||
Column = ColumnAt(screenSpacePosition);
|
Column = (result as ManiaSnapResult)?.Column;
|
||||||
|
|
||||||
if (Column == null) return;
|
|
||||||
|
|
||||||
SnappedWidth = Column.DrawWidth;
|
|
||||||
|
|
||||||
// Snap to the column
|
|
||||||
var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0)));
|
|
||||||
SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(screenSpacePosition).Y);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected double TimeAt(Vector2 screenSpacePosition)
|
|
||||||
{
|
|
||||||
if (Column == null)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
var hitObjectContainer = Column.HitObjectContainer;
|
|
||||||
|
|
||||||
// If we're scrolling downwards, a position of 0 is actually further away from the hit target
|
|
||||||
// so we need to flip the vertical coordinate in the hitobject container's space
|
|
||||||
var hitObjectPos = mouseToHitObjectPosition(Column.HitObjectContainer.ToLocalSpace(screenSpacePosition)).Y;
|
|
||||||
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
|
||||||
hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos;
|
|
||||||
|
|
||||||
return scrollingInfo.Algorithm.TimeAt(hitObjectPos,
|
|
||||||
EditorClock.CurrentTime,
|
|
||||||
scrollingInfo.TimeRange.Value,
|
|
||||||
hitObjectContainer.DrawHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected float PositionAt(double time)
|
|
||||||
{
|
|
||||||
var pos = scrollingInfo.Algorithm.PositionAt(time,
|
|
||||||
EditorClock.CurrentTime,
|
|
||||||
scrollingInfo.TimeRange.Value,
|
|
||||||
Column.HitObjectContainer.DrawHeight);
|
|
||||||
|
|
||||||
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
|
||||||
pos = Column.HitObjectContainer.DrawHeight - pos;
|
|
||||||
|
|
||||||
return hitObjectToMousePosition(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent)).Y;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Column ColumnAt(Vector2 screenSpacePosition)
|
|
||||||
=> composer.ColumnAt(screenSpacePosition);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a mouse position to a hitobject position.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="mousePosition">The mouse position.</param>
|
|
||||||
/// <returns>The resulting hitobject position, acnhored at the top or bottom of the blueprint depending on the scroll direction.</returns>
|
|
||||||
private Vector2 mouseToHitObjectPosition(Vector2 mousePosition)
|
|
||||||
{
|
|
||||||
switch (scrollingInfo.Direction.Value)
|
|
||||||
{
|
|
||||||
case ScrollingDirection.Up:
|
|
||||||
mousePosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScrollingDirection.Down:
|
|
||||||
mousePosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mousePosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a hitobject position to a mouse position.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hitObjectPosition">The hitobject position.</param>
|
|
||||||
/// <returns>The resulting mouse position, anchored at the centre of the hitobject.</returns>
|
|
||||||
private Vector2 hitObjectToMousePosition(Vector2 hitObjectPosition)
|
|
||||||
{
|
|
||||||
switch (scrollingInfo.Direction.Value)
|
|
||||||
{
|
|
||||||
case ScrollingDirection.Up:
|
|
||||||
hitObjectPosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScrollingDirection.Down:
|
|
||||||
hitObjectPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hitObjectPosition;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -11,22 +12,25 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
public class NotePlacementBlueprint : ManiaPlacementBlueprint<Note>
|
public class NotePlacementBlueprint : ManiaPlacementBlueprint<Note>
|
||||||
{
|
{
|
||||||
|
private readonly EditNotePiece piece;
|
||||||
|
|
||||||
public NotePlacementBlueprint()
|
public NotePlacementBlueprint()
|
||||||
: base(new Note())
|
: base(new Note())
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Y;
|
InternalChild = piece = new EditNotePiece { Origin = Anchor.Centre };
|
||||||
|
|
||||||
InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
public override void UpdatePosition(SnapResult result)
|
||||||
{
|
{
|
||||||
base.Update();
|
base.UpdatePosition(result);
|
||||||
|
|
||||||
Width = SnappedWidth;
|
if (result is ManiaSnapResult maniaResult)
|
||||||
Position = SnappedMousePosition;
|
{
|
||||||
|
piece.Width = maniaResult.Column.DrawWidth;
|
||||||
|
piece.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
@ -2,14 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit
|
namespace osu.Game.Rulesets.Mania.Edit
|
||||||
{
|
{
|
||||||
public interface IManiaHitObjectComposer
|
public interface IManiaHitObjectComposer
|
||||||
{
|
{
|
||||||
Column ColumnAt(Vector2 screenSpacePosition);
|
ManiaPlayfield Playfield { get; }
|
||||||
|
|
||||||
int TotalColumns { 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.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Input;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
@ -20,18 +23,26 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject>, IManiaHitObjectComposer
|
public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject>, IManiaHitObjectComposer
|
||||||
{
|
{
|
||||||
private DrawableManiaEditRuleset drawableRuleset;
|
private DrawableManiaEditRuleset drawableRuleset;
|
||||||
|
private ManiaBeatSnapGrid beatSnapGrid;
|
||||||
|
private InputManager inputManager;
|
||||||
|
|
||||||
public ManiaHitObjectComposer(Ruleset ruleset)
|
public ManiaHitObjectComposer(Ruleset ruleset)
|
||||||
: base(ruleset)
|
: base(ruleset)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
[BackgroundDependencyLoader]
|
||||||
/// Retrieves the column that intersects a screen-space position.
|
private void load()
|
||||||
/// </summary>
|
{
|
||||||
/// <param name="screenSpacePosition">The screen-space position.</param>
|
AddInternal(beatSnapGrid = new ManiaBeatSnapGrid());
|
||||||
/// <returns>The column which intersects with <paramref name="screenSpacePosition"/>.</returns>
|
}
|
||||||
public Column ColumnAt(Vector2 screenSpacePosition) => drawableRuleset.GetColumnByPosition(screenSpacePosition);
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
inputManager = GetContainingInputManager();
|
||||||
|
}
|
||||||
|
|
||||||
private DependencyContainer dependencies;
|
private DependencyContainer dependencies;
|
||||||
|
|
||||||
@ -42,28 +53,22 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo;
|
public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo;
|
||||||
|
|
||||||
public int TotalColumns => Playfield.TotalColumns;
|
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||||
|
|
||||||
public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time)
|
|
||||||
{
|
{
|
||||||
var hoc = Playfield.GetColumn(0).HitObjectContainer;
|
var column = Playfield.GetColumnByPosition(screenSpacePosition);
|
||||||
|
|
||||||
float targetPosition = hoc.ToLocalSpace(ToScreenSpace(position)).Y;
|
if (column == null)
|
||||||
|
return new SnapResult(screenSpacePosition, null);
|
||||||
|
|
||||||
if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down)
|
double targetTime = column.TimeAtScreenSpacePosition(screenSpacePosition);
|
||||||
{
|
|
||||||
// We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time.
|
|
||||||
// The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position,
|
|
||||||
// so when scrolling downwards the coordinates need to be flipped.
|
|
||||||
targetPosition = hoc.DrawHeight - targetPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition,
|
// apply beat snapping
|
||||||
EditorClock.CurrentTime,
|
targetTime = BeatSnapProvider.SnapTime(targetTime);
|
||||||
drawableRuleset.ScrollingInfo.TimeRange.Value,
|
|
||||||
hoc.DrawHeight);
|
|
||||||
|
|
||||||
return base.GetSnappedPosition(position, targetTime);
|
// convert back to screen space
|
||||||
|
screenSpacePosition = column.ScreenSpacePositionAtTime(targetTime);
|
||||||
|
|
||||||
|
return new ManiaSnapResult(screenSpacePosition, targetTime, column);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DrawableRuleset<ManiaHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
protected override DrawableRuleset<ManiaHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||||
@ -83,5 +88,28 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
new NoteCompositionTool(),
|
new NoteCompositionTool(),
|
||||||
new HoldNoteCompositionTool()
|
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)
|
private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent)
|
||||||
{
|
{
|
||||||
var currentColumn = composer.ColumnAt(moveEvent.ScreenSpacePosition);
|
var currentColumn = composer.Playfield.GetColumnByPosition(moveEvent.ScreenSpacePosition);
|
||||||
if (currentColumn == null)
|
if (currentColumn == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
maxColumn = obj.Column;
|
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>())
|
foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>())
|
||||||
obj.Column += columnDelta;
|
obj.Column += columnDelta;
|
||||||
|
20
osu.Game.Rulesets.Mania/Edit/ManiaSnapResult.cs
Normal file
20
osu.Game.Rulesets.Mania/Edit/ManiaSnapResult.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 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 osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Edit
|
||||||
|
{
|
||||||
|
public class ManiaSnapResult : SnapResult
|
||||||
|
{
|
||||||
|
public readonly Column Column;
|
||||||
|
|
||||||
|
public ManiaSnapResult(Vector2 screenSpacePosition, double time, Column column)
|
||||||
|
: base(screenSpacePosition, time)
|
||||||
|
{
|
||||||
|
Column = column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling;
|
|||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
@ -37,6 +38,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
internal readonly Container TopLevelContainer;
|
internal readonly Container TopLevelContainer;
|
||||||
|
|
||||||
|
public Container UnderlayElements => hitObjectArea.UnderlayElements;
|
||||||
|
|
||||||
public Column(int index)
|
public Column(int index)
|
||||||
{
|
{
|
||||||
Index = index;
|
Index = index;
|
||||||
@ -140,5 +143,54 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||||
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
|
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
|
||||||
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
|
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given a time, return the screen space position within this column.
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 ScreenSpacePositionAtTime(double time)
|
||||||
|
{
|
||||||
|
var pos = ScrollingInfo.Algorithm.PositionAt(time, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight);
|
||||||
|
|
||||||
|
switch (ScrollingInfo.Direction.Value)
|
||||||
|
{
|
||||||
|
case ScrollingDirection.Down:
|
||||||
|
// We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time.
|
||||||
|
// The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position,
|
||||||
|
// so when scrolling downwards the coordinates need to be flipped.
|
||||||
|
pos = HitObjectContainer.DrawHeight - pos;
|
||||||
|
|
||||||
|
// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction.
|
||||||
|
pos -= DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScrollingDirection.Up:
|
||||||
|
pos += DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HitObjectContainer.ToScreenSpace(new Vector2(HitObjectContainer.DrawWidth / 2, pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given a position in screen space, return the time within this column.
|
||||||
|
/// </summary>
|
||||||
|
public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition)
|
||||||
|
{
|
||||||
|
// convert to local space of column so we can snap and fetch correct location.
|
||||||
|
Vector2 localPosition = HitObjectContainer.ToLocalSpace(screenSpacePosition);
|
||||||
|
|
||||||
|
switch (ScrollingInfo.Direction.Value)
|
||||||
|
{
|
||||||
|
case ScrollingDirection.Down:
|
||||||
|
// as above
|
||||||
|
localPosition.Y = HitObjectContainer.DrawHeight - localPosition.Y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// offset for the fact that blueprints are centered, as above.
|
||||||
|
localPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||||
|
|
||||||
|
return ScrollingInfo.Algorithm.TimeAt(localPosition.Y, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
public class ColumnHitObjectArea : HitObjectArea
|
public class ColumnHitObjectArea : HitObjectArea
|
||||||
{
|
{
|
||||||
public readonly Container Explosions;
|
public readonly Container Explosions;
|
||||||
|
|
||||||
|
public readonly Container UnderlayElements;
|
||||||
|
|
||||||
private readonly Drawable hitTarget;
|
private readonly Drawable hitTarget;
|
||||||
|
|
||||||
public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer)
|
public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer)
|
||||||
@ -19,6 +22,11 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
{
|
{
|
||||||
AddRangeInternal(new[]
|
AddRangeInternal(new[]
|
||||||
{
|
{
|
||||||
|
UnderlayElements = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Depth = 2,
|
||||||
|
},
|
||||||
hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget())
|
hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
|
@ -23,7 +23,6 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
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;
|
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();
|
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer();
|
||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
|
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
|
||||||
|
@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
[Cached]
|
[Cached]
|
||||||
public class ManiaPlayfield : ScrollingPlayfield
|
public class ManiaPlayfield : ScrollingPlayfield
|
||||||
{
|
{
|
||||||
|
public IReadOnlyList<Stage> Stages => stages;
|
||||||
|
|
||||||
private readonly List<Stage> stages = new List<Stage>();
|
private readonly List<Stage> stages = new List<Stage>();
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos));
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos));
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
|
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
|
||||||
|
|
||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IPositionSnapProvider))]
|
||||||
private readonly SnapProvider snapProvider = new SnapProvider();
|
private readonly SnapProvider snapProvider = new SnapProvider();
|
||||||
|
|
||||||
private TestOsuDistanceSnapGrid grid;
|
private TestOsuDistanceSnapGrid grid;
|
||||||
@ -172,9 +172,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SnapProvider : IDistanceSnapProvider
|
private class SnapProvider : IPositionSnapProvider
|
||||||
{
|
{
|
||||||
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time);
|
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||||
|
|
||||||
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
||||||
@ -40,6 +39,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
|||||||
return base.OnMouseDown(e);
|
return base.OnMouseDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePosition(Vector2 screenSpacePosition) => HitObject.Position = ToLocalSpace(screenSpacePosition);
|
public override void UpdatePosition(SnapResult result)
|
||||||
|
{
|
||||||
|
base.UpdatePosition(result);
|
||||||
|
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
private IEditorChangeHandler changeHandler { get; set; }
|
private IEditorChangeHandler changeHandler { get; set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IDistanceSnapProvider snapProvider { get; set; }
|
private IPositionSnapProvider snapProvider { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
@ -162,11 +162,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
if (ControlPoint == slider.Path.ControlPoints[0])
|
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
|
// 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
|
||||||
(Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime);
|
var result = snapProvider?.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition);
|
||||||
Vector2 movementDelta = snappedPosition - slider.Position;
|
|
||||||
|
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? e.ScreenSpaceMousePosition) - slider.Position;
|
||||||
|
|
||||||
slider.Position += movementDelta;
|
slider.Position += movementDelta;
|
||||||
slider.StartTime = snappedTime;
|
slider.StartTime = result?.Time ?? slider.StartTime;
|
||||||
|
|
||||||
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
|
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
|
||||||
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
|
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
|
||||||
|
@ -67,13 +67,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
inputManager = GetContainingInputManager();
|
inputManager = GetContainingInputManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePosition(Vector2 screenSpacePosition)
|
public override void UpdatePosition(SnapResult result)
|
||||||
{
|
{
|
||||||
|
base.UpdatePosition(result);
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case PlacementState.Initial:
|
case PlacementState.Initial:
|
||||||
BeginPlacement();
|
BeginPlacement();
|
||||||
HitObject.Position = ToLocalSpace(screenSpacePosition);
|
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PlacementState.Body:
|
case PlacementState.Body:
|
||||||
|
@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
|
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
|
||||||
};
|
};
|
||||||
|
|
||||||
public override Vector2 SelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre;
|
public override Vector2 ScreenSpaceSelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre;
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ using osu.Game.Rulesets.Edit;
|
|||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||||
@ -60,9 +59,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePosition(Vector2 screenSpacePosition)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Input.Bindings;
|
|||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Taiko.Audio;
|
using osu.Game.Rulesets.Taiko.Audio;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.UI
|
namespace osu.Game.Rulesets.Taiko.UI
|
||||||
@ -145,6 +146,9 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
centreHit.Colour = colours.Pink;
|
centreHit.Colour = colours.Pink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private GameplayClock gameplayClock { get; set; }
|
||||||
|
|
||||||
public bool OnPressed(TaikoAction action)
|
public bool OnPressed(TaikoAction action)
|
||||||
{
|
{
|
||||||
Drawable target = null;
|
Drawable target = null;
|
||||||
@ -157,14 +161,16 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
target = centreHit;
|
target = centreHit;
|
||||||
back = centre;
|
back = centre;
|
||||||
|
|
||||||
drumSample.Centre?.Play();
|
if (gameplayClock?.IsSeeking != true)
|
||||||
|
drumSample.Centre?.Play();
|
||||||
}
|
}
|
||||||
else if (action == RimAction)
|
else if (action == RimAction)
|
||||||
{
|
{
|
||||||
target = rimHit;
|
target = rimHit;
|
||||||
back = rim;
|
back = rim;
|
||||||
|
|
||||||
drumSample.Rim?.Play();
|
if (gameplayClock?.IsSeeking != true)
|
||||||
|
drumSample.Rim?.Play();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target != null)
|
if (target != null)
|
||||||
|
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]
|
[Test]
|
||||||
public void TestDisplayStartTime()
|
public void TestDisplayStartTime()
|
||||||
{
|
{
|
||||||
// Sequential scroll algorithm approximates the start time
|
// easy cases - time range adjusted for velocity fits within control point duration
|
||||||
// This should be fixed in the future
|
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]
|
[Test]
|
||||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[Cached(typeof(EditorBeatmap))]
|
[Cached(typeof(EditorBeatmap))]
|
||||||
private readonly EditorBeatmap editorBeatmap;
|
private readonly EditorBeatmap editorBeatmap;
|
||||||
|
|
||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IPositionSnapProvider))]
|
||||||
private readonly SnapProvider snapProvider = new SnapProvider();
|
private readonly SnapProvider snapProvider = new SnapProvider();
|
||||||
|
|
||||||
public TestSceneDistanceSnapGrid()
|
public TestSceneDistanceSnapGrid()
|
||||||
@ -151,9 +151,9 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
=> (Vector2.Zero, 0);
|
=> (Vector2.Zero, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SnapProvider : IDistanceSnapProvider
|
private class SnapProvider : IPositionSnapProvider
|
||||||
{
|
{
|
||||||
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time);
|
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||||
|
|
||||||
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
|
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ using osu.Game.Configuration;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Timing;
|
using osu.Game.Rulesets.Timing;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
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)));
|
scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
|
||||||
|
|
||||||
for (int i = spawn_rate / 2; i <= time_range; i += spawn_rate)
|
for (int i = spawn_rate / 2; i <= time_range; i += spawn_rate)
|
||||||
addHitObject(Time.Current + i);
|
addHitObject(Time.Current + i);
|
||||||
|
|
||||||
hitObjectSpawnDelegate?.Cancel();
|
|
||||||
hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + time_range), spawn_rate, true);
|
hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + time_range), spawn_rate, true);
|
||||||
}
|
});
|
||||||
|
|
||||||
private IList<MultiplierControlPoint> testControlPoints => new List<MultiplierControlPoint>
|
private IList<MultiplierControlPoint> testControlPoints => new List<MultiplierControlPoint>
|
||||||
{
|
{
|
||||||
@ -101,6 +101,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestScrollAlgorithms()
|
public void TestScrollAlgorithms()
|
||||||
{
|
{
|
||||||
|
setUpHitObjects();
|
||||||
|
|
||||||
AddStep("constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
|
AddStep("constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
|
||||||
AddStep("overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
|
AddStep("overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
|
||||||
AddStep("sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
|
AddStep("sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
|
||||||
@ -113,6 +115,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestConstantScrollLifetime()
|
public void TestConstantScrollLifetime()
|
||||||
{
|
{
|
||||||
|
setUpHitObjects();
|
||||||
|
|
||||||
AddStep("set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
|
AddStep("set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
|
||||||
// scroll container time range must be less than the rate of spawning hitobjects
|
// 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
|
// otherwise the hitobjects will spawn already partly visible on screen and look wrong
|
||||||
@ -122,14 +126,40 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestSequentialScrollLifetime()
|
public void TestSequentialScrollLifetime()
|
||||||
{
|
{
|
||||||
|
setUpHitObjects();
|
||||||
|
|
||||||
AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
|
AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
|
||||||
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
|
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
|
||||||
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
|
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]
|
[Test]
|
||||||
public void TestOverlappingScrollLifetime()
|
public void TestOverlappingScrollLifetime()
|
||||||
{
|
{
|
||||||
|
setUpHitObjects();
|
||||||
|
|
||||||
AddStep("set overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
|
AddStep("set overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
|
||||||
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
|
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
|
||||||
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
|
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
|
||||||
@ -221,7 +251,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private class TestDrawableControlPoint : DrawableHitObject<HitObject>
|
private class TestDrawableControlPoint : DrawableHitObject<HitObject>
|
||||||
{
|
{
|
||||||
public TestDrawableControlPoint(ScrollingDirection direction, double time)
|
public TestDrawableControlPoint(ScrollingDirection direction, double time)
|
||||||
: base(new HitObject { StartTime = time })
|
: base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
@ -252,7 +282,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private class TestDrawableHitObject : DrawableHitObject<HitObject>
|
private class TestDrawableHitObject : DrawableHitObject<HitObject>
|
||||||
{
|
{
|
||||||
public TestDrawableHitObject(double time)
|
public TestDrawableHitObject(double time)
|
||||||
: base(new HitObject { StartTime = time })
|
: base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
|
||||||
{
|
{
|
||||||
Origin = Anchor.Custom;
|
Origin = Anchor.Custom;
|
||||||
OriginPosition = new Vector2(75 / 4.0f);
|
OriginPosition = new Vector2(75 / 4.0f);
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
|
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class BeatmapManager : DownloadableArchiveModelManager<BeatmapSetInfo, BeatmapSetFileInfo>
|
public partial class BeatmapManager : DownloadableArchiveModelManager<BeatmapSetInfo, BeatmapSetFileInfo>, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when a single difficulty has been hidden.
|
/// Fired when a single difficulty has been hidden.
|
||||||
@ -433,6 +433,11 @@ namespace osu.Game.Beatmaps
|
|||||||
return endTime - startTime;
|
return endTime - startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
onlineLookupQueue?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
|
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
public partial class BeatmapManager
|
public partial class BeatmapManager
|
||||||
{
|
{
|
||||||
private class BeatmapOnlineLookupQueue
|
private class BeatmapOnlineLookupQueue : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IAPIProvider api;
|
private readonly IAPIProvider api;
|
||||||
private readonly Storage storage;
|
private readonly Storage storage;
|
||||||
@ -180,6 +180,11 @@ namespace osu.Game.Beatmaps
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
cacheDownloadRequest?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
private class CachedOnlineBeatmapLookup
|
private class CachedOnlineBeatmapLookup
|
||||||
|
@ -337,6 +337,7 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
RulesetStore?.Dispose();
|
RulesetStore?.Dispose();
|
||||||
|
BeatmapManager?.Dispose();
|
||||||
|
|
||||||
contextFactory.FlushConnections();
|
contextFactory.FlushConnections();
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
private IAdjustableClock adjustableClock { get; set; }
|
private IAdjustableClock adjustableClock { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IBeatSnapProvider beatSnapProvider { get; set; }
|
protected IBeatSnapProvider BeatSnapProvider { get; private set; }
|
||||||
|
|
||||||
protected ComposeBlueprintContainer BlueprintContainer { get; private set; }
|
protected ComposeBlueprintContainer BlueprintContainer { get; private set; }
|
||||||
|
|
||||||
@ -244,9 +244,6 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
public void BeginPlacement(HitObject hitObject)
|
public void BeginPlacement(HitObject hitObject)
|
||||||
{
|
{
|
||||||
EditorBeatmap.PlacementObject.Value = hitObject;
|
EditorBeatmap.PlacementObject.Value = hitObject;
|
||||||
|
|
||||||
if (distanceSnapGrid != null)
|
|
||||||
hitObject.StartTime = GetSnappedPosition(distanceSnapGrid.ToLocalSpace(inputManager.CurrentState.Mouse.Position), hitObject.StartTime).time;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndPlacement(HitObject hitObject, bool commit)
|
public void EndPlacement(HitObject hitObject, bool commit)
|
||||||
@ -257,7 +254,8 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
{
|
{
|
||||||
EditorBeatmap.Add(hitObject);
|
EditorBeatmap.Add(hitObject);
|
||||||
|
|
||||||
adjustableClock.Seek(hitObject.GetEndTime());
|
if (adjustableClock.CurrentTime < hitObject.StartTime)
|
||||||
|
adjustableClock.Seek(hitObject.StartTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
showGridFor(Enumerable.Empty<HitObject>());
|
showGridFor(Enumerable.Empty<HitObject>());
|
||||||
@ -265,40 +263,48 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject);
|
public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject);
|
||||||
|
|
||||||
public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => distanceSnapGrid?.GetSnappedPosition(position) ?? (position, time);
|
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||||
|
{
|
||||||
|
if (distanceSnapGrid == null) return new SnapResult(screenSpacePosition, null);
|
||||||
|
|
||||||
|
// TODO: move distance snap grid to OsuHitObjectComposer.
|
||||||
|
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
|
||||||
|
|
||||||
|
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time);
|
||||||
|
}
|
||||||
|
|
||||||
public override float GetBeatSnapDistanceAt(double referenceTime)
|
public override float GetBeatSnapDistanceAt(double referenceTime)
|
||||||
{
|
{
|
||||||
DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime);
|
DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime);
|
||||||
return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / beatSnapProvider.BeatDivisor);
|
return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / BeatSnapProvider.BeatDivisor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float DurationToDistance(double referenceTime, double duration)
|
public override float DurationToDistance(double referenceTime, double duration)
|
||||||
{
|
{
|
||||||
double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime);
|
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime);
|
||||||
return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime));
|
return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double DistanceToDuration(double referenceTime, float distance)
|
public override double DistanceToDuration(double referenceTime, float distance)
|
||||||
{
|
{
|
||||||
double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime);
|
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime);
|
||||||
return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength;
|
return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double GetSnappedDurationFromDistance(double referenceTime, float distance)
|
public override double GetSnappedDurationFromDistance(double referenceTime, float distance)
|
||||||
=> beatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime) - referenceTime;
|
=> BeatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime) - referenceTime;
|
||||||
|
|
||||||
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
|
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
|
||||||
{
|
{
|
||||||
var snappedEndTime = beatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime);
|
var snappedEndTime = BeatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime);
|
||||||
|
|
||||||
return DurationToDistance(referenceTime, snappedEndTime - referenceTime);
|
return DurationToDistance(referenceTime, snappedEndTime - referenceTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cached(typeof(HitObjectComposer))]
|
[Cached(typeof(HitObjectComposer))]
|
||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IPositionSnapProvider))]
|
||||||
public abstract class HitObjectComposer : CompositeDrawable, IDistanceSnapProvider
|
public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider
|
||||||
{
|
{
|
||||||
internal HitObjectComposer()
|
internal HitObjectComposer()
|
||||||
{
|
{
|
||||||
@ -323,7 +329,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable<HitObject> selectedHitObjects) => null;
|
protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable<HitObject> selectedHitObjects) => null;
|
||||||
|
|
||||||
public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time);
|
public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition);
|
||||||
|
|
||||||
public abstract float GetBeatSnapDistanceAt(double referenceTime);
|
public abstract float GetBeatSnapDistanceAt(double referenceTime);
|
||||||
|
|
||||||
|
@ -5,9 +5,14 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit
|
namespace osu.Game.Rulesets.Edit
|
||||||
{
|
{
|
||||||
public interface IDistanceSnapProvider
|
public interface IPositionSnapProvider
|
||||||
{
|
{
|
||||||
(Vector2 position, double time) GetSnappedPosition(Vector2 position, double time);
|
/// <summary>
|
||||||
|
/// Given a position, find a valid time snap.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="screenSpacePosition">The screen-space position to be snapped.</param>
|
||||||
|
/// <returns>The time and position post-snapping.</returns>
|
||||||
|
SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the distance between two points within a timing point that are one beat length apart.
|
/// Retrieves the distance between two points within a timing point that are one beat length apart.
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
public override Vector2 SelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre;
|
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre;
|
||||||
|
|
||||||
public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad;
|
public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
|
@ -61,11 +61,9 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signals that the placement of <see cref="HitObject"/> has started.
|
/// Signals that the placement of <see cref="HitObject"/> has started.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="startTime">The start time of <see cref="HitObject"/> at the placement point. If null, the current clock time is used.</param>
|
|
||||||
/// <param name="commitStart">Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments.</param>
|
/// <param name="commitStart">Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments.</param>
|
||||||
protected void BeginPlacement(double? startTime = null, bool commitStart = false)
|
protected void BeginPlacement(bool commitStart = false)
|
||||||
{
|
{
|
||||||
HitObject.StartTime = startTime ?? EditorClock.CurrentTime;
|
|
||||||
placementHandler.BeginPlacement(HitObject);
|
placementHandler.BeginPlacement(HitObject);
|
||||||
PlacementActive |= commitStart;
|
PlacementActive |= commitStart;
|
||||||
}
|
}
|
||||||
@ -83,11 +81,18 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
PlacementActive = false;
|
PlacementActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private IFrameBasedClock editorClock { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the position of this <see cref="PlacementBlueprint"/> to a new screen-space position.
|
/// Updates the position of this <see cref="PlacementBlueprint"/> to a new screen-space position.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="screenSpacePosition">The screen-space position.</param>
|
/// <param name="snapResult">The snap result information.</param>
|
||||||
public abstract void UpdatePosition(Vector2 screenSpacePosition);
|
public virtual void UpdatePosition(SnapResult snapResult)
|
||||||
|
{
|
||||||
|
if (!PlacementActive)
|
||||||
|
HitObject.StartTime = snapResult.Time ?? editorClock?.CurrentTime ?? Time.Current;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invokes <see cref="Objects.HitObject.ApplyDefaults(ControlPointInfo,BeatmapDifficulty)"/>,
|
/// Invokes <see cref="Objects.HitObject.ApplyDefaults(ControlPointInfo,BeatmapDifficulty)"/>,
|
||||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The screen-space point that causes this <see cref="OverlaySelectionBlueprint"/> to be selected.
|
/// The screen-space point that causes this <see cref="OverlaySelectionBlueprint"/> to be selected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre;
|
public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The screen-space quad that outlines this <see cref="OverlaySelectionBlueprint"/> for selections.
|
/// The screen-space quad that outlines this <see cref="OverlaySelectionBlueprint"/> for selections.
|
||||||
|
29
osu.Game/Rulesets/Edit/SnapResult.cs
Normal file
29
osu.Game/Rulesets/Edit/SnapResult.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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 osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The result of a position/time snapping process.
|
||||||
|
/// </summary>
|
||||||
|
public class SnapResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The screen space position, potentially altered for snapping.
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 ScreenSpacePosition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The resultant time for snapping, if a value could be attained.
|
||||||
|
/// </summary>
|
||||||
|
public double? Time;
|
||||||
|
|
||||||
|
public SnapResult(Vector2 screenSpacePosition, double? time)
|
||||||
|
{
|
||||||
|
ScreenSpacePosition = screenSpacePosition;
|
||||||
|
Time = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Utils;
|
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++)
|
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
|
BarLines.Add(new TBarLine
|
||||||
{
|
{
|
||||||
StartTime = t,
|
StartTime = t,
|
||||||
|
@ -11,13 +11,13 @@ using osu.Framework.Extensions.TypeExtensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Framework.Audio;
|
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Drawables
|
namespace osu.Game.Rulesets.Objects.Drawables
|
||||||
@ -96,8 +96,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected virtual float SamplePlaybackPosition => 0.5f;
|
protected virtual float SamplePlaybackPosition => 0.5f;
|
||||||
|
|
||||||
private readonly BindableDouble balanceAdjust = new BindableDouble();
|
|
||||||
|
|
||||||
private BindableList<HitSampleInfo> samplesBindable;
|
private BindableList<HitSampleInfo> samplesBindable;
|
||||||
private Bindable<double> startTimeBindable;
|
private Bindable<double> startTimeBindable;
|
||||||
private Bindable<bool> userPositionalHitSounds;
|
private Bindable<bool> userPositionalHitSounds;
|
||||||
@ -173,7 +171,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
}
|
}
|
||||||
|
|
||||||
Samples = new SkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)));
|
Samples = new SkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)));
|
||||||
Samples.AddAdjustment(AdjustableProperty.Balance, balanceAdjust);
|
|
||||||
AddInternal(Samples);
|
AddInternal(Samples);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,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();
|
Expire();
|
||||||
|
|
||||||
// apply any custom state overrides
|
// apply any custom state overrides
|
||||||
@ -352,6 +349,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private GameplayClock gameplayClock { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Plays all the hit sounds for this <see cref="DrawableHitObject"/>.
|
/// Plays all the hit sounds for this <see cref="DrawableHitObject"/>.
|
||||||
/// This is invoked automatically when this <see cref="DrawableHitObject"/> is hit.
|
/// This is invoked automatically when this <see cref="DrawableHitObject"/> is hit.
|
||||||
@ -360,8 +360,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
{
|
{
|
||||||
const float balance_adjust_amount = 0.4f;
|
const float balance_adjust_amount = 0.4f;
|
||||||
|
|
||||||
balanceAdjust.Value = balance_adjust_amount * (userPositionalHitSounds.Value ? SamplePlaybackPosition - 0.5f : 0);
|
if (Samples != null && gameplayClock?.IsSeeking != true)
|
||||||
Samples?.Play();
|
{
|
||||||
|
Samples.Balance.Value = balance_adjust_amount * (userPositionalHitSounds.Value ? SamplePlaybackPosition - 0.5f : 0);
|
||||||
|
Samples.Play();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -29,14 +29,16 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal bool FrameStablePlayback = true;
|
internal bool FrameStablePlayback = true;
|
||||||
|
|
||||||
[Cached]
|
public GameplayClock GameplayClock => stabilityGameplayClock;
|
||||||
public GameplayClock GameplayClock { get; }
|
|
||||||
|
[Cached(typeof(GameplayClock))]
|
||||||
|
private readonly StabilityGameplayClock stabilityGameplayClock;
|
||||||
|
|
||||||
public FrameStabilityContainer(double gameplayStartTime = double.MinValue)
|
public FrameStabilityContainer(double gameplayStartTime = double.MinValue)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
GameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock()));
|
stabilityGameplayClock = new StabilityGameplayClock(framedClock = new FramedClock(manualClock = new ManualClock()));
|
||||||
|
|
||||||
this.gameplayStartTime = gameplayStartTime;
|
this.gameplayStartTime = gameplayStartTime;
|
||||||
}
|
}
|
||||||
@ -57,7 +59,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
if (clock != null)
|
if (clock != null)
|
||||||
{
|
{
|
||||||
parentGameplayClock = clock;
|
stabilityGameplayClock.ParentGameplayClock = parentGameplayClock = clock;
|
||||||
GameplayClock.IsPaused.BindTo(clock.IsPaused);
|
GameplayClock.IsPaused.BindTo(clock.IsPaused);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,5 +189,17 @@ namespace osu.Game.Rulesets.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ReplayInputHandler ReplayInputHandler { get; set; }
|
public ReplayInputHandler ReplayInputHandler { get; set; }
|
||||||
|
|
||||||
|
private class StabilityGameplayClock : GameplayClock
|
||||||
|
{
|
||||||
|
public IFrameBasedClock ParentGameplayClock;
|
||||||
|
|
||||||
|
public StabilityGameplayClock(FramedClock underlyingClock)
|
||||||
|
: base(underlyingClock)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsSeeking => ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
|||||||
|
|
||||||
public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
|
public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
|
||||||
{
|
{
|
||||||
double adjustedTime = TimeAt(-offset, originTime, timeRange, scrollLength);
|
return TimeAt(-(scrollLength + offset), originTime, timeRange, scrollLength);
|
||||||
return adjustedTime - timeRange - 1000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
|
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
|
||||||
|
@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IScrollingInfo scrollingInfo { get; set; }
|
protected IScrollingInfo ScrollingInfo { get; private set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Direction.BindTo(scrollingInfo.Direction);
|
Direction.BindTo(ScrollingInfo.Direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer();
|
protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer();
|
||||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
private readonly BindableList<HitObject> selectedHitObjects = new BindableList<HitObject>();
|
private readonly BindableList<HitObject> selectedHitObjects = new BindableList<HitObject>();
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private IDistanceSnapProvider snapProvider { get; set; }
|
private IPositionSnapProvider snapProvider { get; set; }
|
||||||
|
|
||||||
protected BlueprintContainer()
|
protected BlueprintContainer()
|
||||||
{
|
{
|
||||||
@ -326,7 +326,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
foreach (var blueprint in SelectionBlueprints)
|
foreach (var blueprint in SelectionBlueprints)
|
||||||
{
|
{
|
||||||
if (blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.SelectionPoint))
|
if (blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.ScreenSpaceSelectionPoint))
|
||||||
blueprint.Select();
|
blueprint.Select();
|
||||||
else
|
else
|
||||||
blueprint.Deselect();
|
blueprint.Deselect();
|
||||||
@ -384,7 +384,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
// Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject
|
// Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject
|
||||||
movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First();
|
movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First();
|
||||||
movementBlueprintOriginalPosition = movementBlueprint.SelectionPoint; // todo: unsure if correct
|
movementBlueprintOriginalPosition = movementBlueprint.ScreenSpaceSelectionPoint; // todo: unsure if correct
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -405,16 +405,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
|
Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
|
||||||
|
|
||||||
// Retrieve a snapped position.
|
// Retrieve a snapped position.
|
||||||
(Vector2 snappedPosition, double snappedTime) = snapProvider.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime);
|
var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition);
|
||||||
|
|
||||||
// Move the hitobjects.
|
// Move the hitobjects.
|
||||||
if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, ToScreenSpace(snappedPosition))))
|
if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition)))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Apply the start time at the newly snapped-to position
|
if (result.Time.HasValue)
|
||||||
double offset = snappedTime - draggedObject.StartTime;
|
{
|
||||||
foreach (HitObject obj in selectionHandler.SelectedHitObjects)
|
// Apply the start time at the newly snapped-to position
|
||||||
obj.StartTime += offset;
|
double offset = result.Time.Value - draggedObject.StartTime;
|
||||||
|
foreach (HitObject obj in selectionHandler.SelectedHitObjects)
|
||||||
|
obj.StartTime += offset;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ using osu.Game.Rulesets.Edit;
|
|||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
{
|
{
|
||||||
@ -65,12 +64,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
createPlacement();
|
createPlacement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePlacementPosition(Vector2 screenSpacePosition)
|
private void updatePlacementPosition()
|
||||||
{
|
{
|
||||||
Vector2 snappedGridPosition = composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition), 0).position;
|
var snapResult = composer.SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position);
|
||||||
Vector2 snappedScreenSpacePosition = ToScreenSpace(snappedGridPosition);
|
|
||||||
|
|
||||||
currentPlacement.UpdatePosition(snappedScreenSpacePosition);
|
currentPlacement.UpdatePosition(snapResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -85,7 +83,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
removePlacement();
|
removePlacement();
|
||||||
|
|
||||||
if (currentPlacement != null)
|
if (currentPlacement != null)
|
||||||
updatePlacementPosition(inputManager.CurrentState.Mouse.Position);
|
updatePlacementPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject)
|
protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject)
|
||||||
@ -117,7 +115,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
placementBlueprintContainer.Child = currentPlacement = blueprint;
|
placementBlueprintContainer.Child = currentPlacement = blueprint;
|
||||||
|
|
||||||
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
|
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
|
||||||
updatePlacementPosition(inputManager.CurrentState.Mouse.Position);
|
updatePlacementPosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
protected OsuColour Colours { get; private set; }
|
protected OsuColour Colours { get; private set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected IDistanceSnapProvider SnapProvider { get; private set; }
|
protected IPositionSnapProvider SnapProvider { get; private set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorBeatmap beatmap { get; set; }
|
private EditorBeatmap beatmap { get; set; }
|
||||||
|
@ -17,9 +17,9 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
{
|
{
|
||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IPositionSnapProvider))]
|
||||||
[Cached]
|
[Cached]
|
||||||
public class Timeline : ZoomableScrollContainer, IDistanceSnapProvider
|
public class Timeline : ZoomableScrollContainer, IPositionSnapProvider
|
||||||
{
|
{
|
||||||
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
|
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
|
||||||
public readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
public readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
||||||
@ -181,11 +181,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IBeatSnapProvider beatSnapProvider { get; set; }
|
private IBeatSnapProvider beatSnapProvider { get; set; }
|
||||||
|
|
||||||
public double GetTimeFromScreenSpacePosition(Vector2 position)
|
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) =>
|
||||||
=> getTimeFromPosition(Content.ToLocalSpace(position));
|
new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition))));
|
||||||
|
|
||||||
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) =>
|
|
||||||
(position, beatSnapProvider.SnapTime(getTimeFromPosition(position)));
|
|
||||||
|
|
||||||
private double getTimeFromPosition(Vector2 localPosition) =>
|
private double getTimeFromPosition(Vector2 localPosition) =>
|
||||||
(localPosition.X / Content.DrawWidth) * track.Length;
|
(localPosition.X / Content.DrawWidth) * track.Length;
|
||||||
|
@ -186,7 +186,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft;
|
public override Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.TopLeft;
|
||||||
|
|
||||||
public class DragBar : Container
|
public class DragBar : Container
|
||||||
{
|
{
|
||||||
@ -275,32 +275,33 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
OnDragHandled?.Invoke(e);
|
OnDragHandled?.Invoke(e);
|
||||||
|
|
||||||
var time = timeline.GetTimeFromScreenSpacePosition(e.ScreenSpaceMousePosition);
|
if (timeline.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition).Time is double time)
|
||||||
|
|
||||||
switch (hitObject)
|
|
||||||
{
|
{
|
||||||
case IHasRepeats repeatHitObject:
|
switch (hitObject)
|
||||||
// find the number of repeats which can fit in the requested time.
|
{
|
||||||
var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1);
|
case IHasRepeats repeatHitObject:
|
||||||
var proposedCount = Math.Max(0, (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1);
|
// 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)Math.Round((time - hitObject.StartTime) / lengthOfOneRepeat) - 1);
|
||||||
|
|
||||||
if (proposedCount == repeatHitObject.RepeatCount)
|
if (proposedCount == repeatHitObject.RepeatCount)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
repeatHitObject.RepeatCount = proposedCount;
|
repeatHitObject.RepeatCount = proposedCount;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IHasEndTime endTimeHitObject:
|
case IHasEndTime endTimeHitObject:
|
||||||
var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time));
|
var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time));
|
||||||
|
|
||||||
if (endTimeHitObject.EndTime == snappedTime)
|
if (endTimeHitObject.EndTime == snappedTime)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
endTimeHitObject.EndTime = snappedTime;
|
endTimeHitObject.EndTime = snappedTime;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
beatmap.UpdateHitObject(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
beatmap.UpdateHitObject(hitObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDragEnd(DragEndEvent e)
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
|
@ -31,6 +31,11 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
public bool IsRunning => underlyingClock.IsRunning;
|
public bool IsRunning => underlyingClock.IsRunning;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether an ongoing seek operation is active.
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool IsSeeking => false;
|
||||||
|
|
||||||
public void ProcessFrame()
|
public void ProcessFrame()
|
||||||
{
|
{
|
||||||
// we do not want to process the underlying clock.
|
// we do not want to process the underlying clock.
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
|
||||||
using osu.Framework.Audio.Sample;
|
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Graphics.Audio;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
@ -17,25 +17,32 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
private readonly ISampleInfo[] hitSamples;
|
private readonly ISampleInfo[] hitSamples;
|
||||||
|
|
||||||
private List<(AdjustableProperty property, BindableDouble bindable)> adjustments;
|
|
||||||
|
|
||||||
private SampleChannel[] channels;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private ISampleStore samples { get; set; }
|
private ISampleStore samples { get; set; }
|
||||||
|
|
||||||
|
public SkinnableSound(ISampleInfo hitSamples)
|
||||||
|
: this(new[] { hitSamples })
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public SkinnableSound(IEnumerable<ISampleInfo> hitSamples)
|
public SkinnableSound(IEnumerable<ISampleInfo> hitSamples)
|
||||||
{
|
{
|
||||||
this.hitSamples = hitSamples.ToArray();
|
this.hitSamples = hitSamples.ToArray();
|
||||||
}
|
InternalChild = samplesContainer = new AudioContainer<DrawableSample>();
|
||||||
|
|
||||||
public SkinnableSound(ISampleInfo hitSamples)
|
|
||||||
{
|
|
||||||
this.hitSamples = new[] { hitSamples };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool looping;
|
private bool looping;
|
||||||
|
|
||||||
|
private readonly AudioContainer<DrawableSample> samplesContainer;
|
||||||
|
|
||||||
|
public BindableNumber<double> Volume => samplesContainer.Volume;
|
||||||
|
|
||||||
|
public BindableNumber<double> Balance => samplesContainer.Balance;
|
||||||
|
|
||||||
|
public BindableNumber<double> Frequency => samplesContainer.Frequency;
|
||||||
|
|
||||||
|
public BindableNumber<double> Tempo => samplesContainer.Tempo;
|
||||||
|
|
||||||
public bool Looping
|
public bool Looping
|
||||||
{
|
{
|
||||||
get => looping;
|
get => looping;
|
||||||
@ -45,33 +52,23 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
looping = value;
|
looping = value;
|
||||||
|
|
||||||
channels?.ForEach(c => c.Looping = looping);
|
samplesContainer.ForEach(c => c.Looping = looping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Play() => channels?.ForEach(c => c.Play());
|
public void Play() => samplesContainer.ForEach(c =>
|
||||||
|
|
||||||
public void Stop() => channels?.ForEach(c => c.Stop());
|
|
||||||
|
|
||||||
public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable)
|
|
||||||
{
|
{
|
||||||
if (adjustments == null) adjustments = new List<(AdjustableProperty, BindableDouble)>();
|
if (c.AggregateVolume.Value > 0)
|
||||||
|
c.Play();
|
||||||
|
});
|
||||||
|
|
||||||
adjustments.Add((type, adjustBindable));
|
public void Stop() => samplesContainer.ForEach(c => c.Stop());
|
||||||
channels?.ForEach(c => c.AddAdjustment(type, adjustBindable));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable)
|
|
||||||
{
|
|
||||||
adjustments?.Remove((type, adjustBindable));
|
|
||||||
channels?.ForEach(c => c.RemoveAdjustment(type, adjustBindable));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool IsPresent => Scheduler.HasPendingTasks;
|
public override bool IsPresent => Scheduler.HasPendingTasks;
|
||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||||
{
|
{
|
||||||
channels = hitSamples.Select(s =>
|
var channels = hitSamples.Select(s =>
|
||||||
{
|
{
|
||||||
var ch = skin.GetSample(s);
|
var ch = skin.GetSample(s);
|
||||||
|
|
||||||
@ -88,27 +85,12 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
ch.Looping = looping;
|
ch.Looping = looping;
|
||||||
ch.Volume.Value = s.Volume / 100.0;
|
ch.Volume.Value = s.Volume / 100.0;
|
||||||
|
|
||||||
if (adjustments != null)
|
|
||||||
{
|
|
||||||
foreach (var (property, bindable) in adjustments)
|
|
||||||
ch.AddAdjustment(property, bindable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ch;
|
return ch;
|
||||||
}).Where(c => c != null).ToArray();
|
}).Where(c => c != null);
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
|
||||||
{
|
|
||||||
base.Dispose(isDisposing);
|
|
||||||
|
|
||||||
if (channels != null)
|
|
||||||
{
|
|
||||||
foreach (var c in channels)
|
|
||||||
c.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,9 +71,12 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
currentBlueprint.UpdatePosition(InputManager.CurrentState.Mouse.Position);
|
currentBlueprint.UpdatePosition(SnapForBlueprint(currentBlueprint));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual SnapResult SnapForBlueprint(PlacementBlueprint blueprint) =>
|
||||||
|
new SnapResult(InputManager.CurrentState.Mouse.Position, null);
|
||||||
|
|
||||||
public override void Add(Drawable drawable)
|
public override void Add(Drawable drawable)
|
||||||
{
|
{
|
||||||
base.Add(drawable);
|
base.Add(drawable);
|
||||||
@ -81,7 +84,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
if (drawable is PlacementBlueprint blueprint)
|
if (drawable is PlacementBlueprint blueprint)
|
||||||
{
|
{
|
||||||
blueprint.Show();
|
blueprint.Show();
|
||||||
blueprint.UpdatePosition(InputManager.CurrentState.Mouse.Position);
|
blueprint.UpdatePosition(SnapForBlueprint(blueprint));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<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="ppy.osu.Game.Resources" Version="2020.512.0" />
|
||||||
<PackageReference Include="Sentry" Version="2.1.1" />
|
<PackageReference Include="Sentry" Version="2.1.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.25.0" />
|
<PackageReference Include="SharpCompress" Version="0.25.0" />
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<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" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
<!-- 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" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<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="SharpCompress" Version="0.25.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user