Tranquil App documents Part I.
内容目录

High-Level Structure of 'Harmony With Me' Game

Game Design Idea

The game is an interactive music experience where users create harmonious sounds by tapping a circle that appears at random positions on the screen. The game features background music, haptic feedback, and inactivity monitoring to enhance the user experience.

Key Components

  1. File Management

    • Uses FileSystemManager for audio resource handling
    • Checks for essential files like "testBGM.m4a"
  2. Audio System

    • Managed through AudioManager
    • Handles background music and harmony sounds
  3. User Interaction

    • Uses DragGesture for touch detection
    • Implements circle-based touch area detection
    • Includes haptic feedback via HapticManager
  4. State Management

    • Tracks visibility state (showHomeButton)
    • Monitors gameplay state
    • Handles cleanup on view disappear

Control Flow in A Nutshell

graph TD
    A[App Launch] --> B[Initialize Components]
    B --> C[Start BGM]
    B --> D[Setup Touch Detection]
    C --> E[Wait for User Input]
    E --> F{Touch Inside Circle?}
    F -->|Yes| G[Play Harmony Sound]
    G --> H[Trigger Haptics]
    F -->|No| E
    E --> I[Monitor Inactivity]
    I -->|Inactive| J[Show Home Button]
  1. Initialization

    • On view appearance, the game checks for necessary audio files, configures the audio session, prepares haptics, starts background music, and initializes the game state.
  2. User Interaction

    • Users interact with the game by tapping the circle when it appears. The game detects touches inside the circle, plays a random harmony sound, triggers haptic feedback, and resets the inactivity timer.
  3. Background Music and Circle Display

    • Background music plays in a loop, and the game monitors the playback position to display the interactive circle at predefined times. The circle appears at random positions and disappears after a set duration.
  4. Inactivity Monitoring

    • The game monitors user inactivity and displays a home button if the user is inactive for a certain period. The inactivity timer resets with each user interaction.
  5. Cleanup

    • On view disappearance, the game fades out the audio, stops timers, and cleans up resources.

Main View Structure

  1. Imports and Declarations

    • Import necessary frameworks: SwiftUI, AVFoundation, CoreHaptics
    • Define MusicDelegate class for handling background music looping
  2. Main View: HarmonyWithMe

    • State properties for managing game state, audio players, timers, and UI elements
    • Constants for game configuration (e.g., circle duration, sound groups)
  3. body View

    • NavigationStack with GeometryReader for responsive layout
    • ZStack for layering UI elements:
      • Background color
      • Instruction text
      • Interactive circle
      • Home button
  4. Lifecycle Handlers

    • .onAppear to initialize game components and start background music
    • .gesture to handle touch interactions
    • .onDisappear to clean up resources
  5. Helper Methods

    • startBackgroundMusic(): Initialize and start background music with looping
    • initializeGame(): Set up initial game state and start timers
    • playRandomSoundFromCurrentGroup(): Play a random sound from the current sound group
    • isPlayingHarmonySound(): Check if a harmony sound is currently playing
    • isTouchInsideCircle(at:): Determine if a touch is inside the interactive circle
    • showCircleAtRandomPosition(): Display the interactive circle at a random position
    • startMusicTimer(): Monitor background music playback position
    • checkMusicPosition(): Check if it's time to show the interactive circle
    • startInactivityTimer(): Start a timer to monitor user inactivity
    • stopInactivityTimer(): Stop the inactivity timer
    • resetInactivityTimer(): Reset the inactivity timer and hide the home button
    • checkForInactivity(): Check if the user has been inactive for a certain period
    • resetGame(): Reset the game state
    • fadeOutAndClean(): Fade out audio and clean up resources

Control Flows in Detail

Initialization (onAppear)

graph TD
    A[View Appears] --> B[Debug File System Check]
    B --> C[Verify BGM File]
    C --> D[Configure Audio]
    D --> E[Setup Haptics]
    E --> F[Start BGM]
    F --> G[Initialize Game]

