1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 07:42:57 +08:00

Merge pull request #738 from smoogipooo/mania-timing-sections

Mania timing sections
This commit is contained in:
Dean Herbert 2017-05-16 20:17:20 +09:00 committed by GitHub
commit 1112dbb113
12 changed files with 465 additions and 100 deletions

View File

@ -18,6 +18,7 @@ using osu.Game.Rulesets.Taiko.UI;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Desktop.VisualTests.Beatmaps; using osu.Desktop.VisualTests.Beatmaps;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Beatmaps.Timing;
namespace osu.Desktop.VisualTests.Tests namespace osu.Desktop.VisualTests.Tests
{ {
@ -52,6 +53,12 @@ namespace osu.Desktop.VisualTests.Tests
time += RNG.Next(50, 500); time += RNG.Next(50, 500);
} }
TimingInfo timing = new TimingInfo();
timing.ControlPoints.Add(new ControlPoint
{
BeatLength = 200
});
WorkingBeatmap beatmap = new TestWorkingBeatmap(new Beatmap WorkingBeatmap beatmap = new TestWorkingBeatmap(new Beatmap
{ {
HitObjects = objects, HitObjects = objects,
@ -64,8 +71,9 @@ namespace osu.Desktop.VisualTests.Tests
Artist = @"Unknown", Artist = @"Unknown",
Title = @"Sample Beatmap", Title = @"Sample Beatmap",
Author = @"peppy", Author = @"peppy",
} },
} },
TimingInfo = timing
}); });
Add(new Drawable[] Add(new Drawable[]

View File

@ -1,13 +1,16 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 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 osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using System.Linq;
using System; using System;
using System.Collections.Generic;
using osu.Game.Beatmaps.Timing;
using OpenTK;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects;
namespace osu.Desktop.VisualTests.Tests namespace osu.Desktop.VisualTests.Tests
{ {
@ -21,33 +24,58 @@ namespace osu.Desktop.VisualTests.Tests
{ {
base.Reset(); base.Reset();
const int max_columns = 10;
Action<int, SpecialColumnPosition> createPlayfield = (cols, pos) => Action<int, SpecialColumnPosition> createPlayfield = (cols, pos) =>
{ {
Clear(); Clear();
Add(new ManiaPlayfield(cols) Add(new ManiaPlayfield(cols, new List<ControlPoint>())
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
SpecialColumnPosition = pos SpecialColumnPosition = pos,
Scale = new Vector2(1, -1)
}); });
}; };
for (int i = 1; i <= max_columns; i++) Action<int, SpecialColumnPosition> createPlayfieldWithNotes = (cols, pos) =>
{ {
int tempI = i; Clear();
AddStep($"{i} column" + (i > 1 ? "s" : ""), () => createPlayfield(tempI, SpecialColumnPosition.Normal)); ManiaPlayfield playField;
Add(playField = new ManiaPlayfield(cols, new List<ControlPoint> { new ControlPoint { BeatLength = 200 } })
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
SpecialColumnPosition = pos,
Scale = new Vector2(1, -1)
});
AddStep("Trigger keys down", () => ((ManiaPlayfield)Children.First()).Columns.Children.ForEach(triggerKeyDown)); for (int i = 0; i < cols; i++)
AddStep("Trigger keys up", () => ((ManiaPlayfield)Children.First()).Columns.Children.ForEach(triggerKeyUp)); {
playField.Add(new DrawableNote(new Note
{
StartTime = Time.Current + 1000,
Column = i
}));
}
};
AddStep("Left special style", () => createPlayfield(tempI, SpecialColumnPosition.Left)); AddStep("1 column", () => createPlayfield(1, SpecialColumnPosition.Normal));
AddStep("Right special style", () => createPlayfield(tempI, SpecialColumnPosition.Right)); AddStep("4 columns", () => createPlayfield(4, SpecialColumnPosition.Normal));
} AddStep("Left special style", () => createPlayfield(4, SpecialColumnPosition.Left));
AddStep("Right special style", () => createPlayfield(4, SpecialColumnPosition.Right));
AddStep("5 columns", () => createPlayfield(5, SpecialColumnPosition.Normal));
AddStep("8 columns", () => createPlayfield(8, SpecialColumnPosition.Normal));
AddStep("Left special style", () => createPlayfield(8, SpecialColumnPosition.Left));
AddStep("Right special style", () => createPlayfield(8, SpecialColumnPosition.Right));
AddStep("Normal special style", () => createPlayfield(max_columns, SpecialColumnPosition.Normal)); AddStep("Normal special style", () => createPlayfield(4, SpecialColumnPosition.Normal));
AddStep("Notes", () => createPlayfieldWithNotes(4, SpecialColumnPosition.Normal));
AddWaitStep(10);
AddStep("Left special style", () => createPlayfieldWithNotes(4, SpecialColumnPosition.Left));
AddWaitStep(10);
AddStep("Right special style", () => createPlayfieldWithNotes(4, SpecialColumnPosition.Right));
AddWaitStep(10);
} }
private void triggerKeyDown(Column column) private void triggerKeyDown(Column column)

View File

@ -8,6 +8,7 @@ using System;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Beatmaps; using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using OpenTK;
namespace osu.Game.Rulesets.Mania.Beatmaps namespace osu.Game.Rulesets.Mania.Beatmaps
{ {
@ -17,7 +18,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, Beatmap beatmap) protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, Beatmap beatmap)
{ {
yield return null; int availableColumns = (int)Math.Round(beatmap.BeatmapInfo.Difficulty.CircleSize);
var positionData = original as IHasXPosition;
float localWDivisor = 512.0f / availableColumns;
int column = MathHelper.Clamp((int)Math.Floor((positionData?.X ?? 1) / localWDivisor), 0, availableColumns - 1);
yield return new Note
{
StartTime = original.StartTime,
Column = column,
};
} }
} }
} }

