Skip to content

Commit

Permalink
Merge pull request #70 from sp4ce-cowboy/feature/sound-manager
Browse files Browse the repository at this point in the history
Add Audio Manager
  • Loading branch information
Vanessamae23 authored Apr 3, 2024
2 parents ba6535c + 9d5a59f commit 9cfd3a5
Show file tree
Hide file tree
Showing 15 changed files with 270 additions and 5 deletions.
40 changes: 36 additions & 4 deletions TowerForge/TowerForge.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@
BAFFB96C2BB9AB2400D8301F /* LocalDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB96B2BB9AB2400D8301F /* LocalDatabase.swift */; };
BAFFB9712BBA830F00D8301F /* StorageManager+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9702BBA830F00D8301F /* StorageManager+Operations.swift */; };
BAFFB9732BBAC14500D8301F /* AchievementManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9722BBAC14500D8301F /* AchievementManager.swift */; };
BAFFB9752BBD833400D8301F /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFFB9742BBD833400D8301F /* AudioManager.swift */; };
BAFFB97C2BBD83B200D8301F /* beep.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = BAFFB9762BBD83B200D8301F /* beep.mp3 */; };
BAFFB97D2BBD83B200D8301F /* hit-sound.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = BAFFB9772BBD83B200D8301F /* hit-sound.mp3 */; };
BAFFB97E2BBD83B200D8301F /* field-of-memories-soundtrack.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = BAFFB9782BBD83B200D8301F /* field-of-memories-soundtrack.mp3 */; };
BAFFB97F2BBD83B200D8301F /* lose.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = BAFFB9792BBD83B200D8301F /* lose.mp3 */; };
BAFFB9802BBD83B200D8301F /* success.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = BAFFB97A2BBD83B200D8301F /* success.mp3 */; };
BAFFB9812BBD83B200D8301F /* jump.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = BAFFB97B2BBD83B200D8301F /* jump.mp3 */; };
BAFFB9832BBD8CA800D8301F /* Entering-the-stronghold.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = BAFFB9822BBD8CA800D8301F /* Entering-the-stronghold.mp3 */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -344,6 +352,14 @@
BAFFB96B2BB9AB2400D8301F /* LocalDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalDatabase.swift; sourceTree = "<group>"; };
BAFFB9702BBA830F00D8301F /* StorageManager+Operations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorageManager+Operations.swift"; sourceTree = "<group>"; };
BAFFB9722BBAC14500D8301F /* AchievementManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementManager.swift; sourceTree = "<group>"; };
BAFFB9742BBD833400D8301F /* AudioManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioManager.swift; sourceTree = "<group>"; };
BAFFB9762BBD83B200D8301F /* beep.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = beep.mp3; sourceTree = "<group>"; };
BAFFB9772BBD83B200D8301F /* hit-sound.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "hit-sound.mp3"; sourceTree = "<group>"; };
BAFFB9782BBD83B200D8301F /* field-of-memories-soundtrack.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "field-of-memories-soundtrack.mp3"; sourceTree = "<group>"; };
BAFFB9792BBD83B200D8301F /* lose.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = lose.mp3; sourceTree = "<group>"; };
BAFFB97A2BBD83B200D8301F /* success.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = success.mp3; sourceTree = "<group>"; };
BAFFB97B2BBD83B200D8301F /* jump.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = jump.mp3; sourceTree = "<group>"; };
BAFFB9822BBD8CA800D8301F /* Entering-the-stronghold.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "Entering-the-stronghold.mp3"; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -628,12 +644,12 @@
52DF5FA62BA32B2300135367 /* TowerForge */ = {
isa = PBXGroup;
children = (
5240D0972BB0522A004F1486 /* Info.plist */,
BA443D432BAD98CD009F0FFB /* AppMain */,
52A7940E2BBC476B0083C976 /* Networking */,
52A794072BBC35E30083C976 /* Constants */,
52A794002BBC2E940083C976 /* Firebase */,
5240D0972BB0522A004F1486 /* Info.plist */,
3C3CBDFD2BB870770001B8A9 /* Extensions */,
BA443D432BAD98CD009F0FFB /* AppMain */,
BAFFB92F2BB0A01700D8301F /* Commons */,
5295A2052BAA0208005018A8 /* Nodes */,
BAFFB9462BB0AABF00D8301F /* TFCore */,
Expand Down Expand Up @@ -781,6 +797,14 @@
BAFFB9302BB0A02500D8301F /* Sounds */ = {
isa = PBXGroup;
children = (
BAFFB9742BBD833400D8301F /* AudioManager.swift */,
BAFFB9822BBD8CA800D8301F /* Entering-the-stronghold.mp3 */,
BAFFB9762BBD83B200D8301F /* beep.mp3 */,
BAFFB9782BBD83B200D8301F /* field-of-memories-soundtrack.mp3 */,
BAFFB9772BBD83B200D8301F /* hit-sound.mp3 */,
BAFFB97B2BBD83B200D8301F /* jump.mp3 */,
BAFFB9792BBD83B200D8301F /* lose.mp3 */,
BAFFB97A2BBD83B200D8301F /* success.mp3 */,
);
path = Sounds;
sourceTree = "<group>";
Expand Down Expand Up @@ -1114,11 +1138,18 @@
files = (
523C29282BBCFD93004C6EAC /* Nosifer-Regular.ttf in Resources */,
52DF5FB32BA32B2300135367 /* Main.storyboard in Resources */,
BAFFB97D2BBD83B200D8301F /* hit-sound.mp3 in Resources */,
BAFFB97C2BBD83B200D8301F /* beep.mp3 in Resources */,
BAFFB9812BBD83B200D8301F /* jump.mp3 in Resources */,
5298D7932BBC2AC9005177A1 /* GoogleService-Info.plist in Resources */,
BAFFB97F2BBD83B200D8301F /* lose.mp3 in Resources */,
BAFFB9832BBD8CA800D8301F /* Entering-the-stronghold.mp3 in Resources */,
52DF5FAA2BA32B2300135367 /* GameScene.sks in Resources */,
52DF5FB52BA32B2600135367 /* Assets.xcassets in Resources */,
52DF5FB82BA32B2600135367 /* Launch Screen.storyboard in Resources */,
BAFFB9802BBD83B200D8301F /* success.mp3 in Resources */,
52DF5FAC2BA32B2300135367 /* Actions.sks in Resources */,
BAFFB97E2BBD83B200D8301F /* field-of-memories-soundtrack.mp3 in Resources */,
523923E72BADC8530044BA61 /* LaunchScreen.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1288,6 +1319,7 @@
3CAC4A6F2BB6A4F200A5D22E /* LabelRenderStage.swift in Sources */,
3C0B608D2BB2B84000FFECB4 /* ContactComponent.swift in Sources */,
3CE951652BAE0A04008B2785 /* HomeSystem.swift in Sources */,
BAFFB9752BBD833400D8301F /* AudioManager.swift in Sources */,
52DF5FFB2BA3601400135367 /* HealthComponent.swift in Sources */,
3C9955BC2BA563A800D33FA5 /* TFEvent.swift in Sources */,
527A077A2BB3F35300CD9D08 /* DeathProp.swift in Sources */,
Expand Down Expand Up @@ -1497,7 +1529,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = BPB6799M73;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = TowerForge/Info.plist;
Expand Down Expand Up @@ -1534,7 +1566,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = BPB6799M73;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = TowerForge/Info.plist;
Expand Down
7 changes: 7 additions & 0 deletions TowerForge/TowerForge/AppMain/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.

/// Load the local storage and data
StorageManager.initializeData()

/// Connect to Firebase
FirebaseApp.configure()

/// Prepare audio player to begin playing music
AudioManager.shared.setupAllAudioPlayers()
return true
}

Expand Down
15 changes: 15 additions & 0 deletions TowerForge/TowerForge/Commons/Constants/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,25 @@ class Constants {
/// Universally declaring logging
static let LOGGING_IS_ACTIVE = true

/// Firebase URL
static let DATABASE_URL = "https://towerforge-d5ba7-default-rtdb.asia-southeast1.firebasedatabase.app"

/// The name of the folder in which information is stored locally
static let STORAGE_CONTAINER_NAME = "TowerForge"

/// The name of the file that contains TowerForge data locally
static let TF_DATABASE_NAME = "TowerForgeDatabase"

/// Universal setting to enable or disable sound effects
static var SOUND_EFFECTS_ENABLED = true

/// Universal background audio soundtrack to play during game modes
static let GAME_BACKGROUND_AUDIO: String = "field-of-memories-soundtrack.mp3"

/// Universal background audio soundtrack to play during non-game modes
static let MAIN_BACKGROUND_AUDIO: String = "Entering-the-stronghold.mp3"

/// Universal volume control for in-game volume elements
static var SOUND_EFFECTS_VOLUME: Float = 0.8

}
202 changes: 202 additions & 0 deletions TowerForge/TowerForge/Commons/Sounds/AudioManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import Foundation
import AVFoundation

/// Explicit internal class to ensure that external clients cannot
/// interfere with singleton instance. Singleton anti-pattern used here
/// in line with Apple's AVAudioSession sharedInstance.
///
/// Credits (All music used is free for non-commercial use)
/// - Level Background Music: Entering The Stronghold by [Denny Schneidemesser](https://www.autodidactic.ai/music/)
/// - Game Background Music: Field of Memories by [WaterFlame](https://www.waterflame.com/contact-info)
/// - Sound Effect credits: [Pixabay](https://pixabay.com/service/license-summary/)
internal class AudioManager: NSObject, AVAudioPlayerDelegate {
internal static let shared = AudioManager() // Singleton instance
private var backgroundAudioPlayer: AVAudioPlayer?
private var mainAudioPlayer: AVAudioPlayer?

private var soundEffectPlayers: [String: AVAudioPlayer] = [:] // Cache sound effect players
private var isBackgroundPlaying = false
private var isMainPlaying = false

private let SOUND_EFFECTS_ENABLED = Constants.SOUND_EFFECTS_ENABLED
private let VOLUME = Constants.SOUND_EFFECTS_VOLUME

override init() {
super.init()
setupAllAudioPlayers()
backgroundAudioPlayer?.prepareToPlay()
Logger.log("AudioManager is initialized", self)
}

func setupAllAudioPlayers() {
setupGameAudioPlayer()
setupMainAudioPlayer()
}

func setupGameAudioPlayer() {
let soundName = Constants.GAME_BACKGROUND_AUDIO

guard let soundURL = Bundle.main.url(forResource: soundName,
withExtension: nil) else {
Logger.log("Sound effect \(soundName) not found", self)
return
}

do {
try AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
backgroundAudioPlayer = try AVAudioPlayer(contentsOf: soundURL)
backgroundAudioPlayer?.delegate = self
backgroundAudioPlayer?.numberOfLoops = -1 // Loop indefinitely
backgroundAudioPlayer?.volume = VOLUME
backgroundAudioPlayer?.prepareToPlay()
} catch {
Logger.log("Failed to play sound effect \(soundName): \(error)", self)
}
}

func setupMainAudioPlayer() {
let soundName = Constants.MAIN_BACKGROUND_AUDIO

guard let soundURL = Bundle.main.url(forResource: soundName,
withExtension: nil) else {
Logger.log("Sound effect \(soundName) not found", self)
return
}

do {
try AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
mainAudioPlayer = try AVAudioPlayer(contentsOf: soundURL)
mainAudioPlayer?.delegate = self
mainAudioPlayer?.numberOfLoops = -1 // Loop indefinitely
mainAudioPlayer?.volume = VOLUME
mainAudioPlayer?.prepareToPlay()
} catch {
Logger.log("Failed to play sound effect \(soundName): \(error)", self)
}
}

/// Plays background music
func playMainMusic() {
stopBackground()
if !isMainPlaying {
mainAudioPlayer?.play()
isMainPlaying = true
}
}

/// Pauses background music
func pauseMainMusic() {
if isMainPlaying {
mainAudioPlayer?.pause()
isMainPlaying = false
}
}

/// Stops background music and resets the time
func stopMainMusic() {
mainAudioPlayer?.stop()
mainAudioPlayer?.currentTime = 0
isMainPlaying = false
}

/// Plays background music
func playBackground() {
stopMainMusic()
if !isBackgroundPlaying {
backgroundAudioPlayer?.play()
isBackgroundPlaying = true
}
}

/// Pauses background music
func pauseBackground() {
if isBackgroundPlaying {
backgroundAudioPlayer?.pause()
isBackgroundPlaying = false
}
}

/// Stops background music and resets the time
func stopBackground() {
backgroundAudioPlayer?.stop()
backgroundAudioPlayer?.currentTime = 0
isBackgroundPlaying = false
}

/// Mutes background music
func muteBackground() {
backgroundAudioPlayer?.volume = 0
}

/// Unmutes background music
func unmuteBackground() {
backgroundAudioPlayer?.volume = VOLUME
}

/// Toggle play/pause background music
func toggleBackground() {
if isBackgroundPlaying {
self.pauseBackground()
} else {
self.playBackground()
}
}

/// Toggle play/pause main music
func toggleMain() {
if isMainPlaying {
self.pauseMainMusic()
} else {
self.playMainMusic()
}
}

/// Play a sound effect
func playSoundEffect(named soundName: String) {
guard SOUND_EFFECTS_ENABLED else {
return
}
// Attempt to use a cached player if available
if let player = soundEffectPlayers[soundName] {
player.play()
} else {
// Create a new player for the sound effect
guard let soundURL = Bundle.main.url(forResource: soundName, withExtension: nil) else {
Logger.log("Sound effect \(soundName) not found", self)
return
}
do {
let player = try AVAudioPlayer(contentsOf: soundURL)
player.volume = 1.0
player.prepareToPlay()
player.play()
soundEffectPlayers[soundName] = player
} catch {
Logger.log("Failed to play sound effect \(soundName): \(error)", self)
}
}
}

// --- Sound effect players --- //
func playWinSoundEffect() {
playSoundEffect(named: "success.mp3")
}

func playLoseSoundEffect() {
playSoundEffect(named: "lose.mp3")
}

func playHitEffect() {
playSoundEffect(named: "hit-sound.mp3")
}

func playSpecialEffect() {
playSoundEffect(named: "jump.mp3")
}

func playBeepEffect() {
playSoundEffect(named: "beep.mp3")
}
}
Binary file not shown.
Binary file added TowerForge/TowerForge/Commons/Sounds/beep.mp3
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added TowerForge/TowerForge/Commons/Sounds/jump.mp3
Binary file not shown.
Binary file added TowerForge/TowerForge/Commons/Sounds/lose.mp3
Binary file not shown.
Binary file added TowerForge/TowerForge/Commons/Sounds/success.mp3
Binary file not shown.
2 changes: 1 addition & 1 deletion TowerForge/TowerForge/Firebase/FirebaseReference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ enum FirebaseReference: String {

func FirebaseDatabaseReference(_ reference: FirebaseReference) -> DatabaseReference {

Database.database(url: "https://towerforge-d5ba7-default-rtdb.asia-southeast1.firebasedatabase.app")
Database.database(url: Constants.DATABASE_URL)
.reference()
.child(reference.rawValue)
}
2 changes: 2 additions & 0 deletions TowerForge/TowerForge/GameViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
AchievementManager.incrementTotalGamesStarted()
AudioManager.shared.playBackground()
showGameLevelScene(level: 1) // TODO : Change hardcoded level value
}

override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
AudioManager.shared.pauseBackground()
gameWorld = nil
}

Expand Down
2 changes: 2 additions & 0 deletions TowerForge/TowerForge/Scenes/GameOverScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class GameOverScene: SKScene {

override func didMove(to view: SKView) {
setupScene()
AudioManager.shared.stopBackground()
self.win ? AudioManager.shared.playWinSoundEffect() : AudioManager.shared.playLoseSoundEffect()
}

func setupScene() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import UIKit
class MainMenuViewController: UIViewController {
var selectedGameMode: Mode = .deathMatch

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
AudioManager.shared.playMainMusic()
}

@IBAction private func DeathMatch(_ sender: Any) {
selectedGameMode = .deathMatch
performSegue(withIdentifier: "segueToGame", sender: self)
Expand Down

0 comments on commit 9cfd3a5

Please sign in to comment.