游戏时间限制
不知道童鞋们有没有发现目前游戏有一个天大的Bug:在目前的游戏机制下,我们的躲藏者永远都是赢不了的,只有被追着跑的份,这样做出来的游戏平衡性太差了。
这一章我们将要为躲藏者讨回一个公道,增加每局的游戏时间限制,让躲藏者也有赢的机会。
既然要做时间限制,那就需要用到定时器功能了,Swoole
框架为我们提供了两种定时器:循环定时器swoole_timer_tick
和一次性定时器swoole_timer_after
。至于两种定时器有什么区别童鞋们可以参考官方文档,或者参考附录二:Swoole入门篇(下),我们的需求是游戏时间的限制,很显然在这里需使用的是swoole_timer_after
。
功能的逻辑思想很简单,当游戏开始的时候,创建一个定时器,当时间到了则判定躲藏者胜利,在流程上并不会影响到目前的游戏初始化流程。
做题时间
- 游戏初始化的时候,使用
swoole_timer_after
创建一个定时器,定时10秒钟。 - 发送时间秒数给前端,前端进行倒计时渲染。
- 当触发定时器的时候,判断房间是否存在,若存在则判定躲藏者胜利,并关闭游戏房间。
- 前端增加当前玩家类型展示。
index.html
:
...
<div id="app">
...
<div v-if="roomId">
<div>
房间号:{{roomId}}
</div>
<div v-if="timeLimit">
剩余时间:{{timeLimit}}
</div>
</div>
...
<div v-if="playerType">
本局玩家类型:{{playerTypeArr[playerType]}}
</div>
...
</div>
<script>
var app = new Vue({
...
data: {
...
playerType: null,
playerTypeArr: [0, '寻找者', '躲藏者'],
timeLimit: null,
timerId: null
},
...
methods: {
...
websocketonmessage(e) { //数据接收
...
switch (message.code) {
...
case 1004://游戏数据
this.mapData = responseData.map_data;
if (!this.playerType){
let players = responseData.players;
this.playerType = players[this.playerId].type
}
if (!this.timerId) {
this.timeLimit = responseData.time_limit
var that = this
this.timerId = setInterval(function () {
that.timeLimit--
}, 1000);
}
break;
...
}
},
...
}
})
</script>
...
Logic
类:
<?php
...
class Logic
{
...
const GAME_TIME_LIMIT = 10;
...
public function startRoom($roomId, $playerId)
{
...
if (empty(count($gameManager->getPlayers()))) {
...
} else {
//第二个玩家
$gameManager->createPlayer($playerId, 6, 10);
DataCenter::$global['rooms'][$roomId]['timer_id'] = $this->createGameTimer($roomId);
Sender::sendMessage($playerId, Sender::MSG_ROOM_START);
$this->sendGameInfo($roomId);
}
}
...
private function sendGameInfo($roomId)
{
...
foreach ($players as $player) {
$data = [
'players' => $players,
'map_data' => $this->getNearMap($mapData, $player->getX(), $player->getY()),
'time_limit' => self::GAME_TIME_LIMIT
];
Sender::sendMessage($player->getId(), Sender::MSG_GAME_INFO, $data);
}
}
...
private function createGameTimer($roomId)
{
return swoole_timer_after(self::GAME_TIME_LIMIT * 1000, function () use ($roomId) {
if (isset(DataCenter::$global['rooms'][$roomId])) {
//游戏还未结束则主动结束游戏
/**
* @var Game $gameManager
*/
$gameManager = DataCenter::$global['rooms'][$roomId]['manager'];
$players = $gameManager->getPlayers();
$winner = end($players)->getId();
DataCenter::addPlayerWinTimes($winner);
foreach ($players as $player) {
Sender::sendMessage($player->getId(), Sender::MSG_GAME_OVER, ['winner' => $winner]);
DataCenter::delPlayerRoomId($player->getId());
}
unset(DataCenter::$global['rooms'][$roomId]);
}
});
}
}
createGameTimer()
方法里创建的定时器回调方法其实与原有的checkGameOver()
方法逻辑非常类似,对于相同的代码我们应该要抽出来作为一个方法调用。
Logic
类:
<?php
...
class Logic
{
...
private function checkGameOver($roomId)
{
/**
* @var Game $gameManager
* @var Player $player
*/
$gameManager = DataCenter::$global['rooms'][$roomId]['manager'];
if ($gameManager->isGameOver()) {
$players = $gameManager->getPlayers();
$winner = current($players)->getId();
$this->gameOver($roomId, $winner);
}
}
private function createGameTimer($roomId)
{
return swoole_timer_after(self::GAME_TIME_LIMIT * 1000, function () use ($roomId) {
if (isset(DataCenter::$global['rooms'][$roomId])) {
//游戏还未结束则主动结束游戏
/**
* @var Game $gameManager
*/
$gameManager = DataCenter::$global['rooms'][$roomId]['manager'];
$players = $gameManager->getPlayers();
$winner = end($players)->getId();
$this->gameOver($roomId, $winner);
}
});
}
private function gameOver($roomId, $winner)
{
/**
* @var Game $gameManager
* @var Player $player
*/
$gameManager = DataCenter::$global['rooms'][$roomId]['manager'];
$players = $gameManager->getPlayers();
DataCenter::addPlayerWinTimes($winner);
foreach ($players as $player) {
Sender::sendMessage($player->getId(), Sender::MSG_GAME_OVER, ['winner' => $winner]);
DataCenter::delPlayerRoomId($player->getId());
}
unset(DataCenter::$global['rooms'][$roomId]);
}
}
为实体类Player
实现JsonSerializable
接口,满足sendGameInfo()
方法中输出$players
对象的需求。
Player
类:
<?php
...
class Player implements \JsonSerializable
{
...
public function jsonSerialize()
{
return [
'id' => $this->id,
'type' => $this->type,
'x' => $this->x,
'y' => $this->y,
];
}
}
代码写完了,我们赶紧来测试一下游戏效果:
可以看到,左上角已经成功渲染出“剩余时间:6”的文字,也成功展示出了玩家类型。
当触发计时器的时候,躲藏者获得了胜利,游戏功能正常。
Homework
本章就到这里结束了,相信童鞋们已经大概掌握了定时器的用法,但其实我们目前的游戏逻辑还是不够严谨的。
如:
- 当定时器触发的同一时间,寻找者抓到了躲藏者该怎么处理?
这个问题就留给童鞋们自己去思考了,一种比较容易的解决方法就是增加锁机制。
本章对应Github Commit
:扩展七:游戏时间限制