返回

从「复活」和「暂停/恢复」谈游戏数据配置管理

随着游戏制作技术的不断发展,在经历了从 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 等,可是这样做的缺点同样很明显,从本地读取数据库会消耗大量的资源,而且数据库文件一旦丢失,整个游戏都将无法运行。

动态数据

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

总结

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

Built with Hugo v0.110.0
Theme Stack designed by Jimmy
已创作 265 篇文章,共计 1000946 字