AeThex-Engine-Core/docs/GAME_DEVELOPMENT.md
MrPiglr faa98bf76f
Enhance docs with WOW features: cover page, diagrams, alerts, and interactive elements
- Add stunning cyberpunk cover page with animated grid background
- Integrate Mermaid diagrams for visual architecture and flow charts
- Add flexible-alerts for tips, warnings, and notes throughout tutorials
- Enhance index.html with 15+ professional plugins (tabs, progress bar, charts)
- Add sequence diagrams for authentication and analytics flows
- Improve readability with visual callouts and interactive elements
- Add graph visualizations for system architecture
- Better UX with keyboard shortcuts, word count, and edit links
2026-02-25 04:04:27 +00:00

17 KiB

Game Development in AeThex

This guide covers the core game development concepts and systems in AeThex Engine.

Tip

New to game development? Start with the Scene System and Node Hierarchy sections. These are the foundation of everything in AeThex.

Scene System

What is a Scene?

A scene is a collection of nodes organized in a tree structure. Scenes are the building blocks of your game - they can represent:

  • Game levels
  • UI screens
  • Characters
  • Items
  • Prefabs/templates
graph TD
    Scene[🎬 Scene File<br/>.tscn] --> Root[Root Node]
    Root --> Child1[Child Node 1]
    Root --> Child2[Child Node 2]
    Root --> Child3[Child Node 3]
    Child2 --> GrandChild1[Grandchild 1]
    Child2 --> GrandChild2[Grandchild 2]
    
    style Scene fill:#00ffff22,stroke:#00ffff
    style Root fill:#ff00ff22,stroke:#ff00ff

Note

Scenes can be nested inside other scenes. This allows you to create reusable components and maintain a clean project structure.

Working with Scenes

Creating Scenes:

# Load a scene
var scene = load("res://levels/main_level.tscn")

# Instance a scene
var instance = scene.instantiate()

# Add to scene tree
add_child(instance)

Switching Scenes:

# Change to a new scene
get_tree().change_scene_to_file("res://levels/next_level.tscn")

# Or using a packed scene
var packed_scene = load("res://levels/next_level.tscn")
get_tree().change_scene_to_packed(packed_scene)

Warning

Always use change_scene_to_file() or change_scene_to_packed() to switch scenes. Manually removing and adding root nodes can cause issues with signal connections and autoloads.

Scene Lifecycle:

  • _enter_tree() - Called when node enters the scene tree
  • _ready() - Called when node and children are ready
  • _exit_tree() - Called when node exits the scene tree

Node Hierarchy

Understanding Nodes

Nodes are the fundamental building blocks in AeThex. Everything in your game is a node or inherits from the Node class.

Node Types

Common Node Classes:

  • Node - Base class for all scene objects
  • Node2D - Base for all 2D game objects (has position, rotation, scale)
  • Node3D - Base for all 3D game objects
  • Control - Base for all UI elements
  • CanvasItem - Base for anything that can be drawn

Working with Node Hierarchy

Adding/Removing Nodes:

# Add a child node
var sprite = Sprite2D.new()
add_child(sprite)

# Remove a child
sprite.queue_free()  # Deferred removal (safe)
sprite.free()        # Immediate removal (use carefully)

# Get parent
var parent = get_parent()

# Reparent a node
sprite.reparent(new_parent)

Finding Nodes:

# Get a child by name
var player = get_node("Player")
# Or using shorthand
var player = $Player

# Get a node by path
var weapon = $Player/Weapon
var health = get_node("Player/Health")

# Find nodes by group
var enemies = get_tree().get_nodes_in_group("enemies")

# Find parent by type
var level = find_parent("Level")

Node Groups:

# Add to a group
add_to_group("enemies")
add_to_group("damageable")

# Check if in group
if is_in_group("enemies"):
    print("This is an enemy!")

# Remove from group
remove_from_group("enemies")

# Call function on all nodes in group
get_tree().call_group("enemies", "take_damage", 10)

Signals and Callbacks

What are Signals?

Signals are AeThex's implementation of the observer pattern. They allow nodes to communicate without tight coupling.

