16 KiB
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 - Setup and usage guide
- docs/ARCHITECTURE.md - Client architecture overview
- docs/GETTING_STARTED.md - Quickstart guide
- docs/EXPORT.md - Platform export instructions
- docs/THEME_SETUP.md - UI theming guide
- docs/MULTIPLAYER.md - Multiplayer client implementation
- docs/README.md - Scene documentation workflow
Project-Wide Documentation:
- ../docs/ARCHITECTURE.md - System architecture overview
- ../api/docs/API_REFERENCE.md - API endpoints to call
- ../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:
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:
- Class documentation
- Exports (inspector variables)
- Signals
- Public constants
- Private variables
- Lifecycle methods (_ready, _process, etc.)
- Public methods
- Private methods
- Signal handlers
Service Singletons (Autoloads)
Settings Service (scripts/services/settings.gd):
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):
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:
# 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:
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:
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):
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:
# 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:
- Settings -
scripts/services/settings.gd - HTTPClient -
scripts/services/http_client.gd - StateManager -
scripts/services/state_manager.gd
Creating a New Scene
- Plan the scene - Sketch layout, define purpose
- Create scene file -
scenes/feature/scene_name.tscn - Add nodes - Build UI hierarchy
- Attach script -
scripts/feature/scene_name.gd - Connect signals - Wire up interactions
- Test in editor - F6 to test scene
- 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 for detailed instructions.
Git Standards
Commit Messages:
- Use conventional commit format:
feat:,fix:,style:,asset:, etc. - Examples:
feat(godot): add character list scenefix(http): handle API timeout errorsstyle(ui): update character card layoutasset(fonts): add Cinzel font family
Branch Strategy:
- Branch off
devfor features - Merge back to
devfor testing - Promote to
masterfor production
Notes for Claude Code
When working on the Godot client:
- Thin client only - No business logic, just UI and user interaction
- Always call API - Use HTTPClient service for all data operations
- Handle errors gracefully - Show user-friendly error popups
- Keep scenes modular - Create reusable component scenes
- Use signals - Prefer signals over direct node references
- Test on target platforms - Verify exports work correctly
- Performance matters - Optimize for mobile (low draw calls, minimize allocations)
- 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