Last active
March 24, 2021 14:37
-
-
Save gregoryfmartin/3f5eb1586cafb03f116f40ba7a7c6d4e to your computer and use it in GitHub Desktop.
SFML.NET - Playing Audio (Music/BGM) using WAV
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using SFML.System; | |
using SFML.Window; | |
using SFML.Graphics; | |
using SFML.Audio; | |
/// | |
/// I can't distribute the audio files used here, but they were obtained from GameDevMarket at the following link: | |
/// https://www.gamedevmarket.net/asset/8bit-music-pack/ | |
/// | |
namespace SFMLNetAudioPlayground { | |
static class Program { | |
public static void Main (String [] args) { | |
new GameCore ().Run (); | |
} | |
} | |
class GameCore { | |
/// <summary> | |
/// Is the Game Core engine running or not? | |
/// </summary> | |
private Boolean Running; | |
/// <summary> | |
/// The global clock that runs the game. | |
/// </summary> | |
private Clock GameClock; | |
/// <summary> | |
/// The main window where all the action will occur. | |
/// </summary> | |
private GameWindow MainWindow; | |
/// <summary> | |
/// A holder for the FPS Delta so that its value can be used in input delegate functions. | |
/// </summary> | |
private float FPSD; | |
private PlayerObject PO; | |
private AudioManager audioManager; | |
public GameCore () { | |
Running = true; | |
GameClock = null; | |
MainWindow = null; | |
PO = null; | |
audioManager = null; | |
} | |
public void Run () { | |
Init (); | |
while (Running) { | |
FPSD = GameClock.Restart ().AsSeconds (); | |
Update (FPSD); | |
Draw (); | |
} | |
Deinit (); | |
} | |
private void Init () { | |
MainWindow = new GameWindow (); | |
MainWindow.PrincipalWindow.SetFramerateLimit (60); | |
MainWindow.PrincipalWindow.SetVerticalSyncEnabled (true); | |
MainWindow.KDownHandler += this.CheckGlobalInput; | |
MainWindow.KDownHandler += this.CheckPlayerInputPressed; | |
MainWindow.KUpHandler += this.CheckPlayerInputReleased; | |
PO = new PlayerObject (15.0f) { | |
FillColor = Color.Red | |
}; | |
audioManager = new AudioManager (); | |
audioManager.InitLibrary (); | |
audioManager.SetCurrentBGM (AudioManager.BGM_TRACK2); | |
MainWindow.ToRender.Add (PO); | |
GameClock = new Clock (); | |
} | |
private void Update (float Delta) { | |
MainWindow.Update (Delta); | |
PO.Update (Delta); | |
if (audioManager.CurrentBGM.Status != SFML.Audio.SoundStatus.Playing) { | |
audioManager.CurrentBGM.Play (); | |
} | |
} | |
private void Draw () { | |
MainWindow.Draw (); | |
} | |
private void Deinit () { | |
MainWindow.PrincipalWindow.Close (); | |
} | |
private void CheckGlobalInput (Object sender, KeyEventArgs e) { | |
if (e.Code == Keyboard.Key.Escape) { | |
Running = false; | |
} | |
} | |
private void CheckPlayerInputPressed (Object sender, KeyEventArgs e) { | |
PO.CheckInputPressed (e); | |
} | |
private void CheckPlayerInputReleased (Object sender, KeyEventArgs e) { | |
PO.CheckInputReleased (e); | |
} | |
} | |
class GameWindow { | |
public RenderWindow PrincipalWindow { get; } | |
public List<Drawable> ToRender { get; } | |
public delegate void KeyDownHandler (Object sender, KeyEventArgs e); | |
public delegate void KeyUpHandler (Object sender, KeyEventArgs e); | |
public KeyUpHandler KUpHandler { get; set; } | |
public KeyDownHandler KDownHandler { get; set; } | |
public GameWindow () { | |
PrincipalWindow = new RenderWindow (new VideoMode (800, 600), "SFML.NET Game Core"); | |
ToRender = new List<Drawable> (); | |
PrincipalWindow.KeyPressed += this.Window_KeyPressed; | |
PrincipalWindow.KeyReleased += this.Window_KeyReleased; | |
// Because delegates are cool. | |
KUpHandler += this.Dummy_KeyReleased; | |
KDownHandler += this.Dummy_KeyPressed; | |
} | |
public void Update (float Delta) { | |
PrincipalWindow.DispatchEvents (); | |
} | |
public void Draw () { | |
PrincipalWindow.Clear (Color.Black); | |
if (ToRender.Count > 0) { | |
foreach (Drawable d in ToRender) { | |
PrincipalWindow.Draw (d); | |
} | |
} | |
PrincipalWindow.Display (); | |
} | |
private void Window_KeyPressed (Object sender, KeyEventArgs e) { | |
KDownHandler (sender, e); | |
} | |
private void Window_KeyReleased (Object sender, KeyEventArgs e) { | |
KUpHandler (sender, e); | |
} | |
/// <summary> | |
/// Because delegates are cool. | |
/// </summary> | |
/// <param name="sender"></param> | |
/// <param name="e"></param> | |
private void Dummy_KeyPressed (Object sender, KeyEventArgs e) { } | |
/// <summary> | |
/// Because delegates are cool. | |
/// </summary> | |
/// <param name="sender"></param> | |
/// <param name="e"></param> | |
private void Dummy_KeyReleased (Object sender, KeyEventArgs e) { } | |
} | |
/// <summary> | |
/// The entity the player controls. The only major callout here is that it supports eight-directional | |
/// movement by translating key states to flag toggles in a dictionary for valid cardinal directions. | |
/// A combination of these will yield eight-directional movement patterns. | |
/// </summary> | |
class PlayerObject : CircleShape { | |
public Dictionary<String, Boolean> MovementStates { get; } | |
public Vector2f Velocity { get; set; } | |
public PlayerObject () : base () { | |
MovementStates = new Dictionary<String, Boolean> { | |
{ "Left", false }, | |
{ "Right", false }, | |
{ "Up", false }, | |
{ "Down", false } | |
}; | |
Velocity = new Vector2f (3.0f, 3.0f); | |
} | |
public PlayerObject (Single radius) : base (radius) { | |
MovementStates = new Dictionary<String, Boolean> { | |
{ "Left", false }, | |
{ "Right", false }, | |
{ "Up", false }, | |
{ "Down", false } | |
}; | |
Velocity = new Vector2f (3.0f, 3.0f); | |
} | |
public void CheckInputPressed (KeyEventArgs e) { | |
switch (e.Code) { | |
case Keyboard.Key.Right: | |
MovementStates ["Right"] = true; | |
break; | |
case Keyboard.Key.Left: | |
MovementStates ["Left"] = true; | |
break; | |
case Keyboard.Key.Up: | |
MovementStates ["Up"] = true; | |
break; | |
case Keyboard.Key.Down: | |
MovementStates ["Down"] = true; | |
break; | |
} | |
} | |
public void CheckInputReleased (KeyEventArgs e) { | |
switch (e.Code) { | |
case Keyboard.Key.Right: | |
MovementStates ["Right"] = false; | |
break; | |
case Keyboard.Key.Left: | |
MovementStates ["Left"] = false; | |
break; | |
case Keyboard.Key.Up: | |
MovementStates ["Up"] = false; | |
break; | |
case Keyboard.Key.Down: | |
MovementStates ["Down"] = false; | |
break; | |
} | |
} | |
/// <summary> | |
/// The scalar on the Delta here is reliant upon the fact that the desired FPS is set to 60 and VSync is on. Otherwise, | |
/// the Delta becomes highly irregular and this scalar could easily result in neighbouring values ranging from 1 to 11. | |
/// With VSync and nearly locked FPS, the value of Delta becomes considerably more predictable. There's definitely a better | |
/// way of doing this, but I'm unsure what it would be at the moment. | |
/// </summary> | |
/// <param name="Delta">The time between frames, taken from the main game loop through a multidelegate.</param> | |
public void Update (float Delta) { | |
if (MovementStates ["Right"]) { | |
Position += new Vector2f (Velocity.X * (Delta * 100), 0.0f); | |
} | |
if (MovementStates ["Left"]) { | |
Position += new Vector2f (-(Velocity.X * (Delta * 100)), 0.0f); | |
} | |
if (MovementStates ["Up"]) { | |
Position += new Vector2f (0.0f, -(Velocity.Y * (Delta * 100))); | |
} | |
if (MovementStates ["Down"]) { | |
Position += new Vector2f (0.0f, Velocity.Y * (Delta * 100)); | |
} | |
} | |
} | |
/// <summary> | |
/// This represents a really ham-handed method for managing audio, but for the sake of this demo, it's fine. | |
/// </summary> | |
class AudioManager { | |
private Dictionary<String, Music> BGMLibrary; | |
private Music _CurrentBGM; | |
public const String BGM_TRACK1 = "Track 1"; | |
public const String BGM_TRACK2 = "Track 2"; | |
public const String BGM_TRACK3 = "Track 3"; | |
public const String BGM_TRACK4 = "Track 4"; | |
/// <summary> | |
/// <see cref="SFML.Music.Loop"/> | |
/// </summary> | |
public Boolean LoopBGM { get; set; } | |
/// <summary> | |
/// <see cref="SFML.Music.Pitch"/> | |
/// </summary> | |
public float BGMPitch { get; set; } | |
/// <summary> | |
/// <see cref="SFML.Music.RelativeToListener"/> | |
/// </summary> | |
public Boolean BGMRelativeToListener { get; set; } | |
/// <summary> | |
/// <see cref="SFML.Music.Volume"/> | |
/// </summary> | |
public float BGMVolume { get; set; } | |
public Music CurrentBGM { get { return _CurrentBGM; } } | |
public AudioManager () { | |
BGMLibrary = null; | |
_CurrentBGM = null; | |
LoopBGM = true; | |
BGMPitch = 1.0f; | |
BGMRelativeToListener = false; | |
BGMVolume = 100.0f; | |
} | |
/// <summary> | |
/// This function represents a controlled environment, which in large part the deployment will be, | |
/// but this feels too rigid. | |
/// </summary> | |
public void InitLibrary () { | |
BGMLibrary = new Dictionary<String, Music> () { | |
{ BGM_TRACK1, new Music (".\\Assets\\BGM\\Track1.wav") }, | |
{ BGM_TRACK2, new Music (".\\Assets\\BGM\\Track2.wav") }, | |
{ BGM_TRACK3, new Music (".\\Assets\\BGM\\Track3.wav") }, | |
{ BGM_TRACK4, new Music (".\\Assets\\BGM\\Track4.wav") } | |
}; | |
} | |
public void SetCurrentBGM (String TrackName) { | |
// First, we want to see if we can find the track and validate that it's been loaded | |
if (BGMLibrary.ContainsKey (TrackName)) { | |
if (BGMLibrary [TrackName] != null) { | |
_CurrentBGM = BGMLibrary [TrackName]; | |
} else { | |
throw new TrackNotLoadedException (TrackName); | |
} | |
} else { | |
throw new TrackNotFoundException (TrackName); | |
} | |
// Configure the BGM with global settings | |
_CurrentBGM.Loop = LoopBGM; | |
_CurrentBGM.Pitch = BGMPitch; | |
_CurrentBGM.RelativeToListener = BGMRelativeToListener; | |
_CurrentBGM.Volume = BGMVolume; | |
} | |
public class TrackNotLoadedException : Exception { | |
public TrackNotLoadedException (String TrackName) : base ("Requested track " + TrackName + " was found in the library but not loaded into memory.") { } | |
} | |
public class TrackNotFoundException : Exception { | |
public TrackNotFoundException (String TrackName) : base ("Requested track " + TrackName + " was not found in the library.") { } | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment