mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 19:03:08 +08:00
Storyboards implementation.
This commit is contained in:
parent
c2b16dae10
commit
e547416193
91
osu.Desktop.Tests/Visual/TestCaseStoryboard.cs
Normal file
91
osu.Desktop.Tests/Visual/TestCaseStoryboard.cs
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
|
||||
namespace osu.Desktop.Tests.Visual
|
||||
{
|
||||
internal class TestCaseStoryboard : OsuTestCase
|
||||
{
|
||||
public override string Description => @"Tests storyboards.";
|
||||
|
||||
private readonly Bindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
|
||||
|
||||
private MusicController musicController;
|
||||
private Container<Storyboard> storyboardContainer;
|
||||
private Storyboard storyboard;
|
||||
|
||||
public TestCaseStoryboard()
|
||||
{
|
||||
Clock = new FramedClock();
|
||||
|
||||
Add(new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
},
|
||||
storyboardContainer = new Container<Storyboard>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
},
|
||||
});
|
||||
Add(musicController = new MusicController
|
||||
{
|
||||
Origin = Anchor.TopRight,
|
||||
Anchor = Anchor.TopRight,
|
||||
State = Visibility.Visible,
|
||||
});
|
||||
|
||||
AddStep("Restart", restart);
|
||||
AddToggleStep("Passing", passing => { if (storyboard != null) storyboard.Passing = passing; });
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGameBase game)
|
||||
{
|
||||
beatmapBacking.BindTo(game.Beatmap);
|
||||
beatmapBacking.ValueChanged += beatmapChanged;
|
||||
}
|
||||
|
||||
private void beatmapChanged(WorkingBeatmap working)
|
||||
=> loadStoryboard(working);
|
||||
|
||||
private void restart()
|
||||
{
|
||||
var track = beatmapBacking.Value.Track;
|
||||
|
||||
track.Reset();
|
||||
loadStoryboard(beatmapBacking.Value);
|
||||
track.Start();
|
||||
}
|
||||
|
||||
private void loadStoryboard(WorkingBeatmap working)
|
||||
{
|
||||
if (storyboard != null)
|
||||
storyboardContainer.Remove(storyboard);
|
||||
|
||||
var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
|
||||
decoupledClock.ChangeSource(working.Track);
|
||||
storyboardContainer.Clock = decoupledClock;
|
||||
|
||||
storyboardContainer.Add(storyboard = working.Beatmap.Storyboard.CreateDrawable());
|
||||
storyboard.Passing = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -91,6 +91,7 @@
|
||||
<Compile Include="Visual\TestCaseMenuButtonSystem.cs" />
|
||||
<Compile Include="Visual\TestCaseMenuOverlays.cs" />
|
||||
<Compile Include="Visual\TestCaseMods.cs" />
|
||||
<Compile Include="Visual\TestCaseStoryboard.cs" />
|
||||
<Compile Include="Visual\TestCaseMusicController.cs" />
|
||||
<Compile Include="Visual\TestCaseNotificationOverlay.cs" />
|
||||
<Compile Include="Visual\TestCaseOnScreenDisplay.cs" />
|
||||
|
@ -8,6 +8,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.IO.Serialization;
|
||||
using osu.Game.Storyboards;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
@ -40,6 +41,11 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
|
||||
|
||||
/// <summary>
|
||||
/// The Beatmap's Storyboard.
|
||||
/// </summary>
|
||||
public StoryboardDefinition Storyboard = new StoryboardDefinition();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new beatmap.
|
||||
/// </summary>
|
||||
|
@ -10,6 +10,10 @@ using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Storyboards;
|
||||
using OpenTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.IO.File;
|
||||
|
||||
namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
@ -238,42 +242,208 @@ namespace osu.Game.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEvents(Beatmap beatmap, string line)
|
||||
private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spriteDefinition, ref CommandTimelineGroup timelineGroup)
|
||||
{
|
||||
var depth = 0;
|
||||
while (line.StartsWith(" ") || line.StartsWith("_"))
|
||||
{
|
||||
++depth;
|
||||
line = line.Substring(depth);
|
||||
}
|
||||
|
||||
decodeVariables(ref line);
|
||||
|
||||
string[] split = line.Split(',');
|
||||
|
||||
EventType type;
|
||||
if (!Enum.TryParse(split[0], out type))
|
||||
throw new InvalidDataException($@"Unknown event type {split[0]}");
|
||||
|
||||
// Todo: Implement the rest
|
||||
switch (type)
|
||||
if (depth == 0)
|
||||
{
|
||||
case EventType.Video:
|
||||
case EventType.Background:
|
||||
string filename = split[2].Trim('"');
|
||||
spriteDefinition = null;
|
||||
|
||||
if (type == EventType.Background)
|
||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
|
||||
EventType type;
|
||||
if (!Enum.TryParse(split[0], out type))
|
||||
throw new InvalidDataException($@"Unknown event type {split[0]}");
|
||||
|
||||
break;
|
||||
case EventType.Break:
|
||||
var breakEvent = new BreakPeriod
|
||||
{
|
||||
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
|
||||
EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
|
||||
};
|
||||
switch (type)
|
||||
{
|
||||
case EventType.Video:
|
||||
case EventType.Background:
|
||||
string filename = split[2].Trim('"');
|
||||
|
||||
if (!breakEvent.HasEffect)
|
||||
return;
|
||||
if (type == EventType.Background)
|
||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
|
||||
|
||||
beatmap.Breaks.Add(breakEvent);
|
||||
break;
|
||||
break;
|
||||
case EventType.Break:
|
||||
var breakEvent = new BreakPeriod
|
||||
{
|
||||
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
|
||||
EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
|
||||
};
|
||||
|
||||
if (!breakEvent.HasEffect)
|
||||
return;
|
||||
|
||||
beatmap.Breaks.Add(breakEvent);
|
||||
break;
|
||||
case EventType.Sprite:
|
||||
{
|
||||
var layer = split[1];
|
||||
var origin = (Anchor)Enum.Parse(typeof(Anchor), split[2]);
|
||||
var path = cleanFilename(split[3]);
|
||||
var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
|
||||
var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
|
||||
spriteDefinition = new SpriteDefinition(path, origin, new Vector2(x, y));
|
||||
beatmap.Storyboard.GetLayer(layer).Add(spriteDefinition);
|
||||
}
|
||||
break;
|
||||
case EventType.Animation:
|
||||
{
|
||||
var layer = split[1];
|
||||
var origin = (Anchor)Enum.Parse(typeof(Anchor), split[2]);
|
||||
var path = cleanFilename(split[3]);
|
||||
var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
|
||||
var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
|
||||
var frameCount = int.Parse(split[6]);
|
||||
var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo);
|
||||
var loopType = (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]);
|
||||
spriteDefinition = new AnimationDefinition(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType);
|
||||
beatmap.Storyboard.GetLayer(layer).Add(spriteDefinition);
|
||||
}
|
||||
break;
|
||||
case EventType.Sample:
|
||||
{
|
||||
var time = double.Parse(split[1], CultureInfo.InvariantCulture);
|
||||
var layer = split[2];
|
||||
var path = cleanFilename(split[3]);
|
||||
var volume = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||
beatmap.Storyboard.GetLayer(layer).Add(new SampleDefinition(path, time, volume));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (depth < 2)
|
||||
timelineGroup = spriteDefinition;
|
||||
|
||||
switch (split[0])
|
||||
{
|
||||
case "T":
|
||||
{
|
||||
var triggerName = split[1];
|
||||
var startTime = double.Parse(split[2], CultureInfo.InvariantCulture);
|
||||
var endTime = double.Parse(split[3], CultureInfo.InvariantCulture);
|
||||
var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0;
|
||||
timelineGroup = spriteDefinition?.AddTrigger(triggerName, startTime, endTime, groupNumber);
|
||||
}
|
||||
break;
|
||||
case "L":
|
||||
{
|
||||
var startTime = double.Parse(split[1], CultureInfo.InvariantCulture);
|
||||
var loopCount = int.Parse(split[2]);
|
||||
timelineGroup = spriteDefinition?.AddLoop(startTime, loopCount);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
if (string.IsNullOrEmpty(split[3]))
|
||||
split[3] = split[2];
|
||||
|
||||
var commandType = split[0];
|
||||
var easing = (Easing)int.Parse(split[1]);
|
||||
var startTime = double.Parse(split[2], CultureInfo.InvariantCulture);
|
||||
var endTime = double.Parse(split[3], CultureInfo.InvariantCulture);
|
||||
|
||||
switch (commandType)
|
||||
{
|
||||
case "F":
|
||||
{
|
||||
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
||||
timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
|
||||
}
|
||||
break;
|
||||
case "S":
|
||||
{
|
||||
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
||||
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue));
|
||||
}
|
||||
break;
|
||||
case "V":
|
||||
{
|
||||
var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||
var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
|
||||
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
|
||||
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
|
||||
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(startX, endY));
|
||||
}
|
||||
break;
|
||||
case "R":
|
||||
{
|
||||
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
||||
timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue));
|
||||
}
|
||||
break;
|
||||
case "M":
|
||||
{
|
||||
var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||
var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
|
||||
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
|
||||
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
|
||||
timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
|
||||
timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
|
||||
}
|
||||
break;
|
||||
case "MX":
|
||||
{
|
||||
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
||||
timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
|
||||
}
|
||||
break;
|
||||
case "MY":
|
||||
{
|
||||
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
||||
timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
|
||||
}
|
||||
break;
|
||||
case "C":
|
||||
{
|
||||
var startRed = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||
var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture);
|
||||
var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture);
|
||||
var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed;
|
||||
var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen;
|
||||
var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue;
|
||||
timelineGroup?.Colour.Add(easing, startTime, endTime,
|
||||
new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
|
||||
new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
|
||||
}
|
||||
break;
|
||||
case "P":
|
||||
{
|
||||
var type = split[4];
|
||||
switch (type)
|
||||
{
|
||||
case "A": timelineGroup?.Additive.Add(easing, startTime, endTime, true, true); break;
|
||||
case "H": timelineGroup?.FlipH.Add(easing, startTime, endTime, true, true); break;
|
||||
case "V": timelineGroup?.FlipV.Add(easing, startTime, endTime, true, true); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string cleanFilename(string path)
|
||||
=> FileSafety.PathStandardise(path.Trim('\"'));
|
||||
|
||||
private void handleTimingPoints(Beatmap beatmap, string line)
|
||||
{
|
||||
string[] split = line.Split(',');
|
||||
@ -414,6 +584,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
Section section = Section.None;
|
||||
bool hasCustomColours = false;
|
||||
SpriteDefinition spriteDefinition = null;
|
||||
CommandTimelineGroup timelineGroup = null;
|
||||
|
||||
string line;
|
||||
while ((line = stream.ReadLine()) != null)
|
||||
@ -421,7 +593,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (string.IsNullOrEmpty(line))
|
||||
continue;
|
||||
|
||||
if (line.StartsWith(" ") || line.StartsWith("_") || line.StartsWith("//"))
|
||||
if (line.StartsWith("//"))
|
||||
continue;
|
||||
|
||||
if (line.StartsWith(@"osu file format v"))
|
||||
@ -452,7 +624,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
handleDifficulty(beatmap, line);
|
||||
break;
|
||||
case Section.Events:
|
||||
handleEvents(beatmap, line);
|
||||
handleEvents(beatmap, line, ref spriteDefinition, ref timelineGroup);
|
||||
break;
|
||||
case Section.TimingPoints:
|
||||
handleTimingPoints(beatmap, line);
|
||||
|
33
osu.Game/Storyboards/AnimationDefinition.cs
Normal file
33
osu.Game/Storyboards/AnimationDefinition.cs
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class AnimationDefinition : SpriteDefinition
|
||||
{
|
||||
public int FrameCount;
|
||||
public double FrameDelay;
|
||||
public AnimationLoopType LoopType;
|
||||
|
||||
public AnimationDefinition(string path, Anchor origin, Vector2 initialPosition, int frameCount, double frameDelay, AnimationLoopType loopType)
|
||||
: base(path, origin, initialPosition)
|
||||
{
|
||||
FrameCount = frameCount;
|
||||
FrameDelay = frameDelay;
|
||||
LoopType = loopType;
|
||||
}
|
||||
|
||||
public override Drawable CreateDrawable()
|
||||
=> new StoryboardAnimation(this);
|
||||
}
|
||||
|
||||
public enum AnimationLoopType
|
||||
{
|
||||
LoopForever,
|
||||
LoopOnce,
|
||||
}
|
||||
}
|
27
osu.Game/Storyboards/CommandLoop.cs
Normal file
27
osu.Game/Storyboards/CommandLoop.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class CommandLoop : CommandTimelineGroup
|
||||
{
|
||||
private double startTime;
|
||||
private int loopCount;
|
||||
|
||||
public CommandLoop(double startTime, int loopCount)
|
||||
{
|
||||
this.startTime = startTime;
|
||||
this.loopCount = loopCount;
|
||||
}
|
||||
|
||||
public override void ApplyTransforms(Drawable drawable)
|
||||
{
|
||||
//base.ApplyTransforms(drawable);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{startTime} x{loopCount}";
|
||||
}
|
||||
}
|
61
osu.Game/Storyboards/CommandTimeline.cs
Normal file
61
osu.Game/Storyboards/CommandTimeline.cs
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class CommandTimeline<T> : CommandTimeline
|
||||
{
|
||||
private readonly List<Command> commands = new List<Command>();
|
||||
public IEnumerable<Command> Commands => commands.OrderBy(c => c.StartTime);
|
||||
public bool HasCommands => commands.Count > 0;
|
||||
|
||||
private Cached<double> startTimeBacking;
|
||||
public double StartTime => startTimeBacking.IsValid ? startTimeBacking : (startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue);
|
||||
|
||||
private Cached<double> endTimeBacking;
|
||||
public double EndTime => endTimeBacking.IsValid ? endTimeBacking : (endTimeBacking.Value = HasCommands ? commands.Max(c => c.EndTime) : double.MaxValue);
|
||||
|
||||
public T StartValue => HasCommands ? commands.OrderBy(c => c.StartTime).First().StartValue : default(T);
|
||||
public T EndValue => HasCommands ? commands.OrderByDescending(c => c.EndTime).First().EndValue : default(T);
|
||||
|
||||
public void Add(Easing easing, double startTime, double endTime, T startValue, T endValue)
|
||||
{
|
||||
if (endTime < startTime)
|
||||
return;
|
||||
|
||||
commands.Add(new Command { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue, });
|
||||
|
||||
startTimeBacking.Invalidate();
|
||||
endTimeBacking.Invalidate();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{commands.Count} command(s)";
|
||||
|
||||
public class Command
|
||||
{
|
||||
public Easing Easing;
|
||||
public double StartTime;
|
||||
public double EndTime;
|
||||
public T StartValue;
|
||||
public T EndValue;
|
||||
|
||||
public double Duration => EndTime - StartTime;
|
||||
|
||||
public override string ToString()
|
||||
=> $"{StartTime} -> {EndTime}, {StartValue} -> {EndValue} {Easing}";
|
||||
}
|
||||
}
|
||||
|
||||
public interface CommandTimeline
|
||||
{
|
||||
double StartTime { get; }
|
||||
double EndTime { get; }
|
||||
bool HasCommands { get; }
|
||||
}
|
||||
}
|
94
osu.Game/Storyboards/CommandTimelineGroup.cs
Normal file
94
osu.Game/Storyboards/CommandTimelineGroup.cs
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class CommandTimelineGroup
|
||||
{
|
||||
public CommandTimeline<float> X = new CommandTimeline<float>();
|
||||
public CommandTimeline<float> Y = new CommandTimeline<float>();
|
||||
public CommandTimeline<Vector2> Scale = new CommandTimeline<Vector2>();
|
||||
public CommandTimeline<float> Rotation = new CommandTimeline<float>();
|
||||
public CommandTimeline<Color4> Colour = new CommandTimeline<Color4>();
|
||||
public CommandTimeline<float> Alpha = new CommandTimeline<float>();
|
||||
public CommandTimeline<bool> Additive = new CommandTimeline<bool>();
|
||||
public CommandTimeline<bool> FlipH = new CommandTimeline<bool>();
|
||||
public CommandTimeline<bool> FlipV = new CommandTimeline<bool>();
|
||||
|
||||
public IEnumerable<CommandTimeline> Timelines
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return X;
|
||||
yield return Y;
|
||||
yield return Scale;
|
||||
yield return Rotation;
|
||||
yield return Colour;
|
||||
yield return Alpha;
|
||||
yield return Additive;
|
||||
yield return FlipH;
|
||||
yield return FlipV;
|
||||
}
|
||||
}
|
||||
|
||||
public double StartTime => Timelines.Where(t => t.HasCommands).Min(t => t.StartTime);
|
||||
public double EndTime => Timelines.Where(t => t.HasCommands).Max(t => t.EndTime);
|
||||
public bool HasCommands => Timelines.Any(t => t.HasCommands);
|
||||
|
||||
public virtual void ApplyTransforms(Drawable drawable)
|
||||
{
|
||||
if (X.HasCommands) drawable.X = X.StartValue;
|
||||
foreach (var command in X.Commands)
|
||||
using (drawable.BeginAbsoluteSequence(command.StartTime))
|
||||
drawable.MoveToX(command.StartValue)
|
||||
.MoveToX(command.EndValue, command.Duration, command.Easing);
|
||||
|
||||
if (Y.HasCommands) drawable.Y = Y.StartValue;
|
||||
foreach (var command in Y.Commands)
|
||||
using (drawable.BeginAbsoluteSequence(command.StartTime))
|
||||
drawable.MoveToY(command.StartValue)
|
||||
.MoveToY(command.EndValue, command.Duration, command.Easing);
|
||||
|
||||
if (Scale.HasCommands) drawable.Scale = Scale.StartValue;
|
||||
foreach (var command in Scale.Commands)
|
||||
using (drawable.BeginAbsoluteSequence(command.StartTime))
|
||||
drawable.ScaleTo(command.StartValue)
|
||||
.ScaleTo(command.EndValue, command.Duration, command.Easing);
|
||||
|
||||
if (Rotation.HasCommands) drawable.Rotation = Rotation.StartValue;
|
||||
foreach (var command in Rotation.Commands)
|
||||
using (drawable.BeginAbsoluteSequence(command.StartTime))
|
||||
drawable.RotateTo(command.StartValue)
|
||||
.RotateTo(command.EndValue, command.Duration, command.Easing);
|
||||
|
||||
if (Colour.HasCommands) drawable.Colour = Colour.StartValue;
|
||||
foreach (var command in Colour.Commands)
|
||||
using (drawable.BeginAbsoluteSequence(command.StartTime))
|
||||
drawable.FadeColour(command.StartValue)
|
||||
.FadeColour(command.EndValue, command.Duration, command.Easing);
|
||||
|
||||
if (Alpha.HasCommands) drawable.Alpha = Alpha.StartValue;
|
||||
foreach (var command in Alpha.Commands)
|
||||
using (drawable.BeginAbsoluteSequence(command.StartTime))
|
||||
drawable.FadeTo(command.StartValue)
|
||||
.FadeTo(command.EndValue, command.Duration, command.Easing);
|
||||
|
||||
if (Additive.HasCommands)
|
||||
drawable.BlendingMode = BlendingMode.Additive;
|
||||
|
||||
var flippable = drawable as IFlippable;
|
||||
if (flippable != null)
|
||||
{
|
||||
flippable.FlipH = FlipH.HasCommands;
|
||||
flippable.FlipV = FlipV.HasCommands;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
24
osu.Game/Storyboards/CommandTrigger.cs
Normal file
24
osu.Game/Storyboards/CommandTrigger.cs
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class CommandTrigger : CommandTimelineGroup
|
||||
{
|
||||
private string triggerName;
|
||||
private double startTime;
|
||||
private double endTime;
|
||||
private int groupNumber;
|
||||
|
||||
public CommandTrigger(string triggerName, double startTime, double endTime, int groupNumber)
|
||||
{
|
||||
this.triggerName = triggerName;
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
this.groupNumber = groupNumber;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{triggerName} {startTime} -> {endTime} ({groupNumber})";
|
||||
}
|
||||
}
|
11
osu.Game/Storyboards/Drawables/IFlippable.cs
Normal file
11
osu.Game/Storyboards/Drawables/IFlippable.cs
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
public interface IFlippable
|
||||
{
|
||||
bool FlipH { get; set; }
|
||||
bool FlipV { get; set; }
|
||||
}
|
||||
}
|
59
osu.Game/Storyboards/Drawables/Storyboard.cs
Normal file
59
osu.Game/Storyboards/Drawables/Storyboard.cs
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.IO;
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
public class Storyboard : Container<StoryboardLayer>
|
||||
{
|
||||
public StoryboardDefinition Definition { get; private set; }
|
||||
|
||||
protected override Vector2 DrawScale => new Vector2(Parent.DrawHeight / 480);
|
||||
public override bool HandleInput => false;
|
||||
|
||||
private bool passing = true;
|
||||
public bool Passing
|
||||
{
|
||||
get { return passing; }
|
||||
set
|
||||
{
|
||||
if (passing == value) return;
|
||||
passing = value;
|
||||
updateLayerVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private DependencyContainer dependencies;
|
||||
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) =>
|
||||
dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
|
||||
|
||||
public Storyboard(StoryboardDefinition definition)
|
||||
{
|
||||
Definition = definition;
|
||||
Size = new Vector2(640, 480);
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(FileStore fileStore)
|
||||
{
|
||||
dependencies.Cache(new TextureStore(new RawTextureLoaderStore(fileStore.Store), false) { ScaleAdjust = 1, });
|
||||
|
||||
foreach (var layerDefinition in Definition.Layers)
|
||||
Add(layerDefinition.CreateDrawable());
|
||||
}
|
||||
|
||||
private void updateLayerVisibility()
|
||||
{
|
||||
foreach (var layer in Children)
|
||||
layer.Enabled = passing ? layer.Definition.EnabledWhenPassing : layer.Definition.ShowWhenFailing;
|
||||
}
|
||||
}
|
||||
}
|
55
osu.Game/Storyboards/Drawables/StoryboardAnimation.cs
Normal file
55
osu.Game/Storyboards/Drawables/StoryboardAnimation.cs
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.File;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
public class StoryboardAnimation : TextureAnimation, IFlippable
|
||||
{
|
||||
public AnimationDefinition Definition { get; private set; }
|
||||
|
||||
protected override bool ShouldBeAlive => Definition.HasCommands && base.ShouldBeAlive;
|
||||
public override bool RemoveWhenNotAlive => !Definition.HasCommands || base.RemoveWhenNotAlive;
|
||||
|
||||
public bool FlipH { get; set; }
|
||||
public bool FlipV { get; set; }
|
||||
protected override Vector2 DrawScale => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y);
|
||||
|
||||
public StoryboardAnimation(AnimationDefinition definition)
|
||||
{
|
||||
Definition = definition;
|
||||
Origin = definition.Origin;
|
||||
Position = definition.InitialPosition;
|
||||
Repeat = definition.LoopType == AnimationLoopType.LoopForever;
|
||||
|
||||
if (definition.HasCommands)
|
||||
{
|
||||
LifetimeStart = definition.StartTime;
|
||||
LifetimeEnd = definition.EndTime;
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGameBase game, TextureStore textureStore)
|
||||
{
|
||||
for (var frame = 0; frame < Definition.FrameCount; frame++)
|
||||
{
|
||||
var framePath = Definition.Path.Replace(".", frame + ".");
|
||||
|
||||
var path = game.Beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename == framePath)?.FileInfo.StoragePath;
|
||||
if (path == null)
|
||||
continue;
|
||||
|
||||
var texture = textureStore.Get(path);
|
||||
AddFrame(texture, Definition.FrameDelay);
|
||||
}
|
||||
Definition.ApplyTransforms(this);
|
||||
}
|
||||
}
|
||||
}
|
37
osu.Game/Storyboards/Drawables/StoryboardLayer.cs
Normal file
37
osu.Game/Storyboards/Drawables/StoryboardLayer.cs
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2007-2017 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;
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
public class StoryboardLayer : Container
|
||||
{
|
||||
public LayerDefinition Definition { get; private set; }
|
||||
public bool Enabled;
|
||||
|
||||
public override bool IsPresent => Enabled && base.IsPresent;
|
||||
|
||||
public StoryboardLayer(LayerDefinition definition)
|
||||
{
|
||||
Definition = definition;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
Enabled = definition.EnabledWhenPassing;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
foreach (var element in Definition.Elements)
|
||||
{
|
||||
var drawable = element.CreateDrawable();
|
||||
if (drawable != null)
|
||||
Add(drawable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
osu.Game/Storyboards/Drawables/StoryboardSprite.cs
Normal file
49
osu.Game/Storyboards/Drawables/StoryboardSprite.cs
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.File;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
public class StoryboardSprite : Sprite, IFlippable
|
||||
{
|
||||
public SpriteDefinition Definition { get; private set; }
|
||||
|
||||
protected override bool ShouldBeAlive => Definition.HasCommands && base.ShouldBeAlive;
|
||||
public override bool RemoveWhenNotAlive => !Definition.HasCommands || base.RemoveWhenNotAlive;
|
||||
|
||||
public bool FlipH { get; set; }
|
||||
public bool FlipV { get; set; }
|
||||
protected override Vector2 DrawScale => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y);
|
||||
|
||||
public StoryboardSprite(SpriteDefinition definition)
|
||||
{
|
||||
Definition = definition;
|
||||
Origin = definition.Origin;
|
||||
Position = definition.InitialPosition;
|
||||
|
||||
if (definition.HasCommands)
|
||||
{
|
||||
LifetimeStart = definition.StartTime;
|
||||
LifetimeEnd = definition.EndTime;
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGameBase game, TextureStore textureStore)
|
||||
{
|
||||
var spritePath = Definition.Path;
|
||||
var path = game.Beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename == spritePath)?.FileInfo.StoragePath;
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
Texture = textureStore.Get(path);
|
||||
Definition.ApplyTransforms(this);
|
||||
}
|
||||
}
|
||||
}
|
13
osu.Game/Storyboards/ElementDefinition.cs
Normal file
13
osu.Game/Storyboards/ElementDefinition.cs
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public interface ElementDefinition
|
||||
{
|
||||
string Path { get; }
|
||||
Drawable CreateDrawable();
|
||||
}
|
||||
}
|
33
osu.Game/Storyboards/LayerDefinition.cs
Normal file
33
osu.Game/Storyboards/LayerDefinition.cs
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class LayerDefinition
|
||||
{
|
||||
public string Name;
|
||||
public int Depth;
|
||||
public bool EnabledWhenPassing = true;
|
||||
public bool ShowWhenFailing = true;
|
||||
|
||||
private List<ElementDefinition> elements = new List<ElementDefinition>();
|
||||
public IEnumerable<ElementDefinition> Elements => elements;
|
||||
|
||||
public LayerDefinition(string name, int depth)
|
||||
{
|
||||
Name = name;
|
||||
Depth = depth;
|
||||
}
|
||||
|
||||
public void Add(ElementDefinition element)
|
||||
{
|
||||
elements.Add(element);
|
||||
}
|
||||
|
||||
public StoryboardLayer CreateDrawable()
|
||||
=> new StoryboardLayer(this) { Depth = Depth, };
|
||||
}
|
||||
}
|
24
osu.Game/Storyboards/SampleDefinition.cs
Normal file
24
osu.Game/Storyboards/SampleDefinition.cs
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class SampleDefinition : ElementDefinition
|
||||
{
|
||||
public string Path { get; private set; }
|
||||
public double Time;
|
||||
public float Volume;
|
||||
|
||||
public SampleDefinition(string path, double time, float volume)
|
||||
{
|
||||
Path = path;
|
||||
Time = time;
|
||||
Volume = volume;
|
||||
}
|
||||
|
||||
public Drawable CreateDrawable()
|
||||
=> null;
|
||||
}
|
||||
}
|
59
osu.Game/Storyboards/SpriteDefinition.cs
Normal file
59
osu.Game/Storyboards/SpriteDefinition.cs
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class SpriteDefinition : CommandTimelineGroup, ElementDefinition
|
||||
{
|
||||
public string Path { get; private set; }
|
||||
public Anchor Origin;
|
||||
public Vector2 InitialPosition;
|
||||
|
||||
private List<CommandLoop> loops = new List<CommandLoop>();
|
||||
private List<CommandTrigger> triggers = new List<CommandTrigger>();
|
||||
|
||||
public SpriteDefinition(string path, Anchor origin, Vector2 initialPosition)
|
||||
{
|
||||
Path = path;
|
||||
Origin = origin;
|
||||
InitialPosition = initialPosition;
|
||||
}
|
||||
|
||||
public CommandLoop AddLoop(double startTime, int loopCount)
|
||||
{
|
||||
var loop = new CommandLoop(startTime, loopCount);
|
||||
loops.Add(loop);
|
||||
return loop;
|
||||
}
|
||||
|
||||
public CommandTrigger AddTrigger(string triggerName, double startTime, double endTime, int groupNumber)
|
||||
{
|
||||
var trigger = new CommandTrigger(triggerName, startTime, endTime, groupNumber);
|
||||
triggers.Add(trigger);
|
||||
return trigger;
|
||||
}
|
||||
|
||||
public virtual Drawable CreateDrawable()
|
||||
=> new StoryboardSprite(this);
|
||||
|
||||
public override void ApplyTransforms(Drawable target)
|
||||
{
|
||||
base.ApplyTransforms(target);
|
||||
foreach (var loop in loops)
|
||||
loop.ApplyTransforms(target);
|
||||
|
||||
// TODO
|
||||
return;
|
||||
foreach (var trigger in triggers)
|
||||
trigger.ApplyTransforms(target);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Path}, {Origin}, {InitialPosition}";
|
||||
}
|
||||
}
|
36
osu.Game/Storyboards/StoryboardDefinition.cs
Normal file
36
osu.Game/Storyboards/StoryboardDefinition.cs
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class StoryboardDefinition
|
||||
{
|
||||
private Dictionary<string, LayerDefinition> layers = new Dictionary<string, LayerDefinition>();
|
||||
public IEnumerable<LayerDefinition> Layers => layers.Values;
|
||||
|
||||
public StoryboardDefinition()
|
||||
{
|
||||
layers.Add("Background", new LayerDefinition("Background", 3));
|
||||
layers.Add("Fail", new LayerDefinition("Fail", 2) { EnabledWhenPassing = false, });
|
||||
layers.Add("Pass", new LayerDefinition("Pass", 1) { ShowWhenFailing = false, });
|
||||
layers.Add("Foreground", new LayerDefinition("Foreground", 0));
|
||||
}
|
||||
|
||||
public LayerDefinition GetLayer(string name)
|
||||
{
|
||||
LayerDefinition layer;
|
||||
if (!layers.TryGetValue(name, out layer))
|
||||
layers[name] = layer = new LayerDefinition(name, layers.Values.Min(l => l.Depth) - 1);
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
public Storyboard CreateDrawable()
|
||||
=> new Storyboard(this);
|
||||
}
|
||||
}
|
@ -80,6 +80,21 @@
|
||||
<Compile Include="Beatmaps\DifficultyCalculator.cs" />
|
||||
<Compile Include="Beatmaps\DummyWorkingBeatmap.cs" />
|
||||
<Compile Include="Beatmaps\IO\LegacyFilesystemReader.cs" />
|
||||
<Compile Include="Storyboards\Drawables\IFlippable.cs" />
|
||||
<Compile Include="Storyboards\Drawables\StoryboardLayer.cs" />
|
||||
<Compile Include="Storyboards\Drawables\Storyboard.cs" />
|
||||
<Compile Include="Storyboards\Drawables\StoryboardAnimation.cs" />
|
||||
<Compile Include="Storyboards\Drawables\StoryboardSprite.cs" />
|
||||
<Compile Include="Storyboards\AnimationDefinition.cs" />
|
||||
<Compile Include="Storyboards\ElementDefinition.cs" />
|
||||
<Compile Include="Storyboards\CommandTimeline.cs" />
|
||||
<Compile Include="Storyboards\CommandTimelineGroup.cs" />
|
||||
<Compile Include="Storyboards\CommandTrigger.cs" />
|
||||
<Compile Include="Storyboards\CommandLoop.cs" />
|
||||
<Compile Include="Storyboards\SampleDefinition.cs" />
|
||||
<Compile Include="Storyboards\SpriteDefinition.cs" />
|
||||
<Compile Include="Storyboards\LayerDefinition.cs" />
|
||||
<Compile Include="Storyboards\StoryboardDefinition.cs" />
|
||||
<Compile Include="Database\StoreVersion.cs" />
|
||||
<Compile Include="Graphics\Containers\OsuClickableContainer.cs" />
|
||||
<Compile Include="Graphics\Containers\OsuFocusedOverlayContainer.cs" />
|
||||
|
Loading…
Reference in New Issue
Block a user