mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 10:18:22 +08:00
Merge branch 'master' into preset-td-autoplay
This commit is contained in:
commit
71776a583c
@ -30,23 +30,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestAddOverlappingControlPoints()
|
||||
{
|
||||
createVisualiser(true);
|
||||
|
||||
addControlPointStep(new Vector2(200));
|
||||
addControlPointStep(new Vector2(300));
|
||||
addControlPointStep(new Vector2(300));
|
||||
addControlPointStep(new Vector2(500, 300));
|
||||
|
||||
AddAssert("last connection displayed", () =>
|
||||
{
|
||||
var lastConnection = visualiser.Connections.Last(c => c.ControlPoint.Position == new Vector2(300));
|
||||
return lastConnection.DrawWidth > 50;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPerfectCurveTooManyPoints()
|
||||
{
|
||||
@ -194,24 +177,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
addAssertPointPositionChanged(points, i);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStackingUpdatesConnectionPosition()
|
||||
{
|
||||
createVisualiser(true);
|
||||
|
||||
Vector2 connectionPosition;
|
||||
addControlPointStep(connectionPosition = new Vector2(300));
|
||||
addControlPointStep(new Vector2(600));
|
||||
|
||||
// Apply a big number in stacking so the person running the test can clearly see if it fails
|
||||
AddStep("apply stacking", () => slider.StackHeightBindable.Value += 10);
|
||||
|
||||
AddAssert($"Connection at {connectionPosition} changed",
|
||||
() => visualiser.Connections[0].Position,
|
||||
() => !Is.EqualTo(connectionPosition)
|
||||
);
|
||||
}
|
||||
|
||||
private void addAssertPointPositionChanged(Vector2[] points, int index)
|
||||
{
|
||||
AddAssert($"Point at {points.ElementAt(index)} changed",
|
||||
|
@ -4,10 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Lines;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
@ -15,36 +12,21 @@ using osuTK;
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A visualisation of the line between two <see cref="PathControlPointPiece{T}"/>s.
|
||||
/// A visualisation of the lines between <see cref="PathControlPointPiece{T}"/>s.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of <see cref="OsuHitObject"/> which this <see cref="PathControlPointConnectionPiece{T}"/> visualises.</typeparam>
|
||||
public partial class PathControlPointConnectionPiece<T> : CompositeDrawable where T : OsuHitObject, IHasPath
|
||||
/// <typeparam name="T">The type of <see cref="OsuHitObject"/> which this <see cref="PathControlPointConnection{T}"/> visualises.</typeparam>
|
||||
public partial class PathControlPointConnection<T> : SmoothPath where T : OsuHitObject, IHasPath
|
||||
{
|
||||
public readonly PathControlPoint ControlPoint;
|
||||
|
||||
private readonly Path path;
|
||||
private readonly T hitObject;
|
||||
public int ControlPointIndex { get; set; }
|
||||
|
||||
private IBindable<Vector2> hitObjectPosition;
|
||||
private IBindable<int> pathVersion;
|
||||
private IBindable<int> stackHeight;
|
||||
|
||||
public PathControlPointConnectionPiece(T hitObject, int controlPointIndex)
|
||||
public PathControlPointConnection(T hitObject)
|
||||
{
|
||||
this.hitObject = hitObject;
|
||||
ControlPointIndex = controlPointIndex;
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
ControlPoint = hitObject.Path.ControlPoints[controlPointIndex];
|
||||
|
||||
InternalChild = path = new SmoothPath
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
PathRadius = 1
|
||||
};
|
||||
PathRadius = 1;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -68,18 +50,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
/// </summary>
|
||||
private void updateConnectingPath()
|
||||
{
|
||||
Position = hitObject.StackedPosition + ControlPoint.Position;
|
||||
Position = hitObject.StackedPosition;
|
||||
|
||||
path.ClearVertices();
|
||||
ClearVertices();
|
||||
|
||||
int nextIndex = ControlPointIndex + 1;
|
||||
if (nextIndex == 0 || nextIndex >= hitObject.Path.ControlPoints.Count)
|
||||
return;
|
||||
foreach (var controlPoint in hitObject.Path.ControlPoints)
|
||||
AddVertex(controlPoint.Position);
|
||||
|
||||
path.AddVertex(Vector2.Zero);
|
||||
path.AddVertex(hitObject.Path.ControlPoints[nextIndex].Position - ControlPoint.Position);
|
||||
|
||||
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
||||
OriginPosition = PositionInBoundingBox(Vector2.Zero);
|
||||
}
|
||||
}
|
||||
}
|
@ -37,7 +37,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield.
|
||||
|
||||
internal readonly Container<PathControlPointPiece<T>> Pieces;
|
||||
internal readonly Container<PathControlPointConnectionPiece<T>> Connections;
|
||||
|
||||
private readonly IBindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||
private readonly T hitObject;
|
||||
@ -63,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
Connections = new Container<PathControlPointConnectionPiece<T>> { RelativeSizeAxes = Axes.Both },
|
||||
new PathControlPointConnection<T>(hitObject),
|
||||
Pieces = new Container<PathControlPointPiece<T>> { RelativeSizeAxes = Axes.Both }
|
||||
};
|
||||
}
|
||||
@ -78,6 +77,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
controlPoints.BindTo(hitObject.Path.ControlPoints);
|
||||
}
|
||||
|
||||
// Generally all the control points are within the visible area all the time.
|
||||
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Handles correction of invalid path types.
|
||||
/// </summary>
|
||||
@ -185,17 +187,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
Debug.Assert(e.NewItems != null);
|
||||
|
||||
// If inserting in the path (not appending),
|
||||
// update indices of existing connections after insert location
|
||||
if (e.NewStartingIndex < Pieces.Count)
|
||||
{
|
||||
foreach (var connection in Connections)
|
||||
{
|
||||
if (connection.ControlPointIndex >= e.NewStartingIndex)
|
||||
connection.ControlPointIndex += e.NewItems.Count;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < e.NewItems.Count; i++)
|
||||
{
|
||||
var point = (PathControlPoint)e.NewItems[i];
|
||||
@ -209,8 +200,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
d.DragInProgress = DragInProgress;
|
||||
d.DragEnded = DragEnded;
|
||||
}));
|
||||
|
||||
Connections.Add(new PathControlPointConnectionPiece<T>(hitObject, e.NewStartingIndex + i));
|
||||
}
|
||||
|
||||
break;
|
||||
@ -222,19 +211,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
foreach (var piece in Pieces.Where(p => p.ControlPoint == point).ToArray())
|
||||
piece.RemoveAndDisposeImmediately();
|
||||
foreach (var connection in Connections.Where(c => c.ControlPoint == point).ToArray())
|
||||
connection.RemoveAndDisposeImmediately();
|
||||
}
|
||||
|
||||
// If removing before the end of the path,
|
||||
// update indices of connections after remove location
|
||||
if (e.OldStartingIndex < Pieces.Count)
|
||||
{
|
||||
foreach (var connection in Connections)
|
||||
{
|
||||
if (connection.ControlPointIndex >= e.OldStartingIndex)
|
||||
connection.ControlPointIndex -= e.OldItems.Count;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@ -23,6 +24,7 @@ using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Scores.IO
|
||||
{
|
||||
@ -284,6 +286,196 @@ namespace osu.Game.Tests.Scores.IO
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserLookedUpForOnlineScore()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host, true);
|
||||
|
||||
var api = (DummyAPIAccess)osu.API;
|
||||
api.HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case GetUserRequest userRequest:
|
||||
userRequest.TriggerSuccess(new APIUser
|
||||
{
|
||||
Username = "Test user",
|
||||
CountryCode = CountryCode.JP,
|
||||
Id = 1234
|
||||
});
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
|
||||
|
||||
var toImport = new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.B,
|
||||
TotalScore = 987654,
|
||||
Accuracy = 0.8,
|
||||
MaxCombo = 500,
|
||||
Combo = 250,
|
||||
User = new APIUser { Username = "Test user" },
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = 12345,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BeatmapInfo = beatmap.Beatmaps.First()
|
||||
};
|
||||
|
||||
var imported = LoadScoreIntoOsu(osu, toImport);
|
||||
|
||||
Assert.AreEqual(toImport.Rank, imported.Rank);
|
||||
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
|
||||
Assert.AreEqual(toImport.Accuracy, imported.Accuracy);
|
||||
Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo);
|
||||
Assert.AreEqual(toImport.User.Username, imported.User.Username);
|
||||
Assert.AreEqual(toImport.Date, imported.Date);
|
||||
Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
|
||||
Assert.AreEqual(toImport.User.Username, imported.RealmUser.Username);
|
||||
Assert.AreEqual(1234, imported.RealmUser.OnlineID);
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserLookedUpForLegacyOnlineScore()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host, true);
|
||||
|
||||
var api = (DummyAPIAccess)osu.API;
|
||||
api.HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case GetUserRequest userRequest:
|
||||
userRequest.TriggerSuccess(new APIUser
|
||||
{
|
||||
Username = "Test user",
|
||||
CountryCode = CountryCode.JP,
|
||||
Id = 1234
|
||||
});
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
|
||||
|
||||
var toImport = new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.B,
|
||||
TotalScore = 987654,
|
||||
Accuracy = 0.8,
|
||||
MaxCombo = 500,
|
||||
Combo = 250,
|
||||
User = new APIUser { Username = "Test user" },
|
||||
Date = DateTimeOffset.Now,
|
||||
LegacyOnlineID = 12345,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BeatmapInfo = beatmap.Beatmaps.First()
|
||||
};
|
||||
|
||||
var imported = LoadScoreIntoOsu(osu, toImport);
|
||||
|
||||
Assert.AreEqual(toImport.Rank, imported.Rank);
|
||||
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
|
||||
Assert.AreEqual(toImport.Accuracy, imported.Accuracy);
|
||||
Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo);
|
||||
Assert.AreEqual(toImport.User.Username, imported.User.Username);
|
||||
Assert.AreEqual(toImport.Date, imported.Date);
|
||||
Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
|
||||
Assert.AreEqual(toImport.User.Username, imported.RealmUser.Username);
|
||||
Assert.AreEqual(1234, imported.RealmUser.OnlineID);
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserNotLookedUpForOfflineScore()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host, true);
|
||||
|
||||
var api = (DummyAPIAccess)osu.API;
|
||||
api.HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case GetUserRequest userRequest:
|
||||
userRequest.TriggerSuccess(new APIUser
|
||||
{
|
||||
Username = "Test user",
|
||||
CountryCode = CountryCode.JP,
|
||||
Id = 1234
|
||||
});
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
|
||||
|
||||
var toImport = new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.B,
|
||||
TotalScore = 987654,
|
||||
Accuracy = 0.8,
|
||||
MaxCombo = 500,
|
||||
Combo = 250,
|
||||
User = new APIUser { Username = "Test user" },
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = -1,
|
||||
LegacyOnlineID = -1,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BeatmapInfo = beatmap.Beatmaps.First()
|
||||
};
|
||||
|
||||
var imported = LoadScoreIntoOsu(osu, toImport);
|
||||
|
||||
Assert.AreEqual(toImport.Rank, imported.Rank);
|
||||
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
|
||||
Assert.AreEqual(toImport.Accuracy, imported.Accuracy);
|
||||
Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo);
|
||||
Assert.AreEqual(toImport.User.Username, imported.User.Username);
|
||||
Assert.AreEqual(toImport.Date, imported.Date);
|
||||
Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
|
||||
Assert.AreEqual(toImport.User.Username, imported.RealmUser.Username);
|
||||
Assert.That(imported.RealmUser.OnlineID, Is.LessThanOrEqualTo(1));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ScoreInfo LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
|
||||
{
|
||||
// clone to avoid attaching the input score to realm.
|
||||
|
@ -85,8 +85,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
if (scaleTransformProvided)
|
||||
{
|
||||
sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current, Time.Current + 1000, 1, 2);
|
||||
sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current + 1000, Time.Current + 2000, 2, 1);
|
||||
sprite.Commands.AddScale(Easing.None, Time.Current, Time.Current + 1000, 1, 2);
|
||||
sprite.Commands.AddScale(Easing.None, Time.Current + 1000, Time.Current + 2000, 2, 1);
|
||||
}
|
||||
|
||||
layer.Elements.Clear();
|
||||
@ -211,7 +211,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
var layer = storyboard.GetLayer("Background");
|
||||
|
||||
var sprite = new StoryboardSprite(lookupName, origin, initialPosition);
|
||||
sprite.AddLoop(Time.Current, 100).Alpha.Add(Easing.None, 0, 10000, 1, 1);
|
||||
var loop = sprite.AddLoopingGroup(Time.Current, 100);
|
||||
loop.AddAlpha(Easing.None, 0, 10000, 1, 1);
|
||||
|
||||
layer.Elements.Clear();
|
||||
layer.Add(sprite);
|
||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
|
||||
|
||||
sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
|
||||
sprite.Commands.AddAlpha(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
|
||||
|
||||
storyboard.GetLayer("Background").Add(sprite);
|
||||
|
||||
@ -73,17 +73,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
|
||||
|
||||
// these should be ignored as we have an alpha visibility blocker proceeding this command.
|
||||
sprite.TimelineGroup.Scale.Add(Easing.None, loop_start_time, -18000, 0, 1);
|
||||
var loopGroup = sprite.AddLoop(loop_start_time, 50);
|
||||
loopGroup.Scale.Add(Easing.None, loop_start_time, -18000, 0, 1);
|
||||
sprite.Commands.AddScale(Easing.None, loop_start_time, -18000, 0, 1);
|
||||
var loopGroup = sprite.AddLoopingGroup(loop_start_time, 50);
|
||||
loopGroup.AddScale(Easing.None, loop_start_time, -18000, 0, 1);
|
||||
|
||||
var target = addEventToLoop ? loopGroup : sprite.TimelineGroup;
|
||||
var target = addEventToLoop ? loopGroup : sprite.Commands;
|
||||
double loopRelativeOffset = addEventToLoop ? -loop_start_time : 0;
|
||||
target.Alpha.Add(Easing.None, loopRelativeOffset + firstStoryboardEvent, loopRelativeOffset + firstStoryboardEvent + 500, 0, 1);
|
||||
target.AddAlpha(Easing.None, loopRelativeOffset + firstStoryboardEvent, loopRelativeOffset + firstStoryboardEvent + 500, 0, 1);
|
||||
|
||||
// these should be ignored due to being in the future.
|
||||
sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
|
||||
loopGroup.Alpha.Add(Easing.None, 38000, 40000, 0, 1);
|
||||
sprite.Commands.AddAlpha(Easing.None, 18000, 20000, 0, 1);
|
||||
loopGroup.AddAlpha(Easing.None, 38000, 40000, 0, 1);
|
||||
|
||||
storyboard.GetLayer("Background").Add(sprite);
|
||||
|
||||
|
264
osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs
Normal file
264
osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs
Normal file
@ -0,0 +1,264 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneStoryboardCommands : OsuTestScene
|
||||
{
|
||||
[Cached(typeof(Storyboard))]
|
||||
private TestStoryboard storyboard { get; set; } = new TestStoryboard
|
||||
{
|
||||
UseSkinSprites = false,
|
||||
AlwaysProvideTexture = true,
|
||||
};
|
||||
|
||||
private readonly ManualClock manualClock = new ManualClock { Rate = 1, IsRunning = true };
|
||||
private int clockDirection;
|
||||
|
||||
private const string lookup_name = "hitcircleoverlay";
|
||||
private const double clock_limit = 2500;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
private Container content = null!;
|
||||
private SpriteText timelineText = null!;
|
||||
private Box timelineMarker = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
base.Content.Children = new Drawable[]
|
||||
{
|
||||
content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
timelineText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Bottom = 60 },
|
||||
},
|
||||
timelineMarker = new Box
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Size = new Vector2(2, 50),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("start clock", () => clockDirection = 1);
|
||||
AddStep("pause clock", () => clockDirection = 0);
|
||||
AddStep("set clock = 0", () => manualClock.CurrentTime = 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNormalCommandPlayback()
|
||||
{
|
||||
AddStep("create storyboard", () => Child = createStoryboard(s =>
|
||||
{
|
||||
s.Commands.AddY(Easing.OutBounce, 500, 900, 100, 240);
|
||||
s.Commands.AddY(Easing.OutQuint, 1100, 1500, 240, 100);
|
||||
}));
|
||||
|
||||
assert(0, 100);
|
||||
assert(500, 100);
|
||||
assert(1000, 240);
|
||||
assert(1500, 100);
|
||||
assert(clock_limit, 100);
|
||||
assert(1500, 100);
|
||||
assert(1000, 240);
|
||||
assert(500, 100);
|
||||
assert(0, 100);
|
||||
|
||||
void assert(double time, double y)
|
||||
{
|
||||
AddStep($"set clock = {time}", () => manualClock.CurrentTime = time);
|
||||
AddAssert($"sprite y = {y} at t = {time}", () => this.ChildrenOfType<DrawableStoryboardSprite>().Single().Y == y);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLoopingCommandsPlayback()
|
||||
{
|
||||
AddStep("create storyboard", () => Child = createStoryboard(s =>
|
||||
{
|
||||
var loop = s.AddLoopingGroup(250, 1);
|
||||
loop.AddY(Easing.OutBounce, 0, 400, 100, 240);
|
||||
loop.AddY(Easing.OutQuint, 600, 1000, 240, 100);
|
||||
}));
|
||||
|
||||
assert(0, 100);
|
||||
assert(250, 100);
|
||||
assert(850, 240);
|
||||
assert(1250, 100);
|
||||
assert(1850, 240);
|
||||
assert(2250, 100);
|
||||
assert(clock_limit, 100);
|
||||
assert(2250, 100);
|
||||
assert(1850, 240);
|
||||
assert(1250, 100);
|
||||
assert(850, 240);
|
||||
assert(250, 100);
|
||||
assert(0, 100);
|
||||
|
||||
void assert(double time, double y)
|
||||
{
|
||||
AddStep($"set clock = {time}", () => manualClock.CurrentTime = time);
|
||||
AddAssert($"sprite y = {y} at t = {time}", () => this.ChildrenOfType<DrawableStoryboardSprite>().Single().Y == y);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLoopManyTimes()
|
||||
{
|
||||
AddStep("create storyboard", () => Child = createStoryboard(s =>
|
||||
{
|
||||
var loop = s.AddLoopingGroup(500, 10000);
|
||||
loop.AddY(Easing.OutBounce, 0, 60, 100, 240);
|
||||
loop.AddY(Easing.OutQuint, 80, 120, 240, 100);
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestParameterTemporaryEffect()
|
||||
{
|
||||
AddStep("create storyboard", () => Child = createStoryboard(s =>
|
||||
{
|
||||
s.Commands.AddFlipV(Easing.None, 1000, 1500, true, false);
|
||||
}));
|
||||
|
||||
AddAssert("sprite not flipped at t = 0", () => !this.ChildrenOfType<DrawableStoryboardSprite>().Single().FlipV);
|
||||
|
||||
AddStep("set clock = 1250", () => manualClock.CurrentTime = 1250);
|
||||
AddAssert("sprite flipped at t = 1250", () => this.ChildrenOfType<DrawableStoryboardSprite>().Single().FlipV);
|
||||
|
||||
AddStep("set clock = 2000", () => manualClock.CurrentTime = 2000);
|
||||
AddAssert("sprite not flipped at t = 2000", () => !this.ChildrenOfType<DrawableStoryboardSprite>().Single().FlipV);
|
||||
|
||||
AddStep("resume clock", () => clockDirection = 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestParameterPermanentEffect()
|
||||
{
|
||||
AddStep("create storyboard", () => Child = createStoryboard(s =>
|
||||
{
|
||||
s.Commands.AddFlipV(Easing.None, 1000, 1000, true, true);
|
||||
}));
|
||||
|
||||
AddAssert("sprite flipped at t = 0", () => this.ChildrenOfType<DrawableStoryboardSprite>().Single().FlipV);
|
||||
|
||||
AddStep("set clock = 1250", () => manualClock.CurrentTime = 1250);
|
||||
AddAssert("sprite flipped at t = 1250", () => this.ChildrenOfType<DrawableStoryboardSprite>().Single().FlipV);
|
||||
|
||||
AddStep("set clock = 2000", () => manualClock.CurrentTime = 2000);
|
||||
AddAssert("sprite flipped at t = 2000", () => this.ChildrenOfType<DrawableStoryboardSprite>().Single().FlipV);
|
||||
|
||||
AddStep("resume clock", () => clockDirection = 1);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (manualClock.CurrentTime > clock_limit || manualClock.CurrentTime < 0)
|
||||
clockDirection = -clockDirection;
|
||||
|
||||
manualClock.CurrentTime += Time.Elapsed * clockDirection;
|
||||
timelineText.Text = $"Time: {manualClock.CurrentTime:0}ms";
|
||||
timelineMarker.X = (float)(manualClock.CurrentTime / clock_limit);
|
||||
}
|
||||
|
||||
private DrawableStoryboard createStoryboard(Action<StoryboardSprite>? addCommands = null)
|
||||
{
|
||||
var layer = storyboard.GetLayer("Background");
|
||||
|
||||
var sprite = new StoryboardSprite(lookup_name, Anchor.Centre, new Vector2(320, 240));
|
||||
sprite.Commands.AddScale(Easing.None, 0, clock_limit, 0.5f, 0.5f);
|
||||
sprite.Commands.AddAlpha(Easing.None, 0, clock_limit, 1, 1);
|
||||
addCommands?.Invoke(sprite);
|
||||
|
||||
layer.Elements.Clear();
|
||||
layer.Add(sprite);
|
||||
|
||||
return storyboard.CreateDrawable().With(c => c.Clock = new FramedClock(manualClock));
|
||||
}
|
||||
|
||||
private partial class TestStoryboard : Storyboard
|
||||
{
|
||||
public override DrawableStoryboard CreateDrawable(IReadOnlyList<Mod>? mods = null)
|
||||
{
|
||||
return new TestDrawableStoryboard(this, mods);
|
||||
}
|
||||
|
||||
public bool AlwaysProvideTexture { get; set; }
|
||||
|
||||
public override string GetStoragePathFromStoryboardPath(string path) => AlwaysProvideTexture ? path : string.Empty;
|
||||
|
||||
private partial class TestDrawableStoryboard : DrawableStoryboard
|
||||
{
|
||||
private readonly bool alwaysProvideTexture;
|
||||
|
||||
public TestDrawableStoryboard(TestStoryboard storyboard, IReadOnlyList<Mod>? mods)
|
||||
: base(storyboard, mods)
|
||||
{
|
||||
alwaysProvideTexture = storyboard.AlwaysProvideTexture;
|
||||
}
|
||||
|
||||
protected override IResourceStore<byte[]> CreateResourceLookupStore() => alwaysProvideTexture
|
||||
? new AlwaysReturnsTextureStore()
|
||||
: new ResourceStore<byte[]>();
|
||||
|
||||
internal class AlwaysReturnsTextureStore : IResourceStore<byte[]>
|
||||
{
|
||||
private const string test_image = "Resources/Textures/test-image.png";
|
||||
|
||||
private readonly DllResourceStore store;
|
||||
|
||||
public AlwaysReturnsTextureStore()
|
||||
{
|
||||
store = TestResources.GetStore();
|
||||
}
|
||||
|
||||
public void Dispose() => store.Dispose();
|
||||
|
||||
public byte[] Get(string name) => store.Get(test_image);
|
||||
|
||||
public Task<byte[]> GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => store.GetAsync(test_image, cancellationToken);
|
||||
|
||||
public Stream GetStream(string name) => store.GetStream(test_image);
|
||||
|
||||
public IEnumerable<string> GetAvailableResources() => store.GetAvailableResources();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
var storyboard = new Storyboard();
|
||||
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
|
||||
sprite.TimelineGroup.Alpha.Add(Easing.None, startTime, 0, 0, 1);
|
||||
sprite.Commands.AddAlpha(Easing.None, startTime, 0, 0, 1);
|
||||
storyboard.GetLayer("Background").Add(sprite);
|
||||
return storyboard;
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
var storyboard = new Storyboard();
|
||||
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
|
||||
sprite.TimelineGroup.Alpha.Add(Easing.None, 0, duration, 1, 0);
|
||||
sprite.Commands.AddAlpha(Easing.None, 0, duration, 1, 0);
|
||||
storyboard.GetLayer("Background").Add(sprite);
|
||||
return storyboard;
|
||||
}
|
||||
|
@ -424,7 +424,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public void TestIntroStoryboardElement() => testLeadIn(b =>
|
||||
{
|
||||
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
|
||||
sprite.TimelineGroup.Alpha.Add(Easing.None, -2000, 0, 0, 1);
|
||||
sprite.Commands.AddAlpha(Easing.None, -2000, 0, 0, 1);
|
||||
b.Storyboard.GetLayer("Background").Add(sprite);
|
||||
});
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
@ -21,8 +22,10 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private NowPlayingOverlay nowPlayingOverlay;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(FrameworkConfigManager frameworkConfig)
|
||||
{
|
||||
AddToggleStep("toggle unicode", v => frameworkConfig.SetValue(FrameworkSetting.ShowUnicode, v));
|
||||
|
||||
nowPlayingOverlay = new NowPlayingOverlay
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
@ -50,9 +53,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Metadata =
|
||||
{
|
||||
Artist = "very very very very very very very very very very verry long artist",
|
||||
ArtistUnicode = "very very very very very very very very very very verry long artist",
|
||||
ArtistUnicode = "very very very very very very very very very very verry long artist unicode",
|
||||
Title = "very very very very very verry long title",
|
||||
TitleUnicode = "very very very very very verry long title",
|
||||
TitleUnicode = "very very very very very verry long title unicode",
|
||||
}
|
||||
}));
|
||||
|
||||
@ -61,9 +64,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Metadata =
|
||||
{
|
||||
Artist = "very very very very very very very very very very verrry long artist",
|
||||
ArtistUnicode = "very very very very very very very very very very verrry long artist",
|
||||
ArtistUnicode = "not very long artist unicode",
|
||||
Title = "very very very very very verrry long title",
|
||||
TitleUnicode = "very very very very very verrry long title",
|
||||
TitleUnicode = "not very long title unicode",
|
||||
}
|
||||
}));
|
||||
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Storyboards.Commands;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -17,7 +18,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
public class LegacyStoryboardDecoder : LegacyDecoder<Storyboard>
|
||||
{
|
||||
private StoryboardSprite? storyboardSprite;
|
||||
private CommandTimelineGroup? timelineGroup;
|
||||
private StoryboardCommandGroup? currentCommandsGroup;
|
||||
|
||||
private Storyboard storyboard = null!;
|
||||
|
||||
@ -164,7 +165,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
else
|
||||
{
|
||||
if (depth < 2)
|
||||
timelineGroup = storyboardSprite?.TimelineGroup;
|
||||
currentCommandsGroup = storyboardSprite?.Commands;
|
||||
|
||||
string commandType = split[0];
|
||||
|
||||
@ -176,7 +177,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
double startTime = split.Length > 2 ? Parsing.ParseDouble(split[2]) : double.MinValue;
|
||||
double endTime = split.Length > 3 ? Parsing.ParseDouble(split[3]) : double.MaxValue;
|
||||
int groupNumber = split.Length > 4 ? Parsing.ParseInt(split[4]) : 0;
|
||||
timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber);
|
||||
currentCommandsGroup = storyboardSprite?.AddTriggerGroup(triggerName, startTime, endTime, groupNumber);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -184,7 +185,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
double startTime = Parsing.ParseDouble(split[1]);
|
||||
int repeatCount = Parsing.ParseInt(split[2]);
|
||||
timelineGroup = storyboardSprite?.AddLoop(startTime, Math.Max(0, repeatCount - 1));
|
||||
currentCommandsGroup = storyboardSprite?.AddLoopingGroup(startTime, Math.Max(0, repeatCount - 1));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -203,7 +204,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
float startValue = Parsing.ParseFloat(split[4]);
|
||||
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
|
||||
timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
|
||||
currentCommandsGroup?.AddAlpha(easing, startTime, endTime, startValue, endValue);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -211,7 +212,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
float startValue = Parsing.ParseFloat(split[4]);
|
||||
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
|
||||
timelineGroup?.Scale.Add(easing, startTime, endTime, startValue, endValue);
|
||||
currentCommandsGroup?.AddScale(easing, startTime, endTime, startValue, endValue);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -221,7 +222,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
float startY = Parsing.ParseFloat(split[5]);
|
||||
float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX;
|
||||
float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY;
|
||||
timelineGroup?.VectorScale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
|
||||
currentCommandsGroup?.AddVectorScale(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -229,7 +230,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
float startValue = Parsing.ParseFloat(split[4]);
|
||||
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
|
||||
timelineGroup?.Rotation.Add(easing, startTime, endTime, float.RadiansToDegrees(startValue), float.RadiansToDegrees(endValue));
|
||||
currentCommandsGroup?.AddRotation(easing, startTime, endTime, float.RadiansToDegrees(startValue), float.RadiansToDegrees(endValue));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -239,8 +240,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
float startY = Parsing.ParseFloat(split[5]);
|
||||
float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX;
|
||||
float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY;
|
||||
timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
|
||||
timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
|
||||
currentCommandsGroup?.AddX(easing, startTime, endTime, startX, endX);
|
||||
currentCommandsGroup?.AddY(easing, startTime, endTime, startY, endY);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -248,7 +249,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
float startValue = Parsing.ParseFloat(split[4]);
|
||||
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
|
||||
timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
|
||||
currentCommandsGroup?.AddX(easing, startTime, endTime, startValue, endValue);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -256,7 +257,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
float startValue = Parsing.ParseFloat(split[4]);
|
||||
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
|
||||
timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
|
||||
currentCommandsGroup?.AddY(easing, startTime, endTime, startValue, endValue);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -268,7 +269,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
float endRed = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startRed;
|
||||
float endGreen = split.Length > 8 ? Parsing.ParseFloat(split[8]) : startGreen;
|
||||
float endBlue = split.Length > 9 ? Parsing.ParseFloat(split[9]) : startBlue;
|
||||
timelineGroup?.Colour.Add(easing, startTime, endTime,
|
||||
currentCommandsGroup?.AddColour(easing, startTime, endTime,
|
||||
new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
|
||||
new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
|
||||
break;
|
||||
@ -281,16 +282,16 @@ namespace osu.Game.Beatmaps.Formats
|
||||
switch (type)
|
||||
{
|
||||
case "A":
|
||||
timelineGroup?.BlendingParameters.Add(easing, startTime, endTime, BlendingParameters.Additive,
|
||||
currentCommandsGroup?.AddBlendingParameters(easing, startTime, endTime, BlendingParameters.Additive,
|
||||
startTime == endTime ? BlendingParameters.Additive : BlendingParameters.Inherit);
|
||||
break;
|
||||
|
||||
case "H":
|
||||
timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime);
|
||||
currentCommandsGroup?.AddFlipH(easing, startTime, endTime, true, startTime == endTime);
|
||||
break;
|
||||
|
||||
case "V":
|
||||
timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime);
|
||||
currentCommandsGroup?.AddFlipV(easing, startTime, endTime, true, startTime == endTime);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -479,6 +480,11 @@ namespace osu.Game.Overlays
|
||||
private OsuSpriteText mainSpriteText = null!;
|
||||
private OsuSpriteText fillerSpriteText = null!;
|
||||
|
||||
private Bindable<bool> showUnicode = null!;
|
||||
|
||||
[Resolved]
|
||||
private FrameworkConfigManager frameworkConfig { get; set; } = null!;
|
||||
|
||||
private LocalisableString text;
|
||||
|
||||
public LocalisableString Text
|
||||
@ -531,6 +537,9 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
showUnicode = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowUnicode);
|
||||
showUnicode.BindValueChanged(_ => updateText());
|
||||
|
||||
updateFontAndText();
|
||||
}
|
||||
|
||||
|
@ -127,6 +127,9 @@ namespace osu.Game.Scoring
|
||||
if (model.RealmUser.OnlineID == APIUser.SYSTEM_USER_ID)
|
||||
return;
|
||||
|
||||
if (model.OnlineID < 0 && model.LegacyOnlineID <= 0)
|
||||
return;
|
||||
|
||||
string username = model.RealmUser.Username;
|
||||
|
||||
if (usernameLookupCache.TryGetValue(username, out var existing))
|
||||
|
@ -16,6 +16,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -138,7 +139,7 @@ namespace osu.Game.Screens.Play
|
||||
},
|
||||
automaticDownload = new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Automatically download beatmaps",
|
||||
LabelText = OnlineSettingsStrings.AutomaticallyDownloadMissingBeatmaps,
|
||||
Current = config.GetBindable<bool>(OsuSetting.AutomaticallyDownloadMissingBeatmaps),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -1,52 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class CommandLoop : CommandTimelineGroup
|
||||
{
|
||||
public double LoopStartTime;
|
||||
|
||||
/// <summary>
|
||||
/// The total number of times this loop is played back. Always greater than zero.
|
||||
/// </summary>
|
||||
public readonly int TotalIterations;
|
||||
|
||||
public override double StartTime => LoopStartTime + CommandsStartTime;
|
||||
|
||||
public override double EndTime =>
|
||||
// In an ideal world, we would multiply the command duration by TotalIterations here.
|
||||
// Unfortunately this would clash with how stable handled end times, and results in some storyboards playing outro
|
||||
// sequences for minutes or hours.
|
||||
StartTime + CommandsDuration;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new command loop.
|
||||
/// </summary>
|
||||
/// <param name="startTime">The start time of the loop.</param>
|
||||
/// <param name="repeatCount">The number of times the loop should repeat. Should be greater than zero. Zero means a single playback.</param>
|
||||
public CommandLoop(double startTime, int repeatCount)
|
||||
{
|
||||
if (repeatCount < 0) throw new ArgumentException("Repeat count must be zero or above.", nameof(repeatCount));
|
||||
|
||||
LoopStartTime = startTime;
|
||||
TotalIterations = repeatCount + 1;
|
||||
}
|
||||
|
||||
public override IEnumerable<CommandTimeline<T>.TypedCommand> GetCommands<T>(CommandTimelineSelector<T> timelineSelector, double offset = 0)
|
||||
{
|
||||
for (int loop = 0; loop < TotalIterations; loop++)
|
||||
{
|
||||
double loopOffset = LoopStartTime + loop * CommandsDuration;
|
||||
foreach (var command in base.GetCommands(timelineSelector, offset + loopOffset))
|
||||
yield return command;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{LoopStartTime} x{TotalIterations}";
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class CommandTimeline<T> : ICommandTimeline
|
||||
{
|
||||
private readonly List<TypedCommand> commands = new List<TypedCommand>();
|
||||
|
||||
public IEnumerable<TypedCommand> Commands => commands.OrderBy(c => c.StartTime);
|
||||
|
||||
public bool HasCommands => commands.Count > 0;
|
||||
|
||||
public double StartTime { get; private set; } = double.MaxValue;
|
||||
public double EndTime { get; private set; } = double.MinValue;
|
||||
|
||||
public T StartValue { get; private set; }
|
||||
public T EndValue { get; private set; }
|
||||
|
||||
public void Add(Easing easing, double startTime, double endTime, T startValue, T endValue)
|
||||
{
|
||||
if (endTime < startTime)
|
||||
{
|
||||
endTime = startTime;
|
||||
}
|
||||
|
||||
commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue });
|
||||
|
||||
if (startTime < StartTime)
|
||||
{
|
||||
StartValue = startValue;
|
||||
StartTime = startTime;
|
||||
}
|
||||
|
||||
if (endTime > EndTime)
|
||||
{
|
||||
EndValue = endValue;
|
||||
EndTime = endTime;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{commands.Count} command(s)";
|
||||
|
||||
public class TypedCommand : ICommand
|
||||
{
|
||||
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 int CompareTo(ICommand other)
|
||||
{
|
||||
int result = StartTime.CompareTo(other.StartTime);
|
||||
if (result != 0) return result;
|
||||
|
||||
return EndTime.CompareTo(other.EndTime);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{StartTime} -> {EndTime}, {StartValue} -> {EndValue} {Easing}";
|
||||
}
|
||||
}
|
||||
|
||||
public interface ICommandTimeline
|
||||
{
|
||||
double StartTime { get; }
|
||||
double EndTime { get; }
|
||||
bool HasCommands { get; }
|
||||
}
|
||||
|
||||
public interface ICommand : IComparable<ICommand>
|
||||
{
|
||||
Easing Easing { get; set; }
|
||||
double StartTime { get; set; }
|
||||
double EndTime { get; set; }
|
||||
double Duration { get; }
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public delegate CommandTimeline<T> CommandTimelineSelector<T>(CommandTimelineGroup commandTimelineGroup);
|
||||
|
||||
public class CommandTimelineGroup
|
||||
{
|
||||
public CommandTimeline<float> X = new CommandTimeline<float>();
|
||||
public CommandTimeline<float> Y = new CommandTimeline<float>();
|
||||
public CommandTimeline<float> Scale = new CommandTimeline<float>();
|
||||
public CommandTimeline<Vector2> VectorScale = new CommandTimeline<Vector2>();
|
||||
public CommandTimeline<float> Rotation = new CommandTimeline<float>();
|
||||
public CommandTimeline<Color4> Colour = new CommandTimeline<Color4>();
|
||||
public CommandTimeline<float> Alpha = new CommandTimeline<float>();
|
||||
public CommandTimeline<BlendingParameters> BlendingParameters = new CommandTimeline<BlendingParameters>();
|
||||
public CommandTimeline<bool> FlipH = new CommandTimeline<bool>();
|
||||
public CommandTimeline<bool> FlipV = new CommandTimeline<bool>();
|
||||
|
||||
private readonly ICommandTimeline[] timelines;
|
||||
|
||||
public CommandTimelineGroup()
|
||||
{
|
||||
timelines = new ICommandTimeline[]
|
||||
{
|
||||
X,
|
||||
Y,
|
||||
Scale,
|
||||
VectorScale,
|
||||
Rotation,
|
||||
Colour,
|
||||
Alpha,
|
||||
BlendingParameters,
|
||||
FlipH,
|
||||
FlipV
|
||||
};
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public double CommandsStartTime
|
||||
{
|
||||
get
|
||||
{
|
||||
double min = double.MaxValue;
|
||||
|
||||
for (int i = 0; i < timelines.Length; i++)
|
||||
min = Math.Min(min, timelines[i].StartTime);
|
||||
|
||||
return min;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public double CommandsEndTime
|
||||
{
|
||||
get
|
||||
{
|
||||
double max = double.MinValue;
|
||||
|
||||
for (int i = 0; i < timelines.Length; i++)
|
||||
max = Math.Max(max, timelines[i].EndTime);
|
||||
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public double CommandsDuration => CommandsEndTime - CommandsStartTime;
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual double StartTime => CommandsStartTime;
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual double EndTime => CommandsEndTime;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool HasCommands
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 0; i < timelines.Length; i++)
|
||||
{
|
||||
if (timelines[i].HasCommands)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<CommandTimeline<T>.TypedCommand> GetCommands<T>(CommandTimelineSelector<T> timelineSelector, double offset = 0)
|
||||
{
|
||||
if (offset != 0)
|
||||
{
|
||||
return timelineSelector(this).Commands.Select(command =>
|
||||
new CommandTimeline<T>.TypedCommand
|
||||
{
|
||||
Easing = command.Easing,
|
||||
StartTime = offset + command.StartTime,
|
||||
EndTime = offset + command.EndTime,
|
||||
StartValue = command.StartValue,
|
||||
EndValue = command.EndValue,
|
||||
});
|
||||
}
|
||||
|
||||
return timelineSelector(this).Commands;
|
||||
}
|
||||
}
|
||||
}
|
46
osu.Game/Storyboards/Commands/IStoryboardCommand.cs
Normal file
46
osu.Game/Storyboards/Commands/IStoryboardCommand.cs
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public interface IStoryboardCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// The start time of the storyboard command.
|
||||
/// </summary>
|
||||
double StartTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The end time of the storyboard command.
|
||||
/// </summary>
|
||||
double EndTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="Drawable"/> property affected by this storyboard command.
|
||||
/// Used to apply initial property values based on the list of commands given in <see cref="StoryboardSprite"/>.
|
||||
/// </summary>
|
||||
string PropertyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the corresponding property in <see cref="Drawable"/> to the start value of this command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Parameter commands (e.g. <see cref="StoryboardFlipHCommand"/> / <see cref="StoryboardFlipVCommand"/> / <see cref="StoryboardBlendingParametersCommand"/>) only apply the start value if they have zero duration, i.e. take "permanent" effect regardless of time.
|
||||
/// </remarks>
|
||||
/// <param name="d">The target drawable.</param>
|
||||
void ApplyInitialValue<TDrawable>(TDrawable d)
|
||||
where TDrawable : Drawable, IFlippable, IVectorScalable;
|
||||
|
||||
/// <summary>
|
||||
/// Applies the transforms described by this storyboard command to the target drawable.
|
||||
/// </summary>
|
||||
/// <param name="d">The target drawable.</param>
|
||||
/// <returns>The sequence of transforms applied to the target drawable.</returns>
|
||||
TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
where TDrawable : Drawable, IFlippable, IVectorScalable;
|
||||
}
|
||||
}
|
23
osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs
Normal file
23
osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class StoryboardAlphaCommand : StoryboardCommand<float>
|
||||
{
|
||||
public StoryboardAlphaCommand(Easing easing, double startTime, double endTime, float startValue, float endValue)
|
||||
: base(easing, startTime, endTime, startValue, endValue)
|
||||
{
|
||||
}
|
||||
|
||||
public override string PropertyName => nameof(Drawable.Alpha);
|
||||
|
||||
public override void ApplyInitialValue<TDrawable>(TDrawable d) => d.Alpha = StartValue;
|
||||
|
||||
public override TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
=> d.FadeTo(StartValue).Then().FadeTo(EndValue, Duration, Easing);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class StoryboardBlendingParametersCommand : StoryboardCommand<BlendingParameters>
|
||||
{
|
||||
public StoryboardBlendingParametersCommand(Easing easing, double startTime, double endTime, BlendingParameters startValue, BlendingParameters endValue)
|
||||
: base(easing, startTime, endTime, startValue, endValue)
|
||||
{
|
||||
}
|
||||
|
||||
public override string PropertyName => nameof(Drawable.Blending);
|
||||
|
||||
public override void ApplyInitialValue<TDrawable>(TDrawable d)
|
||||
{
|
||||
if (StartTime == EndTime)
|
||||
d.Blending = StartValue;
|
||||
}
|
||||
|
||||
public override TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
=> d.TransformTo(nameof(d.Blending), StartValue).Delay(Duration)
|
||||
.TransformTo(nameof(d.Blending), EndValue);
|
||||
}
|
||||
}
|
24
osu.Game/Storyboards/Commands/StoryboardColourCommand.cs
Normal file
24
osu.Game/Storyboards/Commands/StoryboardColourCommand.cs
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class StoryboardColourCommand : StoryboardCommand<Color4>
|
||||
{
|
||||
public StoryboardColourCommand(Easing easing, double startTime, double endTime, Color4 startValue, Color4 endValue)
|
||||
: base(easing, startTime, endTime, startValue, endValue)
|
||||
{
|
||||
}
|
||||
|
||||
public override string PropertyName => nameof(Drawable.Colour);
|
||||
|
||||
public override void ApplyInitialValue<TDrawable>(TDrawable d) => d.Colour = StartValue;
|
||||
|
||||
public override TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
=> d.FadeColour(StartValue).Then().FadeColour(EndValue, Duration, Easing);
|
||||
}
|
||||
}
|
56
osu.Game/Storyboards/Commands/StoryboardCommand.cs
Normal file
56
osu.Game/Storyboards/Commands/StoryboardCommand.cs
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public abstract class StoryboardCommand<T> : IStoryboardCommand, IComparable<StoryboardCommand<T>>
|
||||
{
|
||||
public double StartTime { get; }
|
||||
public double EndTime { get; }
|
||||
|
||||
public T StartValue { get; }
|
||||
public T EndValue { get; }
|
||||
public Easing Easing { get; }
|
||||
|
||||
public double Duration => EndTime - StartTime;
|
||||
|
||||
protected StoryboardCommand(Easing easing, double startTime, double endTime, T startValue, T endValue)
|
||||
{
|
||||
if (endTime < startTime)
|
||||
endTime = startTime;
|
||||
|
||||
StartTime = startTime;
|
||||
StartValue = startValue;
|
||||
EndTime = endTime;
|
||||
EndValue = endValue;
|
||||
Easing = easing;
|
||||
}
|
||||
|
||||
public abstract string PropertyName { get; }
|
||||
|
||||
public abstract void ApplyInitialValue<TDrawable>(TDrawable d)
|
||||
where TDrawable : Drawable, IFlippable, IVectorScalable;
|
||||
|
||||
public abstract TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
where TDrawable : Drawable, IFlippable, IVectorScalable;
|
||||
|
||||
public int CompareTo(StoryboardCommand<T>? other)
|
||||
{
|
||||
if (other == null)
|
||||
return 1;
|
||||
|
||||
int result = StartTime.CompareTo(other.StartTime);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
return EndTime.CompareTo(other.EndTime);
|
||||
}
|
||||
|
||||
public override string ToString() => $"{StartTime} -> {EndTime}, {StartValue} -> {EndValue} {Easing}";
|
||||
}
|
||||
}
|
130
osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs
Normal file
130
osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Lists;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class StoryboardCommandGroup
|
||||
{
|
||||
private readonly SortedList<StoryboardCommand<float>> x = new SortedList<StoryboardCommand<float>>();
|
||||
|
||||
public IReadOnlyList<StoryboardCommand<float>> X => x;
|
||||
|
||||
private readonly SortedList<StoryboardCommand<float>> y = new SortedList<StoryboardCommand<float>>();
|
||||
|
||||
public IReadOnlyList<StoryboardCommand<float>> Y => y;
|
||||
|
||||
private readonly SortedList<StoryboardCommand<float>> scale = new SortedList<StoryboardCommand<float>>();
|
||||
|
||||
public IReadOnlyList<StoryboardCommand<float>> Scale => scale;
|
||||
|
||||
private readonly SortedList<StoryboardCommand<Vector2>> vectorScale = new SortedList<StoryboardCommand<Vector2>>();
|
||||
|
||||
public IReadOnlyList<StoryboardCommand<Vector2>> VectorScale => vectorScale;
|
||||
|
||||
private readonly SortedList<StoryboardCommand<float>> rotation = new SortedList<StoryboardCommand<float>>();
|
||||
|
||||
public IReadOnlyList<StoryboardCommand<float>> Rotation => rotation;
|
||||
|
||||
private readonly SortedList<StoryboardCommand<Color4>> colour = new SortedList<StoryboardCommand<Color4>>();
|
||||
|
||||
public IReadOnlyList<StoryboardCommand<Color4>> Colour => colour;
|
||||
|
||||
private readonly SortedList<StoryboardCommand<float>> alpha = new SortedList<StoryboardCommand<float>>();
|
||||
|
||||
public IReadOnlyList<StoryboardCommand<float>> Alpha => alpha;
|
||||
|
||||
private readonly SortedList<StoryboardCommand<BlendingParameters>> blendingParameters = new SortedList<StoryboardCommand<BlendingParameters>>();
|
||||
|
||||
public IReadOnlyList<StoryboardCommand<BlendingParameters>> BlendingParameters => blendingParameters;
|
||||
|
||||
private readonly SortedList<StoryboardCommand<bool>> flipH = new SortedList<StoryboardCommand<bool>>();
|
||||
|
||||
public IReadOnlyList<StoryboardCommand<bool>> FlipH => flipH;
|
||||
|
||||
private readonly SortedList<StoryboardCommand<bool>> flipV = new SortedList<StoryboardCommand<bool>>();
|
||||
|
||||
public IReadOnlyList<StoryboardCommand<bool>> FlipV => flipV;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the earliest start time of the commands added to this group.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public double StartTime { get; private set; } = double.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the latest end time of the commands added to this group.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public double EndTime { get; private set; } = double.MinValue;
|
||||
|
||||
[JsonIgnore]
|
||||
public double Duration => EndTime - StartTime;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool HasCommands { get; private set; }
|
||||
|
||||
private readonly IReadOnlyList<IStoryboardCommand>[] lists;
|
||||
|
||||
public IEnumerable<IStoryboardCommand> AllCommands => lists.SelectMany(g => g);
|
||||
|
||||
public StoryboardCommandGroup()
|
||||
{
|
||||
lists = new IReadOnlyList<IStoryboardCommand>[] { X, Y, Scale, VectorScale, Rotation, Colour, Alpha, BlendingParameters, FlipH, FlipV };
|
||||
}
|
||||
|
||||
public void AddX(Easing easing, double startTime, double endTime, float startValue, float endValue)
|
||||
=> AddCommand(x, new StoryboardXCommand(easing, startTime, endTime, startValue, endValue));
|
||||
|
||||
public void AddY(Easing easing, double startTime, double endTime, float startValue, float endValue)
|
||||
=> AddCommand(y, new StoryboardYCommand(easing, startTime, endTime, startValue, endValue));
|
||||
|
||||
public void AddScale(Easing easing, double startTime, double endTime, float startValue, float endValue)
|
||||
=> AddCommand(scale, new StoryboardScaleCommand(easing, startTime, endTime, startValue, endValue));
|
||||
|
||||
public void AddVectorScale(Easing easing, double startTime, double endTime, Vector2 startValue, Vector2 endValue)
|
||||
=> AddCommand(vectorScale, new StoryboardVectorScaleCommand(easing, startTime, endTime, startValue, endValue));
|
||||
|
||||
public void AddRotation(Easing easing, double startTime, double endTime, float startValue, float endValue)
|
||||
=> AddCommand(rotation, new StoryboardRotationCommand(easing, startTime, endTime, startValue, endValue));
|
||||
|
||||
public void AddColour(Easing easing, double startTime, double endTime, Color4 startValue, Color4 endValue)
|
||||
=> AddCommand(colour, new StoryboardColourCommand(easing, startTime, endTime, startValue, endValue));
|
||||
|
||||
public void AddAlpha(Easing easing, double startTime, double endTime, float startValue, float endValue)
|
||||
=> AddCommand(alpha, new StoryboardAlphaCommand(easing, startTime, endTime, startValue, endValue));
|
||||
|
||||
public void AddBlendingParameters(Easing easing, double startTime, double endTime, BlendingParameters startValue, BlendingParameters endValue)
|
||||
=> AddCommand(blendingParameters, new StoryboardBlendingParametersCommand(easing, startTime, endTime, startValue, endValue));
|
||||
|
||||
public void AddFlipH(Easing easing, double startTime, double endTime, bool startValue, bool endValue)
|
||||
=> AddCommand(flipH, new StoryboardFlipHCommand(easing, startTime, endTime, startValue, endValue));
|
||||
|
||||
public void AddFlipV(Easing easing, double startTime, double endTime, bool startValue, bool endValue)
|
||||
=> AddCommand(flipV, new StoryboardFlipVCommand(easing, startTime, endTime, startValue, endValue));
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given storyboard <paramref name="command"/> to the target <paramref name="list"/>.
|
||||
/// Can be overriden to apply custom effects to the given command before adding it to the list (e.g. looping or time offsets).
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The value type of the target property affected by this storyboard command.</typeparam>
|
||||
protected virtual void AddCommand<T>(ICollection<StoryboardCommand<T>> list, StoryboardCommand<T> command)
|
||||
{
|
||||
list.Add(command);
|
||||
HasCommands = true;
|
||||
|
||||
if (command.StartTime < StartTime)
|
||||
StartTime = command.StartTime;
|
||||
|
||||
if (command.EndTime > EndTime)
|
||||
EndTime = command.EndTime;
|
||||
}
|
||||
}
|
||||
}
|
29
osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs
Normal file
29
osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class StoryboardFlipHCommand : StoryboardCommand<bool>
|
||||
{
|
||||
public StoryboardFlipHCommand(Easing easing, double startTime, double endTime, bool startValue, bool endValue)
|
||||
: base(easing, startTime, endTime, startValue, endValue)
|
||||
{
|
||||
}
|
||||
|
||||
public override string PropertyName => nameof(IFlippable.FlipH);
|
||||
|
||||
public override void ApplyInitialValue<TDrawable>(TDrawable d)
|
||||
{
|
||||
if (StartTime == EndTime)
|
||||
d.FlipH = StartValue;
|
||||
}
|
||||
|
||||
public override TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
=> d.TransformTo(nameof(IFlippable.FlipH), StartValue).Delay(Duration)
|
||||
.TransformTo(nameof(IFlippable.FlipH), EndValue);
|
||||
}
|
||||
}
|
29
osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs
Normal file
29
osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class StoryboardFlipVCommand : StoryboardCommand<bool>
|
||||
{
|
||||
public StoryboardFlipVCommand(Easing easing, double startTime, double endTime, bool startValue, bool endValue)
|
||||
: base(easing, startTime, endTime, startValue, endValue)
|
||||
{
|
||||
}
|
||||
|
||||
public override string PropertyName => nameof(IFlippable.FlipV);
|
||||
|
||||
public override void ApplyInitialValue<TDrawable>(TDrawable d)
|
||||
{
|
||||
if (StartTime == EndTime)
|
||||
d.FlipV = StartValue;
|
||||
}
|
||||
|
||||
public override TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
=> d.TransformTo(nameof(IFlippable.FlipV), StartValue).Delay(Duration)
|
||||
.TransformTo(nameof(IFlippable.FlipV), EndValue);
|
||||
}
|
||||
}
|
66
osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs
Normal file
66
osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class StoryboardLoopingGroup : StoryboardCommandGroup
|
||||
{
|
||||
private readonly double loopStartTime;
|
||||
|
||||
/// <summary>
|
||||
/// The total number of times this loop is played back. Always greater than zero.
|
||||
/// </summary>
|
||||
public readonly int TotalIterations;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new command loop.
|
||||
/// </summary>
|
||||
/// <param name="startTime">The start time of the loop.</param>
|
||||
/// <param name="repeatCount">The number of times the loop should repeat. Should be greater than zero. Zero means a single playback.</param>
|
||||
public StoryboardLoopingGroup(double startTime, int repeatCount)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(repeatCount);
|
||||
|
||||
loopStartTime = startTime;
|
||||
TotalIterations = repeatCount + 1;
|
||||
}
|
||||
|
||||
protected override void AddCommand<T>(ICollection<StoryboardCommand<T>> list, StoryboardCommand<T> command)
|
||||
=> base.AddCommand(list, new StoryboardLoopingCommand<T>(command, this));
|
||||
|
||||
public override string ToString() => $"{loopStartTime} x{TotalIterations}";
|
||||
|
||||
private class StoryboardLoopingCommand<T> : StoryboardCommand<T>
|
||||
{
|
||||
private readonly StoryboardCommand<T> command;
|
||||
private readonly StoryboardLoopingGroup loopingGroup;
|
||||
|
||||
public StoryboardLoopingCommand(StoryboardCommand<T> command, StoryboardLoopingGroup loopingGroup)
|
||||
// In an ideal world, we would multiply the command duration by TotalIterations in command end time.
|
||||
// Unfortunately this would clash with how stable handled end times, and results in some storyboards playing outro
|
||||
// sequences for minutes or hours.
|
||||
: base(command.Easing, loopingGroup.loopStartTime + command.StartTime, loopingGroup.loopStartTime + command.EndTime, command.StartValue, command.EndValue)
|
||||
{
|
||||
this.command = command;
|
||||
this.loopingGroup = loopingGroup;
|
||||
}
|
||||
|
||||
public override string PropertyName => command.PropertyName;
|
||||
|
||||
public override void ApplyInitialValue<TDrawable>(TDrawable d) => command.ApplyInitialValue(d);
|
||||
|
||||
public override TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
{
|
||||
if (loopingGroup.TotalIterations == 0)
|
||||
return command.ApplyTransforms(d);
|
||||
|
||||
double loopingGroupDuration = loopingGroup.Duration;
|
||||
return command.ApplyTransforms(d).Loop(loopingGroupDuration - Duration, loopingGroup.TotalIterations);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs
Normal file
23
osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class StoryboardRotationCommand : StoryboardCommand<float>
|
||||
{
|
||||
public StoryboardRotationCommand(Easing easing, double startTime, double endTime, float startValue, float endValue)
|
||||
: base(easing, startTime, endTime, startValue, endValue)
|
||||
{
|
||||
}
|
||||
|
||||
public override string PropertyName => nameof(Drawable.Rotation);
|
||||
|
||||
public override void ApplyInitialValue<TDrawable>(TDrawable d) => d.Rotation = StartValue;
|
||||
|
||||
public override TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
=> d.RotateTo(StartValue).Then().RotateTo(EndValue, Duration, Easing);
|
||||
}
|
||||
}
|
24
osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs
Normal file
24
osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class StoryboardScaleCommand : StoryboardCommand<float>
|
||||
{
|
||||
public StoryboardScaleCommand(Easing easing, double startTime, double endTime, float startValue, float endValue)
|
||||
: base(easing, startTime, endTime, startValue, endValue)
|
||||
{
|
||||
}
|
||||
|
||||
public override string PropertyName => nameof(Drawable.Scale);
|
||||
|
||||
public override void ApplyInitialValue<TDrawable>(TDrawable d) => d.Scale = new Vector2(StartValue);
|
||||
|
||||
public override TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
=> d.ScaleTo(StartValue).Then().ScaleTo(EndValue, Duration, Easing);
|
||||
}
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class CommandTrigger : CommandTimelineGroup
|
||||
public class StoryboardTriggerGroup : StoryboardCommandGroup
|
||||
{
|
||||
public string TriggerName;
|
||||
public double TriggerStartTime;
|
||||
public double TriggerEndTime;
|
||||
public int GroupNumber;
|
||||
|
||||
public CommandTrigger(string triggerName, double startTime, double endTime, int groupNumber)
|
||||
public StoryboardTriggerGroup(string triggerName, double startTime, double endTime, int groupNumber)
|
||||
{
|
||||
TriggerName = triggerName;
|
||||
TriggerStartTime = startTime;
|
@ -0,0 +1,26 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class StoryboardVectorScaleCommand : StoryboardCommand<Vector2>
|
||||
{
|
||||
public StoryboardVectorScaleCommand(Easing easing, double startTime, double endTime, Vector2 startValue, Vector2 endValue)
|
||||
: base(easing, startTime, endTime, startValue, endValue)
|
||||
{
|
||||
}
|
||||
|
||||
public override string PropertyName => nameof(IVectorScalable.VectorScale);
|
||||
|
||||
public override void ApplyInitialValue<TDrawable>(TDrawable d) => d.VectorScale = StartValue;
|
||||
|
||||
public override TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
=> d.TransformTo(nameof(d.VectorScale), StartValue).Then()
|
||||
.TransformTo(nameof(d.VectorScale), EndValue, Duration, Easing);
|
||||
}
|
||||
}
|
23
osu.Game/Storyboards/Commands/StoryboardXCommand.cs
Normal file
23
osu.Game/Storyboards/Commands/StoryboardXCommand.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class StoryboardXCommand : StoryboardCommand<float>
|
||||
{
|
||||
public StoryboardXCommand(Easing easing, double startTime, double endTime, float startValue, float endValue)
|
||||
: base(easing, startTime, endTime, startValue, endValue)
|
||||
{
|
||||
}
|
||||
|
||||
public override string PropertyName => nameof(Drawable.X);
|
||||
|
||||
public override void ApplyInitialValue<TDrawable>(TDrawable d) => d.X = StartValue;
|
||||
|
||||
public override TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
=> d.MoveToX(StartValue).Then().MoveToX(EndValue, Duration, Easing);
|
||||
}
|
||||
}
|
23
osu.Game/Storyboards/Commands/StoryboardYCommand.cs
Normal file
23
osu.Game/Storyboards/Commands/StoryboardYCommand.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
|
||||
namespace osu.Game.Storyboards.Commands
|
||||
{
|
||||
public class StoryboardYCommand : StoryboardCommand<float>
|
||||
{
|
||||
public StoryboardYCommand(Easing easing, double startTime, double endTime, float startValue, float endValue)
|
||||
: base(easing, startTime, endTime, startValue, endValue)
|
||||
{
|
||||
}
|
||||
|
||||
public override string PropertyName => nameof(Drawable.Y);
|
||||
|
||||
public override void ApplyInitialValue<TDrawable>(TDrawable d) => d.Y = StartValue;
|
||||
|
||||
public override TransformSequence<TDrawable> ApplyTransforms<TDrawable>(TDrawable d)
|
||||
=> d.MoveToY(StartValue).Then().MoveToY(EndValue, Duration, Easing);
|
||||
}
|
||||
}
|
@ -1,11 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Graphics.Video;
|
||||
using osu.Framework.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
@ -13,7 +18,7 @@ namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
public readonly StoryboardVideo Video;
|
||||
|
||||
private Video? drawableVideo;
|
||||
private DrawableVideo? drawableVideo;
|
||||
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
|
||||
@ -25,7 +30,7 @@ namespace osu.Game.Storyboards.Drawables
|
||||
// This allows scaling based on the video's absolute size.
|
||||
//
|
||||
// If not specified we take up the full available space.
|
||||
bool useRelative = !video.TimelineGroup.Scale.HasCommands;
|
||||
bool useRelative = !video.Commands.Scale.Any();
|
||||
|
||||
RelativeSizeAxes = useRelative ? Axes.Both : Axes.None;
|
||||
AutoSizeAxes = useRelative ? Axes.None : Axes.Both;
|
||||
@ -42,7 +47,7 @@ namespace osu.Game.Storyboards.Drawables
|
||||
if (stream == null)
|
||||
return;
|
||||
|
||||
InternalChild = drawableVideo = new Video(stream, false)
|
||||
InternalChild = drawableVideo = new DrawableVideo(stream, false)
|
||||
{
|
||||
RelativeSizeAxes = RelativeSizeAxes,
|
||||
FillMode = FillMode.Fill,
|
||||
@ -70,5 +75,65 @@ namespace osu.Game.Storyboards.Drawables
|
||||
drawableVideo.FadeOut(500);
|
||||
}
|
||||
}
|
||||
|
||||
private partial class DrawableVideo : Video, IFlippable, IVectorScalable
|
||||
{
|
||||
private bool flipH;
|
||||
|
||||
public bool FlipH
|
||||
{
|
||||
get => flipH;
|
||||
set
|
||||
{
|
||||
if (flipH == value)
|
||||
return;
|
||||
|
||||
flipH = value;
|
||||
Invalidate(Invalidation.MiscGeometry);
|
||||
}
|
||||
}
|
||||
|
||||
private bool flipV;
|
||||
|
||||
public bool FlipV
|
||||
{
|
||||
get => flipV;
|
||||
set
|
||||
{
|
||||
if (flipV == value)
|
||||
return;
|
||||
|
||||
flipV = value;
|
||||
Invalidate(Invalidation.MiscGeometry);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 vectorScale = Vector2.One;
|
||||
|
||||
public Vector2 VectorScale
|
||||
{
|
||||
get => vectorScale;
|
||||
set
|
||||
{
|
||||
if (vectorScale == value)
|
||||
return;
|
||||
|
||||
if (!Validation.IsFinite(value)) throw new ArgumentException($@"{nameof(VectorScale)} must be finite, but is {value}.");
|
||||
|
||||
vectorScale = value;
|
||||
Invalidate(Invalidation.MiscGeometry);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 DrawScale
|
||||
=> new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale;
|
||||
|
||||
public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV);
|
||||
|
||||
public DrawableVideo(Stream stream, bool startAtCurrentTime = true)
|
||||
: base(stream, startAtCurrentTime)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
public static class DrawablesExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adjusts <see cref="Drawable.Blending"/> after a delay.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
|
||||
public static TransformSequence<T> TransformBlendingMode<T>(this T drawable, BlendingParameters newValue, double delay = 0)
|
||||
where T : Drawable
|
||||
=> drawable.TransformTo(drawable.PopulateTransform(new TransformBlendingParameters(), newValue, delay));
|
||||
}
|
||||
|
||||
public class TransformBlendingParameters : Transform<BlendingParameters, Drawable>
|
||||
{
|
||||
private BlendingParameters valueAt(double time)
|
||||
=> time < EndTime ? StartValue : EndValue;
|
||||
|
||||
public override string TargetMember => nameof(Drawable.Blending);
|
||||
|
||||
protected override void Apply(Drawable d, double time) => d.Blending = valueAt(time);
|
||||
protected override void ReadIntoStartValue(Drawable d) => StartValue = d.Blending;
|
||||
}
|
||||
}
|
@ -1,55 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
internal interface IFlippable : ITransformable
|
||||
public interface IFlippable : IDrawable
|
||||
{
|
||||
bool FlipH { get; set; }
|
||||
bool FlipV { get; set; }
|
||||
}
|
||||
|
||||
internal class TransformFlipH : Transform<bool, IFlippable>
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
internal class TransformFlipV : Transform<bool, IFlippable>
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
internal static class FlippableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adjusts <see cref="IFlippable.FlipH"/> after a delay.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
|
||||
public static TransformSequence<T> TransformFlipH<T>(this T flippable, bool newValue, double delay = 0)
|
||||
where T : class, IFlippable
|
||||
=> flippable.TransformTo(flippable.PopulateTransform(new TransformFlipH(), newValue, delay));
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts <see cref="IFlippable.FlipV"/> after a delay.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
|
||||
public static TransformSequence<T> TransformFlipV<T>(this T flippable, bool newValue, double delay = 0)
|
||||
where T : class, IFlippable
|
||||
=> flippable.TransformTo(flippable.PopulateTransform(new TransformFlipV(), newValue, delay));
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
internal interface IVectorScalable : ITransformable
|
||||
public interface IVectorScalable : IDrawable
|
||||
{
|
||||
Vector2 VectorScale { get; set; }
|
||||
}
|
||||
|
||||
internal static class VectorScalableExtensions
|
||||
{
|
||||
public static TransformSequence<T> VectorScaleTo<T>(this T target, Vector2 newVectorScale, double duration = 0, Easing easing = Easing.None)
|
||||
where T : class, IVectorScalable
|
||||
=> target.TransformTo(nameof(IVectorScalable.VectorScale), newVectorScale, duration, easing);
|
||||
}
|
||||
}
|
||||
|
@ -21,8 +21,7 @@ namespace osu.Game.Storyboards
|
||||
LoopType = loopType;
|
||||
}
|
||||
|
||||
public override Drawable CreateDrawable()
|
||||
=> new DrawableStoryboardAnimation(this);
|
||||
public override Drawable CreateDrawable() => new DrawableStoryboardAnimation(this);
|
||||
}
|
||||
|
||||
public enum AnimationLoopType
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Storyboards.Commands;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using osuTK;
|
||||
|
||||
@ -12,8 +13,8 @@ namespace osu.Game.Storyboards
|
||||
{
|
||||
public class StoryboardSprite : IStoryboardElementWithDuration
|
||||
{
|
||||
private readonly List<CommandLoop> loops = new List<CommandLoop>();
|
||||
private readonly List<CommandTrigger> triggers = new List<CommandTrigger>();
|
||||
private readonly List<StoryboardLoopingGroup> loopingGroups = new List<StoryboardLoopingGroup>();
|
||||
private readonly List<StoryboardTriggerGroup> triggerGroups = new List<StoryboardTriggerGroup>();
|
||||
|
||||
public string Path { get; }
|
||||
public bool IsDrawable => HasCommands;
|
||||
@ -21,7 +22,7 @@ namespace osu.Game.Storyboards
|
||||
public Anchor Origin;
|
||||
public Vector2 InitialPosition;
|
||||
|
||||
public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup();
|
||||
public readonly StoryboardCommandGroup Commands = new StoryboardCommandGroup();
|
||||
|
||||
public double StartTime
|
||||
{
|
||||
@ -34,13 +35,13 @@ namespace osu.Game.Storyboards
|
||||
// anything before that point can be ignored (the sprite is not visible after all).
|
||||
var alphaCommands = new List<(double startTime, bool isZeroStartValue)>();
|
||||
|
||||
var command = TimelineGroup.Alpha.Commands.FirstOrDefault();
|
||||
var command = Commands.Alpha.FirstOrDefault();
|
||||
if (command != null) alphaCommands.Add((command.StartTime, command.StartValue == 0));
|
||||
|
||||
foreach (var loop in loops)
|
||||
foreach (var loop in loopingGroups)
|
||||
{
|
||||
command = loop.Alpha.Commands.FirstOrDefault();
|
||||
if (command != null) alphaCommands.Add((command.StartTime + loop.LoopStartTime, command.StartValue == 0));
|
||||
command = loop.Alpha.FirstOrDefault();
|
||||
if (command != null) alphaCommands.Add((command.StartTime, command.StartValue == 0));
|
||||
}
|
||||
|
||||
if (alphaCommands.Count > 0)
|
||||
@ -61,8 +62,8 @@ namespace osu.Game.Storyboards
|
||||
{
|
||||
// If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value.
|
||||
// The sprite's StartTime will be determined by the earliest command, regardless of type.
|
||||
double earliestStartTime = TimelineGroup.StartTime;
|
||||
foreach (var l in loops)
|
||||
double earliestStartTime = Commands.StartTime;
|
||||
foreach (var l in loopingGroups)
|
||||
earliestStartTime = Math.Min(earliestStartTime, l.StartTime);
|
||||
return earliestStartTime;
|
||||
}
|
||||
@ -72,9 +73,9 @@ namespace osu.Game.Storyboards
|
||||
{
|
||||
get
|
||||
{
|
||||
double latestEndTime = TimelineGroup.EndTime;
|
||||
double latestEndTime = Commands.EndTime;
|
||||
|
||||
foreach (var l in loops)
|
||||
foreach (var l in loopingGroups)
|
||||
latestEndTime = Math.Max(latestEndTime, l.EndTime);
|
||||
|
||||
return latestEndTime;
|
||||
@ -85,20 +86,16 @@ namespace osu.Game.Storyboards
|
||||
{
|
||||
get
|
||||
{
|
||||
double latestEndTime = TimelineGroup.EndTime;
|
||||
double latestEndTime = Commands.EndTime;
|
||||
|
||||
foreach (var l in loops)
|
||||
latestEndTime = Math.Max(latestEndTime, l.StartTime + l.CommandsDuration * l.TotalIterations);
|
||||
foreach (var l in loopingGroups)
|
||||
latestEndTime = Math.Max(latestEndTime, l.StartTime + l.Duration * l.TotalIterations);
|
||||
|
||||
return latestEndTime;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands);
|
||||
|
||||
private delegate void DrawablePropertyInitializer<in T>(Drawable drawable, T value);
|
||||
|
||||
private delegate void DrawableTransformer<in T>(Drawable drawable, T value, double duration, Easing easing);
|
||||
public bool HasCommands => Commands.HasCommands || loopingGroups.Any(l => l.HasCommands);
|
||||
|
||||
public StoryboardSprite(string path, Anchor origin, Vector2 initialPosition)
|
||||
{
|
||||
@ -107,127 +104,39 @@ namespace osu.Game.Storyboards
|
||||
InitialPosition = initialPosition;
|
||||
}
|
||||
|
||||
public CommandLoop AddLoop(double startTime, int repeatCount)
|
||||
public virtual Drawable CreateDrawable() => new DrawableStoryboardSprite(this);
|
||||
|
||||
public StoryboardLoopingGroup AddLoopingGroup(double loopStartTime, int repeatCount)
|
||||
{
|
||||
var loop = new CommandLoop(startTime, repeatCount);
|
||||
loops.Add(loop);
|
||||
var loop = new StoryboardLoopingGroup(loopStartTime, repeatCount);
|
||||
loopingGroups.Add(loop);
|
||||
return loop;
|
||||
}
|
||||
|
||||
public CommandTrigger AddTrigger(string triggerName, double startTime, double endTime, int groupNumber)
|
||||
public StoryboardTriggerGroup AddTriggerGroup(string triggerName, double startTime, double endTime, int groupNumber)
|
||||
{
|
||||
var trigger = new CommandTrigger(triggerName, startTime, endTime, groupNumber);
|
||||
triggers.Add(trigger);
|
||||
var trigger = new StoryboardTriggerGroup(triggerName, startTime, endTime, groupNumber);
|
||||
triggerGroups.Add(trigger);
|
||||
return trigger;
|
||||
}
|
||||
|
||||
public virtual Drawable CreateDrawable()
|
||||
=> new DrawableStoryboardSprite(this);
|
||||
|
||||
public void ApplyTransforms(Drawable drawable, IEnumerable<Tuple<CommandTimelineGroup, double>>? triggeredGroups = null)
|
||||
public void ApplyTransforms<TDrawable>(TDrawable drawable)
|
||||
where TDrawable : Drawable, IFlippable, IVectorScalable
|
||||
{
|
||||
// For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity.
|
||||
// To achieve this, commands are "generated" as pairs of (command, initFunc, transformFunc) and batched into a contiguous list
|
||||
// The list is then stably-sorted (to preserve command order), and applied to the drawable sequentially.
|
||||
HashSet<string> appliedProperties = new HashSet<string>();
|
||||
|
||||
List<IGeneratedCommand> generated = new List<IGeneratedCommand>();
|
||||
// For performance reasons, we need to apply the commands in chronological order.
|
||||
// Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity.
|
||||
IEnumerable<IStoryboardCommand> commands = Commands.AllCommands;
|
||||
commands = commands.Concat(loopingGroups.SelectMany(l => l.AllCommands));
|
||||
|
||||
generateCommands(generated, getCommands(g => g.X, triggeredGroups), (d, value) => d.X = value, (d, value, duration, easing) => d.MoveToX(value, duration, easing));
|
||||
generateCommands(generated, getCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value, (d, value, duration, easing) => d.MoveToY(value, duration, easing));
|
||||
generateCommands(generated, getCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = new Vector2(value), (d, value, duration, easing) => d.ScaleTo(value, duration, easing));
|
||||
generateCommands(generated, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing));
|
||||
generateCommands(generated, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing));
|
||||
generateCommands(generated, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing));
|
||||
generateCommands(generated, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, _) => d.TransformBlendingMode(value, duration),
|
||||
false);
|
||||
|
||||
if (drawable is IVectorScalable vectorScalable)
|
||||
foreach (var command in commands.OrderBy(c => c.StartTime))
|
||||
{
|
||||
generateCommands(generated, getCommands(g => g.VectorScale, triggeredGroups), (_, value) => vectorScalable.VectorScale = value,
|
||||
(_, value, duration, easing) => vectorScalable.VectorScaleTo(value, duration, easing));
|
||||
}
|
||||
|
||||
if (drawable is IFlippable flippable)
|
||||
{
|
||||
generateCommands(generated, getCommands(g => g.FlipH, triggeredGroups), (_, value) => flippable.FlipH = value, (_, value, duration, _) => flippable.TransformFlipH(value, duration),
|
||||
false);
|
||||
generateCommands(generated, getCommands(g => g.FlipV, triggeredGroups), (_, value) => flippable.FlipV = value, (_, value, duration, _) => flippable.TransformFlipV(value, duration),
|
||||
false);
|
||||
}
|
||||
|
||||
foreach (var command in generated.OrderBy(g => g.StartTime))
|
||||
command.ApplyTo(drawable);
|
||||
}
|
||||
|
||||
private void generateCommands<T>(List<IGeneratedCommand> resultList, IEnumerable<CommandTimeline<T>.TypedCommand> commands,
|
||||
DrawablePropertyInitializer<T> initializeProperty, DrawableTransformer<T> transform, bool alwaysInitialize = true)
|
||||
{
|
||||
bool initialized = false;
|
||||
|
||||
foreach (var command in commands)
|
||||
{
|
||||
DrawablePropertyInitializer<T>? initFunc = null;
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
if (alwaysInitialize || command.StartTime == command.EndTime)
|
||||
initFunc = initializeProperty;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
resultList.Add(new GeneratedCommand<T>(command, initFunc, transform));
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<CommandTimeline<T>.TypedCommand> getCommands<T>(CommandTimelineSelector<T> timelineSelector, IEnumerable<Tuple<CommandTimelineGroup, double>>? triggeredGroups)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Path}, {Origin}, {InitialPosition}";
|
||||
|
||||
private interface IGeneratedCommand
|
||||
{
|
||||
double StartTime { get; }
|
||||
|
||||
void ApplyTo(Drawable drawable);
|
||||
}
|
||||
|
||||
private readonly struct GeneratedCommand<T> : IGeneratedCommand
|
||||
{
|
||||
public double StartTime => command.StartTime;
|
||||
|
||||
private readonly DrawablePropertyInitializer<T>? initializeProperty;
|
||||
private readonly DrawableTransformer<T> transform;
|
||||
private readonly CommandTimeline<T>.TypedCommand command;
|
||||
|
||||
public GeneratedCommand(CommandTimeline<T>.TypedCommand command, DrawablePropertyInitializer<T>? initializeProperty, DrawableTransformer<T> transform)
|
||||
{
|
||||
this.command = command;
|
||||
this.initializeProperty = initializeProperty;
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
public void ApplyTo(Drawable drawable)
|
||||
{
|
||||
initializeProperty?.Invoke(drawable, command.StartValue);
|
||||
if (appliedProperties.Add(command.PropertyName))
|
||||
command.ApplyInitialValue(drawable);
|
||||
|
||||
using (drawable.BeginAbsoluteSequence(command.StartTime))
|
||||
{
|
||||
transform(drawable, command.StartValue, 0, Easing.None);
|
||||
transform(drawable, command.EndValue, command.Duration, command.Easing);
|
||||
}
|
||||
command.ApplyTransforms(drawable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Storyboards
|
||||
{
|
||||
// This is just required to get a valid StartTime based on the incoming offset.
|
||||
// Actual fades are handled inside DrawableStoryboardVideo for now.
|
||||
TimelineGroup.Alpha.Add(Easing.None, offset, offset, 0, 0);
|
||||
Commands.AddAlpha(Easing.None, offset, offset, 0, 0);
|
||||
}
|
||||
|
||||
public override Drawable CreateDrawable() => new DrawableStoryboardVideo(this);
|
||||
|
@ -347,6 +347,7 @@
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GL/@EntryIndexedValue">GL</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GLSL/@EntryIndexedValue">GLSL</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HID/@EntryIndexedValue">HID</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HP/@EntryIndexedValue">HP</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSL/@EntryIndexedValue">HSL</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSPA/@EntryIndexedValue">HSPA</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSV/@EntryIndexedValue">HSV</s:String>
|
||||
|
Loading…
Reference in New Issue
Block a user