Add acoustics #2

Merged
fkoppe merged 30 commits from dev/client_koppe into dev_client 2024-11-16 18:21:21 +01:00
10 changed files with 177 additions and 154 deletions
Showing only changes of commit b509ab772d - Show all commits

View File

@@ -1,8 +1,6 @@
package pp.mdga.client.Acoustic; package pp.mdga.client.Acoustic;
import com.jme3.audio.AudioNode;
import com.jme3.system.NanoTimer; import com.jme3.system.NanoTimer;
import pp.mdga.client.Board.Asset;
import pp.mdga.client.MdgaApp; import pp.mdga.client.MdgaApp;
import pp.mdga.client.MdgaState; import pp.mdga.client.MdgaState;
@@ -13,17 +11,12 @@ public class AcousticHandler {
private MdgaState state = MdgaState.NONE; private MdgaState state = MdgaState.NONE;
boolean playGame = false; private boolean playGame = false;
private ArrayList<MusicAsset> gameTracks = new ArrayList<>(); private ArrayList<MusicAsset> gameTracks = new ArrayList<>();
private Random random = new Random();
private NanoTimer trackTimer = new NanoTimer(); private NanoTimer trackTimer = new NanoTimer();
private boolean fading = false; private boolean fading = false;
private boolean crossFading = false;
private GameMusic scheduled = null;
private GameMusic playing = null;
private NanoTimer fadeTimer = new NanoTimer(); private NanoTimer fadeTimer = new NanoTimer();
private float fadeVolume = 1.0f;
private final float FADE_DURATION = 3.0f; private final float FADE_DURATION = 3.0f;
private final float CROSSFADE_DURATION = 1.5f; private final float CROSSFADE_DURATION = 1.5f;
@@ -31,24 +24,15 @@ public class AcousticHandler {
private float musicVolume = 1.0f; private float musicVolume = 1.0f;
private float soundVolume = 1.0f; private float soundVolume = 1.0f;
private GameMusic scheduled = null;
private GameMusic playing = null;
private ArrayList<GameSound> sounds = new ArrayList<>(); private ArrayList<GameSound> sounds = new ArrayList<>();
public AcousticHandler(MdgaApp app) { public AcousticHandler(MdgaApp app) {
this.app = app; this.app = app;
} }
public float getMainVolume() {
return mainVolume;
}
public float getMusicVolume() {
return musicVolume;
}
public float getSoundVolume() {
return soundVolume;
}
public void update() { public void update() {
updateVolumeAndTrack(); updateVolumeAndTrack();
@@ -59,29 +43,31 @@ public void update() {
Iterator<GameSound> iterator = sounds.iterator(); Iterator<GameSound> iterator = sounds.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
GameSound s = iterator.next(); GameSound s = iterator.next();
s.update(getSoundVolumeTotal());
if (!s.isPlaying()) { if (!s.isPlaying()) {
iterator.remove(); iterator.remove();
} }
} }
} }
public void playSound(Sound sound) { public void playSound(MdgaSound sound) {
ArrayList<SoundAsset> assets = new ArrayList<SoundAsset>(); ArrayList<SoundAssetDelayVolume> assets = new ArrayList<SoundAssetDelayVolume>();
switch (sound) { switch (sound) {
case LOST: case LOST:
assets.add(SoundAsset.LOST); assets.add(new SoundAssetDelayVolume(SoundAsset.LOST, 1.0f, 0.0f));
break; break;
case VICTORY: case VICTORY:
assets.add(SoundAsset.VICTORY); assets.add(new SoundAssetDelayVolume(SoundAsset.VICTORY, 1.0f, 2.0f));
break; break;
default: default:
break; break;
} }
for (SoundAsset a : assets) { for (SoundAssetDelayVolume sawd : assets) {
GameSound gameSound = new GameSound(app, a, getSoundVolume() * getMainVolume()); GameSound gameSound = new GameSound(app, sawd.asset(), getSoundVolumeTotal(), sawd.subVolume(), sawd.delay());
gameSound.play();
sounds.add(gameSound); sounds.add(gameSound);
} }
} }
@@ -105,6 +91,7 @@ public void playState(MdgaState state) {
case GAME: case GAME:
addGameTracks(); addGameTracks();
playGame = true; playGame = true;
assert(gameTracks.size() > 0) : "no more game music available";
asset = gameTracks.remove(0); asset = gameTracks.remove(0);
break; break;
case CEREMONY: case CEREMONY:
@@ -115,7 +102,7 @@ public void playState(MdgaState state) {
assert(null != asset) : "music sceduling went wrong"; assert(null != asset) : "music sceduling went wrong";
scheduled = new GameMusic(app, asset, getMusicVolume() * getMainVolume(), asset.getLoop()); scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop());
} }
private float lerp(float start, float end, float t) { private float lerp(float start, float end, float t) {
@@ -126,7 +113,6 @@ private void updateVolumeAndTrack() {
if (playing == null && scheduled != null && !fading) { if (playing == null && scheduled != null && !fading) {
playing = scheduled; playing = scheduled;
scheduled = null; scheduled = null;
fadeVolume = 1.0f;
playing.play(); playing.play();
return; return;
} }
@@ -143,7 +129,7 @@ private void updateVolumeAndTrack() {
float t = Math.min(time / FADE_DURATION, 1.0f); float t = Math.min(time / FADE_DURATION, 1.0f);
float oldVolume = lerp(1.0f, 0.0f, t); float oldVolume = lerp(1.0f, 0.0f, t);
if (playing != null) { if (playing != null) {
playing.update(getMusicVolume() * getMainVolume() * oldVolume); playing.update(getMusicVolumeTotal()* oldVolume);
} }
} }
@@ -154,7 +140,7 @@ private void updateVolumeAndTrack() {
if (!scheduled.isPlaying()) { if (!scheduled.isPlaying()) {
scheduled.play(); scheduled.play();
} }
scheduled.update(getMusicVolume() * getMainVolume() * newVolume); scheduled.update(getMusicVolumeTotal() * newVolume);
} }
if (time > FADE_DURATION + CROSSFADE_DURATION) { if (time > FADE_DURATION + CROSSFADE_DURATION) {
@@ -164,15 +150,16 @@ private void updateVolumeAndTrack() {
playing = scheduled; playing = scheduled;
scheduled = null; scheduled = null;
fadeVolume = 1.0f;
fading = false; fading = false;
} }
} else if (playing != null) { } else if (playing != null) {
playing.update(getMusicVolume() * getMainVolume()); playing.update(getMusicVolumeTotal());
} }
} }
private void addGameTracks() { private void addGameTracks() {
Random random = new Random();
for (int i = 1; i <= 6; i++) { for (int i = 1; i <= 6; i++) {
gameTracks.add(MusicAsset.valueOf("GAME_" + i)); gameTracks.add(MusicAsset.valueOf("GAME_" + i));
} }
@@ -189,7 +176,30 @@ private void updateGameTracks() {
MusicAsset nextTrack = gameTracks.remove(0); MusicAsset nextTrack = gameTracks.remove(0);
scheduled = new GameMusic(app, nextTrack, getMusicVolume() * getMainVolume(), nextTrack.getLoop()); scheduled = new GameMusic(app, nextTrack, getMusicVolumeTotal(), nextTrack.getSubVolume(), nextTrack.getLoop());
} }
} }
public float getMainVolume() {
return mainVolume;
}
public float getMusicVolume() {
return musicVolume;
}
public float getSoundVolume() {
return soundVolume;
}
public float getMusicVolumeTotal() {
return getMusicVolume() * getMainVolume();
}
public float getSoundVolumeTotal() {
return getSoundVolume() * getMainVolume();
}
} }

