dot 未来速度将降临您所在的城市。

加入我们参加 Redis 发布会

如何使用 Unity 和 Redis 构建一款竞争激烈的在线赛车游戏

如果您喜欢游戏并热爱创造,那么您可以从这个 Launchpad 应用程序中获得灵感。Graham Pinsent 利用 Redis 创造了自己的在线赛车游戏,朋友、家人和同事可以在其中解决旧怨,相互竞争,争夺终点线。 

与任何赛车游戏一样,最终目标是以最快的速度从 A 点到达 B 点。玩家会得到一辆汽车,时间会进行竞赛,获胜者将根据谁能够以最快的速度飞驰过每圈来决定。 

但是,为了使此应用程序能够最佳运行,必须以超高的效率传输数据,以确保玩家命令能够实时执行。如果无法实现这一点,就会导致玩家命令与游戏内车辆之间出现脱节,从而导致令人沮丧的游戏体验。 

为了防止这种情况发生,Graham 利用了 Redis 的高级功能,创建了一个应用程序,其中各个组件能够以最大效率无缝地相互传输数据。 

让我们研究一下它是如何实现的。我们还提供 Redis 社区创建的各种令人兴奋的应用程序供您查看。请务必在 Redis Launchpad 上查看所有操作。 

https://www.youtube.com/embed/Tq3yoIn4hLc
  1. 您将构建什么?
  2. 您需要什么?
  3. 架构
  4. 入门
  5. 工作原理

1. 您将构建什么?

您将构建一款在线赛车游戏,允许玩家使用 Redis 相互竞争。下面,我们将按时间顺序介绍每个阶段,并重点介绍使此应用程序栩栩如生的所需组件。 

准备开始了吗?好的,让我们直接进入主题。 

2. 您需要什么?

  • NodeJS: 用作在网络浏览器之外执行 JavaScript 代码的开源跨平台工具
  • Express: 用作灵活的 Node.js Web 应用程序框架,为 Web 和移动应用程序提供强大的功能集。
  • Unity: 用作视频游戏的实时开发平台
  • RedisJSON: 将 ECMA-404 JSON 数据交换标准作为原生数据类型实现。

3. 架构

  1. 每隔 0.1 秒,每个用户汽车的位置都会发送到 Node JS 服务器。
  2. Node JS 处理此数据并将其发送到 Redis 数据库。
  3. 然后,服务器会发送每个用户汽车的位置,并更新其位置。
  4. 用户完成的每圈时间将被发送到 Node JS 服务器,以添加到 Redis 数据库中的实时排行榜。 

4. 入门

先决条件 

  • Node.js
  • NPM
  • Unity 2020.3.4f1
  • 带有 RedisJSON 模块的 Redis

步骤 1:安装 RedisJSON

$ docker run -d -p 6379:6379 redislabs/redismod

您也可以使用启用 RedisJSON 模块的 Redis Enterprise Cloud,如下所示

步骤 2. 克隆存储库

$ git clone https://github.com/redis-developer/Redis-Racing

步骤 3. 设置环境变量

在 Node JS 文件夹中,使用数据库的详细信息创建 .env 文件。

HOST=redis-12132.c72.eu-west-1-2.ec2.cloud.redislabs.comPASSWORD=XXXXXPORT=12132

步骤 4. 安装依赖项

更改目录到 nodejs 并执行以下 CLI

npm install

    步骤 5. 运行服务器

node index.js

步骤 6. 监控 Redis 服务器

redis-12132.c72.eu-west-1-2.ec2.cloud.redislabs.com:12132> monitorOK1633172585.553251 [0 122.167.151.216:50970] "json.get" "players"1633172587.637245 [0 122.167.151.216:51147] "auth" "9740535000"1633172587.637245 [0 122.167.151.216:51147] "info"1633172587.793245 [0 122.167.151.216:51147] "keys" "*"1633172587.793245 [0 122.167.151.216:51147] "json.get" "players"

最后,您可以使用 Unity 构建游戏,也可以直接在项目中按播放。

打开 Web 浏览器 https://IP: 3000 访问游戏。

5. 工作原理

玩家选择姓名并连接到服务器后,其在地图上的位置将发送到 Redis 数据库。当玩家在赛道上行驶时,其位置会每秒更新 10 次。以下是如何显示玩家数据

"players" = {
    "_James" : {"name":"James","xPos":1,"yPos":10,"zRot":-90,"lastping": "2021-05-11T21:44:11.640Z"},
    "_Ryan" : {"name":"Ryan","xPos":20,"yPos":80,"zRot":180,"lastping": "2021-05-11T21:45:11.790Z"},
    "_Paul" : {"name":"Paul","xPos":-2,"yPos":180,"zRot":61,"lastping": "2021-05-11T21:45:15.110Z"}
}

