first commit
This commit is contained in:
558
godot_client/CLAUDE.md
Normal file
558
godot_client/CLAUDE.md
Normal file
@@ -0,0 +1,558 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user