本帖最后由 小篱 于 2015-12-21 18:19 编辑
一、锻造系统对于玩家和开发者的作用
武器/道具锻造系统很早以前就是MMORPG中非常重要的一部分,它丰富了原本空洞而单调的游戏世界。然而,对于这样一种有效的娱乐手段,锻造系统的发展史却并非一帆风顺。以前的游戏总是充满各种刷任务机制,无所用处的组件,并强迫玩家使用无聊的锻造系统,甚至完全采纳毫无用处的交易技能。
锻造系统也可以是维系着玩家与开发者关系的一个“弹簧”。如果玩家自己创造的内容与游戏所提供的道具力量不均,那么锻造者和非锻造者就会认为锻造系统是无用的,而不再与其他玩家进行比较,并责怪开发者糟糕的设计。
如何才能避免这种矛盾?矛盾的根源在于,锻造对于开发者的作用与其对于玩家的作用是完全不同的。而平衡双方需求便是化解这种矛盾的好方法,并且能够帮助游戏创造出一个合适的锻造系统。
所以,锻造系统对于玩家有何作用?
有助于缓解刷任务现象
对于玩家来说这应该说是最重要的作用吧。玩家总是喜欢变化,如果一款游戏只是让玩家一整天都在打怪,而没有其它任务,他们肯定会对此感到厌烦并最终退出游戏。而锻造能够在原本的杀戮-抢夺-杀戮模式中为玩家提供替换式选择,让他们能够以不同的方式控制角色的发展。
同时开发者还应该确保锻造的易用性。这就意味着玩家能够同时拥有战斗技巧和锻造技巧,并且两者间不会产生相互抵触。玩家不需要非得从中做出选择。同时锻造还需要具有吸引力,不论是资料还是产品,都应包含有趣的选择。肯定没有哪个玩家希望用一种更无聊的刷任务机制取代原先的刷任务机制吧。
它是一种道具和货币来源
玩家可以从自己所创造的道具中获得更多可观的收益。甚至,如果玩家能够使用自己锻造的道具,他们便会萌生出一种自力更生感,即通过自己的努力而不断增强战斗技巧。
而对于开发者来说,锻造道具应该能够有效地说服玩家愿意花费他们辛苦所得的货币。这时候他们就需要想办法保持锻造产品与游戏掉落道具之间的平衡。一大策略 便是在锻造中包含非装备性质的选项。如锻造一些能够消耗的物品,如药剂,以确保需求的稳定,以及强化装备威力的功能,如宝石或者能够让锻造者提升道具(游 戏邦注:可同时适用于游戏掉落道具、玩家自己锻造的道具)等。按照这种方法,获得最佳装备的角色便能够同时地利用两种游戏玩法的优势。
吸引建造和社交型玩家
有些玩家喜欢游戏中的破坏,杀戮和掠夺;也有一些玩家更喜欢在城市中闲逛,与人交流并创造神奇的道具。他们会喜欢锻造所推动的相互依赖和相互交流,愿意与合作伙伴和好友分享并共同创造资源。
这些玩家便是这类游戏最重要的玩家基础,游戏应该想办法留住这些玩家。开发者应该努力创造一个具有吸引力且有趣的锻造系统,但是却不应该强求所有玩家不得不关注锻造系统。一些系统采用即时战斗类行动而取代无聊的进度条。但是这么做却适得其反,因为这会强迫玩家不得不在锻造道具时停止社交行为。相反地,锻造应该更加灵活,让玩家能够按照自己的节奏进行。而这些有趣的行动应该在玩家点击“创造”之前就表现出来,例如在选择特定资源,资料或使用方法的时候。
创造有效的自定义机制
锻造功能能够按照不同方式吸引不同类型玩家的注意。对于那些喜欢角色创造的玩家来说,交易技能能够让角色更加与众不同。对于社交游戏玩家来说,他们能够锻造装饰性道具去自定义角色和环境的外观。对于战斗策略型玩家来说,定制属于自己的装备能够增强角色的实力并提高战斗效率。
开发者总是会忽视自定义机制,因为这需要花费他们更多的额外成本。但是如此做法所造成的损失却是我们不容小觑的。如果锻造者能够定制角色或道具的外观,这可以进一步增强玩家的沉浸感。游戏掉落的道具只有原来的固定属性,而如果锻造道具的配方也只有一种固定属性,那么对玩家来说,锻造道具与游戏掉落道具之间并无多大区别,其吸引力也将大打折扣。
那么,锻造对于开发者来说有何作用?
强化游戏的合理性
让人悲哀的是,很多开发者只是因为外部需求而在游戏中添加锻造机制。如此心态最终只能够创造出糟糕的设计系统,并破坏了整款游戏的合理性。
在设计一款强调用户留存的游戏,开发者在设计锻造系统时更是应该仔细考虑玩家的想法。如果游戏的目标是吸引特定玩家类型,如休闲玩家或战斗型玩家,游戏可以适当放松锻造系统的作用,但是同时应该添加能够创造出同等利益的功能。如果锻造系统是游戏所必须的内容,开发者就应该关注预算分配等问题,而如果玩家不喜欢你的锻造系统,那就只是徒劳无功的做法。
它能让玩家投入更多游戏时间
MMO中的任何系统都必须对玩家具有吸引力,并让他们愿意长时间玩游戏。锻造系统其实也是玩家角色发展的体现,结合战斗进程更能够调动玩家的积极性,在游戏中投入更多时间。
但是如果使用不合理,这种理念将会导致玩家不得不在锻造系统中耗费更多时间。不必要的刷任务机制,较长的进度条以及对失败的严厉惩罚都会导致玩家在锻造中投入更多时间,但是同时却会导致玩家在游戏中产生更多挫败感。带有过多挫败感的玩家很可能随时停止玩游戏。为了避免这种情况,游戏应该确保玩家始终能够感受到某种意义上的进步。或许可以让他们按照配方上的材料或其他交易技能中的组件来锻造道具,或者通过经验值而非随机性的技能增长,让他们获得这种进步感。
创造玩家间的相互依赖感
作为一种多人游戏,MMO自然应该包含推动玩家交流与协作的机制。而锻造系统便具有这种功效。它让玩家能够为好友创造及增强道具能量,同时还能够与他们分享所得的资源和材料。
创造玩家间相互协作的一大方法便是引入一种包括稀有掉落道具(或由其他交易技能锻造材料)的配方,但这种配方必须具有特殊性,能够制造出更多有益的道具。它不需要特别要求玩家为了获得一些基本道具进行协作,更不能对玩家做出升级技能的要求,否则只会让玩家因更多时间和金钱投入而备受挫折。
创造强有力的经济系统
强大的经济系统能够提高游戏的用户粘性,而如果开发者允许游戏内部的货币交易,他们便有可能从中获得更多实在的利益。锻造系统是玩家间一种重要的交易方式。除此之外,它让毛皮,尖牙,宝石等能够用于商业交易的材料更凸显其价值,并有效缓解游戏经济系统中的通货鼓胀现象。
使用锻造推动经济发展的一大方法便是,引进可以让让所有玩家都受益的道具配方。其次就是提供多种材料的来源,例如交易商品,掉落的道具(稀有和普遍的),资源节点以及其它配方。设计适用于多种配方的材料以提升它们的需求量,同时增加仅适用于一种配方但却极有用处的掉落道具数量。
二、为怪物掉落道具编写程序的经验
动作游戏中的一个普遍机制就是让敌人在临死前掉落一些道具或者奖励。角色就可以拾取这些战利品从而增加自己的优势。这是包括RPG在内的许多游戏都有的一个机制,它给予玩家一个除掉敌人的动机——以及看到即时奖励时的一点兴奋感。
在本篇教程中,我们将查看这些机制的内在运行机制,以及如果用你所使用的编码工具/语言将其植入游戏中。
我使用Construct 2这种HTML5游戏开发工具来展示这方面的例子,但其使用工具并不局限于此。无论你使用哪种编程语言或工具,应该都能够植入同样的机制。
这些例子是由r167.2所制作,你可以在其软件免费版本中打开和进行编辑。你还可以下载Construct最新版本来操作这些例子。
基本机制
在敌人死时的那一刻(它的HP已经极大减少,或者接近于0)会调用一个函数。该函数的作用就是确定敌人是否存在掉落物品,如果有,又该掉落哪种道具。
该函数还可以处理掉落物品的可视化表现形式, 令其协同敌人在前方屏幕中生成。
看看以下的例子。
0_ Owl is slain, and drops Lollipop.
1_ Goblin is slain, and drops Gold.
2_ Mastodon is slain.
3_ Mastodon is slain, and drops Gold.
4_ Squire is slain, and drops Lollipop.
5_ Owl is slain.
6_ ZogZog is slain, and drops Lollipop.
7_ Owl is slain.
8_ Mastodon is slain.
9_ Owl is slain.
10_ Goblin is slain, and drops Lollipop.
11_ ZogZog is slain.
12_ ZogZog is slain, and drops Lollipop.
13_ Squire is slain.
14_ Mastodon is slain.
15_ Boar is slain, and drops Lollipop.
16_ Mastodon is slain.
17_ Boar is slain, and drops Lollipop.
18_ Boar is slain, and drops Lollipop.
19_ Boar is slain, and drops Lollipop.
20_ Boar is slain, and drops Lollipop.
21_ Boar is slain, and drops Lollipop.
22_ Mastodon is slain, and drops Rocks.
23_ Boar is slain, and drops Lollipop.
24_ ZogZog is slain, and drops Lollipop.
25_ Squire is slain, and drops Lollipop.
26_ Boar is slain, and drops Lollipop.
27_ Squire is slain, and drops Gold.
28_ Owl is slain.
29_ Squire is slain.
30_ Squire is slain.
31_ ZogZog is slain, and drops Lollipop.
32_ Owl is slain, and drops Jewel.
33_ Squire is slain.
34_ Mastodon is slain, and drops Rocks.
35_ Owl is slain.
36_ Owl is slain.
37_ Owl is slain.
38_ ZogZog is slain.
39_ Goblin is slain.
40_ Mastodon is slain.
41_ Boar is slain, and drops Lollipop.
42_ Boar is slain, and drops Lollipop.
43_ ZogZog is slain, and drops Lollipop.
44_ Owl is slain.
45_ Owl is slain.
46_ Mastodon is slain, and drops Rocks.
47_ Squire is slain, and drops Gold.
48_ ZogZog is slain, and drops Lollipop.
49_ Squire is slain, and drops Gold.
50_ Goblin is slain, and drops Lollipop.
51_ Owl is slain.
52_ Mastodon is slain.
53_ Mastodon is slain, and drops Lollipop.
54_ Squire is slain, and drops Gold.
55_ Goblin is slain, and drops Lollipop.
56_ Mastodon is slain.
57_ ZogZog is slain, and drops Lollipop.
58_ Goblin is slain, and drops Lollipop.
59_ ZogZog is slain.
60_ ZogZog is slain.
61_ ZogZog is slain, and drops Lollipop.
62_ Boar is slain, and drops Lollipop.
63_ Goblin is slain, and drops Lollipop.
64_ Squire is slain, and drops Lollipop.
65_ Goblin is slain.
66_ ZogZog is slain, and drops Lollipop.
67_ Owl is slain, and drops Equipment.
68_ Boar is slain, and drops Lollipop.
69_ Boar is slain, and drops Lollipop.
70_ Squire is slain, and drops Gold.
71_ Owl is slain.
72_ Owl is slain.
73_ Goblin is slain, and drops Lollipop.
74_ ZogZog is slain, and drops Lollipop.
75_ ZogZog is slain, and drops Lollipop.
76_ Owl is slain, and drops Equipment.
77_ Goblin is slain, and drops Lollipop.
78_ Boar is slain, and drops Lollipop.
79_ Owl is slain.
80_ ZogZog is slain, and drops Lollipop.
81_ ZogZog is slain.
82_ ZogZog is slain, and drops Lollipop.
83_ Mastodon is slain.
84_ Owl is slain, and drops Equipment.
85_ Mastodon is slain.
86_ Squire is slain.
87_ Mastodon is slain.
88_ Boar is slain, and drops Lollipop.
89_ ZogZog is slain.
90_ ZogZog is slain, and drops Lollipop.
91_ ZogZog is slain.
92_ Mastodon is slain.
93_ Boar is slain, and drops Lollipop.
94_ Goblin is slain.
95_ Owl is slain, and drops Rocks.
96_ Mastodon is slain.
97_ ZogZog is slain, and drops Lollipop.
98_ Mastodon is slain.
99_ Squire is slain, and drops Lollipop.
这将执行一个创造100个随机怪物,并杀死它们,然后展示每个怪物是否掉落道具这一结果的批量过程。屏幕底部将展示有多少怪物掉落道具,以及掉落了多少种类型的道具。
这个例子用文本来解释是为了呈现了该函数背后的逻辑,以及展示该机制适用于任何类型的游戏,无论是踩踏敌人的平台游戏,还是上下视角的射击游戏,或是RPG。
让我们看看这个样本的运行方式。首先,怪物和掉落道具都有各自的阵列。以下是怪物阵列:
这是掉落道具阵列:
Index列中的X值是怪物或道具类型的独特标识符。例如,指数为0的怪物就是一只野猪。而指数为3的道具则是一个宝石。
这些阵列便于我们查找表格,它们包括每个怪物或道具的名称和类型,以及将允许我们确定稀有性或掉落率的其他值。在怪物阵列中,其名称后还有另外两个栏目:
掉落率是怪物被杀死时掉落一项道具的机率。例如,野猪被杀死时有100%的道具掉落率,而猫头鹰的这一机率仅为15%。
稀有性决定了这只怪物掉落某项道具的低概率。例如,野猪可能掉落某项稀有值为100的道具。现在,让我们看看drops阵列。我们可以看到岩石是拥有最大稀有值(95)的道具(游戏邦注:虽然这里的稀有值很高,但作者编写这一函数时,为更普遍的道具设置了更大的稀有值。也就是说,怪物掉落岩石的机率高于稀有值较低的道具。)
从游戏设计角度来看,这对于我们来说非常有趣。为了游戏获得平衡,我们并不希望让玩家过早接触太多装备或过多高端道具——否则,角色可能会过早变得太强大,游戏也就不再那么有趣了。
这些表格和数值只是一些例子,你可以根据自己的游戏系统和环境来进行调整,一切取决于你的系统平衡性。
让我们看看样本的伪代码:
- CONSTANT BEAST_NAME = 0
- CONSTANT BEAST_DROPRATE = 1
- CONSTANT BEAST_RARITY = 2
- CONSTANT DROP_NAME = 0
- CONSTANT DROP_RATE = 1
- //Those constants are used for a better readability of the arrays
- On start of the project, fill the arrays with the correct values
- array aBeast(6,3) //The array that contains the values for each beast
- array aDrop(6,2) //The array that contains the values for each item
- array aTemp(0) //A temporary array that will allow us what item type to drop
- array aStats(6) //The array that will contain the amount of each item dropped
- On button clicked
- Call function “SlainBeast(100)”
- Function SlainBest (Repetitions)
- int BeastDrops = 0 //The variable that will keep the count of how many beasts did drop item
- Text.text = “”
- aStats().clear //Resets all the values contained in this array to make new statistics for the current batch
- Repeat Repetitions times
- int BeastType
- int DropChance
- int Rarity
- BeastType = Random(6) //Since we have 6 beasts in our array
- Rarity = aBeast(BeastType, BEAST_RARITY) //Get the rarity of items the beast should drop from the aBeast array
- DropChance = ceil(random(100)) //Picks a number between 0 and 100)
- Text.text = Text.text & loopindex & ” _ ” & aBeast(BeastType,BEAST_NAME) & “is slain”
- If DropChance > aBeast(BeastType,BEAST_DROPRATE)
- //The DropChance is bigger than the droprate for this beast
- Text.text = Text.text & “.” & newline
- //We stop here, this beast is considered to not have dropped an item.
- If DropChance <= aBeast(BeastType,BEAST_DROPRATE)
- Text.text = Text.Text & ” dropping ” //We will put some text to display what item was dropped
- //On the other hand, DropChance is less or equal the droprate for this beast
- aTemp(0) //We clear/clean the aTemp array in which we will push entries to determine what item type to drop
- For a = 0 to aDrop.Width //We will loop through every elements of the aDrop array
- aDrop(a,DROP_RATE) >= Rarity //When the item drop rate is greater or equal the expected Rarity
- Push aTemp,a //We put the current a index in the temp array. We know that this index is a possible item type to drop
- int DropType
- DropType = random(aTemp.width) //The DropType is one of the indexes contained in the temporary array
- Text.text = Text.text & aDrop(DropType, DROP_NAME) & “.” & newline //We display the item name that was dropped
- //We do some statistics
- aStats(DropType) = aStats(DropType) + 1
- BeastDrops = BeastDrops + 1
- TextStats.Text = BeastDrops & ” beasts dropped items.” & newline
- For a = 0 to aStats.width //Display each item amount that was dropped
- and aStats(a) > 0
- TextStats.Text = TextStats.Text & aStats(a) & ” ” & aDrop(a,DROP_NAME) & ” “
复制代码
首先,是用户行为:点其Slay 100 Beasts可以调用一个参数为100的函数,当然在真正的游戏中,你可能一次只会杀死一只怪物。
由此开始,调用SlainBeast函数。其目的展示一些文本,给予用户反馈让他们知道发生了什么情况。首先,它会清除BeastDrops变量以及aStats阵列。在真正的游戏中,你不可能会需要这些阵列。它也会清除Text,这样你就可以看到这个批量数据的结果。函数本身会创造三个数值变量:BeastType, DropChance和Rarity。
BeastType将成为我们涉及aBeast阵列的特定行,它实际上是玩家必须面对和杀戮的怪物类型。Rarity也同样取自aBeast阵列,它是该怪物应该掉落的道具稀有性,道具稀有值位于aBeast阵列中。
最后,DropChance则是我们随机从0到100挑选出来的一个数值(游戏邦注:多数编程语言都有一个从某个范围随机获取一个数值的函数,或者至少是从0到1获得一个随机数值,之后你可以将其简单地乘以100)。
在这种情况下,我们可以展示位于Text对象中的一点信息:我们已经知道会生成和杀死哪种怪物。所以,我们要将从aBeast阵列中随机挑选的当前BeastType的BEAST_NAME与当前的Text.text值连接起来。
接下来,我们必须确定某项道具是否应该掉落。我们可以通过对比来自aBeast阵列的DropChance值与BEAST_DROPRATE值来实现这一点。如果DropChance少于或者等同于这个值,我们就要掉落一个道具。
(注:你可以选择“多于或等同于这个值”的方法来编写这个函数,这只是一个数值和逻辑上的问题。但是,要保持算法的一贯性,不要半途更改逻辑——否则,你可能就会在调试或维护的时候产生问题。)
要用两行代码来确定某项道具是否掉落,首先:
- DropChance > aBeast(BeastType,BEAST_DROPRATE)
复制代码
这里的DropChance在少于或等同于当前BeastType中的DropRate,所以我们可以认为这意味着一项道具会掉落。为此,我们将运行当前BeastType“允许”掉落道具的Rarity对比,以及我们已经在aDrop表格中设置好的一些稀有值。
我们查看aDrop表格,查找每个索引以便找到其DROP_RATE是否大于或等同于Rarity。(记住,Rarity值越高,该道
具就越普遍)针对匹配该对比的每个索引,我们会将该索引推向一个临时阵列aTemp。
这DropChance远比DropRate更大,我们认为这意味着没有道具会掉落。由此开始,唯一展现的东西就是句子末尾的一个全角句号“.”在移向我们批次的下一个敌人之前,“[BeastType]被杀掉”。
另一方面:
- DropChance <= aBeast(BeastType,BEAST_DROPRATE)
复制代码
在循环的最后,我们应该至少在aTemp阵列中设置一个索引。(否则,我们就必须重新设计aDrop和aBeast表格了!)我们之后可以制作一个新的数值变量DropType,从aTemp阵列中随机挑选一个索引。这就是我们将会掉落的道具。
我们要在文本对象中添加道具名称,形成类似“BeastType被杀死,掉落一个DROP_NAME”的句子。之后,为了便于解释,我们要在多个变量统计表中添加一些数值(添加到aStats阵列和BeastDrop中)。
最后,在重复100次后,我们展示这些统计数据,怪物掉落道具的数量,以及每种道具掉落的数量。
另一个例子:形象化地掉落道具
让我们考虑另一个例子:
你可以从中看到,画面中产生了一个随机敌人。位于左侧的玩家角色可以创造一次发射攻击。当发射攻击命中敌人时,敌人就会死。
在这里,我们在之前的例子中所使用的相似函数可以决定敌人是否掉落了一些道具,并确定其掉落的道具类型。此时,它还会创造道具掉落的视觉形象,并更新屏幕底部的数值。
以下就是伪代码的部署:
- CONSTANT ENEMY_NAME = 0
- CONSTANT ENEMY_DROPRATE = 1
- CONSTANT ENEMY_RARITY = 2
- CONSTANT ENEMY_ANIM = 3
- CONSTANT DROP_NAME = 0
- CONSTANT DROP_RATE = 1
- //Constants for the readability of the arrays
- int EnemiesSpawned = 0
- int EnemiesDrops = 0
- array aEnemy(11,4)
- array aDrop(17,2)
- array aStats(17)
- array aTemp(0)
- On start of the project, we roll the data in aEnemy and aDrop
- Start Timer “Spawn” for 0.2 second
- Function “SpawnEnemy”
- int EnemyType = 0
- EnemyType = random(11) //We roll an enemy type out of the 11 available
- Create object Enemy //We create the visual object Enemy on screen
- Enemy.Animation = aEnemy(EnemyType, ENEMY_ANIM)
- EnemiesSpawned = EnemiesSpawned + 1
- txtEnemy.text = aEnemy(EnemyType, ENEMY_NAME) & ” appeared”
- Enemy.Name = aEnemy(EnemyType, ENEMY_NAME)
- Enemy.Type = EnemyType
- Keyboard Key “Space” pressed
- Create object Projectile from Char.Position
- Projectile collides with Enemy
- Destroy Projectile
- Enemy start Fade
- txtEnemy.text = Enemy.Name & ” has been vanquished.”
- Enemy Fade finished
- Start Timer “Spawn” for 2.5 seconds //Once the fade out is finished, we wait 2.5 seconds before spawning a new enemy at a random position on the screen
- Function “Drop” (Enemy.Type, Enemy.X, Enemy.Y, Enemy.Name)
- Function Drop (EnemyType, EnemyX, EnemyY, EnemyName)
- int DropChance = 0
- int Rarity = 0
- DropChance = ceil(random(100))
- Rarity = aEnemy(EnemyType, ENEMY_RARITY)
- txtEnemy.text = EnemyName & ” dropped ”
- If DropChance > aEnemy(EnemyType, ENEMY_DROPRATE)
- txtEnemy.text = txtEnemy.text & ” nothing.”
- //Nothing was dropped
- If DropChance <= aEnemy(EnemyType, ENEMY_DROPRATE)
- aTemp.clear/set size to 0
- For a = 0 to aDrop.Width
- and aDrop(a, DROP_RATE) >= Rarity
- aTemp.Push(a) //We push the current index into the aTemp array as possible drop index
- int DropType = 0
- DropType = Random(aTemp.Width) //We pick what is the drop index amongst the indexes stored in aTemp
- aStats(DropType) = aStats(DropType) + 1
- EnemiesDrops = EnemiesDrops + 1
- Create Object Drop at EnemyX, EnemyY
- Drop.AnimationFrame = DropType
- txtEnemy.Text = txtEnemy.Text & aDrop.(DropType, DROP_NAME) & “.” //We display the name of the drop
- txtStats.text = EnemiesDrops & ” enemies on ” & EnemiesSpawned & ” dropped items.” & newline
- For a = 0 to aStats.width
- and aStats(a) > 0
- txtStats.text = txtStats.Text & aStats(a) & ” ” & aDrop(a, DROP_NAME) & ” ”
- Timer “Spawn”
- Call Function “SpawnEnemy”
复制代码
先分别来看看aEnemy和aDrop表格的内容:
与之前的例子不同,包含敌人的阵列命名为aEnemy,它还包括另一行数据ENEMY_ANIM,后者拥有敌人动画的名称。这样,当画面生成敌人时,我们就可以查看并自动化图像播放。
同样,aDrop现在包含16个而非6个元素,每个索引都涉及对象的动画帧——但我也可能拥有多个动画,就像针对敌人设置一样,需要确定其掉落道具是否也需要动画。
此时我们所需要的敌人和道具数量远超过之前的例子。但你可以看到,与掉落率和稀有值相关的数据仍然存在。有一个显著的区别就是,我们将计算是否存在掉落道具的函数所生成的敌人区别开了。这是因为,在真正的游戏中,敌人不会无动于衷地等着被玩家杀掉。
所以现在,我们有一个函数SpawnEnemy和另一函数Drop。Drop类似于我们在之前例子中处理道具掉落随机性的方式,但这次要用到一些参数:其中两个是屏幕上敌人的X和Y坐标,因为这正是我们想让道具生成掉落的地方,另一个参数就是EnemyType,这样我们就可以在aEnemy表格中查找敌人的名称,以及一系列角色名称,以便更快速地编写为玩家呈现的反馈。
Drop函数的逻辑与之前例子相似,变化最多的是我们呈现反馈的方式。这次我们不仅仅是呈现文本,我们还要在屏幕上生成一个对象,给予玩家一个视觉形象。
(注:为了在屏幕多个位置生成敌人,我使用了一个隐形对象Spawn作为参照,它可以持续左右移动。无论何时调用SpawnEnemy函数,它都会在Spawn对象当前坐标创造敌人,这样敌人就会出现在一系列水平位置上。)
最后要讨论的是何时调用Drop函数。我并不会直接在敌人死亡的时候触发它,而是在敌人淡出屏幕(这是敌人的死亡动画)时才执行这一操作。你当然可以在敌人仍在屏幕上可见的情况下调用掉落函数,这取决于你的游戏设计需求。
三、如何在游戏设计中利用战利品掉落表
许多游戏都带有战利品。通常情况下这些战利品的分配都是随机的。战利品掉落是特别常见的主题,但却也是每个设计师经常会觉得头疼的内容。以下是我在过去几年所遇到的最佳实践。
你的基本战利品表
这里的目标是为了基于特定几率掉落一组道具。假设当你打败一个敌人,你便有机会获得盾牌,稀有的剑,或者什么都没有。
例子
战利品表
道具:
名字:剑
重量:10
道具:
名字:盾
重量:40
道具:
名字:空
重量:50
设置
道具:你想要提供给玩家的一种道具。
战利品表:将一组道具放进战利品表中。这只是一部分道具。例如一个战利品表将包括:剑,盾,空。
重量:每个道具都带有掉落重量:从1到10000。例如一把剑的掉落率可能是10。
空道具:战利品表中会有一个道具是“空”,这意味着如果滚动到它,便不会掉落任何战利品。
滚动战利品
总概率:首先,计算战利品表中的所有重量。在上述例子中便是10+40+50=100。因为这些数值并不是百分比,所以它们并不需要加到100。
接下来分配每个道具的范围。剑=1至10,盾=11至50,空=51至100。
从1至100生成一个随机数。
将该数值与范围进行比较。这便能够决定到底会掉落哪种道具。
再次滚动:生成多个随机数值去模拟多次滚动。
所以玩家会如何看待它们?我们设置剑的掉落几率为10%,盾的掉落几率为40%,而什么都不会掉落的几率为50%。
作为设计师,我可以将空的重量改为100,而现在我将剑的掉落几率设为6.6%(10/150),盾的掉落几率为26%(40/150),什么都不会掉落的几率为66%(100/150)。
映射到其它常见的随机系统
这一系统只是在重申许多其它相似的随机性方法。这是训练你的设计师大脑在基于战利品表,纸牌或筛子上理解任何随机性问题间转换的有趣方法。
纸牌
想象你能够洗牌并获取的桥牌。
桥牌上的每种纸牌类型都是一种道具。
特定类型的纸牌数量便是道具的重量。
洗牌等同于为每种道具分配范围并生成随机数。
抽取纸牌等同于选择掉落的道具。
现在常见的桥牌都拥有52张牌,但如果是基于战利品表,你便可以不受约束地进行操作。你的桥牌拥有1000张各种类型的纸牌。或者它们可以提供与典型的扑克手所拥有的较小的桥牌。
筛子
筛子同样也能够映射到战利品表上。
每一个独立的筛子都是一张战利品表。
筛子的每一面(1至N)便等同于道具。
筛子的每一面都拥有重量“1”(除非你是在使用超重的筛子!)。
多次滚动筛子代表多次滚动同一个战利品表。所以2D6便等同于抽取一个带有6种道具的战利品表2次。
变量
既然我们定义了一个基本的战利品表,我们还可以做些什么?
变量:道具组合
你同样也可以掉落战利品组合。道具并不需要一定是单一的内容。例如我可以扩展它从而让玩家同时获得一个盾牌和一个生命药剂。
例子
战利品表
道具:
名字:剑
重量:10
道具:
名字:盾
名字:生命药剂 数值:2
重量:40
道具:
名字:空
重量:50
变量:总是掉落
常见的需求是标记一个道具从而提升它的掉落频率。这里存在一种惯例,即带有“-1”重量的道具将会更常掉落。
变量:可重复的随机性
有时候你会希望能够重复一个随机滚动。例如当一名玩家保存了游戏,并能在之后重新加载以避免糟糕的战利品掉落结果,这将导致非常折腾的玩家行为。而如果存在一种方法能够避免这种情况,所有玩家都会很高兴吧。
大多数临时的伪随机数生成程序都是使用一个种子值。只要你能够保存该种子值,你便能够再次运行随机数生成程序并获得同意的结果。
变量:无需改变而滚动
上述系统的问题在于玩家可能会一直滚到“空”。这也是玩家常常抱怨的结果。就像“我玩了3000多次却从未获得MegaGoldenLootGun!”。
在统计学中存在两种基本的抽样类型:
放回抽样:你将从列表中抽取数值然后在记录你所获得的数值后,你会将它们放回去。如此你便有可能在下次抽取时拥有同样的几率。
不放回抽样:你将从列表中抽取数值,并且在你记录之后便将其置于一边。如此你在下次抽取时抽到该道具的几率便会下降,而抽到剩下道具的几率便会增加。
《俄罗斯方块》便使用了不放回抽样。每种俄罗斯方块都有自己的战利品表。每次你获得一个特殊组块时,它便会被移出列表。这种方法能够保证你在长时间等待长方形组块时将能获得它。
以下是关于你在战利品表中如何执行不放回滚动。
当你滚动一个道具时,将其的重量减少1。这也等同于将它的范围和最大范围减去1。
确保在玩家下次滚动时他们的战利品表已经进行了修改。
变量:保证特殊的掉落道具
有时候不放回滚动不够快,而你却希望保证战利品的掉落。暴雪便保证了特定稀有道具的掉落从而让玩家无需长时间地刷道具。
你可以只是提升重量,但是随着玩家多次玩游戏,他们会感受到获得某些有保证的道具的低频率与获得一种道具慢慢提升的几率之间的明确区别。
以下是关于如何执行有保证的掉落战利品。
当你滚动任何无保证的道具时,减少X%无保证的道具重量。
X=100/在有保证的道具掉落前滚动的最大数量。
确保在玩家下次滚动时他们的战利品表已经进行了修改。
例子
假设你想要在5个回合后剑能够频繁掉落,尽管它只拥有10%的掉落几率。
如此X=100/5或20%
所以每次当你滚到剑时,盾的重量便会下降8(40*0.2),而空的重量会下降10(50*0.2)。
在5个回合后,所有其它道具的重量将变成0,剑便会拥有100%的掉落几率。
变量:分等级的战利品表
战利品表通常都是新资源的来源。然而你很容易进入一种情境,即你掉落了太多或太少特殊资源。这时候一些限制将很有帮助。
一种解决方法便是使用不放回的分等级的战利品表。当一种特殊资源用尽时,玩家将不再获得该资源。我们在每日货币奖励中便使用了这一方法。我们想要每天派发100个货币,并且不会超过这一数值。但是我们也想将其作为战利品系统的一部分。
创造两个表:奖励和每日货币。
让主要的战利品表参照每日货币表。
当选择每日货币时,滚动列表并明确你获得了多少货币。
例子
战力品表:奖励
道具:
名字:剑
重量:10
道具:
名字:每日货币
重量:40
道具:
名字:空
重量:50
战利品表:每日货币
类型:不放回
更新率:每日
道具:
名字:货币,数值:1
重量:10
道具:
名字:货币,数值:10
重量:4
道具:
名字:货币,数值:50
重量:1
在上述例子中,玩家有40%的机会获得货币。然后我们将滚动每日货币表并看看它们是否能够基于10次奖励每次1个货币,4次奖励每次10个货币以及1次奖励每次50个货币而在一天中获得最多的100个货币。
当每日货币战利品表空了时,它们只有在隔天更新时才会再次被填满。
变量:有条件的掉落
有时候你会想要测试是否应该基于一些外部变量去掉落道具。在《 Realm of the Mad God》中,我们便想要避免未创造任何伤害而杀死boss的“搭便车者”获得战利品。所以在战利品表中,我们添加了检查。如果滚动到战利品表中的一种有价值的道具,我们便会检查玩家对敌人所造成的伤害是否超过X%。
你可以基于玩家的级别或敌人的级别改变战利品的有效性。就像我更倾向于使用多个较小的战利品表,并且系统非常灵活,足以让你能够轻松地使用一些较大的列表和条件去创造数据。
变量:编辑器
你可以基于以下外部逻辑修改掉落物的数量或重量。例如擅长收集的玩家能够获得比不擅长收集的玩家2倍多的特殊掉落道具。或者你可以修改重量。较高级别的角色的所有道具可能拥有-50%的重量,这远低于他们的级别。
其它使用
掉落物列表通常是用于掉落战利品,但我们也可以在其它地方发现它们。
程序生成:使用列表去创造武器或角色。
AI:使用列表去选择行为,如攻击或移动。
这可能有点愚蠢,但的确存在一些更好的方式去创造AI!一种方式便是将随机性当成任何系统的一阶模型。人类大脑是如何创造系统模型?我们为系统创造了观察报告。并注意到这些观察值重复出现的频率和趋势。在之后我们开始理解“为什么”会发生某些情况以及每个部分之间的临时关系。
在物理学中,我们经常会开玩笑地说,为了创造一只奶牛模型,即一个复杂的有机体,我们需要做的第一步便是“想象一只球形的奶牛。”通过创造一个简单的模型,我们便能够以最低成本生成有用的见解。
很多时候,掉落表其实就是一个复杂系统的以人类为中心的近似值。对于许多系统,大多数玩家的移动都不会超过一个基本的概率理解,所以创造更复杂的模型只会浪费时间。有效的游戏设计是创造模型去最小化必要级别以创造出理想的游戏体验。
考虑:《龙与地下城》便基于必要的战利品掉落表创造了完整的宇宙。这就是专注于最小化系统。
战利品掉落表并不是你需要的唯一工具,但在很多情况下,它却是一种很有效的工具。
程序生成思维实验
以下是使用掉落表的简单程序生成系统。存在许多其它方式能够做到这点,但这却是最需要你进行思考的方法。让我们假设你想要创造一个程序生成敌人。
一开始先创造独特的敌人列表。也许你的敌人是由移动类型,攻击类型,防御类型以及财宝类型所组成。
为每种类型创造战利品表。
基于强度提供给战利品表中的每种道具能量值。例如,刀的攻击可能较弱,那么它的能量值便是5。而较大的铁锤的能量值为15。
创造另一个战利品表。这是各种属性的修改内容。例如,“强大”将为攻击增加20%的数值。你也可以将攻击设为“弱”,这将减少50%的数值。
现在让我们生成一个敌人
设定一个目标:为你的生成敌人设定一个目标能量。假设你想要一个拥有40能量值的敌人。
滚动:滚动每个部分并将其添加到列表上。
分数:添加所有的能量值去获得一个分数。
调整:如果这些部分的总和超过目标值,那就为较低的能量部分添加一个攻击或滚动。如果总和低于目标值,那就为较高的能量部分添加一个攻击或滚动。
重复:重复这一过程直到你到达一个预期的错误门槛(远离能量40)或者你耗尽了你想要消耗的迭代数。
现在你便拥有一个程序生成敌人。对于这一基本系统你可以进行多次调整,但它大多数情况下都是有用的。作为练习,你可以想想:
排除列表:如果选择了列表中的两个部分,那就丢掉敌人再次滚动。
多重限制:基于多个标准进行评判的部分。需要注意的是,当你添加更多限制时,你便更加不可能聚集多重结果。
四、促进角色创造&资源管理的游戏道具类型
什么是“工具”道具?
在本文中,我将把工具定义为一种可获得的道具,即提供给玩家一种全新且非战斗能力去通过障碍。例如撬锁工具将帮助玩家打开门,爪钩将帮助他们攀升到一个全新高度,或者氧气罩将让他们能够长时间待在水里。
工具=能力[-]障碍
需要注意的是我特别排除了战斗工具,如武器或盾牌,因为我想专注于那些用于打开新的前进方向或避免障碍(如守卫,所以就包含了潜行工具)的工具。我同样也排除了在传统RPG风格游戏中通过升级而获得的能力。然而,《生化奇兵》的补养药却包含在内,因为它们的功能与道具很像。
典例:《杀出重围》,《神偷》,《羞辱》,《塞尔达传说》,《银河战士》,《生化奇兵》
工具的类型
着眼于各种游戏,我总结出了工具的以下特征:
获取——玩家是如何获取工具
给予——在某些点上工具是自动给予玩家(如《神偷》中的lockpics)
可发现的——可以在游戏世界中的各种地方发现它们(如在《银河战士》,《塞尔达传说》和《杀出重围》中)
独有的选择——可以排除其它内容而选择它(如《杀出重围》中的AUG升级道具)
可购买的——可以通过使用某些货币进行购买(如在《生化奇兵》,《羞辱》中那样)
永久性——玩家可以使用工具多长时间
无限的——能够无限期地使用(如《神偷》的lockpicks,《银河战士》)
一次性——使用数量有限,如果想再次使用就需要再次获取(如《杀出重围》中的多功能工具)
基于燃料——能够无限次地使用,但要求某些燃料或弹药的补给(如《生化奇兵》的补养药,《杀出重围》的AUG)
可升级性——可改变或完善?
静态的——始终保持一样(如在《塞尔达传说》和《银河战士》那样)
可升级的(通过货币)——通过使用一些货币而获得省级(如在《羞辱》中)
以下是一些例子:
《杀出重围》中的多功能工具=可发现的+一次性+静态的
《杀出重围》中的AUG=可发现的+基于燃料+可升级的(通过货币)
《羞辱》中的能量=可购买的(通过货币)+基于燃料+可升级的
《神偷》中的绳索或青苔箭=可发现的/可购买的+一次性+静态
《银河战士》的能量=可发现的+无限的+静态的
当然,游戏也可以基于单一规格混合多种道具特征——就像《生化奇兵》中的某些补养药便是给予的(如前面几个)而有些则是可发现的(非必要的)。
我能想到的最复杂的例子便是《杀出重围》的AUG——它们不只是要求燃料的可发现且一次性的选择资源,同时也可以通过完全不同的资源获得升级!
通过工具进行角色定制
工具能够提供最基本的角色创造方法。工具的实用性,永久性,和可升级性定义了玩家选择的灵活性和意义,以及他们的角色将具有多大的差别。
举个例子来说,因为工具的规格,《银河战士》具有非定制的线性角色创造—-你将在每次游戏的几乎同一个点上获得工具,并且不能在之后升级它们。基于所有目的,武器例外,“角色创造”总是会基于同样的方式发展。相反地,《杀出重围》的AUG更加定制化且两级化—-它们会要求你在两个会对之后游戏发展造成重要影响的内容中做出选择。
《神偷》的箭就是个有趣的例子——你可以在每个阶段购买它们,并且将看到各种不同的数量,在每个任务结束前你都可以自由地进行购买。《生化奇兵》允许玩家交换补养药,但限制了升级它们的资源。
试验
此外,工具的规格也影响了玩家能够做试验的次数。在此他们会提出一个问题“在致力于某一工具前我是否能够给予基本形式去测试每种工具?”
在《杀出重围》中,一旦玩家做出一个增强选择,他便丧失了一个机会——他将永远不会知道自己错过了什么。所以你并不能真正进行“试验”。
另一方面,《神偷》在某些点上提供给你至少每种类型的箭中的一个,如此你便能够在花钱辛苦获得道具前对它们进行试验。
我们还可以看到《生化奇兵》采取了一种中立的方法,即基于基本形式提供给你几乎每一种补养药,让你决定升级哪一个。《羞辱》也是如此——玩家并不是基于每种能力开始游戏,但是他们却可以通过游戏赚到足够的货币去获取基于基本形式的每种工具。
作为资源的工具
在我看来,资源也是一种你能够发现,获得或购买的工具或货币/燃料,它们是有限供应或者是一次性的。包括一般弹药,《杀出重围》中的电池或《生化奇兵》中的药剂。
当工具变成资源时情况会变得更有趣,它将引进一种管理和探索元素。例如《杀出重围》中的多功能工具或《神偷》中的绳索/青苔箭便是如此。
相反地,《塞尔达传说》中的大多数工具(游戏邦注:如爪钩或盾牌)便不属于资源—-它们在游戏世界中只有一个,并且不要求管理。炸弹则是例外,它们在游戏世界中是一次性的且可发现的。
资源管理和探索
当工具作为或依赖于资源时,它们将能提升游戏玩法的深度——基于你的游戏表现以及做出何种选择,你将不断“获得”并“失去”能力。
再一次地,《杀出重围》的多功能工具和lockpick,《神偷》的箭或《塞尔达传说》的炸弹都是很棒的例子——它们都是探索的一种奖励。
相反地,《银河战士》的工具或《塞尔达传说》的爪钩虽然能够推动探索但却不能基于自身去“鼓励”它——你必须找到它们去推动股市的发展,所以你不能从技术上进行“探索”,只能走在预先设定好的设计之路。
平衡道具的难度
适当地设计并平衡工具很难。解释并验证每一个选择也很难。《杀出重围》中的游泳升级或水中呼吸器便是很好的例子,很少用例是在否定它们。襄樊地,你也会无法忍受某种过度设计的工具—-《羞辱》中一种闪光能够让你非常有效地完成每一个任务,从而导致其它工具变得无用。
此外,当工具是资源或依赖于资源时,我们将很难去平衡它们的数量,特别是考虑到玩家技能的不同以及随机探索的能力时。一位资深的《神偷》玩家将能够基于过多的箭而完成一个任务。但新手却只能勉强维持下来。不同的难度级别能够缓解这种情况,但每个设置中仍然存在着设计问题。
相关阅读:
游戏道具设计(一):道具产出条件和随机设定
游戏邦编译