策划如何用vba调试游戏
前段时间公司准备制作一款以植物大战僵尸为题材,类似捕鱼的射击游戏,之前没有接手过类似项目,所以发表了一篇《捕鱼类游戏计算方式的设计思路》(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掷骰子,需要在循环代码中加入这段代码:
1. IfRnd < Per Then
2. SumInc = SumInc + 1
3. Else
4. 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、打印数据
由于每次循环,都产生了一次新的数值,需要每次都记录,所以需要在循环内,加入这两行代码:
1. Sheet1.Cells(i,1).Value = SumExp
2. 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 调整打印的数值:
1. Sheet1.Cells(i,1).Value = SumExp
2. Sheet1.Cells(i,2).Value = SumInc * Gold
3. 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:
1. Sheet1.Cells(i, 4).Value = 1
2. 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
在循环体中,加入相应的代码使其运作:
1. For i = 1 To 1000 '模拟次数
2. If Rnd < Per Then '子弹击杀了怪物
3. SumInc = SumInc + 1 '收入加起来
4. Sheet1.Cells(i, 4).Value = 1
5. N= M '重置悲情值
6. Else '子弹未击杀怪物
7. Sheet1.Cells(i, 4).Value = 0
8. N= N - 1
9. If N <= 0 Then
10. SumInc = SumInc + 1
11. N = M '重置悲情值
12. End If
13. End If
复制代码
PS:这里注意了,其实变量N才是悲情值,循环体中将M的值赋给N,让N代为执行悲情值的功能,因为N是循环体内的局部变量,所以不需要特别声明就可以用。(或者说将M的值赋给N的时候就已经声明了)
循环体中新增的一个判断
1. If N <= 0 Then
2. SumInc = SumInc + 1
3. N = M '重置悲情值
4. 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加入到概率运算当中,使其生效:
在循环体中加入代码,使其能“动态调整”,收益高降概率,收益低提概率:
1. Select Case(SumInc * Gold - SumExp) / SumExp
2. Case Is <= 0
3. ap = ap + 0.01
4. Case Is > 0
5. ap = ap - 0.01
6. End Select
7. Sheet1.Cells(i,6).Value = ap + Per
复制代码
Select Case是比If判断更好用的一种判断方式,可以判断多个值,执行多行代码
1. Select Case 需要检查的值
2. Case Is 值1
3. 执行1代码
4. Case Is 值2
5. 执行2代码
6. Case Is 值3
7. 执行2代码
8. ......
9. 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的调整:
1. Select Case ap
2. Case Is >= apUp
3. ap = apUp
4. Case Is < apDown
5. ap = apDown
6. 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代码:
1. Sub 按钮1_Click()
2. SumExp = 1 '总支出
3. SumInc = 0 '总收入
4.
5. Per = 0.1 '击杀率
6.
7. Gold = 10 '掉落金币
8.
9. M = 14 '悲情值
10.
11. ap = 0 '实时调整概率
12. apUp = 0.05 '限制最高几率
13. apDown = -0.05 '限制最低几率
14.
15. apt = 3 '概率调整步长
16.
17. For i = 1 To 1000 '模拟次数
18. If Rnd < (Per + ap) Then '子弹击杀了怪物
19. SumInc = SumInc + 1 '收入加起来
20. Sheet1.Cells(i, 4).Value = 1
21. N = M '重置悲情值
22. Else '子弹未击杀怪物
23. Sheet1.Cells(i, 4).Value = 0
24. N = N - 1
25. Sheet1.Cells(i, 5).Value = N '打印出悲情值
26. If N <= 0 Then
27. SumInc = SumInc + 1
28. N = M '重置悲情值
29. End If
30. End If
31.
32. SumExp = SumExp + 1 '支出加起来
33.
34. Sheet1.Cells(i, 1).Value = SumExp '打印支出数值
35. Sheet1.Cells(i, 2).Value = SumInc * Gold '打印收入数值
36. Sheet1.Cells(i, 3).Value = SumInc * Gold - SumExp '打印纯收益数值
37.
38. If i Mod apt = 0 Then '每隔apt炮调整一次概率
39. Select Case (SumInc * Gold - SumExp) / SumExp '根据收益调整几率
40.
41. Case Is <= 0
42. ap = ap + 0.01 '收入过低,提高概率
43. Case Is > 0
44. ap = ap - 0.01 '收入过高,降低概率
45. End Select
46. Select Case ap '调整概率不超过上下限
47. Case Is >= apUp
48. ap = apUp
49. Case Is < apDown
50. ap = apDown
51. End Select
52. End If
53.
54. Sheet1.Cells(i, 6).Value = ap + Per '打印概率的变化情况
55.
56. Next
57. End Sub
复制代码