View File

@ -27,24 +27,20 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
// This will be fixed when new designs are given or the current design is finalized. // This will be fixed when new designs are given or the current design is finalized.
bodyPiece = new BodyPiece bodyPiece = new BodyPiece
{ {
Anchor = Anchor.BottomCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre, Origin = Anchor.TopCentre,
}, },
headPiece = new NotePiece headPiece = new NotePiece
{ {
Anchor = Anchor.BottomCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre Origin = Anchor.TopCentre
}, },
tailPiece = new NotePiece tailPiece = new NotePiece
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre Origin = Anchor.BottomCentre
} }
}); });
// The "length" of the hold note stops at the "base" of the tail piece
// but we want to contain the tail piece within our bounds
Height += (float)HitObject.Duration / headPiece.Height;
} }
public override Color4 AccentColour public override Color4 AccentColour

View File

@ -22,9 +22,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
HitObject = hitObject; HitObject = hitObject;
Anchor = Anchor.TopCentre;
Origin = Anchor.BottomCentre;
RelativePositionAxes = Axes.Y; RelativePositionAxes = Axes.Y;
Y = (float)HitObject.StartTime; Y = (float)HitObject.StartTime;

View File

@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public DrawableNote(Note hitObject) public DrawableNote(Note hitObject)
: base(hitObject) : base(hitObject)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.Both;
AutoSizeAxes = Axes.Y; Height = 100;
Add(headPiece = new NotePiece Add(headPiece = new NotePiece
{ {
Anchor = Anchor.BottomCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre Origin = Anchor.TopCentre
}); });
} }
@ -38,6 +38,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
} }
} }
protected override void Update()
{
if (Time.Current > HitObject.StartTime)
Colour = Color4.Green;
}
protected override void UpdateState(ArmedState state) protected override void UpdateState(ArmedState state)
{ {
} }

View File

