new file: docs/EXPORTING_GAMES.md

This commit is contained in:
Anderson 2026-02-25 02:05:52 +00:00 committed by GitHub
parent 4f4cc10a76
commit d13c2cdfdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 5190 additions and 58 deletions

View file

@ -714,5 +714,5 @@ func _physics_process(delta):
- [Getting Started Guide](../GETTING_STARTED.md) - [Getting Started Guide](../GETTING_STARTED.md)
- [Cloud Services Architecture](CLOUD_SERVICES_ARCHITECTURE.md) - [Cloud Services Architecture](CLOUD_SERVICES_ARCHITECTURE.md)
- [Multiplayer Tutorial](tutorials/MULTIPLAYER_TUTORIAL.md) - [Multiplayer Pong Tutorial](tutorials/FIRST_GAME_TUTORIAL.md)
- [Studio Bridge Guide](STUDIO_BRIDGE_GUIDE.md) - [Studio Bridge Guide](STUDIO_BRIDGE_GUIDE.md)

896
docs/EXPORTING_GAMES.md Normal file
View file

@ -0,0 +1,896 @@
# Exporting Games with AeThex
This guide covers exporting your AeThex game to all supported platforms: Windows, Linux, macOS, Web (HTML5), and Android.
---
## Overview
AeThex supports exporting to:
- **Desktop:** Windows, Linux, macOS
- **Web:** HTML5 (WebAssembly + WebGL)
- **Mobile:** Android (iOS planned)
Each platform has specific requirements and optimization considerations.
---
## Before Exporting
### 1. Test Your Game
Always test thoroughly before exporting:
```bash
# Run in editor
aethex --editor --path ./my-project
# Test in release mode
aethex --path ./my-project
```
### 2. Configure Project Settings
**Project → Project Settings → Application:**
```
Name: Your Game Name
Description: Game description
Icon: res://icon.png
Version: 1.0.0
```
**Display Settings:**
```
Window Width: 1920
Window Height: 1080
Fullscreen: false
Resizable: true
```
### 3. Export Templates
Download export templates for your target platform:
```bash
# Via Studio IDE: Editor → Export Templates → Download
# Or manually:
wget https://aethex.io/downloads/export-templates-[version].zip
unzip export-templates-[version].zip -d ~/.local/share/aethex/templates/
```
---
## Windows Export
### Requirements
- **Build Machine:** Windows, Linux, or macOS
- **Target:** Windows 7+ (64-bit)
- **Export Template:** Windows Desktop template
### Export Steps
**1. Add Export Preset:**
```
Project → Export
Click "Add..." → Windows Desktop
```
**2. Configure Options:**
```
Export Path: builds/windows/YourGame.exe
Architecture: x86_64
Runnable: ✓ (for executable)
Embed PCK: ✓ (single file)
Code Signing:
- Identity: (optional) Your signing certificate
- Password: Your certificate password
```
**3. Export:**
```
Click "Export PCK/ZIP" for package only
Click "Export Project" for executable
```
### Windows-Specific Options
**Application:**
```
Company Name: Your Company
Product Name: Your Game
File Version: 1.0.0.0
Product Version: 1.0.0.0
File Description: Your game description
Copyright: © 2024 Your Company
Trademarks: Your trademarks
```
**Executable:**
```
Console Wrapper: ✗ (disable for release)
Icon: res://icon.ico (Windows icon format)
```
**Code Signing:**
```gdscript
# Sign your executable (optional but recommended)
signtool sign /f certificate.pfx /p password YourGame.exe
```
### Windows Distribution
**Standalone:**
- Single `.exe` file
- Portable, no installation required
- Share via download link or USB
**Installer (Optional):**
```bash
# Use NSIS, Inno Setup, or WiX
# Example with NSIS:
makensis installer.nsi
```
**Steam:**
```bash
# Use Steamworks SDK
# Follow Steam's integration guide
# Configure depot builds
```
---
## Linux Export
### Requirements
- **Target:** Ubuntu 20.04+, most modern distros
- **Architecture:** x86_64, ARM64
- **Export Template:** Linux/X11 template
### Export Steps
**1. Add Export Preset:**
```
Project → Export
Click "Add..." → Linux/X11
```
**2. Configure Options:**
```
Export Path: builds/linux/YourGame.x86_64
Architecture: x86_64 (or arm64)
Runnable: ✓
Embed PCK: ✓
```
**3. Export:**
```
Click "Export Project"
```
### Linux-Specific Options
**Binary:**
```
Strip Debug Symbols: ✓ (reduces size)
Make Executable: ✓
```
**Dependencies:**
```bash
# Your game requires these libraries on user's system:
# - libGL.so.1
# - libX11.so.6
# - libXcursor.so.1
# - libXrandr.so.2
# - libXi.so.6
# Most modern distros include these
```
### Linux Distribution
**AppImage (Recommended):**
```bash
# Create portable AppImage
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool-x86_64.AppImage
# Structure:
# YourGame.AppDir/
# ├── AppRun (symlink to your binary)
# ├── YourGame.x86_64
# ├── YourGame.desktop
# └── icon.png
./appimagetool-x86_64.AppImage YourGame.AppDir
```
**Flatpak:**
```bash
# Create Flatpak manifest
# Follow Flathub guidelines
flatpak-builder build-dir com.yourcompany.yourgame.yml
```
**Snap:**
```bash
# Create snapcraft.yaml
snapcraft
```
**.tar.gz Archive:**
```bash
# Simple distribution
tar -czf YourGame-linux.tar.gz YourGame.x86_64
```
---
## macOS Export
### Requirements
- **Build Machine:** macOS (for signing/notarization)
- **Target:** macOS 10.13+
- **Export Template:** macOS template
- **Apple Developer Account:** For signing (required for distribution)
### Export Steps
**1. Add Export Preset:**
```
Project → Export
Click "Add..." → macOS
```
**2. Configure Options:**
```
Export Path: builds/macos/YourGame.zip
Architecture: universal (Intel + Apple Silicon)
or separate: x86_64, arm64
```
**3. Code Signing:**
```
Codesign:
Identity: "Developer ID Application: Your Name (TEAM_ID)"
Certificate Path: /path/to/certificate.p12
Certificate Password: your_password
Entitlements: res://entitlements.plist (optional)
```
**4. Export:**
```
Click "Export Project"
```
### macOS-Specific Options
**Application Bundle:**
```
Bundle ID: com.yourcompany.yourgame
Display Name: Your Game
Version: 1.0.0
Copyright: © 2024 Your Company
Icon: res://icon.icns (macOS icon format)
```
**Hardened Runtime:**
```
Enable Hardened Runtime: ✓
Disable Library Validation: ✗
Allow JIT Code: ✗ (unless needed)
Allow Unsigned Executable Memory: ✗
Allow DYLD Environment Variables: ✗
Disable Executable Memory Protection: ✗
```
### Notarization (Required for macOS 10.15+)
```bash
# Sign the app
codesign --deep --force --verify --verbose \
--sign "Developer ID Application: Your Name (TEAM_ID)" \
--options runtime \
YourGame.app
# Create ZIP for notarization
ditto -c -k --keepParent YourGame.app YourGame.zip
# Submit for notarization
xcrun notarytool submit YourGame.zip \
--apple-id your@email.com \
--team-id TEAM_ID \
--password app-specific-password \
--wait
# Staple notarization ticket
xcrun stapler staple YourGame.app
# Verify
spctl -a -vv YourGame.app
```
### macOS Distribution
**Direct Download:**
- Distribute `.dmg` or `.zip`
- Users drag to Applications folder
**Mac App Store:**
- Use App Store Connect
- Follow Apple's submission guidelines
- Use "Mac App Store" export preset
**Create DMG:**
```bash
# Create disk image
hdiutil create -volname "Your Game" -srcfolder YourGame.app -ov -format UDZO YourGame.dmg
```
---
## Web (HTML5) Export
### Requirements
- **Target:** Modern browsers (Chrome, Firefox, Safari, Edge)
- **Export Template:** Web template
- **Web Server:** For hosting
### Export Steps
**1. Add Export Preset:**
```
Project → Export
Click "Add..." → Web (HTML5)
```
**2. Configure Options:**
```
Export Path: builds/web/index.html
Head Include: res://web/head.html (custom HTML)
```
**3. Export:**
```
Click "Export Project"
```
### Web-Specific Options
**Performance:**
```
WebGL Version: 2.0 (WebGL 2)
Enable Run: ✓ (for testing)
Full Window Size: ✓
Memory Settings:
Initial Memory: 33554432 (32MB)
Max Memory: 2147483648 (2GB)
Stack Size: 5242880 (5MB)
```
**Progressive Web App:**
```
PWA: ✓
Icon 144x144: res://icons/icon-144.png
Icon 180x180: res://icons/icon-180.png
Icon 512x512: res://icons/icon-512.png
Background Color: #000000
Orientation: landscape
```
**Compression:**
```
Export Type: Regular
Gzip Compression: ✓
```
### Testing Locally
```bash
# Serve with Python
cd builds/web
python3 -m http.server 8000
# Or with Node
npx http-server builds/web -p 8000
```
Open `http://localhost:8000`
### Web Deployment
**Static Hosting (Recommended):**
```bash
# Netlify
netlify deploy --dir=builds/web --prod
# Vercel
vercel --prod builds/web
# GitHub Pages
git subtree push --prefix builds/web origin gh-pages
# Firebase Hosting
firebase deploy --only hosting
```
**itch.io:**
```
1. Zip the web folder
2. Upload to itch.io
3. Set "This file will be played in the browser"
```
**Your Own Server:**
```nginx
# Nginx configuration
server {
listen 80;
server_name yourgame.com;
root /var/www/yourgame;
location / {
try_files $uri $uri/ /index.html;
}
# Enable CORS if needed
location ~* \.(wasm|pck)$ {
add_header Access-Control-Allow-Origin *;
}
# Gzip compression
gzip on;
gzip_types application/wasm application/octet-stream;
}
```
### Web Best Practices
**Loading Screen:**
```html
<!-- builds/web/index.html -->
<style>
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: sans-serif;
}
</style>
<div class="loading">Loading Your Game...</div>
```
**Optimize Size:**
```gdscript
# Disable unused features in export preset
# Compress textures
# Use streaming for audio
# Lazy load assets
```
---
## Android Export
### Requirements
- **Android SDK:** Android 6.0+ (API 23+)
- **Android NDK:** r23b+
- **JDK:** OpenJDK 11+
- **Export Template:** Android template
- **Keystore:** For signing (release builds)
### Setup Android Development
**1. Install Android SDK:**
```bash
# Download Android Studio or command-line tools
# Set ANDROID_HOME environment variable
export ANDROID_HOME=$HOME/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
```
**2. Install Required Components:**
```bash
sdkmanager "platform-tools" "platforms;android-33" "build-tools;33.0.0" "ndk;23.2.8568313"
```
**3. Configure AeThex:**
```
Editor → Editor Settings → Export → Android:
Android SDK Path: /path/to/Android/Sdk
Debug Keystore: ~/.android/debug.keystore
```
### Export Steps
**1. Add Export Preset:**
```
Project → Export
Click "Add..." → Android
```
**2. Configure Options:**
```
Export Path: builds/android/YourGame.apk (or .aab)
Package:
Unique Name: com.yourcompany.yourgame
Name: Your Game
Signed: ✓ (for release)
Version:
Code: 1 (increment for each release)
Name: 1.0.0
```
**3. Code Signing:**
```
Keystore:
Debug: ~/.android/debug.keystore
Release: /path/to/release.keystore
User: your_key_alias
Password: your_keystore_password
```
**4. Export:**
```
Click "Export Project"
```
### Android-Specific Options
**Graphics:**
```
OpenGL ES: 3.0
ASTC Compression: ✓ (for textures)
```
**Permissions:**
```
Internet: ✓ (for cloud features)
Access Network State: ✓
Access WiFi State: ✓
Vibrate: ✓ (optional)
```
**Screen:**
```
Orientation: landscape (or portrait)
Support Small Screen: ✗
Support Normal Screen: ✓
Support Large Screen: ✓
Support XLarge Screen: ✓
```
**Architecture:**
```
Export ABIs:
✓ armeabi-v7a (32-bit ARM)
✓ arm64-v8a (64-bit ARM) - Required by Play Store
✗ x86 (optional for emulators)
✗ x86_64 (optional for emulators)
```
### Creating Debug Build
```bash
# Export unsigned debug APK
# Install via ADB
adb install builds/android/YourGame-debug.apk
# Run and view logs
adb logcat | grep YourGame
```
### Creating Release Build
**1. Create Release Keystore:**
```bash
keytool -genkey -v -keystore release.keystore -alias my_game_key \
-keyalg RSA -keysize 2048 -validity 10000
# Keep this keystore safe! You need it for all future updates
```
**2. Export Release Build:**
```
Select "Android" preset
Enable "Signed"
Provide keystore details
Click "Export Project"
```
**3. Generate AAB (for Play Store):**
```
Export Path: builds/android/YourGame.aab
Export Type: AAB (Android App Bundle)
```
### Testing on Device
```bash
# Enable USB debugging on Android device
# Connect via USB
# List devices
adb devices
# Install
adb install YourGame.apk
# Uninstall
adb uninstall com.yourcompany.yourgame
# View logs
adb logcat -c # Clear logs
adb logcat | grep YourGame
```
### Android Distribution
**Google Play Store:**
```
1. Create Play Console account
2. Create app listing
3. Upload AAB (not APK)
4. Configure store presence
5. Set pricing
6. Submit for review
```
**Other Stores:**
- Amazon Appstore (APK)
- Samsung Galaxy Store (APK)
- itch.io (APK)
- Direct download (APK)
### Android Optimization
**Reduce APK Size:**
```
# In export preset:
- Enable compression
- Disable unused ABIs
- Compress textures (ASTC)
- Remove unused resources
- Use ProGuard/R8
```
**Performance:**
```gdscript
# Target 60 FPS on mid-range devices
# Test on:
# - Low-end device (2GB RAM)
# - Mid-range device (4GB RAM)
# - High-end device (8GB+ RAM)
# Android-specific optimizations:
func _ready():
if OS.get_name() == "Android":
# Reduce particle count
# Lower shadow quality
# Disable post-processing effects
pass
```
---
## Multi-Platform Tips
### Asset Optimization
**Textures:**
```
Desktop: PNG, JPEG, or uncompressed
Web: Compress, reduce resolution
Mobile: ASTC compression, aggressive optimization
```
**Audio:**
```
Music: OGG Vorbis, 128kbps
SFX: OGG Vorbis, 64kbps
Mobile: Lower bitrates
```
**3D Models:**
```
Desktop: Full detail
Web: Reduce poly count 30%
Mobile: Reduce poly count 50%
```
### Platform Detection
```gdscript
extends Node
func _ready():
match OS.get_name():
"Windows":
setup_windows()
"Linux", "FreeBSD", "NetBSD", "OpenBSD", "BSD":
setup_linux()
"macOS":
setup_macos()
"Web":
setup_web()
"Android":
setup_android()
"iOS":
setup_ios()
func setup_windows():
# Windows-specific setup
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)
func setup_mobile():
# Touch controls, battery optimization
if OS.get_name() in ["Android", "iOS"]:
# Mobile optimizations
pass
```
### Feature Flags
```gdscript
const FEATURES = {
"cloud_saves": true,
"multiplayer": true,
"analytics": true,
"haptics": OS.get_name() in ["Android", "iOS"],
"keyboard": OS.get_name() not in ["Android", "iOS", "Web"],
}
func _ready():
if FEATURES.cloud_saves:
await AeThexCloud.connect_to_cloud()
```
---
## Troubleshooting
### Common Issues
**Export template missing:**
```
Solution: Download export templates from
Editor → Export Templates → Download
```
**Code signing failed (macOS):**
```bash
# Verify certificate
security find-identity -v -p codesigning
# Check entitlements
codesign -d --entitlements :- YourGame.app
```
**Web build doesn't load:**
```
Check console for errors
Must be served from HTTP server (not file://)
Check SharedArrayBuffer requirements
Enable CORS headers if needed
```
**Android build fails:**
```bash
# Check SDK paths
echo $ANDROID_HOME
# Update build tools
sdkmanager --update
# Clean build
./gradlew clean
```
### Performance Testing
```gdscript
# Add FPS counter
func _process(delta):
if OS.is_debug_build():
$FPSLabel.text = "FPS: " + str(Engine.get_frames_per_second())
```
### Build Size Optimization
```
1. Disable unused modules in export preset
2. Compress textures appropriately
3. Use streaming for large assets
4. Remove debug symbols (release mode)
5. Use platform-specific compression
```
---
## Best Practices
### Before Every Release
✓ Test on all target platforms
✓ Profile performance
✓ Check memory usage
✓ Test on low-end hardware
✓ Verify all assets load correctly
✓ Test input methods (keyboard, gamepad, touch)
✓ Check for crash logs
### Versioning
```
Use semantic versioning: MAJOR.MINOR.PATCH
Example: 1.2.3
MAJOR: Breaking changes
MINOR: New features (backward compatible)
PATCH: Bug fixes
Android version code: Increment for each release
```
### Continuous Integration
```yaml
# GitHub Actions example
name: Export Game
on: [push]
jobs:
export:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Export for Windows
run: |
# Export commands here
- name: Upload artifacts
uses: actions/upload-artifact@v2
with:
name: game-builds
path: builds/
```
---
## Platform Comparison
| Feature | Windows | Linux | macOS | Web | Android |
|---------|---------|-------|-------|-----|---------|
| **Ease of Export** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| **Distribution** | Easy | Easy | Medium | Easiest | Medium |
| **Performance** | Excellent | Excellent | Excellent | Good | Good |
| **File Size** | Medium | Medium | Large | Large | Medium |
| **Monetization** | Direct | Direct | App Store | Ads/IAP | Play Store |
| **Updates** | Manual | Manual | Manual | Instant | Store |
---
## Next Steps
- **Test Your Exports:** Always test on real hardware
- **Get Feedback:** Share with beta testers
- **Optimize:** Profile and improve performance
- **Distribute:** Choose your distribution method
- **Monitor:** Use AeThexAnalytics to track usage
For platform-specific questions, see:
- [API Reference](API_REFERENCE.md) - Cloud features
- [GAME_DEVELOPMENT.md](GAME_DEVELOPMENT.md) - Core engine features
- [ARCHITECTURE_OVERVIEW.md](ARCHITECTURE_OVERVIEW.md) - Technical details
Happy shipping! 🚀

