com.anythingmachine.tiledMaps.TiledMapHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.anythingmachine.tiledMaps.TiledMapHelper.java

Source

package com.anythingmachine.tiledMaps;

/**
 *   Copyright 2011 David Kirchner dpk@dpk.net
 *
 *   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.
 *   
 * TiledMapHelper can simplify your game's tiled map operations. You can find
 * some sample code using this class at my blog:
 * 
 * http://dpk.net/2011/05/08/libgdx-box2d-tiled-maps-full-working-example-part-2/
 * 
 * Note: This code does have some limitations. It only supports single-layered
 * maps.
 * 
 * This code is based on TiledMapTest.java found at:
 * http://code.google.com/p/libgdx/
 */

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import com.anythingmachine.GameStates.Containers.GamePlayManager;
import com.anythingmachine.Util.Util;
import com.anythingmachine.Util.Util.EntityType;
import com.anythingmachine.aiengine.AINode;
import com.anythingmachine.cinematics.Camera;
import com.anythingmachine.collisionEngine.Entity;
import com.anythingmachine.collisionEngine.ground.Door;
import com.anythingmachine.collisionEngine.ground.LevelWall;
import com.anythingmachine.collisionEngine.ground.Platform;
import com.anythingmachine.collisionEngine.ground.Stairs;
import com.anythingmachine.gdxwrapper.OrthoTileRenderer;
import com.anythingmachine.witchcraft.WitchCraft;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.maps.MapLayer;
import com.badlogic.gdx.maps.MapObject;
import com.badlogic.gdx.maps.MapObjects;
import com.badlogic.gdx.maps.objects.RectangleMapObject;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer.Cell;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.EdgeShape;
import com.badlogic.gdx.physics.box2d.Filter;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.Array;

public class TiledMapHelper {
    private int[] layersList;

    /**
     * Renders the part of the map that should be visible to the user.
     */
    public void render(GamePlayManager main) {
        tileMapRenderer.setView(Camera.camera);// .getProjectionMatrix().set(Camera.camera.combined);

        // Vector3 tmp = new Vector3();
        // tmp.set(0, 0, 0);
        // Camera.camera.unproject(tmp);

        tileMapRenderer.render(main);
    }

    public void render() {
        tileMapRenderer.setView(Camera.camera);// .getProjectionMatrix().set(Camera.camera.combined);
        tileMapRenderer.render();
    }

    /**
     * Get the map, useful for iterating over the set of tiles found within
     * 
     * @return TiledMap
     */
    public TiledMap getMap() {
        return map;
    }

    /**
     * Calls dispose on all disposable resources held by this object.
     */
    public void dispose() {
        // tileAtlas.dispose();
        tileMapRenderer.dispose();
    }

    /**
     * Loads the requested tmx map file in to the helper.
     * 
     * @param tmxFile
     */
    public void loadMap(String level) {

        map = WitchCraft.assetManager.get("data/world/level1/" + level + ".tmx");
        // tileAtlas = new TileAtlas(map, packFileDirectory);

        tileMapRenderer = new OrthoTileRenderer(map, 1);

        int size = getMap().getLayers().getCount();
        layersList = new int[size];
        for (int i = 0; i < size; i++) {
            layersList[i] = i;
        }
    }

    public void loadMenu(String level) {

        map = WitchCraft.assetManager.get("data/world/level1/menu" + level + ".tmx");
        // tileAtlas = new TileAtlas(map, packFileDirectory);

        tileMapRenderer = new OrthoTileRenderer(map, 1);

        int size = getMap().getLayers().getCount();
        layersList = new int[size];
        for (int i = 0; i < size; i++) {
            layersList[i] = i;
        }
    }

    public void destroyMap() {
        Array<Body> bodies = new Array<Body>();
        GamePlayManager.world.getBodies(bodies);
        Iterator it = bodies.iterator();
        while (it.hasNext()) {
            Body b = (Body) it.next();
            Object dat = b.getUserData();
            if (dat != null && dat instanceof Entity) {
                Entity e = (Entity) dat;
                if (e.type == EntityType.PLATFORM || e.type == EntityType.WALL || e.type == EntityType.LEVELWALL
                        || e.type == EntityType.STAIRS || e.type == EntityType.AINODE
                        || e.type == EntityType.DOOR) {
                    // System.out.println(e.type);
                    GamePlayManager.world.destroyBody(b);
                }
            }
            it.remove();
        }
    }

    /**
     * Reads a file describing the collision boundaries that should be set
     * per-tile and adds static bodies to the boxd world.
     * 
     * @param collisionsFile
     * @param world
     * @param pixelsPerMeter
     *            the pixels per meter scale used for this world
     */
    public void loadCollisions(String collisionsFile, World world, float pixelsPerMeter, int level) {
        /**
         * Detect the tiles and dynamically create a representation of the map
         * layout, for collision detection. Each tile has its own collision
         * rules stored in an associated file.
         * 
         * The file contains lines in this format (one line per type of tile):
         * tileNumber XxY,XxY XxY,XxY
         * 
         * Ex:
         * 
         * 3 0x0,31x0 ... 4 0x0,29x0 29x0,29x31
         * 
         * For a 32x32 tileset, the above describes one line segment for tile #3
         * and two for tile #4. Tile #3 has a line segment across the top. Tile
         * #1 has a line segment across most of the top and a line segment from
         * the top to the bottom, 30 pixels in.
         */

        // FileHandle fh = Gdx.files.internal(collisionsFile);
        // String collisionFile = fh.readString();
        // String lines[] = collisionFile.split("\\r?\\n");

        // HashMap<Integer, ArrayList<LineSegment>> tileCollisionJoints = new
        // HashMap<Integer, ArrayList<LineSegment>>();

        // /**
        // * Some locations on the map (perhaps most locations) are "undefined",
        // * empty space, and will have the tile type 0. This code adds an empty
        // * list of line segments for this "default" tile.
        // */
        // tileCollisionJoints.put(Integer.valueOf(0), new
        // ArrayList<LineSegment>());

        // for (int n = 0; n < lines.length; n++) {
        // String cols[] = lines[n].split(" ");
        // int tileNo = Integer.parseInt(cols[0]);
        //
        // ArrayList<LineSegment> tmp = new ArrayList<LineSegment>();
        //
        // for (int m = 1; m < cols.length; m++) {
        // String coords[] = cols[m].split(",");
        //
        // String start[] = coords[0].split("x");
        // String end[] = coords[1].split("x");
        //
        // tmp.add(new LineSegment(Integer.parseInt(start[0]),
        // Integer.parseInt(start[1]),
        // Integer.parseInt(end[0]), Integer.parseInt(end[1]), ""));
        // }
        //
        // tileCollisionJoints.put(Integer.valueOf(tileNo), tmp);
        // }

        ArrayList<LineSegment> collisionLineSegments = new ArrayList<LineSegment>();

        for (int l = 0; l < getMap().getLayers().getCount(); l++) {
            MapLayer nextLayer = getMap().getLayers().get(l);
            if (!nextLayer.getName().equals("AINODEMAP") && !nextLayer.getName().equals("DOORS")) {
                TiledMapTileLayer layer = (TiledMapTileLayer) nextLayer;
                if (layer.getProperties().containsKey("collide")) {
                    for (int y = 0; y < layer.getHeight(); y++) {
                        for (int x = 0; x < layer.getWidth(); x++) {
                            Cell tile = layer.getCell(x, y);
                            if (tile != null) {
                                int tileID = tile.getTile().getId();

                                int start = 0;
                                String type = "";
                                if (tile.getTile().getProperties().containsKey("type")) {
                                    type = (String) getMap().getTileSets().getTile(tileID).getProperties()
                                            .get("type");
                                }
                                if (layer.getProperties().containsKey("WALLS")) {
                                    type = "WALLS";
                                    addOrExtendCollisionLineSegment(x * 32 + 16, y * 32, x * 32 + 16, y * 32 + 32,
                                            collisionLineSegments, type);
                                } else if (layer.getProperties().containsKey("STAIRS")) {
                                    if (!type.equals("LEFTSTAIRS")) {
                                        type = "STAIRS";
                                        addOrExtendCollisionLineSegment(x * 32, y * 32, x * 32 + 32, y * 32 + 32,
                                                collisionLineSegments, type);
                                    } else {
                                        addOrExtendCollisionLineSegment(x * 32, y * 32 + 32, x * 32 + 32, y * 32,
                                                collisionLineSegments, type);
                                    }
                                } else if (layer.getProperties().containsKey("PLATFORMS")) {
                                    type = "PLATFORMS";
                                    addOrExtendCollisionLineSegment(x * 32, y * 32 + 32, x * 32 + 32, y * 32 + 32,
                                            collisionLineSegments, type);
                                }
                            }
                        }
                    }
                }
            } else

            {
                MapObjects objects = nextLayer.getObjects();
                for (MapObject o : objects) {
                    RectangleMapObject rect = (RectangleMapObject) o;
                    if (rect.getProperties().containsKey("set") || rect.getProperties().containsKey("to_level")) {
                        Rectangle shape = rect.getRectangle();

                        BodyDef nodeBodyDef = new BodyDef();
                        nodeBodyDef.type = BodyDef.BodyType.StaticBody;
                        nodeBodyDef.position.set((shape.x + shape.width * 0.5f) * Util.PIXEL_TO_BOX,
                                (shape.y + shape.height * 0.5f) * Util.PIXEL_TO_BOX);

                        Body nodeBody = GamePlayManager.world.createBody(nodeBodyDef);
                        if (!nextLayer.getName().equals("DOORS")) {
                            String set = (String) rect.getProperties().get("set");
                            nodeBody.setUserData(new AINode(Integer.parseInt(set)));
                        } else {
                            if (rect.getProperties().containsKey("to_level")
                                    && rect.getProperties().containsKey("exitX")
                                    && rect.getProperties().containsKey("exitY")) {
                                String to_level = (String) rect.getProperties().get("to_level");
                                String xPos = (String) rect.getProperties().get("exitX");
                                String yPos = (String) rect.getProperties().get("exitY");
                                nodeBody.setUserData(new Door(to_level, xPos, yPos));
                            } else {
                                nodeBody.setUserData(new Door("9999", "1024", "256"));

                            }
                        }

                        PolygonShape nodeShape = new PolygonShape();

                        nodeShape.setAsBox(shape.width * 0.5f * Util.PIXEL_TO_BOX,
                                shape.height * 0.5f * Util.PIXEL_TO_BOX);
                        FixtureDef fixture = new FixtureDef();
                        fixture.shape = nodeShape;
                        fixture.isSensor = true;
                        fixture.density = 0;
                        fixture.filter.categoryBits = Util.CATEGORY_PLATFORMS;
                        fixture.filter.maskBits = Util.CATEGORY_NPC | Util.CATEGORY_PLAYER;
                        nodeBody.createFixture(fixture);
                        nodeShape.dispose();
                    }
                }
            }
        }

        int platnum = 0;
        for (LineSegment lineSegment : collisionLineSegments)

        {
            BodyDef groundBodyDef = new BodyDef();
            groundBodyDef.type = BodyDef.BodyType.StaticBody;
            groundBodyDef.position.set(0, 0);
            Body groundBody = GamePlayManager.world.createBody(groundBodyDef);
            if (lineSegment.type.equals("STAIRS") || lineSegment.type.equals("LEFTSTAIRS")) {
                groundBody.setUserData(
                        new Stairs("stairs_" + level + "_" + platnum, lineSegment.start(), lineSegment.end()));
            } else if (lineSegment.type.equals("WALLS")) {
                groundBody.setUserData(new Entity().setType(EntityType.WALL));
            } else {
                Platform plat = new Platform("plat_" + level + "_" + platnum, lineSegment.start(),
                        lineSegment.end());
                groundBody.setUserData(plat);
                if (GamePlayManager.lowestPlatInLevel == null
                        || lineSegment.start().y < GamePlayManager.lowestPlatInLevel.getDownPosY()) {
                    GamePlayManager.lowestPlatInLevel = plat;
                }
            }
            EdgeShape environmentShape = new EdgeShape();

            environmentShape.set(lineSegment.start().scl(1 / pixelsPerMeter),
                    lineSegment.end().scl(1 / pixelsPerMeter));
            FixtureDef fixture = new FixtureDef();
            fixture.shape = environmentShape;
            fixture.isSensor = true;
            fixture.density = 1f;
            fixture.filter.categoryBits = Util.CATEGORY_PLATFORMS;
            fixture.filter.maskBits = Util.CATEGORY_NPC | Util.CATEGORY_PLAYER | Util.CATEGORY_PARTICLES;
            groundBody.createFixture(fixture);
            environmentShape.dispose();
        }

        /**
         * Drawing a boundary around the entire map. We can't use a box because
         * then the world objects would be inside and the physics engine would
         * try to push them out.
         */

        TiledMapTileLayer layer = (TiledMapTileLayer) getMap().getLayers().get(3);

        BodyDef bodydef = new BodyDef();
        bodydef.type = BodyType.StaticBody;
        bodydef.position.set(0, 0);
        Body body = GamePlayManager.world.createBody(bodydef);

        // left wall
        EdgeShape mapBounds = new EdgeShape();
        if (level == 1)

        {
            mapBounds.set(new Vector2(0.0f, 0.0f), new Vector2(0, layer.getHeight() * 32).scl(Util.PIXEL_TO_BOX));
            body.setUserData(new Entity().setType(EntityType.WALL));
            Fixture fixture = body.createFixture(mapBounds, 0);
            Filter filter = new Filter();
            filter.categoryBits = Util.CATEGORY_PLATFORMS;
            filter.maskBits = Util.CATEGORY_NPC | Util.CATEGORY_PLAYER;
            fixture.setFilterData(filter);
        } else

        {
            mapBounds.set(new Vector2(0f * Util.PIXEL_TO_BOX, 0.0f),
                    new Vector2(0f, layer.getHeight() * 32).scl(Util.PIXEL_TO_BOX));
            body.setUserData(new LevelWall(level - 1));
            Fixture fixture = body.createFixture(mapBounds, 0);
            Filter filter = new Filter();
            filter.categoryBits = Util.CATEGORY_PLATFORMS;
            filter.maskBits = Util.CATEGORY_NPC | Util.CATEGORY_PLAYER;
            fixture.setFilterData(filter);
        }

        // right wall
        body = GamePlayManager.world.createBody(bodydef);
        body.setUserData(new LevelWall(level + 1));

        EdgeShape mapBounds2 = new EdgeShape();
        mapBounds2.set(new Vector2((layer.getWidth() * 32), 0.0f).scl(Util.PIXEL_TO_BOX),
                new Vector2((layer.getWidth() * 32), layer.getHeight() * 32).scl(Util.PIXEL_TO_BOX));
        Fixture fixture = body.createFixture(mapBounds2, 0);
        Filter filter = new Filter();
        filter.categoryBits = Util.CATEGORY_PLATFORMS;
        filter.maskBits = Util.CATEGORY_NPC | Util.CATEGORY_PLAYER;
        fixture.setFilterData(filter);

        // roof
        body = GamePlayManager.world.createBody(bodydef);
        body.setUserData(new Platform("roof_" + level, new Vector2(0.0f, layer.getHeight() * 32),
                new Vector2(layer.getWidth() * 32, layer.getHeight())));
        EdgeShape mapBounds3 = new EdgeShape();
        mapBounds3.set(new Vector2(0.0f, layer.getHeight() * 32).scl(Util.PIXEL_TO_BOX),
                new Vector2(layer.getWidth() * 32, layer.getHeight() * 32).scl(Util.PIXEL_TO_BOX));
        fixture = body.createFixture(mapBounds3, 0);
        fixture.setFilterData(filter);

        mapBounds.dispose();
        mapBounds2.dispose();
        mapBounds3.dispose();

    }

    /**
     * This is a helper function that makes calls that will attempt to extend
     * one of the line segments already tracked by TiledMapHelper, if possible.
     * The goal is to have as few line segments as possible.
     * 
     * Ex: If you have a line segment in the system that is from 1x1 to 3x3 and
     * this function is called for a line that is 4x4 to 9x9, rather than add a
     * whole new line segment to the list, the 1x1,3x3 line will be extended to
     * 1x1,9x9. See also: LineSegment.extendIfPossible.
     * 
     * @param lsx1
     *            starting x of the new line segment
     * @param lsy1
     *            starting y of the new line segment
     * @param lsx2
     *            ending x of the new line segment
     * @param lsy2
     *            ending y of the new line segment
     * @param collisionLineSegments
     *            the current list of line segments
     */
    private void addOrExtendCollisionLineSegment(float lsx1, float lsy1, float lsx2, float lsy2,
            ArrayList<LineSegment> collisionLineSegments, String type) {
        LineSegment line = new LineSegment(lsx1, lsy1, lsx2, lsy2, type);

        boolean didextend = false;

        for (LineSegment test : collisionLineSegments) {
            if (test.extendIfPossible(line)) {
                didextend = true;
                break;
            }
        }

        if (!didextend) {
            collisionLineSegments.add(line);
        }
    }

    /**
     * Prepares the helper's camera object for use.
     * 
     * @param screenWidth
     * @param screenHeight
     */
    public void prepareCamera(int screenWidth, int screenHeight) {
        Camera.camera = new OrthographicCamera(screenWidth, screenHeight);
        // if ( WitchCraft.ON_ANDROID )
        // Camera.camera.zoom = 0.5f;
        Camera.camera.position.set(screenWidth / 2.0f, screenHeight / 2.0f, 0);
    }

    /**
     * Returns the camera object created for viewing the loaded map.
     * 
     * @return OrthographicCamera
     */
    public OrthographicCamera getCamera() {
        if (Camera.camera == null) {
            throw new IllegalStateException("getCamera() called out of sequence");
        }
        return Camera.camera;
    }

    /**
     * Describes the start and end points of a line segment and contains a
     * helper method useful for extending line segments.
     */
    private class LineSegment {
        private Vector2 start = new Vector2();
        private Vector2 end = new Vector2();
        private String type;

        /**
         * Construct a new LineSegment with the specified coordinates.
         * 
         * @param x1
         * @param y1
         * @param x2
         * @param y2
         */
        public LineSegment(float x1, float y1, float x2, float y2, String type) {
            start = new Vector2(x1, y1);
            end = new Vector2(x2, y2);
            this.type = type;
        }

