/*******************************************************************************
* Copyright 2013 pyros2097
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package sink.core;
import java.io.File;
import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import com.badlogic.gdx.Application.ApplicationType;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ArrayMap;
/** Automatic Assets Loading for the Sink Game
* <p>
* This class automatically loads all the assets in the prescribed folders into the appropriate
* class types. All This can be accessed using an neat api.
*
* <p>
* <b>#Important </b> <br>
* All asset files must be lowercase only.. otherwise it causes problems with android <br>
* 1. All Assets are to be stored in the assets directory <br>
* 2. Automatic Asset Loading the directory Structure should be like this <br>
*
* <p>
* assets/icon.png --- your game icon to be displayed on the window<br>
* assets/atlas/ --- all your Texture Atlas files .atlas and .png go here<br>
* assets/font/ --- all your BitmapFont files .fnt and .png go here<br>
* assets/music/ --- all your Music files .mp3 go here<br>
* assets/sound/ --- all your Music files .mp3 go here<br>
* assets/particle/ --- all your Particle files .part go here<br>
* assets/map/ --- all your TMX map files go here<br>
* assets/pack/ --- all your image files which are to be packed are to be stored here<br>
* so that they are automatically packed by the texture packer and stored in
* the atlas folder
*
* <p>
* All assets are accessed this way,<br>
* First import what type of asset you wish to use as static, <br>
* <b>Note:</b> The Scene, SceneActor, SceneGroup classes have these methods inbuilt for easier accessing<br>
*
* <pre>
* <code>
* import static sink.core.Asset.anim;
* import static sink.core.Asset.tex;
* import static sink.core.Asset.musicPlay;
* import static sink.core.Asset.soundPlay;
* import static sink.core.Asset.font;
*
*
* //To load TextureRegion
* TextureRegion cat = tex("cat");
*
* //To load Animation
* Animation catAnim = anim("cat");
*
* //To load BitmapFont
* BitmapFont font1 = font("font1");
*
* //The music and sound files are automatically cached and can be played by invoking:
* musicPlay("musicname");
* soundPlay("soundname");
*
* //The asset functions will return null for Font, TextureRegion and Animation if the asset cannot be found
* </code>
</pre>
* </p>
* @author pyros2097 */
public final class Asset {
private static AssetManager assetMan = new AssetManager();
public static Skin skin;
private static FileHandle[] musicFiles;
private static FileHandle[] soundFiles;
private static FileHandle[] fontFiles;
private static FileHandle[] atlasFiles;
private static Array<String> musicJarFiles = new Array<String>();
private static Array<String> soundJarFiles = new Array<String>();
private static Array<String> fontJarFiles = new Array<String>();
private static Array<String> atlasJarFiles = new Array<String>();
public final static ArrayMap<String, Music> musicMap = new ArrayMap<String, Music>();
public final static ArrayMap<String, Sound> soundMap = new ArrayMap<String, Sound>();
public final static ArrayMap<String, BitmapFont> fontMap = new ArrayMap<String, BitmapFont>();
public final static ArrayMap<String, TextureRegion> texMap = new ArrayMap<String, TextureRegion>();
public final static ArrayMap<String, Animation> animMap = new ArrayMap<String, Animation>();
private static Music currentMusic = null;
public static String currentMusicName = ""; //Only file name no prefix or suffix
private static Sound currentSound = null;
private static boolean readinglock = false;
private static boolean updatinglock = false;
private static File jarFile = null;
/* This is to be used by sink studio */
static String basePath = "";
public static boolean musicOn = true;
public static boolean musicOff = false;
public static boolean loadAsynchronous = true;
public static boolean isStudio = false;
public static void load(){
if(loadAsynchronous)
loadNonBlocking();
//else
// loadBlocking();
}
/*
* This is a blocking load call blocks the display until assets are all loaded.
*/
public static void loadBlocking(){
readinglock = true;
updatinglock = true;
readData();
assetMan.finishLoading();
assetMan.setLoader(TiledMap.class, new TmxMapLoader(new InternalFileHandleResolver()));
loadTextureRegions();
loadMusics();
loadSounds();
loadFonts();
loadSkin();
loadFPS();
}
/*
* This is a non-blocking load call that allows you to display other things while the load is going
* on. This is called in the act method of SplashScene so that it is runs in the background
* once the assets are all loaded it will automatically stop and call SplashScene.onAssetsLoaded()
*/
private static boolean loadNonBlocking(){
if(!readinglock){
readData();
readinglock = true;
}
// once update returns true then condition is satisfied and the lock stops update call
if(!updatinglock)
if(assetMan.update()){
assetMan.setLoader(TiledMap.class, new TmxMapLoader(new InternalFileHandleResolver()));
loadTextureRegions();
loadMusics();
loadSounds();
loadFonts();
loadSkin();
loadFPS();
updatinglock = true;
Sink.nextScene();
}
return updatinglock;
}
private static void readData(){
if (Gdx.app.getType() == ApplicationType.Android){
Sink.log("Loading Music Files");
musicFiles = Gdx.files.internal("music").list(); //adroid works
Sink.log("Loading Sound Files");
soundFiles = Gdx.files.internal("sound").list();
Sink.log("Loading Font Files");
fontFiles = Gdx.files.internal("font").list();
Sink.log("Loading Atlas Files");
atlasFiles = Gdx.files.internal("atlas").list();
assetMan.load(basePath+"skin/uiskin.json", Skin.class);
}
else if(Gdx.app.getType() == ApplicationType.Desktop){
try {
if(Sink.getScene() != null){
jarFile = new File(Sink.getScene().getClass().getProtectionDomain()
.getCodeSource().getLocation().toURI());
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
if(jarFile != null && isStudio == false)
loadFromJar();
else{
Sink.log("Loading Music Files");
musicFiles = Gdx.files.internal(basePath+"/music").list();
Sink.log("Loading Sound Files");
soundFiles = Gdx.files.internal(basePath+"/sound").list();
Sink.log("Loading Font Files");
fontFiles = Gdx.files.internal(basePath+"/font").list();
atlasFiles = Gdx.files.internal(basePath+"/atlas").list();
for(FileHandle f: musicFiles)
assetMan.load(basePath+"music/"+f.name(), Music.class);
for(FileHandle f: soundFiles)
assetMan.load(basePath+"sound/"+f.name(), Sound.class);
for(FileHandle f: fontFiles){
if(f.extension().equals("fnt"))
assetMan.load(basePath+"font/"+f.name(), BitmapFont.class);
}
for(FileHandle f: atlasFiles){
if(f.extension().equals("atlas"))
assetMan.load(basePath+"atlas/"+f.name(), TextureAtlas.class);
}
assetMan.load(basePath+"skin/uiskin.json", Skin.class);
}
}
}
private static void loadFromJar(){
try{
ZipFile zf = new ZipFile(jarFile.getAbsoluteFile());
Enumeration<? extends ZipEntry> e=zf.entries();
while (e.hasMoreElements())
{
ZipEntry ze=(ZipEntry)e.nextElement();
String entryName = ze.getName();
if(entryName.startsWith("music") && entryName.endsWith(".mp3")) {
musicJarFiles.add(entryName);
Sink.log(ze.getName());
}
else if(entryName.startsWith("sound") && entryName.endsWith(".mp3")){
soundJarFiles.add(entryName);
Sink.log(ze.getName());
}
else if(entryName.startsWith("font") && entryName.endsWith(".fnt")){
fontJarFiles.add(entryName);
Sink.log(ze.getName());
}
else if(entryName.startsWith("atlas") && entryName.endsWith(".atlas")){
atlasJarFiles.add(entryName);
Sink.log(ze.getName());
}
else if(entryName.startsWith("skin") && entryName.endsWith(".json")){
assetMan.load("skin/uiskin.json", Skin.class);
Sink.log(ze.getName());
}
}
zf.close();
}
catch (Exception e){e.printStackTrace();}
for(String f: musicJarFiles)
assetMan.load(f, Music.class);
for(String f: soundJarFiles)
assetMan.load(f, Sound.class);
for(String f: fontJarFiles)
assetMan.load(f, BitmapFont.class);
for(String f: atlasJarFiles)
assetMan.load(f, TextureAtlas.class);
}
private static void loadSkin(){
skin = assetMan.get(basePath+"skin/uiskin.json", Skin.class);
}
private static void loadFPS(){
if(skin != null)
Sink.setup();
else{
for(BitmapFont f: fontMap.values()){
Sink.setup(f);
break;
}
}
}
/***********************************************************************************************************
* Music Related Global Functions *
************************************************************************************************************/
private static void loadMusics(){
if(jarFile != null){
for(String f: musicJarFiles){
Music m = assetMan.get(f, Music.class);
String name = f.replace("music/", "");
musicMap.put(name.replace(".mp3", ""),m);
}
}
else{
for(FileHandle f: musicFiles){
Music m = assetMan.get(basePath+"music/"+f.name(), Music.class);
musicMap.put(f.nameWithoutExtension(),m);
}
}
}
/** Plays the music file which was dynamically loaded if it is present otherwise logs the name
* @param filename The Music File name only
* @ex <code>music("title")</code>
* */
public static void musicPlay(String filename){
if(Config.isMusic){
if(currentMusic != null)
if(currentMusic.isPlaying())
if(currentMusicName == filename)
return;
else
musicStop();
if(musicMap.containsKey(filename)){
Sink.log("Music: playing "+filename);
currentMusic = musicMap.get(filename);//Gdx.audio.newMusic(Gdx.files.internal("music/"+filename));
currentMusic.setVolume(Config.volMusic);
currentMusic.setLooping(true);
currentMusic.play();
currentMusicName = filename;
}
else{
Sink.log("Music File Not Found: "+filename);
}
}
}
/** Pauses the current music file being played */
public static void musicPause(){
if(currentMusic != null)
if(currentMusic.isPlaying()){
Sink.log("Music: pausing "+currentMusicName);
currentMusic.pause();
}
}
/** Resumes the current music file being played */
public static void musicResume(){
if(currentMusic != null)
if(!currentMusic.isPlaying()){
Sink.log("Music: resuming "+currentMusicName);
currentMusic.play();
}
else
musicPlay(currentMusicName);
}
/** Stops the current music file being played */
public static void musicStop(){
if(currentMusic != null){
Sink.log("Music: stoping "+currentMusicName);
currentMusic.stop();
currentMusic = null;
}
}
/** Sets the volume music file being played */
public static void musicVolume(){
if(currentMusic != null);
currentMusic.setVolume(Config.volMusic);
}
/** Disoposes the current music file being played */
public static void musicDispose(){
if(currentMusic != null);
currentMusic.dispose();
}
/***********************************************************************************************************
* Sound Related Global Functions *
************************************************************************************************************/
private static void loadSounds(){
if(jarFile != null){
for(String f: soundJarFiles){
Sound m = assetMan.get(f, Sound.class);
String name = f.replace("sound/", "");
soundMap.put(name.replace(".mp3", ""),m);
}
}
else{
for(FileHandle f: soundFiles){
Sound m = assetMan.get(basePath+"sound/"+f.name(), Sound.class);
soundMap.put(f.nameWithoutExtension(),m);
}
}
}
/** Plays the sound file which was dynamically loaded if it is present otherwise logs the name
* @param filename The Sound File name only
* @ex <code>soundPlay("bang")</code>
* */
public static void soundPlay(String filename){
if(Config.isSound){
if(soundMap.containsKey(filename)){
currentSound = soundMap.get(filename);
long id = currentSound.play(Config.volSound);
currentSound.setLooping(id, false);
currentSound.setPriority(id, 99);
Sink.log("Sound:"+"Play "+ filename);
}
else{
Sink.log("Music File Not Found: "+filename);
}
}
}
/** Plays the sound file "click" */
public static void soundClick(){
if(Config.isSound){
currentSound = soundMap.get("click");
long id = currentSound.play(Config.volSound);
currentSound.setLooping(id, false);
currentSound.setPriority(id, 99);
Sink.log("Sound:"+"Play "+ "click");
}
}
/** Pauses the current sound file being played */
public static void soundPause(){
Sink.log("Sound:"+"Pausing");
if(currentSound != null)
currentSound.pause();
}
/** Resumes the current sound file being played */
public static void soundResume(){
Sink.log("Sound:"+"Resuming");
if(currentSound != null)
currentSound.resume();
}
/** Stops the current sound file being played */
public static void soundStop(){
Sink.log("Sound:"+"Stopping");
if(currentSound != null)
currentSound.stop();
}
/** Disposes the current sound file being played */
public static void soundDispose(){
Sink.log("Sound:"+"Disposing Sound");
if(currentSound != null)
currentSound.dispose();
}
/***********************************************************************************************************
* BitmapFont Related Functions *
************************************************************************************************************/
private static void loadFonts(){
if(jarFile != null){
for(String f: fontJarFiles){
BitmapFont m = assetMan.get(f, BitmapFont.class);
String name = f.replace("font/", "");
fontMap.put(name.replace(".fnt", ""),m);
}
}
else{
for(FileHandle f: fontFiles){
if(f.extension().equals("fnt")){
BitmapFont m = assetMan.get(basePath+"font/"+f.name(), BitmapFont.class);
fontMap.put(f.nameWithoutExtension(),m);
}
}
}
}
/** If key is present returns the BitmapFont that was dynamically loaded
* else returns null
* @param fontname The BitmapFont name
* @return BitmapFont or null
* @ex font("font1") or font("arial")
* */
public static BitmapFont font(String fontname){
if(fontMap.containsKey(fontname)){
return fontMap.get(fontname);
}
else{
Sink.log("Font File Not Found: "+fontname);
return null;
}
}
/***********************************************************************************************************
* Texture Related Functions *
************************************************************************************************************/
private static void loadTextureRegions(){
if(jarFile != null){
for(String f: atlasJarFiles){
TextureAtlas atlas = assetMan.get(f, TextureAtlas.class);
Array<TextureAtlas.AtlasRegion> regions = atlas.getRegions();
for(TextureAtlas.AtlasRegion ar: regions){
texMap.put(ar.name, ar);
}
}
}
else{
for(FileHandle f: atlasFiles){
if(f.extension().equals("atlas")){
TextureAtlas atlas = assetMan.get(basePath+"atlas/"+f.name(), TextureAtlas.class);
Array<TextureAtlas.AtlasRegion> regions = atlas.getRegions();
for(TextureAtlas.AtlasRegion ar: regions){
texMap.put(ar.name, ar);
}
}
}
}
}
/** If key is present returns the TextureRegion that was loaded from all the atlases
* else returns null
* @param textureregionName The TextureRegion name
* @return TextureRegion or null
* */
public static TextureRegion tex(String textureregionName){
if(texMap.containsKey(textureregionName)){
return texMap.get(textureregionName);
}
else{
Sink.log("TextureRegion Not Found: "+textureregionName);
return null;
}
}
/***********************************************************************************************************
* Animation Related Functions *
************************************************************************************************************/
/**1.TextureRegion Names with numbers like slug00 slug01 are automatically classified as animations
* 2.TextureRegion Name with "_row" is treated as row animations so don't use _row as filename
* @param animationBaseName The Animation Base name
* @return Animation or null
* @ex <code>anim("slug")</code>
* */
public static Animation anim(String animationBaseName){
if(animMap.containsKey(animationBaseName)){
return animMap.get(animationBaseName);
}
else{
Sink.log("Animation Not Found: "+animationBaseName);
return null;
}
}
/**
* Warning: Image Name a TextureRegion Name should not with numbers
* Warning: Image Name a TextureRegion Name with "_row" is treated as row animations
* for automatic animation loading so dont use _row as filename
*
* */
private static void loadAnimations() {
Array<TextureRegion> keyFrames = new Array<TextureRegion>();
Array<String> animsLoaded = new Array<String>();
String baseAnimName = "";
Array<String> texRegionsName = texMap.keys().toArray();
for(String name: texRegionsName) {
if(name.matches(".*\\d.*") && !name.contains("_row")){
baseAnimName = name.replaceAll("[0-9]","");
Sink.log(baseAnimName);
if(!animsLoaded.contains(baseAnimName, false)){
for(int i=0; i < 100; i++){
if(i<10 && texRegionsName.contains(baseAnimName+"0"+i, false)){
Sink.log("Contains: "+baseAnimName+"0"+i);
keyFrames.add(texMap.get(baseAnimName+"0"+i));
}
else if(texRegionsName.contains(baseAnimName+i, false)){
Sink.log("Contains: "+baseAnimName+"0"+i);
keyFrames.add(texMap.get(baseAnimName+i));
}
else{
//log("KeyFrames: "+keyFrames.toString());
animsLoaded.add(baseAnimName);
animMap.put(baseAnimName, new Animation(1/keyFrames.size, keyFrames));
keyFrames.clear();
break;
}
}
}
}
// Row Single Png Image Texture Animations
else if(name.contains("_row")){
Sink.log("Loading Row Animation: "+name);
TextureRegion textureRegion = texMap.get(name);
String[] sep = name.split("_row");
//log(sep[1]);
int noOfFrames = Integer.parseInt(sep[1]);
TextureRegion[] rowFrames = new TextureRegion[noOfFrames];
// Set key frames (each comes from the single texture)
for (int i = 0; i < noOfFrames; i++) {
rowFrames[i] = new TextureRegion(textureRegion,
(textureRegion.getRegionWidth() / noOfFrames) * i, 0,
textureRegion.getRegionWidth() / noOfFrames,
textureRegion.getRegionHeight());
}
/*TextureRegion[] rowFrames = textureRegion.split(textureRegion.getRegionWidth()/noOfFrames,
textureRegion.getRegionHeight())[0];*/
animMap.put(sep[0] ,new Animation(1/noOfFrames, rowFrames));
}
}
}
/**
* Get animation from texture atlas (Based on TexturePacker). There is only
* single texture which contains all frames (It is like a single png which
* has all the frames). Each frames' width should be same for proper results
* <p>
*
* @param textureAtlas
* atlas which contains the single animation texture
* @param animationName
* name of the animation in atlas
* @param numberOfFrames
* number of frames of the animation
* @param frameDuration
* each frame duration on play
* @return animation created
*
* */
private static Animation getAnimationFromSingleTexture(TextureAtlas textureAtlas, String animationName, int numberOfFrames, float frameDuration) {
// Get animation texture (single texture)
TextureRegion textureRegion = textureAtlas.findRegion(animationName);
// Key frames list
TextureRegion[] keyFrames = new TextureRegion[numberOfFrames];
// Set key frames (each comes from the single texture)
for (int i = 0; i < numberOfFrames; i++) {
keyFrames[i] = new TextureRegion(textureRegion,
(textureRegion.getRegionWidth() / numberOfFrames) * i, 0,
textureRegion.getRegionWidth() / numberOfFrames,
textureRegion.getRegionHeight());
}
//
Animation animation = new Animation(frameDuration, keyFrames);
return animation;
}
/**
* Get animation from texture atlas (Based on TexturePacker). There is only
* single texture which contains all frames (It is like a single png which
* has all the frames). Each frames' width should be same for proper results
* <p>
*
* @param textureAtlas
* atlas which contains the single animation texture
* @param animationName
* name of the animation in atlas
* @param numberOfFrames
* number of frames of the animation
* @param numberOfMaximumFramesInTheSheet
* maximum number of frame in a row in the sheet
* @param numberOfRows
* number of rows that the sheet contains
* @param indexOfAnimationRow
* the row index (starts from 0) that desired animation exists
* @param frameDuration
* each frame duration on play
* @return animation created
*
* */
private static Animation getAnimationFromSingleTextureMultiRows(TextureAtlas textureAtlas, String animationName, int numberOfFrames, int numberOfMaximumFramesInTheSheet,
int numberOfRows, int indexOfAnimationRow, float frameDuration) {
// Get animation texture (single texture)
TextureRegion textureRegion = textureAtlas.findRegion(animationName);
// Key frames list
TextureRegion[] keyFrames = new TextureRegion[numberOfFrames];
// Set key frames (each comes from the single texture)
for (int i = 0; i < numberOfFrames; i++) {
keyFrames[i] = new TextureRegion(
textureRegion,
(textureRegion.getRegionWidth() / numberOfMaximumFramesInTheSheet)
* i, textureRegion.getRegionHeight() / numberOfRows
* indexOfAnimationRow,
textureRegion.getRegionWidth()
/ numberOfMaximumFramesInTheSheet,
textureRegion.getRegionHeight() / numberOfRows);
}
//
Animation animation = new Animation(frameDuration, keyFrames);
return animation;
}
/***********************************************************************************************************
* TMX MAP Related Functions *
************************************************************************************************************/
/*
* Loads a Tmx map by specifying the map/level no
* eg: loadTmx(4) -> returns the TiledMap "map/level4.tmx"
*
* Note: Tmx Maps must be loaded manually as they may take a lot of time to laod
*/
public static TiledMap loadTmx(int i){
assetMan.setLoader(TiledMap.class, new TmxMapLoader(new InternalFileHandleResolver()));
assetMan.load(basePath+"map/level"+i+".tmx", TiledMap.class);
assetMan.finishLoading();
return assetMan.get(basePath+"map/level"+i+".tmx", TiledMap.class);
}
/*
* unloads a Tmx map by specifying the map/level no
* eg: unloadTmx(4) -> unloads the TiledMap "map/level4.tmx"
*
* Note: Tmx Maps must be unloaded manually
*/
public static void unloadTmx(int i){
assetMan.unload(basePath+"map/level"+i+".tmx");
}
/***********************************************************************************************************
* LOG Related Functions *
************************************************************************************************************/
/*
* Logs all the assets that are loaded and cached
*/
public static void logAll(){
logTextures();
logAnimations();
logFonts();
logSounds();
logMusics();
}
/*
* Logs all the TextureRegions that are loaded and cached
*/
public static void logTextures(){
Sink.log("BEGIN logging Textures------------------");
for(String na: texMap.keys())
Sink.log(na);
Sink.log("END logging Textures------------------");
}
/*
* Logs all the Animations that are loaded and cached
*/
public static void logAnimations(){
Sink.log("BEGIN logging Animations------------------");
for(String na: animMap.keys())
Sink.log(na);
Sink.log("END logging Animations------------------");
}
/*
* Logs all the BitmapFonts that are loaded and cached
*/
public static void logFonts(){
Sink.log("BEGIN logging Fonts------------------");
for(String na: fontMap.keys())
Sink.log(na);
Sink.log("END logging Fonts------------------");
}
/*
* Logs all the Sounds that are loaded and cached
*/
public static void logSounds(){
Sink.log("BEGIN logging Sounds------------------");
for(String na: soundMap.keys())
Sink.log(na);
Sink.log("END logging Sounds------------------");
}
/*
* Logs all the Music that are loaded and cached
*/
public static void logMusics(){
Sink.log("BEGIN logging Musics------------------");
for(String na: musicMap.keys())
Sink.log(na);
Sink.log("END logging Musics------------------");
}
/*
* Unloads and disposes all the resources except for Tmx Maps
* This is called by Sink.exit();
*/
public static void unloadAll(){
assetMan.dispose();
}
/*
* If using in eclipse set basePath to ./bin/
*/
public static void setBasePath(String path){
basePath = path;
}
}