BGM Playback Position Monitoring (in the context of game flow)

Control Flow of the Music Timer

graph TD
    A[Start Music Timer] --> B[Timer Tick (0.1s)]
    B --> C[Check Music Position]
    C --> D{Is Music About to Loop?}
    D -->|Yes| E[Reset Game]
    E --> F[Start Music Timer]
    D -->|No| G[Check Circle Trigger Times]
    G --> H{Is Current Time >= Next Trigger Time?}
    H -->|Yes| I[Show Circle at Random Position]
    I --> J[Increment Trigger Index]
    H -->|No| K[Continue Monitoring]
    J --> K
    F --> K

Playback monitoring Explanation within the Game Flow

The primary purpose of checkMusicPosition() is to determine when the background music reaches certain points in its playback timeline, known as "trigger times." At these trigger times, the function attempts to display a circle at a random position on the screen, which the user can interact with.

Call Chain, Timing and Complete Mechanism
// Initial setup in startBackgroundMusic()
musicDelegate = MusicDelegate {
    DispatchQueue.main.async {
        self.resetGame()  // This eventually leads to startMusicTimer()
    }
}

musicTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { 
    ...
        self.checkMusicPosition()  // Called every 0.1 seconds
    }
...

A. Timer-Based Monitoring

  • When the game starts or resets:

    • initializeGame() is called
    • This calls startMusicTimer()
    • The timer is set to fire every 0.1 seconds
  • The timer continuously:

    • Checks if the music is about to loop
    • Calls checkMusicPosition()

B. Checking Logic (function explanation)

private func checkMusicPosition() {
    guard let player = backgroundPlayer,
          nextTriggerIndex < circleTriggerTimes.count else { return }

    let currentTime = player.currentTime
    let nextTriggerTime = circleTriggerTimes[nextTriggerIndex]

    let tolerance: TimeInterval = 0.2
    if currentTime >= nextTriggerTime && currentTime <= nextTriggerTime + tolerance &&
        !isShowingCircle {
        print("⭕️ Attempting to show circle at trigger time: \(nextTriggerTime)")
        currentSoundGroupIndex = nextTriggerIndex
        showCircleAtRandomPosition()
        nextTriggerIndex += 1
    }
}

a. Guard Statement:

  • The function begins with a guard statement to ensure that the backgroundPlayer is not nil and that nextTriggerIndex is within the bounds of the circleTriggerTimes array. If either condition fails, the function exits early.

b. Current Time and Next Trigger Time:

  • currentTime is obtained from the backgroundPlayer, representing the current playback position of the music.
  • nextTriggerTime is retrieved from the circleTriggerTimes array using nextTriggerIndex. This represents the next point in time when an action should be triggered.

c. Tolerance:

  • A tolerance of 0.2 seconds is defined. This allows for a small window of time around the exact trigger time to account for any minor discrepancies in timing.

d. Trigger Condition:

  • The function checks if the currentTime is within the range defined by nextTriggerTime and nextTriggerTime + tolerance.
  • It also checks that isShowingCircle is false, ensuring that a circle is not already being displayed.

e. Action on Trigger:

  • If the conditions are met, the function prints a message indicating that it is attempting to show a circle at the trigger time.
  • currentSoundGroupIndex is updated to nextTriggerIndex, which is used to determine which sound group to play when the circle is interacted with.
  • showCircleAtRandomPosition() is called to display a circle at a random position on the screen.
  • nextTriggerIndex is incremented to prepare for the next trigger time.

C. Game State Management

Trigger Times:

   private let circleTriggerTimes: [TimeInterval] = [ ... ] //Predefined times when circles should appear

Sound Groups:

   private let soundGroups: [[String]] = [
       ["soundname1", "soundname2"],  // Group 1
       ...     // ... more groups. Each trigger point corresponds to a sound group
   ]
Complete Flow

i. Game Initialization:

  • Game starts → initializeGame()startMusicTimer()
  • Background music begins playing

ii. Continuous Monitoring:

  • Every 0.1 seconds:
    • Timer fires
    • Checks music position
    • Calls checkMusicPosition()

