游戏开发论坛

 找回密码
 立即注册
搜索
楼主: storm1986

[原创] 技能和BUFF数据编辑架构

[复制链接]

23

主题

3388

帖子

6440

积分

论坛元老

Rank: 8Rank: 8

积分
6440
发表于 2014-10-27 10:57:28 | 显示全部楼层
快乐杰克 发表于 2014-10-27 10:10
刚才一会居然看完了,感觉卡和M的耐心都不错。

想问下卡你在写哈希表数据结构的时候是否注意到了它的使用 ...

扯到哈希表——完全是题外话。

对于技能这种小规模数据,我是不赞成专门写哈希查询的,毕竟数组更简单更好维护。一个游戏最多也就几百个技能,就算用遍历效率上也低不到哪里去。但要说把一次查询就能完成的事分成几次来做,无论是用遍历还是用哈希都不合理。

lua里面的数据结构就一种——哈希表,但好在这个不需要使用者专门去写哈希查询,只要当做数组来使用就好了。

8

主题

1801

帖子

3450

积分

论坛元老

Rank: 8Rank: 8

积分
3450
发表于 2014-10-27 11:06:35 | 显示全部楼层
技能用表格做的核心原因还是因为拉表方便,无论是数值还是执行小弟。
要是从逻辑实现而言,脚本确实强多了,技能的设计太多样化了,很难找到通用逻辑。往往到了快上线的时候,需求变化的幅度也很大。

23

主题

3388

帖子

6440

积分

论坛元老

Rank: 8Rank: 8

积分
6440
发表于 2014-10-27 11:30:55 | 显示全部楼层
关于脚本结构的设计,分两方面简单说下:

神:
任何类型的游戏中,技能都可以抽象成“施放过程”以及“生效过程”,只是施放条件与方法不同,生效的形式和条件(作用于什么对象、哪些对象、作用于对象的哪些属性……)不同。在这个思路下,脚本接口的抽象方法总结起来就一句——C++调脚本传入各种事件(操作及状态改变);脚本调C++获取和设置游戏中各种对象的状态。不管技能设计如何变化,也不外乎游戏中各种事件与状态按一定的逻辑规则进行组合。

形:
1,脚本只负责处理逻辑过程,游戏中各种对象的维护由C++负责。

2,C++调用脚本的接口保持简单——最好只有一个调用接口,且参数格式固定。通常我喜欢采用一个事件名+N个参数的形式(事件指玩家的操作行为或游戏中各对象的状态改变,参数多少随系统的复杂程度而定)。这个唯一的接口按一个固定的机制将C++传入的事件分发到对应的脚本。

3,脚本调C++的接口可以很多,可以根据需要不断添加。这些接口的功能不外乎就是通过C++操作对象的各种属性。总体上分为两类——get和set。一种用于获取游戏中各种对象的某个状态,一种用于设置对应的状态。

23

主题

3388

帖子

6440

积分

论坛元老

Rank: 8Rank: 8

积分
6440
发表于 2014-10-27 11:52:01 | 显示全部楼层
本帖最后由 卡特铁角 于 2014-10-27 12:33 编辑
ab_946 发表于 2014-10-27 11:06
技能用表格做的核心原因还是因为拉表方便,无论是数值还是执行小弟。
要是从逻辑实现而言,脚本确实强多了 ...

你说得没错。
这个问题的根本矛盾在于技能系统的本质就是多样化的交互方式,因此个人觉得在这里追求“通用”的逻辑就是舍本逐末。

我自己也做过数值策划,关于拉表是否方便我想说——做数值设定时的表格极少有能直接读到程序里的。就算是用表格不用脚本,策划给出的数据最终也还得按程序需要的格式整理一次。

88

主题

2743

帖子

4227

积分

论坛元老

Rank: 8Rank: 8

积分
4227
发表于 2014-10-27 12:52:42 | 显示全部楼层
本帖最后由 快乐杰克 于 2014-10-27 13:00 编辑
卡特铁角 发表于 2014-10-27 10:57
扯到哈希表——完全是题外话。

