add fixture def creation logic

This commit is contained in:
Quillraven
2025-05-25 15:50:57 +02:00
parent 363cd0e679
commit fd4fb0bdfa
6 changed files with 199 additions and 5 deletions

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.11.1-99-gec89c545" orientation="orthogonal" renderorder="right-down" width="18" height="16" tilewidth="32" tileheight="32" infinite="0" nextlayerid="6" nextobjectid="2">
<map version="1.10" tiledversion="1.11.1-99-gec89c545" orientation="orthogonal" renderorder="right-down" width="18" height="16" tilewidth="32" tileheight="32" infinite="0" nextlayerid="6" nextobjectid="3">
<tileset firstgid="1" source="tileset.tsx"/>
<tileset firstgid="99" source="objects.tsx"/>
<layer id="1" name="ground" width="18" height="16">
@@ -24,5 +24,6 @@
</layer>
<objectgroup id="5" name="objects">
<object id="1" gid="99" x="50.0586" y="458.934" width="32" height="32"/>
<object id="2" gid="99" x="112.269" y="470.818" width="64" height="64"/>
</objectgroup>
</map>

View File

@@ -9,6 +9,7 @@ import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.Disposable;
import io.github.com.quillraven.GdxGame;
import io.github.com.quillraven.asset.MapAsset;
import io.github.com.quillraven.system.PhysicDebugRenderSystem;
import io.github.com.quillraven.system.RenderSystem;
import io.github.com.quillraven.system.TiledServiceTestSystem;
import io.github.com.quillraven.tiled.TiledAshleySpawner;
@@ -32,6 +33,7 @@ public class GameScreen extends ScreenAdapter {
// add ECS systems
this.engine.addSystem(new RenderSystem(game.getBatch(), game.getViewport(), game.getCamera()));
this.engine.addSystem(new TiledServiceTestSystem(this.tiledService));
this.engine.addSystem(new PhysicDebugRenderSystem(this.physicWorld, game.getCamera()));
}
@Override

View File

@@ -0,0 +1,29 @@
package io.github.com.quillraven.system;
import com.badlogic.ashley.core.EntitySystem;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.Disposable;
public class PhysicDebugRenderSystem extends EntitySystem implements Disposable {
private final World physicWorld;
private final Box2DDebugRenderer box2DDebugRenderer;
private final Camera camera;
public PhysicDebugRenderSystem(World physicWorld, Camera camera) {
this.box2DDebugRenderer = new Box2DDebugRenderer();
this.physicWorld = physicWorld;
this.camera = camera;
}
@Override
public void update(float deltaTime) {
this.box2DDebugRenderer.render(physicWorld, camera.combined);
}
@Override
public void dispose() {
this.box2DDebugRenderer.dispose();
}
}

View File

@@ -3,6 +3,7 @@ package io.github.com.quillraven.system;
import com.badlogic.ashley.core.EntitySystem;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.maps.tiled.TiledMap;
import io.github.com.quillraven.asset.MapAsset;
import io.github.com.quillraven.tiled.TiledService;
@@ -18,11 +19,11 @@ public class TiledServiceTestSystem extends EntitySystem {
public void update(float deltaTime) {
if (Gdx.input.isKeyJustPressed(Input.Keys.X)) {
Gdx.app.debug("TiledServiceTestSystem", "Setting map to MAIN");
var tiledMap = tiledService.loadMap(MapAsset.MAIN);
TiledMap tiledMap = tiledService.loadMap(MapAsset.MAIN);
tiledService.setMap(tiledMap);
} else if (Gdx.input.isKeyJustPressed(Input.Keys.C)) {
Gdx.app.debug("TiledServiceTestSystem", "Setting map to SECOND");
var tiledMap = tiledService.loadMap(MapAsset.SECOND);
TiledMap tiledMap = tiledService.loadMap(MapAsset.SECOND);
tiledService.setMap(tiledMap);
}
}

View File

@@ -13,6 +13,7 @@ import com.badlogic.gdx.maps.tiled.objects.TiledMapTileMapObject;
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.FixtureDef;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.GdxRuntimeException;
import io.github.com.quillraven.GdxGame;
@@ -71,8 +72,11 @@ public class TiledAshleySpawner {
bodyDef.fixedRotation = true;
Body body = this.physicWorld.createBody(bodyDef);
body.setUserData(entity);
for (MapObject object : objects) {
FixtureDef fixtureDef = TiledPhysics.toFixtureDef(object);
body.createFixture(fixtureDef);
fixtureDef.shape.dispose();
}
entity.add(new Physic(body, new Vector2(body.getPosition())));

View File

@@ -1,6 +1,163 @@
package io.github.com.quillraven.tiled;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.maps.MapObject;
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.Rectangle;
import com.badlogic.gdx.math.Vector2;
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.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) {
if (mapObject instanceof RectangleMapObject rectMapObj) {
return rectangleFixtureDef(rectMapObj, relativeTo);
} else if (mapObject instanceof EllipseMapObject ellipseMapObj) {
return ellipseFixtureDef(ellipseMapObj, relativeTo);
} else if (mapObject instanceof PolygonMapObject polygonMapObj) {
return polygonFixtureDef(polygonMapObj, polygonMapObj.getPolygon().getVertices(), relativeTo);
} else if (mapObject instanceof PolylineMapObject polylineMapObj) {
return polygonFixtureDef(polylineMapObj, polylineMapObj.getPolyline().getVertices(), 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) {
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;
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;
}
private static FixtureDef ellipseFixtureDef(EllipseMapObject mapObject, 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;
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 fixtureDef;
}
private static FixtureDef polygonFixtureDef(
MapObject mapObject, // Could be PolygonMapObject or PolylineMapObject
float[] polyVertices,
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;
}
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;
// y-coordinate
vertices[vertexIdx + 1] = offsetY + polyVertices[vertexIdx + 1] * GdxGame.UNIT_SCALE;
}
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;
}
private static void initFixtureDef(MapObject mapObject, FixtureDef fixtureDef, Shape shape) {
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);
}
}