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

Merge branch 'note-placement' into inter-column-movements

This commit is contained in:
smoogipoo 2018-11-15 20:53:57 +09:00
commit eddba44d0f
15 changed files with 325 additions and 29 deletions

View File

@ -0,0 +1,50 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
[Cached(Type = typeof(IManiaHitObjectComposer))]
public abstract class ManiaPlacementBlueprintTestCase : PlacementBlueprintTestCase, IManiaHitObjectComposer
{
private readonly Column column;
protected ManiaPlacementBlueprintTestCase()
{
Add(column = new Column(0)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AccentColour = Color4.OrangeRed,
Clock = new FramedClock(new StopwatchClock()), // No scroll
});
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs(((ScrollingTestContainer)HitObjectContainer).ScrollingInfo);
return dependencies;
}
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject);
public Column ColumnAt(Vector2 screenSpacePosition) => column;
}
}

View File

@ -49,8 +49,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Spacing = new Vector2(20, 0), Spacing = new Vector2(20, 0),
Children = new[] Children = new[]
{ {
createColumn(ScrollingDirection.Up, ManiaAction.Key1), createColumn(ScrollingDirection.Up, ManiaAction.Key1, 0),
createColumn(ScrollingDirection.Down, ManiaAction.Key2) createColumn(ScrollingDirection.Down, ManiaAction.Key2, 1)
} }
}; };
} }
@ -85,9 +85,9 @@ namespace osu.Game.Rulesets.Mania.Tests
} }
} }
private Drawable createColumn(ScrollingDirection direction, ManiaAction action) private Drawable createColumn(ScrollingDirection direction, ManiaAction action, int index)
{ {
var column = new Column var column = new Column(index)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,

View File

@ -0,0 +1,18 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Tests
{
public class TestCaseNotePlacementBlueprint : ManiaPlacementBlueprintTestCase
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint();
}
}

View File

@ -0,0 +1,101 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
public class ManiaPlacementBlueprint<T> : PlacementBlueprint
where T : ManiaHitObject
{
protected new T HitObject => (T)base.HitObject;
/// <summary>
/// The current mouse position, snapped to the closest column.
/// </summary>
protected Vector2 SnappedMousePosition { get; private set; }
[Resolved]
private IManiaHitObjectComposer composer { get; set; }
[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
public ManiaPlacementBlueprint(T hitObject)
: base(hitObject)
{
RelativeSizeAxes = Axes.None;
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
updateSnappedPosition(e);
return true;
}
private ScheduledDelegate scheduledSnappedPositionUpdate;
private void updateSnappedPosition(MouseMoveEvent e)
{
scheduledSnappedPositionUpdate?.Cancel();
scheduledSnappedPositionUpdate = Schedule(() =>
{
Column column = ColumnAt(e.ScreenSpaceMousePosition);
if (column == null)
SnappedMousePosition = e.MousePosition;
else
{
// Snap to the column
var parentPos = Parent.ToLocalSpace(column.ToScreenSpace(new Vector2(column.DrawWidth / 2, 0)));
SnappedMousePosition = new Vector2(parentPos.X, e.MousePosition.Y);
}
});
}
protected double TimeAt(Vector2 screenSpacePosition)
{
var column = ColumnAt(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 = column.HitObjectContainer.ToLocalSpace(applyPositionOffset(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 Column ColumnAt(Vector2 screenSpacePosition)
=> composer.ColumnAt(applyPositionOffset(screenSpacePosition));
private Vector2 applyPositionOffset(Vector2 position)
{
switch (scrollingInfo.Direction.Value)
{
case ScrollingDirection.Up:
position.Y -= DrawHeight / 2;
break;
case ScrollingDirection.Down:
position.Y += DrawHeight / 2;
break;
}
return position;
}
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
public class NotePlacementBlueprint : ManiaPlacementBlueprint<Note>
{
public NotePlacementBlueprint()
: base(new Note())
{
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Y;
Width = 45;
InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
}
protected override void Update()
{
base.Update();
Position = SnappedMousePosition;
}
protected override bool OnClick(ClickEvent e)
{
Column column;
if ((column = ColumnAt(e.ScreenSpaceMousePosition)) == null)
return base.OnClick(e);
HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition);
HitObject.Column = column.Index;
EndPlacement();
return true;
}
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mania.UI;
using OpenTK;
namespace osu.Game.Rulesets.Mania.Edit
{
public interface IManiaHitObjectComposer
{
Column ColumnAt(Vector2 screenSpacePosition);
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
@ -11,11 +10,14 @@ using osu.Game.Rulesets.Objects.Drawables;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using OpenTK;
namespace osu.Game.Rulesets.Mania.Edit namespace osu.Game.Rulesets.Mania.Edit
{ {
public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject> [Cached(Type = typeof(IManiaHitObjectComposer))]
public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject>, IManiaHitObjectComposer
{ {
public ManiaHitObjectComposer(Ruleset ruleset) public ManiaHitObjectComposer(Ruleset ruleset)
: base(ruleset) : base(ruleset)
@ -27,6 +29,8 @@ namespace osu.Game.Rulesets.Mania.Edit
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); => dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
public Column ColumnAt(Vector2 screenSpacePosition) => ((ManiaPlayfield)RulesetContainer.Playfield).GetColumnByPosition(screenSpacePosition);
protected override RulesetContainer<ManiaHitObject> CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) protected override RulesetContainer<ManiaHitObject> CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
{ {
var rulesetContainer = new ManiaEditRulesetContainer(ruleset, beatmap); var rulesetContainer = new ManiaEditRulesetContainer(ruleset, beatmap);
@ -37,7 +41,10 @@ namespace osu.Game.Rulesets.Mania.Edit
return rulesetContainer; return rulesetContainer;
} }
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => Array.Empty<HitObjectCompositionTool>(); protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
{
new NoteCompositionTool()
};
public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
{ {

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
namespace osu.Game.Rulesets.Mania.Edit
{
public class NoteCompositionTool : HitObjectCompositionTool
{
public NoteCompositionTool()
: base(nameof(Note))
{
}
public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
}
}

View File

@ -13,6 +13,7 @@ using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
@ -21,6 +22,11 @@ namespace osu.Game.Rulesets.Mania.UI
private const float column_width = 45; private const float column_width = 45;
private const float special_column_width = 70; private const float special_column_width = 70;
/// <summary>
/// The index of this column as part of the whole playfield.
/// </summary>
public readonly int Index;
public readonly Bindable<ManiaAction> Action = new Bindable<ManiaAction>(); public readonly Bindable<ManiaAction> Action = new Bindable<ManiaAction>();
private readonly ColumnBackground background; private readonly ColumnBackground background;
@ -30,8 +36,10 @@ namespace osu.Game.Rulesets.Mania.UI
internal readonly Container TopLevelContainer; internal readonly Container TopLevelContainer;
private readonly Container explosionContainer; private readonly Container explosionContainer;
public Column() public Column(int index)
{ {
Index = index;
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
Width = column_width; Width = column_width;
@ -165,5 +173,9 @@ namespace osu.Game.Rulesets.Mania.UI
} }
public bool OnReleased(ManiaAction action) => false; public bool OnReleased(ManiaAction action) => false;
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
=> DrawRectangle.Inflate(new Vector2(1, 0)).Contains(ToLocalSpace(screenSpacePos));
} }
} }

View File

@ -54,6 +54,33 @@ namespace osu.Game.Rulesets.Mania.UI
public void Add(BarLine barline) => stages.ForEach(s => s.Add(barline)); public void Add(BarLine barline) => stages.ForEach(s => s.Add(barline));
/// <summary>
/// Retrieves a column from a screen-space position.
/// </summary>
/// <param name="screenSpacePosition">The screen-space position.</param>
/// <returns>The column which the <paramref name="screenSpacePosition"/> lies in.</returns>
public Column GetColumnByPosition(Vector2 screenSpacePosition)
{
Column found = null;
foreach (var stage in stages)
{
foreach (var column in stage.Columns)
{
if (column.ReceivePositionalInputAt(screenSpacePosition))
{
found = column;
break;
}
}
if (found != null)
break;
}
return found;
}
private ManiaStage getStageByColumn(int column) private ManiaStage getStageByColumn(int column)
{ {
int sum = 0; int sum = 0;

View File

@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < definition.Columns; i++) for (int i = 0; i < definition.Columns; i++)
{ {
var isSpecial = definition.IsSpecialColumn(i); var isSpecial = definition.IsSpecialColumn(i);
var column = new Column var column = new Column(firstColumnIndex + i)
{ {
IsSpecial = isSpecial, IsSpecial = isSpecial,
Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ } Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }

View File

@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Edit
// Process object // Process object
var processor = ruleset.CreateBeatmapProcessor(beatmap); var processor = ruleset.CreateBeatmapProcessor(beatmap);
processor.PreProcess(); processor?.PreProcess();
tObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); tObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
processor.PostProcess(); processor?.PostProcess();
// Add visual representation // Add visual representation
var drawableObject = rulesetContainer.GetVisualRepresentation(tObject); var drawableObject = rulesetContainer.GetVisualRepresentation(tObject);

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Edit
{ {
public abstract class HitObjectComposer : CompositeDrawable public abstract class HitObjectComposer : CompositeDrawable
{ {
public IEnumerable<DrawableHitObject> HitObjects => rulesetContainer.Playfield.AllHitObjects; public IEnumerable<DrawableHitObject> HitObjects => RulesetContainer.Playfield.AllHitObjects;
protected readonly Ruleset Ruleset; protected readonly Ruleset Ruleset;
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Edit
private readonly List<Container> layerContainers = new List<Container>(); private readonly List<Container> layerContainers = new List<Container>();
private EditRulesetContainer rulesetContainer; protected EditRulesetContainer RulesetContainer { get; private set; }
private BlueprintContainer blueprintContainer; private BlueprintContainer blueprintContainer;
@ -54,8 +54,8 @@ namespace osu.Game.Rulesets.Edit
try try
{ {
rulesetContainer = CreateRulesetContainer(); RulesetContainer = CreateRulesetContainer();
rulesetContainer.Clock = framedClock; RulesetContainer.Clock = framedClock;
} }
catch (Exception e) catch (Exception e)
{ {
@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Edit
Children = new Drawable[] Children = new Drawable[]
{ {
layerBelowRuleset, layerBelowRuleset,
rulesetContainer, RulesetContainer,
layerAboveRuleset layerAboveRuleset
} }
} }
@ -140,10 +140,10 @@ namespace osu.Game.Rulesets.Edit
layerContainers.ForEach(l => layerContainers.ForEach(l =>
{ {
l.Anchor = rulesetContainer.Playfield.Anchor; l.Anchor = RulesetContainer.Playfield.Anchor;
l.Origin = rulesetContainer.Playfield.Origin; l.Origin = RulesetContainer.Playfield.Origin;
l.Position = rulesetContainer.Playfield.Position; l.Position = RulesetContainer.Playfield.Position;
l.Size = rulesetContainer.Playfield.Size; l.Size = RulesetContainer.Playfield.Size;
}); });
} }
@ -156,9 +156,9 @@ namespace osu.Game.Rulesets.Edit
/// Adds a <see cref="HitObject"/> to the <see cref="Beatmaps.Beatmap"/> and visualises it. /// Adds a <see cref="HitObject"/> to the <see cref="Beatmaps.Beatmap"/> and visualises it.
/// </summary> /// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> to add.</param> /// <param name="hitObject">The <see cref="HitObject"/> to add.</param>
public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(rulesetContainer.Add(hitObject)); public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(RulesetContainer.Add(hitObject));
public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(rulesetContainer.Remove(hitObject)); public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(RulesetContainer.Remove(hitObject));
internal abstract EditRulesetContainer CreateRulesetContainer(); internal abstract EditRulesetContainer CreateRulesetContainer();

View File

@ -15,18 +15,14 @@ namespace osu.Game.Tests.Visual
[Cached(Type = typeof(IPlacementHandler))] [Cached(Type = typeof(IPlacementHandler))]
public abstract class PlacementBlueprintTestCase : OsuTestCase, IPlacementHandler public abstract class PlacementBlueprintTestCase : OsuTestCase, IPlacementHandler
{ {
private readonly Container hitObjectContainer; protected readonly Container HitObjectContainer;
private PlacementBlueprint currentBlueprint; private PlacementBlueprint currentBlueprint;
protected PlacementBlueprintTestCase() protected PlacementBlueprintTestCase()
{ {
Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = 2; Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = 2;
Add(hitObjectContainer = new Container Add(HitObjectContainer = CreateHitObjectContainer());
{
RelativeSizeAxes = Axes.Both,
Clock = new FramedClock(new StopwatchClock())
});
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -49,7 +45,7 @@ namespace osu.Game.Tests.Visual
public void EndPlacement(HitObject hitObject) public void EndPlacement(HitObject hitObject)
{ {
hitObjectContainer.Add(CreateHitObject(hitObject)); AddHitObject(CreateHitObject(hitObject));
Remove(currentBlueprint); Remove(currentBlueprint);
Add(currentBlueprint = CreateBlueprint()); Add(currentBlueprint = CreateBlueprint());
@ -59,6 +55,10 @@ namespace osu.Game.Tests.Visual
{ {
} }
protected virtual Container CreateHitObjectContainer() => new Container { RelativeSizeAxes = Axes.Both };
protected virtual void AddHitObject(DrawableHitObject hitObject) => HitObjectContainer.Add(hitObject);
protected abstract DrawableHitObject CreateHitObject(HitObject hitObject); protected abstract DrawableHitObject CreateHitObject(HitObject hitObject);
protected abstract PlacementBlueprint CreateBlueprint(); protected abstract PlacementBlueprint CreateBlueprint();
} }

View File

@ -24,6 +24,8 @@ namespace osu.Game.Tests.Visual
public double TimeRange { set => scrollingInfo.TimeRange.Value = value; } public double TimeRange { set => scrollingInfo.TimeRange.Value = value; }
public IScrollingInfo ScrollingInfo => scrollingInfo;
[Cached(Type = typeof(IScrollingInfo))] [Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();