dot Redis 8 来了——而且是开源的

了解更多

如何使用 Unity 和 Redis 构建有竞争力的在线赛车游戏

如果你喜欢游戏并热衷于创造,那么你可以从这个 Launchpad 应用中获得灵感。通过使用 Redis,Graham Pinsent 创建了他自己的在线赛车游戏,朋友、家人和同事可以在游戏中一决高下,争夺终点线. 

就像任何赛车游戏一样,最终目标是以最快的速度从 A 点到达 B 点。玩家会得到一辆车,时间会被记录下来,获胜者将根据谁能最快地完成每一圈来决定。

但是,为了使此应用程序发挥最佳功能,必须以超高效的方式传输数据,以确保玩家的命令得到实时执行。 无法实现这一点会在玩家的命令及其游戏中的车辆之间造成脱节,从而导致令人沮丧的游戏体验. 

为了防止这种情况发生,Graham 利用 Redis 的高级功能创建了一个应用程序,在该应用程序中,组件能够以最高的效率在彼此之间无缝传输数据. 

让我们调查一下这是如何完成的。 我们还有许多由 Redis 社区创建的令人兴奋的应用程序供你查看。 请务必在Redis Launchpad上观看所有操作. 

https://www.youtube.com/embed/Tq3yoIn4hLc
  1. 你将构建什么?
  2. 你需要什么?
  3. 架构
  4. 入门
  5. 它是如何工作的

1. 你将构建什么?

你将构建一个在线赛车游戏,允许玩家使用 Redis 相互竞争。 下面,我们将按时间顺序介绍每个阶段,并突出显示你需要哪些组件才能使此应用程序栩栩如生. 

准备好开始了? 好的,让我们直接进入. 

2. 你需要什么?

  • NodeJS用作一个开源的、跨平台的平台,可以在 Web 浏览器之外执行 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 中,并在 Redis 数据库中设置 JSON. 

一旦客户端收到数据,它将更新连接到游戏的每个玩家的位置. 

更新排行榜

每当玩家完成一圈时,都会向服务器发送一个 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 视频。

谁构建了这个应用程序?

Graham Pinsent

Graeme 是一位在赌博和体育博彩行业工作的 DevOps 工程师。 请务必访问他的 GitHub 页面,了解他还参与了哪些其他项目。