Lua做的“ATB战斗模拟器”,独立游戏开发的好选择

有个小朋友要搞独立游戏,采用ATB的战斗模式(应该是想学轨迹系列吧),为了让他学习我的Buff机制在ATB模式下的使用,以及方便做数值演算(相比excel……也许……更方便),花了1天时间做了个小玩意,纯lua的。要使用他,你得有Lua5.3的环境,然后:

windows:cmd下cd到对应文件夹,运行 main.lua。

macOS:terminal下cd到对应文件夹,运行 lua main.lua。

什么是ATB游戏?

这是史克威尔(和爱尼克斯没啥关系,那时候还没合并)在FF4中首创的一种战斗规则,突破了传统RPG回合制的回合算法,每个角色根据自己的速度(之类的)属性决定自己在战斗中可以出手的回合,而不再是一个回合场上每个人出手一次,即速度越快的角色,一场战斗出手频率也越高。这个规则在之后被广泛运用,很多大作如《梦幻模拟战》系列等都用了这样一个规则,国内的《仙剑》、《轩辕剑》系列后几作也有用到这样的战斗规则。

当然被现代玩家,尤其是喜欢二次元玩家熟知的ATB游戏系列应该就是《XX轨迹》系列了:

在轨迹系列里,UI上没有ATB条,他的显示方法是下一个轮到的谁,但实际上这是根据“最接近轮到行动”来进行排序的,由于是标准的ATB模式,所以很多时候角色使用魔法开始吟唱和受到打击行动顺序的技能影响之后,移动顺序的变化并不是那么“固定”(看不出会往后几个人),原因就是UI“变了戏法”。

关于ATB这个属性

ATB这个属性,对于这个“世界”来说就是时间单位,整个世界进程的推进,就是ATB的推进,在战斗开始的时候ATB是0,结束的时候可能是一个非常大的数字,你可以理解为程序中常见的Date.Time(),只是Date.Time()解释的是1970年1月1日到现在的秒(一些环境下是毫秒)数,而ATB对于战斗而言,即战斗开始(“世界”被创造)之后到“现在”的“时间”。

ATB对于角色属性来说,是当前已经等了多久了,当ATB达到上限的时候,角色就可以发动行动,不同的行动,将会把角色的ATB重置到一个新的位置,让角色继续回到等待状态。通常状况下,世界的ATB推进1,角色的ATB会提高很多,远不止1(如果你的游戏规则就是角色固定每个tick增加1ATB,那当然也是可以的,只是感觉会非常奇怪)。

这个玩意儿怎么用?

之前说了他的运作方法,但是这玩意儿最主要的功能并不是运作,而是让你把它当做战斗模拟器来用。所以首先我得申明,这个花了1天都不到就做出来的东西,十有八九是有bug的。

我把所有的文件分了两个文件夹:

coder下的lua,都是模拟程序端的,在实际项目中,这些会用其他语言去写。这里用lua山寨了一个oop的写法(当然也已经不那么oop了,至少我把继承关系丢了),是因为大多人还是更习惯oop,毕竟从学校学的到实战中都是oop的,所以我就不用ECS了,虽然ECS写游戏太好了(所以这玩意儿我写的也苦啊……)。

designer下则是策划的脚本数据了,其中有一个game_lua_interface.lua,这个是山寨“程序给策划的Lua接口”的,即实际开发中,程序提供的接口都在这里。

所以,使用这玩意儿,你最好能遵守几个规则(当然也不强迫你,如果你有点追求,还是遵守一下吧):

1,designer下的代码不直接调coder下的函数:这块是模拟程序代码,只是让你看一下怎么实现好,通常会是别的语言写,当然类似CharacterObj之类的数据结构,是可以直接“抄”的,毕竟数据接口最后也长那样。

2,coder下也不能轻易调designer下的,除非是formula和game_lua_interface下的函数:只有这里的lua是和策划约定的脚本接口,通常都是写公式和一些游戏独有功能性接口,而策划写的其他脚本,你根本不该知道用了什么函数名。

3,designer下要用到coder下的函数,就要在game_lua_interface中包接口,模拟程序员提供的脚本接口:放心,这个不难。

4,游戏逻辑相关的print,必须用game_lua_interface中的battleReport。调试用的print,必须用print。就像我说的,因为我们是战斗模拟器,所以你要理解BattleReport是让游戏进程推进的(虽然效果也是打出log),而print是调试用的,他们看起来那么“一样”,但骨子里就是不一样的东西。

