From d13c2cdfdc1e950eb4300aa14c1a04fb2614820d Mon Sep 17 00:00:00 2001 From: MrPiglr Date: Wed, 25 Feb 2026 02:05:52 +0000 Subject: [PATCH] new file: docs/EXPORTING_GAMES.md --- docs/API_REFERENCE.md | 2 +- docs/EXPORTING_GAMES.md | 896 ++++++++++++++++++++++++ docs/GAME_DEVELOPMENT.md | 724 +++++++++++++++++++ docs/PUBLISHING_GUIDE.md | 806 +++++++++++++++++++++ docs/README.md | 57 +- docs/_sidebar.md | 41 +- docs/index.html | 19 +- docs/theme.css | 527 ++++++++++++++ docs/tutorials/AI_ASSISTANT_TUTORIAL.md | 632 +++++++++++++++++ docs/tutorials/ANALYTICS_TUTORIAL.md | 752 ++++++++++++++++++++ docs/tutorials/AUTH_TUTORIAL.md | 792 +++++++++++++++++++++ 11 files changed, 5190 insertions(+), 58 deletions(-) create mode 100644 docs/EXPORTING_GAMES.md create mode 100644 docs/GAME_DEVELOPMENT.md create mode 100644 docs/PUBLISHING_GUIDE.md create mode 100644 docs/theme.css create mode 100644 docs/tutorials/AI_ASSISTANT_TUTORIAL.md create mode 100644 docs/tutorials/ANALYTICS_TUTORIAL.md create mode 100644 docs/tutorials/AUTH_TUTORIAL.md diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index 242f022b..52bcd74c 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -714,5 +714,5 @@ func _physics_process(delta): - [Getting Started Guide](../GETTING_STARTED.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) diff --git a/docs/EXPORTING_GAMES.md b/docs/EXPORTING_GAMES.md new file mode 100644 index 00000000..e19545b4 --- /dev/null +++ b/docs/EXPORTING_GAMES.md @@ -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 + + +
Loading Your Game...
+``` + +**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! 🚀 diff --git a/docs/GAME_DEVELOPMENT.md b/docs/GAME_DEVELOPMENT.md new file mode 100644 index 00000000..7504eb63 --- /dev/null +++ b/docs/GAME_DEVELOPMENT.md @@ -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) diff --git a/docs/PUBLISHING_GUIDE.md b/docs/PUBLISHING_GUIDE.md new file mode 100644 index 00000000..18cdf586 --- /dev/null +++ b/docs/PUBLISHING_GUIDE.md @@ -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!** 🚀 diff --git a/docs/README.md b/docs/README.md index f4cea079..9c6bb3ed 100644 --- a/docs/README.md +++ b/docs/README.md @@ -139,52 +139,29 @@ Welcome to AeThex Engine - the cloud-first game engine that makes multiplayer, c ### Game Development -- **Basics:** - - [GDScript Basics](GDSCRIPT_BASICS.md) - - [First Game Tutorial](tutorials/FIRST_GAME_TUTORIAL.md) - - Scene system - - Node hierarchy - - Signals and callbacks +**→ [Complete Game Development Guide](GAME_DEVELOPMENT.md)** -- **Physics:** - - RigidBody2D/3D - - StaticBody2D/3D - - Collision shapes - - Physics layers +Learn all core engine concepts: -- **UI:** - - Control nodes - - Layouts and containers - - Themes - - Responsive design +- **[Scene System](GAME_DEVELOPMENT.md#scene-system)** - Building blocks of your game +- **[Node Hierarchy](GAME_DEVELOPMENT.md#node-hierarchy)** - Organizing game objects +- **[Signals & Callbacks](GAME_DEVELOPMENT.md#signals-and-callbacks)** - Event-driven programming +- **[Physics System](GAME_DEVELOPMENT.md#physics)** - 2D/3D physics, collisions, layers +- **[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:** - - AudioStreamPlayer - - Music management - - Sound effects - - 3D audio +### Platform Export -### Workflow +**→ [Complete Export Guide](EXPORTING_GAMES.md)** -- **Studio IDE:** - - [Studio Integration](STUDIO_INTEGRATION.md) - - Code editor - - Scene tree - - Asset browser - - Live reload +Export to all platforms: -- **Version Control:** - - Git integration - - Collaboration - - Branching strategy - - Merge conflicts - -- **Export:** - - Windows export - - Linux export - - macOS export - - Web (HTML5) export - - Android export +- **[Windows Export](EXPORTING_GAMES.md#windows-export)** - Desktop Windows builds, code signing, distribution +- **[Linux Export](EXPORTING_GAMES.md#linux-export)** - Linux builds, AppImage, Flatpak, Snap +- **[macOS Export](EXPORTING_GAMES.md#macos-export)** - macOS builds, notarization, App Store +- **[Web Export](EXPORTING_GAMES.md#web-html5-export)** - HTML5/WebAssembly, PWA, hosting +- **[Android Export](EXPORTING_GAMES.md#android-export)** - APK/AAB builds, Play Store, optimization --- diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 92596694..8b7381cb 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -7,24 +7,53 @@ * [GDScript Basics](GDSCRIPT_BASICS.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 * [Tutorial Index](tutorials/README.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 * [Complete API Reference](API_REFERENCE.md) - * AeThexCloud - * AeThexAuth - * AeThexSaves - * AeThexMultiplayer - * AeThexAnalytics - * AeThexAI + * [AeThexCloud](API_REFERENCE.md#aethexcloud-singleton) + * [AeThexAuth](API_REFERENCE.md#aethexauth-singleton) + * [AeThexSaves](API_REFERENCE.md#aethexsaves-singleton) + * [AeThexMultiplayer](API_REFERENCE.md#aethexmultiplayer-singleton) + * [AeThexAnalytics](API_REFERENCE.md#aethexanalytics-singleton) + * [AeThexAI](API_REFERENCE.md#aethexai-singleton) + * [AeThexStudio](API_REFERENCE.md#aethexstudio-singleton) * Architecture * [Architecture Overview](ARCHITECTURE_OVERVIEW.md) * [Cloud Services](CLOUD_SERVICES_ARCHITECTURE.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 * [Building from Source](BUILDING_WINDOWS.md) * [Studio Bridge Guide](STUDIO_BRIDGE_GUIDE.md) diff --git a/docs/index.html b/docs/index.html index b0b15069..308279c9 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,17 +6,14 @@ - - + + + + + + + +
Loading...
diff --git a/docs/theme.css b/docs/theme.css new file mode 100644 index 00000000..00c9b314 --- /dev/null +++ b/docs/theme.css @@ -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; } diff --git a/docs/tutorials/AI_ASSISTANT_TUTORIAL.md b/docs/tutorials/AI_ASSISTANT_TUTORIAL.md new file mode 100644 index 00000000..2110a4ce --- /dev/null +++ b/docs/tutorials/AI_ASSISTANT_TUTORIAL.md @@ -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! 🤖 diff --git a/docs/tutorials/ANALYTICS_TUTORIAL.md b/docs/tutorials/ANALYTICS_TUTORIAL.md new file mode 100644 index 00000000..c9ec3f75 --- /dev/null +++ b/docs/tutorials/ANALYTICS_TUTORIAL.md @@ -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)! 🚀 diff --git a/docs/tutorials/AUTH_TUTORIAL.md b/docs/tutorials/AUTH_TUTORIAL.md new file mode 100644 index 00000000..0f46c1ff --- /dev/null +++ b/docs/tutorials/AUTH_TUTORIAL.md @@ -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)! 💾