AeThex-Engine-Core/docs/GAME_DEVELOPMENT.md

16 KiB

Game Development in AeThex

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

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

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)

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.

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

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