Game Development 15 min read

Create Real‑Time Multiplayer Games Using Asyncio‑Gamekit for PHP

The Asyncio‑Gamekit library provides a Workerman‑based asynchronous game framework for PHP, offering room lifecycle management, player communication, broadcasting, timers, load balancing, persistence, logging, and testing, enabling developers to quickly build WebSocket‑driven multiplayer games such as card, board, and real‑time battle titles.

Open Source Tech Hub
Open Source Tech Hub
Open Source Tech Hub
Create Real‑Time Multiplayer Games Using Asyncio‑Gamekit for PHP

Overview

This library provides an asynchronous game framework built on Workerman and pfinal-asyncio. It enables game logic to be written with async/await -style syntax using PHP generators.

Core Features

Room base class – asynchronous room management with a full lifecycle hook set.

Player communication wrapper – simple API for sending and receiving player messages.

Asynchronous broadcast – immediate and delayed broadcasts.

Timer system – flexible timers and delayed tasks.

Room lifecycle – onCreate, onStart, run, onDestroy hooks.

RoomManager – multi‑room management and fast matching.

GameServer – ready‑to‑use WebSocket game server.

Advanced Features

Unit testing – built‑in PHPUnit test framework.

Exception handling – structured exception system with context information.

Logging system – multi‑level logs with console and file output.

State persistence – supports Redis and file storage.

Load balancing – multi‑process room allocation with round‑robin or least‑connection strategies.

Installation

composer require pfinalclub/asyncio-gamekit

Requirements

PHP >= 8.3

pfinalclub/asyncio >= 1.0

workerman/workerman >= 4.1

Quick Start

1. Create a Game Room

<?php
use PfinalClub\AsyncioGamekit\Room;
use PfinalClub\AsyncioGamekit\Player;
use function PfinalClub\Asyncio\{run, sleep};

class MyGameRoom extends Room {
    protected function run(): Generator {
        $this->broadcast('game:start', ['message' => 'Game started!']);
        for ($round = 1; $round <= 3; $round++) {
            $this->broadcast('game:round', ['round' => $round]);
            yield sleep(5); // each round lasts 5 seconds
        }
        $this->broadcast('game:end', ['message' => 'Game over!']);
        yield from $this->destroy();
    }
}

function main(): Generator {
    $room = new MyGameRoom('room_001');
    $player1 = new Player('p1', null, 'Alice');
    $player2 = new Player('p2', null, 'Bob');
    $room->addPlayer($player1);
    $room->addPlayer($player2);
    yield from $room->start();
}

run(main());

2. WebSocket Game Server

<?php
use PfinalClub\AsyncioGamekit\GameServer;

$server = new GameServer('0.0.0.0', 2345, [
    'name' => 'MyGameServer',
    'count' => 4,
    'protocol' => 'websocket',
]);

$server->run();

Client example (JavaScript):

const ws = new WebSocket('ws://localhost:2345');
ws.onopen = () => {
    ws.send(JSON.stringify({event: 'set_name', data: {name: 'Alice'}}));
    ws.send(JSON.stringify({event: 'quick_match', data: {room_class: 'MyGameRoom'}}));
};
ws.onmessage = (event) => {
    const message = JSON.parse(event.data);
    console.log('Received:', message);
};

Core API

Room Class

The room is the core container for game logic, providing a complete lifecycle.

Configuration Options

protected function getDefaultConfig(): array {
    return [
        'max_players' => 4,
        'min_players' => 2,
        'auto_start'  => false,
    ];
}

Lifecycle Hooks

// Called when the room is created (async)
protected function onCreate(): Generator {
    $this->broadcast('room:created', ['message' => 'Room created']);
    yield;
}

// Called when the game starts (async)
protected function onStart(): Generator {
    $this->broadcast('game:start', ['message' => 'Game about to start']);
    yield sleep(3);
}

// Main game loop – must be implemented by the subclass
abstract protected function run(): Generator;

