com.sidereal.dolphinoes.architecture.GameBatch.java Source code

Java tutorial

Introduction

Here is the source code for com.sidereal.dolphinoes.architecture.GameBatch.java

Source

/******************************************************************************* Copyright 2014 See AUTHORS file.
 * 
 * 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.sidereal.dolphinoes.architecture;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.sidereal.dolphinoes.architecture.core.GameData.Settings;
import com.sidereal.dolphinoes.behaviors.input.Clickable;
import com.sidereal.dolphinoes.behaviors.triggers.Hoverable;
import com.sidereal.dolphinoes.util.BooleanWrapper;

/** An object used for handling multiple {@link GameObject} instances at the same time, rendering them using
 * {@link #camera} and {@link #spriteBatch}, with the possibility of adding a shader and sorting the objects, as well as
 * access to the mouse position translated to {@link #camera}'s coordinate system.
 * <p>
 * GameBatch objects are added in {@link GameScene#addGameBatch(GameBatch)} , and are iterated over in
 * {@link GameScene#render(float)}.
 * 
 * @author Claudiu */
public class GameBatch {

    // region fields

    /** Area that the camera sees and outside of which images are not rendered. */
    public Rectangle renderingArea;

    public enum ShaderType {
        Initialisation, Update, Resize
    };

    /** Sprite batch on which to render all objects that are using the {@link GameBatch} that the {@link SpriteBatch} is
     * tied to.
     * <p>
     * The shader found in {@link #shaderProgram} will be applied to this rendering batch. */
    public SpriteBatch spriteBatch;

    /** Shape renderer focused on the {@link GameBatch}'s camera. */
    public ShapeRenderer shapeRenderer;

    /** Camera used for rendering objects tied to {@link #spriteBatch}.
     * <p>
     * The camera's position can be made to target a certain {@link GameObject} by assigning {@link #target}. */
    public OrthographicCamera camera;

    /** Variable used for sorting the {@link GameBatch} rendering order in the scene. Is used in
     * {@link GameScene#gameBatchComparator}. */
    public int priorityLevel;

    /** Name of the gamebatch.
     * <p>
     * Is set in the constructor and used in {@link GameObject#setGameBatch}. */
    public String name;

    /** List of {@link GameObject} entries tied to the {@link GameBatch}. */
    public List<GameObject> objects;

    /** Whether to translate the mouse position to the {@link #camera}'s projection matrix. This will make using certain
     * IO-related behaviors such as {@link Clickable} or {@link Hoverable} work in {@link GameObject} objects tied to
     * the {@link GameBatch} */
    public boolean translateMousePosition;

    /** If not null, the {@link GameBatch} will be using the referencing batch's {@link #camera}, {@link #mousePosition}.
     * Will be set in {@link #GameBatch(GameScene, String, GameBatch)}. */
    public GameBatch referencingBatch;

    /** Whether to sort the GameObjects in {@link #objects} before updating them or not. Set in {@link #setSort(boolean, Comparator)} */
    private boolean sort;

    /** The sorting algorithm to run if {@link #sort} is true on {@link #objects} . Set in {@link #setSort(boolean, Comparator)}
     * <p>
     * By default, will first render objects at the top of the screen, with a lower level. */
    private Comparator<GameObject> objectsComparator;

    /** Thread that updates the batch, calling {@link #doUpdate()} every frame. Is called in {@link #run()}. */
    private Thread updateThread;

    /** The {@link GameScene} that the batch is found in. Set up in {@link #initialiseBatch(GameScene, String)}. */
    private GameScene scene;

    /** Object used for translating the mouse position to the camera's coordinate system. After translating it, will be
     * used to set variables in {@link #mousePosition}. */
    private Vector3 tempMousePosition;

    /** The position of the mouse, translated to the camera's coordinate system. */
    public Vector2 mousePosition;

    /** Shader to run every frame on {@link #spriteBatch}. Is set in
     * {@link #setShader(ShaderProgram, AbstractEvent, ShaderType)}. */
    private ShaderProgram shaderProgram;

    /** Event with the purpose of passing uniform values to the shader. Will be called based on {@link #shaderType}. */
    private AbstractEvent shaderUniformValuesEvent;

    /** The type of shader. Affects when {@link #shaderUniformValuesEvent} runs.
     * <p>
     * If {@link #shaderType} is set to {@link ShaderType#Initialisation}, the event is running when set in
     * {@link #setShader(ShaderProgram, AbstractEvent, ShaderType)} only.
     * <p>
     * If {@link #shaderType} is set to {@link ShaderType#Resize}, the event is running whenever {@link #resize()} is
     * called.
     * <p>
     * If {@link #shaderType} is set to {@link ShaderType#Update}, the event runs every frame in {@link #doUpdate()}. */
    private ShaderType shaderType;

    // endregion

    // region constructors