每次玩家使用其新位置向服务器发出 POST 请求时,来自服务器的响应将包含所有玩家的当前位置和数据。然后,此数据用于将其他所有人的汽车放置到赛道上,以便您在驾驶时看到。线性插值用于使运动平滑,使其看起来比每秒 10 次更新更快。

玩家越过终点线后,其时间和姓名将发送到服务器。以下是一个赛道时间数据的示例

"leaderboard" = {
    "_James" : {"name": "James","laptime": 37.19,"created": "2021-05-11T21:56:55.440Z"},
    "_Ryan" : {"name": "Ryan","laptime": 50.56,"created": "2021-05-11T21:57:35.220Z"},
    "_Paul" : {"name": "Paul","laptime": 45.11,"created": "2021-05-11T21:58:51.120Z"}
}

此数据用于创建实时排行榜,所有玩家都可以在其中查看,因为他们正在彼此竞争以争取最快的成绩。当任何一辆汽车越过终点线时,客户端将向服务器请求新的排行榜数据。 


功能

选择您的姓名

玩家首次开始游戏时,会提示他们选择姓名。一旦玩家输入姓名并按下开始,游戏客户端就会将此信息作为 POST 请求发送到服务器。然后,服务器将对姓名进行检查以验证其有效性,包括验证是否存在重复的姓名。 

这是通过从 Redis 数据库获取每个玩家的 JSON 并将其推入数组以循环遍历来实现的。发生这种情况时,服务器将检查其姓名与数据是否匹配。 

