sink.core.Sink.java Source code

Java tutorial

Introduction

Here is the source code for sink.core.Sink.java

Source

/*******************************************************************************
 * 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 static sink.core.Asset.musicPause;
import static sink.core.Asset.musicResume;
import static sink.core.Asset.soundStop;
import sink.event.ClickedListener;
import sink.event.DisposeListener;
import sink.event.DraggedListener;
import sink.event.PauseListener;
import sink.event.ResumeListener;
import sink.json.ButtonJson;
import sink.json.CheckBoxJson;
import sink.json.DialogJson;
import sink.json.ImageJson;
import sink.json.LabelJson;
import sink.json.ListJson;
import sink.json.SelectBoxJson;
import sink.json.SliderJson;
import sink.json.TableJson;
import sink.json.TextButtonJson;
import sink.json.TextFieldJson;
import sink.json.TouchpadJson;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Files.FileType;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Action;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.JsonReader;
import com.badlogic.gdx.utils.JsonValue;
import com.badlogic.gdx.utils.Scaling;
import com.badlogic.gdx.utils.SnapshotArray;
import com.badlogic.gdx.utils.StringBuilder;

/** The Main Entry Point for the Sink Game is the Sink class
 * <p>
 * It consists of a single Stage and Camera which are all initialized based on the {@link Config} settings.
 * The stage can be accessed in a static way like Sink.getStage() and methods related to camera like moveTo, moveBy,
 * are also accessed the same way.<br>
 * It has extra things like gameUptime, pauseState, PauseListeners, ResumeListeners, AssetLoadedEvent
 * DisposeListeners.<br>
 * 
 * It has automatic asset unloading and disposing and you can use {@link #exit()} to quit your game safely
 * 
 * Note: Your TMX maps have to be unloaded manually as they can be huge resources needing to be freed early.
 * 
 * It has static methods which can be used for panning the camera using mouse, keyboard, drag.. etc.
 * It can also automatically follow a actor by using followActor(Actor actor)<br>
 * 
 * This class will register all your scenes based on your config.json file and then you can switch you scenes by using {@link #setScene}
 * method with the sceneName.<br>
 * 
 * Run the Desktop Game by using sink.core.Sink class as it contains the static main declaration.<br>
 * Your first sceneName in the config.json file gets shown first automatically and once and all your assets <br>
 * are loaded in the background(asynchronously) in the first scene and then automatically the next scene in the list is set.
 * <p>
 * @ex
 * <pre>
 * <code>
//This is our first Scene and it shows the libgdx logo until all the assets are loaded 
//then it automatically switches to the Menu scene
public class  Splash {
      
  public Splash() {
     final Texture bg1 = new Texture("splash/libgdx.png");
     final Image imgbg1 = new Image(bg1);
     imgbg1.setFillParent(true);
     Sink.addActor(imgbg1);
   } 
   }
       
//This is Scene gets called once the assets are loaded
public class  Menu {
    
  public Menu() {
     //create some actors
     // if you used sink studio and create a scene like Menu.json then
     // it will automatically call load("Menu") it will populate your scene after parsing the json file
         
     //you can access these objects like this
     TextButton btn = (TextButton) Sink.findActor("TextButton1");
     Image img = (Image) Sink.findActor("Image5");
         
     // these actors are loaded from the json file and are give names which allows
     // easy access to them
  }
   }
       
   //In config.json
   "scenes": "Splash,Menu"
 </code>
 </pre>
 * @author pyros2097 */

public final class Sink implements ApplicationListener {
    public static String version = "1.01";
    private float startTime = System.nanoTime();
    public static float gameUptime = 0;
    private static Json json = new Json();
    public static JsonReader jsonReader = new JsonReader();
    public static JsonValue jsonValue = null;

    private static Stage stage;
    private static OrthographicCamera camera;
    private static LogPane logPane;
    private static Label fpsLabel;

    private static Object currentScene = null;
    private static String currentSceneName = "";
    private static int sceneIndex = 0;
    public static final Array<String> scenesList = new Array<String>();
    public static final Array<String> objectList = new Array<String>();
    private static final Array<String> serializerList = new Array<String>();
    private static Array<Actor> hudActors = new Array<Actor>();
    private static final Array<ClickedListener> clickedListeners = new Array<ClickedListener>();
    private static final Array<DraggedListener> draggedListeners = new Array<DraggedListener>();
    private static final Array<PauseListener> pauseListeners = new Array<PauseListener>();
    private static final Array<ResumeListener> resumeListeners = new Array<ResumeListener>();
    private static final Array<DisposeListener> disposeListeners = new Array<DisposeListener>();

