From e54741619331b81a002a3d58cbc520e35a5ccf64 Mon Sep 17 00:00:00 2001 From: Damnae Date: Thu, 7 Sep 2017 23:55:05 +0200 Subject: [PATCH 01/18] Storyboards implementation. --- .../Visual/TestCaseStoryboard.cs | 91 +++++++ osu.Desktop.Tests/osu.Desktop.Tests.csproj | 1 + osu.Game/Beatmaps/Beatmap.cs | 6 + osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 222 ++++++++++++++++-- osu.Game/Storyboards/AnimationDefinition.cs | 33 +++ osu.Game/Storyboards/CommandLoop.cs | 27 +++ osu.Game/Storyboards/CommandTimeline.cs | 61 +++++ osu.Game/Storyboards/CommandTimelineGroup.cs | 94 ++++++++ osu.Game/Storyboards/CommandTrigger.cs | 24 ++ osu.Game/Storyboards/Drawables/IFlippable.cs | 11 + osu.Game/Storyboards/Drawables/Storyboard.cs | 59 +++++ .../Drawables/StoryboardAnimation.cs | 55 +++++ .../Storyboards/Drawables/StoryboardLayer.cs | 37 +++ .../Storyboards/Drawables/StoryboardSprite.cs | 49 ++++ osu.Game/Storyboards/ElementDefinition.cs | 13 + osu.Game/Storyboards/LayerDefinition.cs | 33 +++ osu.Game/Storyboards/SampleDefinition.cs | 24 ++ osu.Game/Storyboards/SpriteDefinition.cs | 59 +++++ osu.Game/Storyboards/StoryboardDefinition.cs | 36 +++ osu.Game/osu.Game.csproj | 15 ++ 20 files changed, 925 insertions(+), 25 deletions(-) create mode 100644 osu.Desktop.Tests/Visual/TestCaseStoryboard.cs create mode 100644 osu.Game/Storyboards/AnimationDefinition.cs create mode 100644 osu.Game/Storyboards/CommandLoop.cs create mode 100644 osu.Game/Storyboards/CommandTimeline.cs create mode 100644 osu.Game/Storyboards/CommandTimelineGroup.cs create mode 100644 osu.Game/Storyboards/CommandTrigger.cs create mode 100644 osu.Game/Storyboards/Drawables/IFlippable.cs create mode 100644 osu.Game/Storyboards/Drawables/Storyboard.cs create mode 100644 osu.Game/Storyboards/Drawables/StoryboardAnimation.cs create mode 100644 osu.Game/Storyboards/Drawables/StoryboardLayer.cs create mode 100644 osu.Game/Storyboards/Drawables/StoryboardSprite.cs create mode 100644 osu.Game/Storyboards/ElementDefinition.cs create mode 100644 osu.Game/Storyboards/LayerDefinition.cs create mode 100644 osu.Game/Storyboards/SampleDefinition.cs create mode 100644 osu.Game/Storyboards/SpriteDefinition.cs create mode 100644 osu.Game/Storyboards/StoryboardDefinition.cs diff --git a/osu.Desktop.Tests/Visual/TestCaseStoryboard.cs b/osu.Desktop.Tests/Visual/TestCaseStoryboard.cs new file mode 100644 index 000000000000..1d7b3dffd275 --- /dev/null +++ b/osu.Desktop.Tests/Visual/TestCaseStoryboard.cs @@ -0,0 +1,91 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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 beatmapBacking = new Bindable(); + + private MusicController musicController; + private Container 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 + { + 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; + } + } +} diff --git a/osu.Desktop.Tests/osu.Desktop.Tests.csproj b/osu.Desktop.Tests/osu.Desktop.Tests.csproj index 86268e6110c0..6bd190a1f683 100644 --- a/osu.Desktop.Tests/osu.Desktop.Tests.csproj +++ b/osu.Desktop.Tests/osu.Desktop.Tests.csproj @@ -91,6 +91,7 @@ + diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 82777734bbd2..e4568a19195b 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -8,6 +8,7 @@ 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 @@ public class Beatmap /// public double TotalBreakTime => Breaks.Sum(b => b.Duration); + /// + /// The Beatmap's Storyboard. + /// + public StoryboardDefinition Storyboard = new StoryboardDefinition(); + /// /// Constructs a new beatmap. /// diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index b51ea607ddcb..08e1d0662172 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -10,6 +10,10 @@ 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 @@ private void decodeVariables(ref string line) } } - 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 @@ protected override void ParseFile(StreamReader stream, Beatmap beatmap) Section section = Section.None; bool hasCustomColours = false; + SpriteDefinition spriteDefinition = null; + CommandTimelineGroup timelineGroup = null; string line; while ((line = stream.ReadLine()) != null) @@ -421,7 +593,7 @@ protected override void ParseFile(StreamReader stream, Beatmap beatmap) 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 @@ protected override void ParseFile(StreamReader stream, Beatmap beatmap) handleDifficulty(beatmap, line); break; case Section.Events: - handleEvents(beatmap, line); + handleEvents(beatmap, line, ref spriteDefinition, ref timelineGroup); break; case Section.TimingPoints: handleTimingPoints(beatmap, line); diff --git a/osu.Game/Storyboards/AnimationDefinition.cs b/osu.Game/Storyboards/AnimationDefinition.cs new file mode 100644 index 000000000000..3ac9cbfe0ee1 --- /dev/null +++ b/osu.Game/Storyboards/AnimationDefinition.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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, + } +} diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs new file mode 100644 index 000000000000..b93a04cea1e5 --- /dev/null +++ b/osu.Game/Storyboards/CommandLoop.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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}"; + } +} diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs new file mode 100644 index 000000000000..04e165aa3804 --- /dev/null +++ b/osu.Game/Storyboards/CommandTimeline.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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 : CommandTimeline + { + private readonly List commands = new List(); + public IEnumerable Commands => commands.OrderBy(c => c.StartTime); + public bool HasCommands => commands.Count > 0; + + private Cached startTimeBacking; + public double StartTime => startTimeBacking.IsValid ? startTimeBacking : (startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue); + + private Cached 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; } + } +} diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs new file mode 100644 index 000000000000..cc67c9dd6883 --- /dev/null +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -0,0 +1,94 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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 X = new CommandTimeline(); + public CommandTimeline Y = new CommandTimeline(); + public CommandTimeline Scale = new CommandTimeline(); + public CommandTimeline Rotation = new CommandTimeline(); + public CommandTimeline Colour = new CommandTimeline(); + public CommandTimeline Alpha = new CommandTimeline(); + public CommandTimeline Additive = new CommandTimeline(); + public CommandTimeline FlipH = new CommandTimeline(); + public CommandTimeline FlipV = new CommandTimeline(); + + public IEnumerable 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; + } + } + } +} diff --git a/osu.Game/Storyboards/CommandTrigger.cs b/osu.Game/Storyboards/CommandTrigger.cs new file mode 100644 index 000000000000..e7133e170fef --- /dev/null +++ b/osu.Game/Storyboards/CommandTrigger.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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})"; + } +} diff --git a/osu.Game/Storyboards/Drawables/IFlippable.cs b/osu.Game/Storyboards/Drawables/IFlippable.cs new file mode 100644 index 000000000000..a1899d141121 --- /dev/null +++ b/osu.Game/Storyboards/Drawables/IFlippable.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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; } + } +} diff --git a/osu.Game/Storyboards/Drawables/Storyboard.cs b/osu.Game/Storyboards/Drawables/Storyboard.cs new file mode 100644 index 000000000000..a5b242357f40 --- /dev/null +++ b/osu.Game/Storyboards/Drawables/Storyboard.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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 + { + 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; + } + } +} diff --git a/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs new file mode 100644 index 000000000000..583a0d13c38d --- /dev/null +++ b/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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); + } + } +} diff --git a/osu.Game/Storyboards/Drawables/StoryboardLayer.cs b/osu.Game/Storyboards/Drawables/StoryboardLayer.cs new file mode 100644 index 000000000000..48532e041881 --- /dev/null +++ b/osu.Game/Storyboards/Drawables/StoryboardLayer.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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); + } + } + } +} diff --git a/osu.Game/Storyboards/Drawables/StoryboardSprite.cs b/osu.Game/Storyboards/Drawables/StoryboardSprite.cs new file mode 100644 index 000000000000..927996d75096 --- /dev/null +++ b/osu.Game/Storyboards/Drawables/StoryboardSprite.cs @@ -0,0 +1,49 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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); + } + } +} diff --git a/osu.Game/Storyboards/ElementDefinition.cs b/osu.Game/Storyboards/ElementDefinition.cs new file mode 100644 index 000000000000..1a8de9a120ca --- /dev/null +++ b/osu.Game/Storyboards/ElementDefinition.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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(); + } +} diff --git a/osu.Game/Storyboards/LayerDefinition.cs b/osu.Game/Storyboards/LayerDefinition.cs new file mode 100644 index 000000000000..e632a700f1e2 --- /dev/null +++ b/osu.Game/Storyboards/LayerDefinition.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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 elements = new List(); + public IEnumerable 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, }; + } +} diff --git a/osu.Game/Storyboards/SampleDefinition.cs b/osu.Game/Storyboards/SampleDefinition.cs new file mode 100644 index 000000000000..26d2c4b02989 --- /dev/null +++ b/osu.Game/Storyboards/SampleDefinition.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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; + } +} diff --git a/osu.Game/Storyboards/SpriteDefinition.cs b/osu.Game/Storyboards/SpriteDefinition.cs new file mode 100644 index 000000000000..2574e7adb682 --- /dev/null +++ b/osu.Game/Storyboards/SpriteDefinition.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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 loops = new List(); + private List triggers = new List(); + + 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}"; + } +} diff --git a/osu.Game/Storyboards/StoryboardDefinition.cs b/osu.Game/Storyboards/StoryboardDefinition.cs new file mode 100644 index 000000000000..853ebe42dc75 --- /dev/null +++ b/osu.Game/Storyboards/StoryboardDefinition.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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 layers = new Dictionary(); + public IEnumerable 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); + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 05ba3e25ab3b..2703798a6c64 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -80,6 +80,21 @@ + + + + + + + + + + + + + + + From 13322b4293ad27ed6a672f2492c21b2bf5f5a12e Mon Sep 17 00:00:00 2001 From: Damnae Date: Fri, 8 Sep 2017 12:09:45 +0200 Subject: [PATCH 02/18] Improve compatibility with older storyboards. --- osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 08e1d0662172..bcb2c6f666fe 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -248,7 +248,7 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr while (line.StartsWith(" ") || line.StartsWith("_")) { ++depth; - line = line.Substring(depth); + line = line.Substring(1); } decodeVariables(ref line); @@ -305,7 +305,7 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr 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]); + var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; spriteDefinition = new AnimationDefinition(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); beatmap.Storyboard.GetLayer(layer).Add(spriteDefinition); } @@ -315,7 +315,7 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr 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); + var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; beatmap.Storyboard.GetLayer(layer).Add(new SampleDefinition(path, time, volume)); } break; @@ -326,13 +326,14 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr if (depth < 2) timelineGroup = spriteDefinition; - switch (split[0]) + var commandType = split[0]; + switch (commandType) { case "T": { var triggerName = split[1]; - var startTime = double.Parse(split[2], CultureInfo.InvariantCulture); - var endTime = double.Parse(split[3], CultureInfo.InvariantCulture); + var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue; + var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue; var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0; timelineGroup = spriteDefinition?.AddTrigger(triggerName, startTime, endTime, groupNumber); } @@ -349,7 +350,6 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr 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); @@ -434,6 +434,8 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr } } break; + default: + throw new InvalidDataException($@"Unknown command type: {commandType}"); } } break; From e02b481c6907e17526e53a3edfa9c296583d1934 Mon Sep 17 00:00:00 2001 From: Damnae Date: Fri, 8 Sep 2017 12:11:57 +0200 Subject: [PATCH 03/18] An attempt at implementing storyboard loops. --- osu.Game/Storyboards/CommandLoop.cs | 20 ++++---- osu.Game/Storyboards/CommandTimeline.cs | 28 +++++++---- osu.Game/Storyboards/CommandTimelineGroup.cs | 50 ++++++++++++-------- osu.Game/Storyboards/Drawables/Storyboard.cs | 2 +- osu.Game/Storyboards/LayerDefinition.cs | 4 +- osu.Game/Storyboards/SpriteDefinition.cs | 16 +++---- osu.Game/Storyboards/StoryboardDefinition.cs | 5 +- 7 files changed, 71 insertions(+), 54 deletions(-) diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs index b93a04cea1e5..d8d059aaed57 100644 --- a/osu.Game/Storyboards/CommandLoop.cs +++ b/osu.Game/Storyboards/CommandLoop.cs @@ -2,26 +2,28 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; namespace osu.Game.Storyboards { public class CommandLoop : CommandTimelineGroup { - private double startTime; - private int loopCount; + public double LoopStartTime; + public int LoopCount; public CommandLoop(double startTime, int loopCount) { - this.startTime = startTime; - this.loopCount = loopCount; + LoopStartTime = startTime; + LoopCount = loopCount; } - public override void ApplyTransforms(Drawable drawable) - { - //base.ApplyTransforms(drawable); - } + public override void ApplyTransforms(Drawable drawable, double offset = 0) + => base.ApplyTransforms(drawable, offset + LoopStartTime); + + protected override void PostProcess(Command command, TransformSequence sequence) + => sequence.Loop(Duration - command.Duration, LoopCount); public override string ToString() - => $"{startTime} x{loopCount}"; + => $"{LoopStartTime} x{LoopCount}"; } } diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs index 04e165aa3804..a533f213fc11 100644 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ b/osu.Game/Storyboards/CommandTimeline.cs @@ -10,8 +10,8 @@ namespace osu.Game.Storyboards { public class CommandTimeline : CommandTimeline { - private readonly List commands = new List(); - public IEnumerable Commands => commands.OrderBy(c => c.StartTime); + private readonly List commands = new List(); + public IEnumerable Commands => commands.OrderBy(c => c.StartTime); public bool HasCommands => commands.Count > 0; private Cached startTimeBacking; @@ -19,7 +19,7 @@ public class CommandTimeline : CommandTimeline private Cached 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); @@ -28,7 +28,7 @@ public void Add(Easing easing, double startTime, double endTime, T startValue, T if (endTime < startTime) return; - commands.Add(new Command { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue, }); + commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue, }); startTimeBacking.Invalidate(); endTimeBacking.Invalidate(); @@ -37,16 +37,16 @@ public void Add(Easing easing, double startTime, double endTime, T startValue, T public override string ToString() => $"{commands.Count} command(s)"; - public class Command + public class TypedCommand : Command { - public Easing Easing; - public double StartTime; - public double EndTime; + public Easing Easing { get; set; } + public double StartTime { get; set; } + public double EndTime { get; set; } + public double Duration => EndTime - StartTime; + public T StartValue; public T EndValue; - public double Duration => EndTime - StartTime; - public override string ToString() => $"{StartTime} -> {EndTime}, {StartValue} -> {EndValue} {Easing}"; } @@ -58,4 +58,12 @@ public interface CommandTimeline double EndTime { get; } bool HasCommands { get; } } + + public interface Command + { + Easing Easing { get; set; } + double StartTime { get; set; } + double EndTime { get; set; } + double Duration { get; } + } } diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index cc67c9dd6883..109104b12039 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -4,6 +4,7 @@ using OpenTK; using OpenTK.Graphics; using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; using osu.Game.Storyboards.Drawables; using System.Collections.Generic; using System.Linq; @@ -40,45 +41,52 @@ public IEnumerable Timelines 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 double Duration => EndTime - StartTime; public bool HasCommands => Timelines.Any(t => t.HasCommands); - public virtual void ApplyTransforms(Drawable drawable) + public virtual void ApplyTransforms(Drawable drawable, double offset = 0) { 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); + using (drawable.BeginAbsoluteSequence(offset + command.StartTime)) + PostProcess(command, + 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); + using (drawable.BeginAbsoluteSequence(offset + command.StartTime)) + PostProcess(command, + 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); + using (drawable.BeginAbsoluteSequence(offset + command.StartTime)) + PostProcess(command, + 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); + using (drawable.BeginAbsoluteSequence(offset + command.StartTime)) + PostProcess(command, + 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); + using (drawable.BeginAbsoluteSequence(offset + command.StartTime)) + PostProcess(command, + 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); + using (drawable.BeginAbsoluteSequence(offset + command.StartTime)) + PostProcess(command, + drawable.FadeTo(command.StartValue) + .FadeTo(command.EndValue, command.Duration, command.Easing)); if (Additive.HasCommands) drawable.BlendingMode = BlendingMode.Additive; @@ -90,5 +98,9 @@ public virtual void ApplyTransforms(Drawable drawable) flippable.FlipV = FlipV.HasCommands; } } + + protected virtual void PostProcess(Command command, TransformSequence sequence) + { + } } } diff --git a/osu.Game/Storyboards/Drawables/Storyboard.cs b/osu.Game/Storyboards/Drawables/Storyboard.cs index a5b242357f40..45aa063f7975 100644 --- a/osu.Game/Storyboards/Drawables/Storyboard.cs +++ b/osu.Game/Storyboards/Drawables/Storyboard.cs @@ -53,7 +53,7 @@ private void load(FileStore fileStore) private void updateLayerVisibility() { foreach (var layer in Children) - layer.Enabled = passing ? layer.Definition.EnabledWhenPassing : layer.Definition.ShowWhenFailing; + layer.Enabled = passing ? layer.Definition.EnabledWhenPassing : layer.Definition.EnabledWhenFailing; } } } diff --git a/osu.Game/Storyboards/LayerDefinition.cs b/osu.Game/Storyboards/LayerDefinition.cs index e632a700f1e2..871462c3fd36 100644 --- a/osu.Game/Storyboards/LayerDefinition.cs +++ b/osu.Game/Storyboards/LayerDefinition.cs @@ -11,11 +11,11 @@ public class LayerDefinition public string Name; public int Depth; public bool EnabledWhenPassing = true; - public bool ShowWhenFailing = true; + public bool EnabledWhenFailing = true; private List elements = new List(); public IEnumerable Elements => elements; - + public LayerDefinition(string name, int depth) { Name = name; diff --git a/osu.Game/Storyboards/SpriteDefinition.cs b/osu.Game/Storyboards/SpriteDefinition.cs index 2574e7adb682..b47b6c935ef0 100644 --- a/osu.Game/Storyboards/SpriteDefinition.cs +++ b/osu.Game/Storyboards/SpriteDefinition.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Game.Storyboards.Drawables; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Storyboards { @@ -16,7 +17,7 @@ public class SpriteDefinition : CommandTimelineGroup, ElementDefinition private List loops = new List(); private List triggers = new List(); - + public SpriteDefinition(string path, Anchor origin, Vector2 initialPosition) { Path = path; @@ -41,16 +42,11 @@ public CommandTrigger AddTrigger(string triggerName, double startTime, double en public virtual Drawable CreateDrawable() => new StoryboardSprite(this); - public override void ApplyTransforms(Drawable target) + public override void ApplyTransforms(Drawable target, double offset = 0) { - base.ApplyTransforms(target); - foreach (var loop in loops) - loop.ApplyTransforms(target); - - // TODO - return; - foreach (var trigger in triggers) - trigger.ApplyTransforms(target); + base.ApplyTransforms(target, offset); + foreach (var loop in loops.OrderBy(l => l.StartTime)) + loop.ApplyTransforms(target, offset); } public override string ToString() diff --git a/osu.Game/Storyboards/StoryboardDefinition.cs b/osu.Game/Storyboards/StoryboardDefinition.cs index 853ebe42dc75..e357440bc94d 100644 --- a/osu.Game/Storyboards/StoryboardDefinition.cs +++ b/osu.Game/Storyboards/StoryboardDefinition.cs @@ -4,7 +4,6 @@ using osu.Game.Storyboards.Drawables; using System.Collections.Generic; using System.Linq; -using System; namespace osu.Game.Storyboards { @@ -12,12 +11,12 @@ public class StoryboardDefinition { private Dictionary layers = new Dictionary(); public IEnumerable 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("Pass", new LayerDefinition("Pass", 1) { EnabledWhenFailing = false, }); layers.Add("Foreground", new LayerDefinition("Foreground", 0)); } From 6cde687d87fd27e16fc8754ad3d8d0bdd51cad25 Mon Sep 17 00:00:00 2001 From: Damnae Date: Fri, 8 Sep 2017 12:36:13 +0200 Subject: [PATCH 04/18] Fix V commands parsing. --- osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index bcb2c6f666fe..6fde7263da47 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -376,7 +376,7 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr 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)); + timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); } break; case "R": From e63fb5720c219aafdeaac3f76c3d2f34a8ea3b71 Mon Sep 17 00:00:00 2001 From: Damnae Date: Fri, 8 Sep 2017 13:04:53 +0200 Subject: [PATCH 05/18] Make CI happy. --- osu.Desktop.Tests/Visual/TestCaseStoryboard.cs | 5 ++--- osu.Game/Storyboards/CommandLoop.cs | 2 +- osu.Game/Storyboards/CommandTimeline.cs | 8 ++++---- osu.Game/Storyboards/CommandTimelineGroup.cs | 4 ++-- osu.Game/Storyboards/CommandTrigger.cs | 18 +++++++++--------- .../Drawables/StoryboardAnimation.cs | 1 - .../Storyboards/Drawables/StoryboardSprite.cs | 1 - ...mentDefinition.cs => IElementDefinition.cs} | 2 +- osu.Game/Storyboards/LayerDefinition.cs | 6 +++--- osu.Game/Storyboards/SampleDefinition.cs | 4 ++-- osu.Game/Storyboards/SpriteDefinition.cs | 8 ++++---- osu.Game/osu.Game.csproj | 2 +- 12 files changed, 29 insertions(+), 32 deletions(-) rename osu.Game/Storyboards/{ElementDefinition.cs => IElementDefinition.cs} (84%) diff --git a/osu.Desktop.Tests/Visual/TestCaseStoryboard.cs b/osu.Desktop.Tests/Visual/TestCaseStoryboard.cs index 1d7b3dffd275..10dd706f6275 100644 --- a/osu.Desktop.Tests/Visual/TestCaseStoryboard.cs +++ b/osu.Desktop.Tests/Visual/TestCaseStoryboard.cs @@ -21,8 +21,7 @@ internal class TestCaseStoryboard : OsuTestCase private readonly Bindable beatmapBacking = new Bindable(); - private MusicController musicController; - private Container storyboardContainer; + private readonly Container storyboardContainer; private Storyboard storyboard; public TestCaseStoryboard() @@ -45,7 +44,7 @@ public TestCaseStoryboard() }, }, }); - Add(musicController = new MusicController + Add(new MusicController { Origin = Anchor.TopRight, Anchor = Anchor.TopRight, diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs index d8d059aaed57..d7eb041de180 100644 --- a/osu.Game/Storyboards/CommandLoop.cs +++ b/osu.Game/Storyboards/CommandLoop.cs @@ -20,7 +20,7 @@ public CommandLoop(double startTime, int loopCount) public override void ApplyTransforms(Drawable drawable, double offset = 0) => base.ApplyTransforms(drawable, offset + LoopStartTime); - protected override void PostProcess(Command command, TransformSequence sequence) + protected override void PostProcess(ICommand command, TransformSequence sequence) => sequence.Loop(Duration - command.Duration, LoopCount); public override string ToString() diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs index a533f213fc11..909a629ad21d 100644 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ b/osu.Game/Storyboards/CommandTimeline.cs @@ -8,7 +8,7 @@ namespace osu.Game.Storyboards { - public class CommandTimeline : CommandTimeline + public class CommandTimeline : ICommandTimeline { private readonly List commands = new List(); public IEnumerable Commands => commands.OrderBy(c => c.StartTime); @@ -37,7 +37,7 @@ public void Add(Easing easing, double startTime, double endTime, T startValue, T public override string ToString() => $"{commands.Count} command(s)"; - public class TypedCommand : Command + public class TypedCommand : ICommand { public Easing Easing { get; set; } public double StartTime { get; set; } @@ -52,14 +52,14 @@ public override string ToString() } } - public interface CommandTimeline + public interface ICommandTimeline { double StartTime { get; } double EndTime { get; } bool HasCommands { get; } } - public interface Command + public interface ICommand { Easing Easing { get; set; } double StartTime { get; set; } diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 109104b12039..cff8d54d2926 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -23,7 +23,7 @@ public class CommandTimelineGroup public CommandTimeline FlipH = new CommandTimeline(); public CommandTimeline FlipV = new CommandTimeline(); - public IEnumerable Timelines + public IEnumerable Timelines { get { @@ -99,7 +99,7 @@ public virtual void ApplyTransforms(Drawable drawable, double offset = 0) } } - protected virtual void PostProcess(Command command, TransformSequence sequence) + protected virtual void PostProcess(ICommand command, TransformSequence sequence) { } } diff --git a/osu.Game/Storyboards/CommandTrigger.cs b/osu.Game/Storyboards/CommandTrigger.cs index e7133e170fef..e2731f9c45b1 100644 --- a/osu.Game/Storyboards/CommandTrigger.cs +++ b/osu.Game/Storyboards/CommandTrigger.cs @@ -5,20 +5,20 @@ namespace osu.Game.Storyboards { public class CommandTrigger : CommandTimelineGroup { - private string triggerName; - private double startTime; - private double endTime; - private int groupNumber; + public string TriggerName; + public double TriggerStartTime; + public double TriggerEndTime; + public int GroupNumber; public CommandTrigger(string triggerName, double startTime, double endTime, int groupNumber) { - this.triggerName = triggerName; - this.startTime = startTime; - this.endTime = endTime; - this.groupNumber = groupNumber; + TriggerName = triggerName; + TriggerStartTime = startTime; + TriggerEndTime = endTime; + GroupNumber = groupNumber; } public override string ToString() - => $"{triggerName} {startTime} -> {endTime} ({groupNumber})"; + => $"{TriggerName} {TriggerStartTime} -> {TriggerEndTime} ({GroupNumber})"; } } diff --git a/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs index 583a0d13c38d..c3a4e8b865f0 100644 --- a/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs @@ -5,7 +5,6 @@ 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 diff --git a/osu.Game/Storyboards/Drawables/StoryboardSprite.cs b/osu.Game/Storyboards/Drawables/StoryboardSprite.cs index 927996d75096..35cd19474eba 100644 --- a/osu.Game/Storyboards/Drawables/StoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/StoryboardSprite.cs @@ -5,7 +5,6 @@ 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 diff --git a/osu.Game/Storyboards/ElementDefinition.cs b/osu.Game/Storyboards/IElementDefinition.cs similarity index 84% rename from osu.Game/Storyboards/ElementDefinition.cs rename to osu.Game/Storyboards/IElementDefinition.cs index 1a8de9a120ca..93c9a473f7aa 100644 --- a/osu.Game/Storyboards/ElementDefinition.cs +++ b/osu.Game/Storyboards/IElementDefinition.cs @@ -5,7 +5,7 @@ namespace osu.Game.Storyboards { - public interface ElementDefinition + public interface IElementDefinition { string Path { get; } Drawable CreateDrawable(); diff --git a/osu.Game/Storyboards/LayerDefinition.cs b/osu.Game/Storyboards/LayerDefinition.cs index 871462c3fd36..baefe1626add 100644 --- a/osu.Game/Storyboards/LayerDefinition.cs +++ b/osu.Game/Storyboards/LayerDefinition.cs @@ -13,8 +13,8 @@ public class LayerDefinition public bool EnabledWhenPassing = true; public bool EnabledWhenFailing = true; - private List elements = new List(); - public IEnumerable Elements => elements; + private readonly List elements = new List(); + public IEnumerable Elements => elements; public LayerDefinition(string name, int depth) { @@ -22,7 +22,7 @@ public LayerDefinition(string name, int depth) Depth = depth; } - public void Add(ElementDefinition element) + public void Add(IElementDefinition element) { elements.Add(element); } diff --git a/osu.Game/Storyboards/SampleDefinition.cs b/osu.Game/Storyboards/SampleDefinition.cs index 26d2c4b02989..5d5e8ef1e929 100644 --- a/osu.Game/Storyboards/SampleDefinition.cs +++ b/osu.Game/Storyboards/SampleDefinition.cs @@ -5,9 +5,9 @@ namespace osu.Game.Storyboards { - public class SampleDefinition : ElementDefinition + public class SampleDefinition : IElementDefinition { - public string Path { get; private set; } + public string Path { get; set; } public double Time; public float Volume; diff --git a/osu.Game/Storyboards/SpriteDefinition.cs b/osu.Game/Storyboards/SpriteDefinition.cs index b47b6c935ef0..8529d66c7f07 100644 --- a/osu.Game/Storyboards/SpriteDefinition.cs +++ b/osu.Game/Storyboards/SpriteDefinition.cs @@ -9,14 +9,14 @@ namespace osu.Game.Storyboards { - public class SpriteDefinition : CommandTimelineGroup, ElementDefinition + public class SpriteDefinition : CommandTimelineGroup, IElementDefinition { - public string Path { get; private set; } + public string Path { get; set; } public Anchor Origin; public Vector2 InitialPosition; - private List loops = new List(); - private List triggers = new List(); + private readonly List loops = new List(); + private readonly List triggers = new List(); public SpriteDefinition(string path, Anchor origin, Vector2 initialPosition) { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2703798a6c64..8c91a9e80a2c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -86,7 +86,7 @@ - + From 3f2598543c563fdb8765124088d7c7b03001173f Mon Sep 17 00:00:00 2001 From: Damnae Date: Fri, 8 Sep 2017 18:03:04 +0200 Subject: [PATCH 06/18] Fix CommandLoop start and end time. --- osu.Game/Storyboards/CommandLoop.cs | 5 ++++- osu.Game/Storyboards/CommandTimelineGroup.cs | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs index d7eb041de180..a93903f4a908 100644 --- a/osu.Game/Storyboards/CommandLoop.cs +++ b/osu.Game/Storyboards/CommandLoop.cs @@ -11,6 +11,9 @@ public class CommandLoop : CommandTimelineGroup public double LoopStartTime; public int LoopCount; + public override double StartTime => LoopStartTime; + public override double EndTime => LoopStartTime + CommandsDuration * LoopCount; + public CommandLoop(double startTime, int loopCount) { LoopStartTime = startTime; @@ -21,7 +24,7 @@ public override void ApplyTransforms(Drawable drawable, double offset = 0) => base.ApplyTransforms(drawable, offset + LoopStartTime); protected override void PostProcess(ICommand command, TransformSequence sequence) - => sequence.Loop(Duration - command.Duration, LoopCount); + => sequence.Loop(CommandsDuration - command.Duration, LoopCount); public override string ToString() => $"{LoopStartTime} x{LoopCount}"; diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index cff8d54d2926..badd9a810ac1 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -39,9 +39,14 @@ public IEnumerable Timelines } } - 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 double CommandsStartTime => Timelines.Where(t => t.HasCommands).Min(t => t.StartTime); + public double CommandsEndTime => Timelines.Where(t => t.HasCommands).Max(t => t.EndTime); + public double CommandsDuration => CommandsEndTime - CommandsStartTime; + + public virtual double StartTime => CommandsStartTime; + public virtual double EndTime => CommandsEndTime; public double Duration => EndTime - StartTime; + public bool HasCommands => Timelines.Any(t => t.HasCommands); public virtual void ApplyTransforms(Drawable drawable, double offset = 0) From e4a2ad5eb5a31e41bc83e2db385f985ec1606497 Mon Sep 17 00:00:00 2001 From: Damnae Date: Fri, 8 Sep 2017 20:39:17 +0200 Subject: [PATCH 07/18] Fix storyboard sprite flipping. --- .../Drawables/StoryboardAnimation.cs | 31 ++++++++++++++++++- .../Storyboards/Drawables/StoryboardSprite.cs | 31 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs index c3a4e8b865f0..eb2ba5939790 100644 --- a/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs @@ -3,6 +3,7 @@ using OpenTK; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Textures; using System.Linq; @@ -18,7 +19,35 @@ public class StoryboardAnimation : TextureAnimation, IFlippable 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); + + protected override Vector2 DrawScale + => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y); + + public override Anchor Origin + { + get + { + var origin = base.Origin; + + if (FlipH) + { + if (origin.HasFlag(Anchor.x0)) + origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + else if (origin.HasFlag(Anchor.x2)) + origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + } + + if (FlipV) + { + if (origin.HasFlag(Anchor.y0)) + origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + else if (origin.HasFlag(Anchor.y2)) + origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + } + + return origin; + } + } public StoryboardAnimation(AnimationDefinition definition) { diff --git a/osu.Game/Storyboards/Drawables/StoryboardSprite.cs b/osu.Game/Storyboards/Drawables/StoryboardSprite.cs index 35cd19474eba..565b5a5069da 100644 --- a/osu.Game/Storyboards/Drawables/StoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/StoryboardSprite.cs @@ -3,6 +3,7 @@ using OpenTK; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using System.Linq; @@ -18,7 +19,35 @@ public class StoryboardSprite : Sprite, IFlippable 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); + + protected override Vector2 DrawScale + => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y); + + public override Anchor Origin + { + get + { + var origin = base.Origin; + + if (FlipH) + { + if (origin.HasFlag(Anchor.x0)) + origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + else if (origin.HasFlag(Anchor.x2)) + origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + } + + if (FlipV) + { + if (origin.HasFlag(Anchor.y0)) + origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + else if (origin.HasFlag(Anchor.y2)) + origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + } + + return origin; + } + } public StoryboardSprite(SpriteDefinition definition) { From e8ab853f6f4ec4c630dfdfa7caa0b722aa765f58 Mon Sep 17 00:00:00 2001 From: Damnae Date: Fri, 8 Sep 2017 18:00:17 +0200 Subject: [PATCH 08/18] Make storyboard loops work. --- osu.Game/Storyboards/CommandLoop.cs | 17 +++-- osu.Game/Storyboards/CommandTimelineGroup.cs | 72 ++++---------------- osu.Game/Storyboards/SpriteDefinition.cs | 61 +++++++++++++++-- 3 files changed, 81 insertions(+), 69 deletions(-) diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs index a93903f4a908..02b5eb0122be 100644 --- a/osu.Game/Storyboards/CommandLoop.cs +++ b/osu.Game/Storyboards/CommandLoop.cs @@ -1,8 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Graphics; -using osu.Framework.Graphics.Transforms; +using System.Collections.Generic; namespace osu.Game.Storyboards { @@ -20,11 +19,15 @@ public CommandLoop(double startTime, int loopCount) LoopCount = loopCount; } - public override void ApplyTransforms(Drawable drawable, double offset = 0) - => base.ApplyTransforms(drawable, offset + LoopStartTime); - - protected override void PostProcess(ICommand command, TransformSequence sequence) - => sequence.Loop(CommandsDuration - command.Duration, LoopCount); + public override IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) + { + for (var loop = 0; loop < LoopCount; loop++) + { + var loopOffset = LoopStartTime + loop * CommandsDuration; + foreach (var command in base.GetCommands(timelineSelector, offset + loopOffset)) + yield return command; + } + } public override string ToString() => $"{LoopStartTime} x{LoopCount}"; diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index badd9a810ac1..a42aca7c28f6 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -3,14 +3,13 @@ using OpenTK; using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Transforms; -using osu.Game.Storyboards.Drawables; using System.Collections.Generic; using System.Linq; namespace osu.Game.Storyboards { + public delegate CommandTimeline CommandTimelineSelector(CommandTimelineGroup commandTimelineGroup); + public class CommandTimelineGroup { public CommandTimeline X = new CommandTimeline(); @@ -49,63 +48,20 @@ public IEnumerable Timelines public bool HasCommands => Timelines.Any(t => t.HasCommands); - public virtual void ApplyTransforms(Drawable drawable, double offset = 0) + public virtual IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) { - if (X.HasCommands) drawable.X = X.StartValue; - foreach (var command in X.Commands) - using (drawable.BeginAbsoluteSequence(offset + command.StartTime)) - PostProcess(command, - 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(offset + command.StartTime)) - PostProcess(command, - 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(offset + command.StartTime)) - PostProcess(command, - 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(offset + command.StartTime)) - PostProcess(command, - 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(offset + command.StartTime)) - PostProcess(command, - 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(offset + command.StartTime)) - PostProcess(command, - drawable.FadeTo(command.StartValue) - .FadeTo(command.EndValue, command.Duration, command.Easing)); + if (offset != 0) + return timelineSelector(this).Commands.Select(command => + new CommandTimeline.TypedCommand + { + Easing = command.Easing, + StartTime = offset + command.StartTime, + EndTime = offset + command.EndTime, + StartValue = command.StartValue, + EndValue = command.EndValue, + }); - if (Additive.HasCommands) - drawable.BlendingMode = BlendingMode.Additive; - - var flippable = drawable as IFlippable; - if (flippable != null) - { - flippable.FlipH = FlipH.HasCommands; - flippable.FlipV = FlipV.HasCommands; - } - } - - protected virtual void PostProcess(ICommand command, TransformSequence sequence) - { + return timelineSelector(this).Commands; } } } diff --git a/osu.Game/Storyboards/SpriteDefinition.cs b/osu.Game/Storyboards/SpriteDefinition.cs index 8529d66c7f07..c1a358896020 100644 --- a/osu.Game/Storyboards/SpriteDefinition.cs +++ b/osu.Game/Storyboards/SpriteDefinition.cs @@ -4,6 +4,7 @@ using OpenTK; using osu.Framework.Graphics; using osu.Game.Storyboards.Drawables; +using System; using System.Collections.Generic; using System.Linq; @@ -18,6 +19,9 @@ public class SpriteDefinition : CommandTimelineGroup, IElementDefinition private readonly List loops = new List(); private readonly List triggers = new List(); + private delegate void DrawablePropertyInitializer(Drawable drawable, T value); + private delegate void DrawableTransformer(Drawable drawable, T value, double duration, Easing easing); + public SpriteDefinition(string path, Anchor origin, Vector2 initialPosition) { Path = path; @@ -42,11 +46,60 @@ public CommandTrigger AddTrigger(string triggerName, double startTime, double en public virtual Drawable CreateDrawable() => new StoryboardSprite(this); - public override void ApplyTransforms(Drawable target, double offset = 0) + public override IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) + { + var result = base.GetCommands(timelineSelector, offset); + foreach (var loop in loops) + result = result.Concat(loop.GetCommands(timelineSelector, offset)); + return result; + } + + public void ApplyTransforms(Drawable drawable, IEnumerable> triggeredGroups = null) + { + applyCommands(drawable, triggeredGroups, g => g.X, (d, value) => d.X = value, (d, value, duration, easing) => d.MoveToX(value, duration, easing)); + applyCommands(drawable, triggeredGroups, g => g.Y, (d, value) => d.Y = value, (d, value, duration, easing) => d.MoveToY(value, duration, easing)); + applyCommands(drawable, triggeredGroups, g => g.Scale, (d, value) => d.Scale = value, (d, value, duration, easing) => d.ScaleTo(value, duration, easing)); + applyCommands(drawable, triggeredGroups, g => g.Rotation, (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing)); + applyCommands(drawable, triggeredGroups, g => g.Colour, (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing)); + applyCommands(drawable, triggeredGroups, g => g.Alpha, (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing)); + + if (getAggregatedCommands(g => g.Additive, triggeredGroups).Any()) + drawable.BlendingMode = BlendingMode.Additive; + + var flippable = drawable as IFlippable; + if (flippable != null) + { + flippable.FlipH = getAggregatedCommands(g => g.FlipH, triggeredGroups).Any(); + flippable.FlipV = getAggregatedCommands(g => g.FlipV, triggeredGroups).Any(); + } + } + + private void applyCommands(Drawable drawable, IEnumerable> triggeredGroups, + CommandTimelineSelector timelineSelector, DrawablePropertyInitializer initializeProperty, DrawableTransformer transform) + { + var initialized = false; + foreach (var command in getAggregatedCommands(timelineSelector, triggeredGroups)) + { + if (!initialized) + { + initializeProperty(drawable, command.StartValue); + initialized = true; + } + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + transform(drawable, command.StartValue, 0, Easing.None); + transform(drawable, command.EndValue, command.Duration, command.Easing); + } + } + } + + private IEnumerable.TypedCommand> getAggregatedCommands(CommandTimelineSelector timelineSelector, IEnumerable> triggeredGroups) { - base.ApplyTransforms(target, offset); - foreach (var loop in loops.OrderBy(l => l.StartTime)) - loop.ApplyTransforms(target, offset); + var commands = GetCommands(timelineSelector); + if (triggeredGroups != null) + foreach (var pair in triggeredGroups) + commands = commands.Concat(pair.Item1.GetCommands(timelineSelector, pair.Item2)); + return commands.OrderBy(l => l.StartTime); } public override string ToString() From 4ab243d885f78dc0595c57318216b4cac1fea2db Mon Sep 17 00:00:00 2001 From: Damnae Date: Fri, 8 Sep 2017 21:23:24 +0200 Subject: [PATCH 09/18] CI fixes. --- osu.Game/Storyboards/SpriteDefinition.cs | 4 ++-- osu.Game/Storyboards/StoryboardDefinition.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/SpriteDefinition.cs b/osu.Game/Storyboards/SpriteDefinition.cs index c1a358896020..4f161a34f4b2 100644 --- a/osu.Game/Storyboards/SpriteDefinition.cs +++ b/osu.Game/Storyboards/SpriteDefinition.cs @@ -19,8 +19,8 @@ public class SpriteDefinition : CommandTimelineGroup, IElementDefinition private readonly List loops = new List(); private readonly List triggers = new List(); - private delegate void DrawablePropertyInitializer(Drawable drawable, T value); - private delegate void DrawableTransformer(Drawable drawable, T value, double duration, Easing easing); + private delegate void DrawablePropertyInitializer(Drawable drawable, T value); + private delegate void DrawableTransformer(Drawable drawable, T value, double duration, Easing easing); public SpriteDefinition(string path, Anchor origin, Vector2 initialPosition) { diff --git a/osu.Game/Storyboards/StoryboardDefinition.cs b/osu.Game/Storyboards/StoryboardDefinition.cs index e357440bc94d..4ef24cda91d0 100644 --- a/osu.Game/Storyboards/StoryboardDefinition.cs +++ b/osu.Game/Storyboards/StoryboardDefinition.cs @@ -9,7 +9,7 @@ namespace osu.Game.Storyboards { public class StoryboardDefinition { - private Dictionary layers = new Dictionary(); + private readonly Dictionary layers = new Dictionary(); public IEnumerable Layers => layers.Values; public StoryboardDefinition() From 57e53ff03a7ca3d2b310eda2de4eec0d277209dc Mon Sep 17 00:00:00 2001 From: Damnae Date: Fri, 8 Sep 2017 21:36:30 +0200 Subject: [PATCH 10/18] Fix diff-specific storyboard content being lost after loading. --- osu.Game/Beatmaps/Beatmap.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index e4568a19195b..9b32a6545316 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -56,6 +56,7 @@ public Beatmap(Beatmap original = null) ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo; Breaks = original?.Breaks ?? Breaks; ComboColors = original?.ComboColors ?? ComboColors; + Storyboard = original?.Storyboard ?? Storyboard; } } From 8d55cb7f922968761a17bf4748050ca93da053c9 Mon Sep 17 00:00:00 2001 From: Damnae Date: Sat, 9 Sep 2017 11:00:58 +0200 Subject: [PATCH 11/18] Improve command sorting. --- osu.Game/Storyboards/CommandTimeline.cs | 10 +++++++++- osu.Game/Storyboards/SpriteDefinition.cs | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs index 909a629ad21d..b9bb6629d164 100644 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ b/osu.Game/Storyboards/CommandTimeline.cs @@ -3,6 +3,7 @@ using osu.Framework.Caching; using osu.Framework.Graphics; +using System; using System.Collections.Generic; using System.Linq; @@ -47,6 +48,13 @@ public class TypedCommand : ICommand public T StartValue; public T EndValue; + public int CompareTo(ICommand other) + { + var result = StartTime.CompareTo(other.StartTime); + if (result != 0) return result; + return EndTime.CompareTo(other.EndTime); + } + public override string ToString() => $"{StartTime} -> {EndTime}, {StartValue} -> {EndValue} {Easing}"; } @@ -59,7 +67,7 @@ public interface ICommandTimeline bool HasCommands { get; } } - public interface ICommand + public interface ICommand : IComparable { Easing Easing { get; set; } double StartTime { get; set; } diff --git a/osu.Game/Storyboards/SpriteDefinition.cs b/osu.Game/Storyboards/SpriteDefinition.cs index 4f161a34f4b2..a4697aabfb4e 100644 --- a/osu.Game/Storyboards/SpriteDefinition.cs +++ b/osu.Game/Storyboards/SpriteDefinition.cs @@ -78,7 +78,7 @@ private void applyCommands(Drawable drawable, IEnumerable timelineSelector, DrawablePropertyInitializer initializeProperty, DrawableTransformer transform) { var initialized = false; - foreach (var command in getAggregatedCommands(timelineSelector, triggeredGroups)) + foreach (var command in getAggregatedCommands(timelineSelector, triggeredGroups).OrderBy(l => l)) { if (!initialized) { @@ -99,7 +99,7 @@ private void applyCommands(Drawable drawable, IEnumerable l.StartTime); + return commands; } public override string ToString() From bc01d9a1b04f5909eac2e9829c25243b79cbb7ea Mon Sep 17 00:00:00 2001 From: Damnae Date: Sat, 9 Sep 2017 15:34:26 +0200 Subject: [PATCH 12/18] Animate Additive / FlipH and FlipV. --- osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 6 +-- osu.Game/Storyboards/CommandTimelineGroup.cs | 5 +- .../Drawables/DrawablesExtensions.cs | 27 +++++++++++ osu.Game/Storyboards/Drawables/IFlippable.cs | 46 ++++++++++++++++++- osu.Game/Storyboards/SpriteDefinition.cs | 14 +++--- osu.Game/osu.Game.csproj | 1 + 6 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 osu.Game/Storyboards/Drawables/DrawablesExtensions.cs diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 6fde7263da47..d538960a3773 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -428,9 +428,9 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr 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; + case "A": timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); break; + case "H": timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); break; + case "V": timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); break; } } break; diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index a42aca7c28f6..332a6f79cb04 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -3,6 +3,7 @@ using OpenTK; using OpenTK.Graphics; +using osu.Framework.Graphics; using System.Collections.Generic; using System.Linq; @@ -18,7 +19,7 @@ public class CommandTimelineGroup public CommandTimeline Rotation = new CommandTimeline(); public CommandTimeline Colour = new CommandTimeline(); public CommandTimeline Alpha = new CommandTimeline(); - public CommandTimeline Additive = new CommandTimeline(); + public CommandTimeline BlendingMode = new CommandTimeline(); public CommandTimeline FlipH = new CommandTimeline(); public CommandTimeline FlipV = new CommandTimeline(); @@ -32,7 +33,7 @@ public IEnumerable Timelines yield return Rotation; yield return Colour; yield return Alpha; - yield return Additive; + yield return BlendingMode; yield return FlipH; yield return FlipV; } diff --git a/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs b/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs new file mode 100644 index 000000000000..436b4aafeb81 --- /dev/null +++ b/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs @@ -0,0 +1,27 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Storyboards.Drawables +{ + public static class DrawablesExtensions + { + /// + /// Adjusts after a delay. + /// + /// A to which further transforms can be added. + public static TransformSequence TransformBlendingMode(this T drawable, BlendingMode newValue, double delay = 0) + where T : Drawable + => drawable.TransformTo(drawable.PopulateTransform(new TransformBlendingMode(), newValue, delay)); + } + + public class TransformBlendingMode : Transform + { + private BlendingMode valueAt(double time) + => time < EndTime ? StartValue : EndValue; + + public override string TargetMember => nameof(Drawable.BlendingMode); + + protected override void Apply(Drawable d, double time) => d.BlendingMode = valueAt(time); + protected override void ReadIntoStartValue(Drawable d) => StartValue = d.BlendingMode; + } +} diff --git a/osu.Game/Storyboards/Drawables/IFlippable.cs b/osu.Game/Storyboards/Drawables/IFlippable.cs index a1899d141121..4d21c9d14029 100644 --- a/osu.Game/Storyboards/Drawables/IFlippable.cs +++ b/osu.Game/Storyboards/Drawables/IFlippable.cs @@ -1,11 +1,55 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; + namespace osu.Game.Storyboards.Drawables { - public interface IFlippable + public interface IFlippable : ITransformable { bool FlipH { get; set; } bool FlipV { get; set; } } + + public class TransformFlipH : Transform + { + private bool valueAt(double time) + => time < EndTime ? StartValue : EndValue; + + public override string TargetMember => nameof(IFlippable.FlipH); + + protected override void Apply(IFlippable d, double time) => d.FlipH = valueAt(time); + protected override void ReadIntoStartValue(IFlippable d) => StartValue = d.FlipH; + } + + public class TransformFlipV : Transform + { + private bool valueAt(double time) + => time < EndTime ? StartValue : EndValue; + + public override string TargetMember => nameof(IFlippable.FlipV); + + protected override void Apply(IFlippable d, double time) => d.FlipV = valueAt(time); + protected override void ReadIntoStartValue(IFlippable d) => StartValue = d.FlipV; + } + + public static class FlippableExtensions + { + /// + /// Adjusts after a delay. + /// + /// A to which further transforms can be added. + public static TransformSequence TransformFlipH(this T flippable, bool newValue, double delay = 0) + where T : IFlippable + => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipH(), newValue, delay)); + + /// + /// Adjusts after a delay. + /// + /// A to which further transforms can be added. + public static TransformSequence TransformFlipV(this T flippable, bool newValue, double delay = 0) + where T : IFlippable + => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipV(), newValue, delay)); + } } diff --git a/osu.Game/Storyboards/SpriteDefinition.cs b/osu.Game/Storyboards/SpriteDefinition.cs index a4697aabfb4e..c410fd880797 100644 --- a/osu.Game/Storyboards/SpriteDefinition.cs +++ b/osu.Game/Storyboards/SpriteDefinition.cs @@ -62,27 +62,27 @@ public void ApplyTransforms(Drawable drawable, IEnumerable g.Rotation, (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing)); applyCommands(drawable, triggeredGroups, g => g.Colour, (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing)); applyCommands(drawable, triggeredGroups, g => g.Alpha, (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing)); - - if (getAggregatedCommands(g => g.Additive, triggeredGroups).Any()) - drawable.BlendingMode = BlendingMode.Additive; + applyCommands(drawable, triggeredGroups, g => g.BlendingMode, (d, value) => d.BlendingMode = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration), false); var flippable = drawable as IFlippable; if (flippable != null) { - flippable.FlipH = getAggregatedCommands(g => g.FlipH, triggeredGroups).Any(); - flippable.FlipV = getAggregatedCommands(g => g.FlipV, triggeredGroups).Any(); + applyCommands(drawable, triggeredGroups, g => g.FlipH, (d, value) => flippable.FlipH = value, (d, value, duration, easing) => flippable.TransformFlipH(value, duration), false); + applyCommands(drawable, triggeredGroups, g => g.FlipV, (d, value) => flippable.FlipV = value, (d, value, duration, easing) => flippable.TransformFlipV(value, duration), false); } } private void applyCommands(Drawable drawable, IEnumerable> triggeredGroups, - CommandTimelineSelector timelineSelector, DrawablePropertyInitializer initializeProperty, DrawableTransformer transform) + CommandTimelineSelector timelineSelector, DrawablePropertyInitializer initializeProperty, DrawableTransformer transform, bool alwaysInitialize = true) + where T : struct { var initialized = false; foreach (var command in getAggregatedCommands(timelineSelector, triggeredGroups).OrderBy(l => l)) { if (!initialized) { - initializeProperty(drawable, command.StartValue); + if (alwaysInitialize || command.StartTime == command.EndTime) + initializeProperty.Invoke(drawable, command.StartValue); initialized = true; } using (drawable.BeginAbsoluteSequence(command.StartTime)) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8c91a9e80a2c..7e068979bfbe 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -86,6 +86,7 @@ + From 59d9dc5ee74d7ab2988b7fa50cc516d08807b910 Mon Sep 17 00:00:00 2001 From: Damnae Date: Sun, 10 Sep 2017 20:08:56 +0200 Subject: [PATCH 13/18] Ignore storyboard sprites with invalid positions. --- osu.Game/Storyboards/Drawables/StoryboardAnimation.cs | 3 +++ osu.Game/Storyboards/Drawables/StoryboardSprite.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs index eb2ba5939790..2051b9c4af1b 100644 --- a/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs @@ -49,6 +49,9 @@ public override Anchor Origin } } + public override bool IsPresent + => !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent; + public StoryboardAnimation(AnimationDefinition definition) { Definition = definition; diff --git a/osu.Game/Storyboards/Drawables/StoryboardSprite.cs b/osu.Game/Storyboards/Drawables/StoryboardSprite.cs index 565b5a5069da..ca055fe6d474 100644 --- a/osu.Game/Storyboards/Drawables/StoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/StoryboardSprite.cs @@ -49,6 +49,9 @@ public override Anchor Origin } } + public override bool IsPresent + => !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent; + public StoryboardSprite(SpriteDefinition definition) { Definition = definition; From 9eaa1cb5cdeda76fcb943905baacab808bddffa2 Mon Sep 17 00:00:00 2001 From: Damnae Date: Sun, 10 Sep 2017 21:25:23 +0200 Subject: [PATCH 14/18] Fix sprites not being visible when all their commands are inside loops. --- osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 2 +- osu.Game/Storyboards/SpriteDefinition.cs | 55 ++++++++++--------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index d538960a3773..e436a4398999 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -324,7 +324,7 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr else { if (depth < 2) - timelineGroup = spriteDefinition; + timelineGroup = spriteDefinition?.TimelineGroup; var commandType = split[0]; switch (commandType) diff --git a/osu.Game/Storyboards/SpriteDefinition.cs b/osu.Game/Storyboards/SpriteDefinition.cs index c410fd880797..1e1718a2556b 100644 --- a/osu.Game/Storyboards/SpriteDefinition.cs +++ b/osu.Game/Storyboards/SpriteDefinition.cs @@ -10,14 +10,26 @@ namespace osu.Game.Storyboards { - public class SpriteDefinition : CommandTimelineGroup, IElementDefinition + public class SpriteDefinition : IElementDefinition { + private readonly List loops = new List(); + private readonly List triggers = new List(); + public string Path { get; set; } public Anchor Origin; public Vector2 InitialPosition; - private readonly List loops = new List(); - private readonly List triggers = new List(); + public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup(); + + public double StartTime => Math.Min( + TimelineGroup.HasCommands ? TimelineGroup.CommandsStartTime : double.MaxValue, + loops.Any(l => l.HasCommands) ? loops.Where(l => l.HasCommands).Min(l => l.StartTime) : double.MaxValue); + + public double EndTime => Math.Max( + TimelineGroup.HasCommands ? TimelineGroup.CommandsEndTime : double.MinValue, + loops.Any(l => l.HasCommands) ? loops.Where(l => l.HasCommands).Max(l => l.EndTime) : double.MinValue); + + public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands); private delegate void DrawablePropertyInitializer(Drawable drawable, T value); private delegate void DrawableTransformer(Drawable drawable, T value, double duration, Easing easing); @@ -46,38 +58,29 @@ public CommandTrigger AddTrigger(string triggerName, double startTime, double en public virtual Drawable CreateDrawable() => new StoryboardSprite(this); - public override IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) - { - var result = base.GetCommands(timelineSelector, offset); - foreach (var loop in loops) - result = result.Concat(loop.GetCommands(timelineSelector, offset)); - return result; - } - public void ApplyTransforms(Drawable drawable, IEnumerable> triggeredGroups = null) { - applyCommands(drawable, triggeredGroups, g => g.X, (d, value) => d.X = value, (d, value, duration, easing) => d.MoveToX(value, duration, easing)); - applyCommands(drawable, triggeredGroups, g => g.Y, (d, value) => d.Y = value, (d, value, duration, easing) => d.MoveToY(value, duration, easing)); - applyCommands(drawable, triggeredGroups, g => g.Scale, (d, value) => d.Scale = value, (d, value, duration, easing) => d.ScaleTo(value, duration, easing)); - applyCommands(drawable, triggeredGroups, g => g.Rotation, (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing)); - applyCommands(drawable, triggeredGroups, g => g.Colour, (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing)); - applyCommands(drawable, triggeredGroups, g => g.Alpha, (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing)); - applyCommands(drawable, triggeredGroups, g => g.BlendingMode, (d, value) => d.BlendingMode = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration), false); + applyCommands(drawable, getCommands(g => g.X, triggeredGroups), (d, value) => d.X = value, (d, value, duration, easing) => d.MoveToX(value, duration, easing)); + applyCommands(drawable, getCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value, (d, value, duration, easing) => d.MoveToY(value, duration, easing)); + applyCommands(drawable, getCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = value, (d, value, duration, easing) => d.ScaleTo(value, duration, easing)); + applyCommands(drawable, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing)); + applyCommands(drawable, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing)); + applyCommands(drawable, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing)); + applyCommands(drawable, getCommands(g => g.BlendingMode, triggeredGroups), (d, value) => d.BlendingMode = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration), false); var flippable = drawable as IFlippable; if (flippable != null) { - applyCommands(drawable, triggeredGroups, g => g.FlipH, (d, value) => flippable.FlipH = value, (d, value, duration, easing) => flippable.TransformFlipH(value, duration), false); - applyCommands(drawable, triggeredGroups, g => g.FlipV, (d, value) => flippable.FlipV = value, (d, value, duration, easing) => flippable.TransformFlipV(value, duration), false); + applyCommands(drawable, getCommands(g => g.FlipH, triggeredGroups), (d, value) => flippable.FlipH = value, (d, value, duration, easing) => flippable.TransformFlipH(value, duration), false); + applyCommands(drawable, getCommands(g => g.FlipV, triggeredGroups), (d, value) => flippable.FlipV = value, (d, value, duration, easing) => flippable.TransformFlipV(value, duration), false); } } - private void applyCommands(Drawable drawable, IEnumerable> triggeredGroups, - CommandTimelineSelector timelineSelector, DrawablePropertyInitializer initializeProperty, DrawableTransformer transform, bool alwaysInitialize = true) + private void applyCommands(Drawable drawable, IEnumerable.TypedCommand> commands, DrawablePropertyInitializer initializeProperty, DrawableTransformer transform, bool alwaysInitialize = true) where T : struct { var initialized = false; - foreach (var command in getAggregatedCommands(timelineSelector, triggeredGroups).OrderBy(l => l)) + foreach (var command in commands.OrderBy(l => l)) { if (!initialized) { @@ -93,9 +96,11 @@ private void applyCommands(Drawable drawable, IEnumerable.TypedCommand> getAggregatedCommands(CommandTimelineSelector timelineSelector, IEnumerable> triggeredGroups) + private IEnumerable.TypedCommand> getCommands(CommandTimelineSelector timelineSelector, IEnumerable> triggeredGroups) { - var commands = GetCommands(timelineSelector); + var commands = TimelineGroup.GetCommands(timelineSelector); + foreach (var loop in loops) + commands = commands.Concat(loop.GetCommands(timelineSelector)); if (triggeredGroups != null) foreach (var pair in triggeredGroups) commands = commands.Concat(pair.Item1.GetCommands(timelineSelector, pair.Item2)); From cd15cfc864513228112d7209d526a97f7bbf9bd3 Mon Sep 17 00:00:00 2001 From: Damnae Date: Sun, 10 Sep 2017 22:02:55 +0200 Subject: [PATCH 15/18] Use case insensitive paths to find storyboard textures. --- osu.Game/Storyboards/Drawables/StoryboardAnimation.cs | 5 +++-- osu.Game/Storyboards/Drawables/StoryboardSprite.cs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs index 2051b9c4af1b..a28287fd402e 100644 --- a/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs @@ -69,11 +69,12 @@ public StoryboardAnimation(AnimationDefinition definition) [BackgroundDependencyLoader] private void load(OsuGameBase game, TextureStore textureStore) { + var basePath = Definition.Path.ToLowerInvariant(); for (var frame = 0; frame < Definition.FrameCount; frame++) { - var framePath = Definition.Path.Replace(".", frame + "."); + var framePath = basePath.Replace(".", frame + "."); - var path = game.Beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename == framePath)?.FileInfo.StoragePath; + var path = game.Beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == framePath)?.FileInfo.StoragePath; if (path == null) continue; diff --git a/osu.Game/Storyboards/Drawables/StoryboardSprite.cs b/osu.Game/Storyboards/Drawables/StoryboardSprite.cs index ca055fe6d474..3b1a431a4a43 100644 --- a/osu.Game/Storyboards/Drawables/StoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/StoryboardSprite.cs @@ -68,8 +68,8 @@ public StoryboardSprite(SpriteDefinition definition) [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; + var spritePath = Definition.Path.ToLowerInvariant(); + var path = game.Beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == spritePath)?.FileInfo.StoragePath; if (path == null) return; From 2fb203159f8a32fbc0b487b1268bf3ceeaf9bf9f Mon Sep 17 00:00:00 2001 From: Damnae Date: Mon, 11 Sep 2017 12:33:11 +0200 Subject: [PATCH 16/18] CI fixes. --- osu.Game/Storyboards/Drawables/DrawablesExtensions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs b/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs index 0824ca2ed9b6..3b21c47b966a 100644 --- a/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs +++ b/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs @@ -1,4 +1,7 @@ -using osu.Framework.Graphics; +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; namespace osu.Game.Storyboards.Drawables @@ -6,7 +9,7 @@ namespace osu.Game.Storyboards.Drawables public static class DrawablesExtensions { /// - /// Adjusts after a delay. + /// Adjusts after a delay. /// /// A to which further transforms can be added. public static TransformSequence TransformBlendingMode(this T drawable, BlendingMode newValue, double delay = 0) From 58e65397b06b43973c52babef2e520520fc2e94e Mon Sep 17 00:00:00 2001 From: Damnae Date: Tue, 12 Sep 2017 10:13:55 +0200 Subject: [PATCH 17/18] Add support for storyboards using numerical values. --- osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index e436a4398999..adf366cdea2e 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -287,8 +287,8 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr break; case EventType.Sprite: { - var layer = split[1]; - var origin = (Anchor)Enum.Parse(typeof(Anchor), split[2]); + var layer = parseLayer(split[1]); + var origin = parseOrigin(split[2]); var path = cleanFilename(split[3]); var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); @@ -298,8 +298,8 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr break; case EventType.Animation: { - var layer = split[1]; - var origin = (Anchor)Enum.Parse(typeof(Anchor), split[2]); + var layer = parseLayer(split[1]); + var origin = parseOrigin(split[2]); var path = cleanFilename(split[3]); var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); @@ -313,7 +313,7 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr case EventType.Sample: { var time = double.Parse(split[1], CultureInfo.InvariantCulture); - var layer = split[2]; + var layer = parseLayer(split[2]); var path = cleanFilename(split[3]); var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; beatmap.Storyboard.GetLayer(layer).Add(new SampleDefinition(path, time, volume)); @@ -446,6 +446,27 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr private static string cleanFilename(string path) => FileSafety.PathStandardise(path.Trim('\"')); + private static Anchor parseOrigin(string value) + { + var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value); + switch (origin) + { + case LegacyOrigins.TopLeft: return Anchor.TopLeft; + case LegacyOrigins.TopCentre: return Anchor.TopCentre; + case LegacyOrigins.TopRight: return Anchor.TopRight; + case LegacyOrigins.CentreLeft: return Anchor.CentreLeft; + case LegacyOrigins.Centre: return Anchor.Centre; + case LegacyOrigins.CentreRight: return Anchor.CentreRight; + case LegacyOrigins.BottomLeft: return Anchor.BottomLeft; + case LegacyOrigins.BottomCentre: return Anchor.BottomCentre; + case LegacyOrigins.BottomRight: return Anchor.BottomRight; + } + throw new InvalidDataException($@"Unknown origin: {value}"); + } + + private static string parseLayer(string value) + => Enum.Parse(typeof(StoryLayer), value).ToString(); + private void handleTimingPoints(Beatmap beatmap, string line) { string[] split = line.Split(','); @@ -683,5 +704,27 @@ internal enum EventType Sample = 5, Animation = 6 } + + internal enum LegacyOrigins + { + TopLeft, + Centre, + CentreLeft, + TopRight, + BottomCentre, + TopCentre, + Custom, + CentreRight, + BottomLeft, + BottomRight + }; + + internal enum StoryLayer + { + Background = 0, + Fail = 1, + Pass = 2, + Foreground = 3 + } } } From bab3ef0669de179351956504e7096d85e5a18900 Mon Sep 17 00:00:00 2001 From: Damnae Date: Wed, 13 Sep 2017 11:22:24 +0200 Subject: [PATCH 18/18] Rename storyboard classes. --- .../Visual/TestCaseStoryboard.cs | 6 ++-- osu.Game/Beatmaps/Beatmap.cs | 2 +- osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 24 ++++++------- .../{Storyboard.cs => DrawableStoryboard.cs} | 14 ++++---- ...tion.cs => DrawableStoryboardAnimation.cs} | 32 ++++++++--------- ...ardLayer.cs => DrawableStoryboardLayer.cs} | 12 +++---- ...dSprite.cs => DrawableStoryboardSprite.cs} | 26 +++++++------- ...entDefinition.cs => IStoryboardElement.cs} | 2 +- osu.Game/Storyboards/Storyboard.cs | 35 +++++++++++++++++++ ...onDefinition.cs => StoryboardAnimation.cs} | 6 ++-- osu.Game/Storyboards/StoryboardDefinition.cs | 35 ------------------- ...{LayerDefinition.cs => StoryboardLayer.cs} | 14 ++++---- ...ampleDefinition.cs => StoryboardSample.cs} | 4 +-- ...priteDefinition.cs => StoryboardSprite.cs} | 6 ++-- osu.Game/osu.Game.csproj | 20 +++++------ 15 files changed, 119 insertions(+), 119 deletions(-) rename osu.Game/Storyboards/Drawables/{Storyboard.cs => DrawableStoryboard.cs} (74%) rename osu.Game/Storyboards/Drawables/{StoryboardAnimation.cs => DrawableStoryboardAnimation.cs} (67%) rename osu.Game/Storyboards/Drawables/{StoryboardLayer.cs => DrawableStoryboardLayer.cs} (69%) rename osu.Game/Storyboards/Drawables/{StoryboardSprite.cs => DrawableStoryboardSprite.cs} (71%) rename osu.Game/Storyboards/{IElementDefinition.cs => IStoryboardElement.cs} (84%) create mode 100644 osu.Game/Storyboards/Storyboard.cs rename osu.Game/Storyboards/{AnimationDefinition.cs => StoryboardAnimation.cs} (79%) delete mode 100644 osu.Game/Storyboards/StoryboardDefinition.cs rename osu.Game/Storyboards/{LayerDefinition.cs => StoryboardLayer.cs} (55%) rename osu.Game/Storyboards/{SampleDefinition.cs => StoryboardSample.cs} (77%) rename osu.Game/Storyboards/{SpriteDefinition.cs => StoryboardSprite.cs} (95%) diff --git a/osu.Desktop.Tests/Visual/TestCaseStoryboard.cs b/osu.Desktop.Tests/Visual/TestCaseStoryboard.cs index 10dd706f6275..878198e8d2c6 100644 --- a/osu.Desktop.Tests/Visual/TestCaseStoryboard.cs +++ b/osu.Desktop.Tests/Visual/TestCaseStoryboard.cs @@ -21,8 +21,8 @@ internal class TestCaseStoryboard : OsuTestCase private readonly Bindable beatmapBacking = new Bindable(); - private readonly Container storyboardContainer; - private Storyboard storyboard; + private readonly Container storyboardContainer; + private DrawableStoryboard storyboard; public TestCaseStoryboard() { @@ -38,7 +38,7 @@ public TestCaseStoryboard() RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - storyboardContainer = new Container + storyboardContainer = new Container { RelativeSizeAxes = Axes.Both, }, diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index a28f7832b7fa..458c2304f2b4 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -44,7 +44,7 @@ public class Beatmap /// /// The Beatmap's Storyboard. /// - public StoryboardDefinition Storyboard = new StoryboardDefinition(); + public Storyboard Storyboard = new Storyboard(); /// /// Constructs a new beatmap. diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index adf366cdea2e..21fee0f4650b 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -242,7 +242,7 @@ private void decodeVariables(ref string line) } } - private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spriteDefinition, ref CommandTimelineGroup timelineGroup) + private void handleEvents(Beatmap beatmap, string line, ref StoryboardSprite storyboardSprite, ref CommandTimelineGroup timelineGroup) { var depth = 0; while (line.StartsWith(" ") || line.StartsWith("_")) @@ -257,7 +257,7 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr if (depth == 0) { - spriteDefinition = null; + storyboardSprite = null; EventType type; if (!Enum.TryParse(split[0], out type)) @@ -292,8 +292,8 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr 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); + storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y)); + beatmap.Storyboard.GetLayer(layer).Add(storyboardSprite); } break; case EventType.Animation: @@ -306,8 +306,8 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr var frameCount = int.Parse(split[6]); var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo); var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; - spriteDefinition = new AnimationDefinition(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); - beatmap.Storyboard.GetLayer(layer).Add(spriteDefinition); + storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); + beatmap.Storyboard.GetLayer(layer).Add(storyboardSprite); } break; case EventType.Sample: @@ -316,7 +316,7 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr var layer = parseLayer(split[2]); var path = cleanFilename(split[3]); var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; - beatmap.Storyboard.GetLayer(layer).Add(new SampleDefinition(path, time, volume)); + beatmap.Storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume)); } break; } @@ -324,7 +324,7 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr else { if (depth < 2) - timelineGroup = spriteDefinition?.TimelineGroup; + timelineGroup = storyboardSprite?.TimelineGroup; var commandType = split[0]; switch (commandType) @@ -335,14 +335,14 @@ private void handleEvents(Beatmap beatmap, string line, ref SpriteDefinition spr var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue; var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue; var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0; - timelineGroup = spriteDefinition?.AddTrigger(triggerName, startTime, endTime, groupNumber); + timelineGroup = storyboardSprite?.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); + timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount); } break; default: @@ -607,7 +607,7 @@ protected override void ParseFile(StreamReader stream, Beatmap beatmap) Section section = Section.None; bool hasCustomColours = false; - SpriteDefinition spriteDefinition = null; + StoryboardSprite storyboardSprite = null; CommandTimelineGroup timelineGroup = null; string line; @@ -647,7 +647,7 @@ protected override void ParseFile(StreamReader stream, Beatmap beatmap) handleDifficulty(beatmap, line); break; case Section.Events: - handleEvents(beatmap, line, ref spriteDefinition, ref timelineGroup); + handleEvents(beatmap, line, ref storyboardSprite, ref timelineGroup); break; case Section.TimingPoints: handleTimingPoints(beatmap, line); diff --git a/osu.Game/Storyboards/Drawables/Storyboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs similarity index 74% rename from osu.Game/Storyboards/Drawables/Storyboard.cs rename to osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 45aa063f7975..f88e5d118f1d 100644 --- a/osu.Game/Storyboards/Drawables/Storyboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -10,9 +10,9 @@ namespace osu.Game.Storyboards.Drawables { - public class Storyboard : Container + public class DrawableStoryboard : Container { - public StoryboardDefinition Definition { get; private set; } + public Storyboard Storyboard { get; private set; } protected override Vector2 DrawScale => new Vector2(Parent.DrawHeight / 480); public override bool HandleInput => false; @@ -33,9 +33,9 @@ public bool Passing protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); - public Storyboard(StoryboardDefinition definition) + public DrawableStoryboard(Storyboard storyboard) { - Definition = definition; + Storyboard = storyboard; Size = new Vector2(640, 480); Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -46,14 +46,14 @@ 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()); + foreach (var layer in Storyboard.Layers) + Add(layer.CreateDrawable()); } private void updateLayerVisibility() { foreach (var layer in Children) - layer.Enabled = passing ? layer.Definition.EnabledWhenPassing : layer.Definition.EnabledWhenFailing; + layer.Enabled = passing ? layer.Layer.EnabledWhenPassing : layer.Layer.EnabledWhenFailing; } } } diff --git a/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs similarity index 67% rename from osu.Game/Storyboards/Drawables/StoryboardAnimation.cs rename to osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index a28287fd402e..d8b7d05ee92e 100644 --- a/osu.Game/Storyboards/Drawables/StoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -10,12 +10,12 @@ namespace osu.Game.Storyboards.Drawables { - public class StoryboardAnimation : TextureAnimation, IFlippable + public class DrawableStoryboardAnimation : TextureAnimation, IFlippable { - public AnimationDefinition Definition { get; private set; } + public StoryboardAnimation Animation { get; private set; } - protected override bool ShouldBeAlive => Definition.HasCommands && base.ShouldBeAlive; - public override bool RemoveWhenNotAlive => !Definition.HasCommands || base.RemoveWhenNotAlive; + protected override bool ShouldBeAlive => Animation.HasCommands && base.ShouldBeAlive; + public override bool RemoveWhenNotAlive => !Animation.HasCommands || base.RemoveWhenNotAlive; public bool FlipH { get; set; } public bool FlipV { get; set; } @@ -52,25 +52,25 @@ public override Anchor Origin public override bool IsPresent => !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent; - public StoryboardAnimation(AnimationDefinition definition) + public DrawableStoryboardAnimation(StoryboardAnimation animation) { - Definition = definition; - Origin = definition.Origin; - Position = definition.InitialPosition; - Repeat = definition.LoopType == AnimationLoopType.LoopForever; + Animation = animation; + Origin = animation.Origin; + Position = animation.InitialPosition; + Repeat = animation.LoopType == AnimationLoopType.LoopForever; - if (definition.HasCommands) + if (animation.HasCommands) { - LifetimeStart = definition.StartTime; - LifetimeEnd = definition.EndTime; + LifetimeStart = animation.StartTime; + LifetimeEnd = animation.EndTime; } } [BackgroundDependencyLoader] private void load(OsuGameBase game, TextureStore textureStore) { - var basePath = Definition.Path.ToLowerInvariant(); - for (var frame = 0; frame < Definition.FrameCount; frame++) + var basePath = Animation.Path.ToLowerInvariant(); + for (var frame = 0; frame < Animation.FrameCount; frame++) { var framePath = basePath.Replace(".", frame + "."); @@ -79,9 +79,9 @@ private void load(OsuGameBase game, TextureStore textureStore) continue; var texture = textureStore.Get(path); - AddFrame(texture, Definition.FrameDelay); + AddFrame(texture, Animation.FrameDelay); } - Definition.ApplyTransforms(this); + Animation.ApplyTransforms(this); } } } diff --git a/osu.Game/Storyboards/Drawables/StoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs similarity index 69% rename from osu.Game/Storyboards/Drawables/StoryboardLayer.cs rename to osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index 48532e041881..2b5db5b6fa01 100644 --- a/osu.Game/Storyboards/Drawables/StoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -7,26 +7,26 @@ namespace osu.Game.Storyboards.Drawables { - public class StoryboardLayer : Container + public class DrawableStoryboardLayer : Container { - public LayerDefinition Definition { get; private set; } + public StoryboardLayer Layer { get; private set; } public bool Enabled; public override bool IsPresent => Enabled && base.IsPresent; - public StoryboardLayer(LayerDefinition definition) + public DrawableStoryboardLayer(StoryboardLayer layer) { - Definition = definition; + Layer = layer; RelativeSizeAxes = Axes.Both; Anchor = Anchor.Centre; Origin = Anchor.Centre; - Enabled = definition.EnabledWhenPassing; + Enabled = layer.EnabledWhenPassing; } [BackgroundDependencyLoader] private void load() { - foreach (var element in Definition.Elements) + foreach (var element in Layer.Elements) { var drawable = element.CreateDrawable(); if (drawable != null) diff --git a/osu.Game/Storyboards/Drawables/StoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs similarity index 71% rename from osu.Game/Storyboards/Drawables/StoryboardSprite.cs rename to osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 3b1a431a4a43..4b491fa00853 100644 --- a/osu.Game/Storyboards/Drawables/StoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -10,12 +10,12 @@ namespace osu.Game.Storyboards.Drawables { - public class StoryboardSprite : Sprite, IFlippable + public class DrawableStoryboardSprite : Sprite, IFlippable { - public SpriteDefinition Definition { get; private set; } + public StoryboardSprite Sprite { get; private set; } - protected override bool ShouldBeAlive => Definition.HasCommands && base.ShouldBeAlive; - public override bool RemoveWhenNotAlive => !Definition.HasCommands || base.RemoveWhenNotAlive; + protected override bool ShouldBeAlive => Sprite.HasCommands && base.ShouldBeAlive; + public override bool RemoveWhenNotAlive => !Sprite.HasCommands || base.RemoveWhenNotAlive; public bool FlipH { get; set; } public bool FlipV { get; set; } @@ -52,29 +52,29 @@ public override Anchor Origin public override bool IsPresent => !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent; - public StoryboardSprite(SpriteDefinition definition) + public DrawableStoryboardSprite(StoryboardSprite sprite) { - Definition = definition; - Origin = definition.Origin; - Position = definition.InitialPosition; + Sprite = sprite; + Origin = sprite.Origin; + Position = sprite.InitialPosition; - if (definition.HasCommands) + if (sprite.HasCommands) { - LifetimeStart = definition.StartTime; - LifetimeEnd = definition.EndTime; + LifetimeStart = sprite.StartTime; + LifetimeEnd = sprite.EndTime; } } [BackgroundDependencyLoader] private void load(OsuGameBase game, TextureStore textureStore) { - var spritePath = Definition.Path.ToLowerInvariant(); + var spritePath = Sprite.Path.ToLowerInvariant(); var path = game.Beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == spritePath)?.FileInfo.StoragePath; if (path == null) return; Texture = textureStore.Get(path); - Definition.ApplyTransforms(this); + Sprite.ApplyTransforms(this); } } } diff --git a/osu.Game/Storyboards/IElementDefinition.cs b/osu.Game/Storyboards/IStoryboardElement.cs similarity index 84% rename from osu.Game/Storyboards/IElementDefinition.cs rename to osu.Game/Storyboards/IStoryboardElement.cs index 93c9a473f7aa..d5fc86b0f700 100644 --- a/osu.Game/Storyboards/IElementDefinition.cs +++ b/osu.Game/Storyboards/IStoryboardElement.cs @@ -5,7 +5,7 @@ namespace osu.Game.Storyboards { - public interface IElementDefinition + public interface IStoryboardElement { string Path { get; } Drawable CreateDrawable(); diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs new file mode 100644 index 000000000000..111cdd5d415e --- /dev/null +++ b/osu.Game/Storyboards/Storyboard.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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; + +namespace osu.Game.Storyboards +{ + public class Storyboard + { + private readonly Dictionary layers = new Dictionary(); + public IEnumerable Layers => layers.Values; + + public Storyboard() + { + layers.Add("Background", new StoryboardLayer("Background", 3)); + layers.Add("Fail", new StoryboardLayer("Fail", 2) { EnabledWhenPassing = false, }); + layers.Add("Pass", new StoryboardLayer("Pass", 1) { EnabledWhenFailing = false, }); + layers.Add("Foreground", new StoryboardLayer("Foreground", 0)); + } + + public StoryboardLayer GetLayer(string name) + { + StoryboardLayer layer; + if (!layers.TryGetValue(name, out layer)) + layers[name] = layer = new StoryboardLayer(name, layers.Values.Min(l => l.Depth) - 1); + + return layer; + } + + public DrawableStoryboard CreateDrawable() + => new DrawableStoryboard(this); + } +} diff --git a/osu.Game/Storyboards/AnimationDefinition.cs b/osu.Game/Storyboards/StoryboardAnimation.cs similarity index 79% rename from osu.Game/Storyboards/AnimationDefinition.cs rename to osu.Game/Storyboards/StoryboardAnimation.cs index 3ac9cbfe0ee1..98936df9e5d3 100644 --- a/osu.Game/Storyboards/AnimationDefinition.cs +++ b/osu.Game/Storyboards/StoryboardAnimation.cs @@ -7,13 +7,13 @@ namespace osu.Game.Storyboards { - public class AnimationDefinition : SpriteDefinition + public class StoryboardAnimation : StoryboardSprite { public int FrameCount; public double FrameDelay; public AnimationLoopType LoopType; - public AnimationDefinition(string path, Anchor origin, Vector2 initialPosition, int frameCount, double frameDelay, AnimationLoopType loopType) + public StoryboardAnimation(string path, Anchor origin, Vector2 initialPosition, int frameCount, double frameDelay, AnimationLoopType loopType) : base(path, origin, initialPosition) { FrameCount = frameCount; @@ -22,7 +22,7 @@ public AnimationDefinition(string path, Anchor origin, Vector2 initialPosition, } public override Drawable CreateDrawable() - => new StoryboardAnimation(this); + => new DrawableStoryboardAnimation(this); } public enum AnimationLoopType diff --git a/osu.Game/Storyboards/StoryboardDefinition.cs b/osu.Game/Storyboards/StoryboardDefinition.cs deleted file mode 100644 index 4ef24cda91d0..000000000000 --- a/osu.Game/Storyboards/StoryboardDefinition.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// 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; - -namespace osu.Game.Storyboards -{ - public class StoryboardDefinition - { - private readonly Dictionary layers = new Dictionary(); - public IEnumerable 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) { EnabledWhenFailing = 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); - } -} diff --git a/osu.Game/Storyboards/LayerDefinition.cs b/osu.Game/Storyboards/StoryboardLayer.cs similarity index 55% rename from osu.Game/Storyboards/LayerDefinition.cs rename to osu.Game/Storyboards/StoryboardLayer.cs index baefe1626add..f565b13eb5db 100644 --- a/osu.Game/Storyboards/LayerDefinition.cs +++ b/osu.Game/Storyboards/StoryboardLayer.cs @@ -6,28 +6,28 @@ namespace osu.Game.Storyboards { - public class LayerDefinition + public class StoryboardLayer { public string Name; public int Depth; public bool EnabledWhenPassing = true; public bool EnabledWhenFailing = true; - private readonly List elements = new List(); - public IEnumerable Elements => elements; + private readonly List elements = new List(); + public IEnumerable Elements => elements; - public LayerDefinition(string name, int depth) + public StoryboardLayer(string name, int depth) { Name = name; Depth = depth; } - public void Add(IElementDefinition element) + public void Add(IStoryboardElement element) { elements.Add(element); } - public StoryboardLayer CreateDrawable() - => new StoryboardLayer(this) { Depth = Depth, }; + public DrawableStoryboardLayer CreateDrawable() + => new DrawableStoryboardLayer(this) { Depth = Depth, }; } } diff --git a/osu.Game/Storyboards/SampleDefinition.cs b/osu.Game/Storyboards/StoryboardSample.cs similarity index 77% rename from osu.Game/Storyboards/SampleDefinition.cs rename to osu.Game/Storyboards/StoryboardSample.cs index 5d5e8ef1e929..bcf6a4329df0 100644 --- a/osu.Game/Storyboards/SampleDefinition.cs +++ b/osu.Game/Storyboards/StoryboardSample.cs @@ -5,13 +5,13 @@ namespace osu.Game.Storyboards { - public class SampleDefinition : IElementDefinition + public class StoryboardSample : IStoryboardElement { public string Path { get; set; } public double Time; public float Volume; - public SampleDefinition(string path, double time, float volume) + public StoryboardSample(string path, double time, float volume) { Path = path; Time = time; diff --git a/osu.Game/Storyboards/SpriteDefinition.cs b/osu.Game/Storyboards/StoryboardSprite.cs similarity index 95% rename from osu.Game/Storyboards/SpriteDefinition.cs rename to osu.Game/Storyboards/StoryboardSprite.cs index 4e5c8f890313..598167d72093 100644 --- a/osu.Game/Storyboards/SpriteDefinition.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -10,7 +10,7 @@ namespace osu.Game.Storyboards { - public class SpriteDefinition : IElementDefinition + public class StoryboardSprite : IStoryboardElement { private readonly List loops = new List(); private readonly List triggers = new List(); @@ -34,7 +34,7 @@ public class SpriteDefinition : IElementDefinition private delegate void DrawablePropertyInitializer(Drawable drawable, T value); private delegate void DrawableTransformer(Drawable drawable, T value, double duration, Easing easing); - public SpriteDefinition(string path, Anchor origin, Vector2 initialPosition) + public StoryboardSprite(string path, Anchor origin, Vector2 initialPosition) { Path = path; Origin = origin; @@ -56,7 +56,7 @@ public CommandTrigger AddTrigger(string triggerName, double startTime, double en } public virtual Drawable CreateDrawable() - => new StoryboardSprite(this); + => new DrawableStoryboardSprite(this); public void ApplyTransforms(Drawable drawable, IEnumerable> triggeredGroups = null) { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f9980b0b5828..737d0cc33405 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -81,21 +81,21 @@ - - - - - + + + + + - + - - - - + + + +