1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 10:03:05 +08:00

Add SkinnableSound class

Tidy things up, move logic out of SampleInfo.
This commit is contained in:
Dean Herbert 2018-02-23 20:34:08 +09:00
parent a312fb365a
commit 768e0a4e2a
8 changed files with 184 additions and 96 deletions

View File

@ -2,10 +2,9 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Audio namespace osu.Game.Rulesets.Taiko.Audio
{ {
@ -14,7 +13,9 @@ namespace osu.Game.Rulesets.Taiko.Audio
private readonly ControlPointInfo controlPoints; private readonly ControlPointInfo controlPoints;
private readonly Dictionary<double, DrumSample> mappings = new Dictionary<double, DrumSample>(); private readonly Dictionary<double, DrumSample> mappings = new Dictionary<double, DrumSample>();
public DrumSampleMapping(ControlPointInfo controlPoints, AudioManager audio) public readonly List<SkinnableSound> Drawables = new List<SkinnableSound>();
public DrumSampleMapping(ControlPointInfo controlPoints)
{ {
this.controlPoints = controlPoints; this.controlPoints = controlPoints;
@ -27,20 +28,34 @@ namespace osu.Game.Rulesets.Taiko.Audio
foreach (var s in samplePoints) foreach (var s in samplePoints)
{ {
var centre = s.GetSampleInfo();
var rim = s.GetSampleInfo(SampleInfo.HIT_CLAP);
// todo: this is ugly
centre.Namespace = "taiko";
rim.Namespace = "taiko";
mappings[s.Time] = new DrumSample mappings[s.Time] = new DrumSample
{ {
Centre = s.GetSampleInfo().GetChannel(audio.Sample.Get, "Taiko"), Centre = addDrawableSound(centre),
Rim = s.GetSampleInfo(SampleInfo.HIT_CLAP).GetChannel(audio.Sample.Get, "Taiko") Rim = addDrawableSound(rim)
}; };
} }
} }
private SkinnableSound addDrawableSound(SampleInfo rim)
{
var drawable = new SkinnableSound(rim);
Drawables.Add(drawable);
return drawable;
}
public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time]; public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time];
public class DrumSample public class DrumSample
{ {
public SampleChannel Centre; public SkinnableSound Centre;
public SampleChannel Rim; public SkinnableSound Rim;
} }
} }
} }

View File

