mirror of
https://github.com/ppy/osu.git
synced 2025-01-06 08:22:56 +08:00
Make DrawableStoryboardSample a SkinnableSound
Allows sharing pause logic with gameplay samples.
This commit is contained in:
parent
74e74e1c31
commit
136843c8e4
@ -4,10 +4,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
@ -106,9 +108,14 @@ namespace osu.Game.Tests.Gameplay
|
||||
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio);
|
||||
SelectedMods.Value = new[] { testedMod };
|
||||
|
||||
Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0));
|
||||
var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
|
||||
|
||||
gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
|
||||
Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0)
|
||||
{
|
||||
Child = beatmapSkinSourceContainer
|
||||
});
|
||||
|
||||
beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
|
||||
{
|
||||
Clock = gameplayContainer.GameplayClock
|
||||
});
|
||||
@ -116,7 +123,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
|
||||
AddStep("start", () => gameplayContainer.Start());
|
||||
|
||||
AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate);
|
||||
AddAssert("sample playback rate matches mod rates", () => sample.ChildrenOfType<DrawableSample>().First().AggregateFrequency.Value == expectedRate);
|
||||
}
|
||||
|
||||
private class TestSkin : LegacySkin
|
||||
@ -168,8 +175,6 @@ namespace osu.Game.Tests.Gameplay
|
||||
: base(sampleInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public new SampleChannel Channel => base.Channel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// 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.Audio.Sample;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// </summary>
|
||||
public interface IApplicableToSample : IApplicableMod
|
||||
{
|
||||
void ApplyToSample(SampleChannel sample);
|
||||
void ApplyToSample(DrawableSample sample);
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange);
|
||||
}
|
||||
|
||||
public virtual void ApplyToSample(SampleChannel sample)
|
||||
public virtual void ApplyToSample(DrawableSample sample)
|
||||
{
|
||||
sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange);
|
||||
}
|
||||
|
@ -6,11 +6,11 @@ using System.Linq;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
AdjustPitch.TriggerChange();
|
||||
}
|
||||
|
||||
public void ApplyToSample(SampleChannel sample)
|
||||
public void ApplyToSample(DrawableSample sample)
|
||||
{
|
||||
sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange);
|
||||
}
|
||||
|
@ -191,9 +191,25 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
dependencies.CacheAs(gameplayBeatmap);
|
||||
|
||||
addUnderlayComponents(GameplayClockContainer);
|
||||
addGameplayComponents(GameplayClockContainer, Beatmap.Value, playableBeatmap);
|
||||
addOverlayComponents(GameplayClockContainer, Beatmap.Value);
|
||||
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
|
||||
|
||||
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
|
||||
// full access to all skin sources.
|
||||
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
|
||||
|
||||
// load the skinning hierarchy first.
|
||||
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
|
||||
GameplayClockContainer.Add(beatmapSkinProvider.WithChild(rulesetSkinProvider));
|
||||
|
||||
rulesetSkinProvider.AddRange(new[]
|
||||
{
|
||||
// underlay and gameplay should have access the to skinning sources.
|
||||
createUnderlayComponents(),
|
||||
createGameplayComponents(Beatmap.Value, playableBeatmap)
|
||||
});
|
||||
|
||||
// add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components.
|
||||
GameplayClockContainer.Add(createOverlayComponents(Beatmap.Value));
|
||||
|
||||
if (!DrawableRuleset.AllowGameplayOverlays)
|
||||
{
|
||||
@ -238,45 +254,31 @@ namespace osu.Game.Screens.Play
|
||||
breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
|
||||
}
|
||||
|
||||
private void addUnderlayComponents(Container target)
|
||||
private Drawable createUnderlayComponents() =>
|
||||
DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
private Drawable createGameplayComponents(WorkingBeatmap working, IBeatmap playableBeatmap) => new ScalingContainer(ScalingMode.Gameplay)
|
||||
{
|
||||
target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
|
||||
private void addGameplayComponents(Container target, WorkingBeatmap working, IBeatmap playableBeatmap)
|
||||
{
|
||||
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(working.Skin);
|
||||
|
||||
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
|
||||
// full access to all skin sources.
|
||||
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
|
||||
|
||||
// load the skinning hierarchy first.
|
||||
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
|
||||
target.Add(new ScalingContainer(ScalingMode.Gameplay)
|
||||
.WithChild(beatmapSkinProvider
|
||||
.WithChild(target = rulesetSkinProvider)));
|
||||
|
||||
target.AddRange(new Drawable[]
|
||||
Children = new Drawable[]
|
||||
{
|
||||
DrawableRuleset,
|
||||
DrawableRuleset.With(r =>
|
||||
r.FrameStableComponents.Children = new Drawable[]
|
||||
{
|
||||
ScoreProcessor,
|
||||
HealthProcessor,
|
||||
breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor)
|
||||
{
|
||||
Breaks = working.Beatmap.Breaks
|
||||
}
|
||||
}),
|
||||
new ComboEffects(ScoreProcessor)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
DrawableRuleset.FrameStableComponents.AddRange(new Drawable[]
|
||||
{
|
||||
ScoreProcessor,
|
||||
HealthProcessor,
|
||||
breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor)
|
||||
{
|
||||
Breaks = working.Beatmap.Breaks
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addOverlayComponents(Container target, WorkingBeatmap working)
|
||||
private Drawable createOverlayComponents(WorkingBeatmap working) => new Container
|
||||
{
|
||||
target.AddRange(new[]
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
DimmableStoryboard.OverlayLayerContainer.CreateProxy(),
|
||||
BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
|
||||
@ -342,8 +344,8 @@ namespace osu.Game.Screens.Play
|
||||
},
|
||||
},
|
||||
failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, },
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void onBreakTimeChanged(ValueChangedEvent<bool> isBreakTime)
|
||||
{
|
||||
|
@ -22,7 +22,10 @@ namespace osu.Game.Skinning
|
||||
[Resolved]
|
||||
private ISampleStore samples { get; set; }
|
||||
|
||||
private bool requestedPlaying;
|
||||
/// <summary>
|
||||
/// Whether playback of this sound has been requested, regardless of whether it could be played or not (due to being paused, for instance).
|
||||
/// </summary>
|
||||
protected bool PlaybackRequested;
|
||||
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
public override bool RemoveCompletedTransforms => false;
|
||||
@ -39,7 +42,7 @@ namespace osu.Game.Skinning
|
||||
|
||||
protected virtual bool PlayWhenPaused => false;
|
||||
|
||||
private readonly AudioContainer<DrawableSample> samplesContainer;
|
||||
protected readonly AudioContainer<DrawableSample> SamplesContainer;
|
||||
|
||||
public SkinnableSound(ISampleInfo hitSamples)
|
||||
: this(new[] { hitSamples })
|
||||
@ -49,7 +52,7 @@ namespace osu.Game.Skinning
|
||||
public SkinnableSound(IEnumerable<ISampleInfo> hitSamples)
|
||||
{
|
||||
this.hitSamples = hitSamples.ToArray();
|
||||
InternalChild = samplesContainer = new AudioContainer<DrawableSample>();
|
||||
InternalChild = SamplesContainer = new AudioContainer<DrawableSample>();
|
||||
}
|
||||
|
||||
private readonly IBindable<bool> samplePlaybackDisabled = new Bindable<bool>();
|
||||
@ -63,7 +66,7 @@ namespace osu.Game.Skinning
|
||||
samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled);
|
||||
samplePlaybackDisabled.BindValueChanged(disabled =>
|
||||
{
|
||||
if (requestedPlaying)
|
||||
if (PlaybackRequested)
|
||||
{
|
||||
if (disabled.NewValue && !PlayWhenPaused)
|
||||
stop();
|
||||
@ -87,13 +90,13 @@ namespace osu.Game.Skinning
|
||||
|
||||
looping = value;
|
||||
|
||||
samplesContainer.ForEach(c => c.Looping = looping);
|
||||
SamplesContainer.ForEach(c => c.Looping = looping);
|
||||
}
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
requestedPlaying = true;
|
||||
PlaybackRequested = true;
|
||||
play();
|
||||
}
|
||||
|
||||
@ -102,7 +105,7 @@ namespace osu.Game.Skinning
|
||||
if (samplePlaybackDisabled.Value && !PlayWhenPaused)
|
||||
return;
|
||||
|
||||
samplesContainer.ForEach(c =>
|
||||
SamplesContainer.ForEach(c =>
|
||||
{
|
||||
if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0)
|
||||
c.Play();
|
||||
@ -111,13 +114,13 @@ namespace osu.Game.Skinning
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
requestedPlaying = false;
|
||||
PlaybackRequested = false;
|
||||
stop();
|
||||
}
|
||||
|
||||
private void stop()
|
||||
{
|
||||
samplesContainer.ForEach(c => c.Stop());
|
||||
SamplesContainer.ForEach(c => c.Stop());
|
||||
}
|
||||
|
||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||
@ -146,7 +149,7 @@ namespace osu.Game.Skinning
|
||||
return ch;
|
||||
}).Where(c => c != null);
|
||||
|
||||
samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
|
||||
SamplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
|
||||
|
||||
// Start playback internally for the new samples if the previous ones were playing beforehand.
|
||||
if (wasPlaying)
|
||||
@ -155,24 +158,24 @@ namespace osu.Game.Skinning
|
||||
|
||||
#region Re-expose AudioContainer
|
||||
|
||||
public BindableNumber<double> Volume => samplesContainer.Volume;
|
||||
public BindableNumber<double> Volume => SamplesContainer.Volume;
|
||||
|
||||
public BindableNumber<double> Balance => samplesContainer.Balance;
|
||||
public BindableNumber<double> Balance => SamplesContainer.Balance;
|
||||
|
||||
public BindableNumber<double> Frequency => samplesContainer.Frequency;
|
||||
public BindableNumber<double> Frequency => SamplesContainer.Frequency;
|
||||
|
||||
public BindableNumber<double> Tempo => samplesContainer.Tempo;
|
||||
public BindableNumber<double> Tempo => SamplesContainer.Tempo;
|
||||
|
||||
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
||||
=> samplesContainer.AddAdjustment(type, adjustBindable);
|
||||
=> SamplesContainer.AddAdjustment(type, adjustBindable);
|
||||
|
||||
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
||||
=> samplesContainer.RemoveAdjustment(type, adjustBindable);
|
||||
=> SamplesContainer.RemoveAdjustment(type, adjustBindable);
|
||||
|
||||
public void RemoveAllAdjustments(AdjustableProperty type)
|
||||
=> samplesContainer.RemoveAllAdjustments(type);
|
||||
=> SamplesContainer.RemoveAllAdjustments(type);
|
||||
|
||||
public bool IsPlaying => samplesContainer.Any(s => s.Playing);
|
||||
public bool IsPlaying => SamplesContainer.Any(s => s.Playing);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -4,15 +4,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
public class DrawableStoryboardSample : Component
|
||||
public class DrawableStoryboardSample : SkinnableSound
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of time allowable beyond the start time of the sample, for the sample to start.
|
||||
@ -21,38 +19,37 @@ namespace osu.Game.Storyboards.Drawables
|
||||
|
||||
private readonly StoryboardSampleInfo sampleInfo;
|
||||
|
||||
protected SampleChannel Channel { get; private set; }
|
||||
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
|
||||
public DrawableStoryboardSample(StoryboardSampleInfo sampleInfo)
|
||||
: base(sampleInfo)
|
||||
{
|
||||
this.sampleInfo = sampleInfo;
|
||||
LifetimeStart = sampleInfo.StartTime;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IBindable<WorkingBeatmap> beatmap, IBindable<IReadOnlyList<Mod>> mods)
|
||||
{
|
||||
Channel = beatmap.Value.Skin.GetSample(sampleInfo);
|
||||
if (Channel == null)
|
||||
return;
|
||||
[Resolved]
|
||||
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
|
||||
|
||||
Channel.Volume.Value = sampleInfo.Volume / 100.0;
|
||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||
{
|
||||
base.SkinChanged(skin, allowFallback);
|
||||
|
||||
foreach (var mod in mods.Value.OfType<IApplicableToSample>())
|
||||
mod.ApplyToSample(Channel);
|
||||
{
|
||||
foreach (var sample in SamplesContainer)
|
||||
mod.ApplyToSample(sample);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// TODO: this logic will need to be consolidated with other game samples like hit sounds.
|
||||
if (Time.Current < sampleInfo.StartTime)
|
||||
{
|
||||
// We've rewound before the start time of the sample
|
||||
Channel?.Stop();
|
||||
Stop();
|
||||
|
||||
// In the case that the user fast-forwards to a point far beyond the start time of the sample,
|
||||
// we want to be able to fall into the if-conditional below (therefore we must not have a life time end)
|
||||
@ -63,8 +60,8 @@ namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
// We've passed the start time of the sample. We only play the sample if we're within an allowable range
|
||||
// from the sample's start, to reduce layering if we've been fast-forwarded far into the future
|
||||
if (Time.Current - sampleInfo.StartTime < allowable_late_start)
|
||||
Channel?.Play();
|
||||
if (!PlaybackRequested && Time.Current - sampleInfo.StartTime < allowable_late_start)
|
||||
Play();
|
||||
|
||||
// In the case that the user rewinds to a point far behind the start time of the sample,
|
||||
// we want to be able to fall into the if-conditional above (therefore we must not have a life time start)
|
||||
@ -72,13 +69,5 @@ namespace osu.Game.Storyboards.Drawables
|
||||
LifetimeEnd = sampleInfo.StartTime;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
Channel?.Stop();
|
||||
Channel = null;
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user