add move support

This commit is contained in:
Quillraven
2025-05-29 00:20:25 +02:00
parent 5c234b0176
commit f8b823fad3
10 changed files with 196 additions and 97 deletions

View File

@@ -0,0 +1,25 @@
package io.github.com.quillraven.component;
import com.badlogic.ashley.core.Component;
import com.badlogic.ashley.core.ComponentMapper;
import com.badlogic.gdx.math.Vector2;
public class Move implements Component {
public static final ComponentMapper<Move> MAPPER = ComponentMapper.getFor(Move.class);
private float maxSpeed;
private final Vector2 direction;
public Move(float maxSpeed) {
this.maxSpeed = maxSpeed;
this.direction = new Vector2();
}
public float getMaxSpeed() {
return maxSpeed;
}
public Vector2 getDirection() {
return direction;
}
}

View File

@@ -10,8 +10,9 @@ 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.AnimationSystem;
import io.github.com.quillraven.system.CleanupSystem;
import io.github.com.quillraven.system.PhysicDebugRenderSystem;
import io.github.com.quillraven.system.PhysicMoveSystem;
import io.github.com.quillraven.system.PhysicSystem;
import io.github.com.quillraven.system.RenderSystem;
import io.github.com.quillraven.system.TestSystem;
import io.github.com.quillraven.tiled.TiledAshleySpawner;
@@ -33,9 +34,10 @@ public class GameScreen extends ScreenAdapter {
this.tiledAshleySpawner = new TiledAshleySpawner(this.engine, this.physicWorld);
// add ECS systems
this.engine.addSystem(new PhysicMoveSystem());
this.engine.addSystem(new PhysicSystem(physicWorld, 1 / 60f));
this.engine.addSystem(new AnimationSystem(game.getAssetService()));
this.engine.addSystem(new RenderSystem(game.getBatch(), game.getViewport(), game.getCamera()));
this.engine.addSystem(new CleanupSystem());
this.engine.addSystem(new TestSystem(this.tiledService));
this.engine.addSystem(new PhysicDebugRenderSystem(this.physicWorld, game.getCamera()));
}

View File

@@ -1,40 +0,0 @@
package io.github.com.quillraven.system;
import com.badlogic.ashley.core.Engine;
import com.badlogic.ashley.core.Entity;
import com.badlogic.ashley.core.EntityListener;
import com.badlogic.ashley.core.EntitySystem;
import com.badlogic.gdx.physics.box2d.Body;
import io.github.com.quillraven.component.Physic;
public class CleanupSystem extends EntitySystem implements EntityListener {
@Override
public void addedToEngine(Engine engine) {
super.addedToEngine(engine);
engine.addEntityListener(this);
}
@Override
public void removedFromEngine(Engine engine) {
super.removedFromEngine(engine);
engine.removeEntityListener(this);
}
@Override
public void entityAdded(Entity entity) {
}
@Override
public void entityRemoved(Entity entity) {
// !!! Important !!!
// This does not work if the Physic component gets removed from an entity
// because the component is no longer accessible here.
// This ONLY works when an entity with a Physic component gets removed entirely from the engine.
Physic physic = Physic.MAPPER.get(entity);
if (physic != null) {
Body body = physic.getBody();
body.getWorld().destroyBody(body);
}
}
}

View File

@@ -0,0 +1,34 @@
package io.github.com.quillraven.system;
import com.badlogic.ashley.core.Entity;
import com.badlogic.ashley.core.Family;
import com.badlogic.ashley.systems.IteratingSystem;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import io.github.com.quillraven.component.Facing;
import io.github.com.quillraven.component.Move;
import io.github.com.quillraven.component.Physic;
public class PhysicMoveSystem extends IteratingSystem {
private static final Vector2 TMP_VEC2 = new Vector2();
public PhysicMoveSystem() {
super(Family.all(Physic.class, Move.class, Facing.class).get());
}
@Override
protected void processEntity(Entity entity, float deltaTime) {
Move move = Move.MAPPER.get(entity);
Physic physic = Physic.MAPPER.get(entity);
Body body = physic.getBody();
if (move.getDirection().isZero()) {
// no direction given -> stop movement
body.setLinearVelocity(0f, 0f);
return;
}
float maxSpeed = move.getMaxSpeed();
TMP_VEC2.set(move.getDirection()).nor();
body.setLinearVelocity(maxSpeed * TMP_VEC2.x, maxSpeed * TMP_VEC2.y);
}
}

View File

