/*******************************************************************************
* 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);
}
}