Enhancements in Unit Movement and Pathfinding in RTS Game

Last modified: May 18 2024 18:40:44

This blog post details the recent enhancements made to the unit movement and pathfinding mechanisms in our RTS game, focusing on improvements in navigation and gameplay experience.

Introduction

In this update, we introduce significant improvements to the unit movement and pathfinding mechanisms in our RTS game. These changes aim to make unit navigation more efficient and robust, ensuring a smoother gameplay experience. Let's delve into the modifications and their implications.

ActionSelectionListener Enhancements

In the ActionSelectionListener class, we made several updates to improve how units handle movement actions:


import github.ac15cr.rts.units.UnitPathFind;
import java.util.List;

private final UnitPathFind unitPathFind;

public ActionSelectionListener(Logger logger, NamespacedKey blockKey, PlayerManager playerManager, GameManager gameManager) {
    ...
    this.unitPathFind = new UnitPathFind();
    ...
}
                

We introduced a new UnitPathFind class to handle pathfinding. This class computes the optimal path for units to reach their destinations while avoiding obstacles.

Furthermore, we modified the block assignment loop and the logic for handling unit movement:


for (int i = 3; i < 6; i++) {
    player.getInventory().setItem(i, cancelBlock);
}
...
Location targetLocation = event.getClickedBlock().getLocation().add(0, 1, 0);
...
List<Location> blockPathToGoal = unitPathFind.findPath(selectedUnit.getEntity().getLocation(), targetLocation, selectedUnit.getAttributes().getMoveDistance());
...
if (selectedUnit.getGoal().moveTo(blockPathToGoal)) {
    for (int i = 3; i < 6; i++) {
        player.getInventory().setItem(i, ItemStack.empty());
    }
    playerData.setStatus(PlayerStatusEnum.READY);
}
                

These changes ensure that the cancel block is properly assigned to inventory slots and that units can find and move along valid paths to their destinations.

CustomUnit Class Updates

We made important modifications to the CustomUnit class to support the new pathfinding logic:


private final UnitGoal goal;

public CustomUnit(Location location, Player owner, UnitType unitType, UnitAttributes attributes, EntityType entityType) {
    ...
    this.goal = new UnitGoal(this);
    ...
}
...
public UnitGoal getGoal() {
    return goal;
}
                

A new UnitGoal instance is associated with each unit to manage movement goals and paths.

Introducing UnitGoal Class

The UnitGoal class is designed to handle the movement goals for units. It integrates with the pathfinder to calculate and execute paths:


import com.destroystokyo.paper.entity.Pathfinder;
import java.util.*;

public class UnitGoal implements Goal<Mob> {
    ...
    private final CustomUnit unit;
    private final Pathfinder pathfinder;
    private final Queue<Pathfinder.PathResult> paths;

    public UnitGoal(CustomUnit unit) {
        this.paths = new ArrayDeque<>();
        this.unit = unit;
        this.pathfinder = ((Mob) this.unit.getEntity()).getPathfinder();
    }
    
    public boolean moveTo(List<Location> targets) {
        for (Location target : targets) {
            Pathfinder.PathResult path = pathfinder.findPath(target);
            if (path == null || path.getFinalPoint() != target) {
                unit.getOwner().sendMessage(Component.text("Cannot reach that location!"));
                paths.clear();
                return false;
            }
            paths.add(path);
        }
        return true;
    }
    
    @Override
    public void tick() {
        if (pathfinder.hasPath()) return;
        if (paths.isEmpty()) return;
        pathfinder.moveTo(paths.poll());
    }
    ...
}
                

This class queues paths for units and ensures they follow the calculated paths, providing a more seamless movement experience.

UnitManager Updates

Finally, we updated the UnitManager class to integrate the new goal system:


if (Bukkit.getScoreboardManager().getMainScoreboard().getTeam("RTS-Units") != null) {
    team = Bukkit.getScoreboardManager().getMainScoreboard().getTeam("RTS-Units");
} else {
    team = Bukkit.getScoreboardManager().getMainScoreboard().registerNewTeam("RTS-Units");
}
Objects.requireNonNull(team).setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER);
...
Bukkit.getMobGoals().addGoal((Mob)customUnit.getEntity(), 0, customUnit.getGoal());
                

This ensures that units are assigned their respective goals and that collision rules are correctly configured.

Conclusion

These enhancements bring a more reliable and efficient pathfinding system to our RTS game, improving unit movement and overall gameplay experience. Stay tuned for more updates as we continue to refine and expand the game's features.