@@ -0,0 +1,89 @@
package io.github.com.quillraven.system;
import com.badlogic.ashley.core.Engine;
import com.badlogic.ashley.core.Entity;
import com.badlogic.ashley.core.EntityListener;
import com.badlogic.ashley.core.Family;
import com.badlogic.ashley.systems.IteratingSystem;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.World;
import io.github.com.quillraven.component.Physic;
import io.github.com.quillraven.component.Transform;
public class PhysicSystem extends IteratingSystem implements EntityListener {
private final World world;
private final float interval;
private float accumulator;
public PhysicSystem(World world, float interval) {
super(Family.all(Physic.class, Transform.class).get());
this.world = world;
this.interval = interval;
this.accumulator = 0f;
}
@Override
public void addedToEngine(Engine engine) {
super.addedToEngine(engine);
engine.addEntityListener(getFamily(), this);
}
@Override
public void removedFromEngine(Engine engine) {
super.removedFromEngine(engine);
engine.removeEntityListener(this);
}
@Override
public void update(float deltaTime) {
this.accumulator += deltaTime;
while (this.accumulator >= this.interval) {
this.accumulator -= this.interval;
super.update(interval);
this.world.step(interval, 6, 2);
}
world.clearForces();
float alpha = this.accumulator / this.interval;
for (int i = 0; i < getEntities().size(); ++i) {
this.interpolateEntity(getEntities().get(i), alpha);
}
}
@Override
protected void processEntity(Entity entity, float deltaTime) {
Physic physic = Physic.MAPPER.get(entity);
physic.getPrevPosition().set(physic.getBody().getPosition());
}
private void interpolateEntity(Entity entity, float alpha) {
Transform transform = Transform.MAPPER.get(entity);
Physic physic = Physic.MAPPER.get(entity);
transform.getPosition().set(
MathUtils.lerp(physic.getPrevPosition().x, physic.getBody().getPosition().x, alpha),
MathUtils.lerp(physic.getPrevPosition().y, physic.getBody().getPosition().y, alpha)
);
}
@Override
public void entityAdded(Entity entity) {
}
@Override
public void entityRemoved(Entity entity) {
// !!! Important !!!
// This does not work if the Physic component gets removed from an entity
// because the component is no longer accessible here.
// This ONLY works when an entity with a Physic component gets removed entirely from the engine.
Physic physic = Physic.MAPPER.get(entity);
if (physic != null) {
Body body = physic.getBody();
body.getWorld().destroyBody(body);
}
}
}

View File