@ -0,0 +1,153 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
using osu.Game.Beatmaps.Timing;
namespace osu.Game.Rulesets.Mania.Timing
{
/// <summary>
/// A container in which added drawables are put into a relative coordinate space spanned by a length of time.
/// <para>
/// This container contains <see cref="ControlPoint"/>s which scroll inside this container.
/// Drawables added to this container are moved inside the relevant <see cref="ControlPoint"/>,
/// and as such, will scroll along with the <see cref="ControlPoint"/>s.
/// </para>
/// </summary>
public class ControlPointContainer : Container<Drawable>
{
/// <summary>
/// The amount of time which this container spans.
/// </summary>
public double TimeSpan { get; set; }
private readonly List<DrawableControlPoint> drawableControlPoints;
public ControlPointContainer(IEnumerable<ControlPoint> timingChanges)
{
drawableControlPoints = timingChanges.Select(t => new DrawableControlPoint(t)).ToList();
Children = drawableControlPoints;
}
/// <summary>
/// Adds a drawable to this container. Note that the drawable added must have its Y-position be
/// an absolute unit of time that is _not_ relative to <see cref="TimeSpan"/>.
/// </summary>
/// <param name="drawable">The drawable to add.</param>
public override void Add(Drawable drawable)
{
// Always add timing sections to ourselves
if (drawable is DrawableControlPoint)
{
base.Add(drawable);
return;
}
var controlPoint = drawableControlPoints.LastOrDefault(t => t.CanContain(drawable)) ?? drawableControlPoints.FirstOrDefault();
if (controlPoint == null)
throw new Exception("Could not find suitable timing section to add object to.");
controlPoint.Add(drawable);
}
/// <summary>
/// A container that contains drawables within the time span of a timing section.
/// <para>
/// The content of this container will scroll relative to the current time.
/// </para>
/// </summary>
private class DrawableControlPoint : Container
{
private readonly ControlPoint timingChange;
protected override Container<Drawable> Content => content;
private readonly Container content;
/// <summary>
/// Creates a drawable control point. The height of this container will be proportional
/// to the beat length of the control point it is initialized with such that, e.g. a beat length
/// of 500ms results in this container being twice as high as its parent, which further means that
/// the content container will scroll at twice the normal rate.
/// </summary>
/// <param name="timingChange">The control point to create the drawable control point for.</param>
public DrawableControlPoint(ControlPoint timingChange)
{
this.timingChange = timingChange;
RelativeSizeAxes = Axes.Both;
AddInternal(content = new AutoTimeRelativeContainer
{
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Both,
Y = (float)timingChange.Time
});
}
protected override void Update()
{
var parent = (ControlPointContainer)Parent;
// Adjust our height to account for the speed changes
Height = (float)(1000 / timingChange.BeatLength / timingChange.SpeedMultiplier);
RelativeCoordinateSpace = new Vector2(1, (float)parent.TimeSpan);
// Scroll the content
content.Y = (float)(timingChange.Time - Time.Current);
}
public override void Add(Drawable drawable)
{
// The previously relatively-positioned drawable will now become relative to content, but since the drawable has no knowledge of content,
// we need to offset it back by content's position position so that it becomes correctly relatively-positioned to content
// This can be removed if hit objects were stored such that either their StartTime or their "beat offset" was relative to the timing change
// they belonged to, but this requires a radical change to the beatmap format which we're not ready to do just yet
drawable.Y -= (float)timingChange.Time;
base.Add(drawable);
}
/// <summary>
/// Whether this control point can contain a drawable. This control point can contain a drawable if the drawable is positioned "after" this control point.
/// </summary>
/// <param name="drawable">The drawable to check.</param>
public bool CanContain(Drawable drawable) => content.Y <= drawable.Y;
/// <summary>
/// A container which always keeps its height and relative coordinate space "auto-sized" to its children.
/// <para>
/// This is used in the case where children are relatively positioned/sized to time values (e.g. notes/bar lines) to keep
/// such children wrapped inside a container, otherwise they would disappear due to container flattening.
/// </para>
/// </summary>
private class AutoTimeRelativeContainer : Container
{
public override void InvalidateFromChild(Invalidation invalidation)
{
// We only want to re-compute our size when a child's size or position has changed
if ((invalidation & Invalidation.Geometry) == 0)
{
base.InvalidateFromChild(invalidation);
return;
}
if (!Children.Any())
return;
float height = Children.Select(child => child.Y + child.Height).Max();
Height = height;
RelativeCoordinateSpace = new Vector2(1, height);
base.InvalidateFromChild(invalidation);
}
}
}
}
}

