Integrating Visual Paths into Game Logic
Static or scripted movement patterns often lead to repetitive gameplay. A flexible approach involves defining navigation points directly within the level editor (Tiled) and consuming them at runtime. This method decouples level design from code logic, allowing designers to adjust routes without recompiling.
Configuring Tiled Maps
- Create a new Object Layer named
patrolPath. - Draw rectangles or circles at key intersection points on the map.
- Name each object sequentially (e.g.,
node_0,node_1,node_2) to establish order. - The coordinates stored here represent the logical path points for the actor.
Runtime Data Parsing
Upon initialization, iterate through the loaded TiledMap to extract these wapyoints. Filter objects by their name prefix to ensure only valid path nodes are collected.
private void loadWaypoints(TiledMap map) {
List<Vector2> routePoints = new ArrayList<>();
float offsetY = map.getHeight() * TILE_SIZE; // Adjust based on coordinate system
for (TiledObjectGroup group : map.objectGroups) {
if (!group.name.equals("patrolPath")) continue;
for (TiledObject obj : group.objects) {
if (obj.name.startsWith("node_")) {
// Convert tile coordinates to world/screen coordinates as needed
routePoints.add(new Vector2(obj.x, offsetY - obj.y));
}
}
}
setRoute(routePoints);
}
Implementing the Patroller Class
Instead of hardcoding the distance check, use vector matehmatics to calculate direction and velocity. This ensures smooth interpolation between targets regardless of angle.
public class Patroller extends Image {
private List<Vector2> waypoints;
private int currentNodeIndex;
private float speed;
public Patroller(List<Vector2> route, TextureRegion region, float moveSpeed) {
super(region);
this.waypoints = route;
this.currentNodeIndex = 0;
this.speed = moveSpeed;
setPosition(waypoints.get(0));
}
@Override
public void act(float delta) {
updatePosition(delta);
}
private void updatePosition(float dt) {
if (currentNodeIndex >= waypoints.size() - 1) return;
Vector2 currentPos = getPositionVector();
Vector2 targetPos = waypoints.get(currentNodeIndex + 1);
float distToTarget = currentPos.dst(targetPos);
// Check if reached the specific node
if (distToTarget <= 2.0f) {
currentNodeIndex++;
return;
}
// Calculate direction vector
Vector2 dir = new Vector2();
dir.set(targetPos).sub(currentPos).nor();
// Move towards target
dir.scl(speed * dt);
addTranslation(dir.x, dir.y);
}
private Vector2 getPositionVector() {
return new Vector2(getX(), getY());
}
}
Main Application Flow
The game loop loads the map renderer and instantiates the actor once data is ready. Note that camera positioning should align with the map viewport to maintain visual consistency.
@Override
public void create() {
// Load assets
FileHandle tmxFile = Gdx.files.internal("map/level.tmx");
TiledMap gameMap = TiledLoader.loadMap(tmxFile);
TileMapRenderer renderer = new TileMapRenderer(gameMap, atlas);
// Setup UI Stage
stage = new Stage(new ScreenViewport());
// Parse custom path data
List<Vector2> path = new ArrayList<>();
// ... call loadWaypoints logic ...
// Create entity
TextureRegion sprite = getSpriteFromPack();
Patroller enemy = new Patroller(path, sprite, 50f); // Speed unit
stage.addActor(enemy);
Gdx.input.setInputProcessor(stage);
}
@Override
public void render() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
OrthographicCamera camera = getCamera();
// Follow player or keep fixed
camera.position.set(150, 150, 0);
renderer.render(camera);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
By separating path definition from execution logic, development becomes more modular. Designers can tweak routes in Tiled immediately, while the code handles general traversal mechanics.