Files
Code_of_Conquest/godot_client/CLAUDE.md
2025-11-24 23:10:55 -06:00

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:

Project-Wide Documentation:


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:

  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):

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:

  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 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