sequenceDiagram
    participant Player
    participant Enemy
    participant UI
    
    Player->>Player: take_damage(10)
    Player->>Player: emit_signal("health_changed", 90)
    Player->>UI: health_changed(90)
    UI->>UI: update_health_bar(90)
    
    Note over Player,UI: Signals enable loose coupling
    
    Player->>Player: health reaches 0
    Player->>Player: emit_signal("player_died")
    Player->>UI: player_died
    Player->>Enemy: player_died
    UI->>UI: show_game_over()
    Enemy->>Enemy: celebrate()
    
    style Player fill:#00ffff22,stroke:#00ffff
    style UI fill:#ff00ff22,stroke:#ff00ff

Tip

Signals are perfect for situations where you want multiple systems to react to an event without creating dependencies between them.

Built-in Signals

Common Node Signals:

  • ready - Emitted when node is ready
  • tree_entered - Node entered the scene tree
  • tree_exited - Node left the scene tree

Input Signals:

  • Area2D.body_entered(body) - Another body entered the area
  • Area2D.body_exited(body) - Another body left the area
  • Button.pressed() - Button was clicked

Note

Most built-in nodes come with useful signals. Check the documentation for each node type to see what signals are available.

Creating Custom Signals

extends Node

# Declare signals
signal health_changed(new_health)
signal player_died
signal item_collected(item_name, quantity)

var health = 100

func take_damage(amount):
    health -= amount
    emit_signal("health_changed", health)
    
    if health <= 0:
        emit_signal("player_died")

func collect_item(item, qty):
    emit_signal("item_collected", item, qty)

Connecting to Signals

Code-based Connections:

# Connect to a signal
player.health_changed.connect(_on_health_changed)

# With custom parameters
player.health_changed.connect(_on_health_changed.bind("extra_param"))

# One-shot connection (auto-disconnects after first emit)
player.health_changed.connect(_on_health_changed, CONNECT_ONE_SHOT)

# Disconnect from signal
player.health_changed.disconnect(_on_health_changed)

func _on_health_changed(new_health):
    print("Health is now: ", new_health)

Lambda Connections:

button.pressed.connect(func(): print("Button clicked!"))

Physics

2D Physics

RigidBody2D - Dynamic physics body affected by forces:

extends RigidBody2D

func _ready():
    # Set physics properties
    mass = 10.0
    gravity_scale = 1.0
    linear_damp = 0.1
    angular_damp = 0.1

func _physics_process(delta):
    # Apply force
    apply_force(Vector2(100, 0))
    
    # Apply impulse (instant)
    apply_impulse(Vector2(0, -500))
    
    # Apply torque (rotation)
    apply_torque(100)

StaticBody2D - Immovable physics body (walls, floors):

extends StaticBody2D

func _ready():
    # Static bodies don't move
    # Used for terrain, walls, platforms
    pass

CharacterBody2D - For player-controlled movement:

extends CharacterBody2D

var speed = 300.0
var jump_velocity = -400.0
var gravity = 980.0

func _physics_process(delta):
    # Apply gravity
    if not is_on_floor():
        velocity.y += gravity * delta
    
    # Handle jump
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_velocity
    
    # Get input direction
    var direction = Input.get_axis("move_left", "move_right")
    velocity.x = direction * speed
    
    # Move with collision detection
    move_and_slide()

3D Physics

RigidBody3D:

extends RigidBody3D

func _ready():
    mass = 10.0
    gravity_scale = 1.0

func apply_jump():
    apply_central_impulse(Vector3.UP * 500)

CharacterBody3D:

extends CharacterBody3D

var speed = 5.0
var jump_strength = 10.0
var gravity = 20.0

func _physics_process(delta):
    if not is_on_floor():
        velocity.y -= gravity * delta
    
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_strength
    
    var input_dir = Input.get_vector("left", "right", "forward", "back")
    var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
    
    velocity.x = direction.x * speed
    velocity.z = direction.z * speed
    
    move_and_slide()

Collision Shapes

Adding Collision Shapes:

# Collision shapes must be children of physics bodies
# Common shapes:
# - CollisionShape2D / CollisionShape3D
# - RectangleShape2D, CircleShape2D, CapsuleShape2D
# - BoxShape3D, SphereShape3D, CapsuleShape3D

# In code:
var collision = CollisionShape2D.new()
var shape = CircleShape2D.new()
shape.radius = 32
collision.shape = shape
add_child(collision)

Physics Layers

Collision Layers & Masks:

# Layer: What layers this body is on (up to 32)
collision_layer = 0b0001  # Layer 1

