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.
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-gamekitRequirements
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.phpNew 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 connectionsUnit Testing
# Install PHPUnit
composer require --dev phpunit/phpunit
# Run all tests
./vendor/bin/phpunit
# Run a specific test file
./vendor/bin/phpunit tests/RoomTest.phpAdvanced 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Open Source Tech Hub
Sharing cutting-edge internet technologies and practical AI resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
