From 1a09862dbd6a972deac721cb16f858dbba9f79ef Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Fri, 14 Nov 2025 12:19:28 +0000 Subject: [PATCH] Remove cut-scene improvements documentation and refactor visibility management - Deleted the `CUTSCENE_IMPROVEMENTS.md` file as it contained outdated information. - Refactored the game canvas visibility management in `game.js` and `minigame-manager.js` to streamline the handling of cut-scene visibility. - Updated `PersonChatMinigame` to simplify the handling of the `canEscConversation` parameter. - Adjusted `NPCManager` to pass relevant parameters for minigame initialization without relying on deprecated settings. --- docs/CUTSCENE_IMPROVEMENTS.md | 214 ------------------ js/core/game.js | 15 -- js/minigames/framework/minigame-manager.js | 34 --- .../person-chat/person-chat-minigame.js | 27 --- js/systems/npc-manager.js | 11 +- js/systems/npc-sprites.js | 6 + scenarios/npc-sprite-test2.json | 2 - 7 files changed, 9 insertions(+), 300 deletions(-) delete mode 100644 docs/CUTSCENE_IMPROVEMENTS.md diff --git a/docs/CUTSCENE_IMPROVEMENTS.md b/docs/CUTSCENE_IMPROVEMENTS.md deleted file mode 100644 index 4689c0c..0000000 --- a/docs/CUTSCENE_IMPROVEMENTS.md +++ /dev/null @@ -1,214 +0,0 @@ -# Cut-Scene Improvements: hideGameDuringMinigame and canEscConversation - -## Overview - -Two new features have been added to BreakEscape to improve cut-scene presentation and player experience: - -1. **hideGameDuringMinigame** - Hide the main game canvas while minigames (like person-chat conversations) are running -2. **canEscConversation** - Control whether players can press Esc to exit NPC conversations - -These features are particularly useful for creating immersive opening cut-scenes that trigger at game start (delay: 0ms). - -## Feature 1: hideGameDuringMinigame - -### Problem -When a timed conversation is triggered with `delay: 0`, it starts immediately after the NPC is loaded. However, the main game is briefly visible before the person-chat minigame launches, breaking the immersion of a cut-scene. - -### Solution -Set `hideGameDuringMinigame: true` in the scenario JSON to hide the main game canvas during minigames and show it again when the minigame exits. - -### Usage - -**Scenario-level (applies to all minigames):** -```json -{ - "scenario_brief": "My scenario", - "startRoom": "intro_room", - "hideGameDuringMinigame": true -} -``` - -**Minigame-level (overrides scenario setting for specific minigame):** -```javascript -window.MinigameFramework.startMinigame('person-chat', null, { - npcId: 'director', - hideGameDuringMinigame: true -}); -``` - -### How It Works -1. **At startup:** If `hideGameDuringMinigame: true` is set on the scenario: - - Canvas is hidden (`display: none`) in `game.js` create() BEFORE first room displays - - Inventory container is also hidden (prevents UI from appearing during cut-scene) -2. **During minigames:** Both canvas and inventory container are hidden before minigame starts -3. **On exit:** When the minigame exits: - - Canvas is shown again (`display: block`) - - Inventory container is shown again -4. **Game state:** Game input remains disabled during the minigame to prevent player interaction; the game continues updating in the background - -### Timing Details -The canvas AND inventory are hidden VERY early in game initialization: -- After scenario is loaded ✅ -- After scenario validation ✅ -- **BEFORE first room is created** ✅ -- **BEFORE inventory renders** ✅ -- **BEFORE any visuals render** ✅ - -This ensures zero flash of the main game or UI elements - players see a completely blank page until the minigame launches and fills the screen. - -## Feature 2: canEscConversation - -### Problem -For critical cut-scenes or story moments, you may want to prevent players from casually pressing Esc to exit the conversation. Some scenes should be mandatory viewing. - -### Solution -Set `canEscConversation: false` on the NPC to disable the Escape key during that NPC's conversations AND hide the close button (×). - -### Usage - -**NPC-level setting:** -```json -{ - "id": "director", - "displayName": "Mission Director", - "npcType": "person", - "canEscConversation": false, - "storyPath": "scenarios/ink/director.json", - "timedConversation": { - "delay": 0, - "targetKnot": "mission_briefing" - } -} -``` - -**Default behavior:** -If not specified, `canEscConversation` defaults to `true` (players can press Esc and see the close button). - -### How It Works -1. PersonChatMinigame checks the `canEscConversation` setting in `init()` -2. If `false`, the minigame is configured with `showCancel: false` to hide the close button (×) -3. The fallback Escape handler from the base MinigameScene is removed in `start()` -4. Players cannot press Esc to close -5. Players cannot click a close button (it's not shown) -6. Conversation can only be exited by completing the dialogue naturally -7. This creates a truly "locked" cut-scene that must be viewed to completion - -## Complete Example: Opening Cut-Scene - -```json -{ - "scenario_brief": "Corporate Espionage Mission", - "startRoom": "safe_house", - "hideGameDuringMinigame": true, - - "rooms": { - "safe_house": { - "type": "room_office", - "npcs": [ - { - "id": "handler", - "displayName": "Handler", - "npcType": "person", - "position": { "x": 5, "y": 5 }, - "spriteSheet": "hacker", - "storyPath": "scenarios/ink/handler.json", - "canEscConversation": false, - "currentKnot": "start", - "timedConversation": { - "delay": 0, - "targetKnot": "mission_briefing", - "background": "assets/backgrounds/briefing_room.png" - } - } - ] - } - } -} -``` - -In this setup: -1. ✅ Game scenario loads -2. ✅ Main game canvas is hidden BEFORE first room displays (hideGameDuringMinigame: true) -3. ✅ Handler NPC is loaded -4. ✅ Timed conversation triggers immediately (delay: 0) -5. ✅ Person-chat minigame shows mission briefing at "mission_briefing" knot -6. ✅ Player cannot press Esc to skip (canEscConversation: false) -7. ✅ Close button (×) is hidden (canEscConversation: false) -8. ✅ Conversation must be completed naturally - no escape routes -9. ✅ Once conversation ends, canvas is shown again and game is playable - -## Combining with Other Minigames - -These features work with any minigame type: -- `person-chat` (NPC conversations) -- `notes` (mission briefs, readable documents) -- `container` (equipment/inventory management) -- Custom minigames that extend MinigameScene - -Example with mission brief: -```json -{ - "id": "briefing_doc", - "type": "notes", - "name": "Mission Brief", - "hideGameDuringMinigame": true, - "readable": true, - "text": "Your mission objectives are..." -} -``` - -## Technical Details - -### Implementation Details -- Modified `js/core/game.js` - Hides canvas at startup if `hideGameDuringMinigame: true` -- Modified `js/minigames/framework/minigame-manager.js` - Hides/shows canvas during minigames -- Updated `js/minigames/person-chat/person-chat-minigame.js` - Hides buttons and Esc key when `canEscConversation: false` -- Modified `js/systems/npc-manager.js` - Passes flags during timed conversation startup - -### Canvas & Inventory Manipulation -- **Early hiding:** In `game.js` create() function, both canvas and inventory container are hidden BEFORE room creation -- **Runtime hiding:** Canvas via `this.mainGameScene.game.canvas`, inventory via `document.getElementById('inventory-container')` -- **Visibility control:** Inline CSS: `element.style.display = 'none' | 'block'` -- **Game state:** Preserves game state; the Phaser game continues updating in the background -- **Clean UI:** No game elements visible while minigame is active - -### Escape Key & Button Handling -- **Escape key:** Base MinigameScene sets fallback handler in `start()` method -- **Esc disabling:** PersonChatMinigame removes handler in its `start()` when `canEscConversation: false` -- **Close button (×):** Hidden via `closeBtn.style.display = 'none'` in PersonChatMinigame `init()` -- **Cancel button:** Hidden via `showCancel: false` parameter passed to base class -- **Result:** Creates completely "locked" cut-scene with no escape routes - -## Testing - -To test these features: - -1. Load the test scenario: `scenarios/npc-sprite-test2.json` -2. Observe that: - - Game canvas is hidden when person-chat minigame opens - - Esc key does not work for the "Back NPC" (test_npc_back) - - Close button (×) still works - - Canvas reappears when conversation ends - -## Browser Compatibility - -These features use standard DOM APIs and CSS: -- `HTMLElement.style.display` - Widely supported -- `EventTarget.removeEventListener()` - Widely supported -- No polyfills required for modern browsers - -## Performance Considerations - -- Hiding the canvas (`display: none`) keeps the game running in the background -- The Phaser game continues to update, which maintains game state -- No memory overhead - just DOM style manipulation -- Ideal for scenarios with multiple cut-scenes - -## Future Enhancements - -Possible extensions to these features: -- `pauseGameDuringMinigame` - Pause Phaser update loop during minigames -- `hideUIElementsDuringMinigame` - Hide HUD elements like inventory -- `canClickExitDuringMinigame` - Control close button visibility -- `minigameOpacity` - Add fade-in/fade-out effects - diff --git a/js/core/game.js b/js/core/game.js index 8675a4f..fdfb870 100644 --- a/js/core/game.js +++ b/js/core/game.js @@ -458,21 +458,6 @@ export async function create() { return; } - // Check if we need to hide the game canvas for cut-scenes/minigames - // This must be done BEFORE the first room is displayed - if (gameScenario.hideGameDuringMinigame) { - if (this.game && this.game.canvas) { - this.game.canvas.style.display = 'none'; - console.log('🎮 Hidden main game canvas at startup (hideGameDuringMinigame: true)'); - } - // Also hide the inventory container - const inventoryContainer = document.getElementById('inventory-container'); - if (inventoryContainer) { - inventoryContainer.style.display = 'none'; - console.log('🎮 Hidden inventory container at startup'); - } - } - // Initialize global narrative variables from scenario if (gameScenario.globalVariables) { window.gameState.globalVariables = { ...gameScenario.globalVariables }; diff --git a/js/minigames/framework/minigame-manager.js b/js/minigames/framework/minigame-manager.js index 034603e..0bf4280 100644 --- a/js/minigames/framework/minigame-manager.js +++ b/js/minigames/framework/minigame-manager.js @@ -6,7 +6,6 @@ export const MinigameFramework = { currentMinigame: null, registeredScenes: {}, MinigameScene: MinigameScene, // Export the base class - gameHiddenDuringMinigame: false, // Track if game was hidden init(gameScene) { this.mainGameScene = gameScene; @@ -50,23 +49,6 @@ export const MinigameFramework = { } } - // Check if main game should be hidden during this minigame - const hideGameDuringMinigame = params?.hideGameDuringMinigame || window.gameScenario?.hideGameDuringMinigame || false; - if (hideGameDuringMinigame && this.mainGameScene && this.mainGameScene.game) { - const canvas = this.mainGameScene.game.canvas; - if (canvas) { - canvas.style.display = 'none'; - this.gameHiddenDuringMinigame = true; - console.log('🎮 Hidden main game canvas during minigame'); - } - // Also hide the inventory container - const inventoryContainer = document.getElementById('inventory-container'); - if (inventoryContainer) { - inventoryContainer.style.display = 'none'; - console.log('🎮 Hidden inventory container during minigame'); - } - } - // Disable main game input if we have a main game scene // (unless the minigame explicitly allows game input via disableGameInput: false) if (this.mainGameScene && this.mainGameScene.input) { @@ -140,22 +122,6 @@ export const MinigameFramework = { }); } - // Show main game canvas again if it was hidden - if (this.gameHiddenDuringMinigame && this.mainGameScene && this.mainGameScene.game) { - const canvas = this.mainGameScene.game.canvas; - if (canvas) { - canvas.style.display = 'block'; - this.gameHiddenDuringMinigame = false; - console.log('🎮 Showed main game canvas again after minigame'); - } - // Also show the inventory container again - const inventoryContainer = document.getElementById('inventory-container'); - if (inventoryContainer) { - inventoryContainer.style.display = 'block'; - console.log('🎮 Showed inventory container again after minigame'); - } - } - // Re-enable main game input if we have a main game scene and we disabled it if (this.mainGameScene && this.mainGameScene.input && this.gameInputDisabled) { this.mainGameScene.input.mouse.enabled = true; diff --git a/js/minigames/person-chat/person-chat-minigame.js b/js/minigames/person-chat/person-chat-minigame.js index 6ba672f..b2695cc 100644 --- a/js/minigames/person-chat/person-chat-minigame.js +++ b/js/minigames/person-chat/person-chat-minigame.js @@ -51,7 +51,6 @@ export class PersonChatMinigame extends MinigameScene { this.npcId = params.npcId; this.title = params.title || 'Conversation'; this.background = params.background; // Optional background image path from timedConversation - this.canEscConversation = params.canEscConversation !== false; // Allow Esc by default, can be disabled // Verify NPC exists const npc = this.npcManager.getNPC(this.npcId); @@ -151,24 +150,8 @@ export class PersonChatMinigame extends MinigameScene { if (!this.params.cancelText) { this.params.cancelText = 'End Conversation'; } - - // If canEscConversation is false, hide the close button and cancel button - if (!this.canEscConversation) { - this.params.showCancel = false; - console.log('🎭 Close/cancel buttons hidden because canEscConversation is false'); - } - super.init(); - // If canEscConversation is false, also hide the close (×) button in header - if (!this.canEscConversation) { - const closeBtn = this.container.querySelector('.minigame-close-button'); - if (closeBtn) { - closeBtn.style.display = 'none'; - console.log('🎭 Hidden minigame close button (×)'); - } - } - // Initialize timer for auto-advance this.autoAdvanceTimer = null; @@ -298,16 +281,6 @@ export class PersonChatMinigame extends MinigameScene { console.log('🎭 PersonChatMinigame started'); - // Handle canEscConversation setting - if (!this.canEscConversation) { - // Remove the fallback Escape handler set by base class if Esc is not allowed - if (this._fallbackCloseHandler) { - document.removeEventListener('keydown', this._fallbackCloseHandler); - this._fallbackCloseHandler = null; - console.log('🎭 Disabled Escape key for conversation (canEscConversation: false)'); - } - } - // Track NPC context for tag processing and minigame return flow window.currentConversationNPCId = this.npcId; window.currentConversationMinigameType = 'person-chat'; diff --git a/js/systems/npc-manager.js b/js/systems/npc-manager.js index 4a0fee7..d15fb44 100644 --- a/js/systems/npc-manager.js +++ b/js/systems/npc-manager.js @@ -704,16 +704,11 @@ export default class NPCManager { if (window.MinigameFramework && typeof window.MinigameFramework.startMinigame === 'function') { console.log(`🎭 Starting timed conversation for ${conversation.npcId} at knot: ${conversation.targetKnot}`); - // Build minigame params with optional NPC-specific settings - const minigameParams = { + window.MinigameFramework.startMinigame('person-chat', null, { npcId: conversation.npcId, title: npc.displayName || conversation.npcId, - background: conversation.background, // Optional background image path - canEscConversation: npc.canEscConversation !== false, // Allow by default, disable if set to false - hideGameDuringMinigame: npc.hideGameDuringMinigame !== undefined ? npc.hideGameDuringMinigame : window.gameScenario?.hideGameDuringMinigame - }; - - window.MinigameFramework.startMinigame('person-chat', null, minigameParams); + background: conversation.background // Optional background image path + }); } else { console.warn(`[NPCManager] MinigameFramework not available to start person-chat for timed conversation`); } diff --git a/js/systems/npc-sprites.js b/js/systems/npc-sprites.js index 6fe9a5e..ae6942a 100644 --- a/js/systems/npc-sprites.js +++ b/js/systems/npc-sprites.js @@ -52,6 +52,12 @@ export function createNPCSprite(scene, npc, roomData) { sprite.body.setSize(18, 10); // Collision body size (wider for better hit detection) sprite.body.setOffset(23, 50); // Offset for feet position (64px sprite, adjusted for wider box) + // Add friction to prevent NPCs from sliding far when pushed + // High drag causes velocity to quickly decay (good for stationary NPCs) + // High linear damping provides additional deceleration (complements drag) + sprite.body.setDrag(0.95); // Drag: 0.95 = lose 95% of velocity per second + sprite.body.setLinearDamping(0.8); // Linear damping: additional 80% deceleration + // Set up animations setupNPCAnimations(scene, sprite, spriteSheet, config, npc.id); diff --git a/scenarios/npc-sprite-test2.json b/scenarios/npc-sprite-test2.json index 6c75e5a..ff9c69d 100644 --- a/scenarios/npc-sprite-test2.json +++ b/scenarios/npc-sprite-test2.json @@ -4,7 +4,6 @@ "player_joined_organization": false }, "startRoom": "test_room", - "hideGameDuringMinigame": true, "startItemsInInventory": [ { @@ -97,7 +96,6 @@ "idleFrameEnd": 23 }, "storyPath": "scenarios/ink/test2.json", - "canEscConversation": false, "currentKnot": "hub", "timedConversation": { "delay": 0,