观察 | 类魔兽世界moba技能系统的数值结构设计

2017-11-02 15:57

原文链接:http://gad.qq.com/article/detail/34805

类似魔兽世界,moba这种技能极其复杂,灵活性要求极高的技能系统,必须需要一套及其灵活的数值结构来搭配。数值结构设计好了,实现技能系统就会非常简单,否则就是一场灾难。

比如魔兽世界,一个人物的数值属性非常之多,移动速度,力量,怒气,能量,集中值,魔法值,血量,最大血量,物理攻击,物理防御,法术攻击,法术防御,等等多达几十种之多。属性跟属性之间又相互影响,buff又会给属性增加绝对值,增加百分比,或者某种buff又会在算完所有的增加值之后再来给你翻个倍。

普通的做法:

一般就是写个数值类:

classNumeric

{

publicintHp;

publicintMaxHp;

publicintSpeed;

//能量

publicintEnergy;

publicintMaxEnergy;

//魔法

publicintMp;

publicintMaxMp;

.....

}

仔细一想,我一个盗贼使用的是能量,为什么要有一个Mp的值?我一个法师使用的是魔法为什么要有能量的字段?纠结这个搞毛,当作没看见不就行了吗?实在不行,我来个继承?

//法师数值

calssMageNumeric:Numeric

{

//魔法

publicintMp;

publicintMaxMp;

}

//盗贼数值

calssRougeNumeric:Numeric

{

//能量

publicintEnergy;

publicintMaxEnergy;

}

10个种族,每个种族7,8种英雄,光这些数值类继承关系,你就得懵逼了吧。面向对象是难以适应这种灵活的复杂的需求的。

再来看看Numeric类,每种数值可不能只设计一个字段,比如说,我有个buff会增加10点Speed,还有种buff增加50%的speed,那我至少还得加三个二级属性字段

classNumeric

{

//速度最终值

publicintSpeed;

//速度初始值

publicintSpeedInit;

//速度增加值

publicintSpeedAdd;

//速度增加百分比值

publicintSpeedPct;

}

SpeedAdd跟SpeedPct改变后,进行一次计算,就可以算出最终的速度值。buff只需要去修改SpeedAdd跟SpeedPct就行了。

Speed=(SpeedInitSpeedAdd)*(1SpeedPct)/100

每种属性都可能有好几种间接影响值,可以想想这个类是多么庞大,初略估计得有100多个字段。麻烦的是计算公式基本一样,但是就是无法统一成一个函数,例如MaxHp,也有buff影响

classNumeric

{

publicintSpeed;

publicintSpeedInit;

publicintSpeedAdd;

publicintSpeedPct;

publicintMaxHp;

publicintMaxHpInit;

publicintMaxHpAdd;

publicintMaxHpPct;

}

也得写个Hp的计算公式

MaxHp=(MaxHpInitMaxHpAdd)*(1MaxHpPct)/100

几十种属性,就要写几十遍,并且每个二级属性改变都要正确调用对应的公式计算.非常麻烦!

这样设计还有个很大的问题,buff配置表填对应的属性字段不是很好填,例如疾跑buff(增加速度50%),在buff表中怎么配置才能让程序简单的找到并操作SpeedPct字段呢?不好搞。

ET框架采用了KeyValue形式保存数值属性

usingSystem.Collections.Generic;

namespaceModel

