366 lines
8.7 KiB
Markdown
366 lines
8.7 KiB
Markdown
---
|
|
title: NPC Movement
|
|
type: docs
|
|
weight: 4
|
|
---
|
|
|
|
The NPC movement system handles navigation, pathfinding, and motion control for NPCs.
|
|
|
|
**Packages:**
|
|
- `com.hypixel.hytale.server.npc.movement`
|
|
- `com.hypixel.hytale.server.npc.navigation`
|
|
|
|
## Motion Controller
|
|
|
|
The `MotionController` executes movement commands and manages NPC locomotion.
|
|
|
|
### MotionController Class
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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:
|
|
|
|
```java
|
|
// 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 >}}
|