5,一个模块写一个文件:你要是也没啥追求的话当然可以随便写,it just works。

6,如果A文件调用了B文件的函数,B文件就不允许调用A的任何函数:禁止高耦合的想法出现,基于一个模块写一个文件。

7,一行代码过长时换行:代码可读性。

8,命名用英文,并且不得有二义性:代码可读性,当然编程最难的事情之一是这个,尤其是在脚本里。

9,一个函数尽可能只做一件事情,越少的事情越好:oop的思路无法彻底避免side effect,所以在调通之后,请至少把print干掉(print就是一个典型的side effect)。

既然你开始写代码了,不管你写的是什么代码,就要像程序员一样对待编程,别像个国游程一样“只要能运行不就好了吗”。

那么问题来了:

角色属性那么少,怎么办?

在coder/character_property.lua里,你可以随意定义属性,记得维护一下Init和Add就好。假如你需要一个属性有他的实际逻辑作用,你可以在battle.lua(写的最烂的那个文件)中找到他可以用上的地方,自己改改就行。

这计算公式不是我想要的!

designer/formula.lua是专门的计算公式接口文件,“程序”在一些必要的逻辑中调用这里的脚本接口,所以千万别改了函数名,这些接口分别是:

MAX_ATB_VALUE:即一个角色的ATB达到这个数字的时候才能行动,这是一个规则级的,所以当他是800的时候,就说明“当一个角色的ATB大于等于800就能行动”。

GameDesign_RecheckProperty:重新统计角色的属性和状态,之所以不偷懒抛“...“作为参数,是因为角色(CharacterObj)的属性是严肃的东西,通过这些属性,最后走这个公式获得一个最终的属性,赋给角色的property,给到其他逻辑段使用。

GameDesign_GetDamage:获得最基础的伤害数量,这是很多数值策划的主要工作之一,就是所谓的战斗公式,在不考虑任何buff的情况下去获得伤害结果,唯一要值得注意的是,如果暴击了,请别把暴击调整的伤害算好给damage,damage只是没有暴击的伤害。

GameDesign_GetFinalDamageValue:最后根据是否暴击来算出最终的伤害值,为什么和GetDamage要分开两步呢?因为这个玩意儿已经经历了buff的洗礼,才得出了该不该暴击(想想是不是有些效果可以让角色必定被暴击或者必定免疫暴击的?),所以这是最后了,才去计算带暴击的伤害。

GameDesign_ATBIncreased:根据给过来的角色,给角色在每个tick增加ATB进度。你当然可以根据角色的名字来,让他的ATB不增加,但是建议不要减少。

附加一张伤害流程:

GameDesign_GetDamage是在“根据策划公式获得damageInfo”这一步用的,而GameDesign_GetFinalDamageValue会在所有计算是否能杀死角色和最终造成伤害的时候调用。

我想做20v50的战斗行不行?

那当然是可以的,现在的设计是模仿《秦时明月》手游这样的7v7的,当然并没有做任何额外限制,不过你应该去修改一下designer/game_lua_interface.lua下的GetBattleEmptyPos这个接口的实现,使得有更多的位置可用(现在超过7个会报错,懒得修了)。在你的脑海中,最好建立整个战场的位置图,每个位置对应一个编号,这很重要。

举个实际例子:你在AOE等范围参数里,要有位置关系,比如你的aoe的范围是,而你的AOE中心落在了4号位置上,那么你实际上AOE生效的位置是(范围是偏移量,所以都+4),如果是落在2号位置,则范围是,是有负数位置被允许的,当然你上面没有摆人也就没啥问题。

我能手操战斗吗?

当然可以,打开最渣渣的文件coder/battle.lua,你会“惊奇”的发现一个CONFIG_AUTOBATTLE,当他是true的时候,战斗是自动的,当他是false的时候,你会体验到上个世纪的文字mud游戏的感觉:

输入命令之后……………………

胜败乃兵家常事……我只能这么解释。

有些脚本代码好垃圾啊

没错,很多地方都故意留了错误了,其实是因为想让吃肉小朋友自己去发现的,等他lua等级提高后,会自己发现的。

GrandpaFive和KiloTwo是什么?

相信我,用VBA+Excel你也可以做到的,甚至更好,只要你愿意去做的话,如果你跟我一样懒,那可以考虑改改这个玩意儿,毕竟他会把“所有战斗情况”都反应出来。

  • 发表于:
  • 原文链接:http://kuaibao.qq.com/s/20171225B0ZP6B00?refer=cp_1026

相关快讯

扫码关注云+社区