@ -4,7 +4,6 @@
using System; using System;
using OpenTK; using OpenTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -34,9 +33,9 @@ namespace osu.Game.Rulesets.Taiko.UI
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio) private void load()
{ {
var sampleMappings = new DrumSampleMapping(controlPoints, audio); var sampleMappings = new DrumSampleMapping(controlPoints);
Children = new Drawable[] Children = new Drawable[]
{ {
@ -63,6 +62,8 @@ namespace osu.Game.Rulesets.Taiko.UI
CentreAction = TaikoAction.RightCentre CentreAction = TaikoAction.RightCentre
} }
}; };
AddRangeInternal(sampleMappings.Drawables);
} }
/// <summary> /// <summary>

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Framework.Audio.Sample;
namespace osu.Game.Audio namespace osu.Game.Audio
{ {
@ -14,22 +13,10 @@ namespace osu.Game.Audio
public const string HIT_NORMAL = @"hitnormal"; public const string HIT_NORMAL = @"hitnormal";
public const string HIT_CLAP = @"hitclap"; public const string HIT_CLAP = @"hitclap";
public SampleChannel GetChannel(Func<string, SampleChannel> getChannel, string resourceNamespace = null) /// <summary>
{ /// An optional ruleset namespace.
SampleChannel channel = null; /// </summary>
public string Namespace;
if (resourceNamespace != null)
channel = getChannel($"Gameplay/{resourceNamespace}/{Bank}-{Name}");
// try without namespace as a fallback.
if (channel == null)
channel = getChannel($"Gameplay/{Bank}-{Name}");
if (channel != null)
channel.Volume.Value = Volume / 100.0;
return channel;
}
/// <summary> /// <summary>
/// The bank to load the sample from. /// The bank to load the sample from.

View File

@ -3,21 +3,19 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Game.Rulesets.Judgements;
using Container = osu.Framework.Graphics.Containers.Container;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
using osu.Game.Audio;
using System.Linq; using System.Linq;
using osu.Game.Graphics; using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using OpenTK; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Game.Audio;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Objects.Drawables namespace osu.Game.Rulesets.Objects.Drawables
{ {
@ -33,8 +31,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
// Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first // Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first
protected virtual string SampleNamespace => null; protected virtual string SampleNamespace => null;
protected List<SampleChannel> Samples = new List<SampleChannel>(); protected SkinnableSound Samples;
protected virtual IEnumerable<SampleInfo> GetSamples() => HitObject.Samples;
protected virtual IEnumerable<SampleInfo> GetSamples()
{
return HitObject.Samples;
}
private List<DrawableHitObject> nestedHitObjects; private List<DrawableHitObject> nestedHitObjects;
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects; public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects;
@ -83,41 +85,23 @@ namespace osu.Game.Rulesets.Objects.Drawables
HitObject = hitObject; HitObject = hitObject;
} }
private readonly Bindable<Skin> skin = new Bindable<Skin>();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio, SkinManager skins) private void load()
{ {
var samples = GetSamples(); var samples = GetSamples().ToArray();
if (samples.Any()) if (samples.Any())
{ {
if (HitObject.SampleControlPoint == null) if (HitObject.SampleControlPoint == null)
throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
void loadSamples(Skin skin) AddInternal(Samples = new SkinnableSound(samples.Select(s => new SampleInfo
{ {
Samples.Clear(); Bank = s.Bank ?? HitObject.SampleControlPoint.SampleBank,
Name = s.Name,
foreach (SampleInfo s in samples) Volume = s.Volume > 0 ? s.Volume : HitObject.SampleControlPoint.SampleVolume,
{ Namespace = SampleNamespace
SampleInfo localSampleInfo = new SampleInfo }).ToArray()));
{
Bank = s.Bank ?? HitObject.SampleControlPoint.SampleBank,
Name = s.Name,
Volume = s.Volume > 0 ? s.Volume : HitObject.SampleControlPoint.SampleVolume
};
SampleChannel channel = localSampleInfo.GetChannel(skin.GetSample, SampleNamespace) ?? localSampleInfo.GetChannel(audio.Sample.Get, SampleNamespace);
if (channel == null) return;
Samples.Add(channel);
}
}
skin.ValueChanged += loadSamples;
skin.BindTo(skins.CurrentSkin);
} }
} }
@ -149,7 +133,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// <summary> /// <summary>
/// Plays all the hitsounds for this <see cref="DrawableHitObject"/>. /// Plays all the hitsounds for this <see cref="DrawableHitObject"/>.
/// </summary> /// </summary>
public void PlaySamples() => Samples.ForEach(s => s?.Play()); public void PlaySamples() => Samples?.Play();
protected override void Update() protected override void Update()
{ {
@ -231,10 +215,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
return false; return false;
if (NestedHitObjects != null) if (NestedHitObjects != null)
{
foreach (var d in NestedHitObjects) foreach (var d in NestedHitObjects)
judgementOccurred |= d.UpdateJudgement(userTriggered); judgementOccurred |= d.UpdateJudgement(userTriggered);
}
if (!ProvidesJudgement || judgementFinalized || judgementOccurred) if (!ProvidesJudgement || judgementFinalized || judgementOccurred)
return judgementOccurred; return judgementOccurred;

View File

@ -0,0 +1,53 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Skinning
{
/// <summary>
/// A drawable which has a callback when the skin changes.
/// </summary>
public abstract class SkinReloadableDrawable : CompositeDrawable
{
private Bindable<Skin> skin;
/// <summary>
/// Whether fallback to default skin should be allowed if the custom skin is missing this resource.
/// </summary>
private readonly bool allowDefaultFallback;
/// <summary>
/// Create a new
/// </summary>
/// <param name="fallback">Whether fallback to default skin should be allowed if the custom skin is missing this resource.</param>
protected SkinReloadableDrawable(bool fallback = true)
{
allowDefaultFallback = fallback;
}
[BackgroundDependencyLoader]
private void load(SkinManager skinManager)
{
skin = skinManager.CurrentSkin.GetBoundCopy();
skin.ValueChanged += skin => SkinChanged(skin, allowDefaultFallback || skin.SkinInfo == SkinInfo.Default);
}
protected override void LoadAsyncComplete()
{
base.LoadAsyncComplete();
skin.TriggerChange();
}
/// <summary>
/// Called when a change is made to the skin.
/// </summary>
/// <param name="skin">The new skin.</param>
/// <param name="allowFallback">Whether fallback to default skin should be allowed if the custom skin is missing this resource.</param>
protected virtual void SkinChanged(Skin skin, bool allowFallback)
{
}
}
}

View File

@ -2,10 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
@ -14,40 +11,29 @@ namespace osu.Game.Skinning
public SkinnableDrawable(string name, Func<string, Drawable> defaultImplementation, bool fallback = true) public SkinnableDrawable(string name, Func<string, Drawable> defaultImplementation, bool fallback = true)
: base(name, defaultImplementation, fallback) : base(name, defaultImplementation, fallback)
{ {
RelativeSizeAxes = Axes.Both;
} }
} }
public class SkinnableDrawable<T> : CompositeDrawable public class SkinnableDrawable<T> : SkinReloadableDrawable
where T : Drawable where T : Drawable
{ {
private Bindable<Skin> skin; private readonly Func<string, T> createDefault;
protected Func<string, T> CreateDefault;
public readonly string ComponentName; private readonly string componentName;
public readonly bool DefaultFallback; public SkinnableDrawable(string name, Func<string, T> defaultImplementation, bool fallback = true) : base(fallback)
public SkinnableDrawable(string name, Func<string, T> defaultImplementation, bool fallback = true)
{ {
DefaultFallback = fallback; componentName = name;
ComponentName = name; createDefault = defaultImplementation;
CreateDefault = defaultImplementation;
RelativeSizeAxes = Axes.Both;
} }
[BackgroundDependencyLoader] protected override void SkinChanged(Skin skin, bool allowFallback)
private void load(SkinManager skinManager)
{ {
skin = skinManager.CurrentSkin.GetBoundCopy(); var drawable = skin.GetDrawableComponent(componentName);
skin.ValueChanged += updateComponent; if (drawable == null && allowFallback)
skin.TriggerChange(); drawable = createDefault(componentName);
}
private void updateComponent(Skin skin)
{
var drawable = skin.GetDrawableComponent(ComponentName);
if (drawable == null && (DefaultFallback || skin.SkinInfo == SkinInfo.Default))
drawable = CreateDefault(ComponentName);
if (drawable != null) if (drawable != null)
InternalChild = drawable; InternalChild = drawable;

View File

@ -0,0 +1,62 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Audio;
namespace osu.Game.Skinning
{
public class SkinnableSound : SkinReloadableDrawable
{
private readonly SampleInfo[] samples;
private SampleChannel[] channels;
private AudioManager audio;
public SkinnableSound(params SampleInfo[] samples)
{
this.samples = samples;
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
this.audio = audio;
}
public void Play() => channels?.ForEach(c => c.Play());
protected override void SkinChanged(Skin skin, bool allowFallback)
{
channels = samples.Select(s =>
{
var ch = loadChannel(s, skin.GetSample);
if (ch == null && allowFallback)
ch = loadChannel(s, audio.Sample.Get);
return ch;
}).ToArray();
}
private SampleChannel loadChannel(SampleInfo info, Func<string, SampleChannel> getSampleFunction)
{
SampleChannel ch = null;
if (info.Namespace != null)
ch = getSampleFunction($"Gameplay/{info.Namespace}/{info.Bank}-{info.Name}");
// try without namespace as a fallback.
if (ch == null)
ch = getSampleFunction($"Gameplay/{info.Bank}-{info.Name}");
if (ch != null)
ch.Volume.Value = info.Volume / 100.0;
return ch;
}
}
}

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0">
<Import Project="..\osu.Game.props" /> <Import Project="..\osu.Game.props" />
<PropertyGroup> <PropertyGroup>
@ -861,6 +861,8 @@
<Compile Include="Skinning\SkinInfo.cs" /> <Compile Include="Skinning\SkinInfo.cs" />
<Compile Include="Skinning\SkinManager.cs" /> <Compile Include="Skinning\SkinManager.cs" />
<Compile Include="Skinning\SkinnableDrawable.cs" /> <Compile Include="Skinning\SkinnableDrawable.cs" />
<Compile Include="Skinning\SkinnableSound.cs" />
<Compile Include="Skinning\SkinReloadableDrawable.cs" />
<Compile Include="Skinning\SkinStore.cs" /> <Compile Include="Skinning\SkinStore.cs" />
<Compile Include="Storyboards\CommandLoop.cs" /> <Compile Include="Storyboards\CommandLoop.cs" />
<Compile Include="Storyboards\CommandTimeline.cs" /> <Compile Include="Storyboards\CommandTimeline.cs" />