AeThex-Engine-Core/docs/tutorials/FIRST_GAME_TUTORIAL.md

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


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:

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:

  • GoalLeftGame._on_ball_out_left
  • GoalRightGame._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

  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:

# 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 GPUParticles2D to ball
  • Emit trail when moving

Learn More Features


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)

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:

Ready for more? Check out the Tutorial Index for advanced topics!