iii. Circle Trigger System:

  • When currentTime matches a trigger time (within tolerance):
    • Updates currentSoundGroupIndex
    • Shows circle at random position
    • Prepares for next trigger by incrementing nextTriggerIndex

iv. Music Loop Handling:

  • When music is about to end:
    • MusicDelegate's closure is called
    • Game resets through resetGame()
    • Timer restarts through startMusicTimer()
Error Handling and Guards
  • Guards against:
    • Missing background player
    • Invalid trigger indices
    • Multiple circles showing simultaneously
    • Out-of-bounds array access
State Management

The function manages several state variables:

  • nextTriggerIndex: Tracks next circle appearance
  • currentSoundGroupIndex: Controls available sounds
  • isShowingCircle: Prevents multiple circles
  • Various animation states for circle display

This mechanism creates a synchronized experience where:

  1. Music plays continuously
  2. Circles appear at specific musical moments
  3. User interactions trigger appropriate sounds
  4. The game loops seamlessly when music ends

The 0.1-second interval for checking provides a good balance between accuracy and performance, while the 0.2-second tolerance allows for slight timing variations without affecting the user experience.

Circle Appearance Tapping Handling

graph TD
    A[Check Music Position] --> B{Is Current Time >= Next Trigger Time?}
    B -->|Yes| C[Show Circle at Random Position]
    C --> D[Set Circle Active]
    D --> E[Start Circle Disappearance Timer]
    E --> F[Wait for Circle Duration]
    F --> G[Hide Circle]
    G --> H[Set Circle Inactive]
    B -->|No| I[Continue Monitoring]

    J[User Taps Circle] --> K{Is Circle Active?}
    K -->|Yes| L[Play Random Sound from Current Group]
    L --> M[Trigger Haptic Feedback]
    M --> N[Reset Inactivity Timer]
    K -->|No| O[No Action]

Explanation

Circle Appearance Handling
  1. Check Music Position

    • The current music playback position is checked against predefined trigger times.
  2. Is Current Time >= Next Trigger Time?

    • If the current playback time is greater than or equal to the next trigger time, the circle is shown at a random position.
  3. Show Circle at Random Position

    • The interactive circle is displayed at a random position on the screen.
  4. Set Circle Active

    • The circle is marked as active, allowing it to respond to user taps.
  5. Start Circle Disappearance Timer

    • A timer is started to hide the circle after a set duration.
  6. Wait for Circle Duration

    • The system waits for the circle's duration to elapse.
  7. Hide Circle

    • The circle is hidden after the duration elapses.
  8. Set Circle Inactive

    • The circle is marked as inactive, preventing it from responding to user taps.
  9. Continue Monitoring

    • If the current time is not yet at the next trigger time, the system continues monitoring the music position.
Circle Tapping Handling
  1. User Taps Circle

    • The user taps the circle on the screen.
  2. Is Circle Active?

    • The system checks if the circle is currently active.
  3. Play Random Sound from Current Group

    • If the circle is active, a random sound from the current sound group is played.
  4. Trigger Haptic Feedback

    • Haptic feedback is triggered to provide tactile feedback to the user.
  5. Reset Inactivity Timer

    • The inactivity timer is reset to prevent the home button from appearing.
  6. No Action

    • If the circle is not active, no action is taken.

User Activity Monitoring

graph TD
    A[Start Inactivity Timer] --> B[Timer Tick (1s)]
    B --> C[Check For Inactivity]
    C --> D{Time Since Last Interaction >= 10s?}
    D -->|Yes| E[Show Home Button]
    E --> F[Update UI State]
    D -->|No| G[Continue Monitoring]
    F --> G

    H[User Interaction] --> I[Reset Inactivity Timer]
    I --> J[Update Last Interaction Time]
    J --> K[Hide Home Button]
    K --> G

