559 lines
16 KiB
Markdown
559 lines
16 KiB
Markdown
# 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
|