es.eucm.ead.engine.demobuilder.ExecutableDemoBuilder.java Source code

Java tutorial

Introduction

Here is the source code for es.eucm.ead.engine.demobuilder.ExecutableDemoBuilder.java

Source

/**
 * eAdventure is a research project of the
 *    e-UCM research group.
 *
 *    Copyright 2005-2014 e-UCM research group.
 *
 *    You can access a list of all the contributors to eAdventure at:
 *          http://e-adventure.e-ucm.es/contributors
 *
 *    e-UCM is a research group of the Department of Software Engineering
 *          and Artificial Intelligence at the Complutense University of Madrid
 *          (School of Computer Science).
 *
 *          CL Profesor Jose Garcia Santesmases 9,
 *          28040 Madrid (Madrid), Spain.
 *
 *          For more info please visit:  <http://e-adventure.e-ucm.es> or
 *          <http://www.e-ucm.es>
 *
 * ****************************************************************************
 *
 *  This file is part of eAdventure
 *
 *      eAdventure is free software: you can redistribute it and/or modify
 *      it under the terms of the GNU Lesser General Public License as published by
 *      the Free Software Foundation, either version 3 of the License, or
 *      (at your option) any later version.
 *
 *      eAdventure is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU Lesser General Public License for more details.
 *
 *      You should have received a copy of the GNU Lesser General Public License
 *      along with eAdventure.  If not, see <http://www.gnu.org/licenses/>.
 */
package es.eucm.ead.engine.demobuilder;

