progress on adding base layer

This commit is contained in:
2018-09-11 00:05:34 -05:00
parent 10d9400342
commit bce143efbb
292 changed files with 10211 additions and 0 deletions

17
old/core/.classpath Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="bin/main" path="src">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry exported="true" kind="con" path="org.springsource.ide.eclipse.gradle.classpathcontainer"/>
<classpathentry kind="lib" path="Z:/Projects/RhythmBullet/core/lib/jaudiotagger-2.2.3.jar" sourcepath="lib/jaudiotagger-2.2.3-sources.jar">
<attributes>
<attribute name="javadoc_location" value="jar:file:/Z:/Projects/RhythmBullet/core/lib/jaudiotagger-2.2.3-javadoc.jar!/"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="bin/default"/>
</classpath>

18
old/core/.project Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>RhythmBullet-core</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.springsource.ide.eclipse.gradle.core.nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,4 @@
#org.springsource.ide.eclipse.gradle.core.preferences.GradleProjectPreferences
#Mon Sep 10 00:40:41 CDT 2018
org.springsource.ide.eclipse.gradle.linkedresources=
org.springsource.ide.eclipse.gradle.rootprojectloc=..

View File

@@ -0,0 +1,8 @@
#org.springsource.ide.eclipse.gradle.core.actions.GradleRefreshPreferences
#Mon Sep 10 00:40:41 CDT 2018
addResourceFilters=true
afterTasks=afterEclipseImport;
beforeTasks=cleanEclipse;eclipse;
enableAfterTasks=true
enableBeforeTasks=true
useHierarchicalNames=false

View File

@@ -0,0 +1,11 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8

11
old/core/build.gradle Normal file
View File

@@ -0,0 +1,11 @@
apply plugin: "java"
sourceCompatibility = 1.6
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
sourceSets.main.java.srcDirs = [ "src/" ]
eclipse.project {
name = appName + "-core"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,231 @@
package zero1hd.rhythmbullet;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.assets.loaders.ParticleEffectLoader;
import com.badlogic.gdx.assets.loaders.SoundLoader;
import com.badlogic.gdx.assets.loaders.TextureAtlasLoader;
import com.badlogic.gdx.assets.loaders.TextureLoader;
import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver;
import com.badlogic.gdx.assets.loaders.resolvers.ResolutionFileResolver.Resolution;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.ParticleEffect;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import zero1hd.rhythmbullet.util.AssetPack;
import zero1hd.rhythmbullet.util.GenericFileTypeHandler;
import zero1hd.rhythmbullet.util.InitialScreen;
import zero1hd.rhythmbullet.util.RoundingResolutionHandler;
import zero1hd.rhythmbullet.util.ScreenConfiguration;
import zero1hd.rhythmbullet.util.ResizeReadyScreen;
public class RhythmBullet extends Game {
public static final int WORLD_WIDTH = 64;
public static final int WORLD_HEIGHT = 48;
public static final int SPAWN_CIRCLE_RADIUS = 6;
public static int pixels_per_unit;
private boolean initiated;
private boolean resizing, simpleResizeOnce;
private int screenWidth, screenHeight;
public static final String VERSION = "(1.0.0) R1-PreAlpha";
private AssetManager assetManager = new AssetManager();
private Skin skin;
private Preferences preferences;
private RoundingResolutionHandler rRHandler;
private InitialScreen initialScreen;
private AssetPack assetPack;
private ScreenConfiguration screenConfiguration;
/**
* This should be called before passed to LWJGL. Setup for system-dependent items such as UI and assets.
* @param initialScreen the first screen to go to.
* @param assetPack the asset package to be used.
*/
public void setup(InitialScreen initialScreen, AssetPack assetPack, ScreenConfiguration screenConfiguration) {
this.initialScreen = initialScreen;
this.assetPack = assetPack;
this.screenConfiguration = screenConfiguration;
screenConfiguration.queueBorderless(true);
}
@Override
public void create() {
setScreen(initialScreen);
Gdx.app.setLogLevel(Application.LOG_DEBUG);
screenWidth = Gdx.graphics.getWidth();
screenHeight = Gdx.graphics.getHeight();
initialScreen.init();
initialLoad();
}
private void initialLoad() {
if (initiated) throw new IllegalStateException("Initiation cannot occur more than once.");
simpleResizeOnce = true;
skin = new Skin();
assetPack.initiate();
preferences = Gdx.app.getPreferences("RhythmBullet Preferences");
Resolution[] resolution = {
new Resolution(1280, 720, "1280x720"),
new Resolution(1366, 768, "1366x768"),
new Resolution(1280, 800, "1280x800"),
new Resolution(1920, 1080, "1920x1080"),
new Resolution(1920, 1200, "1920x1200"),
new Resolution(2560, 1440, "2560x1440"),
new Resolution(3840, 2160, "3840x2160")
};
InternalFileHandleResolver internalFileResolver = new InternalFileHandleResolver();
rRHandler = new RoundingResolutionHandler(internalFileResolver, resolution);
GenericFileTypeHandler genericFileFinder = new GenericFileTypeHandler(internalFileResolver);
assetManager.setLoader(TextureAtlas.class, new TextureAtlasLoader(rRHandler));
assetManager.setLoader(Texture.class, new TextureLoader(rRHandler));
assetManager.setLoader(ParticleEffect.class, new ParticleEffectLoader(genericFileFinder));
assetManager.setLoader(Sound.class, new SoundLoader(genericFileFinder));
rRHandler.setResolution(getPreferences().getInteger("screen-width"), getPreferences().getInteger("screen-height"));
queueAssets();
}
private void initialLoadComplete() {
screenConfiguration.queueBorderless(preferences.getBoolean("borderless", false));
skin.addRegions(assetManager.get("uiskin.atlas", TextureAtlas.class));
pixels_per_unit = (int) (Float.valueOf(screenHeight)/Float.valueOf(WORLD_HEIGHT));
if (getPreferences().getBoolean("fullscreen", true)) {
Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode());
} else {
Gdx.graphics.setWindowedMode(getPreferences().getInteger("screen-width"), getPreferences().getInteger("screen-height"));
}
assetPack.generateFonts(skin);
assetPack.setupSkin(skin);
assetPack.complete(assetManager);
}
@Override
public void render() {
checkAssetQueue();
super.render();
}
public boolean checkAssetQueue() {
if (assetManager.update()) {
if (resizing) {
Gdx.app.debug("Resize", "Post resize is starting...");
if (skin != null) skin.dispose();
skin = new Skin();
skin.addRegions(assetManager.get("uiskin.atlas", TextureAtlas.class));
assetPack.generateFonts(skin);
assetPack.setupSkin(skin);
assetPack.complete(assetManager);
if (getScreen() instanceof ResizeReadyScreen) {
((ResizeReadyScreen) getScreen()).postAssetLoad();
} else {
throw new IllegalStateException("Cannot perform window resize on a screen that isn't resize ready.");
}
Gdx.app.debug("Resize", "Post resize has ended.");
resizing = false;
} else if (!initiated) {
initiated = true;
initialLoadComplete();
setScreen(((InitialScreen) initialScreen).advance(this));
}
return true;
}
return false;
}
@Override
public void setScreen(Screen screen) {
if (screen instanceof ResizeReadyScreen) {
ResizeReadyScreen advancedResizeScreen = (ResizeReadyScreen) screen;
try {
advancedResizeScreen.preAssetLoad();
} catch (NullPointerException cleanScreen) {
Gdx.app.debug("Screen", "clean screen: " + advancedResizeScreen.getClass().getSimpleName());
//Tried to perform pre-asset reload, but had uninitialized objects, meaning this is a new screen, or "clean" screen.
} finally {
advancedResizeScreen.postAssetLoad();
}
}
super.setScreen(screen);
}
@Override
public void resize(int width, int height) {
if (screenWidth != width || screenHeight != height) {
Gdx.app.debug("resize", "Current size:" + screenWidth + "x" + screenHeight + " new size: " + width + "x" + height);
screenWidth = width;
screenHeight = height;
pixels_per_unit = (int) (Float.valueOf(screenHeight)/Float.valueOf(WORLD_HEIGHT));
rRHandler.setResolution(width, height);
preferences.putInteger("screen-width", width);
preferences.putInteger("screen-height", height);
preferences.flush();
if (!simpleResizeOnce) {
Gdx.app.debug("Resize", "complex pre-resize is happening. Resizing to " + width + "x" + height);
if (getScreen() instanceof ResizeReadyScreen) {
((ResizeReadyScreen) getScreen()).preAssetLoad();
} else {
throw new IllegalStateException("Cannot perform window resize on a screen that isn't using a resize ready screen.");
}
resizing = true;
assetManager.clear();
queueAssets();
} else {
simpleResizeOnce = false;
}
super.resize(width, height);
}
}
public void queueAssets() {
assetPack.queueTextures(assetManager);
assetPack.queueSFX(assetManager);
assetPack.queueParticles(assetManager);
}
public AssetManager getAssetManager() {
return assetManager;
}
public Skin getSkin() {
return skin;
}
public Preferences getPreferences() {
return preferences;
}
@Override
public void dispose() {
Gdx.app.debug("Core", "disposing...");
try {
getScreen().dispose();
getSkin().dispose();
assetManager.dispose();
assetPack.dispose();
} catch (NullPointerException npe) {
Gdx.app.debug("Core", "Disposal error occurred, possibly caused by failing to complete initialization.", npe);
}
super.dispose();
}
public ScreenConfiguration getScreenConfiguration() {
return screenConfiguration;
}
}

View File

@@ -0,0 +1,166 @@
package zero1hd.rhythmbullet.audio;
import java.util.Comparator;
import java.util.Observable;
import java.util.Observer;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.Sort;
import zero1hd.rhythmbullet.audio.metadata.AudioMetadata;
import zero1hd.rhythmbullet.audio.metadata.MP3Metadata;
import zero1hd.rhythmbullet.audio.metadata.WAVMetadata;
public class AudioMetadataController extends Observable implements Disposable, Observer {
private MusicList musicList;
private volatile Array<AudioMetadata> metadataArray;
private MetadataLoadingThread loadingThread;
private volatile boolean searching;
private Comparator<AudioMetadata> metadataComparer;
public AudioMetadataController(MusicList musicList) {
this.musicList = musicList;
metadataArray = new Array<>();
loadingThread = new MetadataLoadingThread();
musicList.addObserver(this);
metadataComparer = new Comparator<AudioMetadata>() {
@Override
public int compare(AudioMetadata o1, AudioMetadata o2) {
return o1.getTitle().compareToIgnoreCase(o2.getTitle());
}
};
}
public MusicList getMusicList() {
return musicList;
}
/**
* Non-blocking, loads on separate thread.
*/
public void loadAudioMetadata() {
if (!loadingThread.start()) {
loadingThread.stop();
loadingThread = new MetadataLoadingThread();
loadingThread.start();
}
}
/**
* if there is the same amount of metadata as there is music in the music list.
* @return whether or not both sizes are equal.
*/
public boolean isSameSizeMusicList() {
return (metadataArray.size == musicList.getTotal());
}
@Override
public void dispose() {
for (int i = 0; i < metadataArray.size; i++) {
metadataArray.get(i).dispose();
}
}
public int size() {
return metadataArray.size;
}
public AudioMetadata getAudioMetadata(int index) {
return metadataArray.get(index);
}
public AudioMetadata getAudioMetadata(FileHandle filehandle) {
for (int i = 0; i < metadataArray.size; i++) {
if (metadataArray.get(i).getFileHandle() == filehandle) {
return metadataArray.get(i);
}
}
throw new IllegalArgumentException("Couldn't find file " + filehandle.name());
}
public boolean isSearching() {
return searching;
}
private class MetadataLoadingThread implements Runnable {
private Thread thread;
private String name = "AudioMetadata-Load";
private volatile boolean work = true;
@Override
public void run() {
Gdx.app.debug(name, "loading...");
clear();
synchronized (this) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
searching = true;
Array<AudioMetadata> tempMetadataArray = new Array<>();
for (int i = 0; i < musicList.getTotal() && work; i++) {
FileHandle musicFile = musicList.getAudioFileHandle(i);
if (musicFile == null) return;
switch (SupportedFormats.valueOf(musicFile.extension().toUpperCase())) {
case MP3:
tempMetadataArray.add(new MP3Metadata(musicFile));
break;
case WAV:
tempMetadataArray.add(new WAVMetadata(musicFile));
break;
default:
break;
}
}
Sort.instance().sort(tempMetadataArray, metadataComparer);
if (work) {
metadataArray = tempMetadataArray;
searching = false;
Gdx.app.debug(name, "load complete.");
setChanged();
notifyObservers();
}
}
public boolean start() {
if (thread == null) {
thread = new Thread(this, name);
thread.start();
return true;
} else {
return false;
}
}
public void stop() {
work = false;
}
}
@Override
public void update(Observable o, Object arg) {
if (o == musicList && arg == musicList.states.COMPLETE) {
loadAudioMetadata();
}
}
public void clear() {
Gdx.app.postRunnable(() -> {
for (int i = 0; i < metadataArray.size; i++) {
metadataArray.get(i).dispose();
}
metadataArray.clear();
synchronized (loadingThread) {
loadingThread.notify();
}
});
}
}

