summaryrefslogtreecommitdiff
path: root/template
diff options
context:
space:
mode:
authorIndrajith K L2025-11-05 02:22:38 +0530
committerIndrajith K L2025-11-05 02:22:38 +0530
commit7f014770c0ad7f20c70cca1b81b71751d9fbc7c5 (patch)
tree9116d4b6e0e0063e6b509c78f81354cb5faf3d74 /template
parentcf51a7a7dc147346e439f62a57becaa11a03740f (diff)
downloadreilua-enhanced-7f014770c0ad7f20c70cca1b81b71751d9fbc7c5.tar.gz
reilua-enhanced-7f014770c0ad7f20c70cca1b81b71751d9fbc7c5.tar.bz2
reilua-enhanced-7f014770c0ad7f20c70cca1b81b71751d9fbc7c5.zip
Add dialog state pattern documentation for game templates
Added comprehensive documentation for implementing Zelda-style dialog systems using the GameState push/pop stack pattern. Includes: - Complete working dialog state example with text animation - Integration guide for game states - Character portraits and name box support - Post-dialog callback handling via resume() - Visual diagrams of state stack behavior - Best practices for when to use push/pop vs flags This pattern allows developers to create professional dialog systems with proper input control and clean state management.
Diffstat (limited to 'template')
-rw-r--r--template/DIALOG_STATE_PATTERN.md316
1 files changed, 316 insertions, 0 deletions
diff --git a/template/DIALOG_STATE_PATTERN.md b/template/DIALOG_STATE_PATTERN.md
new file mode 100644
index 0000000..08f34b2
--- /dev/null
+++ b/template/DIALOG_STATE_PATTERN.md
@@ -0,0 +1,316 @@
+# Dialog State Pattern
+
+This document explains how to implement Zelda-style dialog systems using the GameState push/pop stack pattern.
+
+## Overview
+
+The GameState system supports a **state stack** that allows you to temporarily push a new state (like a dialog) on top of the current game state, then pop back when done. This is perfect for:
+
+- Dialog systems (Zelda-style text boxes)
+- Pause menus
+- Inventory screens
+- Any UI that needs exclusive input control
+
+## How It Works
+
+```lua
+GameState.push(newState) -- Pushes current state to stack, switches to new state
+GameState.pop() -- Returns to previous state from stack
+```
+
+When you `push()` a state:
+- Current state's `leave()` is called
+- New state's `enter()` is called
+- Current state remains in memory on the stack
+
+When you `pop()`:
+- Current state's `leave()` is called
+- Previous state's `resume()` is called (not `enter()`)
+- Previous state gets control back
+
+## Example: Dialog System
+
+### Step 1: Create Dialog State
+
+Create `states/dialog.lua`:
+
+```lua
+local Object = require("lib.classic")
+local GameState = require("lib.gamestate")
+local DialogState = Object:extend()
+
+function DialogState:new(dialogData)
+ self.texts = dialogData.texts or {"Default dialog text"}
+ self.currentIndex = 1
+ self.characterName = dialogData.name or ""
+ self.portrait = dialogData.portrait or nil
+ self.textSpeed = dialogData.textSpeed or 50 -- chars per second
+ self.currentCharIndex = 0
+ self.displayedText = ""
+ self.isComplete = false
+end
+
+function DialogState:enter(previous)
+ print("Dialog opened")
+ self.currentCharIndex = 0
+ self.displayedText = ""
+ self.isComplete = false
+end
+
+function DialogState:update(dt)
+ local currentText = self.texts[self.currentIndex]
+
+ -- Animate text reveal
+ if not self.isComplete then
+ self.currentCharIndex = self.currentCharIndex + self.textSpeed * dt
+ if self.currentCharIndex >= #currentText then
+ self.currentCharIndex = #currentText
+ self.isComplete = true
+ end
+ self.displayedText = currentText:sub(1, math.floor(self.currentCharIndex))
+ end
+
+ -- Handle input
+ if RL.IsKeyPressed(RL.KEY_ENTER) or RL.IsKeyPressed(RL.KEY_SPACE) then
+ if not self.isComplete then
+ -- Skip to end of current text
+ self.currentCharIndex = #currentText
+ self.displayedText = currentText
+ self.isComplete = true
+ else
+ -- Move to next text or close dialog
+ if self.currentIndex < #self.texts then
+ self.currentIndex = self.currentIndex + 1
+ self.currentCharIndex = 0
+ self.displayedText = ""
+ self.isComplete = false
+ else
+ -- Dialog finished, return to game
+ GameState.pop()
+ end
+ end
+ end
+end
+
+function DialogState:draw()
+ local screenSize = RL.GetScreenSize()
+ local boxHeight = 200
+ local boxY = screenSize[2] - boxHeight - 20
+ local padding = 20
+
+ -- Draw dialog box
+ RL.DrawRectangle({0, boxY, screenSize[1], boxHeight}, {20, 20, 30, 240})
+ RL.DrawRectangleLines({0, boxY, screenSize[1], boxHeight}, 3, RL.WHITE)
+
+ -- Draw character name if present
+ if self.characterName ~= "" then
+ local nameBoxWidth = 200
+ local nameBoxHeight = 40
+ RL.DrawRectangle({padding, boxY - nameBoxHeight + 5, nameBoxWidth, nameBoxHeight}, {40, 40, 50, 255})
+ RL.DrawRectangleLines({padding, boxY - nameBoxHeight + 5, nameBoxWidth, nameBoxHeight}, 2, RL.WHITE)
+ RL.DrawText(self.characterName, {padding + 15, boxY - nameBoxHeight + 15}, 20, RL.WHITE)
+ end
+
+ -- Draw portrait if present
+ local textX = padding + 10
+ if self.portrait then
+ RL.DrawTexture(self.portrait, {padding + 10, boxY + padding}, RL.WHITE)
+ textX = textX + self.portrait.width + 20
+ end
+
+ -- Draw text
+ RL.DrawText(self.displayedText, {textX, boxY + padding}, 20, RL.WHITE)
+
+ -- Draw continue indicator
+ if self.isComplete then
+ local indicator = "▼"
+ if self.currentIndex < #self.texts then
+ indicator = "Press ENTER to continue"
+ else
+ indicator = "Press ENTER to close"
+ end
+ local indicatorSize = 16
+ local indicatorWidth = RL.MeasureText(indicator, indicatorSize)
+ RL.DrawText(indicator, {screenSize[1] - indicatorWidth - padding, boxY + boxHeight - padding - 20}, indicatorSize, RL.LIGHTGRAY)
+ end
+end
+
+function DialogState:leave()
+ print("Dialog closed")
+end
+
+return DialogState
+```
+
+### Step 2: Use Dialog in Game State
+
+Modify `states/game.lua`:
+
+```lua
+local Object = require("lib.classic")
+local GameState = require("lib.gamestate")
+local Animation = require("lib.animation")
+local DialogState = require("states.dialog")
+local GameState_Class = Object:extend()
+
+-- ... (Player class code) ...
+
+function GameState_Class:new()
+ self.player = nil
+ self.paused = false
+end
+
+function GameState_Class:enter(previous)
+ print("Entered game state")
+
+ local screenSize = RL.GetScreenSize()
+ self.player = Player(screenSize[1] / 2 - 16, screenSize[2] / 2 - 16)
+
+ -- Example: Show dialog when entering game (for testing)
+ -- Remove this after testing
+ local welcomeDialog = DialogState({
+ name = "System",
+ texts = {
+ "Welcome to the game!",
+ "Use WASD or Arrow keys to move around.",
+ "Press ENTER to continue through dialogs."
+ },
+ textSpeed = 30
+ })
+ GameState.push(welcomeDialog)
+end
+
+function GameState_Class:update(dt)
+ -- ESC pauses game
+ if RL.IsKeyPressed(RL.KEY_ESCAPE) then
+ self.paused = not self.paused
+ end
+
+ if self.paused then
+ return
+ end
+
+ -- Example: Press T to trigger dialog (for testing)
+ if RL.IsKeyPressed(RL.KEY_T) then
+ local testDialog = DialogState({
+ name = "NPC",
+ texts = {
+ "Hello traveler!",
+ "This is an example dialog system.",
+ "You can have multiple text boxes.",
+ "Press ENTER to continue!"
+ },
+ textSpeed = 40
+ })
+ GameState.push(testDialog)
+ end
+
+ -- Update game objects (only when not in dialog)
+ if self.player then
+ self.player:update(dt)
+ end
+end
+
+function GameState_Class:resume(previous)
+ -- Called when returning from dialog
+ print("Resumed game state from: " .. tostring(previous))
+ -- You can handle post-dialog logic here
+ -- Example: Give item, update quest state, etc.
+end
+
+function GameState_Class:draw()
+ RL.ClearBackground({50, 50, 50, 255})
+
+ -- Draw game objects
+ if self.player then
+ self.player:draw()
+ end
+
+ -- Draw pause overlay
+ if self.paused then
+ local screenSize = RL.GetScreenSize()
+ local centerX = screenSize[1] / 2
+ local centerY = screenSize[2] / 2
+
+ RL.DrawRectangle({0, 0, screenSize[1], screenSize[2]}, {0, 0, 0, 128})
+
+ local text = "PAUSED"
+ local size = 40
+ local width = RL.MeasureText(text, size)
+ RL.DrawText(text, {centerX - width / 2, centerY - 20}, size, RL.WHITE)
+ end
+
+ -- Draw controls hint
+ local hint = "WASD/ARROWS: Move | T: Test Dialog | ESC: Pause"
+ local hintSize = 16
+ local screenSize = RL.GetScreenSize()
+ RL.DrawText(hint, {10, screenSize[2] - 30}, hintSize, RL.LIGHTGRAY)
+end
+
+function GameState_Class:leave()
+ print("Left game state")
+end
+
+return GameState_Class
+```
+
+## Key Benefits
+
+✅ **Clean Separation**: Dialog has its own file and logic
+✅ **Input Control**: Dialog gets exclusive control when active
+✅ **No Coupling**: Game doesn't need to know about dialog internals
+✅ **Automatic Pause**: Game automatically stops updating when dialog is pushed
+✅ **Easy Extension**: Add more dialog types (shops, menus) using same pattern
+✅ **Post-Dialog Logic**: Use `resume()` callback to handle what happens after dialog closes
+
+## Advanced: Passing Data Back to Game
+
+You can pass data when popping:
+
+```lua
+-- In dialog state
+function DialogState:update(dt)
+ if playerMadeChoice then
+ GameState.pop(choiceData) -- Pass choice back to game
+ end
+end
+
+-- In game state
+function GameState_Class:resume(previous, choiceData)
+ if choiceData then
+ print("Player chose: " .. choiceData.choice)
+ -- Handle the choice
+ end
+end
+```
+
+## State Stack Visual
+
+```
+Initial: [Game State]
+
+After push: [Game State] -> [Dialog State] (Dialog has control)
+
+After pop: [Game State] (Game has control back)
+```
+
+## When to Use Push/Pop vs Flags
+
+**Use Push/Pop for:**
+- Dialog systems
+- Pause menus
+- Shop interfaces
+- Inventory screens
+- Any state that needs exclusive control
+
+**Use Flags (self.paused, etc.) for:**
+- Simple on/off toggles
+- Quick state checks
+- Non-blocking overlays
+- Debug info displays
+
+## See Also
+
+- `lib/gamestate.lua` - Full GameState implementation
+- `states/game.lua` - Example game state
+- `states/menu.lua` - Example menu state