Explanation

  1. Start Inactivity Timer

    • A timer is started to check for user inactivity every 1 second.
  2. Timer Tick (1s)

    • On each tick, the time since the last user interaction is checked.
  3. Check For Inactivity

    • The time elapsed since the last interaction is calculated.
  4. Time Since Last Interaction >= 10s?

    • If the elapsed time is greater than or equal to 10 seconds, the home button is shown.
  5. Show Home Button

    • The home button is made visible with an animation.
  6. Update UI State

    • The UI state is updated to reflect the visibility of the home button.
  7. Continue Monitoring

    • The timer continues to monitor user inactivity.
  8. User Interaction

    • When the user interacts with the game (e.g., touches the screen), the inactivity timer is reset.
  9. Reset Inactivity Timer

    • The last interaction time is updated, and the home button is hidden.
  10. Update Last Interaction Time

    • The last interaction time is set to the current time.
  11. Hide Home Button

    • The home button is hidden with an animation.

The Self-defiend Class MusicDelegate

Overview

The MusicDelegate class is a custom delegate for handling background music playback events in the HarmonyWithMe game. It conforms to the AVAudioPlayerDelegate protocol and is primarily responsible for detecting when the background music has finished playing and triggering appropriate actions to reset the game state and loop the music.

How It Works

  1. Initialization

    • The MusicDelegate is initialized with a closure (onMusicLoop) that defines the actions to be taken when the background music finishes playing.
    • This closure is stored in the onMusicLoop property.
  2. Conforming to AVAudioPlayerDelegate

    • The MusicDelegate class conforms to the AVAudioPlayerDelegate protocol, which requires the implementation of the audioPlayerDidFinishPlaying(_:successfully:) method.
  3. Handling Music Playback Completion

    • The audioPlayerDidFinishPlaying(_:successfully:) method is called automatically by the AVAudioPlayer instance when the music finishes playing.
    • The method checks if the playback finished successfully using the flag parameter.
    • If the playback finished successfully, the method prints a debug message and calls the onMusicLoop closure to reset the game and restart the music.

Usage in the main view

The MusicDelegate is used in the HarmonyWithMe view to handle background music looping and game state resetting.

@State private var musicDelegate: MusicDelegate?

private func startBackgroundMusic() {
    backgroundPlayer = AudioManager.createAudioPlayer(filename: "testBGM", fileExtension: "m4a")

    guard let player = backgroundPlayer else {
        print("❌ Failed to create background player")
        return
    }
    AudioManager.fadeIn(player)

    musicDelegate = MusicDelegate {
        DispatchQueue.main.async {
            self.resetGame()
            self.startMusicTimer()
        }
    }
    player.delegate = musicDelegate
    player.numberOfLoops = -1
}
  1. Creating the Audio Player

    • The startBackgroundMusic() method creates an AVAudioPlayer instance for the background music.
  2. Setting the Delegate

    • A MusicDelegate instance is created with a closure that resets the game and restarts the music timer.
    • The musicDelegate property is assigned this instance.
    • The AVAudioPlayer instance's delegate property is set to the musicDelegate.
  3. Handling Music Looping

    • When the background music finishes playing, the audioPlayerDidFinishPlaying(_:successfully:) method of the MusicDelegate is called.
    • The onMusicLoop closure is executed, which resets the game state and restarts the music timer.

control flow

graph TD
    A[Create MusicDelegate Instance] --> B[Initialize MusicDelegate with Closure]
    B --> C[Assign MusicDelegate to AVAudioPlayer]
    C --> D[Start Background Music] 
    D --> E[Music Playback Completes]
    E --> F{Was Playback Successful?}
    F -->|Yes| G[Execute onMusicLoop Closure]
    G --> H[Reset Game]
    G --> I[Start Music Timer]
    F -->|No| J[Do Nothing]

The MusicDelegate class is a crucial component in the HarmonyWithMe game, ensuring that the background music loops seamlessly and the game state is reset appropriately. By conforming to the AVAudioPlayerDelegate protocol and using a closure to define the actions on music completion, it provides a flexible and efficient way to manage background music playback and game state transitions.

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