# Your First AeThex Game - Multiplayer Pong Build a simple multiplayer Pong game in 30 minutes. Learn core AeThex features: multiplayer, cloud saves, and the Studio IDE. **What you'll build:** Real-time multiplayer Pong where 2 players compete, with automatic cloud save of high scores. **Time required:** 30 minutes **Difficulty:** Beginner --- ## Prerequisites - AeThex Engine installed - Basic programming knowledge (see [GDScript Basics](../GDSCRIPT_BASICS.md) if needed) - Studio IDE running at http://localhost:9002/ide --- ## Part 1: Project Setup (5 min) ### 1. Create New Project In Studio IDE: 1. Click **File → New Project** 2. Name: `MultiplayerPong` 3. Location: Choose your projects folder 4. Click **Create & Open** ### 2. Create Scene Structure In the Scene Tree: 1. Create **Node2D** (root) - name it `Game` 2. Add children: - **ColorRect** (background) - name it `Background` - **RigidBody2D** - name it `Ball` - **StaticBody2D** - name it `Paddle1` - **StaticBody2D** - name it `Paddle2` - **CanvasLayer** - name it `UI` Your scene tree should look like: ``` Game (Node2D) ├── Background (ColorRect) ├── Ball (RigidBody2D) ├── Paddle1 (StaticBody2D) ├── Paddle2 (StaticBody2D) └── UI (CanvasLayer) ``` --- ## Part 2: Create the Ball (5 min) ### 1. Add Ball Components Select **Ball** node, add children: - **Sprite2D** - name it `Sprite` - **CollisionShape2D** - name it `Collision` ### 2. Configure Ball **Sprite2D:** - In Inspector: Texture → Create new `CircleTexture2D` - Set Radius: 16 **CollisionShape2D:** - Shape → Create new `CircleShape2D` - Radius: 16 **Ball (RigidBody2D):** - Gravity Scale: 0 (no gravity) - Lock Rotation: ON ### 3. Ball Script Right-click **Ball** → Attach Script: ```gdscript extends RigidBody2D const INITIAL_SPEED = 400 func _ready(): # Start ball moving in random direction var direction = Vector2(randf_range(-1, 1), randf_range(-0.5, 0.5)).normalized() linear_velocity = direction * INITIAL_SPEED func _on_body_entered(body): # Increase speed slightly on each hit linear_velocity *= 1.05 # Keep vertical speed reasonable if abs(linear_velocity.y) < 100: linear_velocity.y = sign(linear_velocity.y) * 100 ``` Connect the `body_entered` signal: - Inspector → Node → Signals - Double-click `body_entered` - Connect to `_on_body_entered` --- ## Part 3: Create Paddles (7 min) ### 1. Setup Paddle1 Select **Paddle1**, add children: - **ColorRect** - name it `Visual` - **CollisionShape2D** - name it `Collision` **ColorRect:** - Size: 20 x 100 - Position: (50, 300) - Color: White **CollisionShape2D:** - Create new `RectangleShape2D` - Size: 20 x 100 **Paddle1 position:** (50, 300) ### 2. Paddle1 Script Right-click **Paddle1** → Attach Script: ```gdscript extends StaticBody2D const SPEED = 400 func _physics_process(delta): var input = Input.get_axis("ui_up", "ui_down") position.y += input * SPEED * delta # Keep paddle on screen position.y = clamp(position.y, 50, 550) ``` ### 3. Setup Paddle2 Select **Paddle2**, add same components as Paddle1: - **ColorRect** - name it `Visual` - **CollisionShape2D** - name it `Collision` **ColorRect:** - Size: 20 x 100 - Position: (1130, 300) - Color: White **CollisionShape2D:** - RectangleShape2D - Size: 20 x 100 **Paddle2 position:** (1130, 300) ### 4. Paddle2 Script Right-click **Paddle2** → Attach Script: ```gdscript extends StaticBody2D const SPEED = 400 func _physics_process(delta): # Control with W/S keys var input = 0 if Input.is_action_pressed("ui_up"): # We'll add this input -= 1 if Input.is_action_pressed("ui_down"): input += 1 position.y += input * SPEED * delta position.y = clamp(position.y, 50, 550) ``` --- ## Part 4: Add Walls & UI (5 min) ### 1. Create Walls Create 2 **StaticBody2D** nodes as children of `Game`: - **TopWall** - **BottomWall** **TopWall:** - Position: (600, 0) - Add **CollisionShape2D** → RectangleShape2D - Size: 1200 x 20 **BottomWall:** - Position: (600, 600) - Add **CollisionShape2D** → RectangleShape2D - Size: 1200 x 20 ### 2. Create Score UI Select **UI** (CanvasLayer), add children: - **Label** - name it `Player1Score` - **Label** - name it `Player2Score` - **Label** - name it `RoomCode` **Player1Score:** - Text: "0" - Position: (400, 50) - Font Size: 48 **Player2Score:** - Text: "0" - Position: (800, 50) - Font Size: 48 **RoomCode:** - Text: "Room: ----" - Position: (500, 10) - Font Size: 24 --- ## Part 5: Add Multiplayer (THE MAGIC - 3 Lines!) ### 1. Update Game Script Select **Game** root node → Attach Script: ```gdscript extends Node2D var score_p1 = 0 var score_p2 = 0 func _ready(): # MULTIPLAYER IN 3 LINES! 🎮 AeThexMultiplayer.create_room("pong-" + str(randi() % 10000)) AeThexMultiplayer.connect("player_joined", _on_player_joined) # Update UI with room code var code = AeThexMultiplayer.get_room_code() $UI/RoomCode.text = "Room: " + code print("Share this code with friend: ", code) func _on_player_joined(player_id): print("Player ", player_id, " joined!") # Assign paddle control if AeThexMultiplayer.get_players().size() == 1: $Paddle1.set_multiplayer_authority(player_id) else: $Paddle2.set_multiplayer_authority(player_id) func _on_ball_out_left(): score_p2 += 1 $UI/Player2Score.text = str(score_p2) reset_ball() func _on_ball_out_right(): score_p1 += 1 $UI/Player1Score.text = str(score_p1) reset_ball() func reset_ball(): $Ball.position = Vector2(600, 300) $Ball.linear_velocity = Vector2.ZERO await get_tree().create_timer(1.0).timeout var direction = Vector2(randf_range(-1, 1), randf_range(-0.5, 0.5)).normalized() $Ball.linear_velocity = direction * 400 ``` ### 2. Create Goal Areas Add 2 **Area2D** nodes as children of `Game`: - **GoalLeft** - **GoalRight** **GoalLeft:** - Position: (-20, 300) - Add **CollisionShape2D** → RectangleShape2D - Size: 40 x 600 **GoalRight:** - Position: (1220, 300) - Add **CollisionShape2D** → RectangleShape2D - Size: 40 x 600 Connect `body_entered` signals: - **GoalLeft** → `Game._on_ball_out_left` - **GoalRight** → `Game._on_ball_out_right` --- ## Part 6: Add Cloud Saves (5 min) Save high scores to cloud automatically! ### Update Game Script Add to `game.gd`: ```gdscript var high_score_p1 = 0 var high_score_p2 = 0 func _ready(): # ... existing multiplayer code ... # Load saved high scores load_high_scores() func load_high_scores(): var save_data = await AeThexSaves.load_game("high_scores") if save_data: high_score_p1 = save_data.get("p1", 0) high_score_p2 = save_data.get("p2", 0) print("Loaded high scores: P1=", high_score_p1, " P2=", high_score_p2) func save_high_scores(): var save_data = { "p1": max(score_p1, high_score_p1), "p2": max(score_p2, high_score_p2), "last_played": Time.get_unix_time_from_system() } if AeThexSaves.save_game("high_scores", save_data): print("High scores saved to cloud!") func _on_ball_out_left(): score_p2 += 1 $UI/Player2Score.text = str(score_p2) # Update high score if score_p2 > high_score_p2: high_score_p2 = score_p2 save_high_scores() reset_ball() func _on_ball_out_right(): score_p1 += 1 $UI/Player1Score.text = str(score_p1) if score_p1 > high_score_p1: high_score_p1 = score_p1 save_high_scores() reset_ball() ``` --- ## Part 7: Test Your Game! (5 min) ### 1. Run the Game Click **Play** button in Studio (or press F5) ### 2. Get Room Code Look in the console output for: ``` Share this code with friend: ABCD-1234 ``` ### 3. Test Multiplayer **Option A: Two Instances** 1. Run game twice (different terminals) 2. Second instance joins with room code **Option B: Share with Friend** 1. Give friend the room code 2. They run AeThex and join your room ### 4. Play! - **Player 1:** Arrow keys - **Player 2:** W/S keys - Score goals to test cloud saves! --- ## What You Just Built ✅ **Real-time multiplayer** - 3 lines of code! ✅ **Cloud saves** - High scores sync automatically ✅ **Room codes** - Easy friend invites ✅ **Cross-platform** - Works on all devices Compare to traditional approach: - **Godot alone:** 500+ lines for networking, server setup, NAT traversal - **With AeThex:** 3 lines for multiplayer, 5 lines for cloud saves --- ## Next Steps ### Improve Your Game **Add effects:** ```gdscript # In ball script func _on_body_entered(body): # Play sound $HitSound.play() # Screen shake get_tree().root.get_camera_2d().shake(0.1) ``` **Add power-ups:** ```gdscript # Random power-up var powerup_scene = preload("res://powerup.tscn") var powerup = powerup_scene.instantiate() powerup.position = Vector2(randf_range(100, 1100), randf_range(100, 500)) add_child(powerup) ``` **Add particle effects:** - Add `GPUParticles2D` to ball - Emit trail when moving ### Learn More Features - **[AI Assistant Tutorial](AI_ASSISTANT_TUTORIAL.md)** - Get coding help in-game - **[Authentication Tutorial](AUTH_TUTORIAL.md)** - Add login system - **[Analytics Tutorial](ANALYTICS_TUTORIAL.md)** - Track player behavior - **[Publishing Guide](../PUBLISHING_GUIDE.md)** - Deploy your game --- ## Troubleshooting **Ball goes through paddle:** - Check CollisionShape2D is child of paddle - Verify RigidBody2D collision layers **Multiplayer not working:** - Check internet connection - Verify `AeThexMultiplayer` is available (check console) - Try creating new room **Cloud saves failing:** - Check `AeThexAuth.is_logged_in()` - may need login - Use guest mode: `AeThexAuth.login_as_guest()` **Paddles not moving:** - Check input actions in Project Settings - Verify `_physics_process` is called --- ## Complete Code ### game.gd (Final Version) ```gdscript extends Node2D var score_p1 = 0 var score_p2 = 0 var high_score_p1 = 0 var high_score_p2 = 0 func _ready(): # Connect to cloud if not AeThexCloud.is_connected(): AeThexCloud.connect_to_cloud() # Login as guest for cloud saves if not AeThexAuth.is_logged_in(): await AeThexAuth.login_as_guest() # Create multiplayer room AeThexMultiplayer.create_room("pong-" + str(randi() % 10000)) AeThexMultiplayer.connect("player_joined", _on_player_joined) # Show room code var code = AeThexMultiplayer.get_room_code() $UI/RoomCode.text = "Room: " + code print("=================================") print(" Share code with friend: ", code) print("=================================") # Load high scores load_high_scores() func _on_player_joined(player_id): print("Player ", player_id, " joined!") var players = AeThexMultiplayer.get_players() if players.size() == 1: $Paddle1.set_multiplayer_authority(player_id) else: $Paddle2.set_multiplayer_authority(player_id) func load_high_scores(): var save_data = await AeThexSaves.load_game("high_scores") if save_data: high_score_p1 = save_data.get("p1", 0) high_score_p2 = save_data.get("p2", 0) print("High scores loaded: P1=", high_score_p1, " P2=", high_score_p2) func save_high_scores(): var save_data = { "p1": max(score_p1, high_score_p1), "p2": max(score_p2, high_score_p2), "timestamp": Time.get_unix_time_from_system() } if AeThexSaves.save_game("high_scores", save_data): print("✓ High scores saved to cloud!") func _on_ball_out_left(): score_p2 += 1 $UI/Player2Score.text = str(score_p2) if score_p2 > high_score_p2: high_score_p2 = score_p2 save_high_scores() reset_ball() func _on_ball_out_right(): score_p1 += 1 $UI/Player1Score.text = str(score_p1) if score_p1 > high_score_p1: high_score_p1 = score_p1 save_high_scores() reset_ball() func reset_ball(): $Ball.position = Vector2(600, 300) $Ball.linear_velocity = Vector2.ZERO await get_tree().create_timer(1.0).timeout var direction = Vector2(randf_range(-1, 1), randf_range(-0.5, 0.5)).normalized() $Ball.linear_velocity = direction * 400 ``` --- ## Congratulations! 🎉 You just built your first multiplayer cloud-enabled game with AeThex! **Share your creation:** - Tweet [@AeThexEngine](https://twitter.com/AeThexEngine) with your room code - Join our [Discord](https://discord.gg/aethex) community - Show off in [r/AeThex](https://reddit.com/r/aethex) Ready for more? Check out the [Tutorial Index](README.md) for advanced topics!