12 KiB
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 Godot/GDScript knowledge (if new, see GDScript Basics)
- Studio IDE running at http://localhost:9002/ide
Part 1: Project Setup (5 min)
1. Create New Project
In Studio IDE:
- Click File → New Project
- Name:
MultiplayerPong - Location: Choose your projects folder
- Click Create & Open
2. Create Scene Structure
In the Scene Tree:
- Create Node2D (root) - name it
Game - Add children:
- ColorRect (background) - name it
Background - RigidBody2D - name it
Ball - StaticBody2D - name it
Paddle1 - StaticBody2D - name it
Paddle2 - CanvasLayer - name it
UI
- ColorRect (background) - name it
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:
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:
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:
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:
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:
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
- Run game twice (different terminals)
- Second instance joins with room code
Option B: Share with Friend
- Give friend the room code
- 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:
# 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:
# 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
GPUParticles2Dto ball - Emit trail when moving
Learn More Features
- AI Assistant Tutorial - Get coding help in-game
- Authentication Tutorial - Add login system
- Analytics Tutorial - Track player behavior
- Publishing Guide - 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
AeThexMultiplayeris 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_processis called
Complete Code
game.gd (Final Version)
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 with your room code
- Join our Discord community
- Show off in r/AeThex
Ready for more? Check out the Tutorial Index for advanced topics!