游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

分享为怪物掉落道具编写程序的经验

发布时间:2014-05-08 14:30:03 Tags:,,,

作者:Kyatric

动作游戏中的一个普遍机制就是让敌人在临死前掉落一些道具或者奖励。角色就可以拾取这些战利品从而增加自己的优势。这是包括RPG在内的许多游戏都有的一个机制,它给予玩家一个除掉敌人的动机——以及看到即时奖励时的一点兴奋感。

在本篇教程中,我们将查看这些机制的内在运行机制,以及如果用你所使用的编码工具/语言将其植入游戏中。

我使用Construct 2这种HTML5游戏开发工具来展示这方面的例子,但其使用工具并不局限于此。无论你使用哪种编程语言或工具,应该都能够植入同样的机制。

这些例子是由r167.2所制作,你可以在其软件免费版本中打开和进行编辑。你还可以下载Construct最新版本来操作这些例子。

基本机制

ScreenShot1(from gamedevelopment)

ScreenShot1(from gamedevelopment)

在敌人死时的那一刻(它的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。

让我们看看这个样本的运行方式。首先,怪物和掉落道具都有各自的阵列。以下是怪物阵列:

beast array(from gamedevelopment)

beast array(from gamedevelopment)

这是掉落道具阵列:

drops array(from gamedevelopment)

drops array(from gamedevelopment)

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次后,我们展示这些统计数据,怪物掉落道具的数量,以及每种道具掉落的数量。

另一个例子:形象化地掉落道具

让我们考虑另一个例子:

screenshot2(from gamedevelopment)

screenshot2(from gamedevelopment)

你可以从中看到,画面中产生了一个随机敌人。位于左侧的玩家角色可以创造一次发射攻击。当发射攻击命中敌人时,敌人就会死。

在这里,我们在之前的例子中所使用的相似函数可以决定敌人是否掉落了一些道具,并确定其掉落的道具类型。此时,它还会创造道具掉落的视觉形象,并更新屏幕底部的数值。

以下就是伪代码的部署:

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(from gamedevelopment)

aEnemy(from gamedevelopment)

aDrop table(from gamedevelopment)

aDrop table(from gamedevelopment)

与之前的例子不同,包含敌人的阵列命名为aEnemy,它还包括另一行数据ENEMY_ANIM,后者拥有敌人动画的名称。这样,当画面生成敌人时,我们就可以查看并自动化图像播放。

同样,aDrop现在包含16个而非6个元素,每个索引都涉及对象的动画帧——但我也可能拥有多个动画,就像针对敌人设置一样,需要确定其掉落道具是否也需要动画。

此时我们所需要的敌人和道具数量远超过之前的例子。但你可以看到,与掉落率和稀有值相关的数据仍然存在。有一个显著的区别就是,我们将计算是否存在掉落道具的函数所生成的敌人区别开了。这是因为,在真正的游戏中,敌人不会无动于衷地等着被玩家杀掉。

所以现在,我们有一个函数SpawnEnemy和另一函数Drop。Drop类似于我们在之前例子中处理道具掉落随机性的方式,但这次要用到一些参数:其中两个是屏幕上敌人的X和Y坐标,因为这正是我们想让道具生成掉落的地方,另一个参数就是EnemyType,这样我们就可以在aEnemy表格中查找敌人的名称,以及一系列角色名称,以便更快速地编写为玩家呈现的反馈。

Drop函数的逻辑与之前例子相似,变化最多的是我们呈现反馈的方式。这次我们不仅仅是呈现文本,我们还要在屏幕上生成一个对象,给予玩家一个视觉形象。

(注:为了在屏幕多个位置生成敌人,我使用了一个隐形对象Spawn作为参照,它可以持续左右移动。无论何时调用SpawnEnemy函数,它都会在Spawn对象当前坐标创造敌人,这样敌人就会出现在一系列水平位置上。)

最后要讨论的是何时调用Drop函数。我并不会直接在敌人死亡的时候触发它,而是在敌人淡出屏幕(这是敌人的死亡动画)时才执行这一操作。你当然可以在敌人仍在屏幕上可见的情况下调用掉落函数,这取决于你的游戏设计需求。

ScreenShot3(from gamedevelopment)

ScreenShot3(from gamedevelopment)

总结

在设计层面上看,让敌人掉落一些战利品可以为玩家提供对抗和摧毁敌人的动力。掉落的道具可以为玩家提供能量升级、状态或目标,可以是直接或间接的方式。

从执行层面上看,掉落道具可以通过一个由程序员来决定何时调用的函数来管理。该函数要根据杀死敌人类型来执行检查掉落道具稀有性的工作,也可以确定在屏幕何处生成道具。道具和敌人数据可以在类似阵列等数据结构中托管,并通过函数进行查找。

函数使用随机数字来确定掉落道具的频率和类型,由程序员来控制这些随机性,其查找的数据,并调这些掉落道具在游戏中的感觉。

希望本文能够让你更加理解如何在自己的游戏中设置怪物道具掉落的情况。(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

How to Code Monster Loot Drops

by Kyatric25

A common mechanic in action games is for enemies to drop some kind of item or reward upon dying. The character can then collect this loot to gain some advantage. It is a mechanic that is expected in a lot of games, like RPGs, since it gives the player an incentive to get rid of the enemies—as well as a small blast of endorphins when discovering what the immediate reward is for doing so.

In this tutorial, we’ll review the inner workings of such a mechanic and see how to implement it, whatever the type of game and the coding tool/language you might be using.

The examples I use to demonstrate this were made using Construct 2, a HTML5 game making tool, but are in no way specific to it. You should be able to implement the same mechanic whatever your coding language or tool is.

The examples were made in r167.2 and can be opened and edited in the free version of the software. You can download the latest version of Construct 2 here (since I started writing this article, at least two newer versions have been released) and mess around with the examples to your liking. The example CAPX source files are attached to this tutorial in the zip file.

The Basic Mechanic

Upon an enemy’s death (so, when its HP is less than or equal to zero) a function is called. The role of this function is to determine whether there is a drop or not, and, if so, the kind of drop it should be.

The function can also handle the creation of the visual representation of the drop, spawning it at the former screen coordinates of the enemy.

Consider the following example :

Click the Slay 100 Beasts button. This will execute a batch process that creates 100 random beasts, slays them, and displays the result for each beast (that is, whether the beast drops an item, and, if so, what kind of item). Statistics at the bottom of the screen display how many beasts dropped items, and the how many of each type of item was dropped.

This example is strictly text to show the logic behind the function, and to show that this mechanic can be applied to any type of game, whether it is a platformer on which you stomp on the enemies, or a top-down view shooter, or an RPG.

Let’s look at how this demo works. First, the beasts and drops are each contained in arrays. Here’s the beast array:

Index (X)
Name (Y-0)
Drop rate (Y-1)
Item rarity (Y-2)
0 Boar 100 100
1 Goblin 75 75
2 Squire 65 55
3 ZogZog 45 100
4 Owl 15 15
5 Mastodon 35 50

And here’s the drops array:
Index (X)
Name (Y-0)
Item rarity (Y-1)
0 Lollipop 75
1 Gold 50
2 Rocks 95
3 Jewel 25
4 Incense 35
5 Equipment 15

The X value (the Index column) for the array acts as a unique identifier for the beast or item type. For example, the beast of index 0 is a Boar. The item of index 3 is a Jewel.

These arrays act as lookup tables for us, containing the name or type of each beast or item, as well as other values that will allow us to determine the rarity or the drop rate. In the beast array, there are two more columns after the name:

Drop rate is how likely the beast is to drop an item when slain. For example, the boar will have a 100% chance to drop an item when killed, whereas the owl will have a 15% chance to do the same.

Rarity defines how uncommon the items that can be dropped by this beast are. For example, a boar will be likely to drop items of a rarity value of 100. Now, if we check the drops array, we can see that the rocks is the item with the biggest rarity (95). (Despite the rarity value being high, due to the way I programmed the function, the bigger the rarity number is, the more common the item is. It has more chances to drop the rocks than an item with a lower rarity value.)

And that’s interesting to us from a game design perspective. For the balance of the game, we don’t want the player to get access to too much equipment or too many high-end items too soon—otherwise, the character might get overpowered too early, and the game will be less interesting to play.

These tables and values are just examples, and you can and should play with and adapt them to your own game system and universe. It all depends on the balancing of your system. If you want to learn more on the subject of balancing, I recommend checking out this series of tutorials: Balancing Turn-Based RPGs.

Let’s now look over the (pseudo)code for the demo:

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) & ” ”

First, the user action: clicking on the Slay 100 Beasts button. This button calls a function with a parameter of 100, just because 100 feels like a good number of enemies to slay. In a real game, it’s more likely that you will slay beasts one by one, of course.

From this, the function SlainBeast is called. Its purpose is to display some text to give the user feedback on what happened. First, it cleans up the BeastDrops variable and aStats array ,which are used for the statistics. In a real game, it’s unlikely you will need those. It cleans the Text as well, so that a new 100 lines will be displayed to see the results of this batch. In the function itself, three numeric variables are created: BeastType, DropChance, and Rarity.

BeastType will be the index we use to refer to a specific row in the aBeast array; it’s basically the kind of beast that the player had to face and kill. Rarity is taken from the aBeast array as well; it’s the rarity of the item this beast should drop, the value of the Item rarity field in the aBeast array.

Finally, DropChance is a number we randomly pick between 0 and 100. (Most coding languages will have a function to get a random number from a range, or at least to get a random number between 0 and 1, which you could then simply multiply by 100.)

At this point, we can display our first bit of information in the Text object: we already know what kind of beast spawned and was slain. So, we concatenate to the current value of Text.text the BEAST_NAME of the current BeastType we’ve randomly picked, out of the aBeast array.

Next, we have to determine whether an item shall be dropped. We do so by comparing the DropChance value to the BEAST_DROPRATE value from the aBeast array. If DropChance is less than or equal to this value, we drop an item.

(I decided to go for the “less than or equal to” approach, having been influenced by these live role players using the D&D King Arthur: Pendragon set of rules regarding dice rolls, but you could very well code the function the other way around, deciding that drops will only occur when “greater or equal”. It’s just a matter of numeric values and logic. However, do stay consistent all through your algorithm, and don’t change the logic halfway—otherwise, you could end up with issues when trying to debug or maintain it.)

So, two lines determine whether an item is dropped or not. First:
1

DropChance > aBeast(BeastType,BEAST_DROPRATE)

Here, DropChance is greater than the DropRate, and we consider this to mean that no item is dropped. From there on, the only thing displayed is a closing “.” (full stop) that ends the sentence, “[BeastType] was slain.”, before moving on to the next enemy in our batch.

On the other hand:
1

DropChance <= aBeast(BeastType,BEAST_DROPRATE)

Here, DropChance is less than or equal to the DropRate for the current BeastType, and so we consider this to mean that an item is dropped. To do so, we will run a comparison between the Rarity of item that the current BeastType is “allowed” to drop, and the several rarity values we have set up in the aDrop table.

We loop through the aDrop table, checking each index to see whether its DROP_RATE is greater than or equal to Rarity. (Remember, counter-intuitively, the higher the Rarity value is, the more common the item is) For each index that matches the comparison, we push that index into a temporary array, aTemp.

At the end of the loop, we should have at least one index in the aTemp array. (If not, we need to redesign our aDrop and aBeast tables!). We then make a new numeric variable DropType that randomly picks one of the indices from the aTemp array.; this will be the item we drop.

We add the name of the item to our Text object, making the sentence to something like “BeastType was slain, dropping a DROP_NAME.”. Then, for the sake of this example, we add some numbers to our various statistics (in the aStats array and in BeastDrops).

Finally, after the 100 repetitions, we display those statistics, the number of beasts (out of 100) that dropped items, and the number of each item that was dropped.
Another Example: Dropping Items Visually

Let’s consider another example:

Press Space to create a fireball that will kill the enemy.

As you can see, a random enemy (from a bestiary of 11) is created. The player character (on the left) can create a projectile attack. When the projectile hit the enemy, the enemy dies.

From there, a similar function to what we’ve seen in the previous example determines whether the enemy is dropping some item or not, and determine what the item is. This time, it also creates the visual representation of the item dropped, and updates the statistics at the bottom of the screen.

Here is an implementation in pseudocode :

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”

Take a look at the contents of the aEnemy and aDrop tables, respectively:

Index (X)
Name (Y-0)
Drop rate (Y-1)
Item rarity (Y-2)
Animation name (Y-3)
0 Healer Female 100 100 Healer_F
1 Healer Male 75 75 Healer_M
2 Mage Female 65 55 Mage_F
3 Mage Male 45 100 Mage_M
4 Ninja Female 15 15 Ninja_F
5 Ninja Male 35 50 Ninja_M
6 Ranger Male 75 80 Ranger_M
7 Townfolk Female 75 15 Townfolk_F
8 Townfolk Male 95 95 Townfolk_M
9 Warrior Female 70 70 Warrior_F
10 Warrior Male 45 55 Warrior_M
Index (X)
Name (Y-0)
Item rarity (Y-1)
0 Apple 75
1 Banana 50
2 Carrot 95
3 Grape 85
4 Empty potion 80
5 Blue potion 75
6 Red potion 70
7 Green potion 60
8 Pink Heart 65
9 Blue pearl 15
10 Rock 100
11 Glove 25
12 Armor 30
13 Jewel 35
14 Mage Hat 65
15 Wood shield 85
16 Iron axe 65

Unlike the previous example, the array that contains the enemy data is named aEnemy and contains one more row of data, ENEMY_ANIM, which has the name of the enemy’s animation. This way, when spawning the enemy, we can look this up and automate the graphical display.

In the same vein, aDrop now contains 16 elements, instead of six, and each index refers to the animation frame of the object—but I could have had several animation as well, as for the enemies, if the dropped items were to be animated.

This time, there are far more enemies and items than in the previous example. You can see, though, that the data regarding drop rates and rarity values is still there. One notable difference is that we have separated the spawning of the enemies from the function that calculates if there is a drop or not. This is because, in a real game, enemies would likely do more than just wait on screen to be slain!

So now we have a function SpawnEnemy and another function Drop. Drop is pretty similar to how we handled the “dice roll” of our item drops in the previous example, but takes several parameters this time: two of these are the X and Y coordinates of the enemy on screen, since that’s the place where we will want to spawn the item when there is a drop; the other parameters are the EnemyType, so we can look up the name of the enemy in the aEnemy table, and the name of the character as a string, to make it quicker to write the feedback we want to give to the player.

The logic of the Drop function is otherwise similar to the previous example; what mostly changes is the way we display feedback. This time, instead of just displaying text, we also spawn an object on screen to give a visual representation to the player.

(Note: To spawn the enemies on several position on screen, I used an invisible object, Spawn, as reference, which continually moves left and right. Whenever the SpawnEnemy function is called, it creates the enemy at the current coordinates of the Spawn object, so that the enemies appear and a variety of horizontal locations.)

One last thing to discuss is when exactly the Drop function is called. I don’t trigger it directly upon an enemy’s death, but after the enemy has faded away (the enemy’s death animation). You can of course call for the drop when the enemy is still visible on screen, if you prefer; once again, that is really down to your game design.

Conclusion

On a design level, having enemies drop some loot gives an incentive to the player to confront and destroy them. The items dropped allow you to give power-ups, stats, or even goals to the player, whether in a direct or indirect way.

On an implementation level, dropping items is managed through a function that the coder decides when to call. The function does the job of checking the rarity of the items that should be dropped according to the type of enemy killed, and can also determine where to spawn it on screen if and when needed. The data for the items and enemies can be held in data structures like arrays, and looked up by the function.

The function uses random numbers to determine the frequency and type of the drops, and the coder has control over those random rolls, and the data it looks up, to adapt the feel of those drops in the game.

I hope you enjoyed this article and have a better understanding of how to make your monsters drop loots in your game. I’m looking forward to seeing your own games using that mechanic.(source:gamedevelopment

 

 


上一篇:

下一篇: