package com.cookbook.box2d; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.maps.Map; import com.badlogic.gdx.maps.MapLayer; import com.badlogic.gdx.maps.MapObject; import com.badlogic.gdx.maps.MapObjects; import com.badlogic.gdx.maps.MapProperties; import com.badlogic.gdx.maps.objects.CircleMapObject; import com.badlogic.gdx.maps.objects.PolygonMapObject; import com.badlogic.gdx.maps.objects.PolylineMapObject; import com.badlogic.gdx.maps.objects.RectangleMapObject; import com.badlogic.gdx.maps.objects.TextureMapObject; import com.badlogic.gdx.math.Circle; 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.ChainShape; import com.badlogic.gdx.physics.box2d.CircleShape; import com.badlogic.gdx.physics.box2d.FixtureDef; import com.badlogic.gdx.physics.box2d.PolygonShape; import com.badlogic.gdx.physics.box2d.Shape; import com.badlogic.gdx.physics.box2d.World; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.JsonReader; import com.badlogic.gdx.utils.JsonValue; import com.badlogic.gdx.utils.JsonValue.JsonIterator; import com.badlogic.gdx.utils.Logger; import com.badlogic.gdx.utils.ObjectMap; /** * @brief Populates box2D world with static bodies using data from a map object * * It uses a JSON formatted materials file to assign properties to the static * bodies it creates. To assign a material to a shape add a "material" custom * property to the shape in question using your editor of choice (Tiled, Gleed, * Tide...). Such file uses the following structure: @code [ { "name" : "ice", "density" : 1.0, "restitution" : 0.0, "friction" : 0.1 }, { "name" : "elastic", "density" : 1.0, "restitution" : 0.8, "friction" : 0.8 } ] @endcode * In case no material property is found, it'll get a default one. * */ public class MapBodyManager { private Logger logger; private World world; private float units; private Array<Body> bodies = new Array<Body>(); private ObjectMap<String, FixtureDef> materials = new ObjectMap<String, FixtureDef>(); /** * @param world box2D world to work with. * @param unitsPerPixel conversion ratio from pixel units to box2D metres. * @param materialsFile json file with specific physics properties to be assigned to newly created bodies. * @param loggingLevel verbosity of the embedded logger. */ public MapBodyManager(World world, float unitsPerPixel, FileHandle materialsFile, int loggingLevel) { logger = new Logger("MapBodyManager", loggingLevel); logger.info("initialising"); this.world = world; this.units = unitsPerPixel; if (materialsFile != null) { loadMaterialsFile(materialsFile); } } /** * @param map will use the "physics" layer of this map to look for shapes in order to create the static bodies. */ public void createPhysics(Map map) { createPhysics(map, "physics"); } /** * @param map map to be used to create the static bodies. * @param layerName name of the layer that contains the shapes. */ public void createPhysics(Map map, String layerName) { MapLayer layer = map.getLayers().get(layerName); if (layer == null) { logger.error("layer " + layerName + " does not exist"); return; } MapObjects objects = layer.getObjects(); for(MapObject object : layer.getObjects()) { if (object instanceof TextureMapObject){ continue; } Shape shape; BodyDef bodyDef = new BodyDef(); bodyDef.type = BodyDef.BodyType.StaticBody; if (object instanceof RectangleMapObject) { RectangleMapObject rectangle = (RectangleMapObject)object; shape = getRectangle(rectangle); } else if (object instanceof PolygonMapObject) { shape = getPolygon((PolygonMapObject)object); } else if (object instanceof PolylineMapObject) { shape = getPolyline((PolylineMapObject)object); } else if (object instanceof CircleMapObject) { shape = getCircle((CircleMapObject)object); } else { logger.error("non suported shape " + object); continue; } MapProperties properties = object.getProperties(); String material = properties.get("material", "default", String.class); FixtureDef fixtureDef = materials.get(material); if (fixtureDef == null) { logger.error("material does not exist " + material + " using default"); fixtureDef = materials.get("default"); } logger.info("Body: " + object.getName() + ", material: " + material); fixtureDef.shape = shape; Body body = world.createBody(bodyDef); body.createFixture(fixtureDef); bodies.add(body); fixtureDef.shape = null; shape.dispose(); } } /** * Destroys every static body that has been created using the manager. */ public void destroyPhysics() { for (Body body : bodies) { world.destroyBody(body); } bodies.clear(); } private void loadMaterialsFile(FileHandle materialsFile) { logger.info("adding default material"); FixtureDef fixtureDef = new FixtureDef(); fixtureDef.density = 1.0f; fixtureDef.friction = 1.0f; fixtureDef.restitution = 0.0f; materials.put("default", fixtureDef); logger.info("loading materials file"); try { JsonReader reader = new JsonReader(); JsonValue root = reader.parse(materialsFile); JsonIterator materialIt = root.iterator(); while (materialIt.hasNext()) { JsonValue materialValue = materialIt.next(); if (!materialValue.has("name")) { logger.error("material without name"); continue; } String name = materialValue.getString("name"); fixtureDef = new FixtureDef(); fixtureDef.density = materialValue.getFloat("density", 1.0f); fixtureDef.friction = materialValue.getFloat("friction", 1.0f); fixtureDef.restitution = materialValue.getFloat("restitution", 0.0f); logger.info("adding material " + name); materials.put(name, fixtureDef); } } catch (Exception e) { logger.error("error loading " + materialsFile.name() + " " + e.getMessage()); } } private Shape getRectangle(RectangleMapObject rectangleObject) { Rectangle rectangle = rectangleObject.getRectangle(); PolygonShape polygon = new PolygonShape(); Vector2 size = new Vector2((rectangle.x + rectangle.width * 0.5f) / units, (rectangle.y + rectangle.height * 0.5f ) / units); polygon.setAsBox(rectangle.width * 0.5f / units, rectangle.height * 0.5f / units, size, 0.0f); return polygon; } private Shape getCircle(CircleMapObject circleObject) { Circle circle = circleObject.getCircle(); CircleShape circleShape = new CircleShape(); circleShape.setRadius(circle.radius / units); circleShape.setPosition(new Vector2(circle.x / units, circle.y / units)); return circleShape; } private Shape getPolygon(PolygonMapObject polygonObject) { PolygonShape polygon = new PolygonShape(); float[] vertices = polygonObject.getPolygon().getTransformedVertices(); float[] worldVertices = new float[vertices.length]; for (int i = 0; i < vertices.length; ++i) { worldVertices[i] = vertices[i] / units; } polygon.set(worldVertices); return polygon; } private Shape getPolyline(PolylineMapObject polylineObject) { float[] vertices = polylineObject.getPolyline().getTransformedVertices(); Vector2[] worldVertices = new Vector2[vertices.length / 2]; for (int i = 0; i < vertices.length / 2; ++i) { worldVertices[i] = new Vector2(); worldVertices[i].x = vertices[i * 2] / units; worldVertices[i].y = vertices[i * 2 + 1] / units; } ChainShape chain = new ChainShape(); chain.createChain(worldVertices); return chain; } }