org.bladecoder.bladeengine.model.Scene.java Source code

Java tutorial

Introduction

Here is the source code for org.bladecoder.bladeengine.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 org.bladecoder.bladeengine.model;

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

import org.bladecoder.bladeengine.assets.AssetConsumer;
import org.bladecoder.bladeengine.assets.EngineAssetManager;
import org.bladecoder.bladeengine.pathfinder.NavNode;
import org.bladecoder.bladeengine.polygonalpathfinder.NavNodePolygonal;
import org.bladecoder.bladeengine.polygonalpathfinder.PolygonalNavGraph;
import org.bladecoder.bladeengine.util.EngineLogger;

import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
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.Json;
import com.badlogic.gdx.utils.Json.Serializable;
import com.badlogic.gdx.utils.JsonValue;

public class Scene implements Serializable, AssetConsumer {

    public static final Color ACTOR_BBOX_COLOR = new Color(0.2f, 0.2f, 0.8f, 1f);
    private static final TextureFilter BG_TEXFILTER_MAG = TextureFilter.Linear;
    private static final TextureFilter BG_TEXFILTER_MIN = TextureFilter.Linear;
    public static final Color WALKZONE_COLOR = Color.GREEN;
    public static final Color OBSTACLE_COLOR = Color.RED;

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

    /**
     * Foreground actors. Non interactive. Always draw this actors in the
     * foreground
     */
    private ArrayList<SpriteActor> fgActors = new ArrayList<SpriteActor>();

    /**
     * Temp list with the 'actors' list + player ordered by 'y' axis to draw in
     * depth order and to check for click
     */
    private final List<Actor> orderedActors = new ArrayList<Actor>();

    private SceneCamera camera = new SceneCamera();

    private Texture[] background;
    private Texture[] lightMap;
    private String backgroundFilename;
    private String lightMapFilename;

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

    /** depth vector. x: scale when y=0, y: scale when y=scene height */
    private Vector2 depthVector;

    /** Overlay image drew over the scene */
    private OverlayImage overlay;

    /** For FADEIN/FADEOUT */
    private Transition transition;

    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;

    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();

    /**
     * Add support for the use of global custom properties/variables in the game
     * logic
     */
    private HashMap<String, String> customProperties;

    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 void setCustomProperty(String name, String value) {
        if (customProperties == null)
            customProperties = new HashMap<String, String>();

        customProperties.put(name, value);
    }

    public String getCustomProperty(String name) {
        return customProperties.get(name);
    }

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

    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;

