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