Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
deedc08b85
commit
7803a561ae
@ -140,6 +140,16 @@ final class GameState {
|
|||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.engine = BaccaratEngine(deckCount: settings.deckCount.rawValue)
|
self.engine = BaccaratEngine(deckCount: settings.deckCount.rawValue)
|
||||||
self.balance = settings.startingBalance
|
self.balance = settings.startingBalance
|
||||||
|
|
||||||
|
// Sync sound settings with SoundManager
|
||||||
|
syncSoundSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Syncs sound settings from GameSettings to SoundManager.
|
||||||
|
private func syncSoundSettings() {
|
||||||
|
sound.soundEnabled = settings.soundEnabled
|
||||||
|
sound.hapticsEnabled = settings.hapticsEnabled
|
||||||
|
sound.volume = settings.soundVolume
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Computed Properties for Bets
|
// MARK: - Computed Properties for Bets
|
||||||
@ -228,13 +238,8 @@ final class GameState {
|
|||||||
|
|
||||||
balance -= amount
|
balance -= amount
|
||||||
|
|
||||||
// Play chip placement sound
|
// Play chip placement sound and haptic
|
||||||
if settings.soundEnabled {
|
sound.playChipPlace()
|
||||||
sound.play(.chipPlace)
|
|
||||||
}
|
|
||||||
if settings.hapticsEnabled {
|
|
||||||
sound.hapticLight()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears all current bets and returns the amounts to balance.
|
/// Clears all current bets and returns the amounts to balance.
|
||||||
@ -243,13 +248,8 @@ final class GameState {
|
|||||||
balance += totalBetAmount
|
balance += totalBetAmount
|
||||||
currentBets = []
|
currentBets = []
|
||||||
|
|
||||||
// Play clear bets sound
|
// Play clear bets sound and haptic
|
||||||
if settings.soundEnabled {
|
sound.playClearBets()
|
||||||
sound.play(.clearBets)
|
|
||||||
}
|
|
||||||
if settings.hapticsEnabled {
|
|
||||||
sound.hapticMedium()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Undoes the last bet placed.
|
/// Undoes the last bet placed.
|
||||||
@ -290,9 +290,7 @@ final class GameState {
|
|||||||
try? await Task.sleep(for: dealDelay)
|
try? await Task.sleep(for: dealDelay)
|
||||||
|
|
||||||
// Play card deal sound
|
// Play card deal sound
|
||||||
if settings.soundEnabled {
|
sound.playCardDeal()
|
||||||
sound.play(.cardDeal)
|
|
||||||
}
|
|
||||||
|
|
||||||
if index % 2 == 0 {
|
if index % 2 == 0 {
|
||||||
visiblePlayerCards.append(card)
|
visiblePlayerCards.append(card)
|
||||||
@ -307,9 +305,7 @@ final class GameState {
|
|||||||
try? await Task.sleep(for: flipDelay)
|
try? await Task.sleep(for: flipDelay)
|
||||||
|
|
||||||
// Play card flip sound
|
// Play card flip sound
|
||||||
if settings.soundEnabled {
|
sound.playCardFlip()
|
||||||
sound.play(.cardFlip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flip all cards face up
|
// Flip all cards face up
|
||||||
for i in 0..<playerCardsFaceUp.count {
|
for i in 0..<playerCardsFaceUp.count {
|
||||||
@ -344,15 +340,11 @@ final class GameState {
|
|||||||
if let playerThird = engine.drawPlayerThirdCard() {
|
if let playerThird = engine.drawPlayerThirdCard() {
|
||||||
if settings.showAnimations {
|
if settings.showAnimations {
|
||||||
try? await Task.sleep(for: dealDelay)
|
try? await Task.sleep(for: dealDelay)
|
||||||
if settings.soundEnabled {
|
sound.playCardDeal()
|
||||||
sound.play(.cardDeal)
|
|
||||||
}
|
|
||||||
visiblePlayerCards.append(playerThird)
|
visiblePlayerCards.append(playerThird)
|
||||||
playerCardsFaceUp.append(false)
|
playerCardsFaceUp.append(false)
|
||||||
try? await Task.sleep(for: shortDelay)
|
try? await Task.sleep(for: shortDelay)
|
||||||
if settings.soundEnabled {
|
sound.playCardFlip()
|
||||||
sound.play(.cardFlip)
|
|
||||||
}
|
|
||||||
playerCardsFaceUp[2] = true
|
playerCardsFaceUp[2] = true
|
||||||
try? await Task.sleep(for: flipDelay)
|
try? await Task.sleep(for: flipDelay)
|
||||||
} else {
|
} else {
|
||||||
@ -366,15 +358,11 @@ final class GameState {
|
|||||||
if let bankerThird = engine.drawBankerThirdCard() {
|
if let bankerThird = engine.drawBankerThirdCard() {
|
||||||
if settings.showAnimations {
|
if settings.showAnimations {
|
||||||
try? await Task.sleep(for: dealDelay)
|
try? await Task.sleep(for: dealDelay)
|
||||||
if settings.soundEnabled {
|
sound.playCardDeal()
|
||||||
sound.play(.cardDeal)
|
|
||||||
}
|
|
||||||
visibleBankerCards.append(bankerThird)
|
visibleBankerCards.append(bankerThird)
|
||||||
bankerCardsFaceUp.append(false)
|
bankerCardsFaceUp.append(false)
|
||||||
try? await Task.sleep(for: shortDelay)
|
try? await Task.sleep(for: shortDelay)
|
||||||
if settings.soundEnabled {
|
sound.playCardFlip()
|
||||||
sound.play(.cardFlip)
|
|
||||||
}
|
|
||||||
bankerCardsFaceUp[2] = true
|
bankerCardsFaceUp[2] = true
|
||||||
try? await Task.sleep(for: dealDelay)
|
try? await Task.sleep(for: dealDelay)
|
||||||
} else {
|
} else {
|
||||||
@ -427,27 +415,12 @@ final class GameState {
|
|||||||
// Determine if it's a big win (>= 5x any bet amount or >= 500)
|
// Determine if it's a big win (>= 5x any bet amount or >= 500)
|
||||||
let maxBetAmount = currentBets.map(\.amount).max() ?? 0
|
let maxBetAmount = currentBets.map(\.amount).max() ?? 0
|
||||||
let isBigWin = totalWinnings >= maxBetAmount * 5 || totalWinnings >= 500
|
let isBigWin = totalWinnings >= maxBetAmount * 5 || totalWinnings >= 500
|
||||||
if settings.soundEnabled {
|
sound.playWin(isBigWin: isBigWin)
|
||||||
sound.play(isBigWin ? .bigWin : .win)
|
|
||||||
}
|
|
||||||
if settings.hapticsEnabled {
|
|
||||||
sound.hapticSuccess()
|
|
||||||
}
|
|
||||||
} else if totalWinnings < 0 {
|
} else if totalWinnings < 0 {
|
||||||
if settings.soundEnabled {
|
sound.playLose()
|
||||||
sound.play(.lose)
|
|
||||||
}
|
|
||||||
if settings.hapticsEnabled {
|
|
||||||
sound.hapticError()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Push (tie with main bet push)
|
// Push (tie with main bet push)
|
||||||
if settings.soundEnabled {
|
sound.playPush()
|
||||||
sound.play(.push)
|
|
||||||
}
|
|
||||||
if settings.hapticsEnabled {
|
|
||||||
sound.hapticMedium()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record result in history
|
// Record result in history
|
||||||
@ -470,9 +443,7 @@ final class GameState {
|
|||||||
guard currentPhase == .roundComplete else { return }
|
guard currentPhase == .roundComplete else { return }
|
||||||
|
|
||||||
// Play new round sound
|
// Play new round sound
|
||||||
if settings.soundEnabled {
|
sound.playNewRound()
|
||||||
sound.play(.newRound)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dismiss result banner
|
// Dismiss result banner
|
||||||
showResultBanner = false
|
showResultBanner = false
|
||||||
@ -519,12 +490,7 @@ final class GameState {
|
|||||||
betResults = []
|
betResults = []
|
||||||
|
|
||||||
// Play new game sound
|
// Play new game sound
|
||||||
if settings.soundEnabled {
|
sound.playNewRound()
|
||||||
sound.play(.newRound)
|
|
||||||
}
|
|
||||||
if settings.hapticsEnabled {
|
|
||||||
sound.hapticMedium()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies new settings (call after settings change).
|
/// Applies new settings (call after settings change).
|
||||||
|
|||||||
@ -148,6 +148,66 @@ let image = IconRenderer.renderAppIcon(config: .baccarat, size: 1024)
|
|||||||
let allImages = IconRenderer.renderAllSizes(config: .baccarat)
|
let allImages = IconRenderer.renderAllSizes(config: .baccarat)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 🔊 Audio & Haptics
|
||||||
|
|
||||||
|
**SoundManager** - Manages game sounds and haptic feedback.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Access shared instance
|
||||||
|
let sound = SoundManager.shared
|
||||||
|
|
||||||
|
// Configure settings
|
||||||
|
sound.soundEnabled = true
|
||||||
|
sound.hapticsEnabled = true
|
||||||
|
sound.volume = 1.0
|
||||||
|
|
||||||
|
// Play sounds
|
||||||
|
sound.play(.chipPlace)
|
||||||
|
sound.play(.cardDeal)
|
||||||
|
sound.play(.win)
|
||||||
|
|
||||||
|
// Convenience methods (include haptics)
|
||||||
|
sound.playChipPlace() // Chip sound + light haptic
|
||||||
|
sound.playCardFlip() // Card sound + light haptic
|
||||||
|
sound.playWin() // Win sound + success haptic
|
||||||
|
sound.playLose() // Lose sound + error haptic
|
||||||
|
sound.playGameOver() // Game over sound + error haptic
|
||||||
|
```
|
||||||
|
|
||||||
|
**Available Sounds:**
|
||||||
|
| Sound | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `.chipPlace` | Placing a bet |
|
||||||
|
| `.chipStack` | Stacking chips |
|
||||||
|
| `.cardDeal` | Dealing a card |
|
||||||
|
| `.cardFlip` | Flipping a card |
|
||||||
|
| `.cardShuffle` | Shuffling deck |
|
||||||
|
| `.win` | Player wins |
|
||||||
|
| `.lose` | Player loses |
|
||||||
|
| `.push` | Tie/push |
|
||||||
|
| `.bigWin` | Large payout |
|
||||||
|
| `.buttonTap` | UI tap |
|
||||||
|
| `.newRound` | Starting new round |
|
||||||
|
| `.clearBets` | Clearing bets |
|
||||||
|
| `.gameOver` | Out of chips |
|
||||||
|
|
||||||
|
**Custom Sound Files:**
|
||||||
|
|
||||||
|
The manager uses iOS system sounds as fallback. To use custom sounds:
|
||||||
|
1. Add `.mp3` files named: `chip_place.mp3`, `card_deal.mp3`, `win.mp3`, etc.
|
||||||
|
2. Add them to your app bundle's Resources folder
|
||||||
|
3. Files are automatically detected and used
|
||||||
|
|
||||||
|
**Haptic Methods:**
|
||||||
|
```swift
|
||||||
|
sound.hapticLight() // Light tap
|
||||||
|
sound.hapticMedium() // Medium tap
|
||||||
|
sound.hapticHeavy() // Heavy tap
|
||||||
|
sound.hapticSuccess() // Success notification
|
||||||
|
sound.hapticError() // Error notification
|
||||||
|
sound.hapticWarning() // Warning notification
|
||||||
|
```
|
||||||
|
|
||||||
### 🎨 Design System
|
### 🎨 Design System
|
||||||
|
|
||||||
**CasinoDesign** - Shared design constants.
|
**CasinoDesign** - Shared design constants.
|
||||||
@ -306,11 +366,15 @@ CasinoKit/
|
|||||||
│ │ ├── AppIconView.swift
|
│ │ ├── AppIconView.swift
|
||||||
│ │ ├── LaunchScreenView.swift
|
│ │ ├── LaunchScreenView.swift
|
||||||
│ │ └── IconRenderer.swift
|
│ │ └── IconRenderer.swift
|
||||||
|
│ ├── Audio/
|
||||||
|
│ │ └── SoundManager.swift
|
||||||
│ ├── Theme/
|
│ ├── Theme/
|
||||||
│ │ ├── CasinoTheme.swift
|
│ │ ├── CasinoTheme.swift
|
||||||
│ │ └── CasinoDesign.swift
|
│ │ └── CasinoDesign.swift
|
||||||
│ └── Resources/
|
│ └── Resources/
|
||||||
│ └── Localizable.xcstrings
|
│ ├── Localizable.xcstrings
|
||||||
|
│ └── Sounds/
|
||||||
|
│ └── README.md (+ .mp3 files)
|
||||||
└── Tests/CasinoKitTests/
|
└── Tests/CasinoKitTests/
|
||||||
└── CasinoKitTests.swift
|
└── CasinoKitTests.swift
|
||||||
```
|
```
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
//
|
//
|
||||||
// SoundManager.swift
|
// SoundManager.swift
|
||||||
// Baccarat
|
// CasinoKit
|
||||||
//
|
//
|
||||||
// Manages game sound effects.
|
// Manages game sound effects for casino games.
|
||||||
//
|
//
|
||||||
|
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
import AudioToolbox
|
import AudioToolbox
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
/// Types of sound effects used in the game.
|
/// Types of sound effects used in casino games.
|
||||||
enum GameSound: String, CaseIterable {
|
public enum GameSound: String, CaseIterable, Sendable {
|
||||||
case chipPlace = "chip_place" // When placing a bet
|
case chipPlace = "chip_place" // When placing a bet
|
||||||
case chipStack = "chip_stack" // Stacking multiple chips
|
case chipStack = "chip_stack" // Stacking multiple chips
|
||||||
case cardDeal = "card_deal" // Dealing a card
|
case cardDeal = "card_deal" // Dealing a card
|
||||||
@ -26,11 +26,11 @@ enum GameSound: String, CaseIterable {
|
|||||||
case gameOver = "game_over" // Out of chips / game over
|
case gameOver = "game_over" // Out of chips / game over
|
||||||
|
|
||||||
/// File extension for the sound file.
|
/// File extension for the sound file.
|
||||||
var fileExtension: String { "mp3" }
|
public var fileExtension: String { "mp3" }
|
||||||
|
|
||||||
/// System sound ID to use as fallback when custom sound file is missing.
|
/// System sound ID to use as fallback when custom sound file is missing.
|
||||||
/// These are built-in iOS sounds that approximate the intended effect.
|
/// These are built-in iOS sounds that approximate the intended effect.
|
||||||
var fallbackSystemSound: SystemSoundID {
|
public var fallbackSystemSound: SystemSoundID {
|
||||||
switch self {
|
switch self {
|
||||||
case .chipPlace: return 1104 // Key press click
|
case .chipPlace: return 1104 // Key press click
|
||||||
case .chipStack: return 1105 // Keyboard key
|
case .chipStack: return 1105 // Keyboard key
|
||||||
@ -49,7 +49,7 @@ enum GameSound: String, CaseIterable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Display name for settings.
|
/// Display name for settings.
|
||||||
var displayName: String {
|
public var displayName: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .chipPlace: return "Chip Place"
|
case .chipPlace: return "Chip Place"
|
||||||
case .chipStack: return "Chip Stack"
|
case .chipStack: return "Chip Stack"
|
||||||
@ -68,34 +68,48 @@ enum GameSound: String, CaseIterable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manages playing game sound effects.
|
/// Manages playing game sound effects for casino games.
|
||||||
|
///
|
||||||
|
/// Use the shared instance and configure it with your app's settings:
|
||||||
|
/// ```swift
|
||||||
|
/// SoundManager.shared.soundEnabled = settings.soundEnabled
|
||||||
|
/// SoundManager.shared.volume = settings.soundVolume
|
||||||
|
/// ```
|
||||||
@MainActor
|
@MainActor
|
||||||
@Observable
|
@Observable
|
||||||
final class SoundManager {
|
public final class SoundManager {
|
||||||
|
|
||||||
// MARK: - Singleton
|
// MARK: - Singleton
|
||||||
|
|
||||||
static let shared = SoundManager()
|
public static let shared = SoundManager()
|
||||||
|
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
|
||||||
/// Whether sound effects are enabled.
|
/// Whether sound effects are enabled.
|
||||||
var soundEnabled: Bool = true {
|
public var soundEnabled: Bool = true {
|
||||||
didSet {
|
didSet {
|
||||||
UserDefaults.standard.set(soundEnabled, forKey: "soundEnabled")
|
UserDefaults.standard.set(soundEnabled, forKey: "casinokit.soundEnabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether haptic feedback is enabled.
|
||||||
|
public var hapticsEnabled: Bool = true {
|
||||||
|
didSet {
|
||||||
|
UserDefaults.standard.set(hapticsEnabled, forKey: "casinokit.hapticsEnabled")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Master volume (0.0 to 1.0).
|
/// Master volume (0.0 to 1.0).
|
||||||
var volume: Float = 1.0 {
|
public var volume: Float = 1.0 {
|
||||||
didSet {
|
didSet {
|
||||||
UserDefaults.standard.set(volume, forKey: "soundVolume")
|
UserDefaults.standard.set(volume, forKey: "casinokit.soundVolume")
|
||||||
updatePlayerVolumes()
|
updatePlayerVolumes()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to use system sounds as fallback (true until custom sounds are added).
|
/// The bundle to load sound files from. Defaults to CasinoKit's bundle.
|
||||||
private var useSystemSoundsFallback: Bool = true
|
/// Set this to `.main` if sounds are in your app bundle instead.
|
||||||
|
public var soundBundle: Bundle = .module
|
||||||
|
|
||||||
/// Cache of audio players for quick playback.
|
/// Cache of audio players for quick playback.
|
||||||
private var audioPlayers: [GameSound: AVAudioPlayer] = [:]
|
private var audioPlayers: [GameSound: AVAudioPlayer] = [:]
|
||||||
@ -104,8 +118,9 @@ final class SoundManager {
|
|||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
// Load saved preferences
|
// Load saved preferences
|
||||||
soundEnabled = UserDefaults.standard.object(forKey: "soundEnabled") as? Bool ?? true
|
soundEnabled = UserDefaults.standard.object(forKey: "casinokit.soundEnabled") as? Bool ?? true
|
||||||
volume = UserDefaults.standard.object(forKey: "soundVolume") as? Float ?? 1.0
|
hapticsEnabled = UserDefaults.standard.object(forKey: "casinokit.hapticsEnabled") as? Bool ?? true
|
||||||
|
volume = UserDefaults.standard.object(forKey: "casinokit.soundVolume") as? Float ?? 1.0
|
||||||
|
|
||||||
// Configure audio session
|
// Configure audio session
|
||||||
configureAudioSession()
|
configureAudioSession()
|
||||||
@ -121,29 +136,26 @@ final class SoundManager {
|
|||||||
try AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default)
|
try AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default)
|
||||||
try AVAudioSession.sharedInstance().setActive(true)
|
try AVAudioSession.sharedInstance().setActive(true)
|
||||||
} catch {
|
} catch {
|
||||||
print("Failed to configure audio session: \(error)")
|
print("CasinoKit: Failed to configure audio session: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Preloading
|
// MARK: - Preloading
|
||||||
|
|
||||||
/// Preloads all sound files for faster playback.
|
/// Preloads all sound files for faster playback.
|
||||||
private func preloadSounds() {
|
/// Call this after setting `soundBundle` if you're using a custom bundle.
|
||||||
var foundCustomSound = false
|
public func preloadSounds() {
|
||||||
|
audioPlayers.removeAll()
|
||||||
|
|
||||||
for sound in GameSound.allCases {
|
for sound in GameSound.allCases {
|
||||||
if let player = createPlayer(for: sound) {
|
if let player = createPlayer(for: sound) {
|
||||||
audioPlayers[sound] = player
|
audioPlayers[sound] = player
|
||||||
foundCustomSound = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any custom sounds are found, disable fallback for all sounds
|
|
||||||
useSystemSoundsFallback = !foundCustomSound
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createPlayer(for sound: GameSound) -> AVAudioPlayer? {
|
private func createPlayer(for sound: GameSound) -> AVAudioPlayer? {
|
||||||
guard let url = Bundle.main.url(
|
guard let url = soundBundle.url(
|
||||||
forResource: sound.rawValue,
|
forResource: sound.rawValue,
|
||||||
withExtension: sound.fileExtension
|
withExtension: sound.fileExtension
|
||||||
) else {
|
) else {
|
||||||
@ -157,7 +169,7 @@ final class SoundManager {
|
|||||||
player.volume = volume
|
player.volume = volume
|
||||||
return player
|
return player
|
||||||
} catch {
|
} catch {
|
||||||
print("Failed to create audio player for \(sound.rawValue): \(error)")
|
print("CasinoKit: Failed to create audio player for \(sound.rawValue): \(error)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,7 +178,7 @@ final class SoundManager {
|
|||||||
|
|
||||||
/// Plays a sound effect.
|
/// Plays a sound effect.
|
||||||
/// - Parameter sound: The sound to play.
|
/// - Parameter sound: The sound to play.
|
||||||
func play(_ sound: GameSound) {
|
public func play(_ sound: GameSound) {
|
||||||
guard soundEnabled else { return }
|
guard soundEnabled else { return }
|
||||||
|
|
||||||
// Try custom sound first
|
// Try custom sound first
|
||||||
@ -190,7 +202,7 @@ final class SoundManager {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - sound: The sound to play.
|
/// - sound: The sound to play.
|
||||||
/// - delay: Delay in seconds before playing.
|
/// - delay: Delay in seconds before playing.
|
||||||
func play(_ sound: GameSound, delay: TimeInterval) {
|
public func play(_ sound: GameSound, delay: TimeInterval) {
|
||||||
Task {
|
Task {
|
||||||
try? await Task.sleep(for: .seconds(delay))
|
try? await Task.sleep(for: .seconds(delay))
|
||||||
play(sound)
|
play(sound)
|
||||||
@ -201,7 +213,7 @@ final class SoundManager {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - sounds: Array of sounds to play.
|
/// - sounds: Array of sounds to play.
|
||||||
/// - interval: Time between each sound.
|
/// - interval: Time between each sound.
|
||||||
func playSequence(_ sounds: [GameSound], interval: TimeInterval = 0.1) {
|
public func playSequence(_ sounds: [GameSound], interval: TimeInterval = 0.1) {
|
||||||
for (index, sound) in sounds.enumerated() {
|
for (index, sound) in sounds.enumerated() {
|
||||||
play(sound, delay: TimeInterval(index) * interval)
|
play(sound, delay: TimeInterval(index) * interval)
|
||||||
}
|
}
|
||||||
@ -218,33 +230,51 @@ final class SoundManager {
|
|||||||
// MARK: - Haptics
|
// MARK: - Haptics
|
||||||
|
|
||||||
/// Plays a light haptic feedback.
|
/// Plays a light haptic feedback.
|
||||||
func hapticLight() {
|
public func hapticLight() {
|
||||||
|
guard hapticsEnabled else { return }
|
||||||
let impact = UIImpactFeedbackGenerator(style: .light)
|
let impact = UIImpactFeedbackGenerator(style: .light)
|
||||||
impact.impactOccurred()
|
impact.impactOccurred()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Plays a medium haptic feedback.
|
/// Plays a medium haptic feedback.
|
||||||
func hapticMedium() {
|
public func hapticMedium() {
|
||||||
|
guard hapticsEnabled else { return }
|
||||||
let impact = UIImpactFeedbackGenerator(style: .medium)
|
let impact = UIImpactFeedbackGenerator(style: .medium)
|
||||||
impact.impactOccurred()
|
impact.impactOccurred()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Plays a heavy haptic feedback.
|
||||||
|
public func hapticHeavy() {
|
||||||
|
guard hapticsEnabled else { return }
|
||||||
|
let impact = UIImpactFeedbackGenerator(style: .heavy)
|
||||||
|
impact.impactOccurred()
|
||||||
|
}
|
||||||
|
|
||||||
/// Plays a success haptic notification.
|
/// Plays a success haptic notification.
|
||||||
func hapticSuccess() {
|
public func hapticSuccess() {
|
||||||
|
guard hapticsEnabled else { return }
|
||||||
let notification = UINotificationFeedbackGenerator()
|
let notification = UINotificationFeedbackGenerator()
|
||||||
notification.notificationOccurred(.success)
|
notification.notificationOccurred(.success)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Plays an error haptic notification.
|
/// Plays an error haptic notification.
|
||||||
func hapticError() {
|
public func hapticError() {
|
||||||
|
guard hapticsEnabled else { return }
|
||||||
let notification = UINotificationFeedbackGenerator()
|
let notification = UINotificationFeedbackGenerator()
|
||||||
notification.notificationOccurred(.error)
|
notification.notificationOccurred(.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Plays a warning haptic notification.
|
||||||
|
public func hapticWarning() {
|
||||||
|
guard hapticsEnabled else { return }
|
||||||
|
let notification = UINotificationFeedbackGenerator()
|
||||||
|
notification.notificationOccurred(.warning)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Convenience Extensions
|
// MARK: - Convenience Extensions
|
||||||
|
|
||||||
extension SoundManager {
|
public extension SoundManager {
|
||||||
|
|
||||||
/// Plays chip placement sound with haptic.
|
/// Plays chip placement sound with haptic.
|
||||||
func playChipPlace() {
|
func playChipPlace() {
|
||||||
@ -257,7 +287,7 @@ extension SoundManager {
|
|||||||
play(.cardDeal)
|
play(.cardDeal)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Plays card flip sound.
|
/// Plays card flip sound with haptic.
|
||||||
func playCardFlip() {
|
func playCardFlip() {
|
||||||
play(.cardFlip)
|
play(.cardFlip)
|
||||||
hapticLight()
|
hapticLight()
|
||||||
@ -275,7 +305,7 @@ extension SoundManager {
|
|||||||
hapticError()
|
hapticError()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Plays push/tie sound.
|
/// Plays push/tie sound with haptic.
|
||||||
func playPush() {
|
func playPush() {
|
||||||
play(.push)
|
play(.push)
|
||||||
hapticMedium()
|
hapticMedium()
|
||||||
@ -286,4 +316,17 @@ extension SoundManager {
|
|||||||
play(.gameOver)
|
play(.gameOver)
|
||||||
hapticError()
|
hapticError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Plays new round sound with haptic.
|
||||||
|
func playNewRound() {
|
||||||
|
play(.newRound)
|
||||||
|
hapticMedium()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plays clear bets sound with haptic.
|
||||||
|
func playClearBets() {
|
||||||
|
play(.clearBets)
|
||||||
|
hapticMedium()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,3 +33,7 @@
|
|||||||
// - CasinoDesign (constants)
|
// - CasinoDesign (constants)
|
||||||
// - Color.Sheet (sheet colors)
|
// - Color.Sheet (sheet colors)
|
||||||
|
|
||||||
|
// MARK: - Audio
|
||||||
|
// - SoundManager
|
||||||
|
// - GameSound
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Sound Effects for Baccarat
|
# Sound Effects for CasinoKit
|
||||||
|
|
||||||
## Required Sound Files
|
## Required Sound Files
|
||||||
|
|
||||||
@ -69,11 +69,12 @@ Add the following `.mp3` files to this folder:
|
|||||||
- Volume is controllable from Settings
|
- Volume is controllable from Settings
|
||||||
- Sounds respect the user's sound/haptic preferences
|
- Sounds respect the user's sound/haptic preferences
|
||||||
|
|
||||||
## Adding to Xcode Project
|
## Adding Sound Files
|
||||||
|
|
||||||
1. Add `.mp3` files to this `Sounds` folder
|
1. Add `.mp3` files to this `Sounds` folder in CasinoKit
|
||||||
2. In Xcode, ensure files are included in the target
|
2. The Package.swift already includes Resources, so files are automatically bundled
|
||||||
3. Build and run - sounds should work automatically
|
3. Build and run - sounds should work automatically
|
||||||
|
4. If sounds are in your app bundle instead, set `SoundManager.shared.soundBundle = .main`
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user