com.bladecoder.engine.model.Scene.java Source code

Java tutorial

Introduction

Here is the source code for com.bladecoder.engine.model.Scene.java

Source

/*******************************************************************************
 * Copyright 2014 Rafael Garcia Moreno.
 * 
 * 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 com.bladecoder.engine.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Polygon;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.Json.Serializable;
import com.badlogic.gdx.utils.JsonValue;
import com.bladecoder.engine.actions.SceneActorRef;
import com.bladecoder.engine.assets.AssetConsumer;
import com.bladecoder.engine.assets.EngineAssetManager;
import com.bladecoder.engine.polygonalpathfinder.NavNodePolygonal;
import com.bladecoder.engine.polygonalpathfinder.PolygonalNavGraph;
import com.bladecoder.engine.util.EngineLogger;
import com.bladecoder.engine.util.SerializationHelper;
import com.bladecoder.engine.util.SerializationHelper.Mode;

public class Scene implements Serializable, AssetConsumer {

    public static final Color ACTOR_BBOX_COLOR = new Color(0.2f, 0.2f, 0.8f, 1f);
    public static final Color WALKZONE_COLOR = Color.GREEN;
    public static final Color OBSTACLE_COLOR = Color.RED;
    public static final Color ANCHOR_COLOR = Color.RED;
    public static final float ANCHOR_RADIUS = 14f;

    /**
     * All actors in the scene
     */
    private HashMap<String, BaseActor> actors = new HashMap<String, BaseActor>();

    /**
     * BaseActor layers
     */
    private List<SceneLayer> layers = new ArrayList<SceneLayer>();

    private SceneCamera camera = new SceneCamera();

    private Array<AtlasRegion> background;
    private String backgroundAtlas;
    private String backgroundRegionId;

    /** For polygonal PathFinding */
    private PolygonalNavGraph polygonalNavGraph;

    /**
     * depth vector. X: the actor 'y' position for a 0.0 scale, Y: the actor 'y'
     * position for a 1.0 scale.
     */
    private Vector2 depthVector;

    private String player;

    /** The actor the camera will follow */
    private SpriteActor followActor;

    private Music music = null;
    private boolean loopMusic = false;
    private float repeatMusicDelay = 0;
    private float initialMusicDelay = 0;
    private float currentMusicDelay = 0;

    private String musicFilename;
    private boolean isPlayingSer = false;
    private float musicPosSer = 0;
    private Vector2 sceneSize;

    transient private boolean isMusicPaused = false;

    private String id;

    /** internal state. Can be used for actions to maintain a state machine */
    private String state;

    private VerbManager verbs = new VerbManager();

    public Scene() {
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getState() {
        return state;
    }

    public void setState(String s) {
        state = s;
    }

    public List<SceneLayer> getLayers() {
        return layers;
    }

    public SceneLayer getLayer(String name) {
        for (SceneLayer l : layers) {
            if (name.equals(l.getName()))
                return l;
        }

        return null;
    }

    public void addLayer(SceneLayer layer) {
        layers.add(layer);
    }

    public void playMusic() {
        if (music != null && !music.isPlaying()) {
            music.play();
            music.setLooping(isLoopMusic());
        }
    }

    public float getRepeatMusicDelay() {
        return repeatMusicDelay;
    }

    public boolean isLoopMusic() {
        return loopMusic;
    }

    public void setLoopMusic(boolean loopMusic) {
        this.loopMusic = loopMusic;
    }

    public float getCurrentMusicDelay() {
        return currentMusicDelay;
    }

    public void setCurrentMusicDelay(float currentMusicDelay) {
        this.currentMusicDelay = currentMusicDelay;
    }

    public float getInitialMusicDelay() {
        return initialMusicDelay;
    }

    public void setInitialMusicDelay(float initialMusicDelay) {
        this.initialMusicDelay = initialMusicDelay;
    }

    public void setRepeatMusicDelay(float repeatMusicDelay) {
        this.repeatMusicDelay = repeatMusicDelay;
    }

    public void pauseMusic() {
        if (music != null && music.isPlaying()) {
            music.pause();
            isMusicPaused = true;
        }
    }

    public void resumeMusic() {
        if (music != null && isMusicPaused) {
            music.play();
            isMusicPaused = false;
        }
    }

    public void stopMusic() {
        if (music != null)
            music.stop();
    }

    public float getFakeDepthScale(float y) {
        if (depthVector == null)
            return 1.0f;

        float worldScale = EngineAssetManager.getInstance().getScale();

        return Math.max(0, (y - depthVector.x * worldScale) / ((depthVector.y - depthVector.x) * worldScale));
    }

    public void setMusic(String filename, boolean loop, float initialDelay, float repeatDelay) {
        if (filename != null && filename.isEmpty())
            filename = null;

        setLoopMusic(loop);
        musicFilename = filename;
        setInitialMusicDelay(initialDelay);
        setRepeatMusicDelay(repeatDelay);
    }

    public VerbManager getVerbManager() {
        return verbs;
    }

    public Verb getVerb(String id) {
        return verbs.getVerb(id, state, null);
    }

    public void runVerb(String id) {
        verbs.runVerb(id, state, null);
    }

    public void update(float delta) {
        // We draw the elements in order: from top to bottom.
        // so we need to order the array list
        for (SceneLayer layer : layers)
            layer.update();

        // music delay update
        if (music != null && !music.isPlaying()) {
            boolean initialTime = false;

            if (getCurrentMusicDelay() <= getInitialMusicDelay())
                initialTime = true;

            setCurrentMusicDelay(getCurrentMusicDelay() + delta);

            if (initialTime) {
                if (getCurrentMusicDelay() > getInitialMusicDelay())
                    playMusic();
            } else {
                if (getRepeatMusicDelay() >= 0
                        && getCurrentMusicDelay() > getRepeatMusicDelay() + getInitialMusicDelay()) {
                    setCurrentMusicDelay(getInitialMusicDelay());
                    playMusic();
                }
            }
        }

        for (BaseActor a : actors.values()) {
            a.update(delta);
        }

        camera.update(delta);

        if (followActor != null) {
            camera.updatePos(followActor);
        }
    }

    public void draw(SpriteBatch batch) {

        if (background != null) {
            batch.disableBlending();
            batch.setProjectionMatrix(camera.calculateParallaxMatrix(1, 1));
            batch.begin();

            float x = 0;

            for (AtlasRegion tile : background) {
                batch.draw(tile, x, 0f);
                x += tile.getRegionWidth();
            }

            batch.end();
            batch.enableBlending();
        }

        // draw layers from bottom to top
        for (int i = layers.size() - 1; i >= 0; i--) {
            SceneLayer layer = layers.get(i);

            batch.setProjectionMatrix(camera.calculateParallaxMatrix(layer.getParallaxMultiplier(), 1));
            batch.begin();
            layer.draw(batch);
            batch.end();
        }
    }

    public void drawBBoxLines(ShapeRenderer renderer) {
        // renderer.begin(ShapeType.Rectangle);
        renderer.begin(ShapeType.Line);

        for (BaseActor a : actors.values()) {
            Polygon p = a.getBBox();

            if (p == null) {
                EngineLogger.error("ERROR DRAWING BBOX FOR: " + a.getId());
            }

            if (a instanceof ObstacleActor) {
                renderer.setColor(OBSTACLE_COLOR);
                renderer.polygon(p.getTransformedVertices());
            } else if (a instanceof AnchorActor) {
                renderer.setColor(Scene.ANCHOR_COLOR);
                renderer.line(p.getX() - Scene.ANCHOR_RADIUS, p.getY(), p.getX() + Scene.ANCHOR_RADIUS, p.getY());
                renderer.line(p.getX(), p.getY() - Scene.ANCHOR_RADIUS, p.getX(), p.getY() + Scene.ANCHOR_RADIUS);
            } else {
                renderer.setColor(ACTOR_BBOX_COLOR);
                renderer.polygon(p.getTransformedVertices());
            }

            // Rectangle r = a.getBBox().getBoundingRectangle();
            // renderer.rect(r.getX(), r.getY(), r.getWidth(), r.getHeight());
        }

        if (polygonalNavGraph != null) {
            renderer.setColor(WALKZONE_COLOR);
            renderer.polygon(polygonalNavGraph.getWalkZone().getTransformedVertices());

            // DRAW LINEs OF SIGHT
            renderer.setColor(Color.WHITE);
            ArrayList<NavNodePolygonal> nodes = polygonalNavGraph.getGraphNodes();
            for (NavNodePolygonal n : nodes) {
                for (NavNodePolygonal n2 : n.neighbors) {
                    renderer.line(n.x, n.y, n2.x, n2.y);
                }
            }
        }

        renderer.end();
    }

    public BaseActor getActor(String id, boolean searchInventory) {
        BaseActor a = actors.get(id);

        if (a == null && searchInventory) {
            a = World.getInstance().getInventory().getItem(id);
        }

        return a;
    }

    public HashMap<String, BaseActor> getActors() {
        return actors;
    }

    public void addActor(BaseActor actor) {
        actors.put(actor.getId(), actor);
        actor.setScene(this);

        if (actor instanceof InteractiveActor) {
            InteractiveActor ia = (InteractiveActor) actor;

            SceneLayer layer = getLayer(ia.getLayer());

            if (layer == null) { // fallback for compatibility
                layer = new SceneLayer();
                layer.setName(ia.getLayer());
                layers.add(layer);
            }

            layer.add(ia);
        }
    }

    public void setBackground(String bgAtlas, String bgId, String lightMapAtlas, String lightMapId) {
        this.backgroundAtlas = bgAtlas;
        this.backgroundRegionId = bgId;
    }

    /**
     * Returns the Interactive actor at the position. The actor must have the
     * interaction property enabled.
     */
    public InteractiveActor getInteractiveActorAt(float x, float y) {

        for (SceneLayer layer : layers) {

            if (!layer.isVisible())
                continue;

            // Obtain actors in reverse (close to camera)
            for (int i = layer.getActors().size() - 1; i >= 0; i--) {
                BaseActor a = layer.getActors().get(i);

                if (a instanceof InteractiveActor && ((InteractiveActor) a).canInteract() && a.hit(x, y)) {
                    return (InteractiveActor) a;
                }
            }
        }

        return null;
    }

    private Rectangle tmpToleranceRect = new Rectangle();

    /**
     * Obtains the actor at (x,y) with TOLERANCE.
     * 
     * Creates a square with size = TOLERANCE and checks:
     * 
     * 1. if some vertex from the TOLERANCE square is inside an actor bbox 2. if
     * some actor bbox vertex is inside the TOLERANCE square
     */
    public InteractiveActor getInteractiveActorAt(float x, float y, float tolerance) {
        if (tolerance <= 0) {
            return getInteractiveActorAt(x, y);
        }

        List<SceneLayer> layers = getLayers();

        tmpToleranceRect.x = x - tolerance / 2;
        tmpToleranceRect.y = y - tolerance / 2;
        tmpToleranceRect.width = tolerance;
        tmpToleranceRect.height = tolerance;

        for (SceneLayer layer : layers) {

            if (!layer.isVisible())
                continue;

            // Obtain actors in reverse (close to camera)
            for (int l = layer.getActors().size() - 1; l >= 0; l--) {
                BaseActor a = layer.getActors().get(l);

                if (a instanceof InteractiveActor && ((InteractiveActor) a).canInteract()) {

                    if (a.hit(x, y) || a.hit(tmpToleranceRect.x, tmpToleranceRect.y)
                            || a.hit(tmpToleranceRect.x + tmpToleranceRect.width, tmpToleranceRect.y)
                            || a.hit(tmpToleranceRect.x, tmpToleranceRect.y + tmpToleranceRect.height)
                            || a.hit(tmpToleranceRect.x + tmpToleranceRect.width,
                                    tmpToleranceRect.y + tmpToleranceRect.height))
                        return (InteractiveActor) a;

                    float[] verts = a.getBBox().getTransformedVertices();
                    for (int i = 0; i < verts.length; i += 2) {
                        float vx = verts[i];
                        float vy = verts[i + 1];

                        if (tmpToleranceRect.contains(vx, vy))
                            return (InteractiveActor) a;
                    }
                }
            }
        }

        return null;
    }

    /**
     * Returns the actor at the position. Include not interactive actors.
     */
    public BaseActor getActorAt(float x, float y) {

        for (SceneLayer layer : layers) {

            if (!layer.isVisible())
                continue;

            // Obtain actors in reverse (close to camera)
            for (int i = layer.getActors().size() - 1; i >= 0; i--) {
                BaseActor a = layer.getActors().get(i);

                if (a.hit(x, y)) {
                    return a;
                }
            }
        }

        // if not found check for obstacles and anchors
        for (BaseActor a : actors.values()) {
            if (a instanceof ObstacleActor && a.hit(x, y)) {
                return a;
            } else if (a instanceof AnchorActor) {
                float dst = Vector2.dst(x, y, a.getX(), a.getY());

                if (dst < ANCHOR_RADIUS)
                    return a;
            }
        }

        return null;
    }

    public void setPlayer(CharacterActor a) {
        if (a != null) {
            player = a.getId();
            a.setInteraction(false);
        } else {
            player = null;
        }
    }

    public CharacterActor getPlayer() {
        return (CharacterActor) actors.get(player);
    }

    public Vector2 getDepthVector() {
        return depthVector;
    }

    public void setDepthVector(Vector2 v) {
        depthVector = v;
    }

    public String getBackgroundAtlas() {
        return backgroundAtlas;
    }

    public void setBackgroundAtlas(String backgroundAtlas) {
        this.backgroundAtlas = backgroundAtlas;
    }

    public String getBackgroundRegionId() {
        return backgroundRegionId;
    }

    public void setBackgroundRegionId(String backgroundRegionId) {
        this.backgroundRegionId = backgroundRegionId;
    }

    public String getMusicFilename() {
        return musicFilename;
    }

    public void setMusicFilename(String musicFilename) {
        this.musicFilename = musicFilename;
    }

    public void removeActor(BaseActor a) {

        if (player != null && a.getId().equals(player)) {
            player = null;
        }

        BaseActor r = actors.remove(a.getId());

        if (r == null) {
            EngineLogger.error("Removing actor from scene: Actor not found");
            return;
        }

        if (a instanceof InteractiveActor) {
            InteractiveActor ia = (InteractiveActor) a;
            SceneLayer layer = getLayer(ia.getLayer());
            layer.getActors().remove(ia);
        }

        if (a instanceof ObstacleActor && polygonalNavGraph != null)
            polygonalNavGraph.removeDinamicObstacle(a.getBBox());

        a.setScene(null);

    }

    public Array<AtlasRegion> getBackground() {
        return background;
    }

    public SceneCamera getCamera() {
        return camera;
    }

    public void resetCamera(float worldWidth, float worldHeight) {
        camera.create(worldWidth, worldHeight);

        if (getPlayer() != null)
            setCameraFollowActor(getPlayer());
    }

    public void setCameraFollowActor(SpriteActor a) {
        followActor = a;

        if (a != null)
            camera.updatePos(a);
    }

    public SpriteActor getCameraFollowActor() {
        return followActor;
    }

    @Override
    public void loadAssets() {

        if (backgroundAtlas != null && !backgroundAtlas.isEmpty()) {
            EngineAssetManager.getInstance().loadAtlas(backgroundAtlas);
        }

        if (musicFilename != null)
            EngineAssetManager.getInstance().loadMusic(musicFilename);

        for (BaseActor a : actors.values()) {
            if (a instanceof AssetConsumer)
                ((AssetConsumer) a).loadAssets();
        }

        // CALC WALK GRAPH
        if (polygonalNavGraph != null) {
            polygonalNavGraph.createInitialGraph(actors.values());
        }
    }

    @Override
    public void retrieveAssets() {

        // RETRIEVE BACKGROUND
        if (backgroundAtlas != null && !backgroundAtlas.isEmpty()) {
            background = EngineAssetManager.getInstance().getRegions(backgroundAtlas, backgroundRegionId);

            int width = 0;

            for (int i = 0; i < background.size; i++) {
                width += background.get(i).getRegionWidth();
            }

            int height = background.get(0).getRegionHeight();

            // Sets the scrolling dimensions. It must be done here because
            // the background must be loaded to calculate the bbox   
            if (sceneSize == null)
                camera.setScrollingDimensions(width, height);

            // if(followActor != null)
            // camera.updatePos(followActor);
        }

        if (sceneSize != null)
            camera.setScrollingDimensions(sceneSize.x, sceneSize.y);

        // RETRIEVE ACTORS
        for (BaseActor a : actors.values()) {
            if (a instanceof AssetConsumer)
                ((AssetConsumer) a).retrieveAssets();
        }

        if (musicFilename != null) {
            music = EngineAssetManager.getInstance().getMusic(musicFilename);
            if (isPlayingSer) { // TODO must be in World???
                if (music != null) {
                    music.setPosition(musicPosSer);
                    musicPosSer = 0f;
                }

                playMusic();
                isPlayingSer = false;
            }
        }
    }

    @Override
    public void dispose() {

        if (backgroundAtlas != null && !backgroundAtlas.isEmpty()) {
            EngineAssetManager.getInstance().disposeAtlas(backgroundAtlas);
        }

        // orderedActors.clear();

        for (BaseActor a : actors.values()) {
            if (a instanceof AssetConsumer)
                ((AssetConsumer) a).dispose();
        }

        if (musicFilename != null && music != null) {
            EngineAssetManager.getInstance().disposeMusic(musicFilename);
            music = null;
        }
    }

    public Vector2 getSceneSize() {
        return sceneSize;
    }

    public void setSceneSize(Vector2 sceneSize) {
        this.sceneSize = sceneSize;
    }

    public void orderLayersByZIndex() {
        for (SceneLayer l : layers) {
            l.orderByZIndex();
        }
    }

    public PolygonalNavGraph getPolygonalNavGraph() {
        return polygonalNavGraph;
    }

    public void setPolygonalNavGraph(PolygonalNavGraph polygonalNavGraph) {
        this.polygonalNavGraph = polygonalNavGraph;
    }

    @Override
    public void write(Json json) {
        if (SerializationHelper.getInstance().getMode() == Mode.MODEL) {

            json.writeValue("id", id);
            json.writeValue("layers", layers, layers.getClass(), SceneLayer.class);

            json.writeValue("actors", actors);

            if (backgroundAtlas != null) {
                json.writeValue("backgroundAtlas", backgroundAtlas);
                json.writeValue("backgroundRegionId", backgroundRegionId);
            }

            if (musicFilename != null) {
                json.writeValue("musicFilename", musicFilename);
                json.writeValue("loopMusic", loopMusic);
                json.writeValue("initialMusicDelay", initialMusicDelay);
                json.writeValue("repeatMusicDelay", repeatMusicDelay);
            }

            if (depthVector != null)
                json.writeValue("depthVector", depthVector);

            if (polygonalNavGraph != null)
                json.writeValue("polygonalNavGraph", polygonalNavGraph);

            if (sceneSize != null)
                json.writeValue("sceneSize", sceneSize);

        } else {
            SceneActorRef actorRef;

            json.writeObjectStart("actors");
            for (BaseActor a : actors.values()) {
                actorRef = new SceneActorRef(a.getInitScene(), a.getId());
                json.writeValue(actorRef.toString(), a);
            }
            json.writeObjectEnd();

            if (musicFilename != null) {
                json.writeValue("isPlaying", music != null && music.isPlaying());
                json.writeValue("musicPos", music != null && music.isPlaying() ? music.getPosition() : 0f);
            }

            json.writeValue("camera", camera);

            if (followActor != null)
                json.writeValue("followActor", followActor.getId());
        }

        verbs.write(json);

        if (state != null)
            json.writeValue("state", state);

        if (player != null)
            json.writeValue("player", player);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void read(Json json, JsonValue jsonData) {
        if (SerializationHelper.getInstance().getMode() == Mode.MODEL) {

            id = json.readValue("id", String.class, jsonData);
            layers = json.readValue("layers", ArrayList.class, SceneLayer.class, jsonData);
            actors = json.readValue("actors", HashMap.class, BaseActor.class, jsonData);

            for (BaseActor actor : actors.values()) {
                actor.setScene(this);
                actor.setInitScene(id);

                if (actor instanceof InteractiveActor) {
                    InteractiveActor ia = (InteractiveActor) actor;

                    SceneLayer layer = getLayer(ia.getLayer());
                    layer.add(ia);
                }
            }

            orderLayersByZIndex();

            backgroundAtlas = json.readValue("backgroundAtlas", String.class, jsonData);
            backgroundRegionId = json.readValue("backgroundRegionId", String.class, jsonData);

            musicFilename = json.readValue("musicFilename", String.class, jsonData);

            if (musicFilename != null) {
                loopMusic = json.readValue("loopMusic", Boolean.class, jsonData);
                initialMusicDelay = json.readValue("initialMusicDelay", Float.class, jsonData);
                repeatMusicDelay = json.readValue("repeatMusicDelay", Float.class, jsonData);
            }

            depthVector = json.readValue("depthVector", Vector2.class, jsonData);

            polygonalNavGraph = json.readValue("polygonalNavGraph", PolygonalNavGraph.class, jsonData);

            sceneSize = json.readValue("sceneSize", Vector2.class, jsonData);

        } else {
            JsonValue jsonValueActors = jsonData.get("actors");
            SceneActorRef actorRef;

            // GET ACTORS FROM HIS INIT SCENE AND MOVE IT TO THE LOADING SCENE.
            for (int i = 0; i < jsonValueActors.size; i++) {
                JsonValue jsonValueAct = jsonValueActors.get(i);
                actorRef = new SceneActorRef(jsonValueAct.name);
                Scene sourceScn = World.getInstance().getScene(actorRef.getSceneId());

                if (sourceScn != this) {
                    BaseActor actor = sourceScn.getActor(actorRef.getActorId(), false);
                    sourceScn.removeActor(actor);
                    addActor(actor);
                }
            }

            // READ ACTOR STATE. 
            // The state must be retrieved after getting actors from his init scene to restore verb cb properly.
            for (int i = 0; i < jsonValueActors.size; i++) {
                JsonValue jsonValueAct = jsonValueActors.get(i);
                actorRef = new SceneActorRef(jsonValueAct.name);

                BaseActor actor = getActor(actorRef.getActorId(), false);

                if (actor != null)
                    actor.read(json, jsonValueAct);
                else
                    EngineLogger.debug("Actor not found: " + actorRef);
            }

            orderLayersByZIndex();

            if (musicFilename != null) {
                isPlayingSer = json.readValue("isPlaying", Boolean.class, jsonData);
                musicPosSer = json.readValue("musicPos", Float.class, jsonData);
            }

            camera = json.readValue("camera", SceneCamera.class, jsonData);
            String followActorId = json.readValue("followActor", String.class, jsonData);

            setCameraFollowActor((SpriteActor) actors.get(followActorId));
        }

        verbs.read(json, jsonData);
        state = json.readValue("state", String.class, jsonData);
        player = json.readValue("player", String.class, jsonData);
    }
}