Browse Source

asynchronous sound playing to prevent lag spikes

Kajetan Johannes Hammerle 7 years ago
parent
commit
48ecda69a6

+ 1 - 1
levels/00.map

@@ -34,7 +34,7 @@
 -1, -1, 15, -1, -1, 15, -1, -1, -1, -1, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
 -1, -1, -1, -1, -1, -1, -1, -1, -1, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
 -1, -1, -1, -1, -1, -1, -1, -1, 15, 64, 64, 64, 64, 64, 64, 64, -1, -1, -1, -1, -1, -1, 64, 64, -1, -1, 64, 64, 64, 64, 64, 64, 64, -1, -1
-112, -1, -1, -1, -1, 32, -1, 15, 64, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 64, -1, -1, -1, -1, -1, -1, -1, -1, -1
+112, 32, 32, 32, 32, 32, 32, 15, 64, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 64, -1, -1, -1, -1, -1, -1, -1, -1, -1
 -1, -1, -1, -1, -1, -1, 64, 64, -1, -1, -1, -1, -1, -1, -1, -1, 32, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 32, -1
 20, 20, 148, 148, 148, 148, 148, 20, 23, -1, -1, -1, -1, -1, -1, -1, -1, -1, 22, 20, 20, 20, 20, 20, 20, 23, -1, -1, 32, -1, -1, -1, -1, -1, -1
 -1,  2,  0,  0,  0,  0,  0,  0,  1, 97, 97,  97,  97, 97, 97, -1, -1, -1,  2,  0,  0,  0,  0,  0,  0,  1, 97, 97,  6,  5, 80, 80, -1, -1, -1

+ 2 - 1
savegame.txt

@@ -1,2 +1,3 @@
+jump=12
 level.00=true
-sound=false
+sound=true

+ 9 - 4
src/me/hammerle/supersnuvi/entity/Hero.java

@@ -3,16 +3,21 @@ package me.hammerle.supersnuvi.entity;
 import javafx.scene.image.Image;
 import javafx.scene.input.KeyCode;
 import me.hammerle.supersnuvi.gamelogic.Level;