# Mask: What layers this body can collide with
collision_mask = 0b0010   # Can collide with layer 2

# Common setup:
# Layer 1: Player
# Layer 2: Enemies  
# Layer 3: Environment
# Layer 4: Collectibles

# Player collides with enemies and environment:
collision_layer = 1      # Binary: 0001
collision_mask = 6       # Binary: 0110 (layers 2 and 3)

Checking Collisions:

# CharacterBody2D/3D
func _physics_process(delta):
    move_and_slide()
    
    for i in get_slide_collision_count():
        var collision = get_slide_collision(i)
        print("Collided with: ", collision.get_collider().name)

# Area2D/3D signals
func _ready():
    body_entered.connect(_on_body_entered)

func _on_body_entered(body):
    if body.is_in_group("player"):
        print("Player entered area!")

UI System

Control Nodes

Control is the base class for all UI elements. Common UI nodes:

Buttons:

  • Button - Standard push button
  • CheckButton - Toggle button
  • OptionButton - Dropdown menu

Text:

  • Label - Display text
  • RichTextLabel - Text with formatting (BBCode)
  • LineEdit - Single-line text input
  • TextEdit - Multi-line text input

Containers:

  • VBoxContainer - Vertical layout
  • HBoxContainer - Horizontal layout
  • GridContainer - Grid layout
  • MarginContainer - Adds margins
  • PanelContainer - Background panel

Basic UI Example

extends Control

func _ready():
    # Create a button
    var button = Button.new()
    button.text = "Click Me"
    button.pressed.connect(_on_button_pressed)
    add_child(button)
    
    # Create a label
    var label = Label.new()
    label.text = "Score: 0"
    add_child(label)

func _on_button_pressed():
    print("Button clicked!")

Layouts and Containers

Automatic Layouts:

# VBoxContainer - stacks children vertically
var vbox = VBoxContainer.new()
vbox.add_child(Button.new())
vbox.add_child(Button.new())
vbox.add_child(Button.new())

# HBoxContainer - arranges children horizontally
var hbox = HBoxContainer.new()
hbox.add_theme_constant_override("separation", 10)  # 10px spacing

# GridContainer - grid layout
var grid = GridContainer.new()
grid.columns = 3
for i in 9:
    grid.add_child(Button.new())

Anchors and Margins:

# Anchors determine where control is positioned (0.0 to 1.0)
var panel = Panel.new()

# Center the panel
panel.anchor_left = 0.5
panel.anchor_top = 0.5
panel.anchor_right = 0.5
panel.anchor_bottom = 0.5

# Offset from anchor point
panel.offset_left = -100
panel.offset_top = -50
panel.offset_right = 100
panel.offset_bottom = 50

Themes

Applying Themes:

# Load a theme
var theme = load("res://themes/main_theme.tres")
theme = theme  # Apply to root Control

# Or set individual theme overrides
var button = Button.new()
button.add_theme_color_override("font_color", Color.CYAN)
button.add_theme_font_size_override("font_size", 24)

Responsive Design

Size Flags:

var label = Label.new()

# Expand horizontally
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL

# Shrink to content
label.size_flags_horizontal = Control.SIZE_SHRINK_CENTER

Handling Resize:

extends Control

func _ready():
    get_viewport().size_changed.connect(_on_viewport_resized)

func _on_viewport_resized():
    var viewport_size = get_viewport_rect().size
    print("New size: ", viewport_size)
    # Adjust UI layout

Audio System

AudioStreamPlayer

Three types of audio players:

  • AudioStreamPlayer - 2D positional audio
  • AudioStreamPlayer2D - 2D positional audio
  • AudioStreamPlayer3D - 3D spatial audio

Playing Sounds

Basic Audio:

extends Node

@onready var sfx_player = $AudioStreamPlayer
@onready var music_player = $MusicPlayer

func _ready():
    # Load and play sound
    var sound = load("res://sounds/jump.ogg")
    sfx_player.stream = sound
    sfx_player.play()

func play_sound(sound_path: String):
    sfx_player.stream = load(sound_path)
    sfx_player.play()

Music Management

Background Music:

extends Node

var current_music = null
var music_player = AudioStreamPlayer.new()

func _ready():
    add_child(music_player)
    music_player.finished.connect(_on_music_finished)

