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

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.11.1-99-gec89c545" name="objects" tilewidth="64" tileheight="64" tilecount="1" columns="0">
<tileset version="1.10" tiledversion="1.11.0" name="objects" tilewidth="64" tileheight="64" tilecount="1" columns="0">
<grid orientation="orthogonal" width="1" height="1"/>
<tile id="0">
<properties>
<property name="animation" value="WALK"/>
<property name="atlasAsset" value="OBJECTS"/>
<property name="speed" type="float" value="5"/>
<property name="speed" type="float" value="2"/>
</properties>
<image source="objects/hero.png" width="64" height="64"/>
<objectgroup draworder="index" id="3">

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) {

View File

@@ -20,19 +20,19 @@ public class Lwjgl3Launcher {
private static Lwjgl3ApplicationConfiguration getDefaultConfiguration() {
Lwjgl3ApplicationConfiguration configuration = new Lwjgl3ApplicationConfiguration();
configuration.setTitle("mystictutorial");
//// Vsync limits the frames per second to what your hardware can display, and helps eliminate
//// screen tearing. This setting doesn't always work on Linux, so the line after is a safeguard.
// Vsync limits the frames per second to what your hardware can display, and helps eliminate
// screen tearing. This setting doesn't always work on Linux, so the line after is a safeguard.
configuration.useVsync(true);
//// Limits FPS to the refresh rate of the currently active monitor, plus 1 to try to match fractional
//// refresh rates. The Vsync setting above should limit the actual FPS to match the monitor.
// Limits FPS to the refresh rate of the currently active monitor, plus 1 to try to match fractional
// refresh rates. The Vsync setting above should limit the actual FPS to match the monitor.
configuration.setForegroundFPS(Lwjgl3ApplicationConfiguration.getDisplayMode().refreshRate + 1);
//// If you remove the above line and set Vsync to false, you can get unlimited FPS, which can be
//// useful for testing performance, but can also be very stressful to some hardware.
//// You may also need to configure GPU drivers to fully disable Vsync; this can cause screen tearing.
// If you remove the above line and set Vsync to false, you can get unlimited FPS, which can be
// useful for testing performance, but can also be very stressful to some hardware.
// You may also need to configure GPU drivers to fully disable Vsync; this can cause screen tearing.
configuration.setWindowedMode((int) (GdxGame.WORLD_WIDTH * 70), (int) (GdxGame.WORLD_HEIGHT * 70));
//// You can change these files; they are in lwjgl3/src/main/resources/ .
//// They can also be loaded from the root of assets/ .
// You can change these files; they are in lwjgl3/src/main/resources/ .
// They can also be loaded from the root of assets/ .
configuration.setWindowIcon("libgdx128.png", "libgdx64.png", "libgdx32.png", "libgdx16.png");
return configuration;
}