Init
This commit is contained in:
365
content/world/entities/npc/npc-movement.en.md
Normal file
365
content/world/entities/npc/npc-movement.en.md
Normal file
@@ -0,0 +1,365 @@
|
||||
---
|
||||
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 >}}
|
||||
Reference in New Issue
Block a user