惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyberwarzone
Cyberwarzone
F
Full Disclosure
V
Visual Studio Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
有赞技术团队
有赞技术团队
J
Java Code Geeks
博客园 - 【当耐特】
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
L
LINUX DO - 最新话题
T
Threatpost
S
SegmentFault 最新的问题
Vercel News
Vercel News
云风的 BLOG
云风的 BLOG
C
Cyber Attacks, Cyber Crime and Cyber Security
Google DeepMind News
Google DeepMind News
Know Your Adversary
Know Your Adversary
S
Schneier on Security
V
Vulnerabilities – Threatpost
D
DataBreaches.Net
G
GRAHAM CLULEY
Latest news
Latest news
P
Privacy International News Feed
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
Scott Helme
Scott Helme
L
Lohrmann on Cybersecurity
T
The Exploit Database - CXSecurity.com
Security Latest
Security Latest
G
Google Developers Blog
L
LangChain Blog
MyScale Blog
MyScale Blog
Project Zero
Project Zero
N
News and Events Feed by Topic
Hacker News - Newest:
Hacker News - Newest: "LLM"
大猫的无限游戏
大猫的无限游戏
P
Proofpoint News Feed
Blog — PlanetScale
Blog — PlanetScale
阮一峰的网络日志
阮一峰的网络日志
N
News | PayPal Newsroom
www.infosecurity-magazine.com
www.infosecurity-magazine.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
SecWiki News
SecWiki News
T
Tor Project blog
C
Check Point Blog
Google Online Security Blog
Google Online Security Blog
GbyAI
GbyAI
The Last Watchdog
The Last Watchdog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
WordPress大学
WordPress大学

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角
从「复活」和「暂停/恢复」谈游戏数据配置管理 - 元视角
飞鸿踏雪 · 2015-03-27 · via 元视角

随着游戏制作技术的不断发展,在经历了从 2D 到 3D、从单机到网游、从 PC 游戏到移动游戏的种种演变后,玩家对于游戏质量的要求越来越高,游戏制作的难度相应地增加,整个游戏研发的体系开始变得庞大而复杂,由此就产生了游戏数据配置和管理的相关问题。本文将从游戏中的"复活"和"暂停/恢复"这两个应用场景的角度来谈谈在游戏开发中如何对游戏中的数据进行管理和配置。

为什么要谈游戏数据的配置和管理

不知道大家是不是会和博主有一样的想法,就是当你回头来思考游戏开发的时候,你常常会发现,如果忽略游戏的画面、情节、特效等等这些游戏中的可视化的东西,那么其实游戏从本质上来说就是一个大型的有限状态机(FSM),而我们通常所做的事情基本就是在维护这个有限状态机里面的各种状态,从游戏加载到游戏开始、从游戏开始到游戏中各种事件的发生再到各种事件影响到整个有限状态机的状态,我们通常所做的事情无外乎是在维护各种状态。这种感觉在 RPG 游戏中可能会更明显些,因为在 RPG 中玩家可能是在场景中行走或者奔跑、可能是在和场景中的某个 NPC 进行对话、可能是在和面前的敌人进行战斗、可能是在和杂货店的老板讨价还价……可以说在整个游戏当中无时无刻不在进行游戏状态的切换,那么在不同的状态间切换的时候,什么最为重要呢?答案是数据。什么是数据呢?玩家的生命值、魔法值、战斗力、防御力,物品的用途、价格、数量,游戏的剧情、对话、音乐等等这些都是数据。当我们在状态间进行切换的时候,其实真正改变的就是这些数据。由此可见,面对复杂而庞大的游戏体系,如何对游戏中的数据进行配置和管理是一件值得我们去思考的问题。

从应用场景来看游戏数据的配置与管理

首先我们来从游戏当中的两个常见的应用场景:“复活"和"暂停/恢复"来看看游戏数据配置和管理的重要性。 这里以博主的一款跑酷游戏为例:

游戏截图 游戏截图

应用场景——“复活”

“复活"是一个在游戏中特别常见的功能,复活这一设定的好处在于无需重新开始游戏就能再次回到游戏当中,当然这只是我们最为直观的一个感受,更为深刻的原因是,游戏者巧妙地利用了玩家在游戏任务失败那一刻的心理。现在生活中每一个人都喜欢胜利,这种心理到了游戏世界中同样是适用的,因为游戏的目的无非就是让玩家有种成就感以获得快乐。可是当游戏任务失败的时候,玩家会竭尽全力不断尝试去打败 Boss 以获得游戏的胜利,因此在游戏中有这样一个设定,可以引导玩家在游戏中形成消费的习惯,这样游戏就能从玩家身上盈利。好了,我们来看看一个基本的"复活"的逻辑吧!