View File

@@ -5,17 +5,20 @@
import com.jme3.audio.AudioSource; import com.jme3.audio.AudioSource;
import pp.mdga.client.MdgaApp; import pp.mdga.client.MdgaApp;
public class GameMusic { class GameMusic {
private final MdgaApp app;
private float volume; private float volume;
private AudioNode music; private final float subVolume;
GameMusic(MdgaApp app, MusicAsset asset, float volume, boolean loop) { private final AudioNode music;
this.app = app;
GameMusic(MdgaApp app, MusicAsset asset, float volume, float subVolume, boolean loop) {
this.volume = volume; this.volume = volume;
this.subVolume = subVolume;
loadMusic(asset.getPath()); music = new AudioNode(app.getAssetManager(), asset.getPath(), AudioData.DataType.Stream);
music.setPositional(false);
music.setDirectional(false);
music.setVolume(volume * subVolume);
music.setLooping(loop); music.setLooping(loop);
} }
@@ -36,15 +39,8 @@ void pause() {
music.stop(); music.stop();
} }
void reset() {
if(null == music) {
return;
}
music.setTimeOffset(0);
}
boolean isPlaying() { boolean isPlaying() {
return music.getStatus() == AudioSource.Status.Playing; return music.getStatus() == AudioSource.Status.Playing;
} }
@@ -68,14 +64,7 @@ boolean nearEnd(float thresholdSeconds) {
void update(float newVolume) { void update(float newVolume) {
if(volume != newVolume) { if(volume != newVolume) {
volume = newVolume; volume = newVolume;
music.setVolume(volume); music.setVolume(volume * subVolume);
} }
} }
private void loadMusic(String path) {
music = new AudioNode(app.getAssetManager(), path, AudioData.DataType.Stream);
music.setVolume(volume);
music.setPositional(false);
music.setDirectional(false);
}
} }

View File

@@ -3,37 +3,56 @@
import com.jme3.audio.AudioData; import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode; import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource; import com.jme3.audio.AudioSource;
import com.jme3.system.NanoTimer;
import pp.mdga.client.MdgaApp; import pp.mdga.client.MdgaApp;
public class GameSound { class GameSound {
private final MdgaApp app;
private float volume; private float volume;
private SoundAsset asset; final private float subVolume;
private AudioNode sound; private final AudioNode sound;
GameSound(MdgaApp app, SoundAsset asset, float volume) { private boolean playing = false;
this.app = app; private boolean finished = false;
private float delay = 0.0f;
private NanoTimer timer = null;
GameSound(MdgaApp app, SoundAsset asset, float volume, float subVolume, float delay) {
this.volume = volume; this.volume = volume;
this.subVolume = subVolume;
this.delay = delay;
loadMusic(asset.getPath()); sound = new AudioNode(app.getAssetManager(), asset.getPath(), AudioData.DataType.Buffer);
}
void play() {
sound.play();
}
boolean isPlaying() {
return sound != null && sound.getStatus() == AudioSource.Status.Playing;
}
private void loadMusic(String path) {
sound = new AudioNode(app.getAssetManager(), path, AudioData.DataType.Buffer);
sound.setVolume(volume);
sound.setPositional(false); sound.setPositional(false);
sound.setDirectional(false); sound.setDirectional(false);
sound.setLooping(false); sound.setLooping(false);
sound.setVolume(volume * subVolume);
timer = new NanoTimer();
}
boolean isPlaying() {
return !finished;
}
void update(float newVolume) {
if(!playing && timer.getTimeInSeconds() > delay) {
sound.play();
playing = true;
}
if(!playing) {
return;
}
if(volume != newVolume) {
volume = newVolume;
sound.setVolume(volume * subVolume);
}
if(sound != null && sound.getStatus() == AudioSource.Status.Playing) {
finished = true;
}
} }
} }

View File

