From 4f34b7aad9abc3aace50140115bd9696441c8231 Mon Sep 17 00:00:00 2001 From: Indrajith K L Date: Wed, 5 Nov 2025 02:21:56 +0530 Subject: Disable antialiasing for all fonts loaded via Lua API All pixel fonts and TTF fonts loaded through RL.LoadFont(), RL.LoadFontEx(), RL.LoadFontFromImage(), and RL.LoadFontFromMemory() now use TEXTURE_FILTER_POINT instead of the default bilinear filtering. This ensures crisp, pixel-perfect rendering without blurring for pixel art fonts and maintains consistency across all font loading methods in the game engine. --- src/text.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/text.c b/src/text.c index 5f2a91a..1417397 100644 --- a/src/text.c +++ b/src/text.c @@ -185,7 +185,9 @@ Load font from file into GPU memory (VRAM) */ int ltextLoadFont( lua_State* L ) { if ( FileExists( luaL_checkstring( L, 1 ) ) ) { - uluaPushFont( L, LoadFont( lua_tostring( L, 1 ) ) ); + Font font = LoadFont( lua_tostring( L, 1 ) ); + SetTextureFilter( font.texture, TEXTURE_FILTER_POINT ); + uluaPushFont( L, font ); return 1; } @@ -207,16 +209,19 @@ int ltextLoadFontEx( lua_State* L ) { int fontSize = luaL_checkinteger( L, 2 ); if ( FileExists( luaL_checkstring( L, 1 ) ) ) { + Font font; if ( lua_istable( L, 3 ) ) { int codepointCount = uluaGetTableLen( L, 3 ); int codepoints[ codepointCount ]; getCodepoints( L, codepoints, 3 ); - uluaPushFont( L, LoadFontEx( lua_tostring( L, 1 ), fontSize, codepoints, codepointCount ) ); - - return 1; + font = LoadFontEx( lua_tostring( L, 1 ), fontSize, codepoints, codepointCount ); + } + else { + font = LoadFontEx( lua_tostring( L, 1 ), fontSize, NULL, 0 ); } - uluaPushFont( L, LoadFontEx( lua_tostring( L, 1 ), fontSize, NULL, 0 ) ); + SetTextureFilter( font.texture, TEXTURE_FILTER_POINT ); + uluaPushFont( L, font ); return 1; } @@ -238,7 +243,9 @@ int ltextLoadFontFromImage( lua_State* L ) { Color key = uluaGetColor( L, 2 ); int firstChar = luaL_checkinteger( L, 3 ); - uluaPushFont( L, LoadFontFromImage( *image, key, firstChar ) ); + Font font = LoadFontFromImage( *image, key, firstChar ); + SetTextureFilter( font.texture, TEXTURE_FILTER_POINT ); + uluaPushFont( L, font ); return 1; } @@ -255,17 +262,20 @@ int ltextLoadFontFromMemory( lua_State* L ) { Buffer* fileData = uluaGetBuffer( L, 2 ); int fontSize = luaL_checkinteger( L, 3 ); + Font font; if ( lua_istable( L, 4 ) ) { int codepointCount = uluaGetTableLen( L, 4 ); int codepoints[ codepointCount ]; getCodepoints( L, codepoints, 4 ); - uluaPushFont( L, LoadFontFromMemory( fileType, fileData->data, fileData->size, fontSize, codepoints, codepointCount ) ); - - return 1; + font = LoadFontFromMemory( fileType, fileData->data, fileData->size, fontSize, codepoints, codepointCount ); } - /* If no codepoints provided. */ - uluaPushFont( L, LoadFontFromMemory( fileType, fileData->data, fileData->size, fontSize, NULL, 0 ) ); + else { + /* If no codepoints provided. */ + font = LoadFontFromMemory( fileType, fileData->data, fileData->size, fontSize, NULL, 0 ); + } + SetTextureFilter( font.texture, TEXTURE_FILTER_POINT ); + uluaPushFont( L, font ); return 1; } -- cgit v1.2.3 From cf51a7a7dc147346e439f62a57becaa11a03740f Mon Sep 17 00:00:00 2001 From: Indrajith K L Date: Wed, 5 Nov 2025 02:22:25 +0530 Subject: Prevent splash screens from being closeable Removed the WindowShouldClose() check from the splash screen loop to prevent users from closing the splash screens prematurely. Previously, users could press ESC or click the X button during splash screens, which would skip to the game but create confusion since it didn't actually exit the application. Now splash screens play through completely for a consistent user experience. Developers can still skip splashes using the --no-logo flag. --- src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index d4d8fd2..72dbdca 100644 --- a/src/main.c +++ b/src/main.c @@ -142,7 +142,7 @@ int main( int argn, const char** argc ) { splashInit(); bool splashDone = false; - while ( !splashDone && !WindowShouldClose() ) { + while ( !splashDone ) { float delta = GetFrameTime(); splashDone = splashUpdate( delta ); splashDraw(); -- cgit v1.2.3 From 7f014770c0ad7f20c70cca1b81b71751d9fbc7c5 Mon Sep 17 00:00:00 2001 From: Indrajith K L Date: Wed, 5 Nov 2025 02:22:38 +0530 Subject: 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. --- template/DIALOG_STATE_PATTERN.md | 316 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 template/DIALOG_STATE_PATTERN.md 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 -- cgit v1.2.3