add scaling support

This commit is contained in:
Quillraven
2025-05-25 21:11:08 +02:00
parent fd4fb0bdfa
commit eb42851379
4 changed files with 67 additions and 86 deletions

View File

@@ -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<Transform> {
public static final ComponentMapper<Transform> MAPPER = ComponentMapper.getFor(Transform.class);

View File

@@ -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()
);
}

View File

@@ -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));
}
}

View File

@@ -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<FixtureDef> toFixtureDefs(TiledMapTile tiledMapTile) {
List<FixtureDef> 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;
}
}