        /**
         * The "start" of the line. Start and end are misnomers, this is just
         * one end of the line.
         * 
         * @return Vector2
         */
        public Vector2 start() {
            return start;
        }

        /**
         * The "end" of the line. Start and end are misnomers, this is just one
         * end of the line.
         * 
         * @return Vector2
         */
        public Vector2 end() {
            return end;
        }

        /**
         * Determine if the requested line could be tacked on to the end of this
         * line with no kinks or gaps. If it can, the current LineSegment will
         * be extended by the length of the passed LineSegment.
         * 
         * @param lineSegment
         * @return boolean true if line was extended, false if not.
         */
        public boolean extendIfPossible(LineSegment lineSegment) {
            /**
             * First, let's see if the slopes of the two segments are the same.
             */
            double slope1 = Math.atan2(end.y - start.y, end.x - start.x);
            double slope2 = Math.atan2(lineSegment.end.y - lineSegment.start.y,
                    lineSegment.end.x - lineSegment.start.x);

            if (Math.abs(slope1 - slope2) > 0.1) {
                return false;
            }

            /**
             * Second, check if either end of this line segment is adjacent to
             * the requested line segment. So, 1 pixel away up through sqrt(2)
             * away.
             * 
             * Whichever two points are within the right range will be "merged"
             * so that the two outer points will describe the line segment.
             */
            if (start.dst(lineSegment.start) <= Math.sqrt(2) + 1e-9) {
                start.set(lineSegment.end);
                return true;
            } else if (end.dst(lineSegment.start) <= Math.sqrt(2) + 1e-9) {
                end.set(lineSegment.end);
                return true;
            } else if (end.dst(lineSegment.end) <= Math.sqrt(2) + 1e-9) {
                end.set(lineSegment.start);
                return true;
            } else if (start.dst(lineSegment.end) <= Math.sqrt(2) + 1e-9) {
                start.set(lineSegment.start);
                return true;
            }
            return false;
        }

        /**
         * Returns a pretty description of the LineSegment.
         * 
         * @return String
         */
        @Override
        public String toString() {
            return "[" + start.x + "x" + start.y + "] -> [" + end.x + "x" + end.y + "]";
        }
    }

    private OrthoTileRenderer tileMapRenderer;

    private TiledMap map;
}