🐕💙 Animal System & Emotional Memory Implementation
FEATURES: - Created animals/ folder structure (wild, domestic, infected) - Implemented proximity-based memory trigger system - Pulsating heart UI when Kai remembers family dog - Emotional storytelling without dialogue NEW FILES: - src/entities/Animal.js - Animal class with proximity detection - src/ui/MemoryHeartUI.js - Pulsating heart with Slovenian text - docs/systems/ANIMAL_MEMORY_SYSTEM.md - Full documentation - scripts/organize_all_tools.py - Tool organization script TOOLS ORGANIZATION: - Moved 84 additional tools to items/tools/ - Final count: 427 tools organized by material tier • wood: 36 tools • stone: 60 tools • iron: 36 tools • gold: 36 tools • special: 259 tools GAMESCENE INTEGRATION: - Added Animal and MemoryHeartUI imports - Preload heart icon and heartbeat audio - Update animals each frame for proximity detection - Example domestic dog spawns at (600, 600) EMOTIONAL IMPACT: When Kai approaches a domestic dog, a pulsating heart appears with text 'Spominjaš se...' (You remember...) - creating a powerful moment of nostalgia for his lost family pet.
This commit is contained in:
77
src/entities/Animal.js
Normal file
77
src/entities/Animal.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 🐕 ANIMAL SYSTEM
|
||||
* Handles all animal interactions including emotional triggers
|
||||
*/
|
||||
|
||||
class Animal extends Phaser.GameObjects.Sprite {
|
||||
constructor(scene, x, y, texture, type = 'domestic') {
|
||||
super(scene, x, y, texture);
|
||||
|
||||
this.scene = scene;
|
||||
this.type = type; // 'wild', 'domestic', 'infected'
|
||||
|
||||
// Animal properties
|
||||
this.animalName = '';
|
||||
this.isHostile = (type === 'infected' || type === 'wild');
|
||||
this.triggerMemory = (type === 'domestic'); // Domestic animals trigger memories
|
||||
|
||||
// Proximity detection
|
||||
this.proximityRadius = type === 'domestic' ? 150 : 100;
|
||||
this.isPlayerNear = false;
|
||||
|
||||
// Memory trigger
|
||||
this.memoryTriggered = false;
|
||||
|
||||
// Add to scene
|
||||
scene.add.existing(this);
|
||||
scene.physics.add.existing(this);
|
||||
|
||||
// Setup physics
|
||||
this.body.setImmovable(true);
|
||||
this.body.setCollideWorldBounds(true);
|
||||
}
|
||||
|
||||
update(player) {
|
||||
if (!player) return;
|
||||
|
||||
// Calculate distance to player
|
||||
const distance = Phaser.Math.Distance.Between(
|
||||
this.x, this.y,
|
||||
player.x, player.y
|
||||
);
|
||||
|
||||
const wasNear = this.isPlayerNear;
|
||||
this.isPlayerNear = distance < this.proximityRadius;
|
||||
|
||||
// Trigger memory when player gets close to domestic animal (first time only)
|
||||
if (this.triggerMemory && this.isPlayerNear && !wasNear && !this.memoryTriggered) {
|
||||
this.onMemoryTriggered();
|
||||
}
|
||||
|
||||
// Stop memory when player leaves
|
||||
if (!this.isPlayerNear && wasNear && this.memoryTriggered) {
|
||||
this.onMemoryEnded();
|
||||
}
|
||||
}
|
||||
|
||||
onMemoryTriggered() {
|
||||
console.log(`💙 Kai remembers the family dog...`);
|
||||
this.memoryTriggered = true;
|
||||
|
||||
// Emit event for UI to handle
|
||||
this.scene.events.emit('animal:memory_triggered', {
|
||||
animal: this,
|
||||
type: 'domestic_dog'
|
||||
});
|
||||
}
|
||||
|
||||
onMemoryEnded() {
|
||||
console.log(`💔 Memory fades...`);
|
||||
|
||||
this.scene.events.emit('animal:memory_ended', {
|
||||
animal: this
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Animal;
|
||||
@@ -1,6 +1,9 @@
|
||||
// 🎮 GAME SCENE - MEADOW AWAKENING VERSION (Hytale Style)
|
||||
// "3x Blink to Wake Up + Virus Fog + Hytale UI"
|
||||
// Updated: January 19, 2026
|
||||
// "3x Blink to Wake Up + Virus Fog + Hytale UI + Emotional Memories"
|
||||
// Updated: January 20, 2026
|
||||
|
||||
import Animal from '../entities/Animal.js';
|
||||
import MemoryHeartUI from '../ui/MemoryHeartUI.js';
|
||||
|
||||
class GameScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
@@ -16,6 +19,18 @@ class GameScene extends Phaser.Scene {
|
||||
this.blinkCount = 0;
|
||||
this.isFullyAwake = false;
|
||||
this.virusFog = null;
|
||||
|
||||
// Animal System
|
||||
this.animals = [];
|
||||
this.memoryHeartUI = null;
|
||||
}
|
||||
|
||||
preload() {
|
||||
// Load heart icon for memory UI
|
||||
this.load.image('heart_icon', 'assets/slike/items/ui/MOJE_SLIKE_KONCNA_ostalo_vmesnik_ikone_heart_icon_style32.png');
|
||||
|
||||
// Optional: Load heartbeat sound
|
||||
this.load.audio('heartbeat', 'assets/audio/_NEW/369017__patobottos__heartbeats-61.wav');
|
||||
}
|
||||
|
||||
create(data) {
|
||||
@@ -63,6 +78,24 @@ class GameScene extends Phaser.Scene {
|
||||
|
||||
// 7. DECORATIONS (Trees, Grass)
|
||||
this.addStarterCampDecoration(centerX, centerY);
|
||||
|
||||
// 8. ANIMAL SYSTEM & MEMORY UI
|
||||
this.initializeAnimalSystem();
|
||||
}
|
||||
|
||||
initializeAnimalSystem() {
|
||||
console.log('🐕 Initializing animal system...');
|
||||
|
||||
// Create memory heart UI
|
||||
this.memoryHeartUI = new MemoryHeartUI(this);
|
||||
|
||||
// Example: Add a domestic dog near player
|
||||
// (Replace with actual dog sprite when available)
|
||||
const dog = new Animal(this, 600, 600, 'kai_idle', 'domestic');
|
||||
dog.animalName = 'Rex (family dog)';
|
||||
this.animals.push(dog);
|
||||
|
||||
console.log(`✅ Added ${this.animals.length} animals to scene`);
|
||||
}
|
||||
|
||||
update(time, delta) {
|
||||
@@ -77,6 +110,11 @@ class GameScene extends Phaser.Scene {
|
||||
|
||||
// NORMAL MOVEMENT LOGIC
|
||||
this.handlePlayerMovement();
|
||||
|
||||
// UPDATE ANIMALS (proximity detection)
|
||||
if (this.player && this.animals) {
|
||||
this.animals.forEach(animal => animal.update(this.player));
|
||||
}
|
||||
}
|
||||
|
||||
startAmnesiaMode(music) {
|
||||
|
||||
156
src/ui/MemoryHeartUI.js
Normal file
156
src/ui/MemoryHeartUI.js
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 💙 MEMORY HEART UI
|
||||
* Displays pulsating heart when Kai remembers family pet
|
||||
*/
|
||||
|
||||
class MemoryHeartUI {
|
||||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
this.container = null;
|
||||
this.heartIcon = null;
|
||||
this.isActive = false;
|
||||
|
||||
this.createUI();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
createUI() {
|
||||
const { width, height } = this.scene.cameras.main;
|
||||
|
||||
// Container positioned in top-left (near health/stats)
|
||||
this.container = this.scene.add.container(80, 80);
|
||||
this.container.setScrollFactor(0);
|
||||
this.container.setDepth(9000);
|
||||
this.container.setAlpha(0); // Start invisible
|
||||
|
||||
// Heart icon
|
||||
this.heartIcon = this.scene.add.image(0, 0, 'heart_icon');
|
||||
this.heartIcon.setScale(0.6);
|
||||
|
||||
// Optional: Memory text
|
||||
this.memoryText = this.scene.add.text(40, 0, '', {
|
||||
fontFamily: 'Verdana',
|
||||
fontSize: '16px',
|
||||
color: '#ff6b9d',
|
||||
fontStyle: 'italic'
|
||||
}).setOrigin(0, 0.5);
|
||||
|
||||
this.container.add([this.heartIcon, this.memoryText]);
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Listen for memory triggers
|
||||
this.scene.events.on('animal:memory_triggered', this.show, this);
|
||||
this.scene.events.on('animal:memory_ended', this.hide, this);
|
||||
}
|
||||
|
||||
show(data) {
|
||||
if (this.isActive) return;
|
||||
|
||||
this.isActive = true;
|
||||
|
||||
// Set memory text based on animal
|
||||
if (data.type === 'domestic_dog') {
|
||||
this.memoryText.setText('Spominjaš se...');
|
||||
}
|
||||
|
||||
// Fade in container
|
||||
this.scene.tweens.add({
|
||||
targets: this.container,
|
||||
alpha: 1,
|
||||
duration: 500,
|
||||
ease: 'Sine.easeIn'
|
||||
});
|
||||
|
||||
// Start pulsating animation
|
||||
this.startPulsating();
|
||||
|
||||
// Optional: Play heartbeat sound
|
||||
if (this.scene.sound.get('heartbeat')) {
|
||||
this.scene.sound.play('heartbeat', { volume: 0.3, loop: true });
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (!this.isActive) return;
|
||||
|
||||
this.isActive = false;
|
||||
|
||||
// Stop pulsating
|
||||
this.stopPulsating();
|
||||
|
||||
// Fade out container
|
||||
this.scene.tweens.add({
|
||||
targets: this.container,
|
||||
alpha: 0,
|
||||
duration: 800,
|
||||
ease: 'Sine.easeOut'
|
||||
});
|
||||
|
||||
// Stop heartbeat sound
|
||||
if (this.scene.sound.get('heartbeat')) {
|
||||
const heartbeat = this.scene.sound.get('heartbeat');
|
||||
this.scene.tweens.add({
|
||||
targets: heartbeat,
|
||||
volume: 0,
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
heartbeat.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
startPulsating() {
|
||||
// Gentle pulsating scale animation
|
||||
this.pulseTween = this.scene.tweens.add({
|
||||
targets: this.heartIcon,
|
||||
scale: 0.7,
|
||||
duration: 800,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.easeInOut'
|
||||
});
|
||||
|
||||
// Subtle glow effect (tint)
|
||||
this.glowTween = this.scene.tweens.add({
|
||||
targets: this.heartIcon,
|
||||
alpha: { from: 0.8, to: 1 },
|
||||
duration: 600,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.easeInOut'
|
||||
});
|
||||
}
|
||||
|
||||
stopPulsating() {
|
||||
if (this.pulseTween) {
|
||||
this.pulseTween.stop();
|
||||
this.pulseTween = null;
|
||||
}
|
||||
|
||||
if (this.glowTween) {
|
||||
this.glowTween.stop();
|
||||
this.glowTween = null;
|
||||
}
|
||||
|
||||
// Reset to default
|
||||
this.scene.tweens.add({
|
||||
targets: this.heartIcon,
|
||||
scale: 0.6,
|
||||
alpha: 1,
|
||||
duration: 300
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.scene.events.off('animal:memory_triggered', this.show, this);
|
||||
this.scene.events.off('animal:memory_ended', this.hide, this);
|
||||
|
||||
if (this.container) {
|
||||
this.container.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MemoryHeartUI;
|
||||
Reference in New Issue
Block a user