{

publicenumNumericType

{

Max=10000,

Speed=1000,

SpeedBase=Speed*101,

SpeedAdd=Speed*102,

SpeedPct=Speed*103,

SpeedFinalAdd=Speed*104,

SpeedFinalPct=Speed*105,

Hp=1001,

HpBase=Hp*101,

MaxHp=1002,

MaxHpBase=MaxHp*101,

MaxHpAdd=MaxHp*102,

MaxHpPct=MaxHp*103,

MaxHpFinalAdd=MaxHp*104,

MaxHpFinalPct=MaxHp*105,

}

publicclassNumericComponent:Component

{

publicreadonlyDictionary<int,int>NumericDic=newDictionary<int,int>();

publicvoidAwake()

{

//这里初始化base值

}

publicfloatGetAsFloat(NumericTypenumericType)

{

return(float)GetByKey((int)numericType)/10000;

}

publicintGetAsInt(NumericTypenumericType)

{

returnGetByKey((int)numericType);

}

publicvoidSet(NumericTypent,floatvalue)

{

this[nt]=(int)(value*10000);

}

publicvoidSet(NumericTypent,intvalue)

{

this[nt]=value;

}

publicintthis[NumericTypenumericType]

{

get

{

returnthis.GetByKey((int)numericType);

}

set

{

intv=this.GetByKey((int)numericType);

if(v==value)

{

return;

}

NumericDic[(int)numericType]=value;

Update(numericType);

}

}

privateintGetByKey(intkey)

{

intvalue=0;

this.NumericDic.TryGetValue(key,outvalue);

returnvalue;

}

publicvoidUpdate(NumericTypenumericType)

{

if(numericType>NumericType.Max)

{

return;

}

intfinal=(int)numericType/10;

intbas=final*101;

intadd=final*102;

intpct=final*103;

intfinalAdd=final*104;

intfinalPct=final*105;

//一个数值可能会多种情况影响,比如速度,加个buff可能增加速度绝对值100,也有些buff增加10%速度,所以一个值可以由5个值进行控制其最终结果

//final=(((baseadd)*(100pct)/100)finalAdd)*(100finalPct)/100;

this.NumericDic[final]=((this.GetByKey(bas)this.GetByKey(add))*(100this.GetByKey(pct))/100this.GetByKey(finalAdd))*(100this.GetByKey(finalPct))/100;

Game.Scene.GetComponent<Eventcomponent>().Run(EventIdType.NumbericChange,this.Entity.Id,numericType,final);

}

}

}

1.数值都用keyvalue来保存,key是数值的类型,由NumericType来定义,value都是整数,float型也可以转成整数,例如乘以1000;keyvalue保存属性会变得非常灵活,例如法师没有能量属性,那么初始化法师对象不加能量的keyvalue就好了。盗贼没有法力值,没有法术伤害等等,初始化就不用加这些。

2.魔兽世界中,一个数值由5个值来影响,可以统一使用一条公式:

final=(((baseadd)*(100pct)/100)finalAdd)*(100finalPct)/100;

比如说速度值speed,有个初始值speedbase,有个buff1增加10点绝对速度,那么buff1创建的时候会给speedadd加10,buff1删除的时候给speedadd减10,buff2增加20%的速度,那么buff2创建的时候给speedpct加20,buff2删除的时候给speedpct减20.甚至可能有buff3,会在最终值上再加100%,那么buff3将影响speedfinalpct。这5个值发生改变,统一使用Update函数就可以重新计算对应的属性了。buff配置中对应数值字段相当简单,buff配置中填上相应的NumericType,程序很轻松就能操作对应的数值。

3.属性的改变可以统一抛出事件给其它模块订阅,写一个属性变化监视器变得非常简单。例如成就模块需要开发一个成就生命值超过1000,会获得长寿大师的成就。那么开发成就模块的人将订阅HP的变化:

///监视hp数值变化

[NumericWatcher(NumericType.Hp)]

publicclassNumericWatcher_Hp:INumericWatcher

{

publicvoidRun(longid,intvalue)

{

if(value&gt;1000)

{

//获得成就长寿大师成就

}

}

}

同理,记录一次金币变化大于10000的异常日志等等都可以这样做。

有了这个数值组件,一个moba技能系统可以说已经完成了一半。

代码地址:https://github.com/egametang/Egametang

 

Ben

Ben

线上线下专访、稿件发布合作请联系QQ或微信:328624956

评论已关闭!

相关资讯