Skip to main content

Create Your First Multiplayer Function

Let’s build a simple multiplayer cursor tracking game where players can see each other’s mouse positions in real-time.

Step 1: Create the Cloud Function

curl -X POST http://localhost:8000/projects/YOUR_PROJECT_ID/functions \
  -H "Content-Type: application/json" \
  -d '{
    "name": "cursor_game",
    "function_type": "websocket",
    "description": "Multiplayer cursor tracking game",
    "code": "YOUR_CODE_HERE"
  }'

Step 2: Define Event Handlers

Here’s the complete function code with all event handlers:
Python (Cloud Function)
async def on_connect():
    """Called when a player connects"""
    player_name = request.get('player_name', 'Guest')

    # Join a room (auto-created if doesn't exist)
    room.join(request.get('room_id', 'lobby'), max_players=10)

    # Initialize room state on first player
    if not room.state:
        room.state = {'players': {}}

    # Add player to state
    room.state['players'][session.player_id] = {
        'name': player_name,
        'x': 400,
        'y': 300,
        'color': '#ff6b6b'
    }

    # Broadcast join event to all players
    await room.broadcast({
        'type': 'player_joined',
        'player_id': session.player_id,
        'player_name': player_name
    })

    # Return welcome message to connecting player
    return {
        'type': 'welcome',
        'your_id': session.player_id,
        'players': room.state['players']
    }


async def on_message():
    """Called when a player sends a message"""
    action = request.get('action')

    if action == 'move':
        # Update player position
        x = request.get('x', 0)
        y = request.get('y', 0)

        if session.player_id in room.state['players']:
            room.state['players'][session.player_id]['x'] = x
            room.state['players'][session.player_id]['y'] = y

            # Broadcast movement to all other players
            await room.broadcast({
                'type': 'player_moved',
                'player_id': session.player_id,
                'x': x,
                'y': y
            }, exclude=[session.player_id])


async def on_disconnect():
    """Called when a player disconnects"""
    if session.player_id in room.state['players']:
        player_name = room.state['players'][session.player_id]['name']
        del room.state['players'][session.player_id]

        await room.broadcast({
            'type': 'player_left',
            'player_id': session.player_id,
            'player_name': player_name
        })

Step 3: Connect from Client

<!DOCTYPE html>
<html>
<head>
    <title>Cursor Game</title>
    <style>
        body { margin: 0; overflow: hidden; background: #1a1a2e; }
        canvas { display: block; }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>

    <script>
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;

        // Connect to WebSocket
        const ws = new WebSocket(
            'ws://localhost:8000/ws/YOUR_PROJECT_ID/cursor_game'
        );

        let myId = null;
        let players = {};

        // Send initial connection message
        ws.onopen = () => {
            ws.send(JSON.stringify({
                room_id: 'lobby',
                player_name: 'Player' + Math.floor(Math.random() * 1000)
            }));
        };

        // Handle incoming messages
        ws.onmessage = (event) => {
            const data = JSON.parse(event.data);

            switch (data.type) {
                case 'welcome':
                    myId = data.your_id;
                    players = data.players;
                    break;

                case 'player_joined':
                    console.log(data.player_name + ' joined!');
                    break;

                case 'player_moved':
                    if (players[data.player_id]) {
                        players[data.player_id].x = data.x;
                        players[data.player_id].y = data.y;
                    }
                    break;

                case 'player_left':
                    delete players[data.player_id];
                    console.log(data.player_name + ' left');
                    break;
            }
        };

        // Send cursor position on mouse move
        canvas.addEventListener('mousemove', (e) => {
            if (ws.readyState === WebSocket.OPEN && myId) {
                ws.send(JSON.stringify({
                    action: 'move',
                    x: e.clientX,
                    y: e.clientY
                }));

                // Update local state immediately
                if (players[myId]) {
                    players[myId].x = e.clientX;
                    players[myId].y = e.clientY;
                }
            }
        });

        // Render loop
        function render() {
            ctx.fillStyle = '#1a1a2e';
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            // Draw all players
            for (const [id, player] of Object.entries(players)) {
                ctx.fillStyle = player.color || '#fff';
                ctx.beginPath();
                ctx.arc(player.x, player.y, 10, 0, Math.PI * 2);
                ctx.fill();

                // Draw name
                ctx.fillStyle = '#fff';
                ctx.font = '14px Arial';
                ctx.fillText(player.name, player.x + 15, player.y + 5);
            }

            requestAnimationFrame(render);
        }

        render();
    </script>
</body>
</html>

Test It Out!

  1. Open the HTML file in multiple browser tabs
  2. Move your mouse - you should see cursors from other tabs
  3. Each player has a different color and name

Next Steps

Add Game Loop

Learn how to use on_tick for continuous game updates

API Reference

Explore all available methods and objects

Examples

See more complete game examples

Best Practices

Tips for production-ready multiplayer apps