// Called when the room is destroyed (async)
protected function onDestroy(): Generator {
    $this->broadcast('room:destroyed', ['message' => 'Room destroyed']);
    yield;
}

// Player joins (sync)
protected function onPlayerJoin(Player $player): void {
    echo "{$player->getName()} joined the room
";
}

// Player leaves (sync)
protected function onPlayerLeave(Player $player): void {
    echo "{$player->getName()} left the room
";
}

// Handle player messages (async)
public function onPlayerMessage(Player $player, string $event, mixed $data): Generator {
    if ($event === 'action') {
        // process player action
    }
    yield;
}

Common Methods

// Broadcast to all players
$this->broadcast('event_name', $data);

// Broadcast with exclusion
$this->broadcast('event_name', $data, $exceptPlayer);

// Delayed broadcast (2‑second delay)
yield from $this->broadcastAsync('event_name', $data, 2.0);

// Delayed execution (5 seconds)
yield from $this->delay(5.0);

// Add a repeating timer
$timerId = $this->addTimer(1.0, function() {
    echo "Every second
";
}, true);

// Remove timer
$this->removeTimer($timerId);

// Player management
$this->addPlayer($player);
$this->removePlayer($playerId);
$player = $this->getPlayer($playerId);
$players = $this->getPlayers();
$count = $this->getPlayerCount();

// Data storage
$this->set('key', 'value');
$value = $this->get('key', 'default');

// Room status
$status = $this->getStatus(); // waiting, running, finished
$canStart = $this->canStart();

Player Class

// Create a player
$player = new Player('player_id', $connection, 'PlayerName');

// Send a message
$player->send('event_name', ['data' => 'value']);

// Data handling
$player->set('score', 100);
$score = $player->get('score', 0);
$hasScore = $player->has('score');

// Ready state
$player->setReady(true);
$isReady = $player->isReady();

// Retrieve info
$id = $player->getId();
$name = $player->getName();
$room = $player->getRoom();
$array = $player->toArray();

RoomManager Class

$manager = new RoomManager();

// Create a room
$room = $manager->createRoom(MyGameRoom::class, 'room_id', ['max_players' => 4]);

// Player joins / leaves
$manager->joinRoom($player, 'room_id');
$manager->leaveRoom($player);

// Retrieve rooms
$room = $manager->getRoom('room_id');
$rooms = $manager->getRooms();
$playerRoom = $manager->getPlayerRoom($player);

// Quick match (auto‑create or join)
$room = $manager->quickMatch($player, MyGameRoom::class, ['max_players' => 4]);

// Delete a room
yield from $manager->removeRoom('room_id');

// Statistics
$stats = $manager->getStats();

GameServer Class

$server = new GameServer('0.0.0.0', 2345, [
    'name' => 'GameServer',
    'count' => 4,
    'protocol' => 'websocket',
]);
$server->run();

Built‑in system events that clients can send: set_name – set player name (data: {name: "PlayerName"}) create_room – create a room (data: {room_class: "ClassName", config: {...}}) join_room – join a room (data: {room_id: "room_id"}) leave_room – leave current room quick_match – fast match (same data as create_room) get_rooms – retrieve room list get_stats – retrieve statistics

Examples

Simple countdown game – php examples/SimpleGame.php Card game – php examples/CardGame.php WebSocket guess‑number game – php examples/WebSocketServer.php then open examples/client.html in a browser

Advanced demo showing logging, exception handling, persistence, etc. –

php examples/AdvancedGame.php

New Feature Usage

Logging System

use PfinalClub\AsyncioGamekit\Logger\LoggerFactory;
use PfinalClub\AsyncioGamekit\Logger\LogLevel;

LoggerFactory::configure([
    'min_level' => LogLevel::INFO,
    'console' => ['enabled' => true, 'color' => true],
    'file' => [
        'enabled' => true,
        'path' => 'logs/game.log',
        'max_size' => 10 * 1024 * 1024,
    ],
]);

LoggerFactory::info('Game started', ['room_id' => 'room_001']);
LoggerFactory::error('Error: {message}', ['message' => 'Connection lost']);

