Skip to content

回合制坦克演示

本演示的目的是作为使用 Colyseus 的异步, 基于回合的游戏的示例. 本演示旨在搭配 Colyseus 0.14.7 版本以及 Unity version 2019.4.20f1 使用.

下载演示 (查看源代码)

玩玩看!

大厅

开始

启用本地服务器

您需要从 提供的服务器目录 中选择安装并启用服务器, 以正常操作本演示. 按照 这些文档中 Unity3d 部分之 "运行演示服务器" 中的说明操作即可.

ColyseusSettings ScriptableObject

服务器的所有设置都可通过此处的 ColyseusSetting ScriptableObject 进行更改:

ScriptableObject

如果您运行的是本地服务器, 默认的设置就能够满足需求; 但若您希望托管服务器, 则需要相应地更改 Colyseus 服务器地址Colyseus 服务器端口.

演示概览

房间元数据

本演示使用房间的元数据, 通过用户名来追踪游戏内的玩家. 当一名玩家加入或创建一个房间时, 其用户名将被存储在一个名为 team0team1 的属性中, team0 代表这名玩家创建了房间, team1 代表这名玩家加入了可用房间来挑战其创建者.

this.metadata.team0
this.metadata.team1

this.setMetadata({"team0": options["creatorId"]});

之后, 元数据中设置的用户名将用于筛选大厅中显示的可用房间. 在大厅里, 用户能够看到他们所创建的任何房间, 或者根据房间是否在等待挑战者加入游戏, 可以看到可用的房间. 非由您创建的房间以及已有两名玩家的房间不会显示在大厅内.

private TanksRoomsAvailable[] TrimRooms(TanksRoomsAvailable[] originalRooms)
{
    List<TanksRoomsAvailable> trimmedRooms = new List<TanksRoomsAvailable>();
    for (int i = 0; i < originalRooms.Length; ++i)
    {
        //Check a rooms metadata. If its one of our rooms OR waiting for a player, we show it
        TanksRoomMetadata metadata = originalRooms[i].metadata;
        if (metadata.team1 == null || (metadata.team1.Equals(ExampleManager.Instance.UserName) ||
                                       metadata.team0.Equals(ExampleManager.Instance.UserName)))
        {
            trimmedRooms.Add(originalRooms[i]);
        }
    }

    return trimmedRooms.ToArray();
}

大厅

保持房间的存在状态

为了使这个演示成为一个异步的回合制游戏, 我们需要保持房间的存在状态, 即使是在双方玩家都离开房间之后. 通过将 autoDispose 标志设为 false, 该房间将继续保持存在状态. (您可以在 onCreate 处理程序的 TanksRoom 服务器代码中看到此项).

this.autoDispose = false;

我们知道,在执行检查确定房间是否应该关闭后, 在布尔标志 inProcessOfQuitingGame 被设置为 true 后断开房间. 这些检查会在一名用户离开游戏时执行.

// 检查创作者是否在其他人加入之前就已经退出了
if(this.metadata.team0 && this.metadata.team1 == null) {
    disconnectRoom = true;
}

// 房间里没有其他用户,所以断开连接
if(this.inProcessOfQuitingGame && this.state.networkedUsers.size <= 1 && this.connectedUsers <= 1) {
    disconnectRoom = true;
}

// 房间是否应该断开连接?
if(disconnectRoom) {
    this.disconnect();
}

暂停房间

由于这是一个异步游戏的示例, 我们的房间可能在任何时间都没有用户连接进来. 当没有用户连接至房间时, 服务器不需要更新模拟循环. 当用户断开与房间的连接时, 将执行检查以查看是否不再有用户连接到房间. 当没有更多用户连接到房间时, 通过将延迟设置为高值, 可以有效地暂停模拟间隔. 在本示例中, 该值略大于 24 天.

// 在房间的 `onLeave` 处理程序中
// 检查服务器是否应该暂停模拟循环,因为
// 没有用户连接到房间
let anyConnected: boolean = false;
this.state.players.forEach((player, index) => {
    if(player.connected) {
        anyConnected = true;
    }
});