View File

@ -12,13 +12,17 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Timing;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Beatmaps.Timing;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
public class Column : Container, IHasAccentColour public class Column : Container, IHasAccentColour
{ {
private const float key_size = 50;
private const float key_icon_size = 10; private const float key_icon_size = 10;
private const float key_icon_corner_radius = 3; private const float key_icon_corner_radius = 3;
private const float key_icon_border_radius = 2; private const float key_icon_border_radius = 2;
@ -35,7 +39,9 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly Container hitTargetBar; private readonly Container hitTargetBar;
private readonly Container keyIcon; private readonly Container keyIcon;
public Column() public readonly ControlPointContainer ControlPointContainer;
public Column(IEnumerable<ControlPoint> timingChanges)
{ {
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
Width = column_width; Width = column_width;
@ -48,59 +54,16 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Alpha = 0.2f Alpha = 0.2f
}, },
new FillFlowContainer new Container
{ {
Name = "Key + hit target", Name = "Hit target + hit objects",
Anchor = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both,
Origin = Anchor.BottomCentre, Padding = new MarginPadding { Top = ManiaPlayfield.HIT_TARGET_POSITION},
RelativeSizeAxes = Axes.X, Children = new Drawable[]
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new[]
{ {
new Container
{
Name = "Key",
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
Height = key_size,
Children = new Drawable[]
{
new Box
{
Name = "Key gradient",
RelativeSizeAxes = Axes.Both,
ColourInfo = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)),
Alpha = 0.5f
},
keyIcon = new Container
{
Name = "Key icon",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(key_icon_size),
Masking = true,
CornerRadius = key_icon_corner_radius,
BorderThickness = 2,
BorderColour = Color4.White, // Not true
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
}
}
},
new Container new Container
{ {
Name = "Hit target", Name = "Hit target",
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = hit_target_height, Height = hit_target_height,
Children = new Drawable[] Children = new Drawable[]
@ -114,8 +77,6 @@ namespace osu.Game.Rulesets.Mania.UI
hitTargetBar = new Container hitTargetBar = new Container
{ {
Name = "Bar", Name = "Bar",
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = hit_target_bar_height, Height = hit_target_bar_height,
Masking = true, Masking = true,
@ -128,6 +89,47 @@ namespace osu.Game.Rulesets.Mania.UI
} }
} }
} }
},
ControlPointContainer = new ControlPointContainer(timingChanges)
{
Name = "Hit objects",
RelativeSizeAxes = Axes.Both,
},
}
},
new Container
{
Name = "Key",
RelativeSizeAxes = Axes.X,
Height = ManiaPlayfield.HIT_TARGET_POSITION,
Children = new Drawable[]
{
new Box
{
Name = "Key gradient",
RelativeSizeAxes = Axes.Both,
ColourInfo = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)),
Alpha = 0.5f
},
keyIcon = new Container
{
Name = "Key icon",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(key_icon_size),
Masking = true,
CornerRadius = key_icon_corner_radius,
BorderThickness = 2,
BorderColour = Color4.White, // Not true
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
} }
} }
} }
@ -176,9 +178,17 @@ namespace osu.Game.Rulesets.Mania.UI
} }
} }
public void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> hitObject)
{
ControlPointContainer.Add(hitObject);
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{ {
if (args.Key == Key && !args.Repeat) if (args.Repeat)
return false;
if (args.Key == Key)
{ {
background.FadeTo(background.Alpha + 0.2f, 50, EasingTypes.OutQuint); background.FadeTo(background.Alpha + 0.2f, 50, EasingTypes.OutQuint);
keyIcon.ScaleTo(1.4f, 50, EasingTypes.OutQuint); keyIcon.ScaleTo(1.4f, 50, EasingTypes.OutQuint);
@ -198,5 +208,4 @@ namespace osu.Game.Rulesets.Mania.UI
return false; return false;
} }
} }
} }

