mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-24 13:38:34 +01:00
Compare commits
3 commits
642608a8a2
...
8d92131630
Author | SHA1 | Date | |
---|---|---|---|
8d92131630 | |||
f352e6b437 | |||
d1b229b589 |
9 changed files with 319 additions and 262 deletions
|
@ -7,11 +7,15 @@ Jump to version:
|
||||||
|
|
||||||
## 5.1.0 (Unreleased)
|
## 5.1.0 (Unreleased)
|
||||||
### MLEM
|
### MLEM
|
||||||
|
Additions
|
||||||
|
- Added RotateBy to Direction2Helper
|
||||||
|
|
||||||
Fixes
|
Fixes
|
||||||
- Set default values for InputHandler held and pressed keys to avoid an exception if buttons are held in the very first frame
|
- Set default values for InputHandler held and pressed keys to avoid an exception if buttons are held in the very first frame
|
||||||
|
|
||||||
Improvements
|
Improvements
|
||||||
- Improved NinePatch memory performance
|
- Improved NinePatch memory performance
|
||||||
|
- Moved sound-related classes into Sound namespace
|
||||||
|
|
||||||
### MLEM.Ui
|
### MLEM.Ui
|
||||||
Fixes
|
Fixes
|
||||||
|
|
|
@ -10,6 +10,7 @@ using MLEM.Input;
|
||||||
using MLEM.Misc;
|
using MLEM.Misc;
|
||||||
using MLEM.Textures;
|
using MLEM.Textures;
|
||||||
using MLEM.Ui.Style;
|
using MLEM.Ui.Style;
|
||||||
|
using SoundEffectInfo = MLEM.Sound.SoundEffectInfo;
|
||||||
|
|
||||||
namespace MLEM.Ui.Elements {
|
namespace MLEM.Ui.Elements {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -5,6 +5,7 @@ using MLEM.Formatting;
|
||||||
using MLEM.Misc;
|
using MLEM.Misc;
|
||||||
using MLEM.Textures;
|
using MLEM.Textures;
|
||||||
using MLEM.Ui.Elements;
|
using MLEM.Ui.Elements;
|
||||||
|
using SoundEffectInfo = MLEM.Sound.SoundEffectInfo;
|
||||||
|
|
||||||
namespace MLEM.Ui.Style {
|
namespace MLEM.Ui.Style {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
using static MLEM.Misc.Direction2;
|
||||||
|
|
||||||
namespace MLEM.Misc {
|
namespace MLEM.Misc {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -83,7 +84,10 @@ namespace MLEM.Misc {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All directions except <see cref="Direction2.None"/>
|
/// All directions except <see cref="Direction2.None"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly Direction2[] AllExceptNone = All.Where(dir => dir != Direction2.None).ToArray();
|
public static readonly Direction2[] AllExceptNone = All.Where(dir => dir != None).ToArray();
|
||||||
|
|
||||||
|
private static readonly Direction2[] Clockwise = {Up, UpRight, Right, DownRight, Down, DownLeft, Left, UpLeft};
|
||||||
|
private static readonly Dictionary<Direction2, int> ClockwiseLookup = Clockwise.Select((d, i) => (d, i)).ToDictionary(kv => kv.d, kv => kv.i);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns if the given direction is considered an "adjacent" direction.
|
/// Returns if the given direction is considered an "adjacent" direction.
|
||||||
|
@ -92,7 +96,7 @@ namespace MLEM.Misc {
|
||||||
/// <param name="dir">The direction to query</param>
|
/// <param name="dir">The direction to query</param>
|
||||||
/// <returns>Whether the direction is adjacent</returns>
|
/// <returns>Whether the direction is adjacent</returns>
|
||||||
public static bool IsAdjacent(this Direction2 dir) {
|
public static bool IsAdjacent(this Direction2 dir) {
|
||||||
return dir == Direction2.Up || dir == Direction2.Right || dir == Direction2.Down || dir == Direction2.Left;
|
return dir == Up || dir == Right || dir == Down || dir == Left;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -101,7 +105,7 @@ namespace MLEM.Misc {
|
||||||
/// <param name="dir">The direction to query</param>
|
/// <param name="dir">The direction to query</param>
|
||||||
/// <returns>Whether the direction is diagonal</returns>
|
/// <returns>Whether the direction is diagonal</returns>
|
||||||
public static bool IsDiagonal(this Direction2 dir) {
|
public static bool IsDiagonal(this Direction2 dir) {
|
||||||
return dir == Direction2.UpRight || dir == Direction2.DownRight || dir == Direction2.UpLeft || dir == Direction2.DownLeft;
|
return dir == UpRight || dir == DownRight || dir == UpLeft || dir == DownLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -112,21 +116,21 @@ namespace MLEM.Misc {
|
||||||
/// <returns>The direction's offset</returns>
|
/// <returns>The direction's offset</returns>
|
||||||
public static Point Offset(this Direction2 dir) {
|
public static Point Offset(this Direction2 dir) {
|
||||||
switch (dir) {
|
switch (dir) {
|
||||||
case Direction2.Up:
|
case Up:
|
||||||
return new Point(0, -1);
|
return new Point(0, -1);
|
||||||
case Direction2.Right:
|
case Right:
|
||||||
return new Point(1, 0);
|
return new Point(1, 0);
|
||||||
case Direction2.Down:
|
case Down:
|
||||||
return new Point(0, 1);
|
return new Point(0, 1);
|
||||||
case Direction2.Left:
|
case Left:
|
||||||
return new Point(-1, 0);
|
return new Point(-1, 0);
|
||||||
case Direction2.UpRight:
|
case UpRight:
|
||||||
return new Point(1, -1);
|
return new Point(1, -1);
|
||||||
case Direction2.DownRight:
|
case DownRight:
|
||||||
return new Point(1, 1);
|
return new Point(1, 1);
|
||||||
case Direction2.DownLeft:
|
case DownLeft:
|
||||||
return new Point(-1, 1);
|
return new Point(-1, 1);
|
||||||
case Direction2.UpLeft:
|
case UpLeft:
|
||||||
return new Point(-1, -1);
|
return new Point(-1, -1);
|
||||||
default:
|
default:
|
||||||
return Point.Zero;
|
return Point.Zero;
|
||||||
|
@ -152,24 +156,24 @@ namespace MLEM.Misc {
|
||||||
/// <returns>The opposite of the direction</returns>
|
/// <returns>The opposite of the direction</returns>
|
||||||
public static Direction2 Opposite(this Direction2 dir) {
|
public static Direction2 Opposite(this Direction2 dir) {
|
||||||
switch (dir) {
|
switch (dir) {
|
||||||
case Direction2.Up:
|
case Up:
|
||||||
return Direction2.Down;
|
return Down;
|
||||||
case Direction2.Right:
|
case Right:
|
||||||
return Direction2.Left;
|
return Left;
|
||||||
case Direction2.Down:
|
case Down:
|
||||||
return Direction2.Up;
|
return Up;
|
||||||
case Direction2.Left:
|
case Left:
|
||||||
return Direction2.Right;
|
return Right;
|
||||||
case Direction2.UpRight:
|
case UpRight:
|
||||||
return Direction2.DownLeft;
|
return DownLeft;
|
||||||
case Direction2.DownRight:
|
case DownRight:
|
||||||
return Direction2.UpLeft;
|
return UpLeft;
|
||||||
case Direction2.DownLeft:
|
case DownLeft:
|
||||||
return Direction2.UpRight;
|
return UpRight;
|
||||||
case Direction2.UpLeft:
|
case UpLeft:
|
||||||
return Direction2.DownRight;
|
return DownRight;
|
||||||
default:
|
default:
|
||||||
return Direction2.None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,26 +194,7 @@ namespace MLEM.Misc {
|
||||||
/// <param name="fortyFiveDegrees">Whether to rotate by 45 degrees. If this is false, the rotation is 90 degrees instead.</param>
|
/// <param name="fortyFiveDegrees">Whether to rotate by 45 degrees. If this is false, the rotation is 90 degrees instead.</param>
|
||||||
/// <returns>The rotated direction</returns>
|
/// <returns>The rotated direction</returns>
|
||||||
public static Direction2 RotateCw(this Direction2 dir, bool fortyFiveDegrees = false) {
|
public static Direction2 RotateCw(this Direction2 dir, bool fortyFiveDegrees = false) {
|
||||||
switch (dir) {
|
return Clockwise[(ClockwiseLookup[dir] + (fortyFiveDegrees ? 1 : 2)) % Clockwise.Length];
|
||||||
case Direction2.Up:
|
|
||||||
return fortyFiveDegrees ? Direction2.UpRight : Direction2.Right;
|
|
||||||
case Direction2.Right:
|
|
||||||
return fortyFiveDegrees ? Direction2.DownRight : Direction2.Down;
|
|
||||||
case Direction2.Down:
|
|
||||||
return fortyFiveDegrees ? Direction2.DownLeft : Direction2.Left;
|
|
||||||
case Direction2.Left:
|
|
||||||
return fortyFiveDegrees ? Direction2.UpLeft : Direction2.Up;
|
|
||||||
case Direction2.UpRight:
|
|
||||||
return fortyFiveDegrees ? Direction2.Right : Direction2.DownRight;
|
|
||||||
case Direction2.DownRight:
|
|
||||||
return fortyFiveDegrees ? Direction2.Down : Direction2.DownLeft;
|
|
||||||
case Direction2.DownLeft:
|
|
||||||
return fortyFiveDegrees ? Direction2.Left : Direction2.UpLeft;
|
|
||||||
case Direction2.UpLeft:
|
|
||||||
return fortyFiveDegrees ? Direction2.Up : Direction2.UpRight;
|
|
||||||
default:
|
|
||||||
return Direction2.None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -219,26 +204,8 @@ namespace MLEM.Misc {
|
||||||
/// <param name="fortyFiveDegrees">Whether to rotate by 45 degrees. If this is false, the rotation is 90 degrees instead.</param>
|
/// <param name="fortyFiveDegrees">Whether to rotate by 45 degrees. If this is false, the rotation is 90 degrees instead.</param>
|
||||||
/// <returns>The rotated direction</returns>
|
/// <returns>The rotated direction</returns>
|
||||||
public static Direction2 RotateCcw(this Direction2 dir, bool fortyFiveDegrees = false) {
|
public static Direction2 RotateCcw(this Direction2 dir, bool fortyFiveDegrees = false) {
|
||||||
switch (dir) {
|
var index = ClockwiseLookup[dir] - (fortyFiveDegrees ? 1 : 2);
|
||||||
case Direction2.Up:
|
return Clockwise[index < 0 ? index + Clockwise.Length : index];
|
||||||
return fortyFiveDegrees ? Direction2.UpLeft : Direction2.Left;
|
|
||||||
case Direction2.Right:
|
|
||||||
return fortyFiveDegrees ? Direction2.UpRight : Direction2.Up;
|
|
||||||
case Direction2.Down:
|
|
||||||
return fortyFiveDegrees ? Direction2.DownRight : Direction2.Right;
|
|
||||||
case Direction2.Left:
|
|
||||||
return fortyFiveDegrees ? Direction2.DownLeft : Direction2.Down;
|
|
||||||
case Direction2.UpRight:
|
|
||||||
return fortyFiveDegrees ? Direction2.Up : Direction2.UpLeft;
|
|
||||||
case Direction2.DownRight:
|
|
||||||
return fortyFiveDegrees ? Direction2.Right : Direction2.UpRight;
|
|
||||||
case Direction2.DownLeft:
|
|
||||||
return fortyFiveDegrees ? Direction2.Down : Direction2.DownRight;
|
|
||||||
case Direction2.UpLeft:
|
|
||||||
return fortyFiveDegrees ? Direction2.Left : Direction2.DownLeft;
|
|
||||||
default:
|
|
||||||
return Direction2.None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -252,7 +219,7 @@ namespace MLEM.Misc {
|
||||||
if (Math.Abs(dir.Angle() - offsetAngle) <= MathHelper.PiOver4 / 2)
|
if (Math.Abs(dir.Angle() - offsetAngle) <= MathHelper.PiOver4 / 2)
|
||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
return Direction2.None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -263,10 +230,24 @@ namespace MLEM.Misc {
|
||||||
/// <returns>The vector's direction</returns>
|
/// <returns>The vector's direction</returns>
|
||||||
public static Direction2 To90Direction(this Vector2 offset) {
|
public static Direction2 To90Direction(this Vector2 offset) {
|
||||||
if (offset.X == 0 && offset.Y == 0)
|
if (offset.X == 0 && offset.Y == 0)
|
||||||
return Direction2.None;
|
return None;
|
||||||
if (Math.Abs(offset.X) > Math.Abs(offset.Y))
|
if (Math.Abs(offset.X) > Math.Abs(offset.Y))
|
||||||
return offset.X > 0 ? Direction2.Right : Direction2.Left;
|
return offset.X > 0 ? Right : Left;
|
||||||
return offset.Y > 0 ? Direction2.Down : Direction2.Up;
|
return offset.Y > 0 ? Down : Up;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rotates the given direction by a given reference direction
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dir">The direction to rotate</param>
|
||||||
|
/// <param name="reference">The direction to rotate by</param>
|
||||||
|
/// <param name="start">The direction to use as the default direction</param>
|
||||||
|
/// <returns>The direction, rotated by the reference direction</returns>
|
||||||
|
public static Direction2 RotateBy(this Direction2 dir, Direction2 reference, Direction2 start = Up) {
|
||||||
|
var diff = ClockwiseLookup[reference] - ClockwiseLookup[start];
|
||||||
|
if (diff < 0)
|
||||||
|
diff += Clockwise.Length;
|
||||||
|
return Clockwise[(ClockwiseLookup[dir] + diff) % Clockwise.Length];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,59 +1,13 @@
|
||||||
|
using System;
|
||||||
using Microsoft.Xna.Framework.Audio;
|
using Microsoft.Xna.Framework.Audio;
|
||||||
using MLEM.Extensions;
|
|
||||||
|
|
||||||
namespace MLEM.Misc {
|
namespace MLEM.Misc {
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// A sound effect info is a wrapper around <see cref="SoundEffect"/> that additionally stores <see cref="Volume"/>, <see cref="Pitch"/> and <see cref="Pan"/> information.
|
[Obsolete("This class has been moved to MLEM.Sound.SoundEffectInfo in 5.1.0")]
|
||||||
/// Additionally, a <see cref="SoundEffectInstance"/> can be created using <see cref="CreateInstance"/>.
|
public class SoundEffectInfo : Sound.SoundEffectInfo {
|
||||||
/// </summary>
|
|
||||||
public class SoundEffectInfo {
|
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// The <see cref="SoundEffect"/> that is played by this info.
|
public SoundEffectInfo(SoundEffect sound, float volume = 1, float pitch = 0, float pan = 0) : base(sound, volume, pitch, pan) {
|
||||||
/// </summary>
|
|
||||||
public readonly SoundEffect Sound;
|
|
||||||
/// <summary>
|
|
||||||
/// Volume, ranging from 0.0 (silence) to 1.0 (full volume). Volume during playback is scaled by SoundEffect.MasterVolume.
|
|
||||||
/// </summary>
|
|
||||||
public float Volume;
|
|
||||||
/// <summary>
|
|
||||||
/// Pitch adjustment, ranging from -1.0 (down an octave) to 0.0 (no change) to 1.0 (up an octave).
|
|
||||||
/// </summary>
|
|
||||||
public float Pitch;
|
|
||||||
/// <summary>
|
|
||||||
/// Pan value ranging from -1.0 (left speaker) to 0.0 (centered), 1.0 (right speaker).
|
|
||||||
/// </summary>
|
|
||||||
public float Pan;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new sound effect info with the given values.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sound">The sound to play</param>
|
|
||||||
/// <param name="volume">The volume to play the sound with</param>
|
|
||||||
/// <param name="pitch">The pitch to play the sound with</param>
|
|
||||||
/// <param name="pan">The pan to play the sound with</param>
|
|
||||||
public SoundEffectInfo(SoundEffect sound, float volume = 1, float pitch = 0, float pan = 0) {
|
|
||||||
this.Sound = sound;
|
|
||||||
this.Volume = volume;
|
|
||||||
this.Pitch = pitch;
|
|
||||||
this.Pan = pan;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Plays this info's <see cref="Sound"/> once.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>False if more sounds are currently playing than the platform allows</returns>
|
|
||||||
public bool Play() {
|
|
||||||
return this.Sound.Play(this.Volume, this.Pitch, this.Pan);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <see cref="SoundEffectInstance"/> with this sound effect info's data.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="isLooped">The value to set the returned instance's <see cref="SoundEffectInstance.IsLooped"/> to. Defaults to false.</param>
|
|
||||||
/// <returns>A new sound effect instance, with this info's data applied</returns>
|
|
||||||
public SoundEffectInstance CreateInstance(bool isLooped = false) {
|
|
||||||
return this.Sound.CreateInstance(this.Volume, this.Pitch, this.Pan, isLooped);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,141 +1,13 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Audio;
|
|
||||||
|
|
||||||
namespace MLEM.Misc {
|
namespace MLEM.Misc {
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// A simple class that handles automatically removing and disposing <see cref="SoundEffectInstance"/> objects once they are done playing to free up the audio source for new sounds.
|
[Obsolete("This class has been moved to MLEM.Sound.SoundEffectInstanceHandler in 5.1.0")]
|
||||||
/// Additionally, a callback can be registered that is invoked when the <see cref="SoundEffectInstance"/> finishes playing.
|
public class SoundEffectInstanceHandler : Sound.SoundEffectInstanceHandler {
|
||||||
/// Note that an object of this class can be added to a <see cref="Game"/> using its <see cref="GameComponentCollection"/>.
|
|
||||||
/// </summary>
|
|
||||||
public class SoundEffectInstanceHandler : GameComponent, IEnumerable<SoundEffectInstance> {
|
|
||||||
|
|
||||||
private readonly List<Entry> playingSounds = new List<Entry>();
|
|
||||||
private AudioListener[] listeners;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new sound effect instance handler with the given settings
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="game">The game instance</param>
|
|
||||||
public SoundEffectInstanceHandler(Game game) : base(game) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Update()"/>
|
|
||||||
public override void Update(GameTime gameTime) {
|
|
||||||
this.Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates this sound effect handler and manages all of the <see cref="SoundEffectInstance"/> objects in it.
|
|
||||||
/// If <see cref="SetListeners"/> has been called, all sounds will additionally be updated in 3D space.
|
|
||||||
/// This should be called each update frame.
|
|
||||||
/// </summary>
|
|
||||||
public void Update() {
|
|
||||||
for (var i = this.playingSounds.Count - 1; i >= 0; i--) {
|
|
||||||
var entry = this.playingSounds[i];
|
|
||||||
if (entry.Instance.IsDisposed || entry.Instance.State == SoundState.Stopped) {
|
|
||||||
entry.Instance.Stop(true);
|
|
||||||
entry.OnStopped?.Invoke(entry.Instance);
|
|
||||||
this.playingSounds.RemoveAt(i);
|
|
||||||
} else {
|
|
||||||
entry.TryApply3D(this.listeners);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the collection <see cref="AudioListener"/> objects that should be listening to the sounds in this handler in 3D space.
|
|
||||||
/// If there are one or more listeners, this handler applies 3d effects to all sound effect instances that have been added to this handler along with an <see cref="AudioEmitter"/> in <see cref="Update(Microsoft.Xna.Framework.GameTime)"/> automatically.
|
|
||||||
/// </summary>
|
|
||||||
public void SetListeners(params AudioListener[] listeners) {
|
|
||||||
this.listeners = listeners;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Pauses all of the sound effect instances that are currently playing
|
|
||||||
/// </summary>
|
|
||||||
public void Pause() {
|
|
||||||
foreach (var entry in this.playingSounds)
|
|
||||||
entry.Instance.Pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resumes all of the sound effect instances in this handler
|
|
||||||
/// </summary>
|
|
||||||
public void Resume() {
|
|
||||||
foreach (var entry in this.playingSounds)
|
|
||||||
entry.Instance.Resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a new <see cref="SoundEffectInstance"/> to this handler.
|
|
||||||
/// This also starts playing the instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="instance">The instance to add</param>
|
|
||||||
/// <param name="onStopped">The function that should be invoked when this instance stops playing, defaults to null</param>
|
|
||||||
/// <param name="emitter">An optional audio emitter with which 3d sound can be applied</param>
|
|
||||||
/// <returns>The passed instance, for chaining</returns>
|
|
||||||
public SoundEffectInstance Add(SoundEffectInstance instance, Action<SoundEffectInstance> onStopped = null, AudioEmitter emitter = null) {
|
|
||||||
var entry = new Entry(instance, onStopped, emitter);
|
|
||||||
this.playingSounds.Add(entry);
|
|
||||||
instance.Play();
|
|
||||||
entry.TryApply3D(this.listeners);
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a new <see cref="SoundEffectInfo"/> to this handler.
|
|
||||||
/// This also starts playing the created instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="info">The info for which to add a <see cref="SoundEffectInstance"/></param>
|
|
||||||
/// <param name="onStopped">The function that should be invoked when this instance stops playing, defaults to null</param>
|
|
||||||
/// <param name="emitter">An optional audio emitter with which 3d sound can be applied</param>
|
|
||||||
/// <returns>The newly created <see cref="SoundEffectInstance"/></returns>
|
|
||||||
public SoundEffectInstance Add(SoundEffectInfo info, Action<SoundEffectInstance> onStopped = null, AudioEmitter emitter = null) {
|
|
||||||
return this.Add(info.CreateInstance(), onStopped, emitter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a new <see cref="SoundEffect"/> to this handler.
|
|
||||||
/// This also starts playing the created instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="effect">The sound for which to add a <see cref="SoundEffectInstance"/></param>
|
|
||||||
/// <param name="onStopped">The function that should be invoked when this instance stops playing, defaults to null</param>
|
|
||||||
/// <param name="emitter">An optional audio emitter with which 3d sound can be applied</param>
|
|
||||||
/// <returns>The newly created <see cref="SoundEffectInstance"/></returns>
|
|
||||||
public SoundEffectInstance Add(SoundEffect effect, Action<SoundEffectInstance> onStopped = null, AudioEmitter emitter = null) {
|
|
||||||
return this.Add(effect.CreateInstance(), onStopped, emitter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerator<SoundEffectInstance> GetEnumerator() {
|
public SoundEffectInstanceHandler(Game game) : base(game) {
|
||||||
foreach (var sound in this.playingSounds)
|
|
||||||
yield return sound.Instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
|
||||||
return this.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly struct Entry {
|
|
||||||
|
|
||||||
public readonly SoundEffectInstance Instance;
|
|
||||||
public readonly Action<SoundEffectInstance> OnStopped;
|
|
||||||
public readonly AudioEmitter Emitter;
|
|
||||||
|
|
||||||
public Entry(SoundEffectInstance instance, Action<SoundEffectInstance> onStopped, AudioEmitter emitter) {
|
|
||||||
this.Instance = instance;
|
|
||||||
this.OnStopped = onStopped;
|
|
||||||
this.Emitter = emitter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TryApply3D(AudioListener[] listeners) {
|
|
||||||
if (listeners != null && listeners.Length > 0 && this.Emitter != null)
|
|
||||||
this.Instance.Apply3D(listeners, this.Emitter);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
60
MLEM/Sound/SoundEffectInfo.cs
Normal file
60
MLEM/Sound/SoundEffectInfo.cs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
using Microsoft.Xna.Framework.Audio;
|
||||||
|
using MLEM.Extensions;
|
||||||
|
|
||||||
|
namespace MLEM.Sound {
|
||||||
|
/// <summary>
|
||||||
|
/// A sound effect info is a wrapper around <see cref="SoundEffect"/> that additionally stores <see cref="Volume"/>, <see cref="Pitch"/> and <see cref="Pan"/> information.
|
||||||
|
/// Additionally, a <see cref="SoundEffectInstance"/> can be created using <see cref="CreateInstance"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class SoundEffectInfo {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="SoundEffect"/> that is played by this info.
|
||||||
|
/// </summary>
|
||||||
|
public readonly SoundEffect Sound;
|
||||||
|
/// <summary>
|
||||||
|
/// Volume, ranging from 0.0 (silence) to 1.0 (full volume). Volume during playback is scaled by SoundEffect.MasterVolume.
|
||||||
|
/// </summary>
|
||||||
|
public float Volume;
|
||||||
|
/// <summary>
|
||||||
|
/// Pitch adjustment, ranging from -1.0 (down an octave) to 0.0 (no change) to 1.0 (up an octave).
|
||||||
|
/// </summary>
|
||||||
|
public float Pitch;
|
||||||
|
/// <summary>
|
||||||
|
/// Pan value ranging from -1.0 (left speaker) to 0.0 (centered), 1.0 (right speaker).
|
||||||
|
/// </summary>
|
||||||
|
public float Pan;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new sound effect info with the given values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sound">The sound to play</param>
|
||||||
|
/// <param name="volume">The volume to play the sound with</param>
|
||||||
|
/// <param name="pitch">The pitch to play the sound with</param>
|
||||||
|
/// <param name="pan">The pan to play the sound with</param>
|
||||||
|
public SoundEffectInfo(SoundEffect sound, float volume = 1, float pitch = 0, float pan = 0) {
|
||||||
|
this.Sound = sound;
|
||||||
|
this.Volume = volume;
|
||||||
|
this.Pitch = pitch;
|
||||||
|
this.Pan = pan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plays this info's <see cref="Sound"/> once.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>False if more sounds are currently playing than the platform allows</returns>
|
||||||
|
public bool Play() {
|
||||||
|
return this.Sound.Play(this.Volume, this.Pitch, this.Pan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="SoundEffectInstance"/> with this sound effect info's data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isLooped">The value to set the returned instance's <see cref="SoundEffectInstance.IsLooped"/> to. Defaults to false.</param>
|
||||||
|
/// <returns>A new sound effect instance, with this info's data applied</returns>
|
||||||
|
public SoundEffectInstance CreateInstance(bool isLooped = false) {
|
||||||
|
return this.Sound.CreateInstance(this.Volume, this.Pitch, this.Pan, isLooped);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
142
MLEM/Sound/SoundEffectInstanceHandler.cs
Normal file
142
MLEM/Sound/SoundEffectInstanceHandler.cs
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Audio;
|
||||||
|
|
||||||
|
namespace MLEM.Sound {
|
||||||
|
/// <summary>
|
||||||
|
/// A simple class that handles automatically removing and disposing <see cref="SoundEffectInstance"/> objects once they are done playing to free up the audio source for new sounds.
|
||||||
|
/// Additionally, a callback can be registered that is invoked when the <see cref="SoundEffectInstance"/> finishes playing.
|
||||||
|
/// Note that an object of this class can be added to a <see cref="Game"/> using its <see cref="GameComponentCollection"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class SoundEffectInstanceHandler : GameComponent, IEnumerable<SoundEffectInstance> {
|
||||||
|
|
||||||
|
private readonly List<Entry> playingSounds = new List<Entry>();
|
||||||
|
private AudioListener[] listeners;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new sound effect instance handler with the given settings
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="game">The game instance</param>
|
||||||
|
public SoundEffectInstanceHandler(Game game) : base(game) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Update()"/>
|
||||||
|
public override void Update(GameTime gameTime) {
|
||||||
|
this.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates this sound effect handler and manages all of the <see cref="SoundEffectInstance"/> objects in it.
|
||||||
|
/// If <see cref="SetListeners"/> has been called, all sounds will additionally be updated in 3D space.
|
||||||
|
/// This should be called each update frame.
|
||||||
|
/// </summary>
|
||||||
|
public void Update() {
|
||||||
|
for (var i = this.playingSounds.Count - 1; i >= 0; i--) {
|
||||||
|
var entry = this.playingSounds[i];
|
||||||
|
if (entry.Instance.IsDisposed || entry.Instance.State == SoundState.Stopped) {
|
||||||
|
entry.Instance.Stop(true);
|
||||||
|
entry.OnStopped?.Invoke(entry.Instance);
|
||||||
|
this.playingSounds.RemoveAt(i);
|
||||||
|
} else {
|
||||||
|
entry.TryApply3D(this.listeners);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the collection <see cref="AudioListener"/> objects that should be listening to the sounds in this handler in 3D space.
|
||||||
|
/// If there are one or more listeners, this handler applies 3d effects to all sound effect instances that have been added to this handler along with an <see cref="AudioEmitter"/> in <see cref="Update(Microsoft.Xna.Framework.GameTime)"/> automatically.
|
||||||
|
/// </summary>
|
||||||
|
public void SetListeners(params AudioListener[] listeners) {
|
||||||
|
this.listeners = listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pauses all of the sound effect instances that are currently playing
|
||||||
|
/// </summary>
|
||||||
|
public void Pause() {
|
||||||
|
foreach (var entry in this.playingSounds)
|
||||||
|
entry.Instance.Pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resumes all of the sound effect instances in this handler
|
||||||
|
/// </summary>
|
||||||
|
public void Resume() {
|
||||||
|
foreach (var entry in this.playingSounds)
|
||||||
|
entry.Instance.Resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new <see cref="SoundEffectInstance"/> to this handler.
|
||||||
|
/// This also starts playing the instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instance">The instance to add</param>
|
||||||
|
/// <param name="onStopped">The function that should be invoked when this instance stops playing, defaults to null</param>
|
||||||
|
/// <param name="emitter">An optional audio emitter with which 3d sound can be applied</param>
|
||||||
|
/// <returns>The passed instance, for chaining</returns>
|
||||||
|
public SoundEffectInstance Add(SoundEffectInstance instance, Action<SoundEffectInstance> onStopped = null, AudioEmitter emitter = null) {
|
||||||
|
var entry = new Entry(instance, onStopped, emitter);
|
||||||
|
this.playingSounds.Add(entry);
|
||||||
|
instance.Play();
|
||||||
|
entry.TryApply3D(this.listeners);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new <see cref="Misc.SoundEffectInfo"/> to this handler.
|
||||||
|
/// This also starts playing the created instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">The info for which to add a <see cref="SoundEffectInstance"/></param>
|
||||||
|
/// <param name="onStopped">The function that should be invoked when this instance stops playing, defaults to null</param>
|
||||||
|
/// <param name="emitter">An optional audio emitter with which 3d sound can be applied</param>
|
||||||
|
/// <returns>The newly created <see cref="SoundEffectInstance"/></returns>
|
||||||
|
public SoundEffectInstance Add(SoundEffectInfo info, Action<SoundEffectInstance> onStopped = null, AudioEmitter emitter = null) {
|
||||||
|
return this.Add(info.CreateInstance(), onStopped, emitter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new <see cref="SoundEffect"/> to this handler.
|
||||||
|
/// This also starts playing the created instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="effect">The sound for which to add a <see cref="SoundEffectInstance"/></param>
|
||||||
|
/// <param name="onStopped">The function that should be invoked when this instance stops playing, defaults to null</param>
|
||||||
|
/// <param name="emitter">An optional audio emitter with which 3d sound can be applied</param>
|
||||||
|
/// <returns>The newly created <see cref="SoundEffectInstance"/></returns>
|
||||||
|
public SoundEffectInstance Add(SoundEffect effect, Action<SoundEffectInstance> onStopped = null, AudioEmitter emitter = null) {
|
||||||
|
return this.Add(effect.CreateInstance(), onStopped, emitter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerator<SoundEffectInstance> GetEnumerator() {
|
||||||
|
foreach (var sound in this.playingSounds)
|
||||||
|
yield return sound.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
|
return this.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly struct Entry {
|
||||||
|
|
||||||
|
public readonly SoundEffectInstance Instance;
|
||||||
|
public readonly Action<SoundEffectInstance> OnStopped;
|
||||||
|
public readonly AudioEmitter Emitter;
|
||||||
|
|
||||||
|
public Entry(SoundEffectInstance instance, Action<SoundEffectInstance> onStopped, AudioEmitter emitter) {
|
||||||
|
this.Instance = instance;
|
||||||
|
this.OnStopped = onStopped;
|
||||||
|
this.Emitter = emitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TryApply3D(AudioListener[] listeners) {
|
||||||
|
if (listeners != null && listeners.Length > 0 && this.Emitter != null)
|
||||||
|
this.Instance.Apply3D(listeners, this.Emitter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,64 @@
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MLEM.Misc;
|
using MLEM.Misc;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using static MLEM.Misc.Direction2;
|
||||||
|
|
||||||
namespace Tests {
|
namespace Tests {
|
||||||
public class DirectionTests {
|
public class DirectionTests {
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDirections() {
|
public void TestDirections() {
|
||||||
Assert.AreEqual(new Vector2(0.5F, 0.5F).ToDirection(), Direction2.DownRight);
|
Assert.AreEqual(new Vector2(0.5F, 0.5F).ToDirection(), DownRight);
|
||||||
Assert.AreEqual(new Vector2(0.25F, 0.5F).ToDirection(), Direction2.DownRight);
|
Assert.AreEqual(new Vector2(0.25F, 0.5F).ToDirection(), DownRight);
|
||||||
Assert.AreEqual(new Vector2(0.15F, 0.5F).ToDirection(), Direction2.Down);
|
Assert.AreEqual(new Vector2(0.15F, 0.5F).ToDirection(), Down);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Test90Directions() {
|
public void Test90Directions() {
|
||||||
Assert.AreEqual(new Vector2(0.75F, 0.5F).To90Direction(), Direction2.Right);
|
Assert.AreEqual(new Vector2(0.75F, 0.5F).To90Direction(), Right);
|
||||||
Assert.AreEqual(new Vector2(0.5F, 0.5F).To90Direction(), Direction2.Down);
|
Assert.AreEqual(new Vector2(0.5F, 0.5F).To90Direction(), Down);
|
||||||
Assert.AreEqual(new Vector2(0.25F, 0.5F).To90Direction(), Direction2.Down);
|
Assert.AreEqual(new Vector2(0.25F, 0.5F).To90Direction(), Down);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRotations() {
|
||||||
|
// rotate cw
|
||||||
|
Assert.AreEqual(Up.RotateCw(), Right);
|
||||||
|
Assert.AreEqual(Up.RotateCw(true), UpRight);
|
||||||
|
Assert.AreEqual(Left.RotateCw(), Up);
|
||||||
|
Assert.AreEqual(UpLeft.RotateCw(), UpRight);
|
||||||
|
|
||||||
|
// rotate ccw
|
||||||
|
Assert.AreEqual(Up.RotateCcw(), Left);
|
||||||
|
Assert.AreEqual(Up.RotateCcw(true), UpLeft);
|
||||||
|
Assert.AreEqual(Left.RotateCcw(), Down);
|
||||||
|
Assert.AreEqual(UpLeft.RotateCcw(), DownLeft);
|
||||||
|
|
||||||
|
// rotate 360 degrees
|
||||||
|
foreach (var dir in Direction2Helper.AllExceptNone) {
|
||||||
|
Assert.AreEqual(RotateMultipleTimes(dir, true, false, 4), dir);
|
||||||
|
Assert.AreEqual(RotateMultipleTimes(dir, true, true, 8), dir);
|
||||||
|
Assert.AreEqual(RotateMultipleTimes(dir, false, false, 4), dir);
|
||||||
|
Assert.AreEqual(RotateMultipleTimes(dir, false, true, 8), dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotate by with start Up
|
||||||
|
Assert.AreEqual(Right.RotateBy(Right), Down);
|
||||||
|
Assert.AreEqual(Right.RotateBy(Down), Left);
|
||||||
|
Assert.AreEqual(Right.RotateBy(Left), Up);
|
||||||
|
Assert.AreEqual(Right.RotateBy(Up), Right);
|
||||||
|
|
||||||
|
// rotate by with start Left
|
||||||
|
Assert.AreEqual(Up.RotateBy(Right, Left), Down);
|
||||||
|
Assert.AreEqual(Up.RotateBy(Down, Left), Left);
|
||||||
|
Assert.AreEqual(Up.RotateBy(Left, Left), Up);
|
||||||
|
Assert.AreEqual(Up.RotateBy(Up, Left), Right);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Direction2 RotateMultipleTimes(Direction2 dir, bool clockwise, bool fortyFiveDegrees, int times) {
|
||||||
|
for (var i = 0; i < times; i++)
|
||||||
|
dir = clockwise ? dir.RotateCw(fortyFiveDegrees) : dir.RotateCcw(fortyFiveDegrees);
|
||||||
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue