Repository: ZHAW-Team-Toxic/PM4-Team-Toxic
Pull request #125: Feature/115 sound system
Author: login: Fezzsack
Created: 2025-04-07T15:20:55Z | Merged: 2025-04-10T08:02:11Z

# Fix
## Checklist (for code changes)

- [x] added docs
- [x] added tests

## Short description

Feature: SoundSystem

- Hintergrundmusik beim Spielstart
- Klick-Sound bei Mausaktion
- Zwei Unit-Tests: Registrierung & playClick()

Library for music: https://pixabay.com/music/search/loop/ -> Name: [Patriotic History - Orchestral Loop](https://pixabay.com/music/main-title-patriotic-history-orchestral-loop-313127/)

Library for sound-effect: https://pixabay.com/sound-effects/search/click/ -> Name: [Mouse click](https://pixabay.com/sound-effects/mouse-click-290204/)

Closes #115

Linked issue #115: create sound system
**Short Description**
Create a system to enable sound effects and background music.

**Acceptance**
- system handles soudn events and plays sounds and handles when mulitple sounds play at the same time
- another music stream system handles playing background musix

**Implementation**
- https://libgdx.com/wiki/audio/streaming-music
- https://libgdx.com/wiki/audio/sound-effects
- https://libgdx.com/wiki/audio/audio

Diff:
diff --git a/frontier/assets/audio/background.mp3 b/frontier/assets/audio/background.mp3
new file mode 100644
index 00000000..af6109c7
Binary files /dev/null and b/frontier/assets/audio/background.mp3 differ
diff --git a/frontier/assets/audio/click.mp3 b/frontier/assets/audio/click.mp3
new file mode 100644
index 00000000..2706b1d2
Binary files /dev/null and b/frontier/assets/audio/click.mp3 differ
diff --git a/frontier/core/src/main/java/com/zhaw/frontier/audio/SoundSystem.java b/frontier/core/src/main/java/com/zhaw/frontier/audio/SoundSystem.java
new file mode 100644
index 00000000..733a3e2d
--- /dev/null
+++ b/frontier/core/src/main/java/com/zhaw/frontier/audio/SoundSystem.java
@@ -0,0 +1,51 @@
+package com.zhaw.frontier.audio;
+
+import com.badlogic.ashley.core.EntitySystem;
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.audio.Music;
+import com.badlogic.gdx.audio.Sound;
+
+/**
+ * Handles game sound effects and background music using libGDX audio system.
+ *
+ * - Loads and plays background music on loop
+ * - Provides method to trigger click sounds
+ */
+
+public class SoundSystem extends EntitySystem {
+
+    Sound clickSound;
+    private Music backgroundMusic;
+
+    /**
+     * Called when the system is added to the Ashley Engine.
+     * Initializes audio assets and starts background music.
+     */
+    @Override
+    public void addedToEngine(com.badlogic.ashley.core.Engine engine) {
+        // Lade Sounds
+        clickSound = Gdx.audio.newSound(Gdx.files.internal("audio/click.mp3"));
+        backgroundMusic = Gdx.audio.newMusic(Gdx.files.internal("audio/background.mp3"));
+
+        backgroundMusic.setLooping(true);
+        backgroundMusic.setVolume(0.3f);
+        backgroundMusic.play();
+    }
+
+    /**
+     * Plays a UI click sound effect.
+     */
+    public void playClick() {
+        clickSound.play(1.0f);
+    }
+
+    /**
+     * Called when the system is removed from the Ashley Engine.
+     * Disposes all loaded audio resources.
+     */
+    @Override
+    public void removedFromEngine(com.badlogic.ashley.core.Engine engine) {
+        clickSound.dispose();
+        backgroundMusic.dispose();
+    }
+}
diff --git a/frontier/core/src/main/java/com/zhaw/frontier/screens/GameScreen.java b/frontier/core/src/main/java/com/zhaw/frontier/screens/GameScreen.java
index c1419799..f971f23c 100644
--- a/frontier/core/src/main/java/com/zhaw/frontier/screens/GameScreen.java
+++ b/frontier/core/src/main/java/com/zhaw/frontier/screens/GameScreen.java
@@ -10,6 +10,7 @@
 import com.badlogic.gdx.utils.viewport.ExtendViewport;
 import com.badlogic.gdx.utils.viewport.ScreenViewport;
 import com.zhaw.frontier.FrontierGame;
+import com.zhaw.frontier.audio.SoundSystem;
 import com.zhaw.frontier.components.InventoryComponent;
 import com.zhaw.frontier.components.map.BottomLayerComponent;
 import com.zhaw.frontier.components.map.DecorationLayerComponent;
@@ -113,6 +114,8 @@ public void show() {
             sampleLayer.getTileHeight()
         );
 
+        engine.addSystem(new SoundSystem());
+
         Gdx.app.debug("[DEBUG] - GameScreen", "Initializing Building Manager System.");
         //set-up BuildingManager
         engine.addSystem(new BuildingManagerSystem(sampleLayer, gameWorldView, engine));
@@ -186,6 +189,13 @@ void handleInput() {
         if (Gdx.input.isKeyJustPressed(Input.Keys.ESCAPE)) {
             frontierGame.switchScreen(new PauseScreen(frontierGame, this));
         }
+
+        if (Gdx.input.isButtonJustPressed(Input.Buttons.LEFT)) {
+            SoundSystem soundSystem = engine.getSystem(SoundSystem.class);
+            if (soundSystem != null) {
+                soundSystem.playClick();
+            }
+        }
     }
 
     @Override
diff --git a/frontier/core/src/test/java/com/zhaw/frontier/audio/SoundSystemTest.java b/frontier/core/src/test/java/com/zhaw/frontier/audio/SoundSystemTest.java
new file mode 100644
index 00000000..cb4db20c
--- /dev/null
+++ b/frontier/core/src/test/java/com/zhaw/frontier/audio/SoundSystemTest.java
@@ -0,0 +1,115 @@
+package com.zhaw.frontier.audio;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import com.badlogic.ashley.core.Engine;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for the SoundSystem class.
+ */
+public class SoundSystemTest {
+
+    @Test
+    public void testPlayClick() {
+        Engine engine = new Engine();
+        SoundSystem soundSystem = new SoundSystem();
+
+        engine.addSystem(soundSystem);
+
+        assertNotNull(engine.getSystem(SoundSystem.class));
+    }
+
+    @Test
+    public void playClick_callsPlayOnSound() {
+        TestableSoundSystem soundSystem = new TestableSoundSystem();
+        MockSound mockSound = new MockSound();
+
+        soundSystem.setClickSound(mockSound);
+        soundSystem.playClick();
+
+        assertTrue(mockSound.played, "Expected click sound to be played");
+    }
+}
+
+// → MockSound-Klasse, um zu prüfen, ob `play()` aufgerufen wird
+class MockSound implements com.badlogic.gdx.audio.Sound {
+
+    public boolean played = false; // → Wird true, sobald eine der play()-Methoden aufgerufen wird
+
+    @Override
+    public long play() {
+        played = true;
+        return 1;
+    } // → überschreibt play() – merkt sich Aufruf
+
+    @Override
+    public long play(float volume) {
+        played = true;
+        return 1;
+    } // → wird ebenfalls für andere Fälle aufgerufen
+
+    @Override
+    public long play(float volume, float pitch, float pan) {
+        played = true;
+        return 1;
+    }
+
+    // → Ab hier: Nicht nötig für deinen Test, aber Pflichtimplementierungen aus dem Interface
+
+    @Override
+    public long loop() {
+        return 0;
+    } // → Muss existieren, wird aber nicht genutzt
+
+    @Override
+    public long loop(float volume) {
+        return 0;
+    }
+
+    @Override
+    public long loop(float volume, float pitch, float pan) {
+        return 0;
+    }
+
+    @Override
+    public void stop() {}
+
+    @Override
+    public void pause() {}
+
+    @Override
+    public void resume() {}
+
+    @Override
+    public void dispose() {}
+
+    @Override
+    public void stop(long soundId) {}
+
+    @Override
+    public void pause(long soundId) {}
+
+    @Override
+    public void resume(long soundId) {}
+
+    @Override
+    public void setLooping(long soundId, boolean looping) {}
+
+    @Override
+    public void setPitch(long soundId, float pitch) {}
+
+    @Override
+    public void setVolume(long soundId, float volume) {}
+
+    @Override
+    public void setPan(long soundId, float pan, float volume) {}
+}
+
+class TestableSoundSystem extends SoundSystem {
+
+    public void setClickSound(com.badlogic.gdx.audio.Sound sound) {
+        this.clickSound = sound;
+    }
+}


Reviews:
user: login: BetaTH13
body: ~The baseui somehow broke with this change. I think the handle input completly absorbs the click event. I think by adding an inputadapter which handles this click to the Multiplexer would solve the issue.~

NVM ignore this comment mixed up some functionalities here. Could you maybe add a sound effect on the right click aswell? currently we are using that for placing structures
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T18:06:56Z
user: login: BetaTH13
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T18:26:32Z
user: login: BetaTH13
body: Lgtm
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-09T09:00:50Z

Review comments:
diff_hunk: @@ -10,6 +10,8 @@
 import com.badlogic.gdx.utils.viewport.ExtendViewport;
 import com.badlogic.gdx.utils.viewport.ScreenViewport;
 import com.zhaw.frontier.FrontierGame;
+//TODO
path: frontier/core/src/main/java/com/zhaw/frontier/screens/GameScreen.java
user: login: BetaTH13
body: remove TODO comment
created_at: 2025-04-07T18:25:13Z
author_association: COLLABORATOR
diff_hunk: @@ -113,6 +115,9 @@ public void show() {
             sampleLayer.getTileHeight()
         );
 
+        //TODO Fezzsack
path: frontier/core/src/main/java/com/zhaw/frontier/screens/GameScreen.java
user: login: BetaTH13
body: remove TODO comment
created_at: 2025-04-07T18:25:29Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,52 @@
+// Pfad: core/src/com/zhaw/frontier/audio/SoundSystem.java
path: frontier/core/src/main/java/com/zhaw/frontier/audio/SoundSystem.java
user: login: BetaTH13
body: Remove this comment
created_at: 2025-04-07T18:25:46Z
author_association: COLLABORATOR
diff_hunk: @@ -186,6 +191,14 @@ void handleInput() {
         if (Gdx.input.isKeyJustPressed(Input.Keys.ESCAPE)) {
             frontierGame.switchScreen(new PauseScreen(frontierGame, this));
         }
+
+        //TODO Fezzsack
path: frontier/core/src/main/java/com/zhaw/frontier/screens/GameScreen.java
user: login: BetaTH13
body: remove TODO comment
created_at: 2025-04-07T18:26:00Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-07T15:22:33Z
body: ## Test Results
25 tests  +2   25 ✅ +2   1s ⏱️ ±0s
12 suites +1    0 💤 ±0 
12 files   +1    0 ❌ ±0 

Results for commit 89dce632. ± Comparison against base commit 5a94ec3e.

[test-results]:data:application/gzip;base64,H4sIAB029mcC/12MSQ7CMBAEvxL5zMFbLMxnkBlPpBFJjLycEH/HDpCFW1e1VE820IiJXTohTx1LhfJGvkSXKcyNK9Ynt0/2P7imAvBn7vSohq9icDQeBMYY4tfEMq/Ftg/Bj9h6C+9yC+9rEKaJcgV2th7QKKl6Cc7oG4JX1SE3XlvhAKw2KCRnrzcd6a2WAQEAAA==

:recycle: This comment has been updated with latest results.
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-04-09T08:55:55Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=ZHAW-Team-Toxic_PM4-Team-Toxic&pullRequest=125) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [52.4% Coverage on New Code](https://sonarcloud.io/component_measures?id=ZHAW-Team-Toxic_PM4-Team-Toxic&pullRequest=125&metric=new_coverage&view=list) (required ≥ 80%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=ZHAW-Team-Toxic_PM4-Team-Toxic&pullRequest=125)


author_association: NONE