mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
Enhance PersonChatMinigame and UI with improved caption area and parallax animation
- Updated CSS for the caption area to span the full width of the screen, enhancing visual consistency. - Introduced a new inner container for caption content to maintain a maximum width, improving layout structure. - Added parallax animation functionality in PersonChatPortraits for a more dynamic visual experience during conversations. - Implemented automatic parallax animation reset when the speaker changes, ensuring smooth transitions between dialogues.
This commit is contained in:
@@ -82,26 +82,34 @@
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
/* Caption area - positioned at bottom 1/3 of screen */
|
||||
/* Caption area - positioned at bottom 1/3 of screen, full width background */
|
||||
.person-chat-caption-area {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
max-width: 1200px;
|
||||
width: calc(100% - 40px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 33%;
|
||||
background: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.95));
|
||||
z-index: 10;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Inner container for caption content - constrained to max-width */
|
||||
.person-chat-caption-content {
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
align-content: flex-end;
|
||||
padding: 20px;
|
||||
gap: 20px;
|
||||
z-index: 10;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Talk right area - speaker name and dialogue (left side, takes more space) */
|
||||
|
||||
@@ -19,6 +19,10 @@ import InkEngine from '../../systems/ink/ink-engine.js?v=1';
|
||||
import { processGameActionTags, determineSpeaker as determineSpeakerFromTags } from '../helpers/chat-helpers.js';
|
||||
import npcConversationStateManager from '../../systems/npc-conversation-state.js?v=2';
|
||||
|
||||
// Configuration constants for dialogue auto-advance timing
|
||||
const DIALOGUE_AUTO_ADVANCE_DELAY = 5000; // Default delay in milliseconds for new dialogue text (5 seconds)
|
||||
const DIALOGUE_END_DELAY = 1000; // Delay in milliseconds for ending conversations (1 second)
|
||||
|
||||
export class PersonChatMinigame extends MinigameScene {
|
||||
/**
|
||||
* Create a PersonChatMinigame instance
|
||||
@@ -245,7 +249,7 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
* @param {Function} callback - Function to call to advance dialogue
|
||||
* @param {number} delay - Delay in milliseconds (ignored in click-through mode)
|
||||
*/
|
||||
scheduleDialogueAdvance(callback, delay = 2000) {
|
||||
scheduleDialogueAdvance(callback, delay = DIALOGUE_AUTO_ADVANCE_DELAY) {
|
||||
// Always store the callback function itself
|
||||
this.pendingContinueCallback = callback;
|
||||
|
||||
@@ -398,11 +402,11 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
if (result.canContinue) {
|
||||
// Can continue - schedule next advance
|
||||
console.log(`📋 Setting pendingContinueCallback - canContinue: true, no choices`);
|
||||
this.scheduleDialogueAdvance(() => this.showCurrentDialogue(), 2000);
|
||||
this.scheduleDialogueAdvance(() => this.showCurrentDialogue(), DIALOGUE_AUTO_ADVANCE_DELAY);
|
||||
} else {
|
||||
// Can't continue but have text - story will end
|
||||
console.log('✓ Waiting for story to end...');
|
||||
this.scheduleDialogueAdvance(() => this.endConversation(), 1000);
|
||||
this.scheduleDialogueAdvance(() => this.endConversation(), DIALOGUE_END_DELAY);
|
||||
}
|
||||
} else {
|
||||
// No text and no choices - story has ended
|
||||
@@ -699,7 +703,7 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
this.ui.showDialogue('(Conversation ended - press ESC to close)', 'system');
|
||||
console.log('🏁 Story has reached an end point');
|
||||
}
|
||||
}, 2000);
|
||||
}, DIALOGUE_AUTO_ADVANCE_DELAY);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -726,7 +730,7 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
// Display next line after delay
|
||||
this.scheduleDialogueAdvance(() => {
|
||||
this.displayDialogueBlocksSequentially(blocks, originalResult, blockIndex, lineIndex + 1, newAccumulatedText);
|
||||
}, 2000);
|
||||
}, DIALOGUE_AUTO_ADVANCE_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -774,8 +778,8 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
console.log(`📋 ${result.choices.length} choices available`);
|
||||
} else if (result.canContinue) {
|
||||
// No choices but can continue - auto-advance after delay
|
||||
console.log('⏳ Auto-continuing in 2 seconds...');
|
||||
this.scheduleDialogueAdvance(() => this.showCurrentDialogue(), 2000);
|
||||
console.log(`⏳ Auto-continuing in ${DIALOGUE_AUTO_ADVANCE_DELAY / 1000} seconds...`);
|
||||
this.scheduleDialogueAdvance(() => this.showCurrentDialogue(), DIALOGUE_AUTO_ADVANCE_DELAY);
|
||||
} else {
|
||||
// No choices and can't continue - check if there's more content
|
||||
// Try to continue anyway (for linear scripted conversations)
|
||||
@@ -800,7 +804,7 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
}
|
||||
this.ui.showDialogue('(No more dialogue available - press ESC to close)', 'system');
|
||||
}
|
||||
}, 2000);
|
||||
}, DIALOGUE_AUTO_ADVANCE_DELAY);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error displaying dialogue:', error);
|
||||
|
||||
@@ -34,6 +34,8 @@ export default class PersonChatPortraits {
|
||||
|
||||
// Background image
|
||||
this.backgroundImage = null; // Loaded background image
|
||||
this.parallaxStartTime = Date.now(); // Track time for parallax animation
|
||||
this.animationFrameId = null; // Track animation frame for cleanup
|
||||
|
||||
// Sprite info
|
||||
this.spriteSheet = null;
|
||||
@@ -98,6 +100,8 @@ export default class PersonChatPortraits {
|
||||
// Handle window resize
|
||||
window.addEventListener('resize', () => this.handleResize());
|
||||
|
||||
// Parallax animation will start automatically when background image loads
|
||||
|
||||
console.log(`✅ Portrait initialized for ${this.npc.id} (${this.canvas.width}x${this.canvas.height})`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -193,6 +197,67 @@ export default class PersonChatPortraits {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start parallax animation loop (stops after movement completes)
|
||||
*/
|
||||
startParallaxAnimation() {
|
||||
if (this.animationFrameId) {
|
||||
return; // Already animating
|
||||
}
|
||||
|
||||
const parallaxDuration = 2.0; // Duration of movement in seconds
|
||||
|
||||
const animate = () => {
|
||||
if (!this.canvas || !this.backgroundImage) {
|
||||
this.animationFrameId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const elapsed = (Date.now() - this.parallaxStartTime) / 1000; // Time in seconds
|
||||
|
||||
// Re-render to update parallax position
|
||||
// This will redraw both background (with parallax) and sprite
|
||||
this.render();
|
||||
|
||||
// Continue animation until movement is complete
|
||||
if (elapsed < parallaxDuration) {
|
||||
this.animationFrameId = requestAnimationFrame(animate);
|
||||
} else {
|
||||
// Movement complete, stop animation loop
|
||||
this.animationFrameId = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Start animation loop
|
||||
this.animationFrameId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop parallax animation loop
|
||||
*/
|
||||
stopParallaxAnimation() {
|
||||
if (this.animationFrameId) {
|
||||
cancelAnimationFrame(this.animationFrameId);
|
||||
this.animationFrameId = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset and restart parallax animation (called when speaker changes)
|
||||
*/
|
||||
resetParallaxAnimation() {
|
||||
// Stop current animation if running
|
||||
this.stopParallaxAnimation();
|
||||
|
||||
// Reset start time to begin new animation
|
||||
this.parallaxStartTime = Date.now();
|
||||
|
||||
// Restart animation if background is loaded
|
||||
if (this.backgroundImage) {
|
||||
this.startParallaxAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up sprite sheet and frame information
|
||||
*/
|
||||
@@ -247,6 +312,8 @@ export default class PersonChatPortraits {
|
||||
console.log(`✅ Background image loaded: ${this.backgroundPath}`);
|
||||
// Re-render when background loads
|
||||
this.render();
|
||||
// Start parallax animation now that background is loaded
|
||||
this.startParallaxAnimation();
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
@@ -311,12 +378,34 @@ export default class PersonChatPortraits {
|
||||
y = 0;
|
||||
}
|
||||
|
||||
// Calculate subtle parallax effect - move background towards sprite once and stop
|
||||
const elapsed = (Date.now() - this.parallaxStartTime) / 1000; // Time in seconds
|
||||
const parallaxDuration = 1.0; // Duration of movement in seconds
|
||||
const maxParallaxAmount = 10; // Maximum parallax offset in pixels
|
||||
|
||||
// Calculate parallax amount: moves from 0 to maxParallaxAmount over duration, then stops
|
||||
let parallaxAmount = 0;
|
||||
if (elapsed < parallaxDuration) {
|
||||
// Ease-out animation: starts fast, slows down as it approaches target
|
||||
const progress = elapsed / parallaxDuration; // 0 to 1
|
||||
const easedProgress = 1 - Math.pow(1 - progress, 3); // Ease-out cubic
|
||||
parallaxAmount = easedProgress * maxParallaxAmount;
|
||||
} else {
|
||||
// Movement complete, stay at max position
|
||||
parallaxAmount = maxParallaxAmount;
|
||||
}
|
||||
|
||||
// Move background towards sprite (towards center)
|
||||
// NPC on right: move left (negative), Player on left: move right (positive)
|
||||
const parallaxOffset = this.flipped ? parallaxAmount : -parallaxAmount;
|
||||
x += parallaxOffset;
|
||||
|
||||
// Draw background image at same pixel scale as sprite
|
||||
// Note: Canvas will clip anything outside its bounds, but background may extend beyond
|
||||
this.ctx.imageSmoothingEnabled = false; // Pixel-perfect rendering
|
||||
this.ctx.drawImage(
|
||||
this.backgroundImage,
|
||||
x, y, // Destination position
|
||||
x, y, // Destination position (with parallax offset)
|
||||
scaledWidth, scaledHeight // Destination size (scaled to match sprite scale exactly)
|
||||
);
|
||||
}
|
||||
@@ -577,7 +666,9 @@ export default class PersonChatPortraits {
|
||||
* Destroy portrait and cleanup
|
||||
*/
|
||||
destroy() {
|
||||
// No timers to clear in this version
|
||||
// Stop parallax animation
|
||||
this.stopParallaxAnimation();
|
||||
|
||||
if (this.canvas && this.canvas.parentNode) {
|
||||
this.canvas.parentNode.removeChild(this.canvas);
|
||||
}
|
||||
|
||||
@@ -108,6 +108,10 @@ export default class PersonChatUI {
|
||||
const captionArea = document.createElement('div');
|
||||
captionArea.className = 'person-chat-caption-area';
|
||||
|
||||
// Inner content wrapper - constrained to max-width
|
||||
const captionContent = document.createElement('div');
|
||||
captionContent.className = 'person-chat-caption-content';
|
||||
|
||||
// Talk right area - speaker name + dialogue
|
||||
const talkRightArea = document.createElement('div');
|
||||
talkRightArea.className = 'person-chat-talk-right';
|
||||
@@ -150,9 +154,12 @@ export default class PersonChatUI {
|
||||
|
||||
controlsArea.appendChild(choicesContainer);
|
||||
|
||||
// Assemble caption area: talk-right, controls
|
||||
captionArea.appendChild(talkRightArea);
|
||||
captionArea.appendChild(controlsArea);
|
||||
// Assemble caption content: talk-right, controls
|
||||
captionContent.appendChild(talkRightArea);
|
||||
captionContent.appendChild(controlsArea);
|
||||
|
||||
// Add content wrapper to caption area
|
||||
captionArea.appendChild(captionContent);
|
||||
|
||||
// Assemble main content
|
||||
mainContent.appendChild(portraitSection);
|
||||
@@ -281,6 +288,12 @@ export default class PersonChatUI {
|
||||
}
|
||||
|
||||
this.portraitRenderer.setupSpriteInfo();
|
||||
|
||||
// Reset parallax animation for new speaker
|
||||
if (this.portraitRenderer.backgroundImage) {
|
||||
this.portraitRenderer.resetParallaxAnimation();
|
||||
}
|
||||
|
||||
this.portraitRenderer.render();
|
||||
} catch (error) {
|
||||
console.error('❌ Error updating portrait:', error);
|
||||
|
||||
Reference in New Issue
Block a user