前段时间公司准备制作一款以植物大战僵尸为题材,类似捕鱼的射击游戏,之前没有接手过类似项目,所以发表了一篇《捕鱼类游戏计算方式的设计思路》(http://www.gameres.com/463918.html) 的文章来验证思路。
游戏截图
根据这个思路,我准备EXCEL自带的VBA做一个数值模拟来调整数值。
本人是初学VBA,草草地看了几页书之后就开始动手撰写代码了,做完之后发现还行,所以就发出来,希望能给大家带来一些帮助。
我们策划不是专业程序员,也不会什么精妙的算法。但我们确实可以借助代码来帮助我们解决某些问题。
1、创建按钮 首先,我们打开EXCEL,新建一个页面,选择“开发工具”-“插入”-“按钮”,随便在屏幕上画一个按钮,画完之后,会弹出“制定宏”。 按钮建议放在G列之后
点击“新建”,即可打开代码窗口
PS:可以右键点击按钮,选择“编辑文字”设定按钮的显示文字,或者右键选中,左键拖动按钮的位置和调整大小。
2、打开代码窗口
按钮创建好之后,也可以点击“开发工具”内的Visual Basic按钮打开代码窗口:
点击右上角的VisualBasic按钮打开代码界面
代码窗口打开后,你会发现只有两句代码,今后的代码之后写在这两句中间,点击之前创建的按钮,就可以执行写的代码了。
3、设置变量和常量
首先我们以最基本的,豌豆射击普通僵尸的概率为例,豌豆攻击力是1,普通僵尸的生命值是10,则根据计算公式算出一颗豌豆杀死一只普通僵尸的概率为10%,也就是0.1。
我们以0.1的击杀概率,进行多次模拟,看看实际情况下,金币支出、金币收入数值的变化。
那么,我们可以先定义金币支出、金币收入的变量:
SumExp= 1 SumInc= 0
定义一个变量的方法很简单: 变量名 = 变量值
后面绿色的字是注释,起说明作用,不写也可以。(单引号+ 注释内容)
当然了,还有别的高大上定义变量的方法,例如:
Dim SumExpas string
代码的意思是: Dim 变量名 as 变量类型
这种方法可以不用先写好变量值,可以定义好变量类型,本文不涉及这些,有兴趣的小伙伴可以自行度娘研究哈。
我们定义好了两个变量SumExp(总金币支出)、SumInc(总金币收入),和他们的初始值1和0。但为什么总支出是1,而总收入0呢,因为后面的代码中,如果SumExp为0的话,会发生除0错误,所以就从1开始。而且,大家可以这么想,我们模拟的情况是从打出第一炮之后开始的,此时金币已经扣1了,支出当然从1开始啦。
有了这两个变量基本就够用了,但是还是建议再设置一个常量:击杀率。
Per= 0.1
为什么要设置击杀率呢,原因就是方便修改,因为本来我们就是为了调试概率写的这个代码,这个击杀率应该会频繁地修改,后期代码复杂了,很多地方都需要引用击杀率,到时候一个个修改起来那感觉可是相当地酸爽,而且特别容易遗漏。
4、设置自动循环
设定好变量后,就正式撰写代码,设置自动循环,让VBA帮忙“掷骰子”了。
让VBA自动干活,大家需要这段代码:
For i =1 To 1000 Next
其中,这个i是啥玩意,大家先不用去管它,就当它是计数器,关键看后面的数值1000,表示让代码循环运行1000次,具体运行什么代码,写在这两句代码中间就可以了。
这个数值1000可以改成任何你需要模拟的次数数值。数值太小,样本不够看不出趋势,数值太大,VBA可是会累“死”的哦,一般来说1000至50000就差不多了。
5、VBA掷骰
要让VBA掷骰子,需要在循环代码中加入这段代码:
- IfRnd < Per Then
- SumInc = SumInc + 1
- Else
- End If
复制代码
这段代码的意思是:
If 这个判断 Then 对了运行我 Else 错了运行我 End If
“这个判断”中的内容是: Rnd< Per
Per大家很熟,就是之前设定的变量击杀率,为0.1
Rnd是VBA内置的一个函数,效果是产生一个0到1之间的一个随机数(可度娘)
如果Rnd产生出来小于0.1,那说明是击杀了,则Rnd < Per这句话是对的,所以应该运行“对了”那一块代码。
如果Rnd产生出来大于或等于0.1,那说明是没击杀,则运行“错了”那一块的代码。
这个判断,击杀了僵尸掉落金币,所以“对了”那一段代码就要把金币给加上去:
SumInc= SumInc + 1
没有击杀僵尸什么事情也没有发生,所以“错了”这段代码为空,后面会用到。
6、完成一个循环
不管有没有击杀,每次豌豆打出去,总支出肯定是要加上去的,注意要加在掷骰代码外、循环代码内:
SumExp= SumExp + 1
这个时候,一个完整的循环已经完成,VBA按照我们的医院,以0.1的几率,进行1000次判断。 每次判断都相当于豌豆进行一次攻击,判断中了,则将收入+1,如果不中,则不加。每次判断结束,不管中没中,支出一定要+1。
这个时候其实可以返回EXCEL界面,点击按钮让VBA运行了。但是点击后,EXCEL表格没有任何反应。实际上代码已经是运行了,但是没有把相关的数据打印出来,我们看不到而已。
7、打印数据
由于每次循环,都产生了一次新的数值,需要每次都记录,所以需要在循环内,加入这两行代码: - Sheet1.Cells(i,1).Value = SumExp
- Sheet1.Cells(i,2).Value = SumInc
复制代码
加入代码后,点击按钮运行。哇,发现EXCEL表格内有数值了:
那么,这两句代码的是什么意思呢?
Sheet1.Cells(i, 1).Value = SumExp
工作簿名字 . Cells函数(单元格坐标X,Y). 单元格值= 输入的值
单元格坐标Y,对应的就是EXCEL表的横坐标,1对应的就是A,2就是B,3就是C,以此类推。
单元格坐标X,大家看到了在第4章出现的i,在我们之前的循环体内,i表示的是计数器,从1到1000,当前循环到了哪一个,刚好可以作为单元格坐标的X。
随着循环的推进,i不停地推进,也就依次将值写入单元格,相当方便。最终,循环进行了多少次,单元格也就被写了多少行,不信大家看:
8、打印数据的优化
大家发现,如果豌豆击杀了普通僵尸,掉落的可是10金币啊,怎么每次收入是+1呢。还有最好可以看一下收入减支出,看一下收益情况如何。
这时候我们就要对代码进行一下调整:
1 增加变量Gold:
Gold = 10
2 调整打印的数值:
- Sheet1.Cells(i,1).Value = SumExp
- Sheet1.Cells(i,2).Value = SumInc * Gold
- Sheet1.Cells(i,3).Value = SumInc * Gold – SumExp
复制代码
为什么要设置常量Gold呢,原因和Per一样——方便调试,因为僵尸掉落的金币数是我们需要频繁修改的,所以最好是设置成一个常量,方便每次更改,避免遗漏出错。
新增的第3行代码和前面的基本一致,单元格坐标的Y表示在C列打印,方便查看。
点击运行后就是密密麻麻的数据了
9、折线图查看数据
既然数据出来了,我们就可以使用EXCEL自带的折线图查看数据,对数据变化有一个宏观的了解。
直接选中A、B两列所有单元格的数据(记得要点选A和B,不能只选中单元格)
选中数据后,选择功能标签“插入”,图表功能栏内选择“二维折线图”,选择第一种折线图。
平稳的蓝色折线表示支出 波动的红色折线表示收入 蓝色折线在上表示亏钱 红色折线在上表示赚钱
另外,我们还可以插入一个单独的表格,查看纯收益情况。我们单独选中C列,插入折线图,得到:
折线图创建好之后,就可以点击按钮查看效果了,可以多次点击,折线图会动态更新。
我们使用的模型是:一次豌豆1金币,击杀一只普通僵尸掉落10金币,击杀率为10%(也就是0.1)。理论上的效果是10*0.1=1,相当于不赚也不亏,达到了收支平衡。
但是,实际情况下,虽然收支能大致保持平衡,但收入折线的波动非常厉害,而且经常出现“一边倒”的情况。所以,根据之前我写的《捕鱼类游戏计算方式的设计思路》,我们可以加入调整击杀概率的方式。
10、查看击杀情况
在击杀数据中,我们还想看一下,大概每隔多少次,豌豆可以击杀一次僵尸。
所以我们在判断中,加入下面两条代码,当击杀发生时,记下1,没有击杀,记下0:
- Sheet1.Cells(i, 4).Value = 1
- Sheet1.Cells(i, 4).Value = 0
复制代码
点击按钮运行,在D列里面,列出了击杀的情况:
但是,光是一串0和1,也是容易看眼花的,所以我们要使用条件格式,将1(击杀)的情况标记出来。
选中D列整行,点击功能标签“开始”-“样式”-“条件格式”-“突出显示单元格规则”-“等于”,填入值为1,设置为“浅红填充色深红色文本”(也可以选中别的喜欢的颜色)。
设置好之后,1这个数值被高亮了,就可以很明显地在数据表中看到击中的情况了。
11、引入悲情值
豌豆攻击普通僵尸,以 0.1的概率,理论数值10次就可以击杀一次,但实际情况可能20次甚至30次都不会中一次,会给玩家带来很大的挫败感。
所以我们要引入一个叫做悲情值的概念,降低击杀与被击杀间隔时间的离散程度,降低玩家由于连续未击中后的挫败感。
悲情值的运作方式为:
1 初始化悲情值=0 2 当玩家攻击一次,未获得任何收益,则悲情值+1 当玩家攻击一次,获得任何收益,则悲情值重置为0 3 当悲情值到达某个值时,玩家下一次攻击的击杀率+100%(必定命中)
在代码中,我们加入悲情值常量: M =10
在循环体中,加入相应的代码使其运作:
- For i = 1 To 1000 '模拟次数
- If Rnd < Per Then '子弹击杀了怪物
- SumInc = SumInc + 1 '收入加起来
- Sheet1.Cells(i, 4).Value = 1
- N= M '重置悲情值
- Else '子弹未击杀怪物
- Sheet1.Cells(i, 4).Value = 0
- N= N - 1
- If N <= 0 Then
- SumInc = SumInc + 1
- N = M '重置悲情值
- End If
- End If
复制代码
PS:这里注意了,其实变量N才是悲情值,循环体中将M的值赋给N,让N代为执行悲情值的功能,因为N是循环体内的局部变量,所以不需要特别声明就可以用。(或者说将M的值赋给N的时候就已经声明了)
循环体中新增的一个判断
- If N <= 0 Then
- SumInc = SumInc + 1
- N = M '重置悲情值
- End If
复制代码
是用来执行悲情值的功能的,通过每次不命中减少N,当N减到0的时候,触发一定命中。无论是随机获得的命中,或者是悲情获得的命中,都需要再次重置悲情值。
我们还可以加入一条代码,将悲情值的变化情况打印出来:
Sheet1.Cells(i,5).Value = N
这样,在EXCEL表格界面E列,就可以看到悲情值的变化情况了。
同样,我们根据第10章学的方式,将E列加一个条件格式,设置0为高亮(0表示此次触发了一定命中),这样我们就可以很容易地看到哪些是概率命中的,哪些是悲情命中的。
但是,引入悲情值后发现,玩家的收益率直线上升。经过多次模拟,收入线基本没有低于过支出线,这和我们所要求的“收支平衡”相违背。
要解决这个问题,有两种方法:
1、提高悲情值,原先设定的值是10,设为15或20,收入线明显下降。但是过高调整悲情值,就违背我们设置这个值的初衷,导致它非常鸡肋。
2、动态调整击杀率,将“高走”趋势拉低,“低开”趋势拔高,达到一种动态的平衡。
12、动态调整击杀率
要动态调整击杀率,首先我们要新增一个变量ap,加在Per上,来增加或减少击杀的几率。
默认ap为0,初始不调整击杀几率
将ap加入到概率运算当中,使其生效:
在循环体中加入代码,使其能“动态调整”,收益高降概率,收益低提概率:
- Select Case(SumInc * Gold - SumExp) / SumExp
- Case Is <= 0
- ap = ap + 0.01
- Case Is > 0
- ap = ap - 0.01
- End Select
- Sheet1.Cells(i,6).Value = ap + Per
复制代码
Select Case是比If判断更好用的一种判断方式,可以判断多个值,执行多行代码
- Select Case 需要检查的值
- Case Is 值1
- 执行1代码
- Case Is 值2
- 执行2代码
- Case Is 值3
- 执行2代码
- ......
- End Select
复制代码
如果大家希望对Select Case想更多的了解,可以度娘更深入学习。
加入了ap后,收入线变得“老实”了,跑高了自动会拉下来,落低了自动会拉上去,纯收益值像是股票走势一样“上上下下”了,但总的趋势还是位置在0上。
另外,代码中也将调整后的概率打印在了表格内的F列内,可以将F列导出折线图,查看概率的变化情况:
不过,我只是提供了一种思路,毕竟游戏是需要有一些趣味性的。玩家打了好半天,还是那些金币,不多也不少,是很容易乏味的。这就需要小伙伴们开脑洞了,充分利用Select Case可以设置多个条件的优势,比如:在游戏初期先调高概率,让玩家获利颇多,然后拉紧钱袋降低概率,刺激玩家充值等等。挖坑这种事应该是大家都喜闻乐见了的吧。
13 、动态调整上下限
在多次模拟后发现,动态调整值ap也会“失控”,一味地走高或走低,而且会变成负数。
所以我们还需要加入apUp、apDown变量值,来限制ap所能达到的最高几率和最低几率,让ap在正常的范围内上下波动:
apUp= 0.05 apDown= -0.05
apUp、apDown上下限制,建议幅度不要太大。也可以设定为只有上限无下限或无上限有下限,根据自己的需求制定。
将apUp、apDown加入代码,参与对ap的调整:
- Select Case ap
- Case Is >= apUp
- ap = apUp
- Case Is < apDown
- ap = apDown
- End Select
复制代码
这段代码的意思就是:当ap高过上限时,将其设置为上限值,当ap低于下限时,将其设置为下限值。
导入了上下限的代码之后,概率值的折线变化为:
是不是规矩多了?而且纯收入的波动变得频繁且平稳,收入线和支出线如胶似漆地黏着,已经达到了理论上的数值平衡了。
而且我将悲情值设置为15,也对曲线的影响也很小。每打15炮不击杀下次一定击杀,这种感觉还是可以的。
14、动态调整步长
我们对概率的动态调整,是每一个循环都进行的,相当于每发射一次,概率就会变化一次。由于性能,或是玩家体验的考虑,我们会希望这个调整不是这么的频繁,可以每3炮调整一次,或者5炮调整一次,甚至是10炮后再调整。
要实现这个功能,就需要增加一个动态调整的步长(或叫间隔)。
比如,我们要设定每3炮调整一次,就这样调整。
我们将动态调整的代码,嵌套到一个判断中去:
If i Mod 3 = 0Then
(这里放动态调整的整段代码)
End If
If 判断我们已经很熟悉了,但是这段: iMod 3 = 0 是什么意思呢?
大家还记得,在循环体里面,i是计数器,表示从1到1000,当前轮到第几位了。
Mod是VBA的内置函数,用于取得两个整数相除后结果的余数。聪明的小伙伴应该已经发现,哪些数字取余数是0啊?3、6、9、13......等3的倍数,遇到3的倍数就执行一次代码,不就实现了我们需要的每隔3次就调整一次的目的了吗。
不过,这个调整步长在调试中会经常修改,最好是能把它拨出来作为常量,方便调整。
定义一个常量:
将代码修改为:
好了,现在概率“抖动”地不是太厉害了,你也可以把apt值得改大一些,让它变得更“迟钝”。
15、结语
我们已经搭建好了最基本的模型,小伙伴们就可以通过修改之前定义好的几个常量,写入各种数值进行调试了。
希望这个教程能够帮助到大家,虽然这段短短的代码对于专业的程序员来说不算什么,但正所谓“策划写代码,神仙挡不住”。写的过程中,锻炼了我们的思维,联想到了更多的细节,另外这其中的成就感也是爆棚的。
作为一个策划,仅仅完成这些数值的调整是不够的,我们所要做的其他工作更多。一个游戏需要成功,仅仅靠一个人写一段代码是完成不了的,需要项目组所有人共同发光发热,每个人提供的能量多一些,那整个团队的战斗力肯定会有非常明显的提升。
祝大家早日实现自己的梦想!
这次教程的全部VBA代码:
- Sub 按钮1_Click()
- SumExp = 1 '总支出
- SumInc = 0 '总收入
- Per = 0.1 '击杀率
- Gold = 10 '掉落金币
- M = 14 '悲情值
- ap = 0 '实时调整概率
- apUp = 0.05 '限制最高几率
- apDown = -0.05 '限制最低几率
- apt = 3 '概率调整步长
- For i = 1 To 1000 '模拟次数
- If Rnd < (Per + ap) Then '子弹击杀了怪物
- SumInc = SumInc + 1 '收入加起来
- Sheet1.Cells(i, 4).Value = 1
- N = M '重置悲情值
- Else '子弹未击杀怪物
- Sheet1.Cells(i, 4).Value = 0
- N = N - 1
- Sheet1.Cells(i, 5).Value = N '打印出悲情值
- If N <= 0 Then
- SumInc = SumInc + 1
- N = M '重置悲情值
- End If
- End If
-
- SumExp = SumExp + 1 '支出加起来
- Sheet1.Cells(i, 1).Value = SumExp '打印支出数值
- Sheet1.Cells(i, 2).Value = SumInc * Gold '打印收入数值
- Sheet1.Cells(i, 3).Value = SumInc * Gold - SumExp '打印纯收益数值
- If i Mod apt = 0 Then '每隔apt炮调整一次概率
- Select Case (SumInc * Gold - SumExp) / SumExp '根据收益调整几率
-
- Case Is <= 0
- ap = ap + 0.01 '收入过低,提高概率
- Case Is > 0
- ap = ap - 0.01 '收入过高,降低概率
- End Select
- Select Case ap '调整概率不超过上下限
- Case Is >= apUp
- ap = apUp
- Case Is < apDown
- ap = apDown
- End Select
- End If
- Sheet1.Cells(i, 6).Value = ap + Per '打印概率的变化情况
- Next
- End Sub
复制代码
|