View File

@@ -0,0 +1,12 @@
package zero1hd.rhythmbullet.audio;
import com.badlogic.gdx.files.FileHandle;
import zero1hd.rhythmbullet.audio.processor.AudioProcessor;
public interface AudioProcessorFactory {
/**
* @return a new {@link #zero1hd.rhythmbullet.audio.processor.AudioProcessor()} from the appropriate platform.
*/
public AudioProcessor newMP3AudioProcessor(FileHandle fileHandle);
}

View File

@@ -0,0 +1,61 @@
package zero1hd.rhythmbullet.audio;
import java.io.IOException;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.AudioHeader;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.audio.mp3.MP3AudioHeader;
import org.jaudiotagger.audio.mp3.MP3File;
import org.jaudiotagger.tag.TagException;
import com.badlogic.gdx.files.FileHandle;
public class MinimalAudioHeader {
private int sampleRate, channelCount;
private SupportedFormats format;
private FileHandle musicFile;
public MinimalAudioHeader(FileHandle musicFile) {
if (musicFile == null) throw new IllegalArgumentException("musicFile for minimal audio headers should not be null.");
this.musicFile = musicFile;
format = SupportedFormats.valueOf(musicFile.extension().toUpperCase());
try {
AudioFile file = AudioFileIO.read(musicFile.file());
AudioHeader header = file.getAudioHeader();
sampleRate = header.getSampleRateAsNumber();
channelCount = (header.getChannels().equals("Mono") ? 1 : 2);
} catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
e.printStackTrace();
}
}
public int getSampleRate() {
return sampleRate;
}
public int getChannelCount() {
return channelCount;
}
public long estimateSampleFrames() {
switch (format) {
case MP3:
try {
MP3File file = (MP3File) AudioFileIO.read(musicFile.file());
MP3AudioHeader header = file.getMP3AudioHeader();
return header.getNumberOfFrames();
} catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
e.printStackTrace();
}
return -1;
default:
return -1;
}
}
}

View File

@@ -0,0 +1,262 @@
package zero1hd.rhythmbullet.audio;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Music.OnCompletionListener;
import com.badlogic.gdx.files.FileHandle;
/**
* Manages current games music playback and does this in tandem with the {@link MusicList} by asking to retrieve files and then feeding it to LibGDX.
* Notifies observers when a new song is loaded.
* The loading model is like taking a disk and loading it into a player. It doesn't necessarily mean it'll play right away, but its ready and the only track that has a stream opened.
* @author yunya
*
*/
public class MusicController extends Observable implements OnCompletionListener, Observer {
public final class States {
public final Integer LOADED = new Integer(0), PLAYING = new Integer(1), PAUSED = new Integer(2);
}
public final States states = new States();
private MusicList musicList;
private MinimalAudioHeader musicHeader;
private volatile Music music;
private int currentlyPlayingIndex;
private boolean autoPlay;
private boolean shuffle;
private Random rand;
private Preferences prefs;
public MusicController(MusicList musicList, Preferences prefs) {
if (prefs == null) throw new NullPointerException("preferences can't be null...");
if (musicList == null) throw new NullPointerException("music list can't be null...");
musicList.addObserver(this);
this.prefs = prefs;
this.musicList = musicList;
rand = new Random();
}
/**
* This play method automatically sets the volume.
*/
public void play() {
if (music != null) {
Gdx.app.debug("MusicController", "Playing from controller.");
music.play();
music.setVolume(prefs.getFloat("music vol", 100f)/100f);
setChanged();
notifyObservers(states.PLAYING);
} else {
Gdx.app.debug("MusicController", "Music isn't loaded!");
Thread.dumpStack();
}
}
/**
* Called to pause current song. Does nothing if no song is playing or loaded.
*/
public void pause() {
if (music != null) {
music.pause();
setChanged();
notifyObservers(states.PAUSED);
}
}
/**
* Loads music based on the index in the {@link MusicList}.
* @param index of music to play
*/
public void setMusicByIndex(int index) {
this.currentlyPlayingIndex = index;
loadMusic();
}
/**
* Loads music using the given file. The given file must be found in the {@link MusicList}.
* This function gets the index of the given file within {@link MusicList} and passes that index to {@link #setMusicByIndex(index)}.
* @param fileHandle to use.
*/
public void setMusicByFileHandle(FileHandle fileHandle) {
setMusicByIndex(musicList.getIndexOfFileHandle(fileHandle));
}
/**
* Goes to the next track
*/
public void skip() {
currentlyPlayingIndex++;
if (shuffle) {
shuffle();
}
loadMusic();
}
/**
* Goes to the previous track
*/
public void previous() {
currentlyPlayingIndex--;
if (shuffle) {
shuffle();
}
loadMusic();
}
@Override
public void onCompletion(Music music) {
if (autoPlay) {
if (shuffle) {
shuffle();
} else {
currentlyPlayingIndex++;
}
loadMusic();
play();
}
}
/**
* Shuffles the controller whether the shuffle boolean is true or false.
*/
public void shuffle() {
Gdx.app.debug("MusicListController", "shuffled.");
if (musicList.getTotal() == 0) {
currentlyPlayingIndex = 0;
} else {
currentlyPlayingIndex = rand.nextInt(musicList.getTotal());
}
}
public void setAutoPlay(boolean autoPlay) {
this.autoPlay = autoPlay;
}
public void setShuffle(boolean shuffle) {
this.shuffle = shuffle;
}
public void setLoop(boolean loop) {
music.setLooping(loop);
}
public boolean isShuffle() {
return shuffle;
}
public boolean isAutoPlay() {
return autoPlay;
}
public boolean isLoop() {
return music.isLooping();
}
/**
* Loads the current selected song.
*/
public void loadMusic() {
Gdx.app.debug("MusicListController", "music is being loaded from music list with " + musicList.getTotal() + " songs.");
boolean playing = isPlaying();
musicHeader = null;
if (music != null) {
music.dispose();
}
if (currentlyPlayingIndex < 0) {
currentlyPlayingIndex = musicList.getTotal()-1;
}
if (currentlyPlayingIndex >= musicList.getTotal()) {
currentlyPlayingIndex = 0;
}
if (musicList.getTotal() != 0) {
this.music = Gdx.audio.newMusic(musicList.getAudioFileHandle(currentlyPlayingIndex));
music.setOnCompletionListener(this);
setChanged();
notifyObservers(states.LOADED);
if (playing || autoPlay) {
play();
}
}
}
public MusicList getMusicList() {
return musicList;
}
public FileHandle getCurrentMusicFileHandle() {
return musicList.getSongFileHandleFromIndex(currentlyPlayingIndex);
}
public MinimalAudioHeader getCurrentMusicHeader() {
if (musicHeader != null) {
return musicHeader;
} else {
return musicHeader = musicList.newMinimalAudioHeader(getCurrentMusicFileHandle());
}
}
@Override
public void update(Observable o, Object arg) {
if (o == musicList) {
if (arg == musicList.states.LOADING) {
pause();
} else if (arg == musicList.states.COMPLETE) {
if (shuffle) {
shuffle();
}
loadMusic();
}
}
}
public String getCurrentSongName() {
return getCurrentMusicFileHandle().nameWithoutExtension();
}
public float getCurrentPosition() {
if (music != null) {
return music.getPosition();
}
return 0;
}
public void setMusicPosition(float position) {
if (music != null) {
music.setPosition(position);
}
}
public boolean isPlaying() {
if (music != null) {
return music.isPlaying();
}
return false;
}
/**
* Returns the current music. In no circumstances should this be used to begin playing or it would be playing independent of the controller.
* Doing otherwise would mean you are playing the music separately of the controller.
* @return the {@link Music} that is currently "loaded" and ready to play.
*/
public Music getCurrentMusic() {
return music;
}
public int getCurrentlyPlayingIndex() {
return currentlyPlayingIndex;
}
public boolean hasSongLoaded() {
if (music != null) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,211 @@
package zero1hd.rhythmbullet.audio;
import java.util.Comparator;
import java.util.Observable;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Sort;
import zero1hd.rhythmbullet.audio.processor.AudioProcessor;
import zero1hd.rhythmbullet.audio.processor.WAVAudioProcessor;
/**
* A music list made to store paths to all the songs within a given directory. Can activate the music calling {@link #newAudioProcessor(FileHandle)} and its derivatives.
* Is observable, meaning, given there are observers, will notify when the list completes a refresh.
* @author yunya
*/
public class MusicList extends Observable {
public final class States {
public final Integer LOADING = new Integer(0), COMPLETE = new Integer(1), EMPTY = new Integer(2);
}
public States states = new States();
private Array<FileHandle> musicList;
private RecursiveMusicSearchThread searchThread;
private AudioProcessorFactory audioProcFactory;
private volatile boolean searched;
private String searchPath;
private Comparator<FileHandle> compare;
public MusicList(AudioProcessorFactory audioProcessorFactory, String searchPath) {
this.audioProcFactory = audioProcessorFactory;
musicList = new Array<>();
setSearchPath(searchPath);
compare = new Comparator<FileHandle>() {
@Override
public int compare(FileHandle o1, FileHandle o2) {
return o1.nameWithoutExtension().compareTo(o2.nameWithoutExtension());
}
};
}
/**
* Asynchronous recursive search on music directory.
* Also notifies listeners that are on the main thread.
* @param refresh does a search whether or not path has changed and whether or not this list has searched before this.
*/
public void attemptAsyncSearch(boolean refresh) {
if (refresh) {
notifyObservers(states.LOADING);
if (searchThread != null) {
if (!searchThread.start()) {
searchThread.stop();
searchThread = new RecursiveMusicSearchThread("Music Search Thread", Gdx.files.absolute(searchPath));
searchThread.start();
}
} else {
searchThread = new RecursiveMusicSearchThread("Music Search Thread", Gdx.files.absolute(searchPath));
searchThread.start();
}
} else {
if (!searched || hasChanged()) {
attemptAsyncSearch(true);
}
}
}
public void setSearchPath(String searchPath) {
if (this.searchPath != null && this.searchPath.equals(searchPath)) return;
this.searchPath = searchPath;
setChanged();
}
public String getSearchPath() {
return searchPath;
}
/**
* @param file
* @return a {@link AudioProcessor} of the given music file. Will return null if theres a format error.
*/
public AudioProcessor newAudioProcessor(FileHandle file) {
switch (SupportedFormats.valueOf(file.extension().toUpperCase())) {
case MP3:
return audioProcFactory.newMP3AudioProcessor(file);
case WAV:
return new WAVAudioProcessor(file);
default:
break;
}
return null;
}
/**
*
* @param file the music file that you need the header for.
* @return the header containing minimal info of the song.
*/
public MinimalAudioHeader newMinimalAudioHeader(FileHandle file) {
return new MinimalAudioHeader(file);
}
public AudioProcessor newAudioProcessorFromIndex(int index) {
if (!searched) {
Gdx.app.debug("MusicList", "Warning, this list has not completed it's search...");
Thread.dumpStack();
}
if (musicList.size == 0) {
return null;
}
return newAudioProcessor(musicList.get(index));
}
public FileHandle getSongFileHandleFromIndex(int index) {
if (!searched) {
Gdx.app.debug("MusicList", "Warning, this list has not completed it's search...");
Thread.dumpStack();
}
if (musicList.size == 0) {
return null;
}
return musicList.get(index);
}
public FileHandle getAudioFileHandle(int index) {
return musicList.get(index);
}
public int getIndexOfFileHandle(FileHandle file) {
return musicList.indexOf(file, true);
}
public boolean isSearched() {
return searched;
}
/**
*
* @return the amount of audio files discovered.
*/
public int getTotal() {
return musicList.size;
}
private class RecursiveMusicSearchThread implements Runnable {
private Thread thread;
private String threadName;
private FileHandle directory;
private volatile boolean work;
public RecursiveMusicSearchThread(String name, FileHandle searchDirectory) {
this.threadName = name;
this.directory = searchDirectory;
}
@Override
public void run() {
Gdx.app.debug("MusicList", "recursive async search beginning.");
Array<FileHandle> obtainedAudioFiles = recursiveMusicSearch(directory);
Sort.instance().sort(obtainedAudioFiles, compare);
if (work) {
musicList = obtainedAudioFiles;
searched = true;
Gdx.app.debug("MusicList", "recursive async search completed.");
setChanged();
if (musicList.size != 0) {
notifyObservers(states.COMPLETE);
} else {
notifyObservers(states.EMPTY);
}
}
}
public boolean start() {
if (thread == null) {
work = true;
thread = new Thread(this, threadName);
thread.setDaemon(true);
thread.start();
return true;
} else {
return false;
}
}
public void stop() {
work = false;
}
private Array<FileHandle> recursiveMusicSearch(FileHandle fileHandle) {
Array<FileHandle> musicFiles = new Array<>();
FileHandle[] files = fileHandle.list();
for (int i = 0; i < files.length && work; i++) {
if (files[i].isDirectory()) {
musicFiles.addAll(recursiveMusicSearch(files[i]));
} else {
try {
SupportedFormats.valueOf(files[i].extension().toUpperCase());
musicFiles.add(files[i]);
} catch (IllegalArgumentException e) {
Gdx.app.log("MusicList", "Unsupported file format: " + files[i].name());
}
}
}
return musicFiles;
}
}
}

View File

@@ -0,0 +1,5 @@
package zero1hd.rhythmbullet.audio;
public enum SupportedFormats {
WAV, MP3;
}

View File

@@ -0,0 +1,49 @@
package zero1hd.rhythmbullet.audio.analyzer;
import com.badlogic.gdx.utils.FloatArray;
public class AudioAnalyzerSection {
private int lower, upper, thresholdRange;
private float thresholdFactor;
private FloatArray peaks;
private int pUID;
public AudioAnalyzerSection(int lowerBin, int upperBin, float thresholdFactor, int thresholdRange) {
this.upper = upperBin;
this.lower = lowerBin;
this.thresholdRange = thresholdRange;
this.thresholdFactor = thresholdFactor;
}
public void setPUID(int pUID) {
this.pUID = pUID;
}
public void setPeaks(FloatArray peaks) {
this.peaks = peaks;
}
public int getLower() {
return lower;
}
public int getUpper() {
return upper;
}
public float getThresholdFactor() {
return thresholdFactor;
}
public int getThresholdRange() {
return thresholdRange;
}
public FloatArray getPeaks() {
return peaks;
}
public int getPUID() {
return pUID;
}
}

View File

@@ -0,0 +1,148 @@
package zero1hd.rhythmbullet.audio.analyzer;
import java.util.Observable;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.TimeUtils;
import edu.emory.mathcs.jtransforms.fft.FloatFFT_1D;
import zero1hd.rhythmbullet.audio.processor.AudioProcessor;
public class DynamicAudioAnalyzer extends Observable implements Runnable {
private volatile boolean work = true;
private Thread thread;
private String threadName = "analyzer";
private final int WINDOWLENGTH = 1024;
private AudioProcessor processor;
private AudioAnalyzerSection[] sections;
private FloatArray[] flux;
private FloatArray[] threshold;
private volatile int pUID = 0;
private long timer;
public DynamicAudioAnalyzer(AudioProcessor processor, AudioAnalyzerSection... sections) {
this.sections = sections;
this.processor = processor;
flux = new FloatArray[sections.length];
threshold = new FloatArray[sections.length];
for (int section = 0; section < sections.length; section++) {
flux[section] = new FloatArray();
threshold[section] = new FloatArray();
}
}
@Override
public void run() {
calculateSpectralFlux();
calculateThreshold();
calculatePeaks();
}
public void start() {
if (thread == null) {
thread = new Thread(this, threadName);
thread.setDaemon(true);
thread.start();
} else {
throw new IllegalStateException("Cannot have two analyzer threads.");
}
}
public void stop() {
work = false;
}
private void calculateSpectralFlux() {
Gdx.app.debug("Spectral Flux Calculation", "Beginning...");
timer = TimeUtils.millis();
float[] audioPCM = new float[WINDOWLENGTH];
float[] spectrum = new float[(WINDOWLENGTH/2)+1];
float[] lastSpectrum = new float[spectrum.length];
FloatFFT_1D fft = new FloatFFT_1D(WINDOWLENGTH);
int windowsComplete = 0;
int seedCurrentDigit = 0;
int totalWindows = (int) processor.getSampleFrames()/WINDOWLENGTH;
while (processor.readFrames(audioPCM) > 0 && work) {
fft.realForward(audioPCM);
//Building a PUID (Pseudo unique ID)
if (windowsComplete == (seedCurrentDigit*totalWindows/9)) {
float avg = 0;
for (int frame = 0; frame < spectrum.length; frame++) {
avg += spectrum[frame];
}
avg /= spectrum.length;
if (avg < 0) {
avg *= -1f;
}
pUID +=(int) Math.pow(10, 9-seedCurrentDigit) * ((int)(avg*1000f)-(int)(avg*100f)*10);
seedCurrentDigit ++;
}
System.arraycopy(spectrum, 0, lastSpectrum, 0, spectrum.length);
System.arraycopy(audioPCM, 0, spectrum, 0, spectrum.length);
for (int section = 0; section < sections.length; section++) {
float currentFlux = 0;
for (int bin = sections[section].getLower(); bin < sections[section].getUpper(); bin++) {
currentFlux += Math.max(0f, spectrum[bin] - lastSpectrum[bin]);
}
flux[section].add(currentFlux);
}
windowsComplete++;
}
for (int section = 0; section < sections.length; section++) {
sections[section].setPUID(pUID);
}
Gdx.app.debug("Spectral Flux Calculation", "Finished. Took " + (TimeUtils.timeSinceMillis(timer)) + "ms");
}
private void calculateThreshold() {
long subTimer = TimeUtils.millis();
Gdx.app.debug("Threshold Calculation", "Beginning...");
for (int section = 0; section < sections.length && work; section++) {
FloatArray fluxArray = flux[section];
for (int bin = 0; bin < fluxArray.size; bin++) {
int range = sections[section].getThresholdRange();
int start = Math.max(0, bin - range);
int end = Math.min(fluxArray.size, bin + range);
float average = 0;
for (int pos = start; pos < end; pos++) {
average += fluxArray.get(pos);
}
average /= (end - start);
threshold[section].add(average);
}
}
Gdx.app.debug("Spectral Flux Calculation", "Finished. Took " + (TimeUtils.timeSinceMillis(subTimer)) + "ms");
}
private void calculatePeaks() {
long subTimer = TimeUtils.millis();
Gdx.app.debug("Peak Calculation", "Beginning...");
for (int section = 0; section < sections.length && work; section++) {
FloatArray peaks = new FloatArray();
for (int bin = 0; bin < threshold[section].size -1; bin++) {
float prunedFlux = flux[section].get(bin) - threshold[section].get(bin);
float prunedNextFlux = flux[section].get(bin + 1) - threshold[section].get(bin + 1);
peaks.add((prunedFlux > prunedNextFlux) ? prunedFlux : 0);
}
sections[section].setPeaks(peaks);
}
Gdx.app.debug("Spectral Flux Calculation", "Finished. Took " + (TimeUtils.timeSinceMillis(subTimer)) + "ms. Total time was " + (TimeUtils.timeSinceMillis(timer)));
setChanged();
notifyObservers();
}
}

View File

@@ -0,0 +1,70 @@
/**
* Metadata for audio. Not thread-safe.
* @author yunya
*
*/
package zero1hd.rhythmbullet.audio.metadata;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.Disposable;
public interface AudioMetadata extends Disposable {
/**
* Load the album art data in to memory.
* Will not load if already loaded.
*/
public void loadAlbumCover();
/**
* Unloads album art from memory.
* Requires OpenGL context.
*/
public void unloadAlbumCover();
/**
*
* @return the author for the song in the metadata.
*/
public String getAuthor();
/**
*
* @return the title of the song in the metadata, or if it doesn't exist, the filename without extension and _ is given.
*/
public String getTitle();
/**
*
* @return the length of the song with proper fomatting.
*/
public String getDuration();
/**
*
* @return the length of the song in seconds.
*/
public int getLength();
/**
* Requires a OpenGL context.
* @return the texture. Needs to be loaded before hand or else will return null.
*/
public Texture getAlbumCover();
/**
*
* @return returns the genre of the song stored in metadata.
*/
public String getGenre();
/**
*
* @return the filehandle.
*/
public FileHandle getFileHandle();
@Override
public void dispose();
}

View File

@@ -0,0 +1,124 @@
package zero1hd.rhythmbullet.audio.metadata;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.audio.mp3.MP3File;
import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.id3.ID3v23FieldKey;
import org.jaudiotagger.tag.id3.ID3v23Tag;
import org.jaudiotagger.tag.images.Artwork;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
public class MP3Metadata implements AudioMetadata {
private String title, author, duration, genre;
private int length;
private Texture albumCover;
private FileHandle fileHandle;
private Pixmap pixmap;
public MP3Metadata(FileHandle fileHandle) {
this.fileHandle = fileHandle;
try {
MP3File mp3file = (MP3File) AudioFileIO.read(fileHandle.file());
ID3v23Tag tag;
tag = (ID3v23Tag) mp3file.getTagAndConvertOrCreateAndSetDefault();
length = mp3file.getAudioHeader().getTrackLength();
SimpleDateFormat f = new SimpleDateFormat("m:ss");
duration = f.format(new Date(length*1000));
author = tag.getFirst(ID3v23FieldKey.ARTIST);
genre = tag.getFirst(ID3v23FieldKey.GENRE);
title = tag.getFirst(ID3v23FieldKey.TITLE);
if (title.isEmpty()) {
title = fileHandle.nameWithoutExtension().replace('_', ' ');
}
} catch (IOException | CannotReadException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
Gdx.app.error("MP3Metadata", "Failed to read metadata of file: " + fileHandle.name());
}
}
@Override
public void loadAlbumCover() {
if (pixmap == null) {
try {
MP3File mp3file;
mp3file = (MP3File) AudioFileIO.read(fileHandle.file());
Artwork art = mp3file.getTag().getFirstArtwork();
if (art != null) {
byte[] imageData = art.getBinaryData();
pixmap = new Pixmap(imageData, 0, imageData.length);
}
} catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
e.printStackTrace();
}
}
}
@Override
public String getAuthor() {
return author;
}
@Override
public String getTitle() {
return title;
}
@Override
public String getDuration() {
return duration;
}
@Override
public int getLength() {
return length;
}
@Override
public String getGenre() {
return genre;
}
@Override
public void unloadAlbumCover() {
if (albumCover != null) {
albumCover.dispose();
albumCover = null;
}
}
@Override
public Texture getAlbumCover() {
if (pixmap != null && albumCover == null) {
albumCover = new Texture(pixmap);
pixmap.dispose();
pixmap = null;
}
return albumCover;
}
@Override
public FileHandle getFileHandle() {
return fileHandle;
}
@Override
public void dispose() {
unloadAlbumCover();
}
}

View File

@@ -0,0 +1,114 @@
package zero1hd.rhythmbullet.audio.metadata;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.images.Artwork;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
public class WAVMetadata implements AudioMetadata {
private String title, author, duration, genre;
private int length;
private Texture albumCover;
private FileHandle fileHandle;
private Pixmap pixmap;
public WAVMetadata(FileHandle fileHandle) {
this.fileHandle = fileHandle;
try {
AudioFile wav = AudioFileIO.read(fileHandle.file());
length = wav.getAudioHeader().getTrackLength();
SimpleDateFormat f = new SimpleDateFormat("m:ss");
duration = f.format(new Date(length*1000));
Tag tag = wav.getTag();
title = tag.getFirst(FieldKey.TITLE);
author = tag.getFirst(FieldKey.ARTIST);
genre = tag.getFirst(FieldKey.GENRE);
if (title.isEmpty()) {
title = fileHandle.nameWithoutExtension().replace('_', ' ');
}
} catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
Gdx.app.error("WAVMetadata", "Failed to read metadata of file: " + fileHandle.name());
}
}
@Override
public void loadAlbumCover() {
if (pixmap == null) {
try {
AudioFile wav = AudioFileIO.read(fileHandle.file());
Artwork art = wav.getTag().getFirstArtwork();
if (art != null) {
byte[] imageData = art.getBinaryData();
pixmap = new Pixmap(imageData, 0, imageData.length);
}
} catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
e.printStackTrace();
}
}
}
@Override
public void unloadAlbumCover() {
if (albumCover != null) {
albumCover.dispose();
albumCover = null;
}
}
@Override
public String getAuthor() {
return author;
}
@Override
public String getTitle() {
return title;
}
@Override
public String getDuration() {
return duration;
}
@Override
public int getLength() {
return length;
}
@Override
public Texture getAlbumCover() {
if (pixmap != null && albumCover == null) {
albumCover = new Texture(pixmap);
pixmap.dispose();
pixmap = null;
}
return albumCover;
}
@Override
public String getGenre() {
return genre;
}
@Override
public FileHandle getFileHandle() {
return fileHandle;
}
@Override
public void dispose() {
unloadAlbumCover();
}
}

