diff --git a/core/src/main/java/io/github/com/quillraven/component/Transform.java b/core/src/main/java/io/github/com/quillraven/component/Transform.java index 17026e9..ee739ef 100644 --- a/core/src/main/java/io/github/com/quillraven/component/Transform.java +++ b/core/src/main/java/io/github/com/quillraven/component/Transform.java @@ -4,14 +4,12 @@ import com.badlogic.ashley.core.Component; import com.badlogic.ashley.core.ComponentMapper; import com.badlogic.gdx.math.Vector2; -/** - * Component that stores the position, z-index, and size of an entity. - * Implements Comparable to allow sorting entities by z-index and position. - */ public record Transform( Vector2 position, int z, - Vector2 size + Vector2 size, + Vector2 scaling, + float rotationDeg ) implements Component, Comparable { public static final ComponentMapper MAPPER = ComponentMapper.getFor(Transform.class); diff --git a/core/src/main/java/io/github/com/quillraven/system/RenderSystem.java b/core/src/main/java/io/github/com/quillraven/system/RenderSystem.java index 288a597..e4b2880 100644 --- a/core/src/main/java/io/github/com/quillraven/system/RenderSystem.java +++ b/core/src/main/java/io/github/com/quillraven/system/RenderSystem.java @@ -70,15 +70,17 @@ public class RenderSystem extends SortedIteratingSystem implements Disposable { } Vector2 position = transform.position(); + Vector2 scaling = transform.scaling(); Vector2 size = transform.size(); batch.setColor(graphic.color()); batch.draw( graphic.region(), - position.x, position.y, + position.x - (1f - scaling.x) * size.x * 0.5f, + position.y - (1f - scaling.y) * size.y * 0.5f, size.x * 0.5f, size.y * 0.5f, size.x, size.y, - 1, 1, - 0 + scaling.x, scaling.y, + transform.rotationDeg() ); } diff --git a/core/src/main/java/io/github/com/quillraven/tiled/TiledAshleySpawner.java b/core/src/main/java/io/github/com/quillraven/tiled/TiledAshleySpawner.java index 3bf817c..f72577a 100644 --- a/core/src/main/java/io/github/com/quillraven/tiled/TiledAshleySpawner.java +++ b/core/src/main/java/io/github/com/quillraven/tiled/TiledAshleySpawner.java @@ -68,13 +68,14 @@ public class TiledAshleySpawner { BodyDef bodyDef = new BodyDef(); bodyDef.type = BodyDef.BodyType.DynamicBody; - bodyDef.position.set(entity.getComponent(Transform.class).position()); + Transform transform = entity.getComponent(Transform.class); + bodyDef.position.set(transform.position()); bodyDef.fixedRotation = true; Body body = this.physicWorld.createBody(bodyDef); body.setUserData(entity); for (MapObject object : objects) { - FixtureDef fixtureDef = TiledPhysics.toFixtureDef(object); + FixtureDef fixtureDef = TiledPhysics.fixtureDefOfMapObject(object, transform.scaling(), Vector2.Zero); body.createFixture(fixtureDef); fixtureDef.shape.dispose(); } @@ -86,10 +87,11 @@ public class TiledAshleySpawner { Vector2 position = new Vector2(tileMapObject.getX(), tileMapObject.getY()); TextureRegion textureRegion = tileMapObject.getTile().getTextureRegion(); Vector2 size = new Vector2(textureRegion.getRegionWidth(), textureRegion.getRegionHeight()); + Vector2 scaling = new Vector2(tileMapObject.getScaleX(), tileMapObject.getScaleY()); + position.scl(GdxGame.UNIT_SCALE); - size.scl(tileMapObject.getScaleX(), tileMapObject.getScaleY()); size.scl(GdxGame.UNIT_SCALE); - entity.add(new Transform(position, 0, size)); + entity.add(new Transform(position, 0, size, scaling, 0f)); } } diff --git a/core/src/main/java/io/github/com/quillraven/tiled/TiledPhysics.java b/core/src/main/java/io/github/com/quillraven/tiled/TiledPhysics.java index 75a24cc..bc76957 100644 --- a/core/src/main/java/io/github/com/quillraven/tiled/TiledPhysics.java +++ b/core/src/main/java/io/github/com/quillraven/tiled/TiledPhysics.java @@ -5,9 +5,10 @@ import com.badlogic.gdx.maps.objects.EllipseMapObject; 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.tiled.TiledMapTile; import com.badlogic.gdx.math.Ellipse; import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Polygon; +import com.badlogic.gdx.math.Polyline; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.physics.box2d.ChainShape; @@ -18,146 +19,124 @@ import com.badlogic.gdx.physics.box2d.Shape; import com.badlogic.gdx.utils.GdxRuntimeException; import io.github.com.quillraven.GdxGame; -import java.util.ArrayList; -import java.util.List; - public final class TiledPhysics { - public static List toFixtureDefs(TiledMapTile tiledMapTile) { - List result = new ArrayList<>(); - for (MapObject mapObject : tiledMapTile.getObjects()) { - result.add(toFixtureDef(mapObject)); - } - return result; - } - // relativeTo is necessary for map objects that are directly placed on a layer because // their x/y is equal to the position of the object, but we need it relative to 0,0 like it // is in the collision editor of a tile. - public static FixtureDef toFixtureDef(MapObject mapObject, Vector2 relativeTo) { + public static FixtureDef fixtureDefOfMapObject(MapObject mapObject, Vector2 scaling, Vector2 relativeTo) { if (mapObject instanceof RectangleMapObject rectMapObj) { - return rectangleFixtureDef(rectMapObj, relativeTo); + return rectangleFixtureDef(rectMapObj, scaling, relativeTo); } else if (mapObject instanceof EllipseMapObject ellipseMapObj) { - return ellipseFixtureDef(ellipseMapObj, relativeTo); + return ellipseFixtureDef(ellipseMapObj, scaling, relativeTo); } else if (mapObject instanceof PolygonMapObject polygonMapObj) { - return polygonFixtureDef(polygonMapObj, polygonMapObj.getPolygon().getVertices(), relativeTo); + Polygon polygon = polygonMapObj.getPolygon(); + float offsetX = polygon.getX() * GdxGame.UNIT_SCALE; + float offsetY = polygon.getY() * GdxGame.UNIT_SCALE; + return polygonFixtureDef(polygonMapObj, polygon.getVertices(), offsetX, offsetY, scaling, relativeTo); } else if (mapObject instanceof PolylineMapObject polylineMapObj) { - return polygonFixtureDef(polylineMapObj, polylineMapObj.getPolyline().getVertices(), relativeTo); + Polyline polyline = polylineMapObj.getPolyline(); + float offsetX = polyline.getX() * GdxGame.UNIT_SCALE; + float offsetY = polyline.getY() * GdxGame.UNIT_SCALE; + return polygonFixtureDef(polylineMapObj, polyline.getVertices(), offsetX, offsetY, scaling, relativeTo); } else { throw new GdxRuntimeException("Unsupported MapObject: " + mapObject); } } - public static FixtureDef toFixtureDef(MapObject mapObject) { - return toFixtureDef(mapObject, Vector2.Zero); - } - // Box is centered around body position in Box2D, but we want to have it aligned in a way // that the body position is the bottom left corner of the box. // That's why we use a 'boxOffset' below. - private static FixtureDef rectangleFixtureDef(RectangleMapObject mapObject, Vector2 relativeTo) { + private static FixtureDef rectangleFixtureDef(RectangleMapObject mapObject, Vector2 scaling, Vector2 relativeTo) { Rectangle rectangle = mapObject.getRectangle(); float rectX = rectangle.x; float rectY = rectangle.y; float rectW = rectangle.width; float rectH = rectangle.height; - float boxX = rectX * GdxGame.UNIT_SCALE - relativeTo.x; - float boxY = rectY * GdxGame.UNIT_SCALE - relativeTo.y; - float boxW = rectW * GdxGame.UNIT_SCALE * 0.5f; - float boxH = rectH * GdxGame.UNIT_SCALE * 0.5f; + float boxX = rectX * GdxGame.UNIT_SCALE * scaling.x - relativeTo.x; + float boxY = rectY * GdxGame.UNIT_SCALE * scaling.y - relativeTo.y; + float boxW = rectW * GdxGame.UNIT_SCALE * scaling.x * 0.5f; + float boxH = rectH * GdxGame.UNIT_SCALE * scaling.y * 0.5f; - FixtureDef fixtureDef = new FixtureDef(); PolygonShape shape = new PolygonShape(); shape.setAsBox(boxW, boxH, new Vector2(boxX + boxW, boxY + boxH), 0f); - initFixtureDef(mapObject, fixtureDef, shape); - - return fixtureDef; + return fixtureDefOfMapObjectAndShape(mapObject, shape); } - private static FixtureDef ellipseFixtureDef(EllipseMapObject mapObject, Vector2 relativeTo) { + private static FixtureDef ellipseFixtureDef(EllipseMapObject mapObject, Vector2 scaling, Vector2 relativeTo) { Ellipse ellipse = mapObject.getEllipse(); float x = ellipse.x; float y = ellipse.y; float w = ellipse.width; float h = ellipse.height; - float ellipseX = x * GdxGame.UNIT_SCALE - relativeTo.x; - float ellipseY = y * GdxGame.UNIT_SCALE - relativeTo.y; - float ellipseW = w * GdxGame.UNIT_SCALE / 2f; - float ellipseH = h * GdxGame.UNIT_SCALE / 2f; + float ellipseX = x * GdxGame.UNIT_SCALE * scaling.x - relativeTo.x; + float ellipseY = y * GdxGame.UNIT_SCALE * scaling.y - relativeTo.y; + float ellipseW = w * GdxGame.UNIT_SCALE * scaling.x * 0.5f; + float ellipseH = h * GdxGame.UNIT_SCALE * scaling.y * 0.5f; - FixtureDef fixtureDef = new FixtureDef(); if (MathUtils.isEqual(ellipseW, ellipseH, 0.1f)) { // width and height are equal -> return a circle shape CircleShape shape = new CircleShape(); shape.setPosition(new Vector2(ellipseX + ellipseW, ellipseY + ellipseH)); shape.setRadius(ellipseW); - initFixtureDef(mapObject, fixtureDef, shape); - } else { - // width and height are not equal -> return an ellipse shape (=polygon with 'numVertices' vertices) - // PolygonShape only supports 8 vertices - // ChainShape supports more but does not properly collide in some scenarios - final int numVertices = 8; - float angleStep = MathUtils.PI2 / numVertices; - Vector2[] vertices = new Vector2[numVertices]; - - for (int vertexIdx = 0; vertexIdx < numVertices; vertexIdx++) { - float angle = vertexIdx * angleStep; - float offsetX = ellipseW * MathUtils.cos(angle); - float offsetY = ellipseH * MathUtils.sin(angle); - vertices[vertexIdx] = new Vector2(ellipseX + ellipseW + offsetX, ellipseY + ellipseH + offsetY); - } - - PolygonShape shape = new PolygonShape(); - shape.set(vertices); - initFixtureDef(mapObject, fixtureDef, shape); + return fixtureDefOfMapObjectAndShape(mapObject, shape); } - return fixtureDef; + // width and height are not equal -> return an ellipse shape (=polygon with 'numVertices' vertices) + // PolygonShape only supports 8 vertices + // ChainShape supports more but does not properly collide in some scenarios + final int numVertices = 8; + float angleStep = MathUtils.PI2 / numVertices; + Vector2[] vertices = new Vector2[numVertices]; + + for (int vertexIdx = 0; vertexIdx < numVertices; vertexIdx++) { + float angle = vertexIdx * angleStep; + float offsetX = ellipseW * MathUtils.cos(angle); + float offsetY = ellipseH * MathUtils.sin(angle); + vertices[vertexIdx] = new Vector2(ellipseX + ellipseW + offsetX, ellipseY + ellipseH + offsetY); + } + + PolygonShape shape = new PolygonShape(); + shape.set(vertices); + return fixtureDefOfMapObjectAndShape(mapObject, shape); } private static FixtureDef polygonFixtureDef( MapObject mapObject, // Could be PolygonMapObject or PolylineMapObject float[] polyVertices, + float offsetX, + float offsetY, + Vector2 scaling, Vector2 relativeTo ) { - final float offsetX; - final float offsetY; - if (mapObject instanceof PolygonMapObject polygonMapObject) { - offsetX = polygonMapObject.getPolygon().getX() * GdxGame.UNIT_SCALE - relativeTo.x; - offsetY = polygonMapObject.getPolygon().getY() * GdxGame.UNIT_SCALE - relativeTo.y; - } else { - PolylineMapObject polylineMapObject = (PolylineMapObject) mapObject; - offsetX = polylineMapObject.getPolyline().getX() * GdxGame.UNIT_SCALE - relativeTo.x; - offsetY = polylineMapObject.getPolyline().getY() * GdxGame.UNIT_SCALE - relativeTo.y; - } + offsetX = (offsetX * scaling.x) - relativeTo.x; + offsetY = (offsetY * scaling.y) - relativeTo.y; float[] vertices = new float[polyVertices.length]; for (int vertexIdx = 0; vertexIdx < polyVertices.length; vertexIdx += 2) { // x-coordinate - vertices[vertexIdx] = offsetX + polyVertices[vertexIdx] * GdxGame.UNIT_SCALE; + vertices[vertexIdx] = offsetX + polyVertices[vertexIdx] * GdxGame.UNIT_SCALE * scaling.x; // y-coordinate - vertices[vertexIdx + 1] = offsetY + polyVertices[vertexIdx + 1] * GdxGame.UNIT_SCALE; + vertices[vertexIdx + 1] = offsetY + polyVertices[vertexIdx + 1] * GdxGame.UNIT_SCALE * scaling.y; } - FixtureDef fixtureDef = new FixtureDef(); ChainShape shape = new ChainShape(); - if (mapObject instanceof PolygonMapObject) { shape.createLoop(vertices); } else { // PolylineMapObject shape.createChain(vertices); } - initFixtureDef(mapObject, fixtureDef, shape); - - return fixtureDef; + return fixtureDefOfMapObjectAndShape(mapObject, shape); } - private static void initFixtureDef(MapObject mapObject, FixtureDef fixtureDef, Shape shape) { + private static FixtureDef fixtureDefOfMapObjectAndShape(MapObject mapObject, Shape shape) { + FixtureDef fixtureDef = new FixtureDef(); fixtureDef.shape = shape; fixtureDef.friction = mapObject.getProperties().get("friction", 0f, Float.class); fixtureDef.restitution = mapObject.getProperties().get("restitution", 0f, Float.class); fixtureDef.density = mapObject.getProperties().get("density", 0f, Float.class); fixtureDef.isSensor = mapObject.getProperties().get("sensor", false, Boolean.class); + return fixtureDef; } }