func play_music(music_path: String, loop: bool = true):
    var music = load(music_path)
    music_player.stream = music
    music_player.play()
    
    if loop:
        music_player.finished.connect(func(): music_player.play())
    
    current_music = music_path

func stop_music():
    music_player.stop()

func fade_out_music(duration: float = 1.0):
    var tween = create_tween()
    tween.tween_property(music_player, "volume_db", -80, duration)
    tween.tween_callback(stop_music)

func fade_in_music(duration: float = 1.0):
    music_player.volume_db = -80
    music_player.play()
    var tween = create_tween()
    tween.tween_property(music_player, "volume_db", 0, duration)

Sound Effects

Sound Effect Pool:

extends Node

var sfx_players = []
var pool_size = 8

func _ready():
    # Create a pool of audio players
    for i in pool_size:
        var player = AudioStreamPlayer.new()
        add_child(player)
        sfx_players.append(player)

func play_sfx(sound_path: String, volume_db: float = 0.0):
    # Find available player
    for player in sfx_players:
        if not player.playing:
            player.stream = load(sound_path)
            player.volume_db = volume_db
            player.play()
            return
    
    # If all busy, use first one
    sfx_players[0].stream = load(sound_path)
    sfx_players[0].volume_db = volume_db
    sfx_players[0].play()

3D Audio

Spatial Audio:

extends Node3D

@onready var audio_3d = $AudioStreamPlayer3D

func _ready():
    # Configure 3D audio
    audio_3d.max_distance = 50.0      # Max hearing distance
    audio_3d.attenuation_model = AudioStreamPlayer3D.ATTENUATION_INVERSE_DISTANCE
    audio_3d.unit_size = 1.0
    
    # Play sound at this position
    audio_3d.stream = load("res://sounds/explosion.ogg")
    audio_3d.play()

func play_3d_sound_at(sound_path: String, position: Vector3):
    var player = AudioStreamPlayer3D.new()
    add_child(player)
    player.global_position = position
    player.stream = load(sound_path)
    player.play()
    
    # Remove when finished
    await player.finished
    player.queue_free()

Workflow & Tools

Studio IDE Integration

AeThex Studio provides a full IDE experience with:

Code Editor:

  • GDScript syntax highlighting
  • Autocomplete and intellisense
  • Inline documentation
  • Error checking and linting
  • Code folding and navigation

Scene Tree:

  • Visual node hierarchy
  • Drag-and-drop node creation
  • Inspector panel for properties
  • Live scene editing

Asset Browser:

  • File system navigation
  • Asset preview (images, 3D models)
  • Drag-and-drop import
  • Asset metadata

Live Reload:

# Changes are hot-reloaded automatically
# No need to restart the game
# Scripts reload on save

Studio Bridge:

# Check if running in Studio
if AeThexStudio.is_available():
    print("Running in AeThex Studio")
    
    # Send messages to Studio
    AeThexStudio.log_message("Custom debug info")
    
    # Open file in Studio
    AeThexStudio.open_file("res://scripts/player.gd")

Version Control

Git Integration:

# Initialize repository
cd your_project
git init

# AeThex-specific .gitignore
echo ".import/" >> .gitignore
echo "*.import" >> .gitignore
echo ".godot/" >> .gitignore
echo "export_presets.cfg" >> .gitignore

# Commit
git add .
git commit -m "Initial commit"

Collaboration Workflow:

# Create feature branch
git checkout -b feature/player-movement

# Make changes and commit
git add scripts/player.gd
git commit -m "Implement player movement"

# Push branch
git push origin feature/player-movement

# Create pull request on GitHub
# Review and merge

Managing Merge Conflicts:

# Update from main
git checkout main
git pull

# Merge into feature branch
git checkout feature/player-movement
git merge main

# If conflicts occur, resolve them in editor
# Then:
git add .
git commit -m "Resolve merge conflicts"

Branching Strategy:

  • main - Stable, production-ready code
  • develop - Integration branch for features
  • feature/* - Individual features
  • hotfix/* - Emergency fixes
  • release/* - Release preparation

Best Practices:

  1. Commit often with clear messages
  2. Use branches for new features
  3. Keep commits atomic (one logical change)
  4. Pull before pushing
  5. Review code before merging
  6. Use .gitattributes for binary files:
*.tscn merge=binary
*.tres merge=binary
*.asset merge=binary

Next Steps