🤖 AI Behavior System + Sound Manifest
✅ NEW SYSTEMS: 1. AnimalBehavior.js - Wander, Flee, Follow, Glowing Eyes 2. NPCIdleBehavior.js - Idle animations (hat fix, sweat wipe) 3. ANIMAL_SOUND_MANIFEST.md - 40+ sound mappings 🎮 AI FEATURES: - Smooth movement (not grid-based) ✓ - Flee from player (100px range) ✓ - Follow with delay (cargo animals: llama, horse, donkey) ✓ - Glowing eyes in darkness (NOIR EFFECT!) ✓ - Neon pink/green eyes visible at distance - Perfect for 'eyes in forest' atmosphere 👤 NPC FEATURES: - Random idle animations every 3-8 seconds - Fix hat, wipe sweat, look around, scratch head - Gronk-specific: coin counting, item polishing 🔊 SOUND SYSTEM: - Kenney pack mapping for 23 animal sounds - Footstep variations (mud, grass, cobble) - Noir sound design: muffled distant sounds + reverb DEMO: 56/60 sprites (93%)! Next: 4 remaining sprites + integration testing
This commit is contained in:
153
src/ai/NPCIdleBehavior.js
Normal file
153
src/ai/NPCIdleBehavior.js
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 👤 NPC IDLE BEHAVIOR SYSTEM
|
||||
* "Krvava Žetev" - Gothic Farm Game
|
||||
*
|
||||
* Makes NPCs feel alive with random idle animations:
|
||||
* - Fix hat
|
||||
* - Wipe sweat
|
||||
* - Look around
|
||||
* - Scratch head
|
||||
* - Adjust clothes
|
||||
*/
|
||||
|
||||
export class NPCIdleBehavior {
|
||||
constructor(scene, sprite, npcType) {
|
||||
this.scene = scene;
|
||||
this.sprite = sprite;
|
||||
this.npcType = npcType;
|
||||
|
||||
// Idle animation timing
|
||||
this.idleTimer = 0;
|
||||
this.nextIdleTime = Phaser.Math.Between(3000, 8000);
|
||||
|
||||
// Available idle animations
|
||||
this.idleAnimations = this.getIdleAnimations();
|
||||
this.currentIdle = null;
|
||||
}
|
||||
|
||||
getIdleAnimations() {
|
||||
// Different NPCs have different idle behaviors
|
||||
const genericIdles = [
|
||||
'fix_hat',
|
||||
'wipe_sweat',
|
||||
'look_around',
|
||||
'scratch_head',
|
||||
'adjust_clothes',
|
||||
'yawn',
|
||||
'stretch'
|
||||
];
|
||||
|
||||
const npcSpecific = {
|
||||
'gronk': [...genericIdles, 'adjust_apron', 'count_coins', 'polish_item'],
|
||||
'farmer': [...genericIdles, 'lean_on_fence', 'check_watch', 'spit'],
|
||||
'merchant': [...genericIdles, 'fix_glasses', 'write_note', 'arrange_goods'],
|
||||
'default': genericIdles
|
||||
};
|
||||
|
||||
return npcSpecific[this.npcType] || npcSpecific.default;
|
||||
}
|
||||
|
||||
update(time, delta) {
|
||||
if (!this.sprite.active) return;
|
||||
|
||||
this.idleTimer += delta;
|
||||
|
||||
// Trigger random idle animation
|
||||
if (this.idleTimer >= this.nextIdleTime && !this.currentIdle) {
|
||||
this.playRandomIdle();
|
||||
this.idleTimer = 0;
|
||||
this.nextIdleTime = Phaser.Math.Between(3000, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
playRandomIdle() {
|
||||
const idle = Phaser.Utils.Array.GetRandom(this.idleAnimations);
|
||||
this.currentIdle = idle;
|
||||
|
||||
// Play animation if it exists
|
||||
const animKey = `${this.npcType}_${idle}`;
|
||||
|
||||
if (this.sprite.anims && this.sprite.anims.exists(animKey)) {
|
||||
this.sprite.play(animKey);
|
||||
|
||||
// Return to default idle after animation
|
||||
this.sprite.on('animationcomplete', () => {
|
||||
this.sprite.play(`${this.npcType}_idle`);
|
||||
this.currentIdle = null;
|
||||
}, this);
|
||||
} else {
|
||||
// Fallback: simple visual effects for idle
|
||||
this.playFallbackIdle(idle);
|
||||
}
|
||||
}
|
||||
|
||||
playFallbackIdle(idleType) {
|
||||
// Create simple tween-based idle behaviors as fallback
|
||||
switch (idleType) {
|
||||
case 'fix_hat':
|
||||
this.scene.tweens.add({
|
||||
targets: this.sprite,
|
||||
angle: -5,
|
||||
duration: 200,
|
||||
yoyo: true,
|
||||
onComplete: () => {
|
||||
this.currentIdle = null;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'wipe_sweat':
|
||||
this.scene.tweens.add({
|
||||
targets: this.sprite,
|
||||
scaleY: 0.98,
|
||||
duration: 300,
|
||||
yoyo: true,
|
||||
onComplete: () => {
|
||||
this.currentIdle = null;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'look_around':
|
||||
this.scene.tweens.add({
|
||||
targets: this.sprite,
|
||||
scaleX: -Math.abs(this.sprite.scaleX),
|
||||
duration: 400,
|
||||
yoyo: true,
|
||||
onComplete: () => {
|
||||
this.currentIdle = null;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'scratch_head':
|
||||
this.scene.tweens.add({
|
||||
targets: this.sprite,
|
||||
y: this.sprite.y - 5,
|
||||
duration: 150,
|
||||
yoyo: true,
|
||||
repeat: 2,
|
||||
onComplete: () => {
|
||||
this.currentIdle = null;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
// Generic bob
|
||||
this.scene.tweens.add({
|
||||
targets: this.sprite,
|
||||
y: this.sprite.y - 3,
|
||||
duration: 200,
|
||||
yoyo: true,
|
||||
onComplete: () => {
|
||||
this.currentIdle = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.sprite = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user