View File

@@ -0,0 +1,44 @@
package zero1hd.rhythmbullet.audio.processor;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Disposable;
public interface AudioProcessor extends Disposable {
/**
* @return number of channels
*/
public boolean isStereo();
/**
* @return sample rate
*/
public int getSampleRate();
/**
* Reads samples with interwoven data for stereo.
* stored in 16 bit format (first 8 are the first byte of data while the second 8 are the second byte of data that composes a short value)
* @param pcm the array the samples should fill
* @return the amount of samples read.
*/
public int readSamples(short[] pcm);
/**
* Reads frames with interwoven data for stereo.
* stored in 16 bit format (first 8 are the first byte of data while the second 8 are the second byte of data that composes a short value)
* @param pcm the array the samples should fill
* @return the amount of samples read.
*/
public int readFrames(float[] pcm);
/**
*
* @return The music file's {@link FileHandle}
*/
public FileHandle getMusicFileHandle();
/**
*
* @return the number of sample frames in the song.
*/
public long getSampleFrames();
}

View File

@@ -0,0 +1,108 @@
package zero1hd.rhythmbullet.audio.processor;
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
public class WAVAudioProcessor implements AudioProcessor {
private boolean stereo;
private int sampleRate;
private byte[] buffer;
private FileHandle fileHandle;
private AudioInputStream audioInputStream;
private long sampleFrames;
public WAVAudioProcessor(FileHandle fileHandle) {
this.fileHandle = fileHandle;
AudioFormat format;
try {
audioInputStream = AudioSystem.getAudioInputStream(fileHandle.file());
format = audioInputStream.getFormat();
stereo = format.getChannels() > 1 ? true : false;
sampleRate = (int) format.getSampleRate();
sampleFrames = audioInputStream.getFrameLength();
} catch (UnsupportedAudioFileException | IOException e) {
Gdx.app.debug("WAVAudioProcessor", "Couldn't instantiate WAVAUdioProcessor due to error.");
e.printStackTrace();
}
buffer = new byte[audioInputStream.getFormat().getFrameSize()];
}
public boolean isStereo() {
return stereo;
}
public int getSampleRate() {
return sampleRate;
}
@Override
public int readSamples(short[] pcm) {
int framesRead = 0;
for (int sampleID = 0; sampleID < pcm.length; sampleID++) {
try {
if (audioInputStream.read(buffer) > 0) {
pcm[sampleID] = (short) ((buffer[1] << 8) + (buffer[0] & 0x00ff));
if (stereo) {
short secondChan = (short) ((buffer[3] << 8) + (buffer[2] & 0x00ff));
sampleID++;
pcm[sampleID] = secondChan;
}
framesRead++;
}
} catch (IOException e) {
e.printStackTrace();
}
}
return framesRead;
}
@Override
public int readFrames(float[] pcm) {
int framesRead = 0;
for (int sampleID = 0; sampleID < pcm.length; sampleID++) {
try {
if (audioInputStream.read(buffer) > 0) {
pcm[sampleID] = (short) ((buffer[1] << 8) + (buffer[0] & 0x00ff));
if (stereo) {
short secondChan = (short) ((buffer[3] << 8) + (buffer[2] & 0x00ff));
pcm[sampleID] = secondChan > pcm[sampleID] ? secondChan : pcm[sampleID];
}
framesRead++;
pcm[sampleID] /= Short.MAX_VALUE+1;
}
} catch (IOException e) {
e.printStackTrace();
}
}
return framesRead;
}
@Override
public FileHandle getMusicFileHandle() {
return fileHandle;
}
@Override
public long getSampleFrames() {
return sampleFrames;
}
@Override
public void dispose() {
try {
audioInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,235 @@
package zero1hd.rhythmbullet.audio.visualizer;
import java.util.Comparator;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import zero1hd.rhythmbullet.RhythmBullet;
public class CircularVisualizer implements Disposable {
private PCMSystem pcm;
private int centerX, centerY;
private int r;
private int componentCount = 2 + 1;
private Array<Vector2> circlePoints;
private float[] vertComponents;
private float color;
private Mesh mesh;
private ShaderProgram shader;
private int barCount = 180;
private float barHeightMultiplier = 1.5f;
private float[] audioSpectrum;
private Camera camera;
public CircularVisualizer(PCMSystem PCMSystem) {
this.pcm = PCMSystem;
shader = new ShaderProgram(Gdx.files.internal("shaders/mesh.vsh"), Gdx.files.internal("shaders/mesh.fsh"));
if (!shader.isCompiled() || shader.getLog().length() != 0) {
Gdx.app.debug("Circular visualizer shader", shader.getLog());
Gdx.app.exit();
}
r = RhythmBullet.pixels_per_unit*RhythmBullet.SPAWN_CIRCLE_RADIUS;
}
/**
* Only should be called when all changes have been done.
*/
public void applyPositionChanges() {
circlePoints = new Array<>();
createCircleVertices(r);
vertComponents = new float[circlePoints.size * componentCount];
if (mesh != null) {
mesh.dispose();
}
mesh = new Mesh(true, circlePoints.size, 0, new VertexAttribute(Usage.Position, 2, "a_position"), new VertexAttribute(Usage.ColorPacked, 4, "a_color"));
}
public void setCenter(int x, int y) {
this.centerX = x;
this.centerY = y;
}
public void drawVisualizer() {
for (int circlePointIndex = 0; circlePointIndex < circlePoints.size; circlePointIndex++) {
for (int comp = 0; comp < componentCount; comp++) {
if (comp != 2) {
vertComponents[circlePointIndex*componentCount + comp] = circlePoints.get(circlePointIndex).x + centerX;
comp++;
vertComponents[circlePointIndex*componentCount + comp] = circlePoints.get(circlePointIndex).y + centerY;
} else {
vertComponents[circlePointIndex*componentCount + comp] = color;
}
}
}
flush();
}
private void flush() {
mesh.setVertices(vertComponents);
Gdx.gl.glDepthMask(false);
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
int vertexCount = circlePoints.size;
((OrthographicCamera) camera).setToOrtho(false, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
shader.begin();
mesh.render(shader, GL20.GL_LINE_STRIP, 0, vertexCount);
shader.end();
Gdx.gl.glDepthMask(true);
}
private void createCircleVertices(int radius) {
int x = radius - 1;
int y = 0;
int dx = 1;
int dy = 1;
int err = dx - (radius << 1);
circlePoints.clear();
while (x >= y) {
circlePoints.add(new Vector2(+ x, + y));
circlePoints.add(new Vector2(+ y, + x));
circlePoints.add(new Vector2(- y, + x));
circlePoints.add(new Vector2(- x, + y));
circlePoints.add(new Vector2(- x, - y));
circlePoints.add(new Vector2(- y, - x));
circlePoints.add(new Vector2(+ y, - x));
circlePoints.add(new Vector2(+ x, - y));
if (err <= 0) {
y++;
err += dy;
dy += 2;
} else {
x--;
dx += 2;
err += dx - (radius << 1);
}
}
circlePoints.sort(new Comparator<Vector2>() {
@Override
public int compare(Vector2 o1, Vector2 o2) {
double deg1;
if (o1.x != 0 || o1.y != 0) {
deg1 = Math.atan(Math.abs(o1.y)/Math.abs(o1.x));
if (o1.x < 0) {
if (o1.y < 0) {
deg1 += 180;
} else {
deg1 = 180 - deg1;
}
} else {
if (o1.y < 0) {
deg1 = 360 - deg1;
}
}
} else {
if (o1.x != 0) {
if (o1.x > 0) {
deg1 = 0;
} else {
deg1 = 180;
}
deg1 = 0;
} else {
if (o1.y > 0) {
deg1 = 90;
} else {
deg1 = 270;
}
}
}
double deg2;
if (o2.x != 0 || o2.y != 0) {
deg2 = Math.atan(Math.abs(o2.y)/Math.abs(o2.x));
if (o2.x < 0) {
if (o2.y < 0) {
deg2 += 180;
} else {
deg2 = 180 - deg2;
}
} else {
if (o2.y < 0) {
deg2 = 360 - deg2;
}
}
} else {
if (o2.x != 0) {
if (o2.x > 0) {
deg2 = 0;
} else {
deg2 = 180;
}
deg2 = 0;
} else {
if (o2.y > 0) {
deg2 = 90;
} else {
deg2 = 270;
}
}
}
if ((deg1 - deg2) > 0) {
return 1;
} else if (deg1 - deg2 == 0) {
return 0;
} else {
return -1;
}
}
});
}
public void setColor(float color) {
this.color = color;
}
@Override
public void dispose() {
mesh.dispose();
shader.dispose();
}
/**
* set the maximum radius
* @param r
*/
public void setR(int r) {
this.r = r;
}
/**
* get the maximum radius
* @return
*/
public int getR() {
return r;
}
public void setCamera(Camera camera) {
this.camera = camera;
}
public Camera getCamera() {
return camera;
}
}

View File

@@ -0,0 +1,212 @@
package zero1hd.rhythmbullet.audio.visualizer;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Disposable;
import zero1hd.rhythmbullet.audio.MusicController;
public class DoubleHorizontalVisualizer implements Disposable {
private int width, height, barWidth, spaceBetweenBars;
private int x, y;
private ShapeRenderer shapeRenderer;
private PCMSystem pcm;
private float[] amplitudes;
private int[] barHeights;
private int binsPerBar;
private float offset;
private int boundaryThickness;
private boolean debug = false;
private boolean significantBeat;
private float maxAverageAmplitude;
private byte significantFrames;
private byte requiredSignificantFrames;
private float targetDelta;
private float significantThreshold = 0.5f;
private float spacePercentage = 0.7f;
private float baseSensitivity = 0.009f;
private int barCount = 120;
private float barChangeRate = 6.5f;
private int smoothRange = 2;
private int binsToInclude = 120;
private Color color = new Color(0.5f, 0.6f, 0.8f, 0.46f);
private int averageAmplitude;
/**
*
* @param barCount amount of bars this visualizer should have.
* @param width the width of the visualizer.
* @param spacePercentage the percentage of a bar that should be space.
*/
public DoubleHorizontalVisualizer(int width, int height, float heightSensitivity, int boundaryThickness, int targetFPS, MusicController musicController, PCMSystem PCMSystem) {
this.barWidth = width/barCount;
this.spaceBetweenBars = MathUtils.round(barWidth * spacePercentage);
this.barWidth -= spaceBetweenBars;
this.baseSensitivity *= heightSensitivity;
pcm = PCMSystem;
binsPerBar = (binsToInclude/barCount);
this.width = width;
this.height = height;
amplitudes = new float[barCount];
barHeights = new int[barCount];
shapeRenderer = new ShapeRenderer();
boundaryThickness = barWidth;
offset = (width - (barCount*(barWidth+spaceBetweenBars)-spaceBetweenBars))/2f + x;
this.targetDelta = 1f/targetFPS;
}
public void act(float delta) {
if (pcm.hasAudioChanged()) {
maxAverageAmplitude = 0;
averageAmplitude = 0;
significantBeat = false;
}
float[] freqBins = pcm.getFrequencyBins();
averageAmplitude = 0;
for (int bar = 0; bar < amplitudes.length; bar++) {
amplitudes[bar] = 0;
for (int freq = bar*binsPerBar; freq < (bar*binsPerBar) + binsPerBar; freq++) {
amplitudes[bar] += Math.abs(freqBins[freq]) * baseSensitivity;
}
amplitudes[bar] /= binsPerBar;
averageAmplitude += amplitudes[bar];
}
averageAmplitude /= amplitudes.length;
if (averageAmplitude > maxAverageAmplitude) {
maxAverageAmplitude = averageAmplitude;
}
if (averageAmplitude > maxAverageAmplitude*significantThreshold && !significantBeat) {
if (maxAverageAmplitude > 0) {
significantFrames++;
if (significantFrames >= requiredSignificantFrames) {
significantFrames = 0;
significantBeat = true;
}
}
} else {
if (significantBeat) {
significantBeat = false;
requiredSignificantFrames = 16;
} else {
requiredSignificantFrames = 4;
}
significantFrames = 0;
}
for (int bar = 0; bar < barHeights.length; bar++) {
int smoothCount = 1;
for (int range = 0; range < smoothRange; range++) {
if (bar+range < amplitudes.length) {
amplitudes[bar] += amplitudes[bar+range];
smoothCount++;
}
if (bar-range > 0) {
amplitudes[bar] += amplitudes[bar-range];
smoothCount++;
}
}
amplitudes[bar] /= smoothCount;
int pixelsMoved = 0;
int difference = MathUtils.round(amplitudes[bar] - barHeights[bar]);
pixelsMoved = MathUtils.floor(difference*targetDelta*barChangeRate);
if (pixelsMoved >= 0) {
if (barHeights[bar] + pixelsMoved > amplitudes[bar]) {
barHeights[bar] += MathUtils.round(difference*targetDelta);
} else {
barHeights[bar] += pixelsMoved;
}
} else {
if (barHeights[bar] + pixelsMoved < amplitudes[bar]) {
barHeights[bar] += MathUtils.round(difference*targetDelta);
} else {
barHeights[bar] += pixelsMoved;
}
}
}
}
public void draw(Batch batch, float parentAlpha) {
batch.end();
Gdx.gl.glEnable(GL20.GL_BLEND);
shapeRenderer.begin(ShapeType.Filled);
shapeRenderer.setProjectionMatrix(batch.getProjectionMatrix());
shapeRenderer.setTransformMatrix(batch.getTransformMatrix());
if (boundaryThickness > 0) {
shapeRenderer.rect(x, y, width, boundaryThickness);
shapeRenderer.rect(x, y+height, width, -boundaryThickness);
}
for (int bar = 0; bar < barCount; bar++) {
shapeRenderer.setColor(color);
shapeRenderer.rect(offset + (spaceBetweenBars+barWidth)*bar, y+height, barWidth, barHeights[bar]);
shapeRenderer.rect(offset + (spaceBetweenBars+barWidth)*bar, y, barWidth, -barHeights[barHeights.length - 1 - bar]);
}
if (debug) {
shapeRenderer.setColor(Color.RED);
shapeRenderer.rect(0, maxAverageAmplitude+y, width, 1);
if (significantBeat) {
shapeRenderer.setColor(Color.GREEN);
} else {
shapeRenderer.setColor(Color.WHITE);
}
shapeRenderer.rect(0, averageAmplitude+y, width, 1);
shapeRenderer.setColor(Color.YELLOW);
shapeRenderer.rect(0, maxAverageAmplitude*significantThreshold + y, width, 1);
}
shapeRenderer.end();
Gdx.gl.glDisable(GL20.GL_BLEND);
batch.begin();
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setPosition(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public boolean isSignificantBeat() {
return significantBeat;
}
public void updateMusic() {
pcm.loadMusic();
}
@Override
public void dispose() {
pcm.dispose();
}
}

View File

@@ -0,0 +1,14 @@
package zero1hd.rhythmbullet.audio.visualizer;
import com.badlogic.gdx.utils.Disposable;
public interface PCMSystem extends Disposable {
float[] getFrequencyBins();
int getWindowSize();
void loadMusic();
boolean hasAudioChanged();
}

View File

@@ -0,0 +1,68 @@
package zero1hd.rhythmbullet.audio.wavedecoder;
import java.io.IOException;
import javax.sound.sampled.AudioInputStream;
public class WAVSampleReader {
private int channels;
private double sampleRate;
private byte[] buffer;
private AudioInputStream audioInputStream;
private boolean mergeChannels;
public WAVSampleReader(AudioInputStream ais) throws IOException {
audioInputStream = ais;
buffer = new byte[audioInputStream.getFormat().getFrameSize()];
channels = audioInputStream.getFormat().getChannels();
sampleRate = audioInputStream.getFormat().getSampleRate();
}
public int getChannels() {
return channels;
}
public double getSampleRate() {
return sampleRate;
}
public long getFrameLength() {
return audioInputStream.getFrameLength();
}
public int readSamplesAsFrames(float[] samples) throws IOException {
int framesRead = 0;
for (int sampleID = 0; sampleID < samples.length; sampleID++) {
if (audioInputStream.read(buffer) > 0) {
samples[sampleID] += (buffer[1] << 8) + (buffer[0] & 0x00ff);
if (audioInputStream.getFormat().getChannels() > 1) {
short altChan = (short) ((buffer[3] << 8) + (buffer[2] & 0x00ff));
if (mergeChannels) {
samples[sampleID] = altChan > samples[sampleID] ? altChan : samples[sampleID];
} else {
sampleID++;
samples[sampleID] = altChan;
}
}
framesRead ++;
samples[sampleID] /= Short.MAX_VALUE+1;
}
}
return framesRead;
}
public AudioInputStream getAudioInputStream() {
return audioInputStream;
}
public void setMergeChannels(boolean mergeChannels) {
this.mergeChannels = mergeChannels;
}
public boolean isMergeChannels() {
return mergeChannels;
}
}

View File

@@ -0,0 +1,56 @@
package zero1hd.rhythmbullet.entity;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.utils.Array;
public class CollisionDetector {
Array<Entity> enemies;
Array<Entity> allies;
AssetManager assets;
Preferences prefs;
private int amassedPoints;
Sound explosionSFX;
public CollisionDetector(Array<Entity> enemies, Array<Entity> allies, AssetManager assetManager, Preferences prefs) {
this.enemies = enemies;
this.allies = allies;
assets = assetManager;
this.prefs = prefs;
}
public void collisionCheck() {
if ((enemies.size != 0) && (allies.size != 0)) {
for (int f = 0; f < enemies.size; f++) {
Entity enemy = enemies.get(f);
if (enemy.getHitZone() != null) {
for (int s = 0; s < allies.size; s++) {
Entity ally = allies.get(s);
if (ally.getHitZone() != null) {
if (enemy.getHitZone().overlaps(ally.getHitZone())) {
Gdx.app.debug("Collision Detector", "Collision between entities: " + enemy.getClass().getSimpleName() + " and " + ally.getClass().getSimpleName());
enemy.collided(ally);
ally.collided(enemy);
amassedPoints += enemy.getPoints();
break;
}
}
}
}
}
}
}
public int getAmassedPoints() {
int amassedPoints = this.amassedPoints;
this.amassedPoints = 0;
return amassedPoints;
}
}

View File

@@ -0,0 +1,201 @@
package zero1hd.rhythmbullet.entity;
import java.security.InvalidParameterException;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Pool.Poolable;
import zero1hd.rhythmbullet.RhythmBullet;
import zero1hd.rhythmbullet.entity.coordinator.Coordinator;
public class Entity implements Poolable {
private Coordinator coordinator;
private EntityFrame<?> ef;
protected AssetManager assets;
protected Preferences prefs;
protected EntityManager ec;
protected boolean enemy;
protected boolean move = true;
protected boolean dead;
protected Rectangle hitbox;
protected Sprite sprite;
protected Vector2 rotRatios;
public float angle;
public float speed;
protected float hitBoxScale;
protected int points;
/**
* called by the entity frame and only once (when this object is created).
* Used by the frame to setup variables for simple calling later.
* Anything that needs to be setup for the entity on first call should not override this.
* (Unless you call the super of this and then the rest which is fine too).
* This will then call preInit() which is the method you should override.
*/
protected void setup(EntityManager ec, EntityFrame<?> ef) {
this.ec = ec;
this.ef = ef;
assets = ec.getAssets();
prefs = ec.getPrefs();
rotRatios = new Vector2();
hitbox = new Rectangle();
preInit();
}
/**
* Method to override should any setup need to be done once (on entity first creation).
*/
public void preInit() {
if (sprite.getTexture() == null) throw new NullPointerException("what, your not going to have a texture for your entity?");
sprite.setOriginCenter();
}
public void init(float deg, float speed, int hp) {
rotRatios.set(MathUtils.cosDeg(angle), MathUtils.sinDeg(angle));
sprite.setSize(sprite.getTexture().getWidth()/RhythmBullet.pixels_per_unit, sprite.getTexture().getHeight()/RhythmBullet.pixels_per_unit);
float r = RhythmBullet.SPAWN_CIRCLE_RADIUS - sprite.getWidth();
if (r < RhythmBullet.SPAWN_CIRCLE_RADIUS -1) {
throw new InvalidParameterException("Entity is too big! Calculated final distance from center: " + r);
}
sprite.setPosition(r*rotRatios.x, r*rotRatios.y);
updatePosition();
}
/**
* Called whenever a collision is detected
* @param entity is the entity that hit this one.
*/
public void collided(Entity entity) {
}
/**
* gets the box that represents the hit box to calculate whether there is a collision or not
* @return the object that represents the hit box
*/
public Rectangle getHitZone() {
return hitbox;
}
public boolean isDead() {
return dead;
}
public void calculate(float delta) {
if (coordinator != null) {
coordinator.coordinate(delta);
}
if (dead) {
ef.recycleEntity(this);
}
if (angle >= 360f) {
angle -= 360f;
}
if (sprite.getX() > RhythmBullet.WORLD_WIDTH || sprite.getY() > RhythmBullet.WORLD_HEIGHT || sprite.getX() < 0-sprite.getWidth() || sprite.getX() < 0-sprite.getWidth()) {
dead = true;
}
updatePosition();
}
public void draw(Batch batch) {
sprite.draw(batch);
batch.setColor(Color.WHITE);
}
public void moveBy(float x, float y) {
sprite.setCenter(sprite.getX() + x, sprite.getY() + y);
}
public void updatePosition() {
rotRatios.set(MathUtils.cosDeg(angle), MathUtils.sinDeg(angle));
sprite.setOriginCenter();
sprite.setRotation(angle);
hitbox.setCenter(sprite.getOriginX(), sprite.getOriginY());
}
@Override
public void reset() {
if (coordinator != null) {
coordinator.clean();
coordinator = null;
}
rotRatios.set(0, 0);
sprite.setPosition(0, 0);
hitbox.set(0, 0, 0, 0);
sprite.setRotation(0);
sprite.setColor(Color.WHITE);
angle = 0;
speed = 0;
points = 0;
dead = false;
}
public float getAngle() {
return angle;
}
public void setSpeed(float speed) {
this.speed = speed;
}
public float getSpeed() {
return speed;
}
public void setCoordinator(Coordinator coordinator) {
this.coordinator = coordinator;
coordinator.setEntity(this);
}
public void kill() {
dead = true;
}
public int getPoints() {
return points + (coordinator != null ? coordinator.getScoreBonus() : 0);
}
public float getX() {
return sprite.getX();
}
public float getY() {
return sprite.getY();
}
public void setPosition(float x, float y) {
sprite.setPosition(x, y);
updatePosition();
}
public float getWidth() {
return sprite.getWidth();
}
public float getHeight() {
return sprite.getHeight();
}
public void setSize(float width, float height) {
sprite.setSize(width, height);
}
public void setHitboxScale(float scale) {
hitbox.setSize(sprite.getWidth()*scale, sprite.getHeight()*scale);
}
}

View File

@@ -0,0 +1,66 @@
package zero1hd.rhythmbullet.entity;
import com.badlogic.gdx.utils.Pool;
public class EntityFrame<T extends Entity> {
private Pool<T> pool;
private EntityManager ec;
Class<T> ct;
EntityFrame<T> ef;
/**
* Manages the entities pooling.
* @param entityController
* @param classType
*/
public EntityFrame(EntityManager entityController, Class<T> classType) {
this.ct = classType;
ef = this;
ec = entityController;
pool = new Pool<T>() {
@Override
protected T newObject() {
try {
T entity = ct.newInstance();
entity.setup(ec, ef);
return entity;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
};
}
public T buildEntity() {
T entity = pool.obtain();
if (entity.enemy) {
ec.activeEnemies.add(entity);
} else {
ec.activeAllies.add(entity);
}
return entity;
}
/**
* Free the entity if no longer used.
* @param entity to be freed.
*/
protected void recycleEntity(Entity entity) {
if (entity.enemy) {
ec.activeEnemies.removeValue(entity, true);
} else {
ec.activeAllies.removeValue(entity, true);
}
pool.free(ct.cast(entity));
}
@Override
public String toString() {
return ct.getSimpleName();
}
}

View File

@@ -0,0 +1,46 @@
package zero1hd.rhythmbullet.entity;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.utils.Array;
import zero1hd.rhythmbullet.entity.ally.Laser;
import zero1hd.rhythmbullet.entity.enemies.Pellet;
import zero1hd.rhythmbullet.entity.enemies.Shard;
public class EntityManager {
private AssetManager assets;
private Preferences prefs;
public Array<Entity> activeAllies;
public Array<Entity> activeEnemies;
public EntityFrame<Pellet> pellet;
public EntityFrame<Shard> shard;
public EntityFrame<Laser> laser;
public EntityManager(AssetManager assetManager, Preferences preferences) {
activeAllies = new Array<Entity>();
activeEnemies = new Array<Entity>();
this.assets = assetManager;
this.prefs = preferences;
setup();
}
private void setup() {
pellet = new EntityFrame<>(this, Pellet.class);
shard = new EntityFrame<>(this, Shard.class);
laser = new EntityFrame<>(this, Laser.class);
}
public AssetManager getAssets() {
return assets;
}
public Preferences getPrefs() {
return prefs;
}
}

View File

@@ -0,0 +1,52 @@
package zero1hd.rhythmbullet.entity.ally;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.Sprite;
import zero1hd.rhythmbullet.RhythmBullet;
import zero1hd.rhythmbullet.entity.Entity;
public class Laser extends Entity {
Sound sfx;
@Override
public void preInit() {
sprite = new Sprite(assets.get("laser.png", Texture.class));
sfx = assets.get("laser.ogg", Sound.class);
setSize(0.25f, 2f);
super.preInit();
}
public void init(float x, float y, float rate) {
setPosition(x-getWidth()/2f, y-getHeight()/2f);
speed = rate;
sfx.play(prefs.getFloat("fx vol")/100f);
angle = 90f;
}
@Override
public void calculate(float delta) {
if (getY() > RhythmBullet.WORLD_HEIGHT) {
dead = true;
}
super.calculate(delta);
}
@Override
public void draw(Batch batch) {
super.draw(batch);
}
@Override
public void reset() {
super.reset();
}
@Override
public void collided(Entity entity) {
dead = true;
}
}

View File

@@ -0,0 +1,95 @@
package zero1hd.rhythmbullet.entity.ally;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.ParticleEffect;
import com.badlogic.gdx.math.Rectangle;
import zero1hd.rhythmbullet.RhythmBullet;
import zero1hd.rhythmbullet.entity.Entity;
public class PolyjetEntity extends Entity {
public float health;
private ParticleEffect thrust;
private Texture polyjet;
private ParticleEffect teleportCloak;
public boolean moveLeft, moveRight, moveUp, moveDown, teleporting, accelerate;
private float speed, accel;
private float rate;
private int maxH;
public PolyjetEntity(AssetManager assets, float speed, float accel,int maxHealth, String jet) {
health = 100;
this.speed = speed;
this.accel = accel;
setSize(1.5f, 1.5f);
setPosition(RhythmBullet.WORLD_WIDTH/2 - getWidth()/2, -4f);
maxH = maxHealth;
hitbox = new Rectangle(getX(), getY(), getWidth(), getHeight());
polyjet = assets.get("polyjet-" + jet + ".png", Texture.class);
thrust = assets.get("standard_thrust.p", ParticleEffect.class);
thrust.start();
teleportCloak = assets.get("teleport-cloak.p", ParticleEffect.class);
}
@Override
public void calculate(float delta) {
hitbox.setPosition(getX(), getY());
thrust.setPosition(getX()+(getWidth())/2 - 1f/16f, getY()-0.2f);
thrust.update(delta);
teleportCloak.setPosition(getX() +(getWidth()-1)/2, getY() + (getHeight()-1)/2);
hitbox.setPosition(getX(), getY());
//Movement!
if (accelerate) {
rate = speed + accel;
} else {
rate = speed;
}
if (moveLeft && !moveRight) {
moveBy(-(rate*delta), 0);
}
if (moveRight && !moveLeft) {
moveBy(rate*delta, 0);
}
if (moveUp && !moveDown) {
moveBy(0, rate*delta);
}
if (moveDown && !moveUp) {
moveBy(0, -rate*delta);
}
if (health <= 0) {
dead = true;
} else if (health > maxH) {
health = maxH;
}
super.calculate(delta);
}
@Override
public void draw(Batch batch) {
thrust.draw(batch);
batch.draw(polyjet, getX(), getY(), getWidth(), getHeight());
super.draw(batch);
}
@Override
public void collided(Entity entity) {
}
public Rectangle getHitbox() {
return hitbox;
}
public void setHealth(float health) {
this.health = health;
}
}

View File

@@ -0,0 +1,47 @@
package zero1hd.rhythmbullet.entity.coordinator;
import com.badlogic.gdx.utils.Pool.Poolable;
import zero1hd.rhythmbullet.entity.Entity;
import zero1hd.rhythmbullet.entity.EntityManager;
/**
* Coordinator coordinates movement of an entity. Movement can range from basic pre-determined to more advanced (with condition based behavior).
* @author Yunyang
*
*/
public class Coordinator implements Poolable {
private CoordinatorFrame<? extends Coordinator> cf;
protected EntityManager em;
protected Entity entity;
protected int scoreBonus;
public void setup(EntityManager em, CoordinatorFrame<? extends Coordinator> cf) {
this.em = em;
this.cf = cf;
}
public void init(Entity entity) {
this.entity = entity;
}
public void coordinate(float delta) {
}
public void clean() {
cf.recycleCoordinator(this);
}
public void setEntity(Entity entity) {
this.entity = entity;
}
@Override
public void reset() {
entity = null;
}
public int getScoreBonus() {
return scoreBonus;
}
}

View File

@@ -0,0 +1,50 @@
package zero1hd.rhythmbullet.entity.coordinator;
import com.badlogic.gdx.utils.Pool;
import zero1hd.rhythmbullet.entity.EntityManager;
public class CoordinatorFrame<T extends Coordinator> {
private Pool<T> pool;
private EntityManager em;
CoordinatorFrame<T> cf;
Class<T> coordinatorType;
/**
* Similar to an entityFrame, however this time, for a coordinator.
* @param entityManager
* @param classtype
*/
public CoordinatorFrame(EntityManager entityManager, Class<T> classtype) {
this.em = entityManager;
coordinatorType = classtype;
cf = this;
pool = new Pool<T>() {
@Override
protected T newObject() {
try {
T coordinator = coordinatorType.newInstance();
coordinator.setup(em, cf);
return coordinator;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
};
}
public T buildCoordinator() {
T coordinator = pool.obtain();
return coordinator;
}
protected void recycleCoordinator(Coordinator coordinator) {
pool.free(coordinatorType.cast(coordinator));
}
@Override
public String toString() {
return coordinatorType.getSimpleName();
}
}

View File

@@ -0,0 +1,20 @@
package zero1hd.rhythmbullet.entity.coordinator;
import zero1hd.rhythmbullet.entity.EntityManager;
public class CoordinatorManager {
private EntityManager em;
public CoordinatorFrame<SlowLeftCoordinator> slowLeft;
public CoordinatorFrame<SlowRightCoordinator> slowRight;
public CoordinatorManager(EntityManager em) {
this.em = em;
setup();
}
private void setup() {
slowLeft = new CoordinatorFrame<>(em, SlowLeftCoordinator.class);
slowRight = new CoordinatorFrame<>(em, SlowRightCoordinator.class);
}
}

View File

@@ -0,0 +1,9 @@
package zero1hd.rhythmbullet.entity.coordinator;
public class SlowLeftCoordinator extends Coordinator {
@Override
public void coordinate(float delta) {
entity.angle -= 32*delta;
super.coordinate(delta);
}
}

View File

@@ -0,0 +1,9 @@
package zero1hd.rhythmbullet.entity.coordinator;
public class SlowRightCoordinator extends Coordinator {
@Override
public void coordinate(float delta) {
entity.angle += 32*delta;
super.coordinate(delta);
}
}

View File

@@ -0,0 +1,40 @@
package zero1hd.rhythmbullet.entity.enemies;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.utils.Pool.Poolable;
import zero1hd.rhythmbullet.entity.Entity;
public class Pellet extends Entity implements Poolable {
@Override
public void preInit() {
sprite = new Sprite(assets.get("pellet.png", Texture.class));
enemy = true;
setSize(0.5f, 0.5f);
sprite.setColor(0.5f, 1f, 1f, 0.5f);
super.preInit();
}
@Override
public void init(float deg, float speed, int hp) {
this.speed = speed;
this.angle = deg;
super.init(deg, speed, hp);
}
@Override
public void collided(Entity entity) {
dead = true;
super.collided(entity);
}
@Override
public void reset() {
dead = false;
super.reset();
}
}

View File

@@ -0,0 +1,68 @@
package zero1hd.rhythmbullet.entity.enemies;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.math.Rectangle;
import zero1hd.rhythmbullet.entity.Entity;
import zero1hd.rhythmbullet.entity.ally.Laser;
public class Shard extends Entity {
private int hp;
private int maxHp;
@Override
public void preInit() {
sprite = new Sprite(assets.get("shard.png", Texture.class));
setSize(2f, 2f);
sprite.setSize(3f, 2f);
enemy = true;
super.preInit();
}
@Override
public void init(float deg, float speed, int hp) {
this.speed = speed;
this.hp = hp;
maxHp = hp;
this.angle = deg;
super.init(deg, speed, hp);
}
@Override
public void reset() {
hp = 0;
maxHp = 0;
super.reset();
}
@Override
public void calculate(float delta) {
if (hp <= 0) {
dead = true;
}
super.calculate(delta);
}
@Override
public void draw(Batch batch) {
sprite.setColor(((float)hp/(float)maxHp), ((float)hp/(float)maxHp), ((float)hp/(float)maxHp), 0.5f);
super.draw(batch);
}
@Override
public void collided(Entity entity) {
if (entity.getClass() == Laser.class) {
hp --;
} else {
dead = true;
}
}
@Override
public Rectangle getHitZone() {
return hitbox;
}
}

View File

@@ -0,0 +1,28 @@
package zero1hd.rhythmbullet.game;
import java.util.HashMap;
import zero1hd.rhythmbullet.entity.Entity;
import zero1hd.rhythmbullet.entity.EntityFrame;
import zero1hd.rhythmbullet.entity.coordinator.Coordinator;
import zero1hd.rhythmbullet.entity.coordinator.CoordinatorFrame;
public class EntitySpawnInfo {
private EntityFrame<? extends Entity> entityToSpawn;
private CoordinatorFrame<? extends Coordinator> entityCoordinator;
public HashMap<String, Float> parameters;
public EntitySpawnInfo(EntityFrame<? extends Entity> entityToSpawn, CoordinatorFrame<? extends Coordinator> coordinator) {
this.entityToSpawn = entityToSpawn;
parameters = new HashMap<>();
}
public EntityFrame<? extends Entity> getEntityToSpawn() {
return entityToSpawn;
}
public CoordinatorFrame<? extends Coordinator> getEntityCoordinator() {
return entityCoordinator;
}
}

View File

@@ -0,0 +1,18 @@
package zero1hd.rhythmbullet.game;
import com.badlogic.gdx.utils.Array;
public class MapWindowData {
Array<EntitySpawnInfo> entityDatas;
public MapWindowData() {
entityDatas = new Array<>(EntitySpawnInfo.class);
}
public void addEntity(EntitySpawnInfo entity) {
entityDatas.add(entity);
}
public EntitySpawnInfo[] getArray() {
return entityDatas.toArray();
}
}

View File

@@ -0,0 +1,26 @@
package zero1hd.rhythmbullet.game;
public class ScoreManager {
private int score;
private boolean different;
public void setScore(int score) {
this.score = score;
different = true;
}
public int getScore() {
return score;
}
public void addScore(int addedScore) {
score += addedScore;
different = true;
}
public boolean checkDifferent() {
boolean current = different;
different = false;
return current;
}
}

View File

@@ -0,0 +1,153 @@
package zero1hd.rhythmbullet.graphics.shaders;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
public class BloomShader implements Disposable {
private ShaderProgram gaussianBlurShader;
private ShaderProgram brightFilterShader;
private ShaderProgram combineShader;
private FrameBuffer lightFilterBuffer;
private FrameBuffer normalBuffer;
private FrameBuffer hBlur, vBlur;
private TextureRegion fboRegion;
private Batch screenBatch;
private ScreenViewport screenViewport;
public BloomShader(Batch batch) {
this.screenBatch = batch;
Gdx.app.debug("Shader", "Loading glow shaders.");
screenViewport = new ScreenViewport();
screenViewport.update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
((OrthographicCamera) screenViewport.getCamera()).setToOrtho(false, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.app.debug("Shader", "using glow shader");
brightFilterShader = new ShaderProgram(Gdx.files.internal("shaders/basic.vsh"), Gdx.files.internal("shaders/bright_filter.fsh"));
if (!brightFilterShader.isCompiled()) {
Gdx.app.error("Shader failed to compile bright filter shader", brightFilterShader.getLog());
System.exit(1);
}
if (brightFilterShader.getLog().length() != 0) {
Gdx.app.error("Shader", brightFilterShader.getLog());
}
gaussianBlurShader = new ShaderProgram(Gdx.files.internal("shaders/basic.vsh"), Gdx.files.internal("shaders/gaussian_blur.fsh"));
if (!gaussianBlurShader.isCompiled()) {
Gdx.app.error("Shader failed to compile gaussian blur shader", gaussianBlurShader.getLog());
System.exit(1);
}
if (gaussianBlurShader.getLog().length() != 0) {
Gdx.app.error("Shader", gaussianBlurShader.getLog());
}
combineShader = new ShaderProgram(Gdx.files.internal("shaders/basic.vsh"), Gdx.files.internal("shaders/combine.fsh"));
if (!combineShader.isCompiled()) {
Gdx.app.error("Shader failed to compile combination shader", combineShader.getLog());
System.exit(1);
}
if (combineShader.getLog().length() != 0) {
Gdx.app.error("Shader", combineShader.getLog());
}
lightFilterBuffer = new FrameBuffer(Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
normalBuffer = new FrameBuffer(Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
hBlur = new FrameBuffer(Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
vBlur = new FrameBuffer(Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
fboRegion = new TextureRegion(normalBuffer.getColorBufferTexture());
fboRegion.flip(false, true);
combineShader.begin();
combineShader.setUniformi("u_texture1", 1);
combineShader.end();
gaussianBlurShader.begin();
gaussianBlurShader.setUniformf("radius", 1.5f);
gaussianBlurShader.setUniformf("resolution", hBlur.getWidth(), vBlur.getHeight());
gaussianBlurShader.end();
vBlur.getColorBufferTexture().bind(1);
lightFilterBuffer.getColorBufferTexture().bind(2);
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0);
}
public void begin() {
// Begin drawing a normal version of screen
normalBuffer.begin();
Gdx.gl.glClearColor(0f, 0f, 0f, 1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
public void end(float width, float height) {
normalBuffer.end();
// BEGINNING NORMAL SCREEN RENDER
screenViewport.apply();
// Begin light filtering
lightFilterBuffer.begin();
Gdx.gl.glClearColor(0f, 0f, 0f, 1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
fboRegion.setTexture(normalBuffer.getColorBufferTexture());
screenBatch.setShader(brightFilterShader);
screenBatch.setProjectionMatrix(screenViewport.getCamera().combined);
screenBatch.begin(); //SCREEN BATCH STARTS HERE
screenBatch.draw(fboRegion, 0, 0, width, height);
screenBatch.flush();
lightFilterBuffer.end();
// Horizontal gaussian blur
fboRegion.setTexture(lightFilterBuffer.getColorBufferTexture());
hBlur.begin();
screenBatch.setShader(gaussianBlurShader);
gaussianBlurShader.setUniformi("pass", 0);
screenBatch.draw(fboRegion, 0f, 0f, width, height);
screenBatch.flush();
hBlur.end();
// //Vertical gaussian blur
fboRegion.setTexture(hBlur.getColorBufferTexture());
vBlur.begin();
gaussianBlurShader.setUniformi("pass", 1);
screenBatch.draw(fboRegion, 0f, 0f, width, height);
screenBatch.flush();
vBlur.end();
Gdx.gl.glClearColor(0f, 0f, 0f, 1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
screenBatch.setShader(combineShader);
fboRegion.setTexture(normalBuffer.getColorBufferTexture());
screenBatch.draw(fboRegion, 0f, 0f, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
screenBatch.setShader(null);
screenBatch.end(); //STAGE BATCH ENDS HERE
}
@Override
public void dispose() {
brightFilterShader.dispose();
combineShader.dispose();
gaussianBlurShader.dispose();
normalBuffer.dispose();
lightFilterBuffer.dispose();
vBlur.dispose();
hBlur.dispose();
brightFilterShader = null;
combineShader = null;
gaussianBlurShader = null;
normalBuffer = null;
lightFilterBuffer = null;
vBlur = null;
hBlur = null;
}
}

View File

@@ -0,0 +1,74 @@
package zero1hd.rhythmbullet.graphics.ui;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
public class Page extends Group implements Disposable {
private Label pageTitle;
private int baseXPos, baseYPos;
public Page(int baseXPos, int baseYPos) {
this.baseXPos = baseXPos;
this.baseYPos = baseYPos;
setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
setTouchable(Touchable.childrenOnly);
setToBasePosition();
setName(getClass().getSimpleName());
}
public Page(int baseXPos, int baseYPos, String titleText, Skin skin) {
this(baseXPos, baseYPos);
pageTitle = new Label(titleText, skin, "large-font", skin.getColor("default"));
pageTitle.setPosition(18f, getHeight()-pageTitle.getHeight()-15f);
addActor(pageTitle);
}
public float getHeightBelowTitle() {
return pageTitle.getY();
}
@Override
public void setStage(Stage stage) {
if (stage == null) {
if (!hasParent()) {
dispose();
}
} else if (!(stage.getViewport() instanceof ScreenViewport)) {
throw new IllegalArgumentException("Pages are explicitly for GUIs, and thus should have a 1:1 ratio between pixel and texture size for maximum clarity. This means that the stage should be using a ScreenViewport.");
}
super.setStage(stage);
}
public void setCameraPositionToPage(Vector3 cameraPosition) {
cameraPosition.x = (baseXPos+0.5f) * getWidth();
cameraPosition.y = (baseYPos+0.5f) * getHeight();
}
public void setToBasePosition() {
setPosition(baseXPos*getWidth(), baseYPos*getHeight());
}
@Override
public void setParent(Group parent) {
if (parent == null && getStage() == null) {
dispose();
}
super.setParent(parent);
}
@Override
public void dispose() {
Gdx.app.debug(getClass().getSimpleName(), "Disposing...");
}
public void simpleDebug(String message) {
Gdx.app.debug(getClass().getSimpleName(), message);
}
}

View File

@@ -0,0 +1,71 @@
package zero1hd.rhythmbullet.graphics.ui.components;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup;
import zero1hd.rhythmbullet.entity.ally.PolyjetEntity;
public class HealthBar extends WidgetGroup {
Image empty;
Image filler;
float health;
float maxHealth;
PolyjetEntity pje;
public HealthBar(Skin skin, float maxHealth) {
super();
filler = new Image(skin.getPatch("bar-fill"));
addActor(filler);
empty = new Image(skin.getPatch("bar-empty"));
addActor(empty);
this.maxHealth = maxHealth;
}
public void setPolyjetEntity(PolyjetEntity pje) {
this.pje = pje;
}
public void setHealth(float health) {
this.health = health;
filler.addAction(Actions.sizeTo(getWidth(), MathUtils.round((health/maxHealth)*getHeight()), 0.1f));;
}
@Override
public void act(float delta) {
if (pje != null) {
health = pje.health;
}
super.act(delta);
}
@Override
public void setSize(float width, float height) {
empty.setSize(width, height);
filler.setSize(width, height);
super.setSize(width, height);
}
@Override
public void setWidth(float width) {
empty.setWidth(width);
filler.setWidth(width);
super.setWidth(width);
}
@Override
public void setHeight(float height) {
empty.setHeight(height);
super.setHeight(height);
}
public float getMaxHealth() {
return maxHealth;
}
}

View File

@@ -0,0 +1,81 @@
package zero1hd.rhythmbullet.graphics.ui.components;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox;
import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup;
import com.badlogic.gdx.scenes.scene2d.ui.ImageButton;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import zero1hd.rhythmbullet.audio.MusicController;
public class MusicControls extends HorizontalGroup {
private ImageButton reverse, forward;
private CheckBox shuffle, play;
public MusicControls(Skin skin, final MusicController sc) {
reverse = new ImageButton(skin, "rewind-button");
reverse.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
sc.previous();
}
});
addActor(reverse);
play = new CheckBox(null, skin, "play-button") {
@Override
public void act(float delta) {
if (sc.hasSongLoaded()) {
play.setChecked(sc.isPlaying());
} else {
play.setChecked(false);
}
super.act(delta);
}
};
play.addListener(new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
if (play.isChecked()) {
sc.play();
} else {
sc.pause();
}
super.clicked(event, x, y);
}
});
addActor(play);
forward = new ImageButton(skin, "fast-forward-button");
forward.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
sc.skip();
}
});
addActor(forward);
shuffle = new CheckBox(null, skin, "shuffle-button") {
@Override
public void act(float delta) {
shuffle.setChecked(sc.isShuffle());
super.act(delta);
}
};
shuffle.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
if (shuffle.isChecked()) {
sc.setShuffle(true);
} else {
sc.setShuffle(false);
}
}
});
addActor(shuffle);
space(15);
setSize(getMinWidth(), getMinHeight());
}
}

View File

@@ -0,0 +1,205 @@
package zero1hd.rhythmbullet.graphics.ui.components;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.graphics.g2d.NinePatch;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Widget;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
public class ScrollingText extends Widget {
Rectangle scissors = new Rectangle();
Rectangle clipBounds = new Rectangle();
GlyphLayout gLayout;
String text1;
String text2;
BitmapFont font;
private float textHeight;
private float text1Width;
private float text2Width;
private boolean dupFirstText;
private boolean scrollOnHover;
private boolean scroll;
private float text1Offset, text2Offset;
private NinePatch background;
private Vector2 coords;
private float targetDelta;
public ScrollingText(String text, String text2, Skin skin, boolean scrollOnHover, boolean useBackground, float targetDelta) {
super();
font = skin.getFont("default-font");
init(text, text2, skin, scrollOnHover, useBackground);
this.targetDelta = targetDelta;
}
public ScrollingText(String text, String text2, Skin skin, String fontName, Color color, boolean scrollOnHover, boolean useBackground) {
super();
font = skin.getFont(fontName);
font.setColor(color);
init(text, text2, skin, scrollOnHover, useBackground);
}
private void init(String text1, String text2, Skin skin, boolean scrollOnHover, boolean useBackground) {
setName(text1);
if (useBackground) {
this.background = skin.getPatch("side-bars");
}
this.scrollOnHover = scrollOnHover;
coords = new Vector2();
addListener(new ClickListener() {
@Override
public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) {
scroll = true;
super.enter(event, x, y, pointer, fromActor);
}
@Override
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
scroll = false;
super.exit(event, x, y, pointer, toActor);
}
@Override
public void clicked(InputEvent event, float x, float y) {
}
});
setText(text1, text2);
layout();
}
public float getFontHeight() {
return textHeight;
}
public float getFontWidth() {
return text1Width;
}
@Override
public void layout() {
super.layout();
if (getHeight() < (textHeight+4)) {
setHeight(textHeight + 4);
}
clipBounds.setSize(getWidth()-2, getHeight()*1.5f);
text2Offset = clipBounds.getWidth();
if (text1Width < clipBounds.getWidth()) {
text1Offset = (clipBounds.getWidth()-text1Width)/2f;
}
}
public void scroll(float delta) {
if (text1Offset >= -text1Width) {
text1Offset -= 60*targetDelta;
if ((text1Offset < - Math.abs((text1Width - clipBounds.getWidth())) - 50) || text2Offset != clipBounds.getWidth()) {
text2Offset -= 60*targetDelta;
if (text2Offset <= -text2Width) {
text2Offset = clipBounds.getWidth();
}
}
} else {
text2Offset -= 60*targetDelta;
if (text2Offset < - Math.abs((text2Width - clipBounds.getWidth())) - 50) {
text1Offset = clipBounds.getWidth();
}
}
}
@Override
public void act(float delta) {
clipBounds.setSize(getWidth()-2, getHeight()*1.5f);
if (dupFirstText) {
if (text1Width > clipBounds.getWidth()) {
if (scrollOnHover) {
if (scroll || text1Offset < 0 || text1Offset > 2) {
scroll(delta);
}
} else {
scroll(delta);
}
}
} else {
if (text1Width + text2Width > clipBounds.getWidth()) {
if (scrollOnHover) {
if (scroll || text1Offset < 0 || text1Offset > 2) {
scroll(delta);
}
} else {
scroll(delta);
}
}
}
super.act(delta);
}
@Override
public void draw(Batch batch, float parentAlpha) {
if (background != null) {
background.draw(batch, getX(), getY(), getWidth(), getHeight());
}
coords.x = getX();
coords.y = getY();
clipBounds.setX(coords.x+1);
clipBounds.setY(coords.y - 0.5f*getHeight());
getStage().calculateScissors(clipBounds, scissors);
batch.flush();
if (ScissorStack.pushScissors(scissors)) {
font.draw(batch, text1, coords.x + text1Offset, coords.y + getFontHeight() + 4);
font.draw(batch, text2, coords.x + text2Offset, coords.y + getFontHeight() + 4);
batch.flush();
ScissorStack.popScissors();
}
super.draw(batch, parentAlpha);
}
@Override
public float getMinHeight() {
return textHeight;
}
/**
* Sets the two strings that will be scrolling.
* @param text1 cannot be null.
* @param text2 can be null.
*/
public void setText(String text1, String text2) {
this.text1 = text1;
gLayout = new GlyphLayout(font, text1);
text1Width = gLayout.width;
textHeight = gLayout.height;
if (text2 != null) {
this.text2 = text2;
gLayout = new GlyphLayout(font, text2);
text2Width = gLayout.width;
} else {
dupFirstText = true;
this.text2 = text1;
this.text2Width = text1Width;
}
layout();
}
}

View File

@@ -0,0 +1,45 @@
package zero1hd.rhythmbullet.util;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.utils.Disposable;
public interface AssetPack extends Disposable {
/**
* Called right after the game instance is created and passed to LWJGL. This method is called once for you to instantiate things for later use but require Libgdx functions.
*/
public void initiate();
/**
* Game manager calls this when it needs to load textures.
*/
public void queueTextures(AssetManager assetManager);
/**
* Game manager calls this when it needs to load sound effects.
*/
public void queueSFX(AssetManager assetManager);
/**
* Game manager calls this when it needs to load particles.
*/
public void queueParticles(AssetManager assetManager);
/**
* Game manager calls when it needs to load particles. Usually called after textures are loaded since the skin requires the other assets.
* @param skin the skin object to set up.
*/
public void setupSkin(Skin skin);
/**
* Game manager calls when it needs the fonts to be generated. Usually called right before setting up the skin itself since items in the skin need fonts.
*/
public void generateFonts(Skin skin);
/**
* Game manager calls this once all assets are loaded. This function should be used to make some in-code adjustments to assets that will be consistent throughout the game for that run.
* @param assetManager gives you access to the assets to modify.
*/
public void complete(AssetManager assetManager);
}

View File

@@ -0,0 +1,25 @@
package zero1hd.rhythmbullet.util;
import com.badlogic.gdx.assets.loaders.FileHandleResolver;
import com.badlogic.gdx.files.FileHandle;
public class GenericFileTypeHandler implements FileHandleResolver {
private final FileHandleResolver resolver;
public GenericFileTypeHandler(FileHandleResolver fileResolver) {
resolver = fileResolver;
}
@Override
public FileHandle resolve(String fileName) {
if (fileName.endsWith(".p")) {
return resolver.resolve("particles/" +fileName);
} else if (fileName.endsWith(".ogg")) {
return resolver.resolve("sounds/" + fileName);
} else {
return null;
}
}
}

View File

@@ -0,0 +1,20 @@
package zero1hd.rhythmbullet.util;
import com.badlogic.gdx.Screen;
import zero1hd.rhythmbullet.RhythmBullet;
public interface InitialScreen extends ResizeReadyScreen {
/**
* Called when everythings loaded and ready to advance.
* Screen should be created on platform.
* @param gameManager the game manager.
* @return the screen that is created.
*/
public Screen advance(RhythmBullet gameManager);
/**
* Immediately called after the LibGDX instance has been instantiated.
*/
public void init();
}

View File

@@ -0,0 +1,15 @@
package zero1hd.rhythmbullet.util;
import com.badlogic.gdx.Screen;
public interface ResizeReadyScreen extends Screen {
/**
* called before assets are cleared from memory.
*/
public void preAssetLoad();
/**
* called after transition completes and assets reloaded.
*/
public void postAssetLoad();
}

View File

@@ -0,0 +1,77 @@
package zero1hd.rhythmbullet.util;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.assets.loaders.FileHandleResolver;
import com.badlogic.gdx.assets.loaders.resolvers.ResolutionFileResolver.Resolution;
import com.badlogic.gdx.files.FileHandle;
public class RoundingResolutionHandler implements FileHandleResolver {
private final Resolution[] descriptors;
private final FileHandleResolver resolver;
private boolean silent = true;
private int width, height;
public RoundingResolutionHandler(FileHandleResolver fileResolver, Resolution... descriptors) {
if (descriptors.length == 0) throw new IllegalArgumentException("At least one Resolution needs to be supplied.");
this.descriptors = descriptors;
this.resolver = fileResolver;
}
public void setResolution(int width, int height) {
this.width = width;
this.height = height;
}
public void resetResolution() {
width = Gdx.graphics.getWidth();
height = Gdx.graphics.getHeight();
}
public Resolution chooseRounded(Resolution... descriptors) {
Resolution best = descriptors[0];
int leastDifference = -1;
int w = width, h = height;
if (w > h) {
for (int i = 0; i < descriptors.length; i++) {
int currentDiff = h - descriptors[i].portraitHeight;
if (currentDiff < 0) {
currentDiff = currentDiff*-1;
}
if ((currentDiff < leastDifference) || leastDifference == -1) {
best = descriptors[i];
leastDifference = currentDiff;
}
}
} else {
for (int i = 0; i < descriptors.length; i++) {
int currentDiff = w - descriptors[i].portraitWidth;
if (currentDiff < 0) {
currentDiff = currentDiff*-1;
}
if (currentDiff < leastDifference || leastDifference == -1) {
best = descriptors[i];
leastDifference = currentDiff;
}
}
}
return best;
}
@Override
public FileHandle resolve(String fileName) {
Resolution bestRes = chooseRounded(descriptors);
if (!silent) {
Gdx.app.debug("RResolution Handler", "Finding best match for resolution: " + width + "x" + height + " for file: " + fileName);
Gdx.app.debug("RResolution Handler", "Selected folder: " + bestRes.folder);
}
FileHandle resSpecificFile = resolver.resolve(bestRes.folder + "/" + fileName);
if (!resSpecificFile.exists()) resSpecificFile = resolver.resolve(fileName);
return resSpecificFile;
}
}

View File

@@ -0,0 +1,30 @@
package zero1hd.rhythmbullet.util;
public interface ScreenConfiguration {
public void setFramesPerSecond(int fps);
public int getTargetFramesPerSecond();
/**
* @param useVsync whether or not to use vSync.
*/
public void setVsync(boolean useVsync);
public boolean getVsync();
public int getScreenWidth();
public int getScreenHeight();
public int getWindowPosX();
public int getWindowPosY();
public void setWindowLocationX(int x);
public void setWindowLocationY(int y);
public void setWindowLocation(int x, int y);
public void queueBorderless(boolean borderless);
}