<?php
class StateSyncGameServer { private $clients; private $gameState; private $updateInterval; private $lastUpdateTime; public function __construct($updateRate = 10) { $this->clients = []; $this->gameState = [ 'players' => [], 'objects' => [], 'timestamp' => microtime(true) ]; $this->updateInterval = 1.0 / $updateRate; $this->lastUpdateTime = microtime(true); }
public function handleClientConnect($clientId, $playerData) { $this->clients[$clientId] = [ 'id' => $clientId, 'player_id' => $playerData['player_id'], 'last_heartbeat' => microtime(true), 'pending_commands' => [] ]; $this->gameState['players'][$playerData['player_id']] = [ 'id' => $playerData['player_id'], 'position' => ['x' => 0, 'y' => 0, 'z' => 0], 'rotation' => ['x' => 0, 'y' => 0, 'z' => 0], 'health' => 100, 'last_input_seq' => 0, 'velocity' => ['x' => 0, 'y' => 0, 'z' => 0] ]; return $this->getFullState(); }
public function handleClientInput($clientId, $inputData) { if (!isset($this->clients[$clientId])) { return false; } $playerId = $this->clients[$clientId]['player_id']; $inputSeq = $inputData['sequence']; if ($inputSeq <= $this->gameState['players'][$playerId]['last_input_seq']) { return false; } $this->clients[$clientId]['pending_commands'][] = [ 'sequence' => $inputSeq, 'type' => $inputData['type'], 'data' => $inputData['data'], 'timestamp' => $inputData['timestamp'] ]; $this->gameState['players'][$playerId]['last_input_seq'] = $inputSeq; return true; }
public function update() { $currentTime = microtime(true); $deltaTime = $currentTime - $this->lastUpdateTime; if ($deltaTime >= $this->updateInterval) { $this->processInputs(); $this->updateGameLogic($deltaTime); $this->broadcastStateUpdate(); $this->lastUpdateTime = $currentTime; return true; } return false; }
private function processInputs() { foreach ($this->clients as $clientId => &$client) { $playerId = $client['player_id']; $player = &$this->gameState['players'][$playerId]; foreach ($client['pending_commands'] as $command) { $this->applyPlayerInput($player, $command); } $client['pending_commands'] = []; } }
private function applyPlayerInput(&$player, $command) { switch ($command['type']) { case 'move': $this->handleMoveInput($player, $command['data']); break; case 'jump': $this->handleJumpInput($player, $command['data']); break; case 'attack': $this->handleAttackInput($player, $command['data']); break; } }
private function handleMoveInput(&$player, $moveData) { $speed = 5.0; $player['velocity']['x'] = $moveData['x'] * $speed; $player['velocity']['z'] = $moveData['z'] * $speed; if (abs($moveData['x']) > 0.1 || abs($moveData['z']) > 0.1) { $player['rotation']['y'] = atan2($moveData['x'], $moveData['z']); } }
private function handleJumpInput(&$player, $jumpData) { if ($player['position']['y'] == 0) { $player['velocity']['y'] = 10.0; } }
private function handleAttackInput(&$player, $attackData) { $targetId = $attackData['target_id']; if (isset($this->gameState['players'][$targetId])) { $this->gameState['players'][$targetId]['health'] -= 10; if ($this->gameState['players'][$targetId]['health'] <= 0) { $this->handlePlayerDeath($targetId); } } }
private function updateGameLogic($deltaTime) { foreach ($this->gameState['players'] as &$player) { $player['position']['x'] += $player['velocity']['x'] * $deltaTime; $player['position']['y'] += $player['velocity']['y'] * $deltaTime; $player['position']['z'] += $player['velocity']['z'] * $deltaTime; if ($player['position']['y'] > 0) { $player['velocity']['y'] -= 20.0 * $deltaTime; } else { $player['position']['y'] = 0; $player['velocity']['y'] = 0; } $player['velocity']['x'] *= 0.9; $player['velocity']['z'] *= 0.9; } $this->gameState['timestamp'] = microtime(true); }
private function broadcastStateUpdate() { $stateUpdate = $this->getDeltaState(); foreach ($this->clients as $clientId => $client) { $this->sendToClient($clientId, 'state_update', $stateUpdate); } }
public function getFullState() { return $this->gameState; }
public function getDeltaState() { return [ 'players' => $this->gameState['players'], 'timestamp' => $this->gameState['timestamp'] ]; }
private function handlePlayerDeath($playerId) { $this->gameState['players'][$playerId]['health'] = 100; $this->gameState['players'][$playerId]['position'] = ['x' => 0, 'y' => 0, 'z' => 0]; $this->broadcastEvent('player_died', [ 'player_id' => $playerId, 'respawn_time' => 3 ]); }
private function broadcastEvent($eventType, $eventData) { foreach ($this->clients as $clientId => $client) { $this->sendToClient($clientId, 'game_event', [ 'type' => $eventType, 'data' => $eventData ]); } }
private function sendToClient($clientId, $messageType, $data) { echo "发送到客户端 {$clientId}: {$messageType}\n"; } }
class StateSyncGameClient { private $playerId; private $server; private $localState; private $pendingInputs; private $inputSequence; private $serverTimeOffset; public function __construct($playerId, $server) { $this->playerId = $playerId; $this->server = $server; $this->localState = []; $this->pendingInputs = []; $this->inputSequence = 0; $this->serverTimeOffset = 0; $this->connectToServer(); }
private function connectToServer() { $fullState = $this->server->handleClientConnect(uniqid(), [ 'player_id' => $this->playerId ]); $this->localState = $fullState; echo "客户端 {$this->playerId} 已连接,获取完整状态\n"; }
public function handleInput($inputType, $inputData) { $this->inputSequence++; $inputPacket = [ 'sequence' => $this->inputSequence, 'type' => $inputType, 'data' => $inputData, 'timestamp' => $this->getClientTime() ]; $this->applyLocalPrediction($inputType, $inputData); $this->server->handleClientInput($this->getClientId(), $inputPacket); $this->pendingInputs[$this->inputSequence] = $inputPacket; }
private function applyLocalPrediction($inputType, $inputData) { $player = &$this->localState['players'][$this->playerId]; switch ($inputType) { case 'move': $speed = 5.0; $player['velocity']['x'] = $inputData['x'] * $speed; $player['velocity']['z'] = $inputData['z'] * $speed; break; } }
public function receiveStateUpdate($serverState) { $this->reconcileWithServer($serverState); }
private function reconcileWithServer($serverState) { $serverPlayer = $serverState['players'][$this->playerId]; $localPlayer = $this->localState['players'][$this->playerId]; $positionDiff = $this->calculateDistance($serverPlayer['position'], $localPlayer['position']); if ($positionDiff > 1.0) { echo "客户端 {$this->playerId}: 位置差异过大({$positionDiff}),采用服务器状态\n"; $this->localState = $serverState; } else { $this->interpolateToServerState($serverState); } $lastServerSeq = $serverPlayer['last_input_seq']; $this->removeAcknowledgedInputs($lastServerSeq); }
private function interpolateToServerState($serverState) { $interpolationFactor = 0.2; foreach ($serverState['players'] as $playerId => $serverPlayer) { if (!isset($this->localState['players'][$playerId])) { $this->localState['players'][$playerId] = $serverPlayer; continue; } $localPlayer = &$this->localState['players'][$playerId]; if ($playerId != $this->playerId) { $localPlayer['position']['x'] = $this->lerp( $localPlayer['position']['x'], $serverPlayer['position']['x'], $interpolationFactor ); } } }
private function removeAcknowledgedInputs($lastServerSeq) { foreach ($this->pendingInputs as $seq => $input) { if ($seq <= $lastServerSeq) { unset($this->pendingInputs[$seq]); } } }
public function update($deltaTime) { $this->updateLocalState($deltaTime); }
private function updateLocalState($deltaTime) { foreach ($this->localState['players'] as &$player) { $player['position']['x'] += $player['velocity']['x'] * $deltaTime; $player['position']['y'] += $player['velocity']['y'] * $deltaTime; $player['position']['z'] += $player['velocity']['z'] * $deltaTime; if ($player['position']['y'] > 0) { $player['velocity']['y'] -= 20.0 * $deltaTime; } else { $player['position']['y'] = 0; $player['velocity']['y'] = 0; } $player['velocity']['x'] *= 0.9; $player['velocity']['z'] *= 0.9; } } private function getClientTime() { return microtime(true) + $this->serverTimeOffset; } private function getClientId() { return "client_{$this->playerId}"; } private function calculateDistance($pos1, $pos2) { $dx = $pos1['x'] - $pos2['x']; $dy = $pos1['y'] - $pos2['y']; $dz = $pos1['z'] - $pos2['z']; return sqrt($dx*$dx + $dy*$dy + $dz*$dz); } private function lerp($a, $b, $f) { return $a + $f * ($b - $a); }
public function getRenderState() { return $this->localState; } }
?>
|