对于技能这种小规模数据,我是不赞成专门写哈希查询的,毕竟数组更简单更 ...

更精确来说这个例子里的应用实际找的是数据入口“指针”操作。你说的几百个操作数据要达到要求的算法可以选择的有好几种。

很多设计哈西表操作多被封装起来以便使用,将繁琐的处理细节隐藏起来了。

23

主题

3388

帖子

6440

积分

论坛元老

Rank: 8Rank: 8

积分
6440
发表于 2014-10-27 13:51:41 | 显示全部楼层
本帖最后由 卡特铁角 于 2014-10-27 14:00 编辑
Mr_I 发表于 2014-10-27 09:30
“大概明白了”,你还是没明白。或者不想明白?

我说的做法就是包括组装的。

你所谓的读表工具里面大概是这个样子:
  1. local function ReadSkillConfigFile(file_path)

  2.         local ret_table = {} -- return data table

  3.         --从c++里读取数据表
  4.         local config = mjlib.LuaXmlParseFile(file_path);
  5.         if config == nil then
  6.                 print( "xml file table is nil")
  7.         else
  8.                 --表的结构与xml的配置文件的结构相关,目前从xml读取到的结构如下
  9.                 --print_table("skill",config)

  10.                 for i, iv in pairs(config) do                 --{}
  11.                         if type(iv) == "table" then  
  12.                         
  13.                                 for k, kv in pairs(iv) do        --skills

  14.                                         if type(kv) == "table"  then  --skill
  15.                                                 
  16.                                                 --处理skill表               
  17.                                                 local tmp_skill_table = {}
  18.                                                 if kv.id ~= nil then
  19.                                                         ret_table[kv.id] = tmp_skill_table;
  20.                                                 end
  21.                                                 
  22.                                                 for j, jv in pairs(kv) do --skill
  23.                                                         
  24.                                                                 if type(jv) == "table"  then   --skill below table
  25.                                                                         
  26.                                                                         --处理表成员
  27.                                                                         if jv.id ~= nil then
  28.                                                                                 --将名字相同的叶子节点合并为一个表,并建立索引
  29.                                                                                 if tmp_skill_table[jv.name] == nil then
  30.                                                                                         tmp_skill_table[jv.name] = {}
  31.                                                                                 end
  32.                                                                                 tmp_skill_table[jv.name][jv.id] = jv;        
  33.                                                                         
  34.                                                                         else
  35.                                                                                 --只存在一个表的不需要合并
  36.                                                                                 if not tmp_skill_table[jv.name] then
  37.                                                                                         tmp_skill_table[jv.name] = jv
  38.                                                                                 end
  39.                                                                         end
  40.                                                                
  41.                                                                 else                                
  42.                                                                         --非表内成员值直接赋值
  43.                                                                         tmp_skill_table[j] = jv
  44.                                                                 end
  45.                                                 end
  46.                                        
  47.                                         end
  48.                                 end
  49.                                 
  50.                         end
  51.                 end
  52.         end

  53.         return ret_table
  54. end
复制代码
对应的XML表大概这样:
  1. <skill id="6" nameid="3" name="突击步枪点射(1级)" vocation="1" skilllevel="1" skilltype="1" casttype="5" bulletspeed="200" humanaccuracy="50" vehicleaccuracy="50" boataccuracy="50" submarineaccuracy="50" aircraftaccuracy="50" friendlyfire="1">
  2.                 <castcondition readytime="0" cooldowntime="0.5" friendly="2" rangemin="0" rangemax="100" enviroment="1" moveable="1" weapontype="6" weaponid="0" ammotype="6" ammosum="1" itemid="0" itemsum="0" machinetype="0" machineid="0" seatid="0" castinmachine="0" equipmentid1="0" equipmentid2="0" equipmentid3="0" hp="0" sp="0" food="0" money="0" fuel="0" endure="0" energy="0" horizonmin="-180" horizonmax="180" verticalmin="-180" verticalmax="180"/>
  3.                 <targeteffect id="1" effectid="6" shape="1" arearadius="0" probability="100"/>
  4.                 <targeteffect id="2" effectid="0" shape="0" arearadius="0" probability="0"/>
  5.                 <targeteffect id="3" effectid="0" shape="0" arearadius="0" probability="0"/>
  6.                 <castereffect id="1" effectid="0" shape="0" arearadius="0" probability="0"/>
  7.                 <castereffect/>
  8.         </skill>
