Skip to content

Instantly share code, notes, and snippets.

@gregoryfmartin
Last active March 24, 2021 14:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gregoryfmartin/3f5eb1586cafb03f116f40ba7a7c6d4e to your computer and use it in GitHub Desktop.
Save gregoryfmartin/3f5eb1586cafb03f116f40ba7a7c6d4e to your computer and use it in GitHub Desktop.
SFML.NET - Playing Audio (Music/BGM) using WAV
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