feat: music & sound!!!
This commit is contained in:
@@ -3,6 +3,7 @@ package eu.midnightdust.yaytris;
|
||||
import eu.midnightdust.yaytris.game.Space;
|
||||
import eu.midnightdust.yaytris.ui.ScoreMenu;
|
||||
import eu.midnightdust.yaytris.ui.TetrisUI;
|
||||
import eu.midnightdust.yaytris.util.SoundUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Random;
|
||||
@@ -36,12 +37,14 @@ public class Tetris {
|
||||
}
|
||||
|
||||
public static void resetSpace() {
|
||||
SoundUtil.stopMusic("/music/theme.wav");
|
||||
if (gravityTask != null) gravityTask.cancel();
|
||||
timer.purge();
|
||||
space = new Space();
|
||||
}
|
||||
|
||||
public static void startGame() {
|
||||
SoundUtil.playMusic("/music/theme.wav", true);
|
||||
space.spawnTetromino();
|
||||
gravityTask = new TimerTask() {
|
||||
@Override
|
||||
@@ -56,6 +59,7 @@ public class Tetris {
|
||||
}
|
||||
|
||||
public static void stopGame() {
|
||||
SoundUtil.stopMusic("/music/theme.wav");
|
||||
if (gravityTask != null) gravityTask.cancel();
|
||||
timer.purge();
|
||||
if (ui.getMenuPanel() instanceof ScoreMenu) ((ScoreMenu) ui.getMenuPanel()).gameOver();
|
||||
|
||||
@@ -24,6 +24,10 @@ public class Space {
|
||||
nextShape = getNextShape();
|
||||
}
|
||||
|
||||
public Tetromino getCurrentTetromino() {
|
||||
return currentTetromino;
|
||||
}
|
||||
|
||||
public TetrominoShape getNextShape() {
|
||||
return TetrominoShape.values()[random.nextInt(TetrominoShape.values().length)];
|
||||
}
|
||||
@@ -60,14 +64,14 @@ public class Space {
|
||||
int combo = 0;
|
||||
Set<Integer> completedLines = new TreeSet<>();
|
||||
for (int line : lines) {
|
||||
if (line > getMapHeight()) continue;
|
||||
if (line >= getMapHeight()) continue;
|
||||
Color[] newBlobs = tetromino.getLine(line);
|
||||
for (int i = 0; i < newBlobs.length; i++) {
|
||||
if (newBlobs[i] == null) continue;
|
||||
gameMap[line][i] = newBlobs[i];
|
||||
}
|
||||
if (Arrays.stream(gameMap[line]).noneMatch(Objects::isNull)) { // Line completed
|
||||
combo += 1;
|
||||
combo += 10;
|
||||
completedLines.add(line);
|
||||
combo *= completedLines.size();
|
||||
}
|
||||
@@ -78,11 +82,11 @@ public class Space {
|
||||
gameMap[i] = (i-1 < 0) ? new Color[gameMap[i].length] : gameMap[i-1];
|
||||
}
|
||||
}
|
||||
this.score += combo;
|
||||
Tetris.updateScore(this.score);
|
||||
increaseScore(combo);
|
||||
}
|
||||
|
||||
public Tetromino getCurrentTetromino() {
|
||||
return currentTetromino;
|
||||
public void increaseScore(int amount) {
|
||||
this.score += amount;
|
||||
Tetris.updateScore(this.score);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.midnightdust.yaytris.game;
|
||||
|
||||
import eu.midnightdust.yaytris.Tetris;
|
||||
import eu.midnightdust.yaytris.util.SoundEffect;
|
||||
import eu.midnightdust.yaytris.util.Vec2i;
|
||||
|
||||
import java.awt.*;
|
||||
@@ -20,6 +21,7 @@ public class Tetromino {
|
||||
public void fall(int length) {
|
||||
Vec2i newPos = centerPos.offset(Vec2i.of(0, length));
|
||||
if (collidesVertically(newPos)) {
|
||||
SoundEffect.BEEP.play();
|
||||
int[] affectedLines = new int[this.collision.length];
|
||||
int line = centerPos.getY();
|
||||
for (int i = 0; i < this.collision.length; i++) {
|
||||
@@ -27,6 +29,7 @@ public class Tetromino {
|
||||
line++;
|
||||
}
|
||||
Tetris.getSpace().onLinesChanged(this, affectedLines);
|
||||
Tetris.getSpace().increaseScore(20-fallLength);
|
||||
if (fallLength >= 1) Tetris.getSpace().spawnTetromino();
|
||||
else Tetris.stopGame();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package eu.midnightdust.yaytris.ui;
|
||||
|
||||
import eu.midnightdust.yaytris.Tetris;
|
||||
import eu.midnightdust.yaytris.util.FileUtil;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
|
||||
public class GameCanvas extends JPanel {
|
||||
final TetrisUI ui;
|
||||
@@ -14,11 +13,7 @@ public class GameCanvas extends JPanel {
|
||||
|
||||
GameCanvas(TetrisUI ui) {
|
||||
this.ui = ui;
|
||||
try {
|
||||
this.texture = ImageIO.read(this.getClass().getResourceAsStream("/textures/tetromino.png"));
|
||||
} catch (IOException | NullPointerException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
this.texture = FileUtil.loadImage("/textures/tetromino.png");
|
||||
}
|
||||
|
||||
public void paintComponent(Graphics graphics) {
|
||||
@@ -27,16 +22,16 @@ public class GameCanvas extends JPanel {
|
||||
|
||||
for (int y = 0; y < Tetris.getSpace().getMapHeight(); y++) {
|
||||
for (int x = 0; x < Tetris.getSpace().getMapWidth(); x++) {
|
||||
Color color = Tetris.getSpace().getGameMapWithTetromino()[y][x];
|
||||
if (color == null) continue;
|
||||
Color color = Tetris.getSpace().getGameMapWithTetromino()[y][x]; // Get the color of the blob at these coordinates
|
||||
if (color == null) continue; // Ignore empty cells
|
||||
int blockSize = (int) Math.ceil((float) (this.getWidth() - this.getInsets().left - this.getInsets().right) / Tetris.getSpace().getMapWidth());
|
||||
//graphics.setXORMode(withAlpha(color,0));
|
||||
graphics.drawImage(texture, x*blockSize +getInsets().left, y*blockSize + getInsets().top, blockSize, blockSize, color, this);
|
||||
graphics.setColor(withAlpha(color, 120));
|
||||
graphics.drawImage(texture, x*blockSize +getInsets().left, y*blockSize + getInsets().top, blockSize, blockSize, color, this); // Draw out (grayscale) texture
|
||||
graphics.setColor(withAlpha(color, 120)); // Overlay the texture with the blobs color
|
||||
graphics.fillRect(x*blockSize +getInsets().left, y*blockSize + getInsets().top, blockSize, blockSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Color withAlpha(Color color, int alpha) {
|
||||
return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
|
||||
}
|
||||
|
||||
37
src/main/java/eu/midnightdust/yaytris/util/FileUtil.java
Normal file
37
src/main/java/eu/midnightdust/yaytris/util/FileUtil.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package eu.midnightdust.yaytris.util;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class FileUtil {
|
||||
public static BufferedImage loadImage(String location) {
|
||||
try (InputStream fileStream = FileUtil.class.getResourceAsStream(location)) {
|
||||
assert fileStream != null;
|
||||
return ImageIO.read(fileStream);
|
||||
} catch (IOException | NullPointerException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static AudioInputStream loadAudio(String location) {
|
||||
try (InputStream fileStream = FileUtil.class.getResourceAsStream(location)) {
|
||||
assert fileStream != null;
|
||||
return AudioSystem.getAudioInputStream(fileStream);
|
||||
} catch (IOException | NullPointerException | UnsupportedAudioFileException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static InputStream getFileStream(String location) {
|
||||
try (InputStream fileStream = FileUtil.class.getResourceAsStream(location)) {
|
||||
return fileStream;
|
||||
} catch (IOException | NullPointerException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/main/java/eu/midnightdust/yaytris/util/SoundEffect.java
Normal file
14
src/main/java/eu/midnightdust/yaytris/util/SoundEffect.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package eu.midnightdust.yaytris.util;
|
||||
|
||||
public enum SoundEffect {
|
||||
BEEP("/sounds/beep.wav");
|
||||
|
||||
final String location;
|
||||
SoundEffect(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public void play() {
|
||||
SoundUtil.playSoundClip(location);
|
||||
}
|
||||
}
|
||||
108
src/main/java/eu/midnightdust/yaytris/util/SoundUtil.java
Normal file
108
src/main/java/eu/midnightdust/yaytris/util/SoundUtil.java
Normal file
@@ -0,0 +1,108 @@
|
||||
package eu.midnightdust.yaytris.util;
|
||||
|
||||
import eu.midnightdust.yaytris.Settings;
|
||||
|
||||
import javax.sound.sampled.*;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class SoundUtil {
|
||||
private static final Map<String, MusicThread> musicThreads = new HashMap<>();
|
||||
|
||||
public static void playMusic(String fileLocation, boolean looped) {
|
||||
if (musicThreads.containsKey(fileLocation)) stopMusic(fileLocation);
|
||||
MusicThread musicThread = new MusicThread(fileLocation, looped);
|
||||
musicThread.start();
|
||||
musicThreads.put(fileLocation, musicThread);
|
||||
}
|
||||
|
||||
public static void stopMusic(String fileLocation) {
|
||||
if (musicThreads.containsKey(fileLocation)) musicThreads.get(fileLocation).stopMusic();
|
||||
}
|
||||
|
||||
// Adapted from: https://www.baeldung.com/java-play-sound
|
||||
public static void playSoundClip(String fileLocation) {
|
||||
try (AudioInputStream stream = AudioSystem.getAudioInputStream(SoundUtil.class.getResource(fileLocation))) { // FIXME: Support audio files from JAR. File streams won't work here for some reason.
|
||||
AudioFormat format = stream.getFormat();
|
||||
DataLine.Info info = new DataLine.Info(Clip.class, format);
|
||||
|
||||
Clip audioClip = (Clip) AudioSystem.getLine(info);
|
||||
audioClip.addLineListener(new LineUpdateListener());
|
||||
audioClip.open(stream);
|
||||
audioClip.start();
|
||||
setVolume(audioClip, Settings.soundVolume);
|
||||
} catch (LineUnavailableException | IOException | UnsupportedAudioFileException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from: https://stackoverflow.com/a/40698149
|
||||
private static void setVolume(Line line, int volume) {
|
||||
if (volume < 0 || volume > 100)
|
||||
throw new IllegalArgumentException("Volume not valid: " + volume);
|
||||
FloatControl gainControl = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN);
|
||||
gainControl.setValue(20f * (float) Math.log10(volume/100f));
|
||||
}
|
||||
|
||||
public static class MusicThread extends Thread {
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
private final boolean looped;
|
||||
private final String fileLocation;
|
||||
private boolean playing;
|
||||
|
||||
MusicThread(String fileLocation, boolean looped) {
|
||||
this.fileLocation = fileLocation;
|
||||
this.looped = looped;
|
||||
this.playing = true;
|
||||
}
|
||||
|
||||
public void stopMusic() {
|
||||
this.playing = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
do {playMusic(fileLocation);} while (looped && playing);
|
||||
}
|
||||
|
||||
// Adapted from: https://www.baeldung.com/java-play-sound
|
||||
private void playMusic(String fileLocation) {
|
||||
try (AudioInputStream stream = AudioSystem.getAudioInputStream(SoundUtil.class.getResource(fileLocation))) {
|
||||
AudioFormat format = stream.getFormat();
|
||||
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
|
||||
|
||||
SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem.getLine(info);
|
||||
sourceDataLine.open(format);
|
||||
sourceDataLine.start();
|
||||
setVolume(sourceDataLine, Settings.musicVolume);
|
||||
float fadeOut = 1.0f;
|
||||
|
||||
byte[] bufferBytes = new byte[BUFFER_SIZE];
|
||||
int readBytes;
|
||||
while ((readBytes = stream.read(bufferBytes)) != -1) {
|
||||
if (!playing) {
|
||||
fadeOut = (float) Math.sin(fadeOut-0.01f); // Sinus fade-out
|
||||
setVolume(sourceDataLine, (int) (fadeOut * Settings.musicVolume));
|
||||
if (fadeOut <= 0) break;
|
||||
}
|
||||
sourceDataLine.write(bufferBytes, 0, readBytes);
|
||||
}
|
||||
|
||||
sourceDataLine.drain();
|
||||
sourceDataLine.close();
|
||||
} catch (LineUnavailableException | IOException | UnsupportedAudioFileException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class LineUpdateListener implements LineListener {
|
||||
@Override
|
||||
public void update(LineEvent event) {
|
||||
if (LineEvent.Type.STOP == event.getType()) {
|
||||
event.getLine().close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user