Requesting some simple help
I’m working on just playing around with different assets, and I’m having a devil of a time with this.
I’ve made a basic world. Literally nothing in it except a ground plane and a interactive npc (in this case a zombie) I’ve managed to get it to wander around aimlessly, (like a chicken) but I can not get it to identify the player and then initiate an attack - as it should being an enemy class and all my scripts telling it to do so. It just wanders around.
I’ve put in a npc manager script, and a npc monster script both of which tell it to wander until it notices a player (45 minimum sight distance) then upon sight of target player play its taunt animation then move and attack. I’ve also got the nav mesh set up, and my player is inside said mesh. (The mesh is the full size of the ground plane)
But it does not do this. It just wanders. Any help on this would be much appreciated. Thanks!
edit:
here is the code I'm working with the "NPCMonster" - in this case, all I'm wanting to do is have the zombie wander around the area until it notices a player, at which point it will do its "taunt" animation, then move towards the player and try to attack them.
i will be adding details to track the attack hits and make them matter (after 5 hits the player is sent back to spawn point), and ways for the player to fight back - but that's all a later addition. trying to take this step by step as i teach myself from basically zero knowledge.
import * as hz from "horizon/core";
import { NPCAgent, NPCAgentEmote } from "NPCAgent";
enum NPCMonsterState {
Idle,
Walking,
Wandering,
Taunting, // New state for taunting
}
class NPCMonster extends NPCAgent<typeof NPCMonster> {
static propsDefinition = {
...NPCAgent.propsDefinition,
maxWanderDistance: { type: hz.PropTypes.Number, default: 20 },
};
state: NPCMonsterState = NPCMonsterState.Idle;
stateTimer: number = 0;
startLocation!: hz.Vec3;
targetPlayer: hz.Player | undefined = undefined;
start() {
super.start();
this.setState(NPCMonsterState.Idle);
this.startLocation = this.entity.position.get();
}
update(deltaTime: number) {
super.update(deltaTime);
this.updateTarget();
this.updateStateMachine(deltaTime);
}
private updateTarget() {
if (this.targetPlayer === undefined) {
const players = hz.World.getPlayers() as hz.Player[];
const monsterPosition = this.entity.position.get();
for (const player of players) {
const playerPosition = player.position.get();
const distanceSq = monsterPosition.distanceSquared(playerPosition);
if (distanceSq < this.props.maxWanderDistance * this.props.maxWanderDistance) {
this.targetPlayer = player;
console.log("Player detected:", player);
this.setState(NPCMonsterState.Taunting);
break;
}
}
}
}
private updateStateMachine(deltaTime: number) {
switch (this.state) {
case NPCMonsterState.Idle:
if (Math.random() < 0.1) {
this.setState(NPCMonsterState.Wandering);
}
break;
case NPCMonsterState.Walking:
this.updateWalkingState(deltaTime);
break;
case NPCMonsterState.Wandering:
this.updateWanderingState(deltaTime);
break;
case NPCMonsterState.Taunting:
this.updateTauntingState(deltaTime);
break;
}
}
private onEnterState(state: NPCMonsterState) {
console.log("Entering state:", NPCMonsterState[state]);
switch (state) {
case NPCMonsterState.Idle:
this.navMeshAgent?.isImmobile.set(true);
this.navMeshAgent?.destination.set(this.entity.position.get());
break;
case NPCMonsterState.Walking:
this.navMeshAgent?.isImmobile.set(false);
this.setMaxSpeedToWalkSpeed();
break;
case NPCMonsterState.Wandering:
this.navMeshAgent?.isImmobile.set(false);
this.setMaxSpeedToWalkSpeed();
this.setRandomDestination();
break;
case NPCMonsterState.Taunting:
this.navMeshAgent?.isImmobile.set(true);
this.triggerEmoteAnimation(NPCAgentEmote.Taunt);
this.stateTimer = 2.8; // Duration of the taunt animation
break;
}
}
private onLeaveState(state: NPCMonsterState) {
console.log("Leaving state:", NPCMonsterState[state]);
if (state === NPCMonsterState.Taunting) {
this.targetPlayer = undefined;
}
}
private setState(state: NPCMonsterState) {
if (this.state !== state) {
this.onLeaveState(this.state);
this.state = state;
this.onEnterState(this.state);
}
}
private updateWalkingState(deltaTime: number) {
if (this.navMeshAgent && this.navMeshAgent.remainingDistance.get() < 0.5) {
this.setState(NPCMonsterState.Idle);
}
}
private updateWanderingState(deltaTime: number) {
this.stateTimer -= deltaTime;
if (this.stateTimer <= 0) {
this.setRandomDestination();
}
}
private updateTauntingState(deltaTime: number) {
this.stateTimer -= deltaTime;
if (this.stateTimer <= 0) {
this.setState(NPCMonsterState.Idle);
}
}
private setRandomDestination() {
const randomOffset = new hz.Vec3(
(Math.random() - 0.5) * this.props.maxWanderDistance,
0,
(Math.random() - 0.5) * this.props.maxWanderDistance
);
const randomDestination = this.startLocation.add(randomOffset);
this.navMeshAgent?.destination.set(randomDestination);
this.stateTimer = Math.random() * 5 + 2; // Wander for 2 to 7 seconds
this.setState(NPCMonsterState.Walking);
}
public setMaxSpeedToWalkSpeed() {
if (this.navMeshAgent) {
this.navMeshAgent.maxSpeed.set(this.props.walkSpeed);
}
}
public triggerEmoteAnimation(emote: NPCAgentEmote) {
console.log("Triggering emote animation:", emote);
super.triggerEmoteAnimation(emote);
}
}
hz.Component.register(NPCMonster);