client.json_get("players", function (err, results) {
    const currentplayers = Object.values(JSON.parse(results))
    currentplayers.forEach(function (item, index){
        playernames.push(item.name)
    })

最后,在验证完成后,服务器将向客户端发送成功响应。如果姓名无效,则服务器将发送失败响应。 

更新位置

玩家进入游戏后,每十分之一秒就会向服务器发送一个 POST 请求。请求中将包含他们的姓名、位置和旋转。服务器收到请求后,会将当前时间添加到 JSON 中,然后将其注册到 Redis 数据库中。 

const namekey = `_${myjson.name}`
client.json_set("players",namekey, JSON.stringify(data))

设置玩家的新位置后,服务器将从 Redis 数据库获取每个玩家的 JSON 并将其作为响应发送回游戏。 

client.json_get("players", function (err, results) {
    res.send(Object.values(JSON.parse(results)))
})

游戏客户端收到数据后,便可以更新所有连接玩家的位置。 

排行榜

玩家完成一圈后,会向服务器发送一个 POST 请求。此请求中的信息将包含玩家的姓名以及完成一圈所花费的时间。此时,服务器会自动尝试从 Redis 数据库检索他们的上圈时间(如果有)。 

const namekey = `_${myjson.name}`
client.json_get("leaderboard", `.${namekey}`, function (err, results) {

然后,服务器会检查他们是否有旧时间,或者这是否是玩家提交的第一圈时间。如果玩家有旧时间,则会将其与新时间进行比较,以查看它是否更快。如果是,则会将新时间注册到数据库中的排行榜 JSON 中。 

if (oldtime) {
    oldtime = JSON.parse(oldtime)
    if (myjson.time < oldtime.laptime) {
        client.json_set("leaderboard", namekey, JSON.stringify(data))
    }
}

如果是第一次,则会将他们的姓名和圈时间注册到排行榜中。

else {
    client.json_set("leaderboard", namekey, JSON.stringify(data))
}

最后,服务器会获取排行榜的所有数据,并将响应发送回游戏客户端。 

client.json_get("leaderboard", function( err, results) {
    res.send(Object.values(JSON.parse(results)))
})

游戏客户端可以使用这些数据来生成排行榜。

断开连接的玩家

每隔 30 秒,服务器会从 Redis 数据库获取每个玩家的 JSON 数据。 

client.json_get("players", function (err, results) {

当玩家关闭游戏时,游戏客户端会停止发送更新位置请求。服务器将循环遍历所有玩家,检查他们上次更新是否超过 30 秒。如果是,则会将其从玩家的 JSON 和排行榜 JSON 中删除。

playerdata.forEach(function (item, index) {
  var namepath = `._${item.name}`
  var seconds = Math.floor((new Date() - Date.parse(item.lastping)) / 1000);
  if (seconds > 30) {
    client.json_del('players', namepath)
    client.json_del('leaderboard', namepath)
    console.log(`Removed ${item.name}`)
  }
});

游戏客户端也会进行相同的检查。如果收到他们的最后一次更新超过 30 秒,则会销毁他们的汽车对象。

if (lastPing.AddSeconds(30) < DateTime.Now) {
    isAFK = true;
    GameManager gameManager = GameObject.Find("GameManager").GetComponent<GameManager>();
    gameManager.StartGetLeaderboard();
    gameManager.RemovePlayer(this);
    Destroy(gameObject);
}

首次开始游戏时,您将有机会选择玩家的姓名,如下所示。输入姓名并按下开始后,游戏客户端会将此信息作为 POST 请求发送到服务器。 

然后,服务器会对姓名进行各种检查以验证其有效性。例如,它会检查姓名是否已被占用。此验证过程是通过从 Redis 数据库获取每个玩家的 JSON 并将其推入数组以循环遍历并检查其姓名与数据是否匹配来实现的。 

client.json_get("players", function (err, results) {
    const currentplayers = Object.values(JSON.parse(results))
    currentplayers.forEach(function (item, index){
        playernames.push(item.name)
    })

验证完成后,服务器会向客户端发送成功响应。如果姓名无效,则会发送失败响应。 

更新位置

玩家进入游戏后,每隔 0.1 秒就会向服务器发送一个 POST 请求。请求中将包含他们的姓名、位置和旋转。服务器收到请求后,会将当前时间添加到 JSON 中,并将 JSON 设置到 Redis 数据库中。 

客户端收到数据后,会更新所有连接到游戏的玩家的位置。 

更新排行榜

玩家完成一圈后,会向服务器发送一个 POST 请求,其中包含重要信息,例如他们的姓名以及完成一圈所花费的时间。在此之前,服务器会访问并包含玩家之前在 Redis 数据库中的圈时间。 

const namekey = `_${myjson.name}`
client.json_get("leaderboard", `.${namekey}`, function (err, results) {

此时,服务器会检查这是否是玩家提交的第一圈时间。正如您所期望的那样,如果玩家已记录了之前的圈时间,则服务器会将其与新时间进行比较,以查看它是否更快。如果是,则会将新时间设置到数据库中的排行榜 JSON 中。 

if (oldtime) {
    oldtime = JSON.parse(oldtime)
    if (myjson.time < oldtime.laptime) {
        client.json_set("leaderboard", namekey, JSON.stringify(data))
    }
}

但是,如果这是他们的第一次,则会记录他们的姓名和时间并将其插入排行榜中。 

else {
    client.json_set("leaderboard", namekey, JSON.stringify(data))
}

最后,服务器将访问整个排行榜并向游戏客户端发送响应。

client.json_get("leaderboard", function( err, results) {
    res.send(Object.values(JSON.parse(results)))
})

此时,您将能够使用数据创建排行榜。

移除断开连接的玩家

在此应用程序中,服务器将每 30 秒从 Redis 接收每个玩家的 JSON 数据。

client.json_get("players", function (err, results) {

当玩家关闭游戏时,游戏客户端将停止发送更新的位置请求。这一点很重要,因为服务器会自行检查以查看每个玩家是否仍连接到游戏。

服务器通过循环遍历所有玩家以查看他们的最后更新时间是否超过 30 秒来实现这一点。如果是,则这些玩家将从玩家 JSON 和排行榜 JSON 中删除。

playerdata.forEach(function (item, index) {
  var namepath = `._${item.name}`
  var seconds = Math.floor((new Date() - Date.parse(item.lastping)) / 1000);
  if (seconds > 30) {
    client.json_del('players', namepath)
    client.json_del('leaderboard', namepath)
    console.log(`Removed ${item.name}`)
  }
});

游戏客户端也会进行相同的检查。如果收到最后更新时间超过 30 秒,则其汽车对象将被销毁。

if (lastPing.AddSeconds(30) < DateTime.Now) {
    isAFK = true;
    GameManager gameManager = GameObject.Find("GameManager").GetComponent<GameManager>();
    gameManager.StartGetLeaderboard();
    gameManager.RemovePlayer(this);
    Destroy(gameObject);
}

结论:准备就绪,稳住...Redis!

赛车游戏仍然是游戏行业中的热门选择。我们先有了马里奥赛车,然后有了极品飞车,现在我们有了可以自己用笔记本电脑创建的东西。借助 Redis,此应用程序能够实时运行,并通过允许用户在没有任何延迟的情况下进行战斗来最大限度地提升游戏体验。

但这还不是全部...

来自世界各地的创新程序员正在利用 Redis 的奇观来创建对社会产生自身影响的非凡应用程序。

无论您只是想玩得开心,还是想构建一些可以改善日常生活的东西,Redis 都可以帮助您的项目雄心勃勃地实现。请务必查看 Redis Launchpad以获得更多灵感。

或者,如果您想了解有关此应用程序的制作方式的更多信息,则可以通过点击此处观看完整的 YouTube 视频。

谁创建了此应用程序?

格雷厄姆·平森特

Graeme 是一名 DevOps 工程师,活跃于博彩和体育博彩行业。请务必前往他的GitHub 页面以查看他参与的其他项目。