    /*Important:
     *  The Target Width  and Target Height refer to the nominal width and height of the game for the
     *  graphics which are created  for this width and height, this allows for the Stage to scale this
     *  graphics for all screen width and height. Therefore your game will work on all screen sizes 
     *  but maybe blurred or look awkward on some devices.
     *  ex:
     *  My Game targetWidth = 800 targetHeight = 480
     *  Then my game works perfectly for SCREEN_WIDTH = 800 SCREEN_HEIGHT = 480
     *  and on others screen sizes it is just zoomed/scaled but works fine thats all
     */
    public static float targetWidth = 800;
    public static float targetHeight = 480;
    public static boolean pauseState = false;

    public static ClassLoader classLoader = null;
    private static boolean firstScene = true;
    private static boolean serializerlock = false;

    //Studio Related Stuff
    /* Selected Actor can never be null as when the stage is clicked it may return an actor or the root actor */
    public static Actor selectedActor = null;
    private ShapeRenderer shapeRenderer;
    public static boolean showGrid = false;
    public static boolean debug = false;

    public static int dots = 20;
    public static int xlines = (int) Sink.targetWidth / dots;
    public static int ylines = (int) Sink.targetHeight / dots;

    public static String sceneBackground = "";
    public static String sceneMusic = "";
    public static String sceneTransition = "";
    public static float sceneDuration = 0;
    public static Interpolation sceneInterpolation = Interpolation.linear;

    /** The Main Launcher for Sink Game
     * <p>
     * Just specify the Sink class as the Main file and when you export your game to jar add
     * the manifest entry Main-Class: sink.core.Sink for it to work
     */
    public static LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();

    public static void main(String[] argc) {
        jsonValue = jsonReader.parse(Sink.class.getClassLoader().getResourceAsStream("config.json"));
        if (jsonValue.getBoolean("hasIcon"))
            cfg.addIcon("icon.png", FileType.Internal);
        cfg.width = jsonValue.getInt("screenWidth");
        cfg.height = jsonValue.getInt("screenHeight");
        cfg.x = jsonValue.getInt("x");
        cfg.y = jsonValue.getInt("y");
        cfg.resizable = jsonValue.getBoolean("resize");
        cfg.forceExit = jsonValue.getBoolean("forceExit");
        cfg.fullscreen = jsonValue.getBoolean("fullScreen");
        cfg.useGL20 = jsonValue.getBoolean("useGL20");
        cfg.vSyncEnabled = jsonValue.getBoolean("vSync");
        cfg.audioDeviceBufferCount = jsonValue.getInt("audioBufferCount");
        LwjglApplicationConfiguration.disableAudio = jsonValue.getBoolean("disableAudio");
        targetWidth = jsonValue.getInt("targetWidth");
        targetHeight = jsonValue.getInt("targetHeight");
        new LwjglApplication(new Sink(), cfg);
    }

    /*
     * This is where the stage and camera are created and the splash scene in created
     * dynamically and set as the first scene;
    */
    @Override
    public final void create() {
        Sink.log("Sink: Created");
        Config.setup();
        stage = new Stage(cfg.width, cfg.height, jsonValue.getBoolean("keepAspectRatio"));
        camera = new OrthographicCamera();
        camera.setToOrtho(false, targetWidth, targetHeight);
        camera.position.set(targetWidth / 2, targetHeight / 2, 0);
        stage.setCamera(camera);
        Gdx.input.setCatchBackKey(true);
        Gdx.input.setCatchMenuKey(true);
        Gdx.input.setInputProcessor(stage);
        stage.addListener(touchInput);
        shapeRenderer = new ShapeRenderer();
        for (String className : jsonValue.getString("scenes").split(","))
            scenesList.add(className.trim()); // registering the scenes
        setScene(scenesList.first());
        selectedActor = stage.getRoot();
    }