724
docs/GAME_DEVELOPMENT.md Normal file
View file

@ -0,0 +1,724 @@
# 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)

806
docs/PUBLISHING_GUIDE.md Normal file
View file

@ -0,0 +1,806 @@
# Publishing Your Game
A comprehensive guide to publishing and distributing your AeThex game to players worldwide.
---
## Overview
This guide covers the complete publishing workflow:
- Pre-launch checklist
- Platform-specific setup
- Store submissions
- Marketing materials
- Post-launch monitoring
- Updates and maintenance
---
## Table of Contents
1. [Pre-Launch Checklist](#pre-launch-checklist)
2. [Export Your Game](#export-your-game)
3. [Platform-Specific Publishing](#platform-specific-publishing)
4. [Marketing Materials](#marketing-materials)
5. [Launch Strategy](#launch-strategy)
6. [Post-Launch](#post-launch)
7. [Updates and Patches](#updates-and-patches)
---
## Pre-Launch Checklist
### ✅ Game Completion
- [ ] All levels/content implemented
- [ ] Tutorial completed and tested
- [ ] All major bugs fixed
- [ ] Performance optimized for target platforms
- [ ] Tested on minimum spec hardware
- [ ] Audio balanced and finalized
- [ ] UI polished and consistent
- [ ] Accessibility options implemented
### ✅ Technical Requirements
- [ ] Cloud services configured (if using)
- [ ] Analytics integrated
- [ ] Crash reporting set up
- [ ] Save system tested thoroughly
- [ ] Multiplayer stress-tested (if applicable)
- [ ] All external APIs work in production
- [ ] Privacy policy created
- [ ] Terms of service written
### ✅ Legal & Business
- [ ] Company/entity registered (if required)
- [ ] Tax information prepared
- [ ] Age rating obtained (ESRB, PEGI, etc.)
- [ ] Trademark search completed
- [ ] Copyright notices added
- [ ] License agreements in place
- [ ] Insurance considered (if applicable)
### ✅ Store Assets
- [ ] Game title finalized
- [ ] Description written (all required languages)
- [ ] Screenshots captured (all required platforms)
- [ ] Trailer created and uploaded
- [ ] Icon/logo designed in all required sizes
- [ ] Banner images created
- [ ] Keywords/tags researched
- [ ] Store page previewed
---
## Export Your Game
See [EXPORTING_GAMES.md](EXPORTING_GAMES.md) for detailed export instructions for:
- Windows
- Linux
- macOS
- Web (HTML5)
- Android
- iOS (coming soon)
**Quick Export:**
```gdscript
# Via editor:
Project → Export → Select Platform → Export Project
# Via command line:
aethex --export-release "Windows Desktop" builds/windows/game.exe
```
---
## Platform-Specific Publishing
### Steam
**1. Create Steamworks Account:**
- Go to [partner.steamgames.com](https://partner.steamgames.com)
- Pay app deposit ($100 per game, recoupable)
- Complete company verification
**2. Set Up Your Game:**
```
Steamworks Admin Panel:
├── App Admin → Basic Info
│ ├── Game name
│ ├── Description
│ └── Release date
├── Store Presence → Graphics
│ ├── Header capsule (460x215)
│ ├── Small capsule (231x87)
│ ├── Screenshots (1920x1080)
│ └── Trailer
└── Technical Requirements
├── Supported OS
└── Minimum specs
```
**3. Upload Build:**
```bash
# Install Steamworks SDK
# Use SteamPipe to upload
# steamcmd.exe
login your_username
set_product your_app_id
upload_depot depot_id depot_manifest.vdf
```
**4. Configure Store Page:**
- Description (short & long)
- Tags/categories
- Supported languages
- System requirements
- Pricing
**5. Submit for Review:**
- Complete all required fields
- Submit for Steam review (1-5 days)
- Address any feedback
- Set release date
**6. Launch:**
- Release when ready
- Consider Early Access for ongoing development
---
### itch.io
**1. Create Account:**
- Go to [itch.io](https://itch.io)
- Sign up for creator account (free)
**2. Create New Project:**
```
Dashboard → Create new project:
├── Title & URL
├── Classification (Game)
├── Kind (Downloadable/HTML5)
└── Release status
```
**3. Upload Files:**
```
Upload → Add file:
├── Windows ZIP
├── Linux TAR.GZ
├── macOS ZIP
└── Web folder (if HTML5)
Set file types:
- Windows: Executable
- Linux: Executable
- Web: This file will be played in the browser
```
**4. Configure Page:**
- Cover image (630x500)
- Screenshots
- Description
- Trailer embed
- Tags
- Pricing (free or paid)
- Donation options
**5. Publish:**
- Preview page
- Click "Publish"
- Share link immediately
**Pros:**
- Free to publish
- Indie-friendly community
- Instant publishing
- Flexible pricing (pay what you want)
- Good for prototypes/demos
---
### GOG
**1. Apply for Publishing:**
- Email [games@gog.com](mailto:games@gog.com)
- Provide game description and trailer
- Wait for approval (selective)
**2. Submission Process:**
- If approved, work with GOG partner manager
- Build must be DRM-free
- GOG handles QA testing
**3. Release:**
- GOG manages store page
- Revenue split: 70/30
---
### Epic Games Store
**1. Apply:**
- Go to [Epic Games Publishing](https://www.epicgames.com/unrealengine/en-US/publish)
- Submit application
- Wait for review
**2. Onboarding:**
- Complete developer agreement
- Set up payments
- Work with Epic partner manager
**3. Requirements:**
- High-quality polish expected
- Epic Games Account integration
- Achievement support recommended
---
### Google Play Store
**1. Create Developer Account:**
- Go to [Google Play Console](https://play.google.com/console)
- Pay one-time fee ($25)
- Complete account verification
**2. Create App:**
```
Play Console → Create app:
├── App details
├── Store listing
│ ├── Title
│ ├── Description (short & full)
│ ├── Screenshots (phone & tablet)
│ ├── Feature graphic (1024x500)
│ └── Icon (512x512)
├── Content rating (ESRB, PEGI)
└── Pricing & distribution
```
**3. Upload APK/AAB:**
```
Release Management → App releases:
├── Production → Create release
├── Upload AAB (recommended) or APK
├── Set version code/name
└── Add release notes
```
**4. Fill Requirements:**
- Privacy policy URL
- Content rating questionnaire
- Target audience
- App content
- Data safety section
**5. Submit for Review:**
- Review takes 1-7 days
- Address any policy violations
- Publish when approved
---
### Apple App Store (iOS)
**1. Apple Developer Account:**
- Join [Apple Developer Program](https://developer.apple.com/programs/) ($99/year)
- Complete agreements
**2. App Store Connect:**
```
Create New App:
├── Platform (iOS)
├── Name
├── Primary language
├── Bundle ID
└── SKU
```
**3. Prepare App:**
```
App Information:
├── Name & subtitle
├── Privacy policy URL
├── Category
├── Screenshots (all device sizes)
├── Description
└── Keywords
```
**4. Build Upload:**
```bash
# Archive in Xcode
# Upload via Xcode or Transporter app
# Wait for processing (15-60 minutes)
```
**5. Submit for Review:**
- Select build
- Set release method (manual/automatic)
- Add version information
- Submit (review takes 1-2 days typically)
---
### Web Publishing
**GitHub Pages:**
```bash
# Build for web
aethex --export "Web" builds/web/index.html
# Create gh-pages branch
git checkout -b gh-pages
cp -r builds/web/* .
git add .
git commit -m "Deploy game"
git push origin gh-pages
# Enable in repository settings
# Access at: https://username.github.io/repository
```
**Netlify:**
```bash
# Install Netlify CLI
npm install -g netlify-cli
# Deploy
netlify deploy --dir=builds/web --prod
# Or drag-and-drop in Netlify dashboard
```
**Vercel:**
```bash
# Install Vercel CLI
npm install -g vercel
# Deploy
vercel builds/web --prod
```
**Self-Hosting:**
```nginx
# nginx configuration
server {
listen 80;
server_name yourgame.com;
root /var/www/yourgame;
location / {
try_files $uri $uri/ /index.html;
}
# Enable CORS for WebAssembly
location ~* \.(wasm|pck)$ {
add_header Access-Control-Allow-Origin *;
}
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|wasm|pck)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
```
---
## Marketing Materials
### Screenshots
**Best Practices:**
- Capture at 1920x1080 or higher
- Show gameplay, not menus
- Highlight key features
- Include UI elements
- Use variety (action, exploration, etc.)
- Add subtle branding watermark
**Tools:**
```gdscript
# In-game screenshot system
func take_screenshot():
var img = get_viewport().get_texture().get_image()
img.save_png("user://screenshot_%s.png" % Time.get_unix_time_from_system())
```
### Trailer
**Structure:**
- 0-5s: Hook (best gameplay moment)
- 5-15s: Core gameplay footage
- 15-30s: Features and variety
- 30-45s: Unique selling points
- 45-60s: Call to action + release date
**Tools:**
- Video editing: DaVinci Resolve (free)
- Screen recording: OBS Studio (free)
- Music: [Epidemic Sound](https://epidemicsound.com), [Artlist](https://artlist.io)
### Store Description
**Template:**
```
[One-sentence hook]
[2-3 sentence description of gameplay]
KEY FEATURES:
• Feature 1 (brief description)
• Feature 2 (brief description)
• Feature 3 (brief description)
• Feature 4 (brief description)
[Optional: Story/setting paragraph]
[System requirements or platform info]
[Social media links]
```
---
## Launch Strategy
### Timing
**When to Launch:**
- Avoid major game releases
- Consider seasonal factors (summer slow, Q4 busy)
- Tuesday/Thursday often best for visibility
- Allow time for reviews/coverage
**Soft Launch:**
- Release to smaller region first
- Gather feedback
- Fix critical issues
- Full launch 1-2 weeks later
### Press Kit
Create a press kit at `yourgame.com/press`:
- Fact sheet (release date, platforms, price)
- Description
- Features
- Trailer embed
- Screenshots (zip download)
- Logo (multiple formats)
- Developer info
- Contact email
### Reaching Out
**Media Contacts:**
- Research relevant gaming sites/YouTubers
- Send personalized emails (not mass blasts)
- Provide steam keys/review copies
- Follow up once if no response
**Email Template:**
```
Subject: [Your Game Name] - [Genre] launching [Date]
Hi [Name],
I'm [your name], developer of [game name], a [genre] game
launching on [platform] on [date].
[One paragraph about what makes your game special]
Key features:
• Feature 1
• Feature 2
• Feature 3
I'd love to send you a review key. Are you interested?
Trailer: [link]
Press kit: [link]
Thanks,
[Your name]
```
---
## Post-Launch
### Monitor Launch
**First 24 Hours:**
- Watch for critical bugs
- Monitor social media mentions
- Respond to player feedback
- Track analytics (sales, downloads, engagement)
- Be ready to deploy hotfix if needed
**First Week:**
- Gather reviews and feedback
- Plan first update
- Engage with community
- Share player content
- Thank supporters
### Community Management
**Platforms to Manage:**
- Steam discussions
- Discord server
- Twitter/X mentions
- Reddit threads
- Email support
**Response Times:**
- Critical bugs: < 4 hours
- General support: < 24 hours
- Feature requests: Acknowledge within 48 hours
### Analytics Review
**Key Metrics:**
- Daily active users (DAU)
- Retention (Day 1, 7, 30)
- Average session length
- Completion rates
- Crash rate
- Purchase conversion (if applicable)
See [ANALYTICS_TUTORIAL.md](tutorials/ANALYTICS_TUTORIAL.md) for implementation.
---
## Updates and Patches
### Hotfix (Critical Bugs)
```bash
# Fix bug
# Test thoroughly
# Export new build
# Upload to platforms
# Version: 1.0.0 → 1.0.1
```
**Update Quickly:**
- Web: Instant (just replace files)
- itch.io: Upload new build (instant)
- Steam: Upload via SteamPipe (< 1 hour)
- Mobile: 1-7 day review
### Content Updates
**Planning:**
- Based on player feedback
- Fix common pain points
- Add requested features
- Balance tweaks
- New content
**Versioning:**
```
Major.Minor.Patch
1.0.0 → 1.1.0 (feature update)
1.1.0 → 1.1.1 (bug fix)
1.1.1 → 2.0.0 (major update)
```
**Changelog:**
```
Version 1.1.0 - November 15, 2024
NEW:
• New boss battle
• 5 new weapons
• Photo mode
IMPROVED:
• Better controller support
• Faster loading times
• Updated UI
FIXED:
• Player getting stuck in walls
• Audio crackling issue
• Save corruption bug
```
### DLC/Expansions
```gdscript
# Check DLC ownership
if AeThexAuth.has_dlc("expansion_pack_1"):
enable_expansion_content()
```
---
## Monetization Strategies
### Premium (Paid)
**Pricing:**
- Research similar games
- Consider your costs
- Regional pricing
- Sales strategy
**Steam Pricing Tips:**
- $9.99 - $19.99: Indie game sweet spot
- Avoid $14.99 (psychological barrier)
- Plan for sales (20-50% off)
### Free-to-Play
**Best Practices:**
- Game must be fun without paying
- No pay-to-win mechanics
- Cosmetic items work well
- Battle pass model
- Generous with free currency
```gdscript
# In-app purchases
func purchase_item(item_id: String):
var result = await AeThexAuth.purchase_item(item_id)
if result.success:
grant_item(item_id)
AeThexAnalytics.track_event("iap_purchase", {
"item_id": item_id,
"price": result.price
})
```
### Freemium
- Free demo/trial
- Upgrade to full version
- Good for single-player games
---
## Marketing Channels
### Social Media
**Twitter/X:**
- Post development updates
- Share GIFs of gameplay
- Engage with gamedev community
- Use hashtags: #indiegame #gamedev
**TikTok/Instagram:**
- Short gameplay clips
- Behind-the-scenes
- Game development tips
- Quick wins/satisfying moments
**Reddit:**
- r/IndieGaming
- r/gamedev
- Genre-specific subreddits
- Avoid spam, engage authentically
### Discord
Create a community server:
- Announcements channel
- General chat
- Bug reports
- Suggestions
- Development updates
### Email List
**Build Pre-Launch:**
- Capture emails on landing page
- Send updates during development
- Offer beta access
- Announce launch date
**Tools:**
- Mailchimp (free tier)
- ConvertKit
- Substack
---
## Common Pitfalls
### ❌ Avoid These Mistakes:
1. **Launching too early** - Polish matters
2. **No marketing** - "Build it and they'll come" is a myth
3. **Ignoring feedback** - Players know what's not fun
4. **No community** - Build audience before launch
5. **Poor store page** - First impression is everything
6. **Broken multiplayer** - Test with real players
7. **No analytics** - You need data to improve
8. **Giving up quickly** - Games can have long tails
---
## Resources
### Tools
- **Analytics:** [AeThex Analytics](https://studio.aethex.io/analytics)
- **Marketing:** [Presskit()](https://dopresskit.com/)
- **Community:** [Discord](https://discord.com)
- **Email:** [Mailchimp](https://mailchimp.com)
### Learning
- **Podcast:** How to Market a Game Podcast
- **Book:** "The Indie Game Developer Handbook"
- **YouTube:** Game Marketing channels
- **Community:** [r/gamedev](https://reddit.com/r/gamedev)
### Support
- **Email:** [support@aethex.io](mailto:support@aethex.io)
- **Discord:** [AeThex Community](https://discord.gg/aethex)
- **Docs:** [docs.aethex.io](https://docs.aethex.io)
---
## Checklist for Launch Day
**24 Hours Before:**
- [ ] Final build tested on all platforms
- [ ] Store pages reviewed (no typos!)
- [ ] Press emails sent
- [ ] Social media posts scheduled
- [ ] Discord announcement prepared
- [ ] Support email ready to monitor
- [ ] Analytics dashboard configured
- [ ] Backup plan for critical bugs
**Launch Day:**
- [ ] Publish on all platforms
- [ ] Post to social media
- [ ] Send email to mailing list
- [ ] Post in relevant communities
- [ ] Monitor for issues
- [ ] Respond to comments
- [ ] Thank supporters
- [ ] Celebrate! 🎉
**Week After:**
- [ ] Gather feedback
- [ ] Plan first update
- [ ] Thank press/influencers who covered
- [ ] Post-mortem analysis
- [ ] Start work on updates
---
## Summary
You've learned how to:
✅ Prepare your game for launch
✅ Publish to multiple platforms
✅ Create marketing materials
✅ Build and engage community
✅ Plan your launch strategy
✅ Support your game post-launch
✅ Handle updates and patches
Publishing is just the beginning - support your game and community for long-term success!
**Need More Help?**
- [Export Guide](EXPORTING_GAMES.md) - Technical export details
- [Analytics Tutorial](tutorials/ANALYTICS_TUTORIAL.md) - Track your success
- [API Reference](API_REFERENCE.md) - Cloud features documentation
**Good luck with your launch!** 🚀

View file

@ -139,52 +139,29 @@ Welcome to AeThex Engine - the cloud-first game engine that makes multiplayer, c
### Game Development ### Game Development
- **Basics:** **→ [Complete Game Development Guide](GAME_DEVELOPMENT.md)**
- [GDScript Basics](GDSCRIPT_BASICS.md)
- [First Game Tutorial](tutorials/FIRST_GAME_TUTORIAL.md)
- Scene system
- Node hierarchy
- Signals and callbacks
- **Physics:** Learn all core engine concepts:
- RigidBody2D/3D
- StaticBody2D/3D
- Collision shapes
- Physics layers
- **UI:** - **[Scene System](GAME_DEVELOPMENT.md#scene-system)** - Building blocks of your game
- Control nodes - **[Node Hierarchy](GAME_DEVELOPMENT.md#node-hierarchy)** - Organizing game objects
- Layouts and containers - **[Signals & Callbacks](GAME_DEVELOPMENT.md#signals-and-callbacks)** - Event-driven programming
- Themes - **[Physics System](GAME_DEVELOPMENT.md#physics)** - 2D/3D physics, collisions, layers
- Responsive design - **[UI System](GAME_DEVELOPMENT.md#ui-system)** - Control nodes, layouts, themes
- **[Audio System](GAME_DEVELOPMENT.md#audio-system)** - Music, sound effects, 3D audio
- **[Workflow & Tools](GAME_DEVELOPMENT.md#workflow--tools)** - Studio IDE, version control
- **Audio:** ### Platform Export
- AudioStreamPlayer
- Music management
- Sound effects
- 3D audio
### Workflow **→ [Complete Export Guide](EXPORTING_GAMES.md)**
- **Studio IDE:** Export to all platforms:
- [Studio Integration](STUDIO_INTEGRATION.md)
- Code editor
- Scene tree
- Asset browser
- Live reload
- **Version Control:** - **[Windows Export](EXPORTING_GAMES.md#windows-export)** - Desktop Windows builds, code signing, distribution
- Git integration - **[Linux Export](EXPORTING_GAMES.md#linux-export)** - Linux builds, AppImage, Flatpak, Snap
- Collaboration - **[macOS Export](EXPORTING_GAMES.md#macos-export)** - macOS builds, notarization, App Store
- Branching strategy - **[Web Export](EXPORTING_GAMES.md#web-html5-export)** - HTML5/WebAssembly, PWA, hosting
- Merge conflicts - **[Android Export](EXPORTING_GAMES.md#android-export)** - APK/AAB builds, Play Store, optimization
- **Export:**
- Windows export
- Linux export
- macOS export
- Web (HTML5) export
- Android export
--- ---

View file

@ -7,24 +7,53 @@
* [GDScript Basics](GDSCRIPT_BASICS.md) * [GDScript Basics](GDSCRIPT_BASICS.md)
* [First Game Tutorial](tutorials/FIRST_GAME_TUTORIAL.md) * [First Game Tutorial](tutorials/FIRST_GAME_TUTORIAL.md)
* Game Development
* [Complete Guide](GAME_DEVELOPMENT.md)
* [Scene System](GAME_DEVELOPMENT.md#scene-system)
* [Node Hierarchy](GAME_DEVELOPMENT.md#node-hierarchy)
* [Signals & Callbacks](GAME_DEVELOPMENT.md#signals-and-callbacks)
* [Physics System](GAME_DEVELOPMENT.md#physics)
* [UI System](GAME_DEVELOPMENT.md#ui-system)
* [Audio System](GAME_DEVELOPMENT.md#audio-system)
* [Workflow & Tools](GAME_DEVELOPMENT.md#workflow--tools)
* Tutorials * Tutorials
* [Tutorial Index](tutorials/README.md) * [Tutorial Index](tutorials/README.md)
* [Multiplayer Pong](tutorials/FIRST_GAME_TUTORIAL.md) * [Multiplayer Pong](tutorials/FIRST_GAME_TUTORIAL.md)
* [AI Assistant](tutorials/AI_ASSISTANT_TUTORIAL.md)
* [Authentication](tutorials/AUTH_TUTORIAL.md)
* [Analytics](tutorials/ANALYTICS_TUTORIAL.md)
* API Reference * API Reference
* [Complete API Reference](API_REFERENCE.md) * [Complete API Reference](API_REFERENCE.md)
* AeThexCloud * [AeThexCloud](API_REFERENCE.md#aethexcloud-singleton)
* AeThexAuth * [AeThexAuth](API_REFERENCE.md#aethexauth-singleton)
* AeThexSaves * [AeThexSaves](API_REFERENCE.md#aethexsaves-singleton)
* AeThexMultiplayer * [AeThexMultiplayer](API_REFERENCE.md#aethexmultiplayer-singleton)
* AeThexAnalytics * [AeThexAnalytics](API_REFERENCE.md#aethexanalytics-singleton)
* AeThexAI * [AeThexAI](API_REFERENCE.md#aethexai-singleton)
* [AeThexStudio](API_REFERENCE.md#aethexstudio-singleton)
* Architecture * Architecture
* [Architecture Overview](ARCHITECTURE_OVERVIEW.md) * [Architecture Overview](ARCHITECTURE_OVERVIEW.md)
* [Cloud Services](CLOUD_SERVICES_ARCHITECTURE.md) * [Cloud Services](CLOUD_SERVICES_ARCHITECTURE.md)
* [Studio Integration](STUDIO_INTEGRATION.md) * [Studio Integration](STUDIO_INTEGRATION.md)
* Platform Export
* [Export Guide](EXPORTING_GAMES.md)
* [Windows](EXPORTING_GAMES.md#windows-export)
* [Linux](EXPORTING_GAMES.md#linux-export)
* [macOS](EXPORTING_GAMES.md#macos-export)
* [Web (HTML5)](EXPORTING_GAMES.md#web-html5-export)
* [Android](EXPORTING_GAMES.md#android-export)
* Publishing
* [Publishing Guide](PUBLISHING_GUIDE.md)
* [Pre-Launch Checklist](PUBLISHING_GUIDE.md#pre-launch-checklist)
* [Platform Submission](PUBLISHING_GUIDE.md#platform-specific-publishing)
* [Marketing Materials](PUBLISHING_GUIDE.md#marketing-materials)
* [Launch Strategy](PUBLISHING_GUIDE.md#launch-strategy)
* Developer Guides * Developer Guides
* [Building from Source](BUILDING_WINDOWS.md) * [Building from Source](BUILDING_WINDOWS.md)
* [Studio Bridge Guide](STUDIO_BRIDGE_GUIDE.md) * [Studio Bridge Guide](STUDIO_BRIDGE_GUIDE.md)

View file

@ -6,17 +6,14 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="AeThex Engine - Cloud-first game engine documentation"> <meta name="description" content="AeThex Engine - Cloud-first game engine documentation">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
<style> <!-- Fonts -->
:root { <link rel="preconnect" href="https://fonts.googleapis.com">
--theme-color: #8B5CF6; <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
--theme-color-secondary: #06B6D4; <link href="https://fonts.googleapis.com/css2?family=Electrolize&family=Source+Code+Pro:wght@400;500;600;700&display=swap" rel="stylesheet">
}
.app-name-link img { <!-- Custom Cyberpunk Theme -->
width: 40px; <link rel="stylesheet" href="theme.css">
margin-right: 10px;
}
</style>
</head> </head>
<body> <body>
<div id="app">Loading...</div> <div id="app">Loading...</div>

527
docs/theme.css Normal file
View file

@ -0,0 +1,527 @@
/* AeThex Cyberpunk Documentation Theme */
@import url('https://fonts.googleapis.com/css2?family=Electrolize&family=Source+Code+Pro:wght@400;500;600;700&display=swap');
:root {
/* Cyberpunk Dark Backgrounds */
--base-background-color: #000000;
--base-color: #ffffff;
/* Neon Accents */
--theme-color: #00ffff;
--theme-color-secondary: #ff00ff;
/* Text Colors */
--text-primary: #ffffff;
--text-secondary: #b0b0b0;
--text-muted: #808080;
/* Borders with Glow */
--border-primary: rgba(0, 255, 255, 0.3);
--border-glow: rgba(0, 255, 255, 0.5);
/* Code Blocks */
--code-theme-background: #0a0a0a;
--code-theme-text: #00ffff;
/* Sidebar */
--sidebar-background: #0a0a0a;
--sidebar-border-color: rgba(0, 255, 255, 0.2);
/* Search */
--search-input-background-color: #1a1a1a;
--search-input-border-color: rgba(0, 255, 255, 0.3);
/* Links */
--link-color: #00ffff;
--link-color-hover: #ff00ff;
}
/* Base Styling */
* {
transition: all 0.2s ease;
}
html {
font-size: 14px;
scroll-behavior: smooth;
}
body {
font-family: 'Source Code Pro', 'Courier New', monospace;
background: #000000;
color: #ffffff;
position: relative;
overflow-x: hidden;
}
/* Scanline Effect Overlay */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: repeating-linear-gradient(
0deg,
rgba(0, 255, 255, 0.03) 0px,
transparent 1px,
transparent 2px,
rgba(0, 255, 255, 0.03) 3px
);
pointer-events: none;
z-index: 9999;
animation: scanline 8s linear infinite;
}
@keyframes scanline {
0% { transform: translateY(0); }
100% { transform: translateY(10px); }
}
/* Headings with Neon Glow */
h1, h2, h3, h4, h5, h6 {
font-family: 'Electrolize', sans-serif;
color: #00ffff;
text-shadow: 0 0 10px rgba(0, 255, 255, 0.5),
0 0 20px rgba(0, 255, 255, 0.3);
font-weight: 700;
letter-spacing: 0.05em;
}
h1 {
font-size: 2em;
border-bottom: 2px solid rgba(0, 255, 255, 0.3);
padding-bottom: 0.5em;
margin-bottom: 1em;
text-transform: uppercase;
}
h2 {
font-size: 1.6em;
margin-top: 2em;
position: relative;
padding-left: 1em;
}
h2::before {
content: '▶';
position: absolute;
left: 0;
color: #ff00ff;
text-shadow: 0 0 10px rgba(255, 0, 255, 0.8);
}
h3 {
font-size: 1.3em;
color: #ff00ff;
text-shadow: 0 0 10px rgba(255, 0, 255, 0.5);
}
/* Links with Neon Hover */
a {
color: #00ffff !important;
text-decoration: none;
position: relative;
transition: all 0.3s ease;
}
a:hover {
color: #ff00ff !important;
text-shadow: 0 0 10px rgba(255, 0, 255, 0.8);
}
/* Sidebar Cyberpunk Style */
.sidebar {
background: #0a0a0a;
border-right: 1px solid rgba(0, 255, 255, 0.2);
box-shadow: 2px 0 20px rgba(0, 255, 255, 0.1);
}
.sidebar-nav {
padding: 1em;
}
.sidebar-nav li {
margin: 0.5em 0;
}
.sidebar-nav a {
color: #b0b0b0 !important;
border-left: 2px solid transparent;
padding-left: 0.5em;
display: block;
font-family: 'Source Code Pro', monospace;
}
.sidebar-nav a:hover {
color: #00ffff !important;
border-left-color: #00ffff !important;
text-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
}
.sidebar-nav li.active > a {
color: #00ffff !important;
border-left-color: #ff00ff !important;
font-weight: 600;
text-shadow: 0 0 10px rgba(0, 255, 255, 0.8);
}
/* App Name with Glow */
.app-name {
font-family: 'Electrolize', sans-serif;
font-size: 1.2em;
color: #00ffff;
text-shadow: 0 0 20px rgba(0, 255, 255, 0.8),
0 0 40px rgba(0, 255, 255, 0.4);
text-transform: uppercase;
letter-spacing: 0.1em;
padding: 0.8em 1em;
border-bottom: 1px solid rgba(0, 255, 255, 0.2);
}
/* Logo Sizing */
.app-name img,
.sidebar img,
.github-corner,
.github-corner svg {
max-width: 60px !important;
max-height: 60px !important;
width: auto !important;
height: auto !important;
}
.app-name-link img {
width: 32px !important;
height: 32px !important;
margin-right: 10px;
vertical-align: middle;
}
/* GitHub Corner Repositioning */
.github-corner {
position: fixed !important;
top: 0 !important;
right: 0 !important;
width: 60px !important;
height: 60px !important;
z-index: 100 !important;
}
.github-corner svg {
fill: #00ffff !important;
color: #000000 !important;
width: 60px !important;
height: 60px !important;
}
.github-corner:hover svg {
fill: #ff00ff !important;
}
.github-corner .octo-arm,
.github-corner .octo-body {
fill: #000000 !important;
}
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
@keyframes octocat-wave {
0%, 100% { transform: rotate(0); }
20%, 60% { transform: rotate(-25deg); }
40%, 80% { transform: rotate(10deg); }
}
/* Content Area */
.content {
padding: 2em 3em;
background: #000000;
}
/* Content Images */
.content img {
max-width: 100%;
height: auto;
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 4px;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.1);
}
/* Code Blocks with Neon Border */
pre {
background: #0a0a0a !important;
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 4px;
padding: 1.5em !important;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.1);
position: relative;
overflow: auto;
}
pre code {
color: #00ffff !important;
font-family: 'Source Code Pro', monospace;
font-size: 0.9em;
}
/* Inline Code */
code {
background: #1a1a1a;
color: #ff00ff;
padding: 0.2em 0.5em;
border-radius: 3px;
border: 1px solid rgba(255, 0, 255, 0.3);
font-family: 'Source Code Pro', monospace;
font-size: 0.9em;
}
/* Blockquotes with Neon Accent */
blockquote {
background: #0a0a0a;
border-left: 4px solid #00ffff;
padding: 1em 1.5em;
margin: 1.5em 0;
color: #b0b0b0;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.1);
}
blockquote p {
margin: 0;
}
/* Tables with Cyberpunk Grid */
table {
border-collapse: collapse;
width: 100%;
margin: 2em 0;
background: #0a0a0a;
border: 1px solid rgba(0, 255, 255, 0.2);
}
thead {
background: #1a1a1a;
border-bottom: 2px solid #00ffff;
}
thead th {
color: #00ffff;
font-family: 'Electrolize', sans-serif;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 1em;
text-align: left;
}
tbody tr {
border-bottom: 1px solid rgba(0, 255, 255, 0.1);
}
tbody tr:hover {
background: #0f0f0f;
box-shadow: 0 0 10px rgba(0, 255, 255, 0.1);
}
tbody td {
padding: 0.8em 1em;
color: #b0b0b0;
}
/* Search Box */
.search input {
background: #1a1a1a;
border: 1px solid rgba(0, 255, 255, 0.3);
color: #ffffff;
padding: 0.8em 1em;
border-radius: 4px;
font-family: 'Source Code Pro', monospace;
transition: all 0.3s ease;
}
.search input:focus {
outline: none;
border-color: #00ffff;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
}
.search input::placeholder {
color: #505050;
}
/* Badges/Tags */
.label {
background: #1a1a1a;
color: #00ffff;
border: 1px solid rgba(0, 255, 255, 0.3);
padding: 0.2em 0.6em;
border-radius: 3px;
font-family: 'Source Code Pro', monospace;
font-size: 0.85em;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Pagination */
.docsify-pagination-container {
border-top: 1px solid rgba(0, 255, 255, 0.2);
margin-top: 3em;
padding-top: 2em;
}
.pagination-item-title {
font-family: 'Electrolize', sans-serif;
color: #00ffff;
font-size: 1.1em;
}
.pagination-item:hover .pagination-item-title {
color: #ff00ff;
text-shadow: 0 0 10px rgba(255, 0, 255, 0.5);
}
/* Checkmarks and Lists */
ul {
list-style: none;
padding-left: 0;
}
ul li {
padding-left: 1.5em;
position: relative;
margin: 0.5em 0;
}
ul li::before {
content: '▶';
position: absolute;
left: 0;
color: #00ffff;
font-size: 0.8em;
}
/* Checkbox Lists */
input[type="checkbox"] {
accent-color: #00ffff;
}
/* Alerts/Notices */
.tip, .warn, .danger {
padding: 1em 1.5em;
border-radius: 4px;
margin: 1.5em 0;
border-left: 4px solid;
background: #0a0a0a;
}
.tip {
border-left-color: #00ff00;
box-shadow: 0 0 20px rgba(0, 255, 0, 0.1);
}
.warn {
border-left-color: #ffff00;
box-shadow: 0 0 20px rgba(255, 255, 0, 0.1);
}
.danger {
border-left-color: #ff0000;
box-shadow: 0 0 20px rgba(255, 0, 0, 0.1);
}
/* Copy Code Button */
.docsify-copy-code-button {
background: #1a1a1a !important;
color: #00ffff !important;
border: 1px solid rgba(0, 255, 255, 0.3);
font-family: 'Source Code Pro', monospace;
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 0.7em;
}
.docsify-copy-code-button:hover {
background: #00ffff !important;
color: #000000 !important;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.5);
}
/* Scrollbar Cyberpunk Style */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: #0a0a0a;
}
::-webkit-scrollbar-thumb {
background: #1a1a1a;
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #00ffff;
box-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
}
/* Glowing Dividers */
hr {
border: none;
height: 1px;
background: linear-gradient(
90deg,
transparent,
rgba(0, 255, 255, 0.5),
transparent
);
margin: 3em 0;
box-shadow: 0 0 10px rgba(0, 255, 255, 0.3);
}
/* Loading Animation */
.progress {
background: #00ffff;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.8);
}
/* Mobile Responsive */
@media screen and (max-width: 768px) {
.content {
padding: 1em;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.6em;
}
.sidebar {
background: #000000;
}
}
/* Animations */
@keyframes glow-pulse {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
.glow-pulse {
animation: glow-pulse 2s ease-in-out infinite;
}
/* Syntax Highlighting Override */
.token.comment { color: #505050; }
.token.keyword { color: #ff00ff; }
.token.string { color: #00ff00; }
.token.function { color: #00ffff; }
.token.number { color: #ffff00; }
.token.operator { color: #ff00ff; }
.token.punctuation { color: #808080; }
.token.class-name { color: #00ffff; font-weight: bold; }

View file

@ -0,0 +1,632 @@
# AI Assistant Tutorial
Learn how to integrate AeThex's AI assistant into your game to provide contextual help and coding assistance to players.
---
## What You'll Build
A game with an in-game AI assistant that can:
- Answer questions about game mechanics
- Provide hints and tips
- Generate code snippets
- Offer context-aware help
**Time:** 20 minutes
**Difficulty:** Beginner
**Prerequisites:** Basic GDScript knowledge
---
## Prerequisites
- Completed [First Game Tutorial](FIRST_GAME_TUTORIAL.md)
- AeThex Cloud connection set up
- Basic understanding of UI systems
---
## Step 1: Connect to AI Service
First, ensure you're connected to AeThex Cloud with AI services enabled.
```gdscript
# main.gd
extends Node
func _ready():
# Connect to cloud
var result = await AeThexCloud.connect_to_cloud()
if result.success:
print("Connected to AeThex Cloud")
else:
print("Failed to connect: ", result.error)
```
---
## Step 2: Create the AI Assistant UI
Create a simple chat interface for the AI assistant.
```gdscript
# ai_assistant.gd
extends Control
@onready var chat_history = $VBox/ChatHistory
@onready var input_field = $VBox/HBox/InputField
@onready var send_button = $VBox/HBox/SendButton
@onready var loading_indicator = $VBox/LoadingIndicator
func _ready():
send_button.pressed.connect(_on_send_pressed)
input_field.text_submitted.connect(_on_text_submitted)
loading_indicator.visible = false
func _on_send_pressed():
_send_message(input_field.text)
func _on_text_submitted(text: String):
_send_message(text)
func _send_message(message: String):
if message.strip_edges().is_empty():
return
# Add user message to chat
add_message("You", message, Color.CYAN)
input_field.clear()
# Show loading indicator
loading_indicator.visible = true
send_button.disabled = true
# Ask AI assistant
var response = await AeThexAI.ask_assistant(message)
# Hide loading indicator
loading_indicator.visible = false
send_button.disabled = false
# Add AI response to chat
if response.success:
add_message("AI Assistant", response.answer, Color.GREEN)
else:
add_message("AI Assistant", "Sorry, I couldn't process that. " + response.error, Color.RED)
func add_message(sender: String, text: String, color: Color):
var label = Label.new()
label.text = "[%s]: %s" % [sender, text]
label.modulate = color
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
chat_history.add_child(label)
# Auto-scroll to bottom
await get_tree().process_frame
if chat_history.get_v_scroll_bar():
chat_history.scroll_vertical = chat_history.get_v_scroll_bar().max_value
```
---
## Step 3: Create the Scene
Create a scene structure for the AI assistant:
```
AIAssistant (Control)
├── Panel (Panel)
│ └── VBox (VBoxContainer)
│ ├── Title (Label) - "AI Assistant"
│ ├── ChatHistory (ScrollContainer)
│ │ └── MessageContainer (VBoxContainer)
│ ├── LoadingIndicator (Label) - "Thinking..."
│ └── HBox (HBoxContainer)
│ ├── InputField (LineEdit)
│ └── SendButton (Button) - "Send"
```
**Layout Tips:**
- Set Panel anchor to center
- ChatHistory should expand vertically
- InputField should expand horizontally
---
## Step 4: Context-Aware Help
Make the AI assistant aware of the player's current context.
```gdscript
# context_aware_assistant.gd
extends Control
var player_context = {
"current_level": 1,
"player_health": 100,
"inventory": [],
"last_checkpoint": "start",
}
func ask_with_context(question: String):
# Build context string
var context = "Player context: Level %d, Health: %d, Checkpoint: %s" % [
player_context.current_level,
player_context.player_health,
player_context.last_checkpoint
]
# Include context in the question
var full_question = "%s\n\nContext: %s" % [question, context]
# Ask AI
var response = await AeThexAI.ask_assistant(full_question)
return response
func update_context(key: String, value):
player_context[key] = value
```
---
## Step 5: Pre-defined Quick Help
Add quick help buttons for common questions.
```gdscript
# quick_help.gd
extends Control
@onready var assistant = get_node("../AIAssistant")
var quick_questions = [
"How do I jump higher?",
"What does this item do?",
"Where should I go next?",
"How do I defeat this enemy?",
]
func _ready():
# Create buttons for quick questions
for question in quick_questions:
var button = Button.new()
button.text = question
button.pressed.connect(_on_quick_question.bind(question))
add_child(button)
func _on_quick_question(question: String):
assistant.send_message(question)
```
---
## Step 6: Code Generation
Use the AI to generate code snippets for players.
```gdscript
# code_generator.gd
extends Control
@onready var code_output = $VBox/CodeOutput
@onready var request_input = $VBox/RequestInput
@onready var generate_button = $VBox/GenerateButton
func _ready():
generate_button.pressed.connect(_on_generate_pressed)
func _on_generate_pressed():
var request = request_input.text
if request.is_empty():
return
# Request code generation
var prompt = "Generate GDScript code for: " + request
var response = await AeThexAI.generate_code(prompt)
if response.success:
code_output.text = response.code
# Add syntax highlighting
code_output.syntax_highlighter = GDScriptSyntaxHighlighter.new()
else:
code_output.text = "Error: " + response.error
```
---
## Step 7: Hint System
Create a progressive hint system using the AI.
```gdscript
# hint_system.gd
extends Node
var current_puzzle = "temple_door"
var hint_level = 0
func get_hint():
hint_level += 1
var prompt = "Give hint level %d (out of 3) for puzzle: %s. Be progressively more specific." % [
hint_level,
current_puzzle
]
var response = await AeThexAI.ask_assistant(prompt)
if response.success:
return response.answer
else:
return "No hints available"
func reset_hints():
hint_level = 0
```
---
## Step 8: Tutorial Generator
Let AI generate custom tutorials for players.
```gdscript
# tutorial_generator.gd
extends Node
func generate_tutorial_for_mechanic(mechanic: String):
var prompt = """
Create a brief in-game tutorial for the mechanic: %s
Format:
1. Title
2. 3 simple steps
3. Tips
""" % mechanic
var response = await AeThexAI.ask_assistant(prompt)
if response.success:
return parse_tutorial(response.answer)
else:
return null
func parse_tutorial(text: String) -> Dictionary:
# Parse AI response into structured tutorial
return {
"title": "Tutorial",
"steps": text.split("\n"),
"completed": false
}
```
---
## Step 9: Error Explanation
Help players understand errors in custom scripting.
```gdscript
# error_explainer.gd
extends Node
func explain_error(error_message: String):
var prompt = """
Explain this game scripting error in simple terms and suggest a fix:
Error: %s
Target audience: Beginners
""" % error_message
var response = await AeThexAI.ask_assistant(prompt)
if response.success:
return {
"explanation": response.answer,
"success": true
}
else:
return {
"explanation": "Could not explain error",
"success": false
}
```
---
## Step 10: Rate Limiting and Caching
Implement rate limiting to prevent API abuse.
```gdscript
# ai_manager.gd
extends Node
var request_cache = {}
var last_request_time = 0
var min_request_interval = 2.0 # seconds
func ask_with_cache(question: String):
# Check cache first
if question in request_cache:
return request_cache[question]
# Rate limiting
var time_since_last = Time.get_ticks_msec() / 1000.0 - last_request_time
if time_since_last < min_request_interval:
var wait_time = min_request_interval - time_since_last
await get_tree().create_timer(wait_time).timeout
# Make request
last_request_time = Time.get_ticks_msec() / 1000.0
var response = await AeThexAI.ask_assistant(question)
# Cache successful responses
if response.success:
request_cache[question] = response
return response
func clear_cache():
request_cache.clear()
```
---
## Complete Example: In-Game Help System
Here's a complete help system implementation:
```gdscript
# help_system.gd
extends Control
@onready var help_panel = $HelpPanel
@onready var chat_container = $HelpPanel/VBox/ChatContainer
@onready var input_field = $HelpPanel/VBox/HBox/Input
@onready var send_btn = $HelpPanel/VBox/HBox/SendBtn
@onready var quick_help = $HelpPanel/VBox/QuickHelp
var is_open = false
func _ready():
help_panel.visible = false
send_btn.pressed.connect(_on_send)
input_field.text_submitted.connect(_on_submit)
# Toggle with F1
set_process_input(true)
# Setup quick help buttons
setup_quick_help()
func _input(event):
if event.is_action_pressed("ui_help"): # F1
toggle_help()
func toggle_help():
is_open = !is_open
help_panel.visible = is_open
if is_open:
input_field.grab_focus()
func _on_send():
_send_message(input_field.text)
func _on_submit(text: String):
_send_message(text)
func _send_message(message: String):
if message.strip_edges().is_empty():
return
add_message("You", message, Color.CYAN)
input_field.clear()
# Get context from game
var context = get_game_context()
var full_message = "%s\n\nGame context: %s" % [message, context]
# Ask AI
var response = await AeThexAI.ask_assistant(full_message)
if response.success:
add_message("Assistant", response.answer, Color.GREEN)
else:
add_message("Assistant", "Error: " + response.error, Color.RED)
func add_message(sender: String, text: String, color: Color):
var msg = RichTextLabel.new()
msg.bbcode_enabled = true
msg.text = "[color=%s][b]%s:[/b] %s[/color]" % [color.to_html(), sender, text]
msg.fit_content = true
chat_container.add_child(msg)
func get_game_context() -> String:
# Gather relevant game state
var player = get_tree().get_first_node_in_group("player")
if player:
return "Level: %d, Health: %d, Position: %s" % [
player.current_level,
player.health,
player.global_position
]
return "No context available"
func setup_quick_help():
var questions = [
"How do I play?",
"What are the controls?",
"Where should I go?",
"How do I use items?",
]
for q in questions:
var btn = Button.new()
btn.text = q
btn.pressed.connect(func(): _send_message(q))
quick_help.add_child(btn)
```
---
## Best Practices
### 1. **Provide Context**
Always include relevant game state when asking the AI:
```gdscript
var context = "Player is at checkpoint 3, has 50% health"
var question_with_context = "%s\nContext: %s" % [user_question, context]
```
### 2. **Clear Expectations**
Tell players what the AI can and cannot do:
```gdscript
var help_text = """
AI Assistant can help with:
- Game mechanics
- Quest objectives
- Strategy tips
Cannot help with:
- Technical support
- Account issues
"""
```
### 3. **Rate Limiting**
Prevent abuse with rate limiting:
```gdscript
const MAX_REQUESTS_PER_MINUTE = 10
```
### 4. **Caching**
Cache common questions to reduce API calls:
```gdscript
var faq_cache = {
"how to jump": "Press Space or A button",
"how to save": "Game auto-saves at checkpoints",
}
```
### 5. **Fallback Responses**
Always have fallback responses:
```gdscript
if not response.success:
return "Check the tutorial at Main Menu → Help"
```
---
## Advanced Features
### Personality Customization
Give your AI assistant personality:
```gdscript
func ask_with_personality(question: String):
var system_prompt = """
You are a helpful wizard companion in a fantasy RPG.
Speak in a wise, mystical tone.
Be encouraging and friendly.
"""
var response = await AeThexAI.ask_assistant(
question,
{"system_prompt": system_prompt}
)
return response
```
### Multi-Language Support
Support multiple languages:
```gdscript
func ask_in_language(question: String, language: String):
var prompt = "Answer in %s: %s" % [language, question]
return await AeThexAI.ask_assistant(prompt)
```
### Voice Integration
Combine with text-to-speech:
```gdscript
func speak_response(text: String):
# Use DisplayServer TTS if available
if DisplayServer.tts_is_speaking():
DisplayServer.tts_stop()
DisplayServer.tts_speak(text, "en", 50, 1.0, 1.0, 0, true)
```
---
## Testing
Test your AI assistant:
```gdscript
# test_ai_assistant.gd
extends Node
func _ready():
run_tests()
func run_tests():
print("Testing AI Assistant...")
# Test basic question
var r1 = await AeThexAI.ask_assistant("How do I move?")
assert(r1.success, "Basic question failed")
# Test code generation
var r2 = await AeThexAI.generate_code("Create a jump function")
assert(r2.success and "func" in r2.code, "Code generation failed")
print("All tests passed!")
```
---
## Troubleshooting
**AI not responding:**
- Check cloud connection: `AeThexCloud.is_connected()`
- Verify AI service is enabled in project settings
- Check console for error messages
**Slow responses:**
- Implement request caching
- Use loading indicators
- Consider reducing context size
**Irrelevant answers:**
- Provide more specific context
- Use structured prompts
- Implement feedback system
---
## Next Steps
- **[Authentication Tutorial](AUTH_TUTORIAL.md)** - Add user accounts
- **[Analytics Tutorial](ANALYTICS_TUTORIAL.md)** - Track AI usage
- **[API Reference](../API_REFERENCE.md#aethexai-singleton)** - Complete AI API docs
---
## Summary
You've learned how to:
✅ Integrate AI assistant into your game
✅ Create context-aware help systems
✅ Generate code and tutorials with AI
✅ Implement caching and rate limiting
✅ Build an in-game help UI
The AI assistant can dramatically improve player experience by providing instant, contextual help!
**Questions?** Ask the AI assistant in your game! 🤖

View file

@ -0,0 +1,752 @@
# Analytics Tutorial
Learn how to track player behavior, measure engagement, and make data-driven decisions with AeThex Analytics.
---
## What You'll Build
A complete analytics system that tracks:
- Player events (level complete, item collected, etc.)
- User properties (level, skill, preferences)
- Custom funnels (onboarding, purchases, retention)
- Crash reports and errors
- Performance metrics
**Time:** 25 minutes
**Difficulty:** Beginner
**Prerequisites:** Basic GDScript knowledge
---
## Why Use Analytics?
Analytics helps you:
- **Understand players** - See what they do in your game
- **Improve retention** - Find where players drop off
- **Optimize monetization** - Track purchase funnels
- **Fix bugs faster** - Get crash reports automatically
- **Make data-driven decisions** - Know what features to build
---
## Step 1: Connect to Analytics
First, connect to AeThex Cloud and initialize analytics:
```gdscript
# main.gd
extends Node
func _ready():
# Connect to cloud
var result = await AeThexCloud.connect_to_cloud()
if result.success:
print("Connected to AeThex Cloud")
initialize_analytics()
else:
print("Failed to connect: ", result.error)
func initialize_analytics():
# Analytics initialization is automatic
# Track app start
AeThexAnalytics.track_event("app_started", {
"platform": OS.get_name(),
"version": ProjectSettings.get_setting("application/config/version")
})
```
---
## Step 2: Track Basic Events
Track important game events:
```gdscript
#player.gd
extends CharacterBody2D
var level = 1
var score = 0
func level_complete():
# Track level completion
AeThexAnalytics.track_event("level_complete", {
"level": level,
"score": score,
"time_spent": get_level_time(),
"deaths": death_count
})
level += 1
func collect_item(item_name: String):
# Track item collection
AeThexAnalytics.track_event("item_collected", {
"item_name": item_name,
"level": level,
"timestamp": Time.get_unix_time_from_system()
})
func player_died():
# Track deaths
AeThexAnalytics.track_event("player_died", {
"level": level,
"cause": death_cause,
"position": global_position
})
```
---
## Step 3: Set User Properties
Store persistent information about users:
```gdscript
# analytics_manager.gd (autoload)
extends Node
func _ready():
# Set up user properties when game starts
setup_user_properties()
func setup_user_properties():
# Basic user info
AeThexAnalytics.set_user_property("platform", OS.get_name())
AeThexAnalytics.set_user_property("game_version", ProjectSettings.get_setting("application/config/version"))
# Load from save file
var save_data = load_save()
if save_data:
AeThexAnalytics.set_user_property("player_level", save_data.level)
AeThexAnalytics.set_user_property("total_playtime", save_data.playtime)
AeThexAnalytics.set_user_property("achievements_unlocked", save_data.achievements.size())
func on_player_level_up(new_level: int):
# Update user property when it changes
AeThexAnalytics.set_user_property("player_level", new_level)
func on_achievement_unlocked(achievement_id: String):
# Increment property
AeThexAnalytics.increment_user_property("achievements_unlocked", 1)
# Track event
AeThexAnalytics.track_event("achievement_unlocked", {
"achievement_id": achievement_id
})
```
---
## Step 4: Track Screen Views
Monitor which screens players visit:
```gdscript
# base_screen.gd (inherit from this for all screens)
extends Control
var screen_name: String = "Unknown"
var screen_enter_time: float = 0
func _ready():
screen_enter_time = Time.get_ticks_msec() / 1000.0
track_screen_view()
func _exit_tree():
track_screen_duration()
func track_screen_view():
AeThexAnalytics.track_event("screen_view", {
"screen_name": screen_name,
"timestamp": Time.get_unix_time_from_system()
})
func track_screen_duration():
var duration = (Time.get_ticks_msec() / 1000.0) - screen_enter_time
AeThexAnalytics.track_event("screen_duration", {
"screen_name": screen_name,
"duration_seconds": duration
})
```
**Example usage:**
```gdscript
# main_menu.gd
extends "res://scripts/base_screen.gd"
func _ready():
screen_name = "main_menu"
super._ready()
```
---
## Step 5: Track Economy Events
Monitor in-game economy:
```gdscript
# economy_tracker.gd
extends Node
func currency_earned(currency_name: String, amount: int, source: String):
AeThexAnalytics.track_event("currency_earned", {
"currency": currency_name,
"amount": amount,
"source": source, # "quest", "shop", "reward", etc.
"total_balance": get_currency_balance(currency_name)
})
func currency_spent(currency_name: String, amount: int, item: String):
AeThexAnalytics.track_event("currency_spent", {
"currency": currency_name,
"amount": amount,
"item": item,
"remaining_balance": get_currency_balance(currency_name)
})
func item_purchased(item_id: String, price: int, currency: String):
AeThexAnalytics.track_event("item_purchased", {
"item_id": item_id,
"price": price,
"currency": currency,
"source": "shop" # or "chest", "reward", etc.
})
func real_money_purchase(product_id: String, price: float, currency: String):
AeThexAnalytics.track_event("iap_purchase", {
"product_id": product_id,
"price": price,
"currency": currency,
"revenue": price # Important for revenue tracking
})
```
---
## Step 6: Create Funnels
Track conversion funnels to understand player flow:
```gdscript
# funnel_tracker.gd
extends Node
# Onboarding funnel
func track_onboarding_funnel(step: String):
var funnel_steps = [
"tutorial_start",
"tutorial_complete",
"first_level_start",
"first_level_complete",
"account_created"
]
AeThexAnalytics.track_event("onboarding_funnel", {
"step": step,
"step_number": funnel_steps.find(step) + 1,
"total_steps": funnel_steps.size()
})
# Purchase funnel
func track_purchase_funnel(step: String, product_id: String = ""):
AeThexAnalytics.track_event("purchase_funnel", {
"step": step, # "view_shop", "select_item", "confirm", "complete"
"product_id": product_id
})
# Level progression funnel
func track_level_funnel(step: String, level: int):
AeThexAnalytics.track_event("level_funnel", {
"step": step, # "start", "complete", "fail", "abandon"
"level": level
})
```
**Example usage:**
```gdscript
# tutorial.gd
func _ready():
FunnelTracker.track_onboarding_funnel("tutorial_start")
func on_tutorial_complete():
FunnelTracker.track_onboarding_funnel("tutorial_complete")
# shop.gd
func _on_item_clicked(product_id):
FunnelTracker.track_purchase_funnel("select_item", product_id)
func _on_purchase_confirmed(product_id):
FunnelTracker.track_purchase_funnel("confirm", product_id)
```
---
## Step 7: Track Engagement Metrics
Measure player engagement:
```gdscript
# engagement_tracker.gd (autoload)
extends Node
var session_start_time: float = 0
var total_sessions: int = 0
var events_this_session: int = 0
func _ready():
start_session()
func start_session():
session_start_time = Time.get_ticks_msec() / 1000.0
total_sessions = load_total_sessions() + 1
save_total_sessions(total_sessions)
AeThexAnalytics.track_event("session_start", {
"session_number": total_sessions,
"days_since_install": get_days_since_install()
})
# Set user property
AeThexAnalytics.set_user_property("total_sessions", total_sessions)
func end_session():
var session_duration = (Time.get_ticks_msec() / 1000.0) - session_start_time
AeThexAnalytics.track_event("session_end", {
"duration_seconds": session_duration,
"events_tracked": events_this_session
})
func _notification(what):
if what == NOTIFICATION_WM_CLOSE_REQUEST:
end_session()
func track_engagement_event():
events_this_session += 1
func get_days_since_install() -> int:
var install_date = load_install_date()
if install_date == 0:
install_date = Time.get_unix_time_from_system()
save_install_date(install_date)
return 0
var current_time = Time.get_unix_time_from_system()
var days = (current_time - install_date) / 86400.0 # seconds in a day
return int(days)
```
---
## Step 8: Track Errors and Crashes
Automatically report errors:
```gdscript
# error_tracker.gd (autoload)
extends Node
func _ready():
# Catch unhandled errors
Engine.get_singleton("ScriptServer").add_global_constant("ErrorTracker", self)
func track_error(error_message: String, stack_trace: String = ""):
AeThexAnalytics.track_event("error_occurred", {
"error_message": error_message,
"stack_trace": stack_trace,
"platform": OS.get_name(),
"version": ProjectSettings.get_setting("application/config/version")
})
func track_crash(crash_reason: String):
AeThexAnalytics.track_event("game_crash", {
"reason": crash_reason,
"platform": OS.get_name(),
"memory_used": Performance.get_monitor(Performance.MEMORY_STATIC),
"fps": Engine.get_frames_per_second()
})
# Catch GDScript errors
func _on_error(error_message: String):
track_error(error_message, get_stack())
# Helper to get stack trace
func get_stack() -> String:
var stack = get_stack_trace()
var result = ""
for frame in stack:
result += "%s:%d in %s()\n" % [frame.source, frame.line, frame.function]
return result
func get_stack_trace() -> Array:
return [] # Implemented in debug builds
```
---
## Step 9: Performance Tracking
Track game performance metrics:
```gdscript
# performance_tracker.gd (autoload)
extends Node
const SAMPLE_INTERVAL = 5.0 # seconds
var sample_timer: Timer
func _ready():
sample_timer = Timer.new()
sample_timer.timeout.connect(_sample_performance)
sample_timer.wait_time = SAMPLE_INTERVAL
add_child(sample_timer)
sample_timer.start()
func _sample_performance():
var fps = Engine.get_frames_per_second()
var memory_mb = Performance.get_monitor(Performance.MEMORY_STATIC) / 1024.0 / 1024.0
var draw_calls = Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME)
# Track performance event
AeThexAnalytics.track_event("performance_sample", {
"fps": fps,
"memory_mb": memory_mb,
"draw_calls": draw_calls,
"scene": get_tree().current_scene.name if get_tree().current_scene else "unknown"
})
# Alert on low performance
if fps < 30:
track_low_performance(fps, memory_mb)
func track_low_performance(fps: int, memory_mb: float):
AeThexAnalytics.track_event("low_performance", {
"fps": fps,
"memory_mb": memory_mb,
"platform": OS.get_name(),
"scene": get_tree().current_scene.name if get_tree().current_scene else "unknown"
})
```
---
## Step 10: A/B Testing
Implement A/B tests to compare features:
```gdscript
# ab_testing.gd (autoload)
extends Node
var user_variant: String = ""
func _ready():
assign_variant()
func assign_variant():
# Check if user already has a variant assigned
user_variant = load_user_variant()
if user_variant.is_empty():
# Randomly assign variant (50/50 split)
user_variant = "A" if randf() < 0.5 else "B"
save_user_variant(user_variant)
# Set as user property
AeThexAnalytics.set_user_property("ab_test_variant", user_variant)
# Track assignment
AeThexAnalytics.track_event("ab_test_assigned", {
"variant": user_variant
})
func get_variant() -> String:
return user_variant
func is_variant_a() -> bool:
return user_variant == "A"
func is_variant_b() -> bool:
return user_variant == "B"
func track_conversion(goal: String):
AeThexAnalytics.track_event("ab_test_conversion", {
"variant": user_variant,
"goal": goal
})
```
**Example usage:**
```gdscript
# Configure feature based on variant
func _ready():
if ABTesting.is_variant_a():
# Show red button
$Button.modulate = Color.RED
else:
# Show blue button
$Button.modulate = Color.BLUE
func _on_button_pressed():
# Track which variant converted
ABTesting.track_conversion("button_clicked")
```
---
## Analytics Dashboard
View your analytics data:
1. **Go to:** [https://studio.aethex.io/analytics](https://studio.aethex.io/analytics)
2. **Select your project**
3. **View dashboards:**
- Overview: DAU, MAU, retention
- Events: All tracked events
- Funnels: Conversion funnels
- User Properties: Audience segments
- Errors: Crash reports
- Performance: FPS, memory, load times
---
## Best Practices
### 1. **Name Events Consistently**
```gdscript
# ✓ DO - Use snake_case
AeThexAnalytics.track_event("level_complete", {})
AeThexAnalytics.track_event("item_collected", {})
# ❌ DON'T - Inconsistent naming
AeThexAnalytics.track_event("LevelComplete", {})
AeThexAnalytics.track_event("item-collected", {})
```
### 2. **Include Context in Events**
```gdscript
# ✓ DO - Rich context
AeThexAnalytics.track_event("button_clicked", {
"button_name": "play",
"screen": "main_menu",
"session_time": get_session_time()
})
# ❌ DON'T - No context
AeThexAnalytics.track_event("button_clicked", {})
```
### 3. **Track the User Journey**
```gdscript
# Track entire player flow
AeThexAnalytics.track_event("game_started", {})
AeThexAnalytics.track_event("tutorial_started", {})
AeThexAnalytics.track_event("tutorial_completed", {})
AeThexAnalytics.track_event("first_level_started", {})
AeThexAnalytics.track_event("first_level_completed", {})
```
### 4. **Avoid PII (Personally Identifiable Information)**
```gdscript
# ❌ DON'T - Include PII
AeThexAnalytics.track_event("user_info", {
"email": "user@example.com", # Never track emails
"name": "John Doe", # Never track real names
"ip_address": "192.168.1.1" # Never track IPs
})
# ✓ DO - Use anonymous identifiers
AeThexAnalytics.track_event("user_info", {
"user_id": "anon_123456",
"player_level": 5
})
```
### 5. **Batch Events if Needed**
```gdscript
# For high-frequency events, batch them
var event_batch = []
func track_player_movement():
event_batch.append({
"event": "player_moved",
"position": player.position
})
if event_batch.size() >= 10:
flush_events()
func flush_events():
for event in event_batch:
AeThexAnalytics.track_event(event.event, event)
event_batch.clear()
```
---
## Common Metrics to Track
### Engagement Metrics:
- Daily Active Users (DAU)
- Monthly Active Users (MAU)
- Session length
- Sessions per user
- Day 1/7/30 retention
### Monetization Metrics:
- Average Revenue Per User (ARPU)
- Paying User Rate
- Lifetime Value (LTV)
- Conversion rate
### Game Metrics:
- Level completion rate
- Tutorial completion rate
- Time to first action
- Churn points
- Player progression
---
## Example: Complete Analytics Setup
```gdscript
# analytics_complete.gd (autoload)
extends Node
signal analytics_ready
var is_initialized = false
func _ready():
initialize()
func initialize():
# Wait for cloud connection
await get_tree().create_timer(1.0).timeout
if not AeThexCloud.is_connected():
await AeThexCloud.connect_to_cloud()
# Set up user properties
setup_user_properties()
# Start session tracking
start_session()
# Connect to game signals
connect_game_signals()
is_initialized = true
analytics_ready.emit()
func setup_user_properties():
AeThexAnalytics.set_user_property("platform", OS.get_name())
AeThexAnalytics.set_user_property("game_version", get_game_version())
AeThexAnalytics.set_user_property("install_date", get_install_date())
func start_session():
AeThexAnalytics.track_event("session_start", {
"platform": OS.get_name(),
"version": get_game_version()
})
func connect_game_signals():
# Connect to various game events
var game = get_tree().root.get_node("Game")
if game:
game.level_completed.connect(on_level_completed)
game.player_died.connect(on_player_died)
game.item_collected.connect(on_item_collected)
func on_level_completed(level: int, score: int):
AeThexAnalytics.track_event("level_complete", {
"level": level,
"score": score
})
func on_player_died(level: int, cause: String):
AeThexAnalytics.track_event("player_died", {
"level": level,
"cause": cause
})
func on_item_collected(item: String):
AeThexAnalytics.track_event("item_collected", {
"item": item
})
func get_game_version() -> String:
return ProjectSettings.get_setting("application/config/version", "1.0.0")
func get_install_date() -> int:
# Load from save or set to current time
var save_file = FileAccess.open("user://install_date.save", FileAccess.READ)
if save_file:
var date = save_file.get_64()
save_file.close()
return date
else:
var date = Time.get_unix_time_from_system()
save_file = FileAccess.open("user://install_date.save", FileAccess.WRITE)
save_file.store_64(date)
save_file.close()
return date
```
---
## Testing Analytics
Test your analytics implementation:
```gdscript
# test_analytics.gd
extends Node
func _ready():
test_analytics()
func test_analytics():
print("Testing analytics...")
# Test basic event
AeThexAnalytics.track_event("test_event", {
"test": true
})
# Test user property
AeThexAnalytics.set_user_property("test_property", "test_value")
# Check console for confirmation
print("Analytics test complete. Check dashboard for events.")
```
---
## Next Steps
- **[Publishing Guide](../PUBLISHING_GUIDE.md)** - Deploy your game with analytics
- **[API Reference](../API_REFERENCE.md#aethexanalytics-singleton)** - Complete analytics API
- **Dashboard:** View your data at [studio.aethex.io/analytics](https://studio.aethex.io/analytics)
---
## Summary
You've learned how to:
✅ Track custom events
✅ Set user properties
✅ Create conversion funnels
✅ Monitor performance
✅ Track errors and crashes
✅ Implement A/B testing
✅ Measure engagement metrics
Analytics gives you the insights to make your game better and understand your players!
**Ready to publish?** Check out the [Publishing Guide](../PUBLISHING_GUIDE.md)! 🚀

View file

@ -0,0 +1,792 @@
# Authentication Tutorial
Learn how to add user authentication to your AeThex game with email/password, OAuth, and guest login support.
---
## What You'll Build
A complete authentication system with:
- Email/password registration and login
- OAuth login (Google, GitHub, Discord)
- Guest accounts
- User profiles
- Session management
**Time:** 30 minutes
**Difficulty:** Beginner
**Prerequisites:** Basic GDScript knowledge
---
## Why Add Authentication?
Authentication enables:
- **Cloud saves** - Save progress across devices
- **Multiplayer** - Identify players in matches
- **Social features** - Friends, leaderboards, chat
- **Analytics** - Track user behavior
- **Monetization** - In-app purchases, subscriptions
---
## Step 1: Connect to AeThex Cloud
First, ensure cloud services are connected:
```gdscript
# main.gd
extends Node
func _ready():
# Connect to AeThex Cloud
var result = await AeThexCloud.connect_to_cloud()
if result.success:
print("Connected to AeThex Cloud")
check_existing_session()
else:
print("Failed to connect: ", result.error)
show_error("Could not connect to servers")
func check_existing_session():
if AeThexAuth.is_logged_in():
var user = AeThexAuth.get_current_user()
print("Welcome back, ", user.display_name)
go_to_main_menu()
else:
show_login_screen()
```
---
## Step 2: Create Login UI
Create a login screen with options for different auth methods:
```gdscript
# login_screen.gd
extends Control
@onready var email_field = $VBox/EmailField
@onready var password_field = $VBox/PasswordField
@onready var login_btn = $VBox/LoginButton
@onready var register_btn = $VBox/RegisterButton
@onready var guest_btn = $VBox/GuestButton
@onready var google_btn = $VBox/OAuthButtons/GoogleButton
@onready var github_btn = $VBox/OAuthButtons/GitHubButton
@onready var discord_btn = $VBox/OAuthButtons/DiscordButton
@onready var status_label = $VBox/StatusLabel
func _ready():
login_btn.pressed.connect(_on_login_pressed)
register_btn.pressed.connect(_on_register_pressed)
guest_btn.pressed.connect(_on_guest_pressed)
google_btn.pressed.connect(_on_oauth_pressed.bind("google"))
github_btn.pressed.connect(_on_oauth_pressed.bind("github"))
discord_btn.pressed.connect(_on_oauth_pressed.bind("discord"))
func _on_login_pressed():
var email = email_field.text
var password = password_field.text
if not validate_email(email):
show_status("Invalid email address", Color.RED)
return
if password.length() < 6:
show_status("Password must be at least 6 characters", Color.RED)
return
show_status("Logging in...", Color.YELLOW)
set_buttons_enabled(false)
var result = await AeThexAuth.login_email(email, password)
set_buttons_enabled(true)
if result.success:
show_status("Login successful!", Color.GREEN)
on_login_success()
else:
show_status("Login failed: " + result.error, Color.RED)
func _on_register_pressed():
var email = email_field.text
var password = password_field.text
if not validate_email(email):
show_status("Invalid email address", Color.RED)
return
if password.length() < 6:
show_status("Password must be at least 6 characters", Color.RED)
return
show_status("Creating account...", Color.YELLOW)
set_buttons_enabled(false)
var result = await AeThexAuth.register_email(email, password)
set_buttons_enabled(true)
if result.success:
show_status("Account created! Logging in...", Color.GREEN)
on_login_success()
else:
show_status("Registration failed: " + result.error, Color.RED)
func _on_guest_pressed():
show_status("Creating guest account...", Color.YELLOW)
set_buttons_enabled(false)
var result = await AeThexAuth.login_as_guest()
set_buttons_enabled(true)
if result.success:
show_status("Logged in as guest!", Color.GREEN)
on_login_success()
else:
show_status("Guest login failed: " + result.error, Color.RED)
func _on_oauth_pressed(provider: String):
show_status("Opening " + provider + " login...", Color.YELLOW)
set_buttons_enabled(false)
var result = await AeThexAuth.login_oauth(provider)
set_buttons_enabled(true)
if result.success:
show_status("Logged in with " + provider + "!", Color.GREEN)
on_login_success()
else:
show_status("OAuth login failed: " + result.error, Color.RED)
func validate_email(email: String) -> bool:
var regex = RegEx.new()
regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
return regex.search(email) != null
func show_status(message: String, color: Color):
status_label.text = message
status_label.modulate = color
func set_buttons_enabled(enabled: bool):
login_btn.disabled = !enabled
register_btn.disabled = !enabled
guest_btn.disabled = !enabled
google_btn.disabled = !enabled
github_btn.disabled = !enabled
discord_btn.disabled = !enabled
func on_login_success():
# Transition to main menu
await get_tree().create_timer(1.0).timeout
get_tree().change_scene_to_file("res://scenes/main_menu.tscn")
```
---
## Step 3: Create Registration Flow
Separate registration screen with additional fields:
```gdscript
# registration_screen.gd
extends Control
@onready var email_field = $VBox/EmailField
@onready var password_field = $VBox/PasswordField
@onready var confirm_password_field = $VBox/ConfirmPasswordField
@onready var display_name_field = $VBox/DisplayNameField
@onready var terms_checkbox = $VBox/TermsCheckbox
@onready var register_btn = $VBox/RegisterButton
@onready var back_btn = $VBox/BackButton
func _ready():
register_btn.pressed.connect(_on_register_pressed)
back_btn.pressed.connect(_on_back_pressed)
func _on_register_pressed():
# Validation
if not validate_input():
return
var email = email_field.text
var password = password_field.text
var display_name = display_name_field.text
register_btn.disabled = true
# Register with additional profile data
var result = await AeThexAuth.register_email(email, password, {
"display_name": display_name
})
register_btn.disabled = false
if result.success:
# Send verification email (optional)
await AeThexAuth.send_verification_email()
show_success("Account created! Check your email to verify.")
else:
show_error("Registration failed: " + result.error)
func validate_input() -> bool:
# Check email
if not validate_email(email_field.text):
show_error("Invalid email address")
return false
# Check password
if password_field.text.length() < 6:
show_error("Password must be at least 6 characters")
return false
# Check password match
if password_field.text != confirm_password_field.text:
show_error("Passwords do not match")
return false
# Check display name
if display_name_field.text.strip_edges().is_empty():
show_error("Display name is required")
return false
# Check terms
if not terms_checkbox.button_pressed:
show_error("You must accept the terms of service")
return false
return true
func validate_email(email: String) -> bool:
var regex = RegEx.new()
regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
return regex.search(email) != null
func _on_back_pressed():
get_tree().change_scene_to_file("res://scenes/login_screen.tscn")
```
---
## Step 4: Handle Authentication State
Create a global authentication manager:
```gdscript
# autoload: auth_manager.gd
extends Node
signal login_changed(is_logged_in: bool)
signal user_profile_updated(profile: Dictionary)
var current_user: Dictionary = {}
func _ready():
# Listen for auth state changes
AeThexAuth.login_state_changed.connect(_on_login_state_changed)
func _on_login_state_changed(is_logged_in: bool):
login_changed.emit(is_logged_in)
if is_logged_in:
current_user = AeThexAuth.get_current_user()
print("User logged in: ", current_user.display_name)
else:
current_user = {}
print("User logged out")
func is_logged_in() -> bool:
return AeThexAuth.is_logged_in()
func get_user_id() -> String:
return current_user.get("user_id", "")
func get_display_name() -> String:
return current_user.get("display_name", "Guest")
func get_email() -> String:
return current_user.get("email", "")
func is_guest() -> bool:
return current_user.get("is_guest", false)
func logout():
await AeThexAuth.logout()
get_tree().change_scene_to_file("res://scenes/login_screen.tscn")
```
**Add to Project Settings:**
```
Project → Project Settings → Autoload
Name: AuthManager
Path: res://scripts/auth_manager.gd
```
---
## Step 5: User Profiles
Display and edit user profiles:
```gdscript
# profile_screen.gd
extends Control
@onready var avatar_texture = $VBox/Avatar
@onready var display_name_label = $VBox/DisplayName
@onready var email_label = $VBox/Email
@onready var user_id_label = $VBox/UserID
@onready var edit_btn = $VBox/EditButton
@onready var logout_btn = $VBox/LogoutButton
func _ready():
edit_btn.pressed.connect(_on_edit_pressed)
logout_btn.pressed.connect(_on_logout_pressed)
load_profile()
func load_profile():
var user = AeThexAuth.get_current_user()
display_name_label.text = user.get("display_name", "Unknown")
email_label.text = user.get("email", "No email")
user_id_label.text = "ID: " + user.get("user_id", "unknown")
# Load avatar if available
if "avatar_url" in user:
load_avatar(user.avatar_url)
func load_avatar(url: String):
var http = HTTPRequest.new()
add_child(http)
http.request_completed.connect(_on_avatar_loaded)
http.request(url)
func _on_avatar_loaded(result, response_code, headers, body):
if response_code == 200:
var image = Image.new()
var error = image.load_png_from_buffer(body)
if error == OK:
avatar_texture.texture = ImageTexture.create_from_image(image)
func _on_edit_pressed():
get_tree().change_scene_to_file("res://scenes/edit_profile.tscn")
func _on_logout_pressed():
AuthManager.logout()
```
---
## Step 6: Edit Profile
Allow users to update their profile:
```gdscript
# edit_profile.gd
extends Control
@onready var display_name_field = $VBox/DisplayNameField
@onready var bio_field = $VBox/BioField
@onready var save_btn = $VBox/SaveButton
@onready var cancel_btn = $VBox/CancelButton
var original_profile: Dictionary
func _ready():
save_btn.pressed.connect(_on_save_pressed)
cancel_btn.pressed.connect(_on_cancel_pressed)
load_current_profile()
func load_current_profile():
original_profile = AeThexAuth.get_current_user()
display_name_field.text = original_profile.get("display_name", "")
bio_field.text = original_profile.get("bio", "")
func _on_save_pressed():
var new_profile = {
"display_name": display_name_field.text,
"bio": bio_field.text
}
save_btn.disabled = true
var result = await AeThexAuth.update_profile(new_profile)
save_btn.disabled = false
if result.success:
AuthManager.user_profile_updated.emit(new_profile)
show_success("Profile updated!")
await get_tree().create_timer(1.0).timeout
go_back()
else:
show_error("Failed to update profile: " + result.error)
func _on_cancel_pressed():
go_back()
func go_back():
get_tree().change_scene_to_file("res://scenes/profile_screen.tscn")
```
---
## Step 7: Password Reset
Implement password reset functionality:
```gdscript
# forgot_password.gd
extends Control
@onready var email_field = $VBox/EmailField
@onready var send_btn = $VBox/SendButton
@onready var back_btn = $VBox/BackButton
@onready var status_label = $VBox/StatusLabel
func _ready():
send_btn.pressed.connect(_on_send_pressed)
back_btn.pressed.connect(_on_back_pressed)
func _on_send_pressed():
var email = email_field.text
if not validate_email(email):
show_status("Invalid email address", Color.RED)
return
send_btn.disabled = true
show_status("Sending reset email...", Color.YELLOW)
var result = await AeThexAuth.send_password_reset_email(email)
send_btn.disabled = false
if result.success:
show_status("Reset email sent! Check your inbox.", Color.GREEN)
else:
show_status("Failed: " + result.error, Color.RED)
func validate_email(email: String) -> bool:
var regex = RegEx.new()
regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
return regex.search(email) != null
func _on_back_pressed():
get_tree().change_scene_to_file("res://scenes/login_screen.tscn")
```
---
## Step 8: Session Management
Handle session timeouts and refresh:
```gdscript
# session_manager.gd (autoload)
extends Node
const SESSION_CHECK_INTERVAL = 300 # 5 minutes
const SESSION_TIMEOUT = 3600 # 1 hour
var session_timer: Timer
func _ready():
session_timer = Timer.new()
session_timer.timeout.connect(_check_session)
session_timer.wait_time = SESSION_CHECK_INTERVAL
add_child(session_timer)
if AuthManager.is_logged_in():
session_timer.start()
func _check_session():
if not AeThexAuth.is_logged_in():
session_expired()
return
# Refresh session token
var result = await AeThexAuth.refresh_session()
if not result.success:
session_expired()
func session_expired():
session_timer.stop()
show_session_expired_dialog()
func show_session_expired_dialog():
var dialog = AcceptDialog.new()
dialog.dialog_text = "Your session has expired. Please log in again."
dialog.confirmed.connect(func(): get_tree().change_scene_to_file("res://scenes/login_screen.tscn"))
get_tree().root.add_child(dialog)
dialog.popup_centered()
```
---
## Step 9: Account Linking
Allow users to link multiple auth providers:
```gdscript
# account_linking.gd
extends Control
@onready var linked_providers = $VBox/LinkedProviders
@onready var available_providers = $VBox/AvailableProviders
func _ready():
load_linked_providers()
load_available_providers()
func load_linked_providers():
var user = AeThexAuth.get_current_user()
var providers = user.get("linked_providers", [])
for provider in providers:
var label = Label.new()
label.text = "✓ " + provider.capitalize() + " (linked)"
label.modulate = Color.GREEN
linked_providers.add_child(label)
var unlink_btn = Button.new()
unlink_btn.text = "Unlink"
unlink_btn.pressed.connect(_on_unlink_provider.bind(provider))
linked_providers.add_child(unlink_btn)
func load_available_providers():
var all_providers = ["google", "github", "discord", "twitter"]
var user = AeThexAuth.get_current_user()
var linked = user.get("linked_providers", [])
for provider in all_providers:
if provider not in linked:
var btn = Button.new()
btn.text = "Link " + provider.capitalize()
btn.pressed.connect(_on_link_provider.bind(provider))
available_providers.add_child(btn)
func _on_link_provider(provider: String):
var result = await AeThexAuth.link_oauth_provider(provider)
if result.success:
show_success("Linked " + provider.capitalize())
reload_ui()
else:
show_error("Failed to link: " + result.error)
func _on_unlink_provider(provider: String):
var result = await AeThexAuth.unlink_provider(provider)
if result.success:
show_success("Unlinked " + provider.capitalize())
reload_ui()
else:
show_error("Failed to unlink: " + result.error)
func reload_ui():
# Clear and reload
for child in linked_providers.get_children():
child.queue_free()
for child in available_providers.get_children():
child.queue_free()
await get_tree().process_frame
load_linked_providers()
load_available_providers()
```
---
## Step 10: Guest Account Conversion
Allow guests to create permanent accounts:
```gdscript
# guest_conversion.gd
extends Control
@onready var email_field = $VBox/EmailField
@onready var password_field = $VBox/PasswordField
@onready var convert_btn = $VBox/ConvertButton
func _ready():
convert_btn.pressed.connect(_on_convert_pressed)
# Only show if user is guest
if not AuthManager.is_guest():
queue_free()
func _on_convert_pressed():
var email = email_field.text
var password = password_field.text
if not validate_input(email, password):
return
convert_btn.disabled = true
var result = await AeThexAuth.convert_guest_to_account(email, password)
convert_btn.disabled = false
if result.success:
show_success("Account created! Your progress is saved.")
await get_tree().create_timer(2.0).timeout
get_tree().change_scene_to_file("res://scenes/main_menu.tscn")
else:
show_error("Conversion failed: " + result.error)
func validate_input(email: String, password: String) -> bool:
if not validate_email(email):
show_error("Invalid email address")
return false
if password.length() < 6:
show_error("Password must be at least 6 characters")
return false
return true
func validate_email(email: String) -> bool:
var regex = RegEx.new()
regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
return regex.search(email) != null
```
---
## Security Best Practices
### 1. **Never Store Passwords**
```gdscript
# ❌ DON'T
var user_password = "secret123" # Never store passwords!
# ✓ DO
# Let AeThexAuth handle authentication securely
```
### 2. **Validate Input**
```gdscript
func validate_password(password: String) -> bool:
if password.length() < 8:
return false
# Check for numbers
var has_number = false
var has_letter = false
for c in password:
if c.is_valid_int():
has_number = true
if c.to_lower() >= 'a' and c.to_lower() <= 'z':
has_letter = true
return has_number and has_letter
```
### 3. **Use HTTPS Only**
```gdscript
# AeThex automatically uses HTTPS
# Never disable SSL verification in production
```
### 4. **Handle Tokens Securely**
```gdscript
# Auth tokens are automatically managed by AeThexAuth
# Don't try to extract or store them manually
```
### 5. **Rate Limiting**
```gdscript
var login_attempts = 0
var last_attempt_time = 0
func attempt_login():
var now = Time.get_ticks_msec() / 1000.0
if now - last_attempt_time > 60:
login_attempts = 0
if login_attempts >= 5:
show_error("Too many attempts. Try again later.")
return
login_attempts += 1
last_attempt_time = now
# Proceed with login
```
---
## Testing
Test authentication flows:
```gdscript
# test_auth.gd
extends Node
func _ready():
run_tests()
func run_tests():
print("Testing authentication...")
# Test guest login
await test_guest_login()
await AeThexAuth.logout()
# Test email registration
await test_email_registration()
await AeThexAuth.logout()
print("All auth tests passed!")
func test_guest_login():
var result = await AeThexAuth.login_as_guest()
assert(result.success, "Guest login failed")
assert(AeThexAuth.is_logged_in(), "Not logged in after guest login")
print("✓ Guest login works")
func test_email_registration():
var test_email = "test%d@example.com" % Time.get_ticks_msec()
var test_password = "Test123456"
var result = await AeThexAuth.register_email(test_email, test_password)
assert(result.success, "Registration failed")
assert(AeThexAuth.is_logged_in(), "Not logged in after registration")
print("✓ Email registration works")
```
---
## Next Steps
- **[Cloud Saves](FIRST_GAME_TUTORIAL.md#cloud-saves)** - Save user data
- **[Analytics Tutorial](ANALYTICS_TUTORIAL.md)** - Track user behavior
- **[API Reference](../API_REFERENCE.md#aethexauth-singleton)** - Complete auth API docs
---
## Summary
You've learned how to:
✅ Implement email/password authentication
✅ Add OAuth login (Google, GitHub, Discord)
✅ Support guest accounts
✅ Manage user profiles
✅ Handle password resets
✅ Link multiple auth providers
✅ Convert guest accounts to permanent accounts
Authentication is the foundation for cloud features, social systems, and user engagement!
**Ready to save user data?** Check out [Cloud Saves](FIRST_GAME_TUTORIAL.md#cloud-saves)! 💾