@@ -1,6 +1,6 @@
package pp.mdga.client.Acoustic; package pp.mdga.client.Acoustic;
public enum Sound { public enum MdgaSound {
DICE_ROLL, DICE_ROLL,
TURN_START, TURN_START,
TURN_END, TURN_END,

View File

@@ -1,32 +1,41 @@
package pp.mdga.client.Acoustic; package pp.mdga.client.Acoustic;
enum MusicAsset { enum MusicAsset {
MAIN_MENU("Spaceship.wav"), MAIN_MENU("Spaceship.wav", 1.0f),
LOBBY("DeadPlanet.wav"), LOBBY("DeadPlanet.wav", 1.0f),
CEREMONY("80s,Disco,Life.wav"), CEREMONY("80s,Disco,Life.wav", 1.0f),
GAME_1("NeonRoadTrip.wav", false), GAME_1("NeonRoadTrip.wav", false, 1.0f),
GAME_2("NoPressureTrance.wav", false), GAME_2("NoPressureTrance.wav", false, 1.0f),
GAME_3("TheSynthRave.wav", false), GAME_3("TheSynthRave.wav", false, 1.0f),
GAME_4("LaserParty.wav", false), GAME_4("LaserParty.wav", false, 1.0f),
GAME_5("RetroNoir.wav", false), GAME_5("RetroNoir.wav", false, 1.0f),
GAME_6("SpaceInvaders.wav", false); GAME_6("SpaceInvaders.wav", false, 1.0f);
private final String path; private final String path;
private final boolean loop; private final boolean loop;
private final float subVolume;
MusicAsset(String name) { MusicAsset(String name, float subVolume) {
this.path = "music/" + name; this.path = "music/" + name;
this.loop = false; this.loop = false;
this.subVolume = subVolume;
} }
MusicAsset(String name, boolean loop) { MusicAsset(String name, boolean loop, float subVolume) {
this.path = "music/" + name; this.path = "music/" + name;
this.loop = loop; this.loop = loop;
this.subVolume = subVolume;
} }
public String getPath() { public String getPath() {
return path; return path;
} }
public boolean getLoop() { return loop; } public boolean getLoop() {
return loop;
}
public float getSubVolume() {
return subVolume;
}
} }

View File

@@ -0,0 +1,4 @@
package pp.mdga.client.Acoustic;
record SoundAssetDelayVolume(SoundAsset asset, float subVolume, float delay) {
}

View File

@@ -1,4 +0,0 @@
package pp.mdga.client.Board;
public class BoardCamera {
}

View File

@@ -1,9 +1,13 @@
package pp.mdga.client.Board; package pp.mdga.client.Board;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import pp.mdga.client.MdgaApp; import pp.mdga.client.MdgaApp;
import java.util.ArrayList; import java.util.ArrayList;
@@ -14,7 +18,7 @@ public class BoardView {
private static final float GRID_ELEVATION = 0.0f; private static final float GRID_ELEVATION = 0.0f;
private static final String MAP_NAME = "circle_map.mdga"; private static final String MAP_NAME = "circle_map.mdga";
private final MdgaApp mdgaApp; private final MdgaApp app;
private PileControl drawPile = new PileControl(); private PileControl drawPile = new PileControl();
private PileControl discardPile = new PileControl(); private PileControl discardPile = new PileControl();
@@ -22,23 +26,45 @@ public class BoardView {
private ArrayList<NodeControl> infield = new ArrayList<NodeControl>(40); private ArrayList<NodeControl> infield = new ArrayList<NodeControl>(40);
private ArrayList<PieceControl> pieces; private ArrayList<PieceControl> pieces;
public BoardView(MdgaApp mdgaApp) { public BoardView(MdgaApp app) {
assert(mdgaApp != null) : "app is null"; assert(app != null) : "app is null";
this.mdgaApp = mdgaApp; this.app = app;
pieces = new ArrayList<PieceControl>(4 * 4); pieces = new ArrayList<PieceControl>(4 * 4);
initMap(); initMap();
initCamera();
}
private void initCamera() {
app.getFlyByCamera().setEnabled(true);
int zoom = 20;
app.getCamera().setLocation(new Vector3f(zoom,0,zoom));
app.getCamera().lookAt(new Vector3f(0,0,0), new Vector3f(0,0,1));
DirectionalLight sun = new DirectionalLight();
sun.setColor(ColorRGBA.White);
sun.setDirection(new Vector3f(-1,0,-1));
app.getRootNode().addLight(sun);
AmbientLight ambient = new AmbientLight();
ambient.setColor(new ColorRGBA(0.3f,0.3f,0.3f,1));
app.getRootNode().addLight(ambient);
final int SHADOWMAP_SIZE= 1024 * 8;
DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(app.getAssetManager(), SHADOWMAP_SIZE, 4);
dlsr.setLight(sun);
app.getViewPort().addProcessor(dlsr);
} }
private void initMap() { private void initMap() {
List<AssetOnMap> assetOnMaps = new MapLoader().loadMap(MAP_NAME); List<AssetOnMap> assetsOnMap = MapLoader.loadMap(MAP_NAME);
for (AssetOnMap assetOnMap : assetOnMaps){ for (AssetOnMap aom : assetsOnMap){
int x = assetOnMap.x(); int x = aom.x();
int y = assetOnMap.y(); int y = aom.y();
Spatial model = createModel(assetOnMap.asset()); Spatial model = createModel(aom.asset());
model.setLocalTranslation(gridToWorld(x,y)); model.setLocalTranslation(gridToWorld(x,y));
} }
} }
@@ -46,14 +72,14 @@ private void initMap() {
private Spatial createModel(Asset asset){ private Spatial createModel(Asset asset){
String modelName = asset.getModelPath(); String modelName = asset.getModelPath();
String texName = asset.getDiffPath(); String texName = asset.getDiffPath();
Spatial model = mdgaApp.getAssetManager().loadModel(modelName); Spatial model = app.getAssetManager().loadModel(modelName);
model.scale(asset.getSize()); model.scale(asset.getSize());
model.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(90)); model.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(90));
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
Material mat = new Material(mdgaApp.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md"); Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", mdgaApp.getAssetManager().loadTexture(texName)); mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(texName));
model.setMaterial(mat); model.setMaterial(mat);
mdgaApp.getRootNode().attachChild(model); app.getRootNode().attachChild(model);
return model; return model;
} }

View File

@@ -8,13 +8,10 @@
import java.util.List; import java.util.List;
public class MapLoader { public class MapLoader {
public MapLoader(){ static public List<AssetOnMap> loadMap(String mapName) {
}
public List<AssetOnMap> loadMap(String mapName) {
List<AssetOnMap> assetsOnMap = new ArrayList<>(); List<AssetOnMap> assetsOnMap = new ArrayList<>();
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(mapName); try (InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(mapName);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
while (true) { while (true) {
@@ -40,16 +37,14 @@ public List<AssetOnMap> loadMap(String mapName) {
Asset asset = getLoadedAsset(assetName); Asset asset = getLoadedAsset(assetName);
assetsOnMap.add(new AssetOnMap(asset, x, y)); assetsOnMap.add(new AssetOnMap(asset, x, y));
} }
} catch (IOException e) { } catch (Exception e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace(); e.printStackTrace();
} }
return assetsOnMap; return assetsOnMap;
} }
private Asset getLoadedAsset(String assetName) { static private Asset getLoadedAsset(String assetName) {
return switch(assetName){ return switch(assetName){
case "node" -> Asset.node_normal; case "node" -> Asset.node_normal;
case "node_start" -> Asset.node_start; case "node_start" -> Asset.node_start;
@@ -59,7 +54,14 @@ private Asset getLoadedAsset(String assetName) {
case "node_home_black" -> Asset.node_home_black; case "node_home_black" -> Asset.node_home_black;
case "node_home_green" -> Asset.node_home_green; case "node_home_green" -> Asset.node_home_green;
case "world" -> Asset.world; case "world" -> Asset.world;
default -> throw new IllegalStateException("Unexpected value: " + assetName); case "tent_big" -> Asset.bigTent;
case "tent_small" -> Asset.smallTent;
case "stack" -> Asset.cardStack;
case "jet" -> Asset.jet;
case "radar" -> Asset.radar;
case "ship" -> Asset.ship;
case "tank" -> Asset.tank;
default -> throw new IllegalStateException("Unexpected asset in .mdga file: " + assetName);
}; };
} }
} }

View File

@@ -3,25 +3,12 @@
import com.jme3.app.SimpleApplication; import com.jme3.app.SimpleApplication;
import com.jme3.system.NanoTimer; import com.jme3.system.NanoTimer;
import pp.mdga.client.Acoustic.AcousticHandler; import pp.mdga.client.Acoustic.AcousticHandler;
import pp.mdga.client.Acoustic.Sound; import pp.mdga.client.Acoustic.MdgaSound;
import pp.mdga.client.Animation.AnimationHandler; import pp.mdga.client.Animation.AnimationHandler;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import com.jme3.system.AppSettings; import com.jme3.system.AppSettings;
import pp.mdga.client.Board.Asset;
import pp.mdga.client.Board.AssetOnMap;
import pp.mdga.client.Board.BoardView; import pp.mdga.client.Board.BoardView;
import pp.mdga.client.Board.MapLoader;
import pp.mdga.client.Dialog.DialogView; import pp.mdga.client.Dialog.DialogView;
import java.util.List;
public class MdgaApp extends SimpleApplication { public class MdgaApp extends SimpleApplication {
private AnimationHandler animationHandler; private AnimationHandler animationHandler;
private AcousticHandler acousticHandler; private AcousticHandler acousticHandler;
@@ -54,27 +41,8 @@ public void simpleInitApp() {
//dialogView.mainMenu(); //dialogView.mainMenu();
//acousticHandler.playState(MdgaState.GAME); //acousticHandler.playState(MdgaState.GAME);
acousticHandler.playSound(Sound.LOST); acousticHandler.playSound(MdgaSound.LOST);
acousticHandler.playSound(Sound.VICTORY); acousticHandler.playSound(MdgaSound.VICTORY);
flyCam.setEnabled(true);
int zoom = 20;
cam.setLocation(new Vector3f(zoom,0,zoom));
cam.lookAt(new Vector3f(0,0,0), new Vector3f(0,0,1));
DirectionalLight sun = new DirectionalLight();
sun.setColor(ColorRGBA.White);
sun.setDirection(new Vector3f(-1,0,-1));
rootNode.addLight(sun);
AmbientLight ambient = new AmbientLight();
ambient.setColor(new ColorRGBA(0.3f,0.3f,0.3f,1));
rootNode.addLight(ambient);
final int SHADOWMAP_SIZE= 1024 * 8;
DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 4);
dlsr.setLight(sun);
viewPort.addProcessor(dlsr);
} }
@Override @Override