# CLAUDE.md - Godot Client ## Service Overview **Godot Client** for Code of Conquest - Native cross-platform game client using Godot 4.5 + GDScript. **Tech Stack:** Godot 4.5 + GDScript + HTTPRequest **Platforms:** Desktop (Windows, macOS, Linux), Mobile (Android, iOS), Web (HTML5) **Location:** `/godot_client` --- ## Architecture Role This Godot client is a **thin UI layer** that makes HTTP requests to the API backend: - ✅ Render game UI (scenes, components) - ✅ Handle user input - ✅ Make HTTP requests to API backend - ✅ Display API responses - ✅ Client-side animations and effects - ✅ Local state management (UI state only) **What this service does NOT do:** - ❌ No business logic (all in `/api`) - ❌ No game mechanics calculations (use API) - ❌ No direct database access (use API) - ❌ No AI calls (use API) - ❌ No authoritative game state (API is source of truth) **Communication:** ``` Godot Client → HTTP Request → API Backend ↑ ↓ ←───── HTTP Response ────────┘ ``` --- ## Documentation Index **Godot Client Documentation:** - **[README.md](README.md)** - Setup and usage guide - **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** - Client architecture overview - **[docs/GETTING_STARTED.md](docs/GETTING_STARTED.md)** - Quickstart guide - **[docs/EXPORT.md](docs/EXPORT.md)** - Platform export instructions - **[docs/THEME_SETUP.md](docs/THEME_SETUP.md)** - UI theming guide - **[docs/MULTIPLAYER.md](docs/MULTIPLAYER.md)** - Multiplayer client implementation - **[docs/README.md](docs/README.md)** - Scene documentation workflow **Project-Wide Documentation:** - **[../docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md)** - System architecture overview - **[../api/docs/API_REFERENCE.md](../api/docs/API_REFERENCE.md)** - API endpoints to call - **[../docs/WEB_VS_CLIENT_SYSTEMS.md](../docs/WEB_VS_CLIENT_SYSTEMS.md)** - Feature distribution between frontends --- ## Development Guidelines ### Project Structure ``` godot_client/ ├── project.godot # Godot project configuration ├── scenes/ # Godot scene files (.tscn) │ ├── main.tscn # Entry point │ ├── auth/ # Authentication scenes │ ├── character/ # Character scenes │ ├── combat/ # Combat scenes │ └── components/ # Reusable UI components ├── scripts/ # GDScript code │ ├── services/ # Singleton autoloads │ ├── models/ # Data models │ ├── components/ # Component scripts │ └── utils/ # Helper utilities └── assets/ # Game assets ├── fonts/ # Fonts ├── themes/ # UI themes └── ui/ # UI assets ``` ### GDScript Coding Standards **Style & Structure** - Follow GDScript style guide (snake_case for functions/variables) - Use PascalCase for class names - Always include doc comments for classes and functions - Prefer explicit types over dynamic typing - Group related code with region comments - **Do NOT apply styles/themes in code** - All visual styling (StyleBoxFlat, colors, fonts, margins) should be done in the Godot editor, not programmatically in GDScript **Example:** ```gdscript class_name CharacterCard extends Control ## A UI component that displays character information. ## ## This card shows the character's name, level, class, and stats. ## It makes an HTTP request to fetch character data from the API. #region Exports @export var character_id: String = "" @export var auto_load: bool = true #endregion #region Private Variables var _character_data: Dictionary = {} var _is_loading: bool = false #endregion #region Lifecycle func _ready() -> void: """Initialize the character card.""" if auto_load and character_id != "": load_character() func _process(_delta: float) -> void: """Update card UI each frame.""" pass #endregion #region Public Methods func load_character() -> void: """Load character data from API.""" if _is_loading: return _is_loading = true var url = Settings.api_base_url + "/api/v1/characters/" + character_id HTTPClient.get_request(url, _on_character_loaded, _on_character_error) func refresh() -> void: """Refresh character data.""" load_character() #endregion #region Private Methods func _update_ui() -> void: """Update UI elements with character data.""" $Name.text = _character_data.get("name", "Unknown") $Level.text = "Level " + str(_character_data.get("level", 1)) func _show_error(message: String) -> void: """Display error message to user.""" $ErrorLabel.text = message $ErrorLabel.visible = true #endregion #region Signal Handlers func _on_character_loaded(response: Dictionary) -> void: """Handle successful character data load.""" _is_loading = false if response.has("result"): _character_data = response.result _update_ui() func _on_character_error(error: String) -> void: """Handle character data load error.""" _is_loading = false _show_error("Failed to load character: " + error) func _on_button_pressed() -> void: """Handle button click.""" refresh() #endregion ``` **Code Organization:** 1. Class documentation 2. Exports (inspector variables) 3. Signals 4. Public constants 5. Private variables 6. Lifecycle methods (_ready, _process, etc.) 7. Public methods 8. Private methods 9. Signal handlers ### Service Singletons (Autoloads) **Settings Service (`scripts/services/settings.gd`):** ```gdscript extends Node ## Global settings and configuration management. var api_base_url: String = "http://localhost:5000" var environment: String = "development" var version: String = "0.1.0" func _ready() -> void: load_settings() func load_settings() -> void: """Load settings from file or environment.""" # Load from settings file or use defaults if FileAccess.file_exists("user://settings.json"): var file = FileAccess.open("user://settings.json", FileAccess.READ) var json = JSON.parse_string(file.get_as_text()) file.close() if json: api_base_url = json.get("api_base_url", api_base_url) environment = json.get("environment", environment) func save_settings() -> void: """Save settings to file.""" var data = { "api_base_url": api_base_url, "environment": environment } var file = FileAccess.open("user://settings.json", FileAccess.WRITE) file.store_string(JSON.stringify(data, "\t")) file.close() ``` **HTTP Client Service (`scripts/services/http_client.gd`):** ```gdscript extends Node ## Centralized HTTP client for API communication. signal request_completed(response: Dictionary) signal request_failed(error: String) var _http_requests: Dictionary = {} func get_request(url: String, on_success: Callable, on_error: Callable = Callable()) -> void: """Make a GET request to the API.""" _make_request(url, HTTPClient.METHOD_GET, {}, on_success, on_error) func post_request(url: String, data: Dictionary, on_success: Callable, on_error: Callable = Callable()) -> void: """Make a POST request to the API.""" _make_request(url, HTTPClient.METHOD_POST, data, on_success, on_error) func _make_request(url: String, method: int, data: Dictionary, on_success: Callable, on_error: Callable) -> void: """Internal method to make HTTP requests.""" var http_request = HTTPRequest.new() add_child(http_request) # Store callbacks var request_id = str(http_request.get_instance_id()) _http_requests[request_id] = { "on_success": on_success, "on_error": on_error, "node": http_request } # Connect signals http_request.request_completed.connect(_on_request_completed.bind(request_id)) # Make request var headers = ["Content-Type: application/json"] var body = JSON.stringify(data) if method != HTTPClient.METHOD_GET else "" var error = http_request.request(url, headers, method, body) if error != OK: _handle_error(request_id, "Request failed: " + str(error)) func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray, request_id: String) -> void: """Handle HTTP request completion.""" var request_data = _http_requests.get(request_id) if not request_data: return # Parse response var json = JSON.parse_string(body.get_string_from_utf8()) # Check for errors if result != HTTPRequest.RESULT_SUCCESS or response_code >= 400: var error_message = json.get("error", {}).get("message", "Request failed") if json else "Request failed" _handle_error(request_id, error_message) return # Success if request_data.on_success.is_valid(): request_data.on_success.call(json) # Cleanup _cleanup_request(request_id) func _handle_error(request_id: String, error: String) -> void: """Handle request error.""" var request_data = _http_requests.get(request_id) if request_data and request_data.on_error.is_valid(): request_data.on_error.call(error) _cleanup_request(request_id) func _cleanup_request(request_id: String) -> void: """Clean up completed request.""" var request_data = _http_requests.get(request_id) if request_data: request_data.node.queue_free() _http_requests.erase(request_id) ``` ### Scene Organization **Scene Structure:** ``` Main Scene (main.tscn) ├── UI Layer (CanvasLayer) │ ├── MainMenu │ ├── LoadingScreen │ └── ErrorPopup ├── Game Layer (Node2D/Node3D) │ ├── Character │ ├── World │ └── Combat └── Services (Node) ├── AudioManager └── InputManager ``` **Scene Best Practices:** - Keep scenes focused (single responsibility) - Use scenes as components (reusable UI elements) - Attach scripts to scene root nodes - Use groups for easy node access (`node.add_to_group("enemies")`) - Prefer signals over direct node references ### UI Theming **Theme Structure:** ```gdscript # Create programmatically or use theme resource var theme = Theme.new() # Colors theme.set_color("font_color", "Label", Color.WHITE) theme.set_color("font_color", "Button", Color("#e5e7eb")) # Fonts var title_font = load("res://assets/fonts/Cinzel-Bold.ttf") theme.set_font("font", "TitleLabel", title_font) # Stylebox var button_style = StyleBoxFlat.new() button_style.bg_color = Color("#8b5cf6") button_style.corner_radius_top_left = 8 button_style.corner_radius_top_right = 8 button_style.corner_radius_bottom_left = 8 button_style.corner_radius_bottom_right = 8 theme.set_stylebox("normal", "Button", button_style) ``` ### API Communication Patterns **Simple GET Request:** ```gdscript func load_characters() -> void: var url = Settings.api_base_url + "/api/v1/characters" HTTPClient.get_request(url, _on_characters_loaded, _on_characters_error) func _on_characters_loaded(response: Dictionary) -> void: var characters = response.get("result", []) # Update UI with characters func _on_characters_error(error: String) -> void: print("Error loading characters: ", error) ``` **POST Request with Data:** ```gdscript func create_character(name: String, class_id: String, origin_id: String) -> void: var url = Settings.api_base_url + "/api/v1/characters" var data = { "name": name, "class_id": class_id, "origin_id": origin_id } HTTPClient.post_request(url, data, _on_character_created, _on_create_error) func _on_character_created(response: Dictionary) -> void: var character = response.get("result") print("Character created: ", character.name) # Navigate to character screen func _on_create_error(error: String) -> void: show_error_popup("Failed to create character: " + error) ``` ### State Management **StateManager Service (`scripts/services/state_manager.gd`):** ```gdscript extends Node ## Global game state management. signal state_changed(key: String, value: Variant) var _state: Dictionary = {} func set_value(key: String, value: Variant) -> void: """Set a state value.""" _state[key] = value state_changed.emit(key, value) func get_value(key: String, default: Variant = null) -> Variant: """Get a state value.""" return _state.get(key, default) func has_value(key: String) -> bool: """Check if state has a key.""" return _state.has(key) func clear() -> void: """Clear all state.""" _state.clear() ``` **Usage:** ```gdscript # Set current character StateManager.set_value("current_character_id", "char_123") # Get current character var char_id = StateManager.get_value("current_character_id") # Listen for changes StateManager.state_changed.connect(_on_state_changed) func _on_state_changed(key: String, value: Variant) -> void: if key == "current_character_id": load_character(value) ``` --- ## Godot Workflow ### Project Setup (Autoloads) In Project Settings → Autoload, add these singletons: 1. **Settings** - `scripts/services/settings.gd` 2. **HTTPClient** - `scripts/services/http_client.gd` 3. **StateManager** - `scripts/services/state_manager.gd` ### Creating a New Scene 1. **Plan the scene** - Sketch layout, define purpose 2. **Create scene file** - `scenes/feature/scene_name.tscn` 3. **Add nodes** - Build UI hierarchy 4. **Attach script** - `scripts/feature/scene_name.gd` 5. **Connect signals** - Wire up interactions 6. **Test in editor** - F6 to test scene 7. **Integrate** - Add to main scene or navigation ### Testing **In Editor:** - F5 - Run project - F6 - Run current scene - Use Debugger panel for breakpoints - Monitor output for print statements **Export Testing:** - Test on target platforms (Desktop, Mobile, Web) - Check API connectivity - Verify UI scaling --- ## Export Configuration ### Desktop Export **Windows:** ``` Platform: Windows Desktop Architecture: x86_64 Export Template: Release ``` **macOS:** ``` Platform: macOS Architecture: Universal (arm64 + x86_64) Export Template: Release Code Sign: Required for distribution ``` **Linux:** ``` Platform: Linux/X11 Architecture: x86_64 Export Template: Release ``` ### Mobile Export **Android:** ``` Platform: Android Architecture: arm64-v8a Min SDK: 21 Target SDK: 33 ``` **iOS:** ``` Platform: iOS Architecture: arm64 Deployment Target: iOS 12.0 Bundle ID: com.codeofconquest.game ``` ### Web Export ``` Platform: Web Export Type: HTML5 Progressive Web App: Yes ``` See [EXPORT.md](EXPORT.md) for detailed instructions. --- ## Git Standards **Commit Messages:** - Use conventional commit format: `feat:`, `fix:`, `style:`, `asset:`, etc. - Examples: - `feat(godot): add character list scene` - `fix(http): handle API timeout errors` - `style(ui): update character card layout` - `asset(fonts): add Cinzel font family` **Branch Strategy:** - Branch off `dev` for features - Merge back to `dev` for testing - Promote to `master` for production --- ## Notes for Claude Code When working on the Godot client: 1. **Thin client only** - No business logic, just UI and user interaction 2. **Always call API** - Use HTTPClient service for all data operations 3. **Handle errors gracefully** - Show user-friendly error popups 4. **Keep scenes modular** - Create reusable component scenes 5. **Use signals** - Prefer signals over direct node references 6. **Test on target platforms** - Verify exports work correctly 7. **Performance matters** - Optimize for mobile (low draw calls, minimize allocations) 8. **Follow Godot conventions** - Use GDScript idioms, not Python/JavaScript patterns **Remember:** - This is a thin client - all logic lives in the API backend - The API serves multiple frontends (this Godot client and web UI) - Godot handles rendering and input - API handles game state - Keep it responsive - show loading indicators during API calls - Mobile-first design - optimize for touch and smaller screens