724 lines
16 KiB
Markdown
724 lines
16 KiB
Markdown
# 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:**
|
|
```gdscript
|
|
# 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:**
|
|
```gdscript
|
|
# 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:**
|
|
```gdscript
|
|
# 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:**
|
|
```gdscript
|
|
# 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:**
|
|
```gdscript
|
|
# 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
|
|
|
|
```gdscript
|
|
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:**
|
|
```gdscript
|
|
# 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:**
|
|
```gdscript
|
|
button.pressed.connect(func(): print("Button clicked!"))
|
|
```
|
|
|
|
---
|
|
|
|
## Physics
|
|
|
|
### 2D Physics
|
|
|
|
**RigidBody2D** - Dynamic physics body affected by forces:
|
|
```gdscript
|
|
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):
|
|
```gdscript
|
|
extends StaticBody2D
|
|
|
|
func _ready():
|
|
# Static bodies don't move
|
|
# Used for terrain, walls, platforms
|
|
pass
|
|
```
|
|
|
|
**CharacterBody2D** - For player-controlled movement:
|
|
```gdscript
|
|
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:**
|
|
```gdscript
|
|
extends RigidBody3D
|
|
|
|
func _ready():
|
|
mass = 10.0
|
|
gravity_scale = 1.0
|
|
|
|
func apply_jump():
|
|
apply_central_impulse(Vector3.UP * 500)
|
|
```
|
|
|
|
**CharacterBody3D:**
|
|
```gdscript
|
|
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:**
|
|
```gdscript
|
|
# 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:**
|
|
```gdscript
|
|
# 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:**
|
|
```gdscript
|
|
# 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
|
|
|
|
```gdscript
|
|
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:**
|
|
```gdscript
|
|
# 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:**
|
|
```gdscript
|
|
# 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:**
|
|
```gdscript
|
|
# 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:**
|
|
```gdscript
|
|
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:**
|
|
```gdscript
|
|
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:**
|
|
```gdscript
|
|
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:**
|
|
```gdscript
|
|
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:**
|
|
```gdscript
|
|
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:**
|
|
```gdscript
|
|
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:**
|
|
```gdscript
|
|
# Changes are hot-reloaded automatically
|
|
# No need to restart the game
|
|
# Scripts reload on save
|
|
```
|
|
|
|
**Studio Bridge:**
|
|
```gdscript
|
|
# 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:**
|
|
```bash
|
|
# 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:**
|
|
```bash
|
|
# 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:**
|
|
```bash
|
|
# 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
|
|
|
|
- **API Reference:** See [API_REFERENCE.md](API_REFERENCE.md) for AeThex cloud services
|
|
- **First Game Tutorial:** Build a complete game in [FIRST_GAME_TUTORIAL.md](tutorials/FIRST_GAME_TUTORIAL.md)
|
|
- **Studio Integration:** Learn more at [STUDIO_INTEGRATION.md](STUDIO_INTEGRATION.md)
|
|
- **Architecture:** Understand the engine at [ARCHITECTURE_OVERVIEW.md](ARCHITECTURE_OVERVIEW.md)
|