@@ -6,12 +6,7 @@ import com.badlogic.ashley.core.Family;
import com.badlogic.ashley.utils.ImmutableArray;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.maps.tiled.TiledMap;
import io.github.com.quillraven.asset.MapAsset;
import io.github.com.quillraven.component.Animation2D;
import io.github.com.quillraven.component.Facing;
import io.github.com.quillraven.component.Facing.FacingDirection;
import io.github.com.quillraven.component.Move;
import io.github.com.quillraven.tiled.TiledService;
public class TestSystem extends EntitySystem {
@@ -24,46 +19,30 @@ public class TestSystem extends EntitySystem {
@Override
public void update(float deltaTime) {
if (Gdx.input.isKeyJustPressed(Input.Keys.X)) {
Gdx.app.debug("TiledServiceTestSystem", "Setting map to 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");
TiledMap tiledMap = tiledService.loadMap(MapAsset.SECOND);
tiledService.setMap(tiledMap);
} else if (Gdx.input.isKeyJustPressed(Input.Keys.A)) {
ImmutableArray<Entity> entities = getEngine().getEntitiesFor(Family.all(Facing.class).get());
if (Gdx.input.isKeyJustPressed(Input.Keys.W)) {
ImmutableArray<Entity> entities = getEngine().getEntitiesFor(Family.all(Move.class).get());
for (Entity entity : entities) {
Facing.MAPPER.get(entity).setDirection(FacingDirection.LEFT);
}
} else if (Gdx.input.isKeyJustPressed(Input.Keys.D)) {
ImmutableArray<Entity> entities = getEngine().getEntitiesFor(Family.all(Facing.class).get());
for (Entity entity : entities) {
Facing.MAPPER.get(entity).setDirection(FacingDirection.RIGHT);
Move.MAPPER.get(entity).getDirection().set(0f, 1f);
}
} else if (Gdx.input.isKeyJustPressed(Input.Keys.S)) {
ImmutableArray<Entity> entities = getEngine().getEntitiesFor(Family.all(Animation2D.class).get());
ImmutableArray<Entity> entities = getEngine().getEntitiesFor(Family.all(Move.class).get());
for (Entity entity : entities) {
Facing.MAPPER.get(entity).setDirection(FacingDirection.DOWN);
Animation2D.MAPPER.get(entity).setType(Animation2D.AnimationType.IDLE);
Move.MAPPER.get(entity).getDirection().set(0f, -1f);
}
} else if (Gdx.input.isKeyJustPressed(Input.Keys.A)) {
ImmutableArray<Entity> entities = getEngine().getEntitiesFor(Family.all(Move.class).get());
for (Entity entity : entities) {
Move.MAPPER.get(entity).getDirection().set(-1f, 0f);
}
} else if (Gdx.input.isKeyJustPressed(Input.Keys.D)) {
ImmutableArray<Entity> entities = getEngine().getEntitiesFor(Family.all(Move.class).get());
for (Entity entity : entities) {
Move.MAPPER.get(entity).getDirection().set(1f, 0f);
}
} else if (Gdx.input.isKeyJustPressed(Input.Keys.Q)) {
ImmutableArray<Entity> entities = getEngine().getEntitiesFor(Family.all(Animation2D.class).get());
ImmutableArray<Entity> entities = getEngine().getEntitiesFor(Family.all(Move.class).get());
for (Entity entity : entities) {
Animation2D animation2D = Animation2D.MAPPER.get(entity);
animation2D.setSpeed(animation2D.getSpeed() * 1.2f);
}
} else if (Gdx.input.isKeyJustPressed(Input.Keys.E)) {
ImmutableArray<Entity> entities = getEngine().getEntitiesFor(Family.all(Animation2D.class).get());
for (Entity entity : entities) {
Animation2D animation2D = Animation2D.MAPPER.get(entity);
animation2D.setSpeed(animation2D.getSpeed() / 1.2f);
}
} else if (Gdx.input.isKeyJustPressed(Input.Keys.W)) {
ImmutableArray<Entity> entities = getEngine().getEntitiesFor(Family.all(Animation2D.class).get());
for (Entity entity : entities) {
Animation2D.MAPPER.get(entity).setPlayMode(Animation.PlayMode.LOOP_RANDOM);
Move.MAPPER.get(entity).getDirection().setZero();
}
}
}

View File

@@ -29,6 +29,7 @@ import io.github.com.quillraven.component.Animation2D.AnimationType;
import io.github.com.quillraven.component.Facing;
import io.github.com.quillraven.component.Facing.FacingDirection;
import io.github.com.quillraven.component.Graphic;
import io.github.com.quillraven.component.Move;
import io.github.com.quillraven.component.Physic;
import io.github.com.quillraven.component.Transform;
@@ -110,25 +111,34 @@ public class TiledAshleySpawner {
private void spawnEntityOf(TiledMapTileMapObject tileMapObject) {
Entity entity = this.engine.createEntity();
TiledMapTile tile = tileMapObject.getTile();
TextureRegion textureRegion = tile.getTextureRegion();
TextureRegion textureRegion = tileMapObject.getTile().getTextureRegion();
addEntityTransform(
tileMapObject.getX(), tileMapObject.getY(),
textureRegion.getRegionWidth(), textureRegion.getRegionHeight(),
tileMapObject.getScaleX(), tileMapObject.getScaleY(),
entity);
addEntityPhysic(
tileMapObject.getTile().getObjects(),
tile.getObjects(),
BodyType.DynamicBody,
Vector2.Zero,
entity);
addEntityAnimation(tileMapObject.getTile(), entity);
addEntityAnimation(tile, entity);
addEntityMove(tile, entity);
entity.add(new Facing(FacingDirection.DOWN));
entity.add(new Graphic(textureRegion, Color.WHITE.cpy()));
this.engine.addEntity(entity);
}
private void addEntityMove(TiledMapTile tile, Entity entity) {
float speed = tile.getProperties().get("speed", 0f, Float.class);
if (speed == 0f) return;
entity.add(new Move(speed));
}
private void addEntityAnimation(TiledMapTile tile, Entity entity) {
String animationStr = tile.getProperties().get("animation", "", String.class);
if (animationStr.isBlank()) {
@@ -179,7 +189,7 @@ public class TiledAshleySpawner {
Body body = this.physicWorld.createBody(bodyDef);
body.setUserData(userData);
for (MapObject object : mapObjects) {
FixtureDef fixtureDef = TiledPhysics.fixtureDefOfMapObject(object, scaling, relativeTo);
FixtureDef fixtureDef = TiledPhysics.fixtureDefOf(object, scaling, relativeTo);
body.createFixture(fixtureDef);
fixtureDef.shape.dispose();
}

View File

@@ -24,7 +24,7 @@ public final class TiledPhysics {
// relativeTo is necessary for map objects that are directly placed on a layer (like rectangles for trigger).
// 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 fixtureDefOfMapObject(MapObject mapObject, Vector2 scaling, Vector2 relativeTo) {
public static FixtureDef fixtureDefOf(MapObject mapObject, Vector2 scaling, Vector2 relativeTo) {
if (mapObject instanceof RectangleMapObject rectMapObj) {
return rectangleFixtureDef(rectMapObj, scaling, relativeTo);
} else if (mapObject instanceof EllipseMapObject ellipseMapObj) {