mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user