    /*
     * This is the main rendering call that updates the time, updates the stage
     * and loads updates the camera and fps text
     */
    @Override
    public final void render() {
        if (System.nanoTime() - startTime >= 1000000000) {
            gameUptime += 1;
            startTime = System.nanoTime();
        }
        Gdx.gl.glClearColor(1f, 1f, 1f, 1f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
        Asset.load();
        stage.act(Gdx.graphics.getDeltaTime());
        stage.draw();
        updateController();
        if (debug) {
            drawGrid();
            drawSelection();
        }
        if (fpsLabel != null && jsonValue.getBoolean("showFps"))
            fpsLabel.setText("Fps: " + Gdx.graphics.getFramesPerSecond());
    }

    void drawGrid() {
        if (showGrid) {
            shapeRenderer.begin(ShapeType.Point);
            shapeRenderer.setColor(Color.BLACK);
            for (int i = 0; i < xlines; i++)
                for (int j = 0; j < ylines; j++)
                    shapeRenderer.point(i * dots, j * dots, 0);
            shapeRenderer.end();
        }
    }

    void drawSelection() {
        if (selectedActor.getName() != null) {
            shapeRenderer.begin(ShapeType.Line);
            shapeRenderer.setColor(Color.GREEN);
            shapeRenderer.rect(selectedActor.getX(), selectedActor.getY(), selectedActor.getWidth(),
                    selectedActor.getHeight());
            shapeRenderer.end();
        }
    }

    /*
     * This will resize the stage accordingly to fit to your target width and height
     */
    @Override
    public final void resize(int width, int height) {
        Sink.log("Sink: Resize");
        stage.setViewport(targetWidth, targetHeight, jsonValue.getBoolean("keepAspectRatio"));
    }

    /*
     * This will pause any music and stop any sound being played
     * and will fire the Pause event
     */
    @Override
    public final void pause() {
        Sink.log("Sink: Pause");
        musicPause();
        soundStop();
        firePauseEvent();
    }

    /*
     * This will resume any music currently being played
     * and will fire the Resume event
     */
    @Override
    public final void resume() {
        Sink.log("Sink: Resume");
        musicResume();
        fireResumeEvent();
    }

    /*
     * When disposed is called
     * It will automatically unload all your assets and dispose the stage
     */
    @Override
    public final void dispose() {
        Sink.log("Sink: Disposing");
        fireDisposeEvent();
        stage.dispose();
        Asset.unloadAll();
        Config.writeTotalTime(gameUptime);
        Gdx.app.exit();
    }

    /*
     * Use this to exit your game safely
     * It will automatically unload all your assets and dispose the stage
    */
    public static final void exit() {
        Sink.log("Sink: Disposing and Exiting");
        fireDisposeEvent();
        stage.dispose();
        Asset.unloadAll();
        Config.writeTotalTime(gameUptime);
        Gdx.app.exit();
    }

    public static void log(String log) {
        if (jsonValue.getBoolean("loggingEnabled")) {
            Gdx.app.log("", log);
            if (logPane != null && jsonValue.getBoolean("showLogger"))
                logPane.update(log);
        }
    }

    public static Stage getStage() {
        return stage;
    }

    public static OrthographicCamera getCamera() {
        return camera;
    }

    public static void addListener(ClickedListener cl) {
        clickedListeners.add(cl);
    }

    public static void addListener(DraggedListener dl) {
        draggedListeners.add(dl);
    }

    public static void addListener(PauseListener pl) {
        pauseListeners.add(pl);
    }

    public static void addListener(ResumeListener rl) {
        resumeListeners.add(rl);
    }

    public static void addListener(DisposeListener dl) {
        disposeListeners.add(dl);
    }

    public static void removeListener(ClickedListener cl) {
        clickedListeners.removeValue(cl, true);
    }

    public static void removeListener(DraggedListener dl) {
        draggedListeners.removeValue(dl, true);
    }

    public static void removeListener(PauseListener pl) {
        pauseListeners.removeValue(pl, true);
    }

    public static void removeListener(ResumeListener rl) {
        resumeListeners.removeValue(rl, true);
    }

    public static void removeListener(DisposeListener dl) {
        disposeListeners.removeValue(dl, true);
    }

    public static void clearAllListeners() {
        clickedListeners.clear();
        draggedListeners.clear();
        pauseListeners.clear();
        resumeListeners.clear();
        disposeListeners.clear();
        hudActors.clear();
    }

    /**
     * Manually Fire a Pause Event
     * */
    public static void firePauseEvent() {
        pauseState = true;
        for (PauseListener pl : pauseListeners)
            pl.onPause();
    }

    /**
     * Manually Fire a Resume Event
     * */
    public static void fireResumeEvent() {
        pauseState = false;
        for (ResumeListener rl : resumeListeners)
            rl.onResume();
    }

    private static void fireDisposeEvent() {
        for (DisposeListener dl : disposeListeners)
            dl.onDispose();
    }

    /**
     * Get screen time from start in format of HH:MM:SS. It is calculated from
     * "secondsTime" parameter.
     * */
    public static String toScreenTime(float secondstime) {
        int seconds = (int) (secondstime % 60);
        int minutes = (int) ((secondstime / 60) % 60);
        int hours = (int) ((secondstime / 3600) % 24);
        return new String(addZero(hours) + ":" + addZero(minutes) + ":" + addZero(seconds));
    }

    private static String addZero(int value) {
        String str = "";
        if (value < 10)
            str = "0" + value;
        else
            str = "" + value;
        return str;
    }

    /***********************************************************************************************************
    *                Scene Related Functions                                                           *
    ************************************************************************************************************/
    /**
     * Set the current scene to be displayed
     * @param className The registered scene's name
     **/
    public static void setScene(String className) {
        if (scenesList.contains(className, false)) {
            Sink.log("Current Scene :" + className);
            camera.position.set(targetWidth / 2, targetHeight / 2, 0);
            clearAllListeners();
            stage.clear();
            stage.addListener(touchInput);
            stage.getRoot().setPosition(0, 0);
            stage.getRoot().setSize(targetWidth, targetHeight);
            stage.getRoot().setBounds(0, 0, targetWidth, targetHeight);
            try {
                currentSceneName = className;
                load();
                if (classLoader == null)
                    currentScene = Class.forName(className).newInstance();
                else
                    currentScene = classLoader.loadClass(className).newInstance();
            } catch (InstantiationException e) {
                Sink.log("Sink: Scene cannot be created , Check if scene class can be found");
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            if (fpsLabel != null && jsonValue.getBoolean("showFps")) {
                registerSceneHud(fpsLabel);
                fpsLabel.setPosition(targetWidth - 80, targetHeight - 20);
                stage.addActor(fpsLabel);
            }
            if (logPane != null && jsonValue.getBoolean("showLogger")) {
                registerSceneHud(logPane);
                logPane.setPosition(0, 0);
                stage.addActor(logPane);
            }
        } else {
            Sink.log(className + ": Scene Does not Exist");
        }
    }

    /**
     * Returns the current scene being Displayed on stage
     **/
    public static Object getScene() {
        return currentScene;
    }

    /**
     * Changes to the next scene in the scnesList
     **/
    public static void nextScene() {
        if (sceneIndex <= scenesList.size)
            sceneIndex++;
        setScene(scenesList.get(sceneIndex));
    }

    /**
     * Changes to the previous scene in the scnesList
     **/
    public static void prevScene() {
        if (sceneIndex >= 0)
            sceneIndex--;
        setScene(scenesList.get(sceneIndex));
    }

    /**
     * This loads the fonts for fps and logPane from the skin file. This is called by Asset once the
     * assets are done loading
     * */
    static void setup() {
        fpsLabel = new Label("", Asset.skin);
        logPane = new LogPane(Asset.skin);
    }

    /**
     * This loads the fonts for fps and logPane from a BitmapFont. This is called by Asset once the
     * assets are done loading
     * */
    static void setup(BitmapFont font) {
        if (font != null) {
            LabelStyle ls = new LabelStyle();
            ls.font = font;
            fpsLabel = new Label("", ls);
            logPane = new LogPane(font);
        }
    }

    /* If you want to make any elements/actors to move along with the camera like HUD's add them using
     * this method
     */
    public static void registerSceneHud(Actor actor) {
        if (!hudActors.contains(actor, false))
            hudActors.add(actor);
    }

    /* If you want to stop any elements/actors from moving along with the camera like HUD's you can stop them
     * by using this method
     */
    public static void unregisterSceneHud(Actor actor) {
        hudActors.removeValue(actor, false);
    }

    public static void clearSceneHud() {
        hudActors.clear();
    }

    public static void addActor(Actor actor) {
        stage.addActor(actor);
    }

    public static void addActor(Actor actor, float x, float y) {
        actor.setPosition(x, y);
        stage.addActor(actor);
    }

    public static boolean removeActor(Actor actor) {
        return stage.getRoot().removeActor(actor);
    }

    public static void addAction(Action action) {
        stage.addAction(action);
    }

    public static void removeAction(Action action) {
        stage.getRoot().removeAction(action);
    }

    public static Actor findActor(String actorName) {
        return stage.getRoot().findActor(actorName);
    }

    public static SnapshotArray<Actor> getChildren() {
        return stage.getRoot().getChildren();
    }

    public static Actor hit(float x, float y) {
        return stage.getRoot().hit(x, y, true);
    }

    private static Image imgbg = null;

    public static void setBackground(String texName) {
        if (imgbg != null)
            removeBackground();
        if (Asset.tex(texName) != null) {
            imgbg = new Image(new TextureRegionDrawable(Asset.tex(texName)), Scaling.stretch);
            imgbg.setFillParent(true);
            stage.addActor(imgbg);
            imgbg.toBack();
            Sink.log("Sink: Background Image Set " + texName);
        }
    }

    public static void removeBackground() {
        stage.getRoot().removeActor(imgbg);
    }

    private static void load() {
        Sink.log("Loading");
        if (firstScene) {
            firstScene = false;
            // First time Splash Scene do not load serializers as assets are yet to be loaded
            return;
        }
        if (!serializerlock)
            initSerializers();
        FileHandle fh = Gdx.files.internal(Asset.basePath + "scene/" + currentSceneName + ".json");
        if (fh.exists()) {
            String[] lines = fh.readString("UTF-8").split("\n");
            for (String line : lines) {
                if (line.trim().isEmpty())
                    continue;
                JsonValue jv = jsonReader.parse(line);
                deserialize(jv.get("class").asString(), line);
            }
        }
        Sink.log("Loaded");
    }

    /* This is used by the studio so dont use this */
    public static void load(String sceneName) {
        Sink.log("Loading");
        currentSceneName = sceneName;
        FileHandle fh = Gdx.files.internal(Asset.basePath + "scene/" + currentSceneName + ".json");
        if (fh.exists()) {
            String[] lines = fh.readString("UTF-8").split("\n");
            for (String line : lines) {
                if (line.trim().isEmpty())
                    continue;
                JsonValue jv = jsonReader.parse(line);
                deserialize(jv.get("class").asString(), line);
            }
        }
        Sink.log("Loaded");
    }

    /* This is used by the studio so dont use this */
    public static void save() {
        Sink.log("Saving");
        StringBuilder sb = new StringBuilder();
        sb.append("{class:SceneJson,");
        sb.append("background:\"" + sceneBackground + "\",");
        sb.append("music:\"" + sceneMusic + "\",");
        sb.append("transition:\"" + sceneTransition + "\",");
        sb.append("duration:" + sceneDuration + ",");
        for (int i = 0; i < interpolationsValue.length; i++) {
            if (sceneInterpolation.equals(interpolationsValue[i])) {
                sb.append("interpolation:" + Interpolations.values()[i].toString() + "}");
                break;
            }
        }
        sb.append("\n");
        for (Actor actor : getChildren()) {
            if (actor.getName() != null) {
                sb.append(json.toJson(actor));
                sb.append("\n");
            }
        }
        FileHandle fh = Gdx.files.local(Asset.basePath + "scene/" + currentSceneName + ".json");
        if (fh.exists())
            fh.writeString(sb.toString(), false, "UTF-8");
        Sink.log("Saved");
    }

    public static enum Transitions {
        None, leftToRight, rightToLeft, upToDown, downToUp, FadeIn, FadeOut, ScaleIn, ScaleOut
    };

    public static enum Interpolations {
        Bounce, BounceIn, BounceOut, Circle, CircleIn, CircleOut, Elastic, ElasticIn, ElasticOut, Exp10, Exp10In, Exp10Out, Exp5, Exp5In, Exp5Out, Linear, Fade, Pow2, Pow2In, Pow2Out, Pow3, Pow3In, Pow3Out, Pow4, Pow4In, pow4Out, Pow5, Pow5In, Pow5Out, Sine, SineIn, SineOut, Swing, SwingIn, SwingOut
    };

    public static Interpolation[] interpolationsValue = { Interpolation.bounce, Interpolation.bounceIn,
            Interpolation.bounceOut, Interpolation.circle, Interpolation.circleIn, Interpolation.circleOut,
            Interpolation.elastic, Interpolation.elasticIn, Interpolation.elasticOut, Interpolation.exp10,
            Interpolation.exp10In, Interpolation.exp10Out, Interpolation.exp5, Interpolation.exp5In,
            Interpolation.exp5Out, Interpolation.linear, Interpolation.fade, Interpolation.pow2,
            Interpolation.pow2In, Interpolation.pow2Out, Interpolation.pow3, Interpolation.pow3In,
            Interpolation.pow3Out, Interpolation.pow4, Interpolation.pow4In, Interpolation.pow4Out,
            Interpolation.pow5, Interpolation.pow5In, Interpolation.pow5Out, Interpolation.sine,
            Interpolation.sineIn, Interpolation.sineOut, Interpolation.swing, Interpolation.swingIn,
            Interpolation.swingOut, };

    public static void deserialize(String className, String value) {
        if (className.equals("SceneJson")) {
            JsonValue jv = jsonReader.parse(value);
            sceneBackground = jv.getString("background");
            sceneMusic = jv.getString("music");
            sceneTransition = jv.getString("transition");
            sceneDuration = jv.getFloat("duration");
            Sink.setBackground(sceneBackground);
            Asset.musicPlay(sceneMusic);
            Interpolations interp = Interpolations.valueOf(jv.getString("interpolation"));
            int i = 0;
            for (Interpolations interpolation : Interpolations.values()) {
                if (interp.equals(interpolation)) {
                    sceneInterpolation = interpolationsValue[i];
                    break;
                }
                i++;
            }
            switch (Transitions.valueOf(sceneTransition)) {
            case None:
                break;
            case leftToRight:
                transitionLeftToRight();
                break;
            case rightToLeft:
                transitionRightToLeft();
                break;
            case upToDown:
                transitionUpToDown();
                break;
            case downToUp:
                transitionDownToUp();
                break;
            case FadeIn:
                transitionFadeIn();
                break;
            case FadeOut:
                transitionFadeOut();
                break;
            case ScaleIn:
                transitionScaleIn();
                break;
            case ScaleOut:
                transitionScaleOut();
                break;
            }
        } else {
            try {
                Class cc = Class.forName(className);
                addActor((Actor) json.fromJson(cc, value));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    public static void registerSerializer(Class clazz, Json.Serializer serializer) {
        json.setSerializer(clazz, serializer);
    }

    public static void initSerializers() {
        /*registerSerializer(ActorJson.class, new ActorJson());*/
        registerSerializer(ImageJson.class, new ImageJson());
        registerSerializer(LabelJson.class, new LabelJson());
        registerSerializer(ButtonJson.class, new ButtonJson());
        registerSerializer(TextButtonJson.class, new TextButtonJson());
        registerSerializer(TableJson.class, new TableJson());
        registerSerializer(CheckBoxJson.class, new CheckBoxJson());
        registerSerializer(SelectBoxJson.class, new SelectBoxJson());
        registerSerializer(ListJson.class, new ListJson());
        registerSerializer(SliderJson.class, new SliderJson());
        registerSerializer(TextFieldJson.class, new TextFieldJson());
        registerSerializer(DialogJson.class, new DialogJson());
        registerSerializer(TouchpadJson.class, new TouchpadJson());
        serializerlock = true;
    }

    /***********************************************************************************************************
    *                Camera Related Functions                                                       *
    ************************************************************************************************************/
    private static float duration;
    private static float time;
    private static Interpolation interpolation;
    private static boolean complete;
    private static float lastPercent;
    private static float panSpeedX, panSpeedY;
    private static final Vector3 mousePos = new Vector3();

    // try to re-implement this with statetime
    private void updateController() {
        float delta = Gdx.graphics.getDeltaTime();
        if (!complete)
            moveByAction(delta);
        if (hasControl) {
            if (Config.usePan)
                panCameraWithMouse();
            if (Config.useKeyboard)
                panCameraWithKeyboard();
        }
        if (followedActor != null)
            follow();
    }

    public static void moveTo(Actor actor) {
        camera.position.x = actor.getX();
        camera.position.y = actor.getY();
        for (Actor hudactor : Sink.hudActors)
            hudactor.setPosition(camera.position.x + hudactor.getWidth() / 12 - targetWidth / 2,
                    camera.position.y + hudactor.getHeight() / 2 - targetHeight / 2);
    }

    public void moveTo(float x, float y) {
        camera.position.x = x;
        camera.position.y = y;
        for (Actor hudactor : Sink.hudActors)
            hudactor.setPosition(x - targetWidth / 2, y - targetHeight / 2);
    }

    /** Moves the actor instantly. */
    public static void moveBy(float amountX, float amountY) {
        moveBy(amountX, amountY, 0, null);
    }

    public static void moveBy(float amountX, float amountY, float duration) {
        moveBy(amountX, amountY, duration, null);
    }

    public static void moveBy(float amountX, float amountY, float dur, Interpolation interp) {
        duration = dur;
        interpolation = interp;
        panSpeedX = amountX;
        panSpeedY = amountY;
        lastPercent = 0;
        restart();
    }

    private void moveByAction(float delta) {
        time += delta;
        complete = time >= duration;
        float percent;
        if (complete)
            percent = 1;
        else {
            percent = time / duration;
            if (interpolation != null)
                percent = interpolation.apply(percent);
        }
        updateMoveBy(percent);
        if (complete)
            end();
    }

    private void updateMoveBy(float percent) {
        updateRelativeMoveBy(percent - lastPercent);
        lastPercent = percent;
    }

    private void updateRelativeMoveBy(float percentDelta) {
        camera.translate(panSpeedX * percentDelta, panSpeedY * percentDelta, 0);
        for (Actor actor : Sink.hudActors)
            actor.setPosition(actor.getX() + panSpeedX * percentDelta, actor.getY() + panSpeedY * percentDelta);
    }

    private static void restart() {
        time = 0;
        complete = false;
    }

    private void reset() {
        interpolation = null;
    }

    private void end() {
        reset();
    }

    public static float getCameraWidth() {
        return camera.viewportWidth;
    }

    public static float getCameraHeight() {
        return camera.viewportHeight;
    }

    public static float getCameraX() {
        return camera.position.x;
    }

    public static float getCameraY() {
        return camera.position.y;
    }

    /***********************************************************************************************************
    *                Controller Related Functions                                                 *
    ************************************************************************************************************/
    private static boolean hasControl = false;

    public static void enablePanning() {
        hasControl = true;
    }

    public static void disablePanning() {
        hasControl = false;
    }

    public static void followActor(Actor actor) {
        followedActor = actor;
    }

    private static Actor followedActor;
    private float followSpeed = 3;
    private float followTopOffset = 60;
    private float followLeftOffset = 10;
    private float followBotOffset = 70;
    private float followRightOffset = 10;

    public void setFollowActorOffset(float top, float left, float bot, float right) {
        followTopOffset = top;
        followLeftOffset = left;
        followBotOffset = bot;
        followRightOffset = right;
    }

    public void setFollowSpeed(float speed) {
        followSpeed = speed;
    }

    private void follow() {
        if (camera.position.x < followedActor.getX() - followLeftOffset)
            moveBy(followSpeed, 0);
        else if (camera.position.x > followedActor.getX() + followRightOffset)
            moveBy(-followSpeed, 0);
        else if (camera.position.y < followedActor.getY() - followBotOffset)
            moveBy(0, followSpeed);
        else if (camera.position.y > followedActor.getY() - followTopOffset)
            moveBy(0, -followSpeed);
        else
            followedActor = null;
    }

    private float panSpeed = 5f;
    private float panXLeftOffset = 100;
    private float panXRightOffset = cfg.width - 100;
    private float panYUpOffset = 70;
    private float panYDownOffset = cfg.height - 70;
    public static float camOffsetX = 160f;
    public static float camOffsetYTop = 110f;
    public static float camOffsetYBot = 65f;
    public static float mapOffsetX = 0;
    public static float mapOffsetY = 0;

    public void setPanSpeed(float speed) {
        panSpeed = speed;
    }

    public void setPanOffset(float xLeft, float xRight, float yUp, float dDown) {
        panXLeftOffset = xLeft;
        panXRightOffset = xRight;
        panYUpOffset = yUp;
        panYDownOffset = dDown;
    }

    public void setCamOffset(float xOffset, float yOffsetTop, float yOffsetBot) {
        camOffsetX = xOffset;
        camOffsetYTop = yOffsetTop;
        camOffsetYBot = yOffsetBot;
    }

    private void panCameraWithMouse() {
        mousePos.x = Gdx.input.getX();
        mousePos.y = Gdx.input.getY();
        if (mousePos.x > panXRightOffset && camera.position.x < mapOffsetX - 5)
            moveBy(panSpeed, 0);
        else if (mousePos.x < panXLeftOffset && camera.position.x > camOffsetX + 5)
            moveBy(-panSpeed, 0);
        else if (mousePos.y < panYUpOffset && camera.position.y < mapOffsetY - 5)
            moveBy(0, panSpeed);
        else if (mousePos.y > panYDownOffset && camera.position.y > camOffsetYBot + 5)
            moveBy(0, -panSpeed);
    }

    private void panCameraWithKeyboard() {
        if (Gdx.input.isKeyPressed(Keys.LEFT))
            //if(camera.position.x > camOffsetX +5)
            moveBy(-panSpeed, 0);
        else if (Gdx.input.isKeyPressed(Keys.RIGHT))
            //if(camera.position.x < mapOffsetX - 5)
            moveBy(panSpeed, 0);
        else if (Gdx.input.isKeyPressed(Keys.UP))
            //if(camera.position.y < mapOffsetY -5)
            moveBy(0, panSpeed);
        else if (Gdx.input.isKeyPressed(Keys.DOWN))
            //if(camera.position.y > camOffsetYBot +5)
            moveBy(0, -panSpeed);
    }

    private final static Vector3 curr = new Vector3();
    private final static Vector3 last = new Vector3(-1, -1, -1);
    private final static Vector3 delta = new Vector3();
    private static float deltaCamX = 0;
    private static float deltaCamY = 0;

    private static void dragCam(int x, int y) {
        camera.unproject(curr.set(x, y, 0));
        if (!(last.x == -1 && last.y == -1 && last.z == -1)) {
            camera.unproject(delta.set(last.x, last.y, 0));
            delta.sub(curr);
            deltaCamX = delta.x + camera.position.x;
            deltaCamY = delta.y + camera.position.y;
            if (deltaCamX > camOffsetX && deltaCamX < mapOffsetX)
                moveBy(delta.x, 0);
            if (deltaCamY > camOffsetYBot && deltaCamY < mapOffsetY)
                moveBy(0, delta.y);
        }
        last.set(x, y, 0);
    }

    private final static ClickListener touchInput = new ClickListener() {
        @Override
        public void clicked(InputEvent event, float x, float y) {
            super.clicked(event, x, y);
            selectedActor = hit(x, y);
            if (selectedActor != null)
                for (ClickedListener cl : clickedListeners)
                    cl.onClicked();
        }

        @Override
        public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
            super.touchDown(event, x, y, pointer, button);
            return true;
        }

        @Override
        public void touchDragged(InputEvent event, float x, float y, int pointer) {
            super.touchDragged(event, x, y, pointer);
            for (DraggedListener dl : draggedListeners)
                dl.onDragged();
            if (hasControl)
                if (Config.useDrag)
                    dragCam((int) x, (int) -y);
        }

        @Override
        public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
            super.touchUp(event, x, y, pointer, button);

            if (hasControl)
                last.set(-1, -1, -1);
        }
    };

    public void touchPad(float xPercent, float yPercent) {
    }

    /***********************************************************************************************************
    *                Transition Related Functions                                                 *
    ************************************************************************************************************/

    public static void transitionLeftToRight() {
        stage.getRoot().setPosition(-999, 0);
        addAction(Actions.moveTo(0, 0, sceneDuration, sceneInterpolation));
    }

    public static void transitionRightToLeft() {
        stage.getRoot().setPosition(999, 0);
        addAction(Actions.moveTo(0, 0, sceneDuration, sceneInterpolation));
    }

    public static void transitionUpToDown() {
        stage.getRoot().setPosition(0, 999);
        addAction(Actions.moveTo(0, 0, sceneDuration, sceneInterpolation));
    }

    public static void transitionDownToUp() {
        stage.getRoot().setPosition(0, -999);
        addAction(Actions.moveTo(0, 0, sceneDuration, sceneInterpolation));
    }

    public static void transitionFadeIn() {
        Color color = stage.getRoot().getColor();
        color.a = 0f;
        stage.getRoot().setColor(color);
        addAction(Actions.fadeIn(sceneDuration, sceneInterpolation));
    }

    public static void transitionFadeOut() {
        Action action2 = new Action() {
            @Override
            public boolean act(float delta) {
                Color color = stage.getRoot().getColor();
                color.a = 1f;
                stage.getRoot().setColor(color);
                return true;
            }
        };
        addAction(Actions.sequence(Actions.fadeOut(sceneDuration, sceneInterpolation), action2));
    }

    public static void transitionScaleIn() {
        Sink.stage.getRoot().setScale(0, 0);
        addAction(Actions.scaleTo(1, 1, sceneDuration, sceneInterpolation));
    }

    public static void transitionScaleOut() {
        Action action2 = new Action() {
            @Override
            public boolean act(float delta) {
                stage.getRoot().scale(1f);
                return true;
            }
        };
        addAction(Actions.sequence(Actions.scaleTo(1, 1, sceneDuration, sceneInterpolation), action2));
    }
}

/*
 * This class is used to display the sink logs on the game screen itself so it
 * becomes easier to track game states can be disabled in the options
*/
class LogPane extends SceneGroup {
    Label logLabel;
    ScrollPane scroll;

    public LogPane(Skin skin) {
        setSize(300, 100);
        logLabel = new Label("", skin);
        scroll = new ScrollPane(logLabel);
        scroll.setPosition(0, 0);
        scroll.setSize(300, 100);
        scroll.setBounds(0, 0, 300, 100);
        addActor(scroll);
    }

    public LogPane(BitmapFont font) {
        setSize(300, 100);
        LabelStyle ls = new LabelStyle();
        ls.font = font;
        logLabel = new Label("", ls);
        scroll = new ScrollPane(logLabel);
        scroll.setPosition(0, 0);
        scroll.setSize(300, 100);
        scroll.setBounds(0, 0, 300, 100);
        addActor(scroll);
    }

    public void update(String text) {
        logLabel.setText(logLabel.getText() + "\n" + text);
        scroll.setScrollPercentY(100);
    }
}