复制代码
这是那个伪FPS项目中一个技能的描述,还是挺早期的版本。后来这个<skill>下又加了两种包含若干参数的子节点。

上面的数据解析函数已经写的比较“通用”了,但为了方便后面的技能处理逻辑获取数据并减少冗余,还是做了一些针对性的处理。对于这个数据解析函数,修改节点的成员数量或在某个节点下增加同名叶子节点时,这个解析函数不用修改就可以用;但如果增加的新节点内部还有结构,或者在当前某个叶子节点里面又加了一层,那就需要修改解析函数。当然,这个解析函数还可以写得更“通用”一些,不过又有多大意义呢?当XML表的结构发生改变,技能处理逻辑中访问/使用/处理数据的方法流程必定要做相应的调整,这才是耗神又容易出错的工作。

通用技能处理逻辑部分代码(这里贴上来的就是按你说的表格描述通用技能,脚本描述特殊技能的方式做的):
  1. function OnLoad()
  2.         local scriptType = luacom.SKILL_SCRIPT
  3.         local event_tab = luacom.GetEventTable(scriptType)
  4.         luacom.RegisterEvent(event_tab,scriptType,0,OnEvent)
  5.         
  6.         luacom.ImportSkillData()  --将技能、效果数据从硬盘文件导入到内存
  7. end

  8. function OnEvent(event,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8)
  9.         --_G.print("debug:luascript-skill:OnEvent() called! event="..event.." arg1="..arg1.." arg2="..arg2.." arg3="..arg3.." arg4="..arg4.." arg5="..arg5.." arg6="..arg6.." arg7="..arg7.." arg8="..arg8)
  10.         
  11.         --若存在对应于当前事件中技能ID的特殊技能脚本,则调用此特殊技能脚本处理该事件,而不需要再通过技能脚本的总入口处理
  12.         local subScriptType = luacom.SKILL_SCRIPT
  13.         local subScriptID = arg1
  14.         if(luacom.CallRegisteredScript(subScriptType,subScriptID,event,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8)) then
  15.                 return
  16.         end
  17.         
  18.         if(eventProcessor[event]) then
  19.                 eventProcessor[event](arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8)
  20.         end
  21. end


  22. ventProcessor[luacom.SKILL_BULLET_IMPACT_TARGET] = function(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8)
  23.         --_G.print("debug:luascript-skill:eventProcessor[luacom.SKILL_BULLET_IMPACT_TARGET]() called!")
  24.         local skillID = arg1
  25.         local casterID = arg2
  26.         local targetID = arg3
  27.         local impact_x = arg4
  28.         local impact_y = arg5
  29.         local impact_z = arg6
  30.         local criticalBonus = arg7
  31.         local weaponAndAmmo = arg8
  32.         
  33.         local skill = luacom.GetSkillData(skillID)
  34.         if(skill.casttype ~= 3 and skill.cassttyp ~= 4) then   --保险措施:正确的情况下,3、4类技能(按角色朝向施放的范围技能)不会触发该事件
  35.                 skillImpactAndExplode(skillID,casterID,targetID,impact_x,impact_y,impact_z,criticalBonus,weaponAndAmmo)
  36.         end
  37. end

  38. eventProcessor[luacom.SKILL_EXPLODE_AFFECT_CHARACTER] = function(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8)
  39.         local skillID = arg1
  40.         local casterID = arg2
  41.         local targetID = arg3
  42.         local explodeShape = arg4
  43.         local distanceSquare = arg5
  44.         local criticalBonus = arg6
  45.         local weaponAndAmmo = arg7
  46.         
  47.         local skill = luacom.GetSkillData(skillID)
  48.         
  49.         if(skill == nil) then
  50.                 _G.print("luascript-skill:eventProcessor[luacom.SKILL_EXPLODE_AFFECT_CHARACTER]()error! skillID:"..arg1.." is not exist!")
  51.                 return
  52.         end
  53.         
  54.         if(skill.targeteffect == nil) then
  55.                 _G.print("luascript-skill:eventProcessor[luacom.SKILL_EXPLODE_AFFECT_CHARACTER]() error! skill.targeteffect is nil! skillID:"..arg1)
  56.                 return
  57.         end
  58.         
  59.         if(luacom.IsCharacterInMachine(targetID) == false) then
  60.         --[[setTargetAttribute()将一并负责对目标及其乘员的属性进行计算和设置,若没有乘员则只对目标本身进行设置。机体内的乘员不会单独受到爆炸影响。
  61.         即,此处认为爆炸只对爆炸范围内处于机体外的单兵角色以及机体产生作用。否则机体内乘员在同一次爆炸中所受伤害会被重复计算。]]               
  62.                 for i in _G.pairs(skill.targeteffect) do
  63.                         local radiusSquare = skill.targeteffect[i].arearadius^2
  64.                         local shape = skill.targeteffect[i].shape
  65.                         
  66.                         if(shape == explodeShape and radiusSquare >= distanceSquare) then   --由于同一技能中形状不同的爆炸事件是分别设置的,故这里只对explodeShape指定形状的爆炸进行处理                                
  67.                                 local effect = luacom.GetEffectData(skill.targeteffect[i].effectid)
  68.                                 if(effect == nil) then
  69.                                         _G.print("luascript-skill:eventProcessor[luacom.SKILL_EXPLODE_AFFECT_CHARACTER]() error! effectID:"..skill.targeteffect[i].effectid.." is not exist!")
  70.                                         return
  71.                                 end
  72.                                 
  73.                                 if(effect.friendly ~= 0) then                                   --阵营属性对效果生效与否的判断
  74.                                         if(effect.friendly == 1) then            --援助技能
  75.                                                 if(luacom.TargetCanAssist(casterID,targetID) == false) then
  76.                                                         return
  77.                                                 end
  78.                                         elseif(effect.friendly == 2) then        --攻击技能
  79.                                                 if(luacom.TargetCanAttack(casterID,targetID) == false) then
  80.                                                         return
  81.                                                 end
  82.                                         end
  83.                                 end
  84.                                 
  85.                                 if(skill.targeteffect[i].probability >= 100 or skill.targeteffect[i].probability ~= nil
  86.                                 and skill.targeteffect[i].probability > 0 and luacom.MathRandom(1,100) <= skill.targeteffect[i].probability) then
  87.                                         if(canAffectTarget(skill.targeteffect[i].effectid,casterID,targetID)) then
  88.                                                 setTargetAttribute(skillID,skill.targeteffect[i].effectid,criticalBonus,weaponAndAmmo,casterID,targetID,true)
  89.                                         end
  90.                                         if(canAffectPassenger(skill.targeteffect[i].effectid,casterID,targetID)) then
  91.                                                 setPassengerAttribute(skillID,skill.targeteffect[i].effectid,criticalBonus,weaponAndAmmo,shape,casterID,targetID,true)
  92.                                         end
  93.                                 end
  94.                         end
  95.                 end
  96.         end
  97. end

  98. eventProcessor[luacom.SKILL_BUFF_CHECK] = function(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8)
  99.         --arg1=skillID   arg2=casterID   arg3=targetID   arg4=effectID   arg5=buffslotID   arg6=startTime   arg7=preBeat   arg8=weaponAndAmmo
  100.         local skillID = arg1
  101.         local casterID = arg2
  102.         local targetID = arg3
  103.         local effectID = arg4
  104.         local buffslotID = arg5
  105.         local startTime = arg6
  106.         local preBeat = arg7
  107.         local weaponAndAmmo = arg8
  108.         
  109.         local effect = luacom.GetEffectData(effectID)
  110.         if(effect == nil) then
  111.                 _G.print("luascript-skill:eventProcessor[luacom.SKILL_BUFF_CHECK]() error! effectID:"..effectID.." is not exist!")
  112.                 return
  113.         end
  114.         
  115.         local currentTime = luacom.GetSystemSec()
  116.         local endTime = startTime + effect.lasttime
  117.         if(currentTime > endTime) then
  118.                 currentTime = endTime
  119.         end
  120.         
  121.         if(effect.intervaltime > 0 and currentTime - preBeat >= effect.intervaltime) then
  122.                 local times = luacom.MathFloor((currentTime - preBeat)/effect.intervaltime)
  123.                 for i = 1,times do
  124.                         setTargetAttribute(skillID,effectID,1,weaponAndAmmo,casterID,targetID,false)
  125.                 end
  126.                 luacom.SetBuffPreBeat(targetID,buffslotID,preBeat+times*effect.intervaltime,startTime)
  127.         end
  128.         
  129.         if(currentTime >= endTime) then        
  130.                 luacom.ClearBuff(targetID,effectID,buffslotID,startTime)
  131.                
  132.                 --[[if(effect.endeffect == nil) then
  133.                         _G.print("luascript-skill:eventProcessor[luacom.SKILL_BUFF_CHECK]() error! effectID:"..effectID.." 's endeffect is nil!")
  134.                         return
  135.                 end
  136.                
  137.                 for i in _G.pairs(effect.endeffect) do
  138.                         setTargetAttribute(skillID,effect.endeffect[i].effectid,weaponAndAmmo,casterID
  139.                 end]]
  140.         end
  141. end
复制代码
再一段:
  1. eventProcessor[luacom.SKILL_CHARACTER_CAST_BEGIN] = function(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8)
  2.         --_G.print("debug:luascript-skill:eventProcessor[luacom.SKILL_CHARACTER_CAST_BEGIN]() called!")
  3.         local skillID = arg1
  4.         local casterID = arg2
  5.         local targetID = arg3
  6.         local impact_x = arg4
  7.         local impact_y = arg5
  8.         local impact_z = arg6
  9.         local event = luacom.SKILL_CHARACTER_CAST_BEGIN
  10.        
  11.         local skill = luacom.GetSkillData(arg1)
  12.         if(skill == nil) then
  13.                 _G.print("luascript-skill:skillID:"..arg1.." is not exist!")
  14.                 if(luacom.IsNpc(arg2)) then
  15.                         _G.print("luascript-skill:caster is NPC:"..luacom.GetNpcTypeID(arg2))
  16.                 end
  17.                 return
  18.         end
  19.        
  20.         if(targetID ~= nil and targetID ~= INVALID_ID) then
  21.                 local machineInstanceID = luacom.GetMachineAndSeat(targetID)
  22.                 if(machineInstanceID ~= 0) then  --目标在机体中
  23.                         targetID = machineInstanceID
  24.                 end
  25.         end
  26.        
  27.         --[[if((skill.castcondition.weapontype == nil or skill.castcondition.weapontype == 0)
  28.         and (skill.castcondition.weaponid == nil or skill.castcondition.weaponid == 0))  then
  29.                 local _,_,buffslotID,startTime,_,_ = luacom.GetBuffInfo(casterID,30)
  30.                 if(buffslotID) then
  31.                         local clearSucceed,sameNameBuffEffection = luacom.ClearBuff(casterID,buffslotID,startTime)
  32.                         if(clearSucceed) then
  33.                                 local recoverValue
  34.                                 if(effect.intervaltime == 0) then  --BUFF结束时,对恒定状态BUFF(intervaltime为0)的属性加成/扣减进行恢复
  35.                                         for _,attriChange in _G.pairs(effect.attrichange) do
  36.                                                 if(attriChange.percentage ~= 1) then
  37.                                                         recoverValue = luacom.GetCharacterAttribute(casterID,attriChange.attributeid) - attriChange.changevalue
  38.                                                 else
  39.                                                         recoverValue = luacom.GetCharacterAttribute(casterID,attriChange.attributeid)*100/(100 + attriChange.changevalue)
  40.                                                 end
  41.                                                 luacom.SetCharacterAttribute(casterID,attriChange.attributeid,recoverValue)
  42.                                         end       
  43.                                 end
  44.                         end
  45.                 end
  46.         end]]
  47.        
  48.         if(skill.casttype == 1) then                                                    --选中目标才能施放的技能               
  49.                 if(targetID == nil or targetID == INVALID_ID) then
  50.                         canCast = false
  51.                         failReason = CAST_FAIL_TAR_NIL
  52.                         luacom.SkillCastFail(skillID,casterID,failReason)
  53.                         return
  54.                 end
  55.                
  56.                 local t_x,t_y,t_z = luacom.GetCharacterPosition(targetID)
  57.                 local c_x,c_y,c_z = luacom.GetCharacterPosition(casterID)
  58.                 local distanceSquare = luacom.GetDistanceSquare(t_x,t_y,t_z,c_x,c_y,c_z)
  59.                 local distance = luacom.MathSqrt(distanceSquare)
  60.                
  61.                 local canCast,failReason = skillCanCast(skillID,casterID,targetID,distanceSquare,impact_x,impact_y,impact_z,event)
  62.                 if(canCast) then
  63.                         if(skill.castcondition == nil) then
  64.                                 _G.print("luascript-skill:error! skill.castcondition is nil! skillID:"..skillID)
  65.                                 return
  66.                         end
  67.                         if(skill.castcondition.readytime == 0) then                                                                 --瞬发技能
  68.                                 skillExpend(skillID,casterID)
  69.                                 local flyingTime = skillFlyingTime(skillID,distance)
  70.                                 local canHit,bullet_angle = skillCanHit(skillID,casterID,targetID,distanceSquare)
  71.                                 if(canHit) then                                           --命中判断
  72.                                         luacom.SkillWillHit(skillID,casterID,targetID,impact_x,impact_y,impact_z,flyingTime)  --技能施放成功完成,且将在flyingTime后命中目标或弹着点(当第三个参数为0时表示该技能的命中点是三维坐标)
  73.                                        
  74.                                         local criticalBonus = getCriticalBonus(casterID)
  75.                                         local weaponAndAmmo
  76.                                         if(arg8 == nil or arg8 == 0) then   --若arg8不为空或0,则说明改技能所使用的道具(手雷等)已通过arg8传入。目前,只有特殊技能脚本会通过arg8传入道具ID
  77.                                                 weaponAndAmmo = getWeaponAndAmmo(skillID,casterID)
  78.                                         else
  79.                                                 weaponAndAmmo = arg8
  80.                                         end
  81.                                         if(flyingTime == 0) then
  82.                                                 skillImpactAndExplode(skillID,casterID,targetID,impact_x,impact_y,impact_z,criticalBonus,weaponAndAmmo)
  83.                                         else
  84.                                                 luacom.SetDelayedEvent(flyingTime,1,luacom.SKILL_BULLET_IMPACT_TARGET,arg1,arg2,arg3,arg4,arg5,arg6,criticalBonus,weaponAndAmmo)   --飞行延迟定时
  85.                                         end
  86.                                 else
  87.                                         local errorDistance = bullet_angle*distance
  88.                                         impact_x = t_x + luacom.MathRandom(-errorDistance,errorDistance)
  89.                                         impact_y = t_y + luacom.MathRandom(-errorDistance,errorDistance)
  90.                                         impact_z = t_z
  91.                                         targetID = INVALID_ID
  92.                                         luacom.SkillWillHit(skillID,casterID,targetID,impact_x,impact_y,impact_z,flyingTime)
  93.                                        
  94.                                         local criticalBonus = 1
  95.                                         local weaponAndAmmo
  96.                                         if(arg8 == nil or arg8 == 0) then
  97.                                                 weaponAndAmmo = getWeaponAndAmmo(skillID,casterID)
  98.                                         else
  99.                                                 weaponAndAmmo = arg8
  100.                                         end
  101.                                         if(flyingTime == 0) then
  102.                                                 skillImpactAndExplode(skillID,casterID,targetID,impact_x,impact_y,impact_z,criticalBonus,weaponAndAmmo)
  103.                                         else
  104.                                                 luacom.SetDelayedEvent(flyingTime,1,luacom.SKILL_BULLET_IMPACT_TARGET,skillID,casterID,targetID,impact_x,impact_y,impact_z,criticalBonus,weaponAndAmmo)
  105.                                         end
  106.                                 end
  107.                         else                                                                                          --需要吟唱(预备)的技能
  108.                                 if(skill.castcondition == nil) then
  109.                                         return
  110.                                 end
  111.                                 luacom.SkillCastBegin(skillID,casterID,skill.castcondition.readytime*1000,1,luacom.SKILL_CHARACTER_CAST_END,skillID,casterID,targetID,0,0,0,0,arg8)                                 --通知服务器该次技能施放成功开始且会持续吟唱skill.castcondition.readytime(秒),之后触发SKILL_CHARACTER_CAST_END事件
  112.                         end
  113.                 elseif(failReason) then
  114.                         luacom.SkillCastFail(skillID,casterID,failReason)  --通知服务器该次技能施放失败,及失败原因
  115.                 end
复制代码


13

主题

832

帖子

1875

积分

金牌会员

空想家

Rank: 6Rank: 6

积分
1875
发表于 2014-10-27 18:30:04 | 显示全部楼层
卡特铁角 发表于 2014-10-27 10:40
你用JSON的做法只是把“组装”的结构要求以及具体的数据包含在了JSON文件里而已,跟我之前用CSV+XML的做 ...

1.我这里是策划定的。
2.采用哪一种做法都和数据与逻辑是否分开无关。那只和你在表里放了什么东西有关。

13

主题

832

帖子

1875

积分

金牌会员

空想家

Rank: 6Rank: 6

积分
1875
发表于 2014-10-27 19:13:44 | 显示全部楼层
卡特铁角 发表于 2014-10-27 13:51
你所谓的读表工具里面大概是这个样子:
对应的XML表大概这样:这是那个伪FPS项目中一个技能的描述,还是挺 ...

”但如果增加的新节点内部还有结构,或者在当前某个子节点里面又加了一层“

看到这里我明白了,这实际上是数据组织方式上的区别。
你们把对象层次的逻辑也放到配置数据的结构里,这样做就是我所谓的“不通用”。
打个比方,假设技能有“攻击范围”属性,攻击范围里包括了最近、最远、角度三个参数。
你们的做法是对于属性“攻击范围”这个节点,它之下还有三个节点。
但实际上完全没必要做成2层节点,这里需要分而治之:
对于不太通用的数据,直接取消上层节点,只保留最后要用的数据即可。
对应经常用到的数据,则是拆表。

还是这个例子,所谓的拆表做法是,攻击范围的数据是指向另一张表的ID。
另一张表是对游戏中所有用来获得范围的情况的方法所用的数据。
这样做的理由是:
1.将”范围“这个可能在多处用到的功能单独抽象出来。(相当于脚本里的一个接口)
   相对单独的逻辑对应相对单独的数据,易于维护和扩展。
2.对应单独一张表,不会出现你所说的多级子节点的情况。(易于维护,不用重写解析)
3.对于顶级的表,策划在配置时对字段的含义会非常清晰。(比如”锥形“)
4.对于底层的表,可以在不影响别人的表的情况下单独调试。(比如调整”锥形“的范围)

这种拆表,对应于你所说的”扁平化“,也可以理解成是“数据的扁平化”。

23

主题

3388

帖子

6440

积分

论坛元老

Rank: 8Rank: 8

积分
6440
发表于 2014-10-28 10:25:44 | 显示全部楼层
本帖最后由 卡特铁角 于 2014-10-28 10:30 编辑
Mr_I 发表于 2014-10-27 19:13
”但如果增加的新节点内部还有结构,或者在当前某个子节点里面又加了一层“

看到这里我明白了,这实际上 ...

像上面那样采用多级节点来描述数据,一方面是为了提高表格的可读性。另一方面的原因则是——我们的脚本结构是事件驱动的,因此希望将数据按照跟不同事件的相关性进行划分到不同的节点中,每个事件尽量只访问一个节点内的数据。比如<castcondition>里面的数据都是跟SKILL_CAST_BEGIN以及SKILL_CAST_END事件关联最紧的,而<targeteffect>等节点里面的数据则跟SKILL_BULLET_IMPACT_TARGET等结果事件关联最紧。甚至,我们还把effect(包括buff)单独拆出来作为一张独立的xml表,不过这是考虑到同一个效果可以和不同技能的施放条件进行组合。

这么做乍看之下是比较合理的。但你也发现了,这样做的问题就是“数据组装”在面对数据结构调整时会很麻烦,处理逻辑中查找需要的数据也会比较烦人——程序员都不会喜欢这么复杂的表格。因此希望减少节点层数(你说的“数据扁平化”,我们说的“数据降维”)。考虑最极端的情况,我们可以把一个技能仅用一层节点表达,所有数据都作为该层节点的成员。这么做带来的唯一问题就是表格会变得比较难理解。同时这么做也还是无法避免维护复杂冗长的“通用逻辑”代码(通用逻辑复杂的根源在于它要区分不同技能之间的差异,然后跳转到不同的逻辑分支作针对性的处理,表现在代码上就是大量的if else或者大量的函数map,而且还是一层套一层的)。于是我们跳过了这一步,直接采用更加极端的逻辑扁平化,于是就有了一个脚本对应一个技能的做法——每个技能的数值和逻辑都包含在按其ID命名的脚本文件中(我看不到数据和逻辑分开在这里有什么好处)。

这样做了之后,虽然策划要直接调整技能会比较困难,但好处是大大降低了程序维护工作的难度——每个技能脚本多则百来行,少则10多行,简单易读;不用再考虑不同类型技能之间的差异,不用再纠结一层套一层的if else……大部分程序员都更会觉得维护简短的代码要轻松很多,大不了把某个技能的脚本按新需求重写一次,这也要不了多少时间。实际上对于简单一点的技能脚本,策划要改改数值也还是很容易上手的。对于那些逻辑上没有差别的技能则更方便了——一个简单的代码生成器就能搞定,甚至可以用excel的字符串拼接来做这个事。

23

主题

3388

帖子

6440

积分

论坛元老

Rank: 8Rank: 8

积分
6440
发表于 2014-10-28 10:51:47 | 显示全部楼层
快乐杰克 发表于 2014-10-27 12:52
更精确来说这个例子里的应用实际找的是数据入口“指针”操作。你说的几百个操作数据要达到要求的算法可以 ...

哈希表查询跟指针的概念还是有差别的。

指针在C/C++中是存储内存单元地址的变量。
而哈希则是用一个函数来计算应该把某个值放在什么地址并生成索引。这个函数称为哈希函数,通常针对一组要存储的数据进行专门的设计。哈希函数实际上是一种散列函数,而散列函数就有一个问题——可能不同的数据会通过这个函数计算出一个相同的地址,术语叫“冲突”。这时就要有另外一个函数或者说逻辑来处理冲突。

点评

哈希一般用在操作系统级别做查询。里面的细节很多,技巧要求也高。  发表于 2014-10-28 11:51
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-6-20 00:53

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表