    /** Constructor for a GameBatch that calls {@link #initialiseBatch(GameScene, String)}, as well as set up the camera
     * and the mouse position as the batch does not reference anything.
     * 
     * @param scene
     *            The scene in which to add the game batch.
     * @param name
     *            name of the gameBatch, used for retrieval of Batch using {@link GameScene#getGameBatch(String)}. */
    public GameBatch(GameScene scene, String tag) {

        initialiseBatch(scene, tag);

        this.tempMousePosition = new Vector3();
        this.mousePosition = new Vector2();

        this.camera = new OrthographicCamera();
        camera.setToOrtho(false, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

        this.translateMousePosition = false;

        this.renderingArea = new Rectangle();
    }

    /** Constructor for a GameBatch that uses resources from the <code>
     * referencingBatch</code> parameter to keep consistency.
     * <p>
     * Throws a {@link GdxRuntimeException} if passed {@link GameBatch} is null.
     * 
     * @param scene
     *            The scene in which to add the game batch.
     * @param name
     *            name of the gameBatch, used for retrieval of Batch using {@link GameScene#getGameBatch(String)}.
     * @param referencingBatch */
    public GameBatch(GameScene scene, String tag, GameBatch referencingBatch) {

        initialiseBatch(scene, tag);

        if (referencingBatch == null)
            throw new GdxRuntimeException("Referencing batch in constructor for the " + tag
                    + " batch was null. Use the constructor without a batch as a parameter if no referencing batch is "
                    + "needed");

        this.referencingBatch = referencingBatch;
        this.camera = referencingBatch.camera;
        this.mousePosition = referencingBatch.mousePosition;
        this.renderingArea = referencingBatch.renderingArea;
    }

    /** Common initialisation code between the two constructors of {@link GameBatch}, initialising variables in the
     * scene.
     * <p>
     * If the scene or the batch name are null, a {@link GdxRuntimeException} is thrown.
     * 
     * @param scene
     *            The scene in which to add the game batch.
     * @param name
     *            name of the gameBatch, used for retrieval of Batch using {@link GameScene#getGameBatch(String)}. */
    private final void initialiseBatch(GameScene scene, String name) {

        if (scene == null)
            throw new GdxRuntimeException("GameScene parameter passed to GameBatch is null");
        if (name == null)
            throw new GdxRuntimeException("String parameter passed to GameBatch is null");

        this.scene = scene;
        this.name = name;

        this.shapeRenderer = new ShapeRenderer();
        this.shapeRenderer.setAutoShapeType(true);
        this.spriteBatch = new SpriteBatch();
        this.objects = new ArrayList<GameObject>();
        this.priorityLevel = 0;
        this.sort = true;

        this.objectsComparator = new Comparator<GameObject>() {
            @Override
            public int compare(GameObject o1, GameObject o2) {

                if (o1.pos.getZ() != o2.pos.getZ()) {
                    return (int) (o1.pos.getZ() - o2.pos.getZ());
                } else {
                    return (int) o2.pos.getY() - (int) o1.pos.getY();
                }

            }
        };

        updateThread = new Thread(new Runnable() {
            @Override
            public void run() {

                doUpdate();
            }
        });
    }

    // endregion

    // region methods

    /** Updates the area within which objects will be drawn. */
    private final void updateRenderingArea() {

        renderingArea.set(camera.position.x - ((camera.viewportWidth / 2f) * camera.zoom),
                camera.position.y - ((camera.viewportHeight / 2f) * camera.zoom),
                camera.viewportWidth * camera.zoom, camera.viewportHeight * camera.zoom);
    }

    /** Runs every frame, updating all game objects and the mouse position to the camera's location. Gets called from
     * {@link GameScene#render(float)}
     * 
     * @param info
     *            the game information */
    final void run() {

        if (translateMousePosition) {
            tempMousePosition.set(Gdx.input.getX(), Gdx.input.getY(), 0);

            // translate the pixel to world
            camera.unproject(tempMousePosition);

            // updating mouse position.
            mousePosition.x = tempMousePosition.x;
            mousePosition.y = tempMousePosition.y;
        }

        if (((BooleanWrapper) DolphinOES.data.getSettings(Settings.MULTI_THREADED)).get())
            updateThread.run();
        else
            doUpdate();
    }

    /** Handles setting the shader for the {@link #spriteBatch}.
     * 
     * @param program
     *            The shader to run every frame
     * @param uniformValuesEvent
     *            the event for setting uniforms.
     * @param type
     *            The type of shader, which affects when <code>uniformValuesEvent</code> gets called. */
    public final void setShader(ShaderProgram program, AbstractEvent uniformValuesEvent, ShaderType type) {

        // not using shaders in the settings.
        if (!((BooleanWrapper) DolphinOES.data.getSettings(Settings.USE_SHADERS)).get())
            return;

        // don't handle shader if it GLSL failed compilation
        if (!program.isCompiled()) {
            DolphinOES.debug.log(program.getLog());
            return;
        }

        // handling null variables
        if (program == null || type == null) {
            this.shaderProgram = null;
            this.shaderUniformValuesEvent = null;
            this.spriteBatch.setShader(null);

            return;
        }
        ShaderProgram.pedantic = false;
        this.shaderProgram = program;
        this.shaderType = type;

        this.shaderUniformValuesEvent = uniformValuesEvent;
        spriteBatch.setShader(this.shaderProgram);

        DolphinOES.debug.log("Shader compiled successfully? " + this.shaderProgram.isCompiled());

        // run initialisation or resize event
        if (this.shaderUniformValuesEvent != null
                && (shaderType.equals(ShaderType.Initialisation) || shaderType.equals(ShaderType.Resize))
                && this.shaderProgram.isCompiled()) {
            shaderProgram.begin();
            this.shaderUniformValuesEvent.run(shaderProgram);
            shaderProgram.end();
        }

    }

    /** Resizes the GameBatch, updating the camera's size, as well as the objects that depend on it, such as
     * {@link #shaderProgram}, {@link #spriteBatch} or {@link #shapeRenderer}.
     * <p>
     * All of the objects tied to the GameBatch ( the ones in {@link #objects}) are being resized by calling
     * {@link GameObject#onResize(float, float, float, float)} on all of them. */
    public final void resize() {

        float oldWidth = camera.viewportWidth;
        float oldHeight = camera.viewportHeight;

        // set up new width
        camera.setToOrtho(false, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

        // if camera has a target, set pos back to target

        camera.update();
        spriteBatch.setProjectionMatrix(camera.combined);

        shapeRenderer.setProjectionMatrix(camera.combined);

        // run resize event
        if (this.shaderUniformValuesEvent != null && this.shaderType.equals(ShaderType.Resize)
                && this.shaderProgram.isCompiled()) {
            shaderProgram.begin();
            this.shaderUniformValuesEvent.run(shaderProgram);
            shaderProgram.end();
        }

        for (int i = 0; i < objects.size(); i++) {
            if (objects.get(i).isInScene())
                objects.get(i).onResize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), oldWidth, oldHeight);
        }
    }

    /** Method that updates all objects in {@link #objects}. */
    private final void doUpdate() {

        if (objects == null || scene == null)
            return;

        // batch's scene is not the same as the current scene.
        if (DolphinOES.getScene() != null && !this.scene.equals(DolphinOES.getScene())) {
            scene = null;
            return;
        }
        if (objects.size() == 0)
            return;

        if (sort) {
            try {
                Collections.sort(objects, objectsComparator);
            } catch (Exception e) {
                DolphinOES.debug.log("Caught exception " + e.getMessage() + e.toString());
                e.printStackTrace();
            }
        }

        updateRenderingArea();

        if (shaderUniformValuesEvent != null && shaderType.equals(ShaderType.Update)
                && shaderProgram.isCompiled()) {
            shaderProgram.begin();
            shaderUniformValuesEvent.run(shaderProgram);
            shaderProgram.end();
        }

        spriteBatch.setProjectionMatrix(camera.combined);
        shapeRenderer.setProjectionMatrix(camera.combined);
        spriteBatch.begin();
        shapeRenderer.begin();

        // update objects
        if (objects != null) {
            for (int i = 0; i < objects.size(); i++) {

                objects.get(i).updateInternal();
                if (objects == null)
                    return;
            }
            if (DolphinOES.debug.isEnabled()) {
                for (int i = 0; i < objects.size(); i++) {
                    objects.get(i).updateDebug();
                }
            }

        }
        spriteBatch.end();
        shapeRenderer.end();

    }

    public void dispose() {

        spriteBatch.dispose();
    }

    /** Sets whether or not sorting should be enabled, and maybe the comparator used in sorting
     * <p>
     * If the <code>enabled</code> parameter is set to true, the <code>comparator</code> can be null only
     * if the value of the internal comparator has been set before, either in the {@link GameBatch} comparator or using
     * {@link #setSort(boolean, Comparator)}. Passing a null <code>comparator</code> parameter will imply that you don't want the
     * comparator to change
     * 
     * @param enabled Whether to enable sorting or not
     * @param comparator The Comparator to use, use null if already defined and you don't want to change it.
     */
    public void setSort(boolean enabled, Comparator<GameObject> comparator) {

        if (!enabled) {
            sort = false;
            if (comparator != null)
                objectsComparator = comparator;
            return;
        }

        if (comparator == null && objectsComparator == null)
            throw new NullPointerException(
                    "Gamebatch.setsort parameter comparator of type Comparator<GameObject> is null, and you are trying to enable"
                            + "the sorting without a predefined comparator");

        sort = enabled;
        this.objectsComparator = comparator;
    }

    // endregion
}