        // interpolation equation
        return Math.abs(depthVector.x + (depthVector.y - depthVector.x) * y / camera.getScrollingHeight());
    }

    public void setMusic(String filename, boolean loop, float initialDelay, float repeatDelay) {
        loopMusic = loop;
        musicFilename = filename;
        initialMusicDelay = initialDelay;
        repeatMusicDelay = 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
        Collections.sort(orderedActors);

        if (overlay != null) {
            overlay.update(delta);

            if (overlay.isFinish()) {
                overlay.dispose();
                overlay = null;
            }
        }

        if (transition != null) {
            transition.update(delta);

            if (transition.isFinish()) {
                transition = null;
            }
        }

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

            if (currentMusicDelay <= initialMusicDelay)
                initialTime = true;

            currentMusicDelay += delta;

            if (initialTime) {
                if (currentMusicDelay > initialMusicDelay)
                    playMusic();
            } else {
                if (repeatMusicDelay >= 0 && currentMusicDelay > repeatMusicDelay + initialMusicDelay) {
                    currentMusicDelay = initialMusicDelay;
                    playMusic();
                }
            }
        }

        for (Actor a : orderedActors) {
            if (a instanceof SpriteActor)
                ((SpriteActor) a).update(delta);
        }

        camera.update(delta);
    }

    public void draw(SpriteBatch spriteBatch) {

        if (background != null) {
            spriteBatch.disableBlending();

            float x = 0;

            for (Texture tile : background) {
                spriteBatch.draw(tile, x, 0f);
                x += tile.getWidth();
            }

            spriteBatch.enableBlending();
        }

        for (Actor a : orderedActors) {
            if (a instanceof SpriteActor)
                ((SpriteActor) a).draw(spriteBatch);
        }

        for (SpriteActor a : fgActors) {
            a.draw(spriteBatch);
        }

        // Draw the light map
        if (lightMap != null) {
            // Multiplicative blending for light maps
            spriteBatch.setBlendFunction(GL20.GL_DST_COLOR, GL20.GL_ZERO);

            float x = 0;

            for (Texture tile : lightMap) {
                spriteBatch.draw(tile, x, 0f);
                x += tile.getWidth();
            }

            spriteBatch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
        }

        if (overlay != null) {
            overlay.draw(spriteBatch);
        }

        if (EngineLogger.debugMode() && EngineLogger.getDebugLevel() == EngineLogger.DEBUG1) {

            StringBuilder sb = new StringBuilder();

            for (Actor a : orderedActors) {
                Rectangle r = a.getBBox().getBoundingRectangle();
                sb.setLength(0);
                sb.append(a.getId());
                if (a.getState() != null)
                    sb.append(".").append(a.getState());
                // sb.append(" (").append((int) r.getX()).append(", ");
                // sb.append((int) r.getY()).append(", ").append((int)
                // r.getWidth());
                // sb.append(", ").append((int) r.getHeight()).append(") ");
                EngineLogger.getDebugFont().draw(spriteBatch, sb.toString(), r.getX(), r.getY());
            }

        }
    }

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

        for (Actor a : orderedActors) {
            Polygon p = a.getBBox();

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

            Rectangle r = a.getBBox().getBoundingRectangle();

            renderer.polygon(p.getTransformedVertices());
            renderer.rect(r.getX(), r.getY(), r.getWidth(), r.getHeight());
        }

        for (SpriteActor a : fgActors) {
            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());

            ArrayList<Polygon> obstacles = polygonalNavGraph.getObstacles();

            renderer.setColor(OBSTACLE_COLOR);
            for (Polygon p : obstacles) {
                renderer.polygon(p.getTransformedVertices());
            }

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

        renderer.end();
    }

    public ArrayList<SpriteActor> getForegroundActors() {
        return fgActors;
    }

    public void setOverlay(OverlayImage o) {
        if (overlay != null)
            overlay.dispose();

        overlay = o;
    }

    public OverlayImage getOverlay() {
        return overlay;
    }

    public void setTransition(Transition t) {
        transition = t;
    }

    public Transition getTransition() {
        return transition;
    }

    public Actor getActor(String id) {
        return getActor(id, true, false);
    }

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

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

        if (a == null && searchFG) {
            for (SpriteActor fg : fgActors) {
                if (fg.getId().equals(id))
                    a = fg;
            }
        }

        return a;
    }

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

    public void addActor(Actor actor) {
        actors.put(actor.getId(), actor);
        orderedActors.add(actor);
        actor.setScene(this);
    }

    public void addFgActor(SpriteActor actor) {
        fgActors.add(actor);
        // ADD SCENE?
    }

    public void setBackground(String bgFilename, String lightMapFilename) {

        if (bgFilename != null && !bgFilename.isEmpty()) {
            ArrayList<String> tiles = getTilesByFilename(bgFilename);

            if (tiles.size() > 0) {
                backgroundFilename = bgFilename;
                background = new Texture[tiles.size()];
            }
        }

        // SET LIGHT MAP
        if (lightMapFilename != null && !lightMapFilename.isEmpty()) {
            ArrayList<String> tiles = getTilesByFilename(lightMapFilename);

            if (tiles.size() > 0) {
                this.lightMapFilename = lightMapFilename;
                lightMap = new Texture[tiles.size()];
            }
        }
    }

    /**
     * Search for files based in the filename parameter. Used for bg images and maps.
     * 
     * ex. for filename 'bg_0.png': bg_0.png, bg_1.png, bg_2.png... ex. for
     * filename 'bg.png': bg.png, bg_1.png, bg_2.png...
     * 
     * @param filename
     *            The filename used as search base
     * @return
     */
    private ArrayList<String> getTilesByFilename(String filename) {
        ArrayList<String> tiles = new ArrayList<String>();

        StringBuilder sb = new StringBuilder();

        // name without extension
        String name = filename.substring(0, filename.lastIndexOf('.'));
        // extension
        String ext = filename.substring(filename.lastIndexOf('.'));
        String nameWithoutIndex = name.endsWith("_0") ? name.substring(0, name.length() - 2) : name;

        int i = 0;

        sb.append(EngineAssetManager.BACKGROUND_DIR).append(filename);

        while (EngineAssetManager.getInstance().assetExists(sb.toString())) {
            i++;

            tiles.add(sb.toString());
            sb.setLength(0);
            sb.append(EngineAssetManager.BACKGROUND_DIR).append(nameWithoutIndex).append("_").append(i).append(ext);
        }

        return tiles;
    }

    public Actor getActorAt(float x, float y) {
        // Se recorre la lista al revs para quedarnos con el ms cercano a la
        // cmara
        for (int i = orderedActors.size() - 1; i >= 0; i--) {
            Actor a = orderedActors.get(i);

            if (a.hit(x, y) && !a.getId().equals(player) && a.hasInteraction()) {
                return a;
            }
        }

        return null;
    }

    /**
     * Method used for the editor to select one actor.
     * 
     * @param x
     * @param y
     * @return
     */
    public Actor getFullSearchActorAt(float x, float y) {
        // Se recorre la lista al revs para quedarnos con el ms cercano a la
        // cmara
        for (int i = orderedActors.size() - 1; i >= 0; i--) {
            Actor a = orderedActors.get(i);

            if (a.getBBox().getBoundingRectangle().contains(x, y)) {
                return a;
            }
        }

        for (Actor a : fgActors) {
            if (a.getBBox().getBoundingRectangle().contains(x, y)) {
                return a;
            }
        }

        return null;
    }

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

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

    public Vector2 getDepthVector() {
        return depthVector;
    }

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

    public void removeActor(Actor a) {
        Actor res = null;

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

        res = actors.remove(a.getId());

        if (res == null)
            fgActors.remove(a);
        else
            orderedActors.remove(a);

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

    }

    public Texture[] 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 (background != null) {
            ArrayList<String> tiles = getTilesByFilename(backgroundFilename);

            // LOAD BACKGROUND TEXTURES
            for (String filename : tiles) {
                EngineAssetManager.getInstance().loadTexture(filename);
            }

        }

        // LOAD LIGHT MAP
        if (lightMap != null) {
            ArrayList<String> tiles = getTilesByFilename(lightMapFilename);

            for (String filename : tiles) {
                EngineAssetManager.getInstance().loadTexture(filename);
            }
        }

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

        for (Actor a : actors.values()) {
            a.loadAssets();
        }

        for (SpriteActor a : fgActors) {
            a.loadAssets();
        }
    }

    @Override
    public void retrieveAssets() {

        // RETRIEVE BACKGROUND
        if (background != null) {
            ArrayList<String> tiles = getTilesByFilename(backgroundFilename);

            int width = 0;

            for (int i = 0; i < background.length; i++) {
                Texture texture = EngineAssetManager.getInstance().getTexture(tiles.get(i));
                texture.setFilter(BG_TEXFILTER_MIN, BG_TEXFILTER_MAG);
                background[i] = texture;
                width += texture.getWidth();
            }

            int height = background[0].getHeight();

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

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

        // RETRIEVE LIGHT MAP
        if (lightMap != null) {
            ArrayList<String> tiles = getTilesByFilename(lightMapFilename);

            for (int i = 0; i < lightMap.length; i++) {
                Texture texture = EngineAssetManager.getInstance().getTexture(tiles.get(i));
                // texture.setFilter(BG_TEXFILTER_MIN, BG_TEXFILTER_MAG);
                texture.setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
                lightMap[i] = texture;
            }
        }

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

        // RETRIEVE ACTORS
        for (Actor a : actors.values()) {
            a.retrieveAssets();
        }

        for (SpriteActor a : fgActors) {
            a.retrieveAssets();
        }

        if (musicFilename != null) {
            music = EngineAssetManager.getInstance().getMusic(musicFilename);
            if (isPlayingSer) { // TODO must be in World???
                playMusic();
                isPlayingSer = false;
            }
        }

        if (overlay != null)
            overlay.retrieveAssets();
    }

    @Override
    public void dispose() {

        if (background != null) {
            for (Texture tile : background)
                if (tile != null)
                    EngineAssetManager.getInstance().disposeTexture(tile);
        }

        if (lightMap != null) {
            for (Texture tile : lightMap)
                if (tile != null)
                    EngineAssetManager.getInstance().disposeTexture(tile);
        }

        // orderedActors.clear();

        for (Actor a : actors.values()) {
            a.dispose();
        }

        for (SpriteActor a : fgActors) {
            a.dispose();
        }

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

        if (overlay != null) {
            overlay.dispose();
            overlay = null;
        }

        transition = null;
    }

    public PolygonalNavGraph getPolygonalNavGraph() {
        return polygonalNavGraph;
    }

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

    // TODO SAVE BG WIDTH AND HEIGHT + WALKZONE
    @Override
    public void write(Json json) {
        json.writeValue("id", id);
        json.writeValue("state", state, state == null ? null : state.getClass());
        json.writeValue("verbs", verbs);

        json.writeValue("actors", actors);
        json.writeValue("fgActors", fgActors);
        json.writeValue("player", player, player == null ? null : player.getClass());

        json.writeValue("background", backgroundFilename,
                backgroundFilename == null ? null : backgroundFilename.getClass());

        json.writeValue("lightMap", lightMapFilename,
                lightMapFilename == null ? null : lightMapFilename.getClass());

        json.writeValue("musicFilename", musicFilename, musicFilename == null ? null : musicFilename.getClass());
        json.writeValue("loopMusic", loopMusic);
        json.writeValue("initialMusicDelay", initialMusicDelay);
        json.writeValue("repeatMusicDelay", repeatMusicDelay);

        json.writeValue("isPlaying", music != null && music.isPlaying());
        // TODO save music positionSer when available in API

        json.writeValue("overlay", overlay, overlay == null ? null : overlay.getClass());
        json.writeValue("transition", transition, transition == null ? null : transition.getClass());

        json.writeValue("camera", camera);

        json.writeValue("followActor", followActor == null ? null : followActor.getId(),
                followActor == null ? null : String.class);

        json.writeValue("customProperties", customProperties,
                customProperties == null ? null : customProperties.getClass());

        json.writeValue("depthVector", depthVector);

        json.writeValue("polygonalNavGraph", polygonalNavGraph,
                polygonalNavGraph == null ? null : PolygonalNavGraph.class);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void read(Json json, JsonValue jsonData) {
        id = json.readValue("id", String.class, jsonData);
        state = json.readValue("state", String.class, jsonData);
        verbs = json.readValue("verbs", VerbManager.class, jsonData);

        actors = json.readValue("actors", HashMap.class, Actor.class, jsonData);
        fgActors = json.readValue("fgActors", ArrayList.class, SpriteActor.class, jsonData);
        player = json.readValue("player", String.class, jsonData);

        for (Actor a : actors.values()) {
            orderedActors.add(a);
            a.setScene(this);
        }

        backgroundFilename = json.readValue("background", String.class, jsonData);
        lightMapFilename = json.readValue("lightMap", String.class, jsonData);

        setBackground(backgroundFilename, lightMapFilename);

        musicFilename = json.readValue("musicFilename", String.class, jsonData);
        loopMusic = json.readValue("loopMusic", Boolean.class, jsonData);
        initialMusicDelay = json.readValue("initialMusicDelay", Float.class, jsonData);
        repeatMusicDelay = json.readValue("repeatMusicDelay", Float.class, jsonData);

        isPlayingSer = json.readValue("isPlaying", Boolean.class, jsonData);
        // TODO restore positionSer for music when available in API

        overlay = json.readValue("overlay", OverlayImage.class, jsonData);
        transition = json.readValue("transition", Transition.class, jsonData);

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

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

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

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