8.7 KiB
8.7 KiB
title, type, weight
| title | type | weight |
|---|---|---|
| NPC Movement | docs | 4 |
The NPC movement system handles navigation, pathfinding, and motion control for NPCs.
Packages:
com.hypixel.hytale.server.npc.movementcom.hypixel.hytale.server.npc.navigation
Motion Controller
The MotionController executes movement commands and manages NPC locomotion.
MotionController Class
public class MotionController {
private NPCEntity npc;
private MovementState state;
private float currentSpeed;
private Vector3d targetVelocity;
// Movement commands
public void moveTo(Vector3d target);
public void moveInDirection(Vector3d direction);
public void stop();
// Speed control
public void setSpeed(float speed);
public void walk();
public void run();
public void sprint();
// State queries
public boolean isMoving();
public boolean hasReachedTarget();
public MovementState getState();
}
Movement States
public enum MovementState {
IDLE, // Not moving
WALKING, // Normal movement
RUNNING, // Fast movement
SPRINTING, // Maximum speed
JUMPING, // In air (jump)
FALLING, // In air (fall)
SWIMMING, // In water
CLIMBING, // On ladder/vine
SLIDING // On slope
}
Using MotionController
NPCEntity npc = // get NPC
MotionController motion = npc.getMotionController();
// Move to position
motion.moveTo(targetPosition);
// Set movement speed
motion.run(); // or motion.setSpeed(5.0f);
// Check if arrived
if (motion.hasReachedTarget()) {
// Destination reached
}
// Stop movement
motion.stop();
Path Follower
The PathFollower tracks and follows calculated paths.
PathFollower Class
public class PathFollower {
private List<Vector3d> path;
private int currentIndex;
private float waypointRadius;
private boolean smoothPath;
// Path management
public void setPath(List<Vector3d> path);
public void clearPath();
public boolean hasPath();
// Following
public Vector3d getNextWaypoint();
public void advanceToNextWaypoint();
public boolean hasReachedWaypoint(Vector3d position);
// Progress
public float getPathProgress(); // 0.0 to 1.0
public int getRemainingWaypoints();
}
Path Following Example
PathFollower pathFollower = npc.getPathFollower();
MotionController motion = npc.getMotionController();
// Set a path
pathFollower.setPath(calculatedPath);
// In update loop
if (pathFollower.hasPath()) {
Vector3d nextWaypoint = pathFollower.getNextWaypoint();
// Move towards waypoint
motion.moveTo(nextWaypoint);
// Check if reached
if (pathFollower.hasReachedWaypoint(npc.getPosition())) {
pathFollower.advanceToNextWaypoint();
}
}
Navigation Graph
The navigation system uses A* pathfinding on a navigation graph.
NavigationGraph Class
public class NavigationGraph {
// Find path between points
public List<Vector3d> findPath(
Vector3d start,
Vector3d end,
NavigationConfig config
);
// Check if point is navigable
public boolean isNavigable(Vector3d position);
// Get nearest navigable point
public Vector3d getNearestNavigablePoint(Vector3d position);
}
NavigationConfig
public class NavigationConfig {
private float maxDistance; // Maximum path length
private float stepHeight; // Max step up height
private float entityWidth; // Entity collision width
private float entityHeight; // Entity collision height
private boolean canSwim; // Allow water paths
private boolean canClimb; // Allow ladder/vine paths
private boolean canOpenDoors; // Allow door traversal
private Set<String> avoidBlocks; // Blocks to avoid
// Builder pattern
public static NavigationConfig builder()
.maxDistance(100.0f)
.stepHeight(1.0f)
.entityWidth(0.6f)
.entityHeight(1.8f)
.canSwim(false)
.build();
}
Pathfinding Example
NavigationGraph navGraph = world.getNavigationGraph();
NavigationConfig config = NavigationConfig.builder()
.maxDistance(50.0f)
.canSwim(true)
.build();
List<Vector3d> path = navGraph.findPath(
npc.getPosition(),
targetPosition,
config
);
if (path != null && !path.isEmpty()) {
npc.getPathFollower().setPath(path);
}
Movement Behaviors
Pre-built movement behaviors for common patterns.
WanderBehavior
public class WanderBehavior {
private float wanderRadius;
private float minPauseDuration;
private float maxPauseDuration;
public WanderBehavior(float radius) {
this.wanderRadius = radius;
}
public void update(NPCEntity npc, float deltaTime) {
if (!npc.getMotionController().isMoving()) {
Vector3d wanderTarget = calculateWanderTarget(npc);
npc.getMotionController().moveTo(wanderTarget);
}
}
}
FollowBehavior
public class FollowBehavior {
private Entity target;
private float followDistance;
private float catchUpDistance;
public void update(NPCEntity npc, float deltaTime) {
float distance = npc.getPosition().distance(target.getPosition());
if (distance > catchUpDistance) {
npc.getMotionController().run();
} else if (distance > followDistance) {
npc.getMotionController().walk();
npc.getMotionController().moveTo(target.getPosition());
} else {
npc.getMotionController().stop();
}
}
}
PatrolBehavior
public class PatrolBehavior {
private List<Vector3d> patrolPoints;
private int currentPointIndex;
private boolean loop;
private float waitTimeAtPoint;
public void update(NPCEntity npc, float deltaTime) {
Vector3d currentTarget = patrolPoints.get(currentPointIndex);
MotionController motion = npc.getMotionController();
if (motion.hasReachedTarget()) {
// Wait at point
currentPointIndex = (currentPointIndex + 1) % patrolPoints.size();
} else {
motion.moveTo(currentTarget);
}
}
}
FleeBehavior
public class FleeBehavior {
private float fleeDistance;
private Entity threat;
public void update(NPCEntity npc, float deltaTime) {
Vector3d awayFromThreat = npc.getPosition()
.subtract(threat.getPosition())
.normalize()
.multiply(fleeDistance);
Vector3d fleeTarget = npc.getPosition().add(awayFromThreat);
npc.getMotionController().sprint();
npc.getMotionController().moveTo(fleeTarget);
}
}
Obstacle Avoidance
public class ObstacleAvoidance {
private float avoidanceRadius;
private float lookAheadDistance;
public Vector3d calculateAvoidanceVector(
NPCEntity npc,
Vector3d desiredDirection
) {
// Cast rays to detect obstacles
List<RaycastHit> obstacles = castAvoidanceRays(npc, desiredDirection);
if (obstacles.isEmpty()) {
return desiredDirection;
}
// Calculate avoidance steering
Vector3d avoidance = Vector3d.ZERO;
for (RaycastHit hit : obstacles) {
Vector3d away = npc.getPosition().subtract(hit.position).normalize();
avoidance = avoidance.add(away);
}
return desiredDirection.add(avoidance.normalize()).normalize();
}
}
Movement Systems
ECS systems that process NPC movement:
// Movement update system
public class NPCMovementSystem implements System {
@Override
public void update(float deltaTime) {
for (NPCEntity npc : npcsWithMovement) {
MotionController motion = npc.getMotionController();
PathFollower path = npc.getPathFollower();
// Update path following
if (path.hasPath()) {
updatePathFollowing(npc, motion, path, deltaTime);
}
// Apply movement
motion.update(deltaTime);
}
}
}
Best Practices
{{< callout type="info" >}} Movement Guidelines:
- Use NavigationConfig appropriate for the NPC type
- Cache paths when possible to avoid frequent recalculation
- Use path smoothing for more natural movement
- Consider entity size when pathfinding
- Implement obstacle avoidance for dynamic environments {{< /callout >}}
{{< callout type="warning" >}} Pathfinding Performance:
- Limit pathfinding requests per frame
- Use shorter max distances when possible
- Cache frequently used paths
- Consider hierarchical pathfinding for large worlds {{< /callout >}}