找回密码
 点一下
查看: 4799|回复: 9

由低级到复杂-技能模板制作教程

[复制链接]
发表于 2009-3-16 13:25:20 | 显示全部楼层 |阅读模式
(被自己忍无可忍的和谐掉了)
【技能模板】
技能模板,可以最大限度的在达到它的指定效果(使用者想要的技能的效果~~)在基础上,开放其功能给使用者。
一个模板,分成三个组成部分:
1.参数(变量),从使用者传递来的参数,模板使用这些参数完成任务(对骨架的设定)
2.动作部分(代码),编译者赋予模板的骨头,模板的中心部分,完成效果就靠它。
3.可拓展部分(代码及其参数),使用者赋予模板的肉和皮肤,模板的外观部分,达到使用者想要的效果,但模板不能完成的功能,或者用来连接其他模板。

其中,一个模板只有动作部分,剩下的参数(变量)、可拓展部分(代码)都是使用者直接传递给模板的。
当然,这实在不破坏模板的封装性的前提下。
如果一个模板是在满足不了使用者的效果(需要更改动作部分的代码),那只有使用者亲自操刀了~~
在这种情况,我们只能说,造成这种情况,这个模板是肯定有责任的~~
所以,本教程中,在实现最大开放性(即满足尽可能大的使用者的效果)的前提下,才会对其进行最大优化。优化主要针对排泄~~效率……(技能只要不是华丽派,那就没必要抠效率)
(PS:此教程中,所有绑定数据都是依靠GameCache+ReturnBug来实现,主要原因下面会讲
1. 初级技能模板的制作
这类模板很简单~~有两类:【1】.主要是按照现今技能的分类和自己多年的技能制作经验,来完成一个超浓缩和简单的技能模板(如群体技能模板、跳跃技能模板等)
【2】.主要是直接通过手头的一个技能,稍微配合自己的经验,通过压缩及在开放性的拓展,完成的技能模板。
先说【1】吧。这是一种基于超多经验的模板。一般人是真的做不出来的。
比如群体技能模板,你可以发现它的代码非常精简。几行代码,完事~~而且功能正因为非常简单,而开放性基本就没法再提升了,也就是说,它已经是极限了,也是很完美的模板了~~
由于这个直接是以一类技能来做的,所以受众是很高的~~~
制作教程【1】:
这种模板,首先,你在脑海中,出现一个此种技能的模糊印象。然后,将其不断地割掉一些非通用的功能或者一些个人风格的东西或没用的东西,再加上一些增加开放性/封装性什么的东西,最后,你发现这个模糊的印象,再也无法删掉/增加什么(或你自己满意)的时候,你就可以开WE编了。
编的时候,一定不要忘了排泄。
还有,一个技能模板一定要尽可能去掉所有的Bug。
然后,你再不断地想,把一些可能的使用这个模板应该实现但你的模板没实现的情况都想到,再进行改进。
小小的建议:在想的时候,可以拿张纸什么的,记一下自己的想法什么的,编的时候好编~~
实例:群体技能模板【1】
首先,我们想:一个群体技能模板,貌似就是选取一定圆范围内的所有符合条件的单位,对每个单位释放一次指定的技能(可能是点/目标两种技能)。
那么我们首先想到一个boolexpr参数,用来辨别单位是否为我们想要的单位。一个real参数,选取单位的半径。三个integer参数:1.对目标释放的技能的id。2.释放技能的等级。3.技能的orderid。两个real参数,一个x,一个y,用来存储中心的坐标。一个integer参数,存储马甲单位的id。
那么,确定函数Cluster_Spell的声明为:
[codes=jass]function Cluster takes real x, real y, real radius, integer u_id, integer a_id, integer a_lv, integer a_order, boolexpr cond returns nothing
endfunction[/codes]
接下来,函数体如下:
[codes=jass]globals
    real Cluster_x = 0.0
    real Cluster_y = 0.0
    integer Cluster_uid = 0
    integer Cluster_aid = 0
    integer Cluster_alv = 0
    integer Cluster_aor = 0
    player Cluster_p = null
endglobals
function Cluster_Action takes nothing returns nothing
    local unit u = CreateUnit( Cluster_p, Cluster_uid, Cluster_x, Cluster_y, 270.0 )
    call UnitAddAbility( u, Cluster_aid )
    call SetUnitAbilityLevel( u, Cluster_aid, Cluster_alv )
    call ShowUnit( u, false )
    //对单位进行禁止食物等设置
    call IssueTargetOrderById( u, Cluster_aor, GetEnumUnit() )
    call UnitApplyTimedLife( u, ‘BHwe’, 1.0 )
    set u = null
endfunction
function Cluster_Spell takes player p, real x, real y, real radius, integer u_id, integer a_id, integer a_lv, integer a_order, boolexpr cond returns nothing
    local group g = CreateGroup()
    call GroupEnumUnitsInRange( g, x, y, radius, cond )
    set Cluster_p = p
    set Cluster_x = x
    set Cluster_y = y
    set Cluster_uid = u_id
    set Cluster_aid = a_id
    set Cluster_alv = a_lv
    set Cluster_aor = a_order
    call ForGroup( g, function Cluster_Action )
    set Cluster_p = null
    set Cluster_x = 0.0
    set Cluster_y = 0.0
    set Cluster_uid = 0
    set Cluster_aid = 0
    set Cluster_alv = 0
    set Cluster_aor = 0
    call DestroyBoolExpr( cond )
    call DestroyGroup( g )
    set g = null
endfunction[/codes]
这还是比较初步的~~。
我们可以这么想:
1.使用者可能会限定被释放技能的单位的个数。
2.使用者可能会释放点目标技能在目标所在点。
…………
所以改进:
[codes=jass]function Cluster_skill_order takes unit u, unit target, integer order returns nothing
    if IssueTargetOrderById( u, order, target ) == false then
        call IssuePointOrderById( u, order, GetUnitX( target ), GetUnitY( target ) )
    endif
endfunction
function Cluster_skill_act takes unit target, real x, real y, integer u_id, player p, integer a_id, integer a_lv, integer order, real lifetime returns nothing
    local unit u = CreateUnit( p, u_id, x, y, 0.0 )
    call UnitAddAbility( u, a_id )
    call SetUnitAbilityLevel( u, a_id, a_lv )
    call ShowUnit( u, false )
    call SetUnitPathing( u, false )
    call SetUnitInvulnerable( u, true )
    call SetUnitUseFood( u, false )
    call SetUnitX( u, x )
    call SetUnitY( u, y )
    call UnitApplyTimedLife( u, ‘BHwe’, lifetime )
    call Cluster_skill_order( u, target, order )
    set u = null
endfunction
function XL_Cluster_skill takes real source_x, real source_y, real targetx, real targety, real radius, integer maxnumber, integer u_id, player p, integer a_id, integer ab_lv, integer order, real lifetime, boolexpr condition returns nothing
    local group g = CreateGroup()
    local integer i = maxnumber
    local unit target = null
    call GroupEnumUnitsInRange( g, source_x, source_y, radius, condition )
    if i < 0 then
        set i = CountUnitsInGroup( g )
    endif
    loop
        exitwhen i <= 0
        set target = GroupPickRandomUnit( g )
        call Cluster_skill_act( target, source_x, source_y, u_id, p, a_id, ab_lv, order, lifetime )
        call GroupRemoveUnit( g, target )
        set target = null
        set i = i - 1
    endloop
    call DestroyBoolExpr( condition )
    call DestroyGroup( g )
    set g = null
    set target = null
endfunction[/codes]
再来看看【2】吧~~
根据我的经验,按【2】的方法来做,你肯定能得到一个模板,但是,要想做得跟【1】中的那样完美,并且受众并不是太高~~
也是需要不断地剔肉,而且比【1】中要剔的多得多得多。
比如我玩Dota看到了谜团的大招——黑洞(…………名可能不是这个o(╯□╰)o),发现这个技能非常的棒~~华丽~~所以我下定决心,要把它改成技能模板~~
那么,首先,看这个黑洞对他进行抽象的分析:1个中心点,周围X范围内的符合条件的单位,被暂停并不断地往中心点牵引,牵引速度Y,持续时间W。
然后我们发现,暂停貌似是一个【个人风格】的东东,很有可能使用者不让单位暂停。
于是,得出此技能模板的参数:
2个real,一个x,一个y,存储中心点坐标。一个boolexpr,选取单位的条件。一个boolean,表示是否暂停单位,一个real,表示被牵引的速度(按距离/秒来算),一个real,表示选取单位的半径。一个real,表示持续时间
由于这个模板是持续一个时间段的,所以我们需要一个不断循环的timer来进行选取并吸引的动作。
PS:一个小小的提示:如果位移什么的有视觉效果的不断循环技能的间隔time<0.03,虽然会很好看,但效率低下。time>0.03,虽然效率很高,但视觉效果很差,time=0.03,既好看又可以很效率。这主要是因为人眼每秒钟看多少个画面研究出来的……囧吧
引入2个函数,用来计算角度和距离。
得出整个函数体:
[codes=jass]globals
    gamecache gc = null
    real Pull_X = 0.0
    real Pull_Y = 0.0
    real Pull_speed = 0.0
endglobals
function Init takes nothing returns nothing
    call FlushGameCache( InitGameCache( “gc.w3v“ ) )
    set gc = InitGameCache( “gc.w3v“ )
endfunction
function H2I takes handle h returns integer
    return h
    return 0
endfunction
function I2G takes integer i returns group
    return i
    return null
endfunction
function I2BE takes integer i returns boolexpr
    return i
    return null
endfunction
function DistanceBetweenLocs takes real x1, real y1, real x2, real y2 returns real
    local real dx = x2 - x1
    local real dy = y2 - y1
    return SquareRoot( dx * dx + dy * dy )
endfunction
function AngleBetweenTwoLocs2Angle_Second takes real x1, real y1, real x2, real y2 returns real
    return bj_RADTODEG * Atan2( y2 - y1, x2 - x1) + 180.0
endfunction
function Pull_Move takes nothing returns nothing
    local unit u = GetEnumUnit()
    local real x = GetUnitX( u )
    local real y = GetUnitY( u )
    local real distance = DistanceBetweenLocs( x, y, Pull_X, Pull_Y )
    local real angle = AngleBetweenTwoLocs2Angle_Second( Pull_X, Pull_Y, x, y )
    if distance <= Pull_speed then
        set x = Pull_X
        set y = Pull_Y
    else
        set x = x + Pull_speed * Cos( angle * bj_DEGTORAD )
        set y = y + Pull_speed * Sin( angle * bj_DEGTORAD )
    endif
    call SetUnitX( u, x )
    call SetUnitY( u, y )
    set u = null
endfunction
function Pull_UnPause takes nothing returns nothing
    call PauseUnit( GetEnumUnit(), false )
endfunction
function Pull_Pause takes nothing returns nothing
    call PauseUnit( GetEnumUnit(), true )
endfunction
function Pull_Action takes nothing returns nothing
    local timer ti = GetExpiredTimer()
    local string s = I2S( H2I( ti ) )
    local real timeout = 0.03
    local real elapsed = GetStoredReal( gc, s, “elapsed“ )
    local real time = GetStoredReal( gc, s, “time“ )
    local boolean pause = GetStoredBoolean( gc, s, “pause“ )
    local real x = GetStoredReal( gc, s, “x“ )
    local real y = GetStoredReal( gc, s, “y“ )
    local real speed = GetStoredReal( gc, s, “speed“ )
    local real radius = GetStoredReal( gc, s, “radius“ )
    local boolexpr be = I2BE( GetStoredInteger( gc, s, “cond“ ) )
    local group g = I2G( GetStoredInteger( gc, s, “g“ ) )
    if elapsed > time then
        if pause then
            call ForGroup( g, function Pull_UnPause )
        endif
        call PauseTimer( ti )
        call DestroyTimer( ti )
        call DestroyGroup( g )
        if be != null then
            call DestroyBoolExpr( be )
        endif
        call FlushStoredMission( gc, s )
    else
        if pause then
            call ForGroup( g, function Pull_UnPause )
        endif        
        call GroupClear( g )
        call GroupEnumUnitsInRange( g, x, y, radius, be )
        if pause then
            call ForGroup( g, function Pull_Pause )
        endif
        set Pull_X = x
        set Pull_Y = y
        set Pull_speed = speed
        call ForGroup( g, function Pull_Move )
        set Pull_X = 0.0
        set Pull_Y = 0.0
        set Pull_speed = 0.0
        call StoreReal( gc, s, “elapsed“, elapsed + timeout )
    endif
    set ti = null
    set s = null
    set g = null
    set be = null
endfunction
function Pull_Spell takes real x, real y, real radius, boolean pause, real speed, real time, boolexpr cond returns nothing
    local group g = CreateGroup()
    local timer ti = CreateTimer()
    local string s = I2S( H2I( ti ) )
    call StoreReal( gc, s, “x“, x )
    call StoreReal( gc, s, “y“, y )
    call StoreReal( gc, s, “radius“, radius )
    call StoreBoolean( gc, s, “pause“, pause )
    call StoreReal( gc, s, “speed“, speed )
    call StoreInteger( gc, s, “cond“, H2I( cond ) )
    call StoreInteger( gc, s, “g“, H2I( g ) )
    call StoreReal( gc, s, “time“, time )
    call TimerStart( ti, 0.03, true, function Pull_Action )
    set g = null
    set ti = null
endfunction[/codes]
╮(╯▽╰)╭别说大家了,就是我看见这么多代码,饿也难受的要死啊~~但是所以在下面我们将省略GameCache和ReturnBug的函数~~
(但是真正的技能中是不能省的~~)
一看上去……哎,受众太小了…………我们再为他增加一些功能:
1.根据单位离中心点的距离,增加牵引的速度
2.如果,使用者想要废物利用,用完的东西不删掉,我们就可以为他保留
3.可能牵引中心是一个单位(他的位置可活动),而不是一个点
…………
代码(在其中加入的code是中级模板的内容,现在暂时不用理会)
(全部代码及说明在我的技能模板的《牵引技能模板》中有,大家可以去下)
(这个模板的高度,就是我最后要讲的高级模板了……所以,只看自己能看的懂的东西吧~~)
[codes=jass]globals
    gamecache Tow_GC = null
    //游戏缓存
    integer Tow_Data_Int = 0
    real Tow_Data_Real = 0.0
    boolean Tow_Data_Bool = false
    string Tow_Data_Str = null
    real Tow_X = 0.0
    real Tow_Y = 0.0
    group Tow_TheGroup = null
    timer Tow_TheTimer = null
    boolexpr Tow_TheCond = null
    real Tow_speed = 0.0
    real Tow_speed_add = 0.0
    //对code-action的辅助参数。
endglobals
//Copy这个
//GameCache Init And ReturnBug Functions
function DistanceBetweenLocs takes real x1, real y1, real x2, real y2 returns real
    local real dx = x2 - x1
    local real dy = y2 - y1
    return SquareRoot( dx * dx + dy * dy )
endfunction
function AngleBetweenTwoLocs2Angle_Second takes real x1, real y1, real x2, real y2 returns real
    return bj_RADTODEG * Atan2( y2 - y1, x2 - x1) + 180.0
endfunction
function TowAbility_UnPause takes nothing returns nothing
    call PauseUnit( GetEnumUnit(), false )
endfunction
function TowAbility_Pause takes nothing returns nothing
    call PauseUnit( GetEnumUnit(), true )
endfunction
function TowAbility_Move takes nothing returns nothing
    local unit u = GetEnumUnit()
    local real x = GetUnitX( u )
    local real y = GetUnitY( u )
    local real distance = DistanceBetweenLocs( x, y, Tow_X, Tow_Y )
    local real angle = AngleBetweenTwoLocs2Angle_Second( Tow_X, Tow_Y, x, y )
    local real speed = Tow_speed + Tow_speed_add * distance
    if distance <= speed then
        set x = Tow_X
        set y = Tow_Y
    else
        set x = x + speed * Cos( angle * bj_DEGTORAD )
        set y = y + speed * Sin( angle * bj_DEGTORAD )
    endif
    call SetUnitX( u, x )
    call SetUnitY( u, y )
    set u = null
endfunction
function TowAbility_Action takes nothing returns nothing
    local timer ti = GetExpiredTimer()
    local string ti_s = I2S(H2I( ti ))
    local real timeout = GetStoredReal( Tow_GC, ti_s, “timeout“ )
    local real elapsed = GetStoredReal( Tow_GC, ti_s, “elapsed“ )
    local real time = GetStoredReal( Tow_GC, ti_s, “time“ )
    local unit u = I2U( GetStoredInteger( Tow_GC, ti_s, “target“ ) )
    local real x = GetStoredReal( Tow_GC, ti_s, “target_x“ )
    local real y = GetStoredReal( Tow_GC, ti_s, “target_y“ )
    local group g = I2G( GetStoredInteger( Tow_GC, ti_s, “group“ ) )
    local code c = I2C( GetStoredInteger( Tow_GC, ti_s, “action“ ) )
    local real action_time = GetStoredReal( Tow_GC, ti_s, “action_time“ )
    local boolean pause = GetStoredBoolean( Tow_GC, ti_s, “pause“ )
    local boolean destroy = GetStoredBoolean( Tow_GC, ti_s, “destroy“ )
    local real speed = GetStoredReal( Tow_GC, ti_s, “speed“ )
    local real speed_add = GetStoredReal( Tow_GC, ti_s, “speed_add“ )
    local real radius = GetStoredReal( Tow_GC, ti_s, “radius“ )
    local boolexpr be = I2BE( GetStoredInteger( Tow_GC, ti_s, “condition“ ) )
    if elapsed > time then
        if pause then
            call ForGroup( g, function TowAbility_UnPause )
        endif
        if destroy then
            call PauseTimer( ti )
            call DestroyTimer( ti )
            call DestroyGroup( g )
            if be != null then
                call DestroyBoolExpr( be )
            endif
            call FlushStoredMission( Tow_GC, ti_s )
        endif
    else
        if u != null then
            set x = GetUnitX( u )
            set y = GetUnitY( u )
        endif
        if pause then
            call ForGroup( g, function TowAbility_UnPause )
        endif        
        call GroupClear( g )
        call GroupEnumUnitsInRange( g, x, y, radius, be )
        if u != null then
            call GroupRemoveUnit( g, u )
        endif
        if pause then
            call ForGroup( g, function TowAbility_Pause )
        endif
        set Tow_X = x
        set Tow_Y = y
        set Tow_speed = speed
        set Tow_speed_add = speed_add
        call ForGroup( g, function TowAbility_Move )
        set Tow_speed = speed / timeout
        set Tow_speed_add = speed_add / timeout
        set Tow_TheGroup = g
        set Tow_TheTimer = ti
        set Tow_TheCond = be
        if c != null and ModuloReal( elapsed, action_time ) == 0.0 then
            set Tow_Data_Int = GetStoredInteger( Tow_GC, ti_s, “help“ )
            set Tow_Data_Real = GetStoredReal( Tow_GC, ti_s, “help“ )
            set Tow_Data_Bool = GetStoredBoolean( Tow_GC, ti_s, “help“ )
            set Tow_Data_Str = GetStoredString( Tow_GC, ti_s, “help“ )
            call ForGroup( g, c )
            call StoreInteger( Tow_GC, ti_s, “help“, Tow_Data_Int )
            call StoreReal( Tow_GC, ti_s, “help“, Tow_Data_Real )
            call StoreBoolean( Tow_GC, ti_s, “help“, Tow_Data_Bool )
            call StoreString( Tow_GC, ti_s, “help“, Tow_Data_Str )
            set Tow_Data_Int = 0
            set Tow_Data_Real = 0.0
            set Tow_Data_Bool = false
            set Tow_Data_Str = null
        endif
        set Tow_X = 0.0
        set Tow_Y = 0.0
        call StoreReal( Tow_GC, ti_s, “speed“, Tow_speed * timeout )
        call StoreReal( Tow_GC, ti_s, “speed_add“, Tow_speed_add * timeout )
        call StoreInteger( Tow_GC, ti_s, “group“, H2I( Tow_TheGroup ) )
        call StoreInteger( Tow_GC, ti_s, “condition“, H2I( Tow_TheCond ) )
        set Tow_speed = 0.0
        set Tow_speed_add = 0.0
        set Tow_TheGroup = null
        set Tow_TheTimer = null
        set Tow_TheCond = null
        call StoreReal( Tow_GC, ti_s, “elapsed“, elapsed + timeout )
    endif
    set ti = null
    set ti_s = null
    set u = null
    set g = null
    set c = null
    set be = null
endfunction
function TowAbility takes timer TheTimer, unit u, real x, real y, real radius, real timeout, real time, real speed, real speed_add, boolean pause, boolean destroygroupandboolexpr, real time_action_run, boolexpr condition, code action returns nothing
    local timer ti = null
    local string ti_s = null
    if TheTimer == null then
        set ti = CreateTimer()
    else
        set ti = TheTimer
    endif
    set ti_s = I2S(H2I( ti ))
    call StoreInteger( Tow_GC, ti_s, “help“, Tow_Data_Int )
    call StoreReal( Tow_GC, ti_s, “help“, Tow_Data_Real )
    call StoreBoolean( Tow_GC, ti_s, “help“, Tow_Data_Bool )
    call StoreString( Tow_GC, ti_s, “help“, Tow_Data_Str )
    set Tow_Data_Int = 0
    set Tow_Data_Real = 0.0
    set Tow_Data_Bool = false
    set Tow_Data_Str = null
    if u != null then
        call StoreInteger( Tow_GC, ti_s, “target“, H2I( u ) )
    else
        call StoreReal( Tow_GC, ti_s, “target_x“, x )
        call StoreReal( Tow_GC, ti_s, “target_y“, y )
    endif
    call StoreInteger( Tow_GC, ti_s, “group“, H2I( CreateGroup() ) )
    call StoreReal( Tow_GC, ti_s, “radius“, radius )
    call StoreReal( Tow_GC, ti_s, “timeout“, timeout )
    call StoreReal( Tow_GC, ti_s, “time“, time )
    if action != null then
        call StoreInteger( Tow_GC, ti_s, “action“, C2I( action ) )
        call StoreReal( Tow_GC, ti_s, “action_time“, time_action_run )
    endif
    if condition != null then
        call StoreInteger( Tow_GC, ti_s, “condition“, H2I( condition ) )
    endif
    call StoreBoolean( Tow_GC, ti_s, “pause“, pause )
    call StoreBoolean( Tow_GC, ti_s, “destroy“, destroygroupandboolexpr )
    call StoreReal( Tow_GC, ti_s, “speed“, speed * timeout )
    call StoreReal( Tow_GC, ti_s, “speed_add“, speed_add * timeout )
    call TimerStart( ti, timeout, true, function TowAbility_Action )
    set ti = null
    set ti_s = null
endfunction[/codes]
说得挺少,代码很多…………大家肯定怨念了吧╮(╯▽╰)╭
其实我是真的语文不太好…………主要大家需要不断地锻炼自己的剔肉意识,看见一个技能,就想怎样模板……你很快就会发现自己的模板有了长足的进步。但是,你在不断的制作中会发现,你到了一定的积累后,发现自己的模板就只能在一个范围内原地踏步……无法再提高开放性了。
那么,如果我在未发现code变量可以为技能模板服务前,这就是GA最强技能模板制作者的表现了……
可惜,我居然发现了这个领域…………所以,大家继续往下看吧~~
2. 中级技能模板的制作
恩可以说,这是一条分界线,之前,技能模板只能根据基本类型的参数来运行程序。
==============================================================
之后,由于code的存在,模板中代码就可以被使用者拓展了……~~
而且是在不对模板本身代码改动的情况下,通过code添加代码~~。
一个形象的比喻:
之前,我们做的技能模板,对于制作者来说,其实就是一个魔兽自己带的技能,可以有很多个参数(比如持续时间什么的)。如果自己想更改其中的一些功能,那么必须破坏掉模板,使得一个好好的模板被卸成很恶心的模样…………或者像模拟魔兽技能一样模拟一个再添加代码…………
这对模板的制作者和使用者都是一件很恶心的事…………
之后,我们做的技能模板,使用者只需要知道一点点的code function的传递参数的方式,就可以很好的在我们做的模板中添加使用者自己的函数了。
code变量,就是一个takes nothing returns boolean/nothing的函数的指针。JASS,可以用TriggerAction/Condition/Group/TimerAction来进行调用。这里因为技能涉及到单位,所以,用物美价廉的group就是上选。至于其他的,我们会在下面专门讨论其可行性
//--------------------------------------------------------------------------------
还有,很多人会问:为什么不用boolexpr?不是效率更高吗?
确实,用boolexpr是很好。但是,由于boolexpr对于group来说,只能调用一次(选取一次集合)(你会发现,不管你怎么试,boolexpr对于一个混杂了使用者要和使用者不要,就是没用的group集合时,如果执行boolexpr,它只能执行一次),这就限制了boolexpr的开放性。
但一个group,却可以无限次调用code(call ForGroup)。更别说,boolexpr是用来给使用者传递条件的,为何要用做动作?没准会有人说:boolexpr可以把条件和动作合并在一起啊~~甚至还有人提出:所有的group动作只要用一个group就行了(这个肯定不行,因为我们需要group存储一些打击过的目标),就用boolexpr运行function。
但是,这在某些技能模板中,这是不能用的。
比如:一个弹射技能模板,我们需要他打击被弹射单位周围radius半径的敌对单位。但是,注意:它是肯定不能弹射自己这个原被弹射单位。所以我们需要GroupRemoveUnit()。然后我们选取一个GroupPickRandomUnit()。获得下一次的目标,此时将GroupClear(),GroupAddUnit(<random unit>),此时对这个单位运行code。如果你要用boolexpr合并的话,那么必然会出现Bug。
//--------------------------------------------------------------------------------

一个code变量,相当于一个过程,从group的GetEnumUnit()得到目标(参数),对这个单位进行动作~~
而且,这个code变量指向的函数,还可以调用其他的使用者自己的函数,所以,就相当于使用者可以动态的添加一段代码并执行………………
现在你可以仔细地体会一下,这个加了code的模板和没加code模板的开放性的对比。
这根本不是一个数量级的。一个加了code的模板,相当于可以在模板中可以加一段使用者自己的代码,而不加code的模板,估计所有GAer用一生的时间完善一个这样一个模板,恐怕都赶不上加了code的模板。
你可以想象一下~~效率比是怎样的恐怖~~
(希望大家想清楚为什么加了code相当于可以让使用者的代码加进来,这相当于,让使用者可以干JASS能达到的任何事情)
比如:我们在上面的Pull_Spell中,没有为单位弄上一个参数,用来说明它的伤害(我的本意就是如此)。
如果在以前,使用者只能把这个模板中添加代码,并且用“替换”将Pull改成其他的名字,以防重名。但是看我们的TowAbility(也就是改进过的黑洞),他有一个code参数,叫action,还有一个real参数,叫time_action_run,恩……就是每几秒运行一次这个action(这个参数必须为timeout,也就是timer执行间隔的整数倍,但本身可以是小数)。如此,我们要还原这个原本的黑洞,那就需要一个函数,BlackHole_Action,用来给予单位们伤害…………
我们将整个属于blackhole自己的函数发上:
[codes=jass]function BlackHole_Action takes nothing returns nothing
    local unit u = GetEnumUnit()
    call SetUnitState( u, UNIT_STATE_LIFE, GetUnitState( u, UNIT_STATE_LIFE ) - 10.0 )
    set u = null
endfunction
function BlackHole_Ability takes real x, real y, integer ab_lv returns nothing
    //可能是技能等级什么的判定
    call TowAbility( null, <BlackNoleStone>, 0.0, 0.0, 500.0, 0.03, 10.0, 50.0, 0.0, true, true, 0.1, <condition>, function BlackHole_Action )
    //some codes
endfunction[/codes]
大家可以很直观的用这个函数和前面的模板的长度进行对比~~,用技能模板还有一个好处,就是一个技能的代码量减小了~~虽然可能效率不是最高,但估计100个技能全用模板(可能多个)的话(打比方)……至少节约30KB(这有什么用!!)
好吧上面是废话~~
但是这却是反映了模板的可重复性,这个可重复性,在这个模板被用的多的时候,发挥的效果很可怕的~~~~~~~
好啦~~来说怎样弄一个code变量。
首先我们说:一个基础的ForGroup执行的code,他只能通过GetEnumUnit()得到被选取的单位。
在我首先发现这个code变量的好处时,当时很自傲的以为,加一个code就相当于加了一段由使用者提供的代码,但是,事实证明,我错了,我被自己很强有力地击倒了。
我当时问了自己一个非常简单,任何技能都有的问题:怎样在code里,获得技能的等级?
真的……当时真是只能说自己自傲、大笨蛋…………
于是,我认真的想,一个代码必须有真正的输入,才能运行………………
于是,我增加了4个全局变量,用来传递使用者自定义的值(以下以牵引技能模板Tow为范例)。
[codes=jass]    integer Tow_Data_Int = 0
    real Tow_Data_Real = 0.0
    boolean Tow_Data_Bool = false
    string Tow_Data_Str = null[/codes]
分别存放一个integer,real,boolean,string。
在TowAbility中,使用:
[codes=jass]    call StoreInteger( Tow_GC, ti_s, “help“, Tow_Data_Int )
    call StoreReal( Tow_GC, ti_s, “help“, Tow_Data_Real )
    call StoreBoolean( Tow_GC, ti_s, “help“, Tow_Data_Bool )
    call StoreString( Tow_GC, ti_s, “help“, Tow_Data_Str )[/codes]
来存储这四个变量的值。
运行code前,使用这些读取值。
[codes=jass]            set Tow_Data_Int = GetStoredInteger( Tow_GC, ti_s, “help“ )
            set Tow_Data_Real = GetStoredReal( Tow_GC, ti_s, “help“ )
            set Tow_Data_Bool = GetStoredBoolean( Tow_GC, ti_s, “help“ )
            set Tow_Data_Str = GetStoredString( Tow_GC, ti_s, “help“ )[/codes]
尤其是加入了string,使得使用者可以从缓存中读取数据了(这还是很自傲的表现)
(其实这是很难实现的,因为我们很难找出一个独一无二的地址string给这个技能使用)
(注意:在code function中使用全局变量给这个function传递参数,是迄今为止最好、效率最高的办法
后来,我又问自己:如果弹射时,每次被击中不是会降低伤害百分比吗?
(假设此时real传递的是damage)
那怎么办呢?
光是现在这个程度,是只能为单位添加一个固定的的伤害值的。
我又想,既然伤害时,是根据弹射的次数进行,那么直接给使用者一个全局变量,用来存储弹射的次数:
[codes=jass]integer Tow_Count = 0[/codes]
实现就不说了(太简单了)。
我当时还很自以为是地想:用一个步数为Count的loop,就可以求出此次的伤害值了。
于是乎,我的第一款合格的code风格模板出炉了。
它的标准格式是这样的:
首先,使用者设置四个全局变量。
在使用者调用技能函数时,传递一个code变量,这个code变量指向使用者自定的函数。
Code和全局变量的值被保存到GameCache。
……
运行code,使用者可以从全局变量中得到当初自己设定的值。
这样一个使用者的code,他可以这样:
[codes=jass]function AFunction takes nothing returns nothing
    //some local values
    local unit u = GetEnumUnit()
    //可以引用XXX_Data_Int, XXX_Data_Real, XXX_Data_Str, XXX_Data_Bool中的值
    //some codes
    set u = null
endfunction[/codes]
后来我又想,很有可能使用者还会需要一些内部的参数,比如speed,或者上一次被弹射的unit什么的。所以,这些也可以添加进全局变量来。
(代码就不放了)
后来,我又突然想到,何不让code运行时,也可以对全局变量赋值,并保留到下一次code执行时应用呢
这样的效果是很好的:一个闪电链的伤害递减15%,只要每次在code中伤害Data_Real点伤害完,再将Data_Real乘以85%就可以让下次的伤害递减15%了。而不是用Count加loop来推算了。
于是:在code执行完后,我们只需将全局变量的值覆盖到GameCache里就行了:
[codes=jass]            call StoreInteger( Tow_GC, ti_s, “help“, Tow_Data_Int )
            call StoreReal( Tow_GC, ti_s, “help“, Tow_Data_Real )
            call StoreBoolean( Tow_GC, ti_s, “help“, Tow_Data_Bool )
            call StoreString( Tow_GC, ti_s, “help“, Tow_Data_Str )[/codes]
你可以发现,这和TowAbility的存储值的代码是一模一样的。确实,这就是一个通过code为code设置全局变量的参数的机制。
TowAbility是通过TowAbility为code设置初始的参数。
(意义稍微有点不同)
这样,一个加了code,并且能勉强运行的,还是很幼稚的技能模板就完成了。
3. 高级技能模板的制作
区别与高级模板和低级模板的分界线有3:
【1】.模板外部参数无限化
【2】.对code开放自己的内部存储Data的位置,使得code可以直接通过GameCache更改/引用内部的存储参数(比如speed什么的),及其安全性
【3】.在destroy所有handle之前,运行承接code。
先解释一遍吧:
【1】.对于使用者提供的参数,不管其类型如何,不管数目多少,都可以传递给code
【2】.对于code,使自己存储的speed什么的地址开放给使用者,让使用者直接通过GameCache覆盖数据
【3】.对于一个效果达到的用完了的技能模板的timer,group什么的东西,如果我们不删除它们,而是需要删除的时候才删除,不需要的时候,就交给使用者提供的一个Last Code。
对于【1】,乍一看貌似很可怕很难实现,但是…………其实很简单…………
就是将自己的handle绑定数据的类型(比如timer),作为参数,由使用者传递,为空时,才创建一个新的handle。
这样,使用者就可以在调用技能模板之前,通过GameCache向自己的code提供参数了。
至于多少个和什么类型,GameCache足够应付了。
以Tow为例:
以前我的TowAbility:
[codes=jass]function TowAbility takes ... returns nothing
    local timer ti = CreateTimer()
    local string s = I2S( H2I( ti ) )
    call Store...( <gc>, s, ... )
    set ti = null
endfunction[/codes]
现在的TowAbility:
[codes=jass]function TowAbility takes timer TheTimer, ... returns nothing
    local timer ti = null
    local string s = null
    if TheTimer == null then
        set ti = CreateTimer()
    else
        set ti = TheTimer
    endif
    set s = I2S( H2I( ti ) )
    call Store...( <gc>, s, ... )
    set ti = null
endfunction[/codes]
我曾经这样想过,干脆把返回类型改成string,让使用者直接用这个string来存储Data,不就行了?
但是,我又被自己问倒了…………“如果使用者闲着没事干想用个TimerDataSystem怎么办?”
…………好吧,都说用GameCache了……怎么又扯到TimerDataSystem上了?(到底我还是搞系统的)
换个,如果与另一个模板结合,这个模板在前一个模板结束后,前个模板不用的timer就可以传递过来,废物利用~~
然后,我们需要为code提供这个绑定数据的handle。
[codes=jass]    timer Tow_TheTimer = null[/codes]
这样,在Code执行时,使用者就可以通过这个来获得它设置的参数。
【2】.这个………很好实现…………由于【1】中已经提供给了使用者handle,而且我们的数据都绑定在handle。所以使用者,只要知道我们存储数据的位置,就可以覆盖数据了。而其中会牵扯到一个安全性(逻辑性)的东西。比如你的一个debug判断在前,然后code运行,很不幸的,将一个bug的数据覆盖了前面正确的数据。然后,你认为这是一个debug的数据,你直接使用它,那么就会有bug出现。
办法很简单,1.把所有的数据直接在函数头下的local直接初始化获得 2.所有数据使用前一行,用if检测。
(个人认为1比较好~~)
【3】.这个算是我的5个模板后我才想出来的。
就是在结束一切之前,运行一个code,将handle等资源直接传递给使用者,由他来处置~~
好吧…………暂时想不起什么了~~~

嗯…………其实你会发现我这个教程写得很没有头绪………………
确实,这应该不叫教程,应该叫《血XXXX发现的技能新领域的各种成果》…………
写得很乱,主要是按照我发现的技巧的顺序来写的。
(越写越像写学术方面的东西………………)
………………
写完这个教程,我可能就不会再深究这个领域了。所以我可能会尽快教会疯人大哥JASS,并让他来继续我的研究。
所以说…………大家崇拜疯人吧(被无数人pia飞~~)!!
除非我又发现了很好的东西,否则我可能就不再做技能模板了。
你可以在技能区搜“技能模板”,以发现我的可能是最后的技能模板的5个技能模板。
开头那句话,虽然很吓人,但是我希望所有GA的Abilityer们能达到我的这个期望…………
一下为一些讨论和未解决的课题~~大家看看吧~~
---------------------------------------------------------------
【1】为何使用GameCache+ReturnBug作为存储结构?
这主要是因为现在大部分地图和制作者,还没能使用数组结构(这会更快,并且string使用会更少)。而且如果使用数组的话,还需要TimerDataSystem,这就有些破坏了技能模板的独立性。不过,等大部分地图都有数组后,应该就可以把GameCache抛弃了。
【2】为何使用group,而不是用trigger的condition?
主要原因是group跟技能是贴的很近的,很多技能都是以单位组/单位为目标研究的。而trigger,只能一次执行一个unit的boolepxr。而且trigger会占用3个handle(trigger,event,boolepxr),而group只有一个(group)。
【3】如何避免GameCache的存储/读取由于code的使用而次数很多
这是个对于GameCache来说很严重的问题。它的效率实在是太低了…………
我的建议是在覆盖存储前对比是否更改过。
或者根本不用全局变量传递自身参数,由使用者自己弄。
【4】一个魔兽风格的技能模板设定系统
这个…………我不说~~~(被无数人pia飞~~)
是疯人大哥告诉我的一个他的想法,打算做成系统………………
估计可以在code以外设定一个很规范的魔兽风格的参数设置的系统~~
大家就等着看好吧~~
【5】运行code的位置无限提供
这是一个现阶段我无法解决的问题。
描述:使用者可以在模板的他想要的任意一条语句之后/前运行他的code(就是动态的添加code)。
这个…………真是不太能解决…………
进而的问题:运行无限数量的code的位置无限提供
这主要是有一个阶段性的东西在里面~~通常来说,弹射一个单位前/后,是不一样的~~
目前的解决方法:
1.在每条语句之前加判定,如果成立则运行code(这个是傻子办法)
2.根据经验,在受众高的地方加code(可解决90%以上的问题)。
但是真正的解决办法是没有的…………
【6】使用T制作可添加代码的技能模板
这个………………实现是完全没问题的。
思想:全局变量=参数
全局变量数组=存储结构(index可以直接递增,直到8191,就回绕)
一个TimerDataSystem………………………………(加了这个……就不算是……纯T了)
另一个办法:疯人哥哥的《T的局域化与变量传递研究演示技能——近战抵抗护盾》中已经解决了这个问题……但是效率异常低下…………疯人不要说我……)
(被自己忍无可忍的和谐掉了)

由低级到复杂-技能模板制作教程及课题.txt

35 KB, 下载次数: 88

评分

参与人数 2威望 +3 收起 理由
sxlrose + 1 优秀文章
与魔共舞 + 2 不懂J的路过~~

查看全部评分

发表于 2009-3-16 13:40:26 | 显示全部楼层
坐上沙发先抽支烟~慢慢品赏
回复

使用道具 举报

发表于 2009-3-16 15:03:12 | 显示全部楼层
“疯人大哥的技能模板设定系统”
我自己都不知道是什么呢……
《龙之回归》U9有个修正BUG的
我在战役区发了地址
你玩那个也行,就是改动很多
回复

使用道具 举报

 楼主| 发表于 2009-3-16 16:43:00 | 显示全部楼层
其实…………俺是想不用秘籍还能打通~~啊~~~~
回复

使用道具 举报

发表于 2009-3-17 03:19:56 | 显示全部楼层
目前已知缓存可以被其他机器入侵,所以还是要用哈希表
回复

使用道具 举报

 楼主| 发表于 2009-3-17 13:19:00 | 显示全部楼层
郁闷………………为什么不加精华…………
回复

使用道具 举报

发表于 2009-3-17 13:55:06 | 显示全部楼层
因为路人没来
恩恩
没办法
经常这样了
回复

使用道具 举报

发表于 2009-3-17 15:04:44 | 显示全部楼层
不过说实话吧,我觉得这个帖子虽然写了很多,不外乎就是
函数主体《---直接扔到自己的函数库就成,你完全不需要知道这个函数是干什么的,扔进去就是。
函数启动主体《-----放在你想用的触发器里面,这个地方可以改参数

命名规范:
使用的系统名称+制作者名称+功能描述名称。
例如
Func_TS_EFF_HitUnitBack(u,su,pointTo)
Func_CS_EFF_HitUnitBack(u,su,pointTo)
回复

使用道具 举报

发表于 2009-3-17 15:06:31 | 显示全部楼层
还有,你知道为什么一般不用code型变量么?因为那个玩意的效率无法预计。
更好的办法是用Trigger自带的一个叫做Condition的东西,那个效率更高。
回复

使用道具 举报

 楼主| 发表于 2009-3-17 16:23:21 | 显示全部楼层
回LS,虽然……Condition效率虽然高……但是……一个trigger+condition……的组合……
好吧…………我拜倒了。
想出了一个方法:
1.1全局trigger(创建)、unit(为空)。
2.将使用者的Condition加入trigger。
3.ForGroup+unit=GetEnumUnit()+ConditionalTriggerExecute( trigger )。
4.RemoveCondition( trigger, condition )
5.unit=null
非常感谢eff的指导
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 点一下

本版积分规则

Archiver|移动端|小黑屋|地精研究院

GMT+8, 2024-12-28 04:05 , Processed in 0.112076 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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