private void Update()
{
    //如果玩家的生命值大于0则游戏正常进行
    if(Player.Hp>0)
    { 
       //游戏状态为Normal
       GameManager.Instance.GameState=GameStateEnum.Normal;
       //执行正常的游戏逻辑
       DoNormalEvent();
    }else
    {
       //游戏状态为Over
       GameManager.Instance.GameState=GameStateEnum.Over;
       //显示GameOver
       ShowGameOver();
       //玩家复活
       ReLive()
    }
}

玩家复活需要做两件事情:

  • 将游戏的状态从 Over 调整到 Normal
  • 将玩家的状态从死亡调整到正常

调整游戏的状态特别容易,因为 GameManager 是一个典型的单例模式,因此我们可以直接将 GameState 从 Over 变成 Noral。可是对于玩家状态的调整,我们却遇到了困难。问题出在什么地方呢?问题出在我们将玩家的生命值等一系列属性都写在了 PlayerController 这个类中,如果我们将玩家的属性全部都设为 Private,那么我们将无法从外部来调整这些属性。比如我们想让玩家满血复活,可是因为这些属性都是私有的,我们无法从外部访问,所以我们在给玩家恢复生命值的时候,无法获得玩家当前的生命值以及最大生命值。可是如果我们将玩家的属性全部都设为 Public,我们可能不得不去面对在编辑器窗口中为每一个属性去赋值,因为一旦我们试图调整游戏双方力量的平衡时,这将是我们不得不去面对的问题,更为致命的玩家的属性并不是永远不变的,比如在 RPG 游戏中玩家的生命值等属性会随着角色等级的提升而不断增加。因此不管我们将这些属性设为 Public 还是 Private,我们都无法保证每次访问到的这些数据都是最新的数据。换句话说,我们不能想当然地在脚本中将玩家的属性写成一个不变的值,因为这些数据随时都在发生着变化,当然如果像敌人和 Boss 这种数值相对稳定的情况,我们可以直接在脚本中将其写成一个固定值,不过我并不推荐大家这样做。由此可见,游戏中数据配置和管理的一个重要作用是维持各个状态间的正常切换。如图是雨血前传.蜃楼中的复活界面,每次复活需要消耗一个复活玉:

雨血 雨血

那么博主在这款跑酷游戏里面是怎样做这个复活的呢?因为博主当时在设计这个游戏的时候考虑不周,直接将玩家的生命值写成了 100,所以在复活玩家时候,同样是先将游戏的状态调整过来,然后再将相关的 GUI 窗口隐藏,然后将玩家的生命值重新设置为 100,重新生成玩家就好了。正是因为感觉这段时间做游戏缺乏一种良好的游戏架构,所以每次游戏做到最后都是自己把逼到了绝路上,留给了自己一个自己都不想再去维护的烂摊子,这样显然是不好的,所以以后需要在正式动手写代码前做好规划,相信这样就能够保证游戏的质量了吧!任何东西学习到一定阶段都会遭遇瓶颈,尽管打破这种瓶颈的过程是痛苦的,可是如果不去打破它,那么你永远都只能停留在这个位置。

应用场景——“暂停/恢复”

和"复活"一样,“暂停/恢复"同样是一个在游戏中常见的功能,该功能是给了玩家暂时离开游戏的一种选择,可以保证玩家在做其它事情的时候不会影响到游戏的进程。比如在仙剑奇侠传、古剑奇谭等游戏中,玩家可以按下 ESC 键调出游戏设置界面,在玩家进入游戏设置界面的这段时间,游戏世界里的时间似乎是静止的,场景中的敌人不会因为玩家在查看系统设置界面就去主动偷袭玩家,因为这种情况下游戏是暂停的。而当玩家退出系统设置界面后,游戏恢复为正常状态。到了移动互联网时代,游戏中出现"暂停/恢复"的情况更为普遍,这是由移动互联网时代人们玩游戏更注重休闲和娱乐这样的性质来决定的。记得天天酷跑刚刚在微信上线的那段时间,我身边好多同学都在上课的时候玩,可是因为这游戏一跑起来就根本停不下来,所以经常是一次游戏玩下来一节课就结束了。博主不提倡这样啊,玩游戏归玩游戏,可是什么事情都要有个度啊,不然就会变成玩物丧志。好了,我们分析这个案例的目的无非就是想告诉大家在游戏里增加这样一个"暂停/恢复"的功能还是十分必要的。好了,现在我们来分析下在这个应用场景中发生状态转换的时候都会牵扯到那些数据吧!

首先,游戏暂停后,场景内所有的物体都会停止运动,此时游戏中每个物体的状态都发生了变化,不过因为在 Unity3D 中控制游戏暂停/的恢复主要是通过调整 Time.timeScale 的值来实现的。当 Time.timeScale 取值为 0 时,游戏暂停;当 Time.timeScale 取值为 1 时,游戏恢复正常。不过需要注意的是 Time.timeScale 会对 Unity3D 中所有的时间产生影响如 FixedUpdate()、协程、Destroy()、动画组件等等,所以如果对暂停后的游戏状态有特殊要求的话,建议还是通过其它的方法来实现吧!这里没有提到 Update() 和 LaterUpdate() 这是因为这两个方法不会受到影响。我们来看这样一段代码:

//游戏是否暂停
private bool isPause = false;

//暂停/恢复游戏的方法
private void Resume()
{
    if (!isPause) {
       Time.timeScale = 0;
       isPause = true;
    } else {
       Time.timeScale = 1;
       isPause = false;
    }
}

通过这段代码我们就能够实现一个基本的游戏"暂停/恢复"的功能。在游戏管理类 GameManager 中我们定义了一个玩家的得分。正常情况下,当玩家没有死亡的时候会在 GUI 中更新玩家的得分,而玩家的得分是直接采用在 Update()中累加的方式实现的,因此玩家的得分会在游戏暂停后继续更新,这当然是不符合实际情况的,因此可以在这个增量前乘上一个 Time.deltaTime 就可以解决这个问题了。博主举这个例子无非就是想告诉大家使用这种方法来暂停游戏会存在这样的问题,希望大家以后注意啊!

跑酷游戏复活界面 跑酷游戏复活界面

游戏数据配置和管理的思路和方法

既然我们在今天的的文章中主要阐述的就是游戏数据配置和管理,那么下面我们就来说说游戏数据配置和管理的常见的思路和方法。根据游戏中数据变动的相对大小,我们将游戏中的数据分为静态数据和动态数据两类。

静态数据

静态数据是指在游戏中基本不变或者不需要变动的数据。比如游戏中 Boss 的等级和生命值一般都是确定的,因此这种类型的数据可以称为静态数据。同样地,游戏中 NPC 对话的内容是一种静态数据,因为 NPC 的对话内容是在设计剧情的时候就设计好的无需再对它进行修改。那么对于静态数据,我们可以考虑下列方法:

  • 将静态数据作为常量定义在一个类中,这样做的好处是无需对每一个脚本进行修改。
  • 将静态数据存储在文件当中,这样做的好处是可以对数据进行管理,缺点是需要针对不同的文件编写解析接口,游戏开发中常用的数据存储形式有:Json、Xml、Excel、CSV 等。
  • 将静态数据存储在数据库当中,如 SQLIite 等,可是这样做的缺点同样很明显,从本地读取数据库会消耗大量的资源,而且数据库文件一旦丢失,整个游戏都将无法运行。

动态数据

动态数据是指在游戏中会不断变化的数据,比如玩家的得分、玩家的生命值、玩家的经验值等等。动态数据的处理方式除作为常量写在类中以外,其它的都和静态数据是一样的,在此就不再多说了。

总结

可能今天这篇文章显得唠叨些,甚至从技术的角度来看,这篇文章都没有讲到什么有价值的技术要点。可是在博主看来,不管一项技术有多么伟大,如果没有良好的架构或者说结构,那么当这个项目的规模到了一定程度以后,这个项目就会出现问题。因为根据破窗户理论,当你看到窗户破了而不去及时修补的话,那么时间一长你破掉的就是整个房子了。回顾博主这么长时间的游戏开发,其实做过的好多游戏到最后之所以没有做完,都是因为到最后项目基本失控、变成了一个连自己都不愿意去维护的项目,这样的情况是可怕的。平时是你一个人做项目,可能你觉得这些都没有什么,可是当你和别人一起去完成这样一个项目的时候,你的这些问题都会成为整个团队的问题。博主一直想知道自己做游戏和团队在一起做游戏会有什么不同,因为博主感觉自己在这一块确实不是掌握得很好。虽然说架构这种事情你做多了才会有经验,可是你现在发现了问题,为什么不在现在改掉呢?架构真的很重要,致那些因为架构死去的项目,真正的项目应该死在实践中,因为架构的问题最终变得不可收拾的,这件事情本身就是可耻的。好了,今天就说这么多了。