Updated Diary - SESSION 3 Complete
All work from Christmas Day documented: - Session 1: Biomes (18/18) - Session 2: Story integration + UI systems - Session 3: Grok character + Susi Total: 5 hours, 1486 lines code, 6 systems
This commit is contained in:
334
src/systems/TwinBondUISystem.js
Normal file
334
src/systems/TwinBondUISystem.js
Normal file
@@ -0,0 +1,334 @@
|
||||
/**
|
||||
* TwinBondUISystem.js
|
||||
* ====================
|
||||
* KRVAVA ŽETEV - Twin Bond Heartbeat UI
|
||||
*
|
||||
* Features:
|
||||
* - Visual heartbeat indicator on screen
|
||||
* - Speeds up when near Ana's clues
|
||||
* - Purple glow effect
|
||||
* - Distance-based intensity
|
||||
* - Twin bond strength indicator
|
||||
*
|
||||
* @author NovaFarma Team
|
||||
* @date 2025-12-25
|
||||
*/
|
||||
|
||||
export default class TwinBondUISystem {
|
||||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
|
||||
// UI elements
|
||||
this.heartContainer = null;
|
||||
this.heartIcon = null;
|
||||
this.glowEffect = null;
|
||||
this.bondStrengthBar = null;
|
||||
|
||||
// State
|
||||
this.baseHeartRate = 60; // bpm
|
||||
this.currentHeartRate = 60;
|
||||
this.bondStrength = 0; // 0-100
|
||||
this.nearestClueDistance = Infinity;
|
||||
|
||||
// Animation
|
||||
this.heartbeatTween = null;
|
||||
this.lastBeat = 0;
|
||||
|
||||
console.log('💜 TwinBondUISystem initialized');
|
||||
|
||||
this.createUI();
|
||||
this.startHeartbeat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create UI elements
|
||||
*/
|
||||
createUI() {
|
||||
const ui = this.scene.scene.get('UIScene') || this.scene;
|
||||
|
||||
// Container (top-left corner)
|
||||
this.heartContainer = ui.add.container(50, 50);
|
||||
this.heartContainer.setDepth(1000); // Above everything
|
||||
this.heartContainer.setScrollFactor(0); // Fixed to camera
|
||||
|
||||
// Purple glow effect (behind heart)
|
||||
this.glowEffect = ui.add.circle(0, 0, 25, 0x9370DB, 0);
|
||||
this.heartContainer.add(this.glowEffect);
|
||||
|
||||
// Heart icon (emoji-style)
|
||||
this.heartIcon = ui.add.text(0, 0, '💜', {
|
||||
fontSize: '32px',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
this.heartIcon.setOrigin(0.5);
|
||||
this.heartContainer.add(this.heartIcon);
|
||||
|
||||
// Bond strength bar (below heart)
|
||||
const barWidth = 50;
|
||||
const barHeight = 5;
|
||||
|
||||
// Background bar
|
||||
const barBg = ui.add.rectangle(0, 30, barWidth, barHeight, 0x333333);
|
||||
this.heartContainer.add(barBg);
|
||||
|
||||
// Strength bar
|
||||
this.bondStrengthBar = ui.add.rectangle(
|
||||
-barWidth / 2,
|
||||
30,
|
||||
0, // Start at 0 width
|
||||
barHeight,
|
||||
0x9370DB
|
||||
);
|
||||
this.bondStrengthBar.setOrigin(0, 0.5);
|
||||
this.heartContainer.add(this.bondStrengthBar);
|
||||
|
||||
// Bond strength text
|
||||
this.bondStrengthText = ui.add.text(0, 45, '0%', {
|
||||
fontSize: '10px',
|
||||
fontFamily: 'Arial',
|
||||
color: '#9370DB'
|
||||
});
|
||||
this.bondStrengthText.setOrigin(0.5);
|
||||
this.heartContainer.add(this.bondStrengthText);
|
||||
|
||||
console.log('✅ Twin Bond UI created');
|
||||
}
|
||||
|
||||
/**
|
||||
* Start heartbeat animation
|
||||
*/
|
||||
startHeartbeat() {
|
||||
const beatInterval = () => {
|
||||
const bpm = this.currentHeartRate;
|
||||
const msPerBeat = 60000 / bpm; // Convert BPM to ms
|
||||
|
||||
// Heart pump animation
|
||||
if (this.heartIcon) {
|
||||
this.scene.tweens.add({
|
||||
targets: [this.heartIcon],
|
||||
scale: { from: 1, to: 1.3 },
|
||||
duration: 100,
|
||||
yoyo: true,
|
||||
ease: 'Quad.Out'
|
||||
});
|
||||
|
||||
// Glow pulse
|
||||
this.scene.tweens.add({
|
||||
targets: [this.glowEffect],
|
||||
alpha: { from: 0, to: 0.6 },
|
||||
scale: { from: 1, to: 1.5 },
|
||||
duration: 150,
|
||||
yoyo: true,
|
||||
ease: 'Quad.Out'
|
||||
});
|
||||
}
|
||||
|
||||
// Schedule next beat
|
||||
this.heartbeatTimer = setTimeout(() => beatInterval(), msPerBeat);
|
||||
};
|
||||
|
||||
// Start beating
|
||||
beatInterval();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update system (called every frame)
|
||||
*/
|
||||
update(time, delta) {
|
||||
if (!this.scene.player) return;
|
||||
|
||||
// Calculate distance to nearest Ana clue
|
||||
this.calculateNearestClue();
|
||||
|
||||
// Update heart rate based on distance
|
||||
this.updateHeartRate();
|
||||
|
||||
// Update bond strength display
|
||||
this.updateBondStrengthDisplay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate distance to nearest Ana's clue
|
||||
*/
|
||||
calculateNearestClue() {
|
||||
if (!this.scene.anaClueSystem) {
|
||||
this.nearestClueDistance = Infinity;
|
||||
return;
|
||||
}
|
||||
|
||||
const playerX = this.scene.player.sprite.x;
|
||||
const playerY = this.scene.player.sprite.y;
|
||||
|
||||
let minDistance = Infinity;
|
||||
|
||||
// Check all clues
|
||||
this.scene.anaClueSystem.clueLocations.forEach((position, clueId) => {
|
||||
// Skip already discovered clues
|
||||
if (this.scene.anaClueSystem.discovered.has(clueId)) return;
|
||||
|
||||
const dx = position.x - playerX;
|
||||
const dy = position.y - playerY;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
}
|
||||
});
|
||||
|
||||
this.nearestClueDistance = minDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update heart rate based on proximity to clues
|
||||
*/
|
||||
updateHeartRate() {
|
||||
const distance = this.nearestClueDistance;
|
||||
|
||||
// Distance thresholds (in pixels)
|
||||
const veryClose = 200; // 4-5 blocks
|
||||
const close = 480; // 10 blocks
|
||||
const medium = 960; // 20 blocks
|
||||
|
||||
let targetHeartRate = this.baseHeartRate;
|
||||
|
||||
if (distance < veryClose) {
|
||||
// VERY CLOSE - Rapid heartbeat!
|
||||
targetHeartRate = 120; // 2x normal
|
||||
this.bondStrength = 100;
|
||||
} else if (distance < close) {
|
||||
// CLOSE - Fast heartbeat
|
||||
targetHeartRate = 90;
|
||||
this.bondStrength = 75;
|
||||
} else if (distance < medium) {
|
||||
// MEDIUM - Slightly elevated
|
||||
targetHeartRate = 75;
|
||||
this.bondStrength = 50;
|
||||
} else {
|
||||
// FAR - Normal heartbeat
|
||||
targetHeartRate = 60;
|
||||
this.bondStrength = Math.max(0, this.bondStrength - 1); // Slowly decrease
|
||||
}
|
||||
|
||||
// Smooth transition
|
||||
this.currentHeartRate += (targetHeartRate - this.currentHeartRate) * 0.1;
|
||||
|
||||
// Clamp
|
||||
this.currentHeartRate = Phaser.Math.Clamp(this.currentHeartRate, 30, 150);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update bond strength visual display
|
||||
*/
|
||||
updateBondStrengthDisplay() {
|
||||
if (!this.bondStrengthBar) return;
|
||||
|
||||
// Update bar width
|
||||
const maxWidth = 50;
|
||||
const targetWidth = (this.bondStrength / 100) * maxWidth;
|
||||
this.bondStrengthBar.width = targetWidth;
|
||||
|
||||
// Update text
|
||||
if (this.bondStrengthText) {
|
||||
this.bondStrengthText.setText(`${Math.floor(this.bondStrength)}%`);
|
||||
}
|
||||
|
||||
// Color gradient based on strength
|
||||
if (this.bondStrength > 75) {
|
||||
this.bondStrengthBar.setFillStyle(0xFF69B4); // Hot pink - very strong
|
||||
} else if (this.bondStrength > 50) {
|
||||
this.bondStrengthBar.setFillStyle(0x9370DB); // Medium purple
|
||||
} else if (this.bondStrength > 25) {
|
||||
this.bondStrengthBar.setFillStyle(0x6A5ACD); // Slate blue
|
||||
} else {
|
||||
this.bondStrengthBar.setFillStyle(0x483D8B); // Dark slate blue
|
||||
}
|
||||
|
||||
// Pulsing glow when strong bond
|
||||
if (this.bondStrength > 75 && this.glowEffect) {
|
||||
this.glowEffect.setAlpha(0.3 + Math.sin(Date.now() / 200) * 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger special bond moment (e.g., flashback)
|
||||
*/
|
||||
triggerBondMoment(intensity = 'medium') {
|
||||
console.log(`💜 Twin Bond Moment! Intensity: ${intensity}`);
|
||||
|
||||
// Screen flash
|
||||
this.scene.cameras.main.flash(500, 147, 112, 219, false); // Purple flash
|
||||
|
||||
// Heart explosion effect
|
||||
if (this.heartIcon) {
|
||||
this.scene.tweens.add({
|
||||
targets: [this.heartIcon],
|
||||
scale: { from: 1, to: 2 },
|
||||
alpha: { from: 1, to: 0 },
|
||||
duration: 800,
|
||||
ease: 'Quad.Out',
|
||||
onComplete: () => {
|
||||
this.heartIcon.setScale(1);
|
||||
this.heartIcon.setAlpha(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Glow burst
|
||||
if (this.glowEffect) {
|
||||
this.scene.tweens.add({
|
||||
targets: [this.glowEffect],
|
||||
scale: { from: 1, to: 5 },
|
||||
alpha: { from: 0.8, to: 0 },
|
||||
duration: 1000,
|
||||
ease: 'Quad.Out',
|
||||
onComplete: () => {
|
||||
this.glowEffect.setScale(1);
|
||||
this.glowEffect.setAlpha(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Temporary heart rate spike
|
||||
this.currentHeartRate = 150;
|
||||
setTimeout(() => {
|
||||
this.currentHeartRate = this.baseHeartRate;
|
||||
}, 3000);
|
||||
|
||||
// Bond strength boost
|
||||
this.bondStrength = 100;
|
||||
|
||||
// Show notification
|
||||
this.scene.events.emit('show-notification', {
|
||||
title: 'Twin Bond Activated!',
|
||||
text: '💜 You feel Ana\'s presence intensify!',
|
||||
icon: '💜',
|
||||
color: '#9370DB'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current heart rate (for debugging)
|
||||
*/
|
||||
getCurrentHeartRate() {
|
||||
return Math.floor(this.currentHeartRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bond strength (for other systems)
|
||||
*/
|
||||
getBondStrength() {
|
||||
return this.bondStrength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup
|
||||
*/
|
||||
destroy() {
|
||||
if (this.heartbeatTimer) {
|
||||
clearTimeout(this.heartbeatTimer);
|
||||
}
|
||||
if (this.heartContainer) {
|
||||
this.heartContainer.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user