Exception Handling

use PfinalClub\AsyncioGamekit\Exceptions\RoomException;

try {
    $room->addPlayer($player);
} catch (RoomException $e) {
    $player->send('error', [
        'message' => $e->getMessage(),
        'code' => $e->getCode(),
        'context' => $e->getContext(),
    ]);
}

State Persistence

use PfinalClub\AsyncioGamekit\Persistence\{FileAdapter, RedisAdapter, RoomStateManager};

// File storage
$adapter = new FileAdapter('storage/game');
$stateManager = new RoomStateManager($adapter);

// Redis storage (recommended)
$adapter = RedisAdapter::create('127.0.0.1', 6379);
$stateManager = new RoomStateManager($adapter);

$stateManager->saveRoomState($room);
$state = $stateManager->getRoomState('room_001');

Load Balancing

use PfinalClub\AsyncioGamekit\LoadBalance\{RoomDistributor, LeastConnectionsBalancer};

$balancer = new LeastConnectionsBalancer();
$distributor = new RoomDistributor($balancer);

$distributor->registerWorker(1, ['connections' => 10]);
$distributor->registerWorker(2, ['connections' => 5]);

$workerId = $distributor->assignRoom('room_001'); // selects worker with fewest connections

Unit Testing

# Install PHPUnit
composer require --dev phpunit/phpunit

# Run all tests
./vendor/bin/phpunit

# Run a specific test file
./vendor/bin/phpunit tests/RoomTest.php

Advanced Usage

Custom Timer Tasks

protected function run(): Generator {
    $timerId = $this->addTimer(1.0, function() {
        $elapsed = time() - $this->get('start_time');
        $this->broadcast('game:timer', ['elapsed' => $elapsed]);
    }, true);

    $this->set('start_time', time());
    yield sleep(60); // game runs for 60 seconds
    $this->removeTimer($timerId);
}

Asynchronous Task Orchestration

use function PfinalClub\Asyncio\{create_task, gather};

protected function run(): Generator {
    $task1 = create_task($this->taskA());
    $task2 = create_task($this->taskB());
    $results = yield gather($task1, $task2);
    // continue game logic with $results
}

private function taskA(): Generator {
    yield sleep(2);
    return 'Result A';
}

private function taskB(): Generator {
    yield sleep(1);
    return 'Result B';
}

Timeout Control

use function PfinalClub\Asyncio\wait_for;
use PfinalClub\Asyncio\TimeoutException;

protected function run(): Generator {
    try {
        $result = yield wait_for($this->waitForPlayerAction(), 10.0);
    } catch (TimeoutException $e) {
        $this->broadcast('game:timeout', ['message' => 'Timeout!']);
    }
}

Inter‑Room Communication

$roomManager = new RoomManager();
$room1 = $roomManager->getRoom('room_001');
$room2 = $roomManager->getRoom('room_002');

foreach ($room2->getPlayers() as $player) {
    $player->send('cross_room_message', ['from' => $room1->getId()]);
}

Configuration Suggestions

Production Environment

$server = new GameServer('0.0.0.0', 2345, [
    'name' => 'ProductionGameServer',
    'count' => 8, // match CPU core count
]);

Debug Mode

use Workerman\Worker;

Worker::$daemonize = false;
Worker::$stdoutFile = '/tmp/workerman.log';

Comparison with Python asyncio

Coroutine definition : Python uses async def, this library uses function(): Generator.

Awaiting a coroutine : Python uses await expr, this library uses yield expr.

Sleep : Python await asyncio.sleep(1) vs. yield sleep(1).

Concurrent tasks : Python asyncio.gather() vs. yield gather().

Task creation : Python asyncio.create_task() vs. create_task().

Event loop : Python asyncio.run() vs. run() in this library.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

WebSocketPHPmultiplayerWorkermanGame Framework
Open Source Tech Hub
Written by

Open Source Tech Hub

Sharing cutting-edge internet technologies and practical AI resources.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.