import com.badlogic.gdx.Application;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.vividsolutions.jts.geom.Geometry;
import es.eucm.ead.builder.DemoBuilder;
import es.eucm.ead.engine.EngineDesktop;
import es.eucm.ead.engine.assets.GameAssets;
import es.eucm.ead.engine.demobuilder.img.ImgMagickUtils;
import es.eucm.ead.engine.demobuilder.img.ImgUtils;
import es.eucm.ead.engine.utils.DesktopImageUtils;
import es.eucm.ead.engine.utils.GeometryUtils;
import es.eucm.ead.schema.components.ModelComponent;
import es.eucm.ead.schema.data.Dimension;
import es.eucm.ead.schema.data.Parameter;
import es.eucm.ead.schema.data.shape.Polygon;
import es.eucm.ead.schema.data.shape.Rectangle;
import es.eucm.ead.schema.entities.ModelEntity;
import es.eucm.ead.schema.renderers.Frame;
import es.eucm.ead.schema.renderers.Frames;
import es.eucm.ead.schema.renderers.Image;
import es.eucm.ead.schema.renderers.Renderer;
import es.eucm.ead.schema.renderers.ShapeRenderer;
import es.eucm.ead.schema.renderers.State;
import es.eucm.ead.schema.renderers.States;
import es.eucm.utils.gdx.ZipUtils;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * DemoBuilder is a tool for helping make simple demo games quickly. Games
 * created with the tool can be saved automatically to a temp location on disk
 * and run by the engine. Use {@link #build()}, {@link #save()}, {@link #run()}
 * and {@link #clean()} for more info.
 * 
 * Usage: To create a new demo with the tool, create a class that extends
 * DemoBuilder. Then implement {@link #doBuild()} and, perhaps,
 * {@link #assetPaths()}, just in case you want to have all your asset paths
 * defined in one place. You may also consider overriding
 * {@link #getDescription()} and {@link #getSnapshotUri()} if you plan to add
 * the demo to DemoLauncher.
 * 
 * All the resources of the demo (images, sounds, etc.) must be zipped into a
 * file that should be available to DemoBuilder at runtime. DemoBuilder will
 * unzip these files to the temp location of the game. The information required
 * for resolving the location of the zipFile is placed at construction time (see
 * {@link #ExecutableDemoBuilder(String)}).
 */
public abstract class ExecutableDemoBuilder extends DemoBuilder {

    protected GameAssets gameAssets;

    /* To avoid building entities more than once */
    protected boolean built = false;

    /* Convenient container of asset paths - no actual need to use it */
    protected String[] assets;

    /* points to the temp folder where the game is saved */
    protected FileHandle rootFolder;
    /*
     * relative path of zip file with resources for this game (with no
     * extension)
     */
    protected String root;

    // To determine image dimensions
    protected DesktopImageUtils imageUtils;

    /*
     * Parameter that indicates whether the png files must be converted using
     * demoBuilderImgUtils to a format that is known to work with libgdx
     */
    protected boolean convertPNGs = false;

    /*
     * Used to
     */
    protected ImgUtils demoBuilerImgUtils;

    public ExecutableDemoBuilder(String root, ImgUtils demoBuilerImgUtils) {
        this.root = root;
        this.demoBuilerImgUtils = demoBuilerImgUtils;
    }

    public ExecutableDemoBuilder(String root) {
        this(root, new ImgMagickUtils());
    }

    public void prepare() {
        imageUtils = new DesktopImageUtils();
        this.gameAssets = new GameAssets(Gdx.files, imageUtils);
    }

    // ///////////////////////////////////////////////////
    // Private methods
    // //////////////////////////////////////////////////
    /**
     * Creates a model collider for the given image
     */
    protected Array<Polygon> createSchemaCollider(String imageUri) {

        try {
            Array<Polygon> collider = new Array<Polygon>();
            Pixmap pixmap = new Pixmap(gameAssets.resolve(imageUri));
            Array<Geometry> geometryArray = GeometryUtils.findBorders(pixmap, .1, 2);
            for (Geometry geometry : geometryArray) {
                collider.add(GeometryUtils.jtsToSchemaPolygon(geometry));
            }
            pixmap.dispose();
            return collider;
        } catch (GdxRuntimeException e) {
            Gdx.app.error(LOG_TAG, "An error occurred creating the collider for the next image: " + imageUri
                    + ". Sometimes that's to do with unsupported PNG features. Image properties will be shown.");
            demoBuilerImgUtils.showImageProperties(gameAssets.resolve(imageUri).path());
            return null;
        }
    }

    protected Dimension getRendererDimension(Renderer component) {
        int width = 0, height = 0;
        if (component instanceof Image) {
            Image image = (Image) component;
            Dimension dim = getImageDimension(image.getUri());
            width = dim.getWidth();
            height = dim.getHeight();
        } else if (component instanceof Frames) {
            Frames frames = (Frames) component;
            for (Frame frame : frames.getFrames()) {
                Dimension frameDim = getRendererDimension(frame.getRenderer());
                width = Math.max(width, frameDim.getWidth());
                height = Math.max(height, frameDim.getHeight());
            }
        } else if (component instanceof States) {
            States states = (States) component;
            for (State state : states.getStates()) {
                Dimension stateDim = getRendererDimension(state.getRenderer());
                width = Math.max(width, stateDim.getWidth());
                height = Math.max(height, stateDim.getHeight());
            }
        }
        Dimension dimension = new Dimension();
        dimension.setWidth(width);
        dimension.setHeight(height);
        return dimension;
    }

    protected Dimension getImageDimension(String imageUri) {
        Dimension dimension = new Dimension();
        Vector2 size = new Vector2();
        imageUtils.imageSize(gameAssets.resolve(imageUri), size);
        dimension.setWidth((int) size.x);
        dimension.setHeight((int) size.y);
        return dimension;
    }

    protected Dimension adjustOrigin(ModelEntity entity) {
        // /////// Entity adjustments
        // Calculate current dimension
        Dimension actualDim = null;
        for (ModelComponent component : entity.getComponents()) {
            if (component instanceof Renderer) {
                actualDim = getRendererDimension((Renderer) component);
                break;
            }
        }

        if (actualDim == null || actualDim.getWidth() == 0 || actualDim.getHeight() == 0) {
            return null;
        }

        float actualHeight = actualDim.getHeight();
        float actualWidth = actualDim.getWidth();

        // Center origin
        entity.setOriginX(actualWidth / 2.0F);
        entity.setOriginY(actualHeight / 2.0F);

        return actualDim;
    }

    // ////////////////////////////////////////////////////
    // Public methods for building, saving, and running the game
    // ////////////////////////////////////////////////////

    /**
     * Builds the game, saves it to disk, and runs it.
     */
    public void run() {
        final EngineDesktop engine = new EngineDesktop((int) gameWidth, (int) gameHeight) {
            @Override
            protected void dispose() {
                ExecutableDemoBuilder.this.clean();
                super.dispose();
            }
        };

        prepare();
        if (!built) {
            build();
        }
        save();
        engine.setSize((int) gameWidth, (int) gameHeight);
        engine.run(rootFolder.file().getAbsolutePath(), false);
        if (debug()) {
            Gdx.app.postRunnable(new Runnable() {
                @Override
                public void run() {
                    Stage stage = engine.getApplicationListener().getStage();
                    stage.setDebugAll(true);
                }
            });
        }
        Gdx.app.setLogLevel(Application.LOG_DEBUG);
    }

    /**
     * Builds all the entities of the game, and sets up assets, but does not
     * save the game to disk or run the game. Use {@link #save()} or
     * {@link #run()} instead.
     * 
     * @return The hashmap with all the entities of the game
     */
    public HashMap<String, ModelEntity> build() {
        createOutputFolder();
        assets = assetPaths();
        doBuild();
        built = true;
        return entities;
    }

    /**
     * Saves all entities to the temp folder ({@link #rootFolder}). Should be
     * invoked always after {@link #build()}.
     * 
     * After this method is invoked, {@link #getRootFolder()} can be used to
     * determine the location of the folder this game was saved to.
     */
    public void save() {
        for (Map.Entry<String, ModelEntity> entry : entities.entrySet()) {
            FileHandle fh = rootFolder.child(entry.getKey());
            if (fh.isDirectory()) {
                fh.mkdirs();
            } else if (!fh.parent().exists()) {
                fh.parent().mkdirs();
            }
            Gdx.app.debug(LOG_TAG, "Saving to: " + fh.file().getAbsolutePath());
            gameAssets.toJson(entry.getValue(), null, fh);
        }
    }

    /**
     * Creates the output folder and extracts contents from the zip. Needed
     * before building
     */
    protected void createOutputFolder() {
        rootFolder = FileHandle.tempDirectory(root);
        rootFolder.mkdirs();

        FileHandle zip = gameAssets.resolve(root + ".zip");
        if (zip.exists()) {
            ZipUtils.unzip(zip, rootFolder);

            if (convertPNGs) {
                demoBuilerImgUtils.convertPNGs(rootFolder);
            }

        }
        gameAssets.setLoadingPath(rootFolder.file().getAbsolutePath(), false);
    }

    /**
     * @return An input stream ready for reading a snapshot image of the game
     *         for preview, or null if no image is available. By default, it is
     *         assumed that an image with name {@code root} and png extension
     *         will be available. Demos can define their snapshot to be in
     *         another location or with another name by overriding
     *         {@link #getSnapshotUri()}.
     */
    public InputStream getSnapshotInputStream() {
        return ClassLoader.getSystemResourceAsStream(getSnapshotUri());
    }

    /**
     * @return An external FileHandle pointing to the temp folder where the game
     *         was saved
     */
    public FileHandle getRootFolder() {
        return rootFolder;
    }

    /**
     * Deletes the whole temp folder the game was saved to. Should be called
     * once the game is not planned to be run anymore.
     */
    public void clean() {
        if (built && rootFolder != null) {
            rootFolder.deleteDirectory();
            built = false;
            entities.clear();
            rootFolder = null;
            lastEntity = lastScene = null;
            lastComponent = null;
            sceneCount = 0;
        }
    }

    // /////////////////////////////////////////////////////////////////////////////
    // Protected methods that subclasses may want or need to implement or
    // override
    // /////////////////////////////////////////////////////////////////////////////

    /**
     * Sets up {@link #assets}. Useful to encapsulate asset paths all in one
     * place
     */
    protected String[] assetPaths() {
        return new String[] {};
    }

    /**
     * @return A textual description of the demo (for DemoLauncher). By default,
     *         "No description available is returned". Is strongly encouraged
     *         for subclasses to override this method.
     */
    public String getDescription() {
        return "No description available";
    }

    /**
     * @return The path of the snapshot image for the demo.
     */
    protected String getSnapshotUri() {
        return root + ".png";
    }

    /**
     * @return A name of the demo, for the DemoLauncher. By default returns the
     *         relative path ({@link #root}).
     */
    public String getName() {
        return root;
    }

    public boolean debug() {
        return false;
    }

    // //////////////////////////////////////////////////////////
    // Methods for accessing last entities and component
    // /////////////////////////////////////////////////////////

    /**
     * Creates a game with just one scene configured with the given
     * {@code backgroundUri} image. The size of the screen (game width and
     * height) is determined automatically from the size of the image.
     * 
     * @param backgroundUri
     *            Relative uri of the background image of the scene
     */
    public ExecutableDemoBuilder singleSceneGame(String backgroundUri) {
        Dimension backgroundDim = getImageDimension(backgroundUri);
        super.singleSceneGame(backgroundUri, backgroundDim.getWidth(), backgroundDim.getHeight());
        return this;
    }

    /**
     * Creates a new entity with the given screen alignment as a child of the
     * {@code parent} entity provided.
     * 
     * @param parent
     *            The entity to add this entity to to
     * @param imageUri
     *            The relative uri of the image to serve as renderer
     * @param verticalAlign
     *            Can be TOP (sticks the entity to screen top), BOTTOM (sticks
     *            the entity to screen bottom) or CENTER (places the entity
     *            vertically centered on the screen).
     * @param horizontalAlign
     *            Can be LEFT(sticks the entity to the left of the screen),
     *            RIGHT (sticks the entity to the right of the screen) or CENTER
     *            (places the entity horizontally on the screen).
     */
    public ExecutableDemoBuilder entity(ModelEntity parent, String imageUri, VerticalAlign verticalAlign,
            HorizontalAlign horizontalAlign) {
        Dimension imageDim = getImageDimension(imageUri);
        super.entity(parent, imageUri, verticalAlign, horizontalAlign, (float) imageDim.getWidth(),
                (float) imageDim.getHeight());
        return this;
    }

    /**
     * Calculates the width and height of the given {@code entity} and adjusts
     * its origin to be centered (width/2, height/2).
     * 
     * @param entity
     *            The entity whose origin is to be centered
     * @return This object, for chaining calls
     */
    public ExecutableDemoBuilder centerOrigin(ModelEntity entity) {
        adjustOrigin(entity);
        return this;
    }

    /**
     * Calculates the width and height of the last entity added and adjusts its
     * origin to be centered (width/2, height/2). Equivalent to
     * {@code centerOrigin(getLastEntity())}
     * 
     * @return This object, for chaining calls
     */
    public ExecutableDemoBuilder centerOrigin() {
        return centerOrigin(getLastEntity());
    }

    @Override
    protected Image createImage(String uri) {
        Image image = super.createImage(uri);
        image.setCollider(createSchemaCollider(uri));
        return image;
    }

    protected ShapeRenderer rectangle(int width, int height) {
        ShapeRenderer shapeRenderer = new ShapeRenderer();
        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(width);
        rectangle.setHeight(height);
        shapeRenderer.setShape(rectangle);
        return shapeRenderer;
    }

    protected Parameter param(String name, String value) {
        Parameter parameter = new Parameter();
        parameter.setName(name);
        parameter.setValue(value);
        return parameter;
    }
}