如何在EventListener中触发事件?PHP游戏核心中介者模式实现问询
Hey there! Triggering events inside an event listener is totally common—think of it as chaining actions after a core event is processed. The core idea is straightforward: you just need access to your event dispatcher instance, then call its dispatch method to fire off the new event.
Let's use PHP as an example (since your second question is PHP-focused). Here's a simple implementation using a custom or framework-provided dispatcher:
// Assume we have a basic EventDispatcher class class EventDispatcher { private $listeners = []; public function addListener(string $eventName, callable $listener) { $this->listeners[$eventName][] = $listener; } public function dispatch(string $eventName, $eventData) { foreach ($this->listeners[$eventName] ?? [] as $listener) { call_user_func($listener, $eventData); } } } // A listener that triggers another event class UserSignupListener { private $dispatcher; // Inject the dispatcher via constructor (dependency injection is key here!) public function __construct(EventDispatcher $dispatcher) { $this->dispatcher = $dispatcher; } public function handleSignup($userData) { // Do core signup logic: save user to DB, send confirmation email, etc. echo "User {$userData['email']} signed up!\n"; // Trigger a post-signup event for follow-up actions (like onboarding) $this->dispatcher->dispatch('user.post_signup', [ 'userId' => $userData['id'], 'email' => $userData['email'] ]); } } // Usage $dispatcher = new EventDispatcher(); $dispatcher->addListener('user.signup', [new UserSignupListener($dispatcher), 'handleSignup']); $dispatcher->addListener('user.post_signup', function($data) { echo "Sending onboarding guide to {$data['email']}\n"; }); $dispatcher->dispatch('user.signup', ['id' => 1, 'email' => 'player@game.com']);
Key Notes:
- Avoid infinite loops: Make sure you don't create a cycle where Listener A triggers Event X, which triggers Listener B, which triggers Event X again. Add checks or guard clauses if needed.
- Dispatcher availability: Always inject the dispatcher into your listener (don't use singletons unless you have to)—this keeps your code testable and decoupled.
Great call using Mediator (EventDispatcher) for your game—it's perfect for managing all those events and state changes. Adding a responsibility chain twist just means letting listeners decide whether to pass the event along to the next one. Here's how to implement this tailored to a game context:
Step 1: Create a GameEvent Class with Propagation Control
First, we need an event class that tracks if propagation should stop:
class GameEvent { private $propagationStopped = false; private $data; public function __construct(array $data) { $this->data = $data; } public function getData(): array { return $this->data; } public function setData(array $data): void { $this->data = $data; } public function stopPropagation(): void { $this->propagationStopped = true; } public function isPropagationStopped(): bool { return $this->propagationStopped; } }
Step 2: Build a Mediator (EventDispatcher) with Responsibility Chain Logic
This dispatcher will run listeners in priority order and stop if any listener halts propagation:
class GameEventDispatcher { private $listeners = []; // Add listeners with priority (higher numbers run first) public function addListener(string $eventName, callable $listener, int $priority = 0): void { if (!isset($this->listeners[$eventName])) { $this->listeners[$eventName] = []; } $this->listeners[$eventName][$priority][] = $listener; // Sort priorities descending so higher values run first krsort($this->listeners[$eventName]); } // Dispatch events with responsibility chain behavior public function dispatch(string $eventName, GameEvent $event): void { if (!isset($this->listeners[$eventName])) { return; } foreach ($this->listeners[$eventName] as $priorityListeners) { foreach ($priorityListeners as $listener) { // Execute the listener call_user_func($listener, $event); // Stop processing if propagation is halted if ($event->isPropagationStopped()) { return; } } } } }
Step 3: Example Game Listeners Using the Chain
Let's apply this to a "player takes damage" scenario, where multiple listeners handle the event in sequence:
// Listener 1: Apply armor damage reduction (high priority) class ArmorReductionListener { public function __invoke(GameEvent $event): void { $data = $event->getData(); $player = $data['player']; $originalDamage = $data['damage']; // Calculate reduced damage based on armor $armorFactor = 1 - ($player->getArmor() / 100); $reducedDamage = max(0, $originalDamage * $armorFactor); $data['damage'] = $reducedDamage; $event->setData($data); echo "Armor reduced damage from {$originalDamage} to {$reducedDamage}\n"; // Stop propagation if damage is fully negated if ($reducedDamage <= 0) { $event->stopPropagation(); echo "No damage taken—stopping further processing\n"; } } } // Listener 2: Deduct health from the player (lower priority) class HealthDeductListener { public function __invoke(GameEvent $event): void { $data = $event->getData(); $player = $data['player']; $newHealth = max(0, $player->getHealth() - $data['damage']); $player->setHealth($newHealth); echo "Player health updated to {$newHealth}\n"; // Trigger death event if health hits zero $dispatcher = new GameEventDispatcher(); // In real code, inject this! $dispatcher->dispatch('player.died', new GameEvent(['player' => $player])); } } // Mock Player class for the example class Player { private $health = 100; private $armor = 70; public function getHealth(): int { return $this->health; } public function setHealth(int $health): void { $this->health = $health; } public function getArmor(): int { return $this->armor; } } // Usage $dispatcher = new GameEventDispatcher(); $dispatcher->addListener('player.takes_damage', new ArmorReductionListener(), 10); $dispatcher->addListener('player.takes_damage', new HealthDeductListener(), 5); // Trigger the damage event $player = new Player(); $dispatcher->dispatch('player.takes_damage', new GameEvent([ 'player' => $player, 'damage' => 80 ]));
Game-Specific Tips:
- Prioritize critical listeners: For example, damage mitigation should run before health deduction, so give it a higher priority.
- Isolate listener responsibilities: Each listener should only handle one part of the logic (armor reduction, health deduction, death handling) to keep your code maintainable.
- Async processing for non-critical tasks: For things like logging achievements or sending in-game notifications, use an async queue to avoid blocking the game's main loop.
内容的提问来源于stack exchange,提问作者automatix