if(anyConnected == false) {
    // 没有用户连接,所以暂停服务器更新
    this.setServerPause(true);
}


private setServerPause(pause: boolean) {

    if(pause) {
        this.setSimulationInterval(dt => this.gameLoop(dt), this.pauseDelay);
    }
    else {
        // 设置模拟间隔回调
        this.setSimulationInterval(dt => this.gameLoop(dt));
    }

    this.serverPaused = pause;
}

当用户重新加入被暂停的房间时, 模拟间隔恢复.

// 在房间的 `onJoin` 处理程序中/
// 检查服务器是否需要解除暂停状态
if(this.serverPaused) {
    // 服务器目前处于暂停状态,由于有玩家加入连接,因此取消暂停状态
    this.setServerPause(false);
}

播放演示

让玩家出生在 "TanksLobby" 场景, 位置是 Assets\TurnBasedTanks\Scenes\TanksLobby. 输入您的用户名并创建房间以开始. 如果您无法进入房间制作界面, 请确认您的本地服务器工作正常, 并检查 Unity 编辑器的错误日志. 如果您成功了, 客户端将加载 "TankArena" 场景.

  • 本演示为异步回合制游戏.

  • 您可以随时离开房间, 之后返回进行中的游戏时, 将从最后离开的地方继续.

  • 一局游戏中只能有两名玩家.

  • 游戏目标是摧毁对手的坦克.

  • 每名玩家有 3 点生命值, 显示在屏幕上方角落.

  • 当您创建一个房间时, 您可以立即开始您的回合, 无论是否已经有另一名玩家加入.

  • 所有控制选项都显示在 ESC 菜单中.

  • 您可以使用 ESC 菜单中的退出选项随时离开房间, 也可以向您的对手投降.

  • 您的回合开始时有 3 个行动点数. 向左/右移动消耗 一个 行动点数, 开火消耗 两个 行动点数.

  • 移动可以被过高的地形所阻挡.

  • 想要发射坦克的武器时, 单击鼠标左键并长按来为射击充能. 松开左键进行开火.

  • 您有 3 种射程不同的武器可供选择. 使用数字键 1-3 来选择武器.

  • 在每次移动或开火行动后有 2 秒延迟, 之后才能进行下一次行动.

  • 在一名玩家的坦克损毁或有人投降时游戏结束, 游戏结束菜单显示胜/负消息, 并展示两个选项: 再来一局或退出游戏. 如果另一名玩家在您离开前要求再来一局, 您的游戏结束菜单上将显示一条消息.

  • 您的对手名称旁边会有一个 "在线标志", 来显示其是否与您同时处于房间内.

    • 红色 = 离线
    • 绿色 = 在线.
  • 您可以通过按下空格键来选择跳过您的剩余回合.

大厅 大厅

调整演示

当您播放此演示的时候, 您可能希望进行一些调整, 帮您更好地了解当前发生的情况. 下面您将学习如何进行微调整.

游戏规则和武器数据

Game RulesWeapon Data 的值都可以在游戏代码的 ArenaServer\src\rooms\tanks\rules.ts 中找到. Game Rules 控制移动, 开火消耗以及玩家拥有多少行动点数. weaponList 的数据详细规定了每种武器的最大充能, 充能时间, 冲击范围以及冲击伤害.

const GameRules = {
    MaxActionPoints: 3,
    MovementActionPointCost: 1,
    FiringActionPointCost: 2,
    ProjectileSpeed: 30,
    MaxMovement: 3,
    MaxHitPoints: 3,
    MovementTime: 2,
}

const weaponList = [
    {
        name: "Short Range",
        maxCharge: 5,
        chargeTime: 1,
        radius: 1,
        impactDamage: 1,
        index: 0
    },
    {
        name: "Mid Range",
        maxCharge: 8,
        chargeTime: 2,
        radius: 1,
        impactDamage: 1,
        index: 1
    },
    {
        name: "Long Range",
        maxCharge: 10,
        chargeTime: 5,
        radius: 1,
        impactDamage: 1,
        index: 2
    }
]

Back to top