+import me.hammerle.supersnuvi.gamelogic.StateRenderer;
 import me.hammerle.supersnuvi.input.IKeyHandler;
 import me.hammerle.supersnuvi.util.SoundUtils;
 import me.hammerle.supersnuvi.util.Utils;
 
 public class Hero extends AnimatedEntity
 {
+    private final double jumpPower;
+    
     public Hero(Level level,  double x, double y, double width, double height) 
     {
         super(level, x, y, width, height);
         
+        jumpPower = StateRenderer.CONFIG.getDouble("jump", 10);
+                
         face = Utils.getImage("snuvi/snuvi");
         super.setImage(face);
         move = new Image[9];
@@ -42,7 +47,7 @@ public class Hero extends AnimatedEntity
     @Override
     public double getJumpPower() 
     {
-        return 10 * getMaxSpeedModifier();
+        return jumpPower * getMaxSpeedModifier();
     }
     
     @Override
@@ -54,7 +59,7 @@ public class Hero extends AnimatedEntity
         {
             if(jump())
             {
-                SoundUtils.playSound(SoundUtils.JUMP);
+                SoundUtils.playSound(SoundUtils.Sound.JUMP);
             }
 
             /*posX = 96;
@@ -90,11 +95,11 @@ public class Hero extends AnimatedEntity
         {
             if(isInWater())
             {
-                SoundUtils.playSound(SoundUtils.WALK_WATER, false);
+                SoundUtils.playSound(SoundUtils.Sound.WALK_WATER);
             }
             else
             {
-                SoundUtils.playSound(SoundUtils.WALK, false);
+                SoundUtils.playSound(SoundUtils.Sound.WALK);
             }           
         }
     }

+ 15 - 17
src/me/hammerle/supersnuvi/gamelogic/StateRenderer.java

@@ -26,12 +26,12 @@ public class StateRenderer
     private Level currentLevel;
     private final Level[] levels;
     private int levelIndex;
-    private final SimpleConfig config;
-    
-    public static boolean noSound;
-    
+    public static final SimpleConfig CONFIG = new SimpleConfig("savegame.txt");
+
     public StateRenderer(IGameRenderer renderer)
     {
+        CONFIG.load();
+        
         this.renderer = renderer;
         this.registeredTiles = new HashMap<>();
         
@@ -44,11 +44,8 @@ public class StateRenderer
         }
         currentLevel = null;
         levelIndex = 0;
-        config = new SimpleConfig("savegame.txt");
-        config.load();
-        
-        noSound = !config.getBoolean("sound", false);
-        SoundUtils.loadSounds();
+
+        SoundUtils.initSounds();
         
         registerTiles();
     }    
@@ -196,8 +193,8 @@ public class StateRenderer
     {
         if(currentLevel != null)
         {
-            SoundUtils.playSound(SoundUtils.SONG_1, false);
-            SoundUtils.stopSound(SoundUtils.MENU_MUSIC);
+            SoundUtils.playSound(SoundUtils.Sound.SONG_1);
+            SoundUtils.stopSound(SoundUtils.Sound.MENU_MUSIC);
             
             currentLevel.tick(keys);
             
@@ -205,9 +202,9 @@ public class StateRenderer
             if(currentLevel.shouldFinish())
             {
                 currentLevel.resetLevel();
-                config.set("level." + currentLevel.getName(), true);
+                CONFIG.set("level." + currentLevel.getName(), true);
                 currentLevel = null;
-                config.save();
+                CONFIG.save();
                 return;
             }
             
@@ -223,8 +220,8 @@ public class StateRenderer
         }
         else
         {
-            SoundUtils.playSound(SoundUtils.MENU_MUSIC, false);
-            SoundUtils.stopSound(SoundUtils.SONG_1);
+            SoundUtils.playSound(SoundUtils.Sound.MENU_MUSIC);
+            SoundUtils.stopSound(SoundUtils.Sound.SONG_1);
             
             if(keys.isKeyJustReleased(KeyCode.ENTER) > 0)
             {
@@ -306,9 +303,10 @@ public class StateRenderer
             }
 
             renderer.drawChars(TABLE_BOTTOM, x, y);
-
             renderer.stopStringDrawing();
         }
+        
+        SoundUtils.playSounds();
     }
     
     private double paintLevelName(double x, double y, int from, int length)
@@ -332,7 +330,7 @@ public class StateRenderer
             }
             Arrays.fill(chars, border + 1, MENU_WIDTH - 1, (char) 0);
             chars[chars.length - 3] = 134;
-            if(config.getBoolean("level." + s, false))
+            if(CONFIG.getBoolean("level." + s, false))
             {
                 chars[chars.length - 2] = 'x';
             }

+ 5 - 1
src/me/hammerle/supersnuvi/rendering/GameRenderer.java

@@ -10,7 +10,6 @@ import javafx.scene.input.KeyEvent;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Paint;
 import me.hammerle.supersnuvi.input.KeyHandler;
-import me.hammerle.supersnuvi.util.SoundUtils;
 import me.hammerle.text.SnuviTextPainter;
 
 public class GameRenderer extends AnimationTimer implements IGameRenderer
@@ -105,8 +104,13 @@ public class GameRenderer extends AnimationTimer implements IGameRenderer
             timingSum = 0;
             keys.tick();
 
+            timingSum = System.nanoTime();
+            
             prepareRendering();
+
             state.tick(keys);
+            
+            timingSum = System.nanoTime() - timingSum;
         }
     }
     

+ 6 - 1
src/me/hammerle/supersnuvi/rendering/GameWindow.java

@@ -1,14 +1,16 @@
 package me.hammerle.supersnuvi.rendering;
 
-import java.util.Arrays;
 import javafx.application.Application;
 import javafx.scene.Group;
 import javafx.scene.Scene;
 import javafx.scene.canvas.Canvas;
 import javafx.stage.Stage;
+import me.hammerle.supersnuvi.util.SoundUtils;
 
 public class GameWindow extends Application
 {
+    public static boolean isRunning;
+    
     public static void main(String[] args) 
     {
         Application.launch(args);
@@ -18,6 +20,7 @@ public class GameWindow extends Application
     public void init() throws Exception 
     {
         super.init();
+        isRunning = true;
     }
     
     @Override
@@ -43,5 +46,7 @@ public class GameWindow extends Application
     public void stop() throws Exception 
     {
         super.stop();
+        isRunning = false;
+        SoundUtils.playSounds();
     }
 }

+ 3 - 3
src/me/hammerle/supersnuvi/savegame/SimpleConfig.java

@@ -186,17 +186,17 @@ public class SimpleConfig
     
     public final float getFloat(String key, float error)
     {
-        return get(key, Float.class, error);
+        return get(key, Number.class, error).floatValue();
     }
     
     public final double getDouble(String key, double error)
     {
-        return get(key, Double.class, error);
+        return get(key, Number.class, error).doubleValue();
     }
     
     public final int getInt(String key, int error)
     {
-        return get(key, Integer.class, error);
+        return get(key, Number.class, error).intValue();
     }
     
     public final boolean getBoolean(String key, boolean error)

+ 1 - 1
src/me/hammerle/supersnuvi/tiles/BottledSoulTile.java

@@ -30,7 +30,7 @@ public class BottledSoulTile extends Tile
         {
             if(states.add(getKey(x, y)))
             {
-                SoundUtils.playSound(SoundUtils.COLLECT);
+                SoundUtils.playSound(SoundUtils.Sound.COLLECT);
                 ent.getLevel().increaseSouls();
             }
         }

+ 1 - 1
src/me/hammerle/supersnuvi/tiles/CrumblingStoneTile.java

@@ -49,7 +49,7 @@ public class CrumblingStoneTile extends Tile
             if(!states.containsKey(key))
             {
                 states.put(getKey(x, y), 0);
-                SoundUtils.playSound(SoundUtils.STONE_CRUMBLING, true);
+                SoundUtils.playSound(SoundUtils.Sound.STONE_CRUMBLING);
             }
         }
     }

+ 1 - 1
src/me/hammerle/supersnuvi/tiles/TrampolinTile.java

@@ -18,7 +18,7 @@ public class TrampolinTile extends BaseBoxTile
         if(face == Face.UP)
         {
             ent.setMotionY(ent.getJumpPower() * 1.5);
-            SoundUtils.playSound(SoundUtils.JUMP_ON_BOUNCE_SHROOM);
+            SoundUtils.playSound(SoundUtils.Sound.JUMP_ON_BOUNCE_SHROOM);
             /*double motionY = ent.getPreviousMotionY();
             if(motionY < 0)
             {

+ 173 - 47
src/me/hammerle/supersnuvi/util/SoundUtils.java

@@ -1,6 +1,7 @@
 package me.hammerle.supersnuvi.util;
 
 import java.io.File;
+import java.util.Arrays;
 import javafx.scene.media.Media;
 import javafx.scene.media.MediaPlayer;
 import me.hammerle.supersnuvi.gamelogic.StateRenderer;
@@ -8,75 +9,200 @@ import me.hammerle.supersnuvi.rendering.GameWindow;
 
 public class SoundUtils 
 {
-    public static void loadSounds()
+    private static SoundPlayer soundPlayer;
+            
+    public static void initSounds()
     {
+        soundPlayer = new SoundPlayer(!StateRenderer.CONFIG.getBoolean("sound", false));
+        new Thread(soundPlayer).start();
     }
     
-    public final static MediaPlayer COLLECT = getMedia("collect");
-    public final static MediaPlayer JUMP = getMedia("jump");
-    public final static MediaPlayer JUMP_ON_BOUNCE_SHROOM = getMedia("jump_on_bounce_shroom");
-    public final static MediaPlayer STONE_CRUMBLING = getMedia("stone_crumbling");
-    public final static MediaPlayer WALK = getMedia("walk");
-    public final static MediaPlayer WALK_WATER = getMedia("walk_water");
-    
-    public final static MediaPlayer MENU_MUSIC = getMedia("menu_music", true);
-    public final static MediaPlayer SONG_1 = getMedia("song1", true);
+    public enum Sound
+    {
+        COLLECT(true), 
+        JUMP(true), 
+        JUMP_ON_BOUNCE_SHROOM(false), 
+        STONE_CRUMBLING(true), 
+        WALK(false), 
+        WALK_WATER(false),
+        MENU_MUSIC(false), 
+        SONG_1(false);
+        
+        private final boolean reset;
+        
+        Sound(boolean reset)
+        {
+            this.reset = reset;
+        }
+        
+        public boolean shouldReset()
+        {
+            return reset;
+        }
+    }
     
-    public static MediaPlayer getMedia(String path, boolean loop)
+    private static class SoundPlayer implements Runnable
     {
-        if(StateRenderer.noSound)
+        private final MediaPlayer collect;
+        private final MediaPlayer jump;
+        private final MediaPlayer jumpOnBounceShroom;
+        private final MediaPlayer stoneCrumbling;
+        private final MediaPlayer walk;
+        private final MediaPlayer walkWater;
+
+        private final MediaPlayer menuMusic;
+        private final MediaPlayer song1;
+        
+        private final boolean noSound;
+        
+        private final boolean[] playState;
+        private boolean shouldRun;
+        
+        public SoundPlayer(boolean noSound)
         {
-            return null;
+            this.noSound = noSound;
+            shouldRun = false;
+            
+            collect = getMedia("collect");
+            jump = getMedia("jump");
+            jumpOnBounceShroom = getMedia("jump_on_bounce_shroom");
+            stoneCrumbling = getMedia("stone_crumbling");
+            walk = getMedia("walk");
+            walkWater = getMedia("walk_water");
+
+            menuMusic = getMedia("menu_music", true);
+            song1 = getMedia("song1", true);
+            
+            playState = new boolean[Sound.values().length];
+            Arrays.fill(playState, false);
         }
-        Media sound = new Media(new File("sounds/" + path + ".wav").toURI().toString());
-        MediaPlayer mp = new MediaPlayer(sound);
-        if(loop)
+        
+        private MediaPlayer getMedia(String path, boolean loop)
         {
-            mp.setOnEndOfMedia(() -> 
+            if(noSound)
+            {
+                return null;
+            }
+            Media sound = new Media(new File("sounds/" + path + ".wav").toURI().toString());
+            MediaPlayer mp = new MediaPlayer(sound);
+            mp.setCycleCount(MediaPlayer.INDEFINITE);
+            if(loop)
+            {
+                mp.setCycleCount(MediaPlayer.INDEFINITE);
+            }
+            else
             {
-                mp.seek(mp.getStartTime());
-            });
+                mp.setOnEndOfMedia(() -> 
+                {
+                    mp.stop();
+                });
+            }
+            return mp;
+        }
+        
+        private MediaPlayer getMedia(String path)
+        {
+            return getMedia(path, false);
         }
-        else
+        
+        public synchronized void playSound(Sound sound)
         {
-            mp.setOnEndOfMedia(() -> 
+            if(noSound)
             {
-                mp.seek(mp.getStartTime());
-                mp.stop();
-            });
+                return;
+            }
+            shouldRun = true;
+            playState[sound.ordinal()] = true;
         }
-        return mp;
-    }
-    
-    public static MediaPlayer getMedia(String path)
-    {
-        return getMedia(path, false);
-    }
-    
-    public static void playSound(MediaPlayer mp, boolean reset)
-    {
-        if(StateRenderer.noSound)
+        
+        public synchronized void playSounds()
+        {
+            if(!noSound && shouldRun)
+            {
+                this.notifyAll();
+            }
+        }
+        
+        public void stopSound(Sound sound)
         {
-            return;
+            if(noSound)
+            {
+                return;
+            }
+            getMediaPlayer(sound).stop();
+        }
+        
+        private MediaPlayer getMediaPlayer(Sound sound)
+        {
+            switch(sound)
+            {
+                case COLLECT: return collect;
+                case JUMP: return jump;
+                case JUMP_ON_BOUNCE_SHROOM: return jumpOnBounceShroom;
+                case MENU_MUSIC: return menuMusic;
+                case SONG_1: return song1;
+                case STONE_CRUMBLING: return stoneCrumbling;
+                case WALK: return walk;
+                case WALK_WATER: return walkWater;
+            }
+            return null;
         }
-        if(reset)
+
+        @Override
+        public void run() 
         {
-            mp.seek(mp.getStartTime());
+            while(GameWindow.isRunning)
+            {
+                final boolean[] states;
+                synchronized(this)
+                {
+                    states = Arrays.copyOf(soundPlayer.playState, soundPlayer.playState.length);
+                    Arrays.fill(soundPlayer.playState, false);
+                }
+                Sound sound;
+                MediaPlayer mp;
+                for(int i = 0; i < states.length; i++)
+                {
+                    if(states[i])
+                    {
+                        states[i] = false;
+                        sound = Sound.values()[i];
+                        mp = soundPlayer.getMediaPlayer(sound);
+                        if(sound.shouldReset())
+                        {
+                            mp.seek(mp.getStartTime());
+                        }
+                        mp.play();
+                    }
+                }
+                
+                synchronized(this)
+                {
+                    try
+                    {
+                        this.wait();
+                    }
+                    catch(InterruptedException ex)
+                    {
+
+                    }
+                }
+            }
         }
-        mp.play();
     }
     
-    public static void playSound(MediaPlayer mp)
+    public static void playSounds()
     {
-        playSound(mp, true);
+        soundPlayer.playSounds();
     }
     
-    public static void stopSound(MediaPlayer mp)
+    public static void playSound(Sound sound)
     {
-        if(StateRenderer.noSound)
-        {
-            return;
-        }
-        mp.stop();
+        soundPlayer.playSound(sound);
+    }
+    
+    public static void stopSound(Sound sound)
+    {
+        soundPlayer.stopSound(sound);
     }
 }