View File

@ -1,13 +1,20 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 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 System.Linq;
using OpenTK;
using osu.Framework.Graphics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Beatmaps; using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -15,20 +22,67 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
public class ManiaHitRenderer : HitRenderer<ManiaHitObject, ManiaJudgement> public class ManiaHitRenderer : HitRenderer<ManiaHitObject, ManiaJudgement>
{ {
private readonly int columns; public int? Columns;
public ManiaHitRenderer(WorkingBeatmap beatmap, int columns = 5) public ManiaHitRenderer(WorkingBeatmap beatmap)
: base(beatmap) : base(beatmap)
{ {
this.columns = columns; }
protected override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield()
{
ControlPoint firstTimingChange = Beatmap.TimingInfo.ControlPoints.FirstOrDefault(t => t.TimingChange);
if (firstTimingChange == null)
throw new Exception("The Beatmap contains no timing points!");
// Generate the timing points, making non-timing changes use the previous timing change
var timingChanges = Beatmap.TimingInfo.ControlPoints.Select(c =>
{
ControlPoint t = c.Clone();
if (c.TimingChange)
firstTimingChange = c;
else
t.BeatLength = firstTimingChange.BeatLength;
return t;
});
double lastObjectTime = (Objects.Last() as IHasEndTime)?.EndTime ?? Objects.Last().StartTime;
// Perform some post processing of the timing changes
timingChanges = timingChanges
// Collapse sections after the last hit object
.Where(s => s.Time <= lastObjectTime)
// Collapse sections with the same start time
.GroupBy(s => s.Time).Select(g => g.Last()).OrderBy(s => s.Time)
// Collapse sections with the same beat length
.GroupBy(s => s.BeatLength * s.SpeedMultiplier).Select(g => g.First())
.ToList();
return new ManiaPlayfield(Columns ?? (int)Math.Round(Beatmap.BeatmapInfo.Difficulty.CircleSize), timingChanges)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
// Invert by default for now (should be moved to config/skin later)
Scale = new Vector2(1, -1)
};
} }
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter(); protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter();
protected override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield() => new ManiaPlayfield(columns); protected override DrawableHitObject<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h)
{
var note = h as Note;
if (note != null)
return new DrawableNote(note);
protected override DrawableHitObject<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h) => null; return null;
}
protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(1, 0.8f);
} }
} }

View File

@ -15,11 +15,25 @@ using osu.Framework.Allocation;
using OpenTK.Input; using OpenTK.Input;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Mania.Timing;
using osu.Framework.Input;
using osu.Game.Beatmaps.Timing;
using osu.Framework.Graphics.Transforms;
using osu.Framework.MathUtils;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
public class ManiaPlayfield : Playfield<ManiaHitObject, ManiaJudgement> public class ManiaPlayfield : Playfield<ManiaHitObject, ManiaJudgement>
{ {
public const float HIT_TARGET_POSITION = 50;
private const float time_span_default = 5000;
private const float time_span_min = 10;
private const float time_span_max = 50000;
private const float time_span_step = 200;
/// <summary> /// <summary>
/// Default column keys, expanding outwards from the middle as more column are added. /// Default column keys, expanding outwards from the middle as more column are added.
/// E.g. 2 columns use FJ, 4 columns use DFJK, 6 use SDFJKL, etc... /// E.g. 2 columns use FJ, 4 columns use DFJK, 6 use SDFJKL, etc...
@ -43,12 +57,14 @@ namespace osu.Game.Rulesets.Mania.UI
public readonly FlowContainer<Column> Columns; public readonly FlowContainer<Column> Columns;
private readonly ControlPointContainer barlineContainer;
private List<Color4> normalColumnColours = new List<Color4>(); private List<Color4> normalColumnColours = new List<Color4>();
private Color4 specialColumnColour; private Color4 specialColumnColour;
private readonly int columnCount; private readonly int columnCount;
public ManiaPlayfield(int columnCount) public ManiaPlayfield(int columnCount, IEnumerable<ControlPoint> timingChanges)
{ {
this.columnCount = columnCount; this.columnCount = columnCount;
@ -59,10 +75,11 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
new Container new Container
{ {
Anchor = Anchor.Centre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.X,
Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
new Box new Box
@ -72,18 +89,34 @@ namespace osu.Game.Rulesets.Mania.UI
}, },
Columns = new FillFlowContainer<Column> Columns = new FillFlowContainer<Column>
{ {
Name = "Columns",
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.X,
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Padding = new MarginPadding { Left = 1, Right = 1 }, Padding = new MarginPadding { Left = 1, Right = 1 },
Spacing = new Vector2(1, 0) Spacing = new Vector2(1, 0)
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = HIT_TARGET_POSITION },
Children = new[]
{
barlineContainer = new ControlPointContainer(timingChanges)
{
Name = "Bar lines",
RelativeSizeAxes = Axes.Both,
}
}
} }
} }
} }
}; };
for (int i = 0; i < columnCount; i++) for (int i = 0; i < columnCount; i++)
Columns.Add(new Column()); Columns.Add(new Column(timingChanges));
TimeSpan = time_span_default;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -155,5 +188,73 @@ namespace osu.Game.Rulesets.Mania.UI
return column == columnCount - 1; return column == columnCount - 1;
} }
} }
public override void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> h) => Columns.Children.ElementAt(h.HitObject.Column).Add(h);
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (state.Keyboard.ControlPressed)
{
switch (args.Key)
{
case Key.Minus:
transformTimeSpanTo(TimeSpan + time_span_step, 200, EasingTypes.OutQuint);
break;
case Key.Plus:
transformTimeSpanTo(TimeSpan - time_span_step, 200, EasingTypes.OutQuint);
break;
}
}
return false;
}
private double timeSpan;
/// <summary>
/// The amount of time which the length of the playfield spans.
/// </summary>
public double TimeSpan
{
get { return timeSpan; }
set
{
if (timeSpan == value)
return;
timeSpan = value;
timeSpan = MathHelper.Clamp(timeSpan, time_span_min, time_span_max);
barlineContainer.TimeSpan = value;
Columns.Children.ForEach(c => c.ControlPointContainer.TimeSpan = value);
}
}
private void transformTimeSpanTo(double newTimeSpan, double duration = 0, EasingTypes easing = EasingTypes.None)
{
TransformTo(() => TimeSpan, newTimeSpan, duration, easing, new TransformTimeSpan());
}
private class TransformTimeSpan : Transform<double>
{
public override double CurrentValue
{
get
{
double time = Time?.Current ?? 0;
if (time < StartTime) return StartValue;
if (time >= EndTime) return EndValue;
return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing);
}
}
public override void Apply(Drawable d)
{
base.Apply(d);
var p = (ManiaPlayfield)d;
p.TimeSpan = CurrentValue;
}
}
} }
} }

View File

@ -68,6 +68,7 @@
<Compile Include="ManiaRuleset.cs" /> <Compile Include="ManiaRuleset.cs" />
<Compile Include="Mods\ManiaMod.cs" /> <Compile Include="Mods\ManiaMod.cs" />
<Compile Include="UI\SpecialColumnPosition.cs" /> <Compile Include="UI\SpecialColumnPosition.cs" />
<Compile Include="Timing\ControlPointContainer.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj"> <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">

View File

@ -7,7 +7,7 @@ namespace osu.Game.Beatmaps.Timing
{ {
public string SampleBank; public string SampleBank;
public int SampleVolume; public int SampleVolume;
public TimeSignatures TimeSignature; public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple;
public double Time; public double Time;
public double BeatLength = 500; public double BeatLength = 500;
public double SpeedMultiplier = 1; public double SpeedMultiplier = 1;