|
楼主 |
发表于 2011-7-12 07:51:13
|
显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:31 编辑
注:本章于2011/08/16补充了StringHash的使用方法。
第十八章:哈希表——基础应用
现在我们来看下十六章中的剧情弄成J到底是怎么样的。
先假设事件为任意单位使用“睡眠”技能后触发,函数就叫F吧,技能目标叫A吧:
- function F_Act takes nothing returns nothing
- local unit A=GetSpellTargetUnit()
- set A=null
- endfunction
-
- function F_Cond takes nothing returns Boolean
- return GetSpellAbilityId() == “睡眠”
- endunction
-
- function Init_F takes nothing returns nothing
- local trigger trig=CreateTrigger()
- call TriggerRegisterAnyUnitEvent( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
- call TriggerAddCondition( trig, Condition(function F_Cond) )
- call TriigerAddAction( trig, function F_Act )
- set trig=null
- endfunction
复制代码
PS:还是再提醒下吧,之后我就把没改变的函数截掉了。
显然需要个计时器:
顺便简化下动作部分,就事件触发3s后叫醒A这个单位好了
- function F_TimeAct takes nothing returns nothing
- endfunction
-
- function F_Act takes nothing returns nothing
- local timer t=CreateTimer()
- local unit A= GetSpellTargetUnit()
- call TimerStart( t, 3, false, function F_TimeAct )
- set t=null
- set A=null
- endfunction
复制代码
同学们应该发现了,这个F_TimeAct是“动作”或者说“代码”,也就意为着不能有参数,那么用局部变量记录的单位A无法用传参的方式从F_Act传递到F_TimerAct(注意局部变量只能在一个函数内部使用,别的函数无法使用的,要用的话就必须使用某种手段传递过去),有的同学说用全局变量就好了啊,的确能解决问题,但是注意全局有可能会造成多人使用时变量冲突,尤其是在做技能的时候,一个技能若被多次使用,那么这个全局变量会被最后使用的技能覆盖,也就造成了变量冲突,使用局部变量就能简单有效地避免这样的问题。
虽然这个单位变量无法传递,但是这个计时器却可以传递:
- function F_TimeAct takes nothing returns nothing
- local timer t=GetExpiredTimer()
- set t=null
- endfunction
复制代码
那么有什么方法能使单位随着这个计时器一起传过去吗?
这就要用哈希表把单位A和这个计时器绑定了,换成哈希表的术语说,就是把这个单位A储存在计时器的整数地址主目录下。
估计某些同学忘了,于是复习一下,除了基础数据外的数据都是指针型数据,都有个整数地址,计时器也是个数据类型,自然也有整数地址。
也就是先在F_Act里记录,之后便可以在F_TimeAct里读取,相当于管理员在闹钟响后取出对应闹钟编号的本子查看人名一样。
但是问题又来了。。如何获得计时器(或者其他指针型数据)的整数地址。。
这就要用到GetHandleId()这个函数了,所有的指针型数据都可以用这个来获取整数地址。
记得初始化哈希表~
- globals
- Hashtable ht=InitHashtable()
- endglobals
-
- function F_TimeAct takes nothing returns nothing
- local timer t=GetExpiredTimer()
- set t=null
- endfunction
-
- function F_Act takes nothing returns nothing
- local timer t=CreateTimer()
- local unit A= GetSpellTargetUnit()
- local integer i=GetHandleId( t )
- call TimerStart( t, 3, false, function F_TimeAct )
- // 被储存的是单位不是计时器,计时器只是作为主目录编号
- call SaveUnitHandle( ht, i, 1, A )
- set t=null
- set A=null
- endfunction
复制代码
之后在F_TimeAct中读取这个单位,当然了,还要再获取次这个计时器的地址:
- globals
- Hashtable ht=InitHashtable()
- endglobals
-
- function F_TimeAct takes nothing returns nothing
- local timer t=GetExpiredTimer()
- local integer i=GetHandleId( t )
- local unit u=LoadUnitHandle( ht, i, 1 )
- set t=null
- set u=null
- endfunction
-
- function F_Act takes nothing returns nothing
- local timer t=CreateTimer()
- local unit A= GetSpellTargetUnit()
- local integer i=GetHandleId( t )
- call TimerStart( t, 3, false, function F_TimeAct )
- // 被储存的是单位不是计时器,计时器只是作为主目录编号
- call SaveUnitHandle( ht, i, 1, A )
- set t=null
- set A=null
- endfunction
复制代码
这样单位A就从F_Act传到F_TimeAct了,最后就是在F_TimeAct中写让A做的动作,比如要叫醒A,那么就使用UnitWakeUp()这个函数。。额。。不过这个函数不会影响催眠魔法效果(我没有试过,所以不是很清楚,想知道的同学可以自己试试~)
记得计时器的排泄~(这里的计时器是一次性的,所以不用先暂停以避免BUG了)
- function F_TimeAct takes nothing returns nothing
- local timer t=GetExpiredTimer()
- local integer i=GetHandleId( t )
- local unit u=LoadUnitHandle( ht, i, 1 )
- call UnitWakeUp( u )
- call DestroyTimer( t )
- set t=null
- set u=null
- endfunction
复制代码
最后的最后,就要对哈希表进行排泄了,记得那个管理员把第一批人叫醒后做了什么吗?
撕下“1”这个标签,把本子扔了……
也就是把计时器整数地址的子目录全部清除了,因为之后绑定到闹钟1上的人可能不同了,虽然也可能重复,或者说可以新数据覆盖旧数据,但是一直放在箱子里很占重量的……
有人想偷懒不排泄,于是不服:一本本子能有多重?
那如果有十万本呢?并且其中99999本没用呢?
所以排泄有益于减少内存的占用,提高运行速度~
PS:一般比较常用的是清除一个主目录下的所有子目录,子目录里的数据一般都是被新数据覆盖的,不用替换数据前特地清除一下
- function F_TimeAct takes nothing returns nothing
- local timer t=GetExpiredTimer()
- local integer i=GetHandleId( t )
- local unit u=LoadUnitHandle( ht, i, 1 )
- call UnitWakeUp( u )
- call DestroyTimer( t )
- call FlushChildHashtable( ht, i )
- set t=null
- set u=null
- endfunction
复制代码
之前忘说了于是补上:每个子目录只能记录一个数据。
举个实例的思路吧:
比如击退,施放技能后局部变量获取被击退目标,记作u,绑定u到一个计时器t,然后开启计时器t每X秒运行另一个函数F;
F中获得到期的计时器t,读取绑定的单位u,移动u,用一个整数i记录移动的次数,也就相当于t的循环次数,当达到一定值后(用条件判定式判断)停止t,删除t,清空哈希表中储存在主目录t下的所有子目录。
小误区:
有的同学可能觉得把计时器的地址作为子目录也行,主目录用普通整数(1,2,3,……),是没问题,但是存多个单位时就要改变主目录或者让计时器的地址做算术运算,对于前者,清理的时候要清除主目录而不是子目录,也就会把其它储存的主目录也删掉,自然不行;对于后者。。不仅麻烦,也可能会造成主目录冲突,因为主目录是用数字,而触发做多了之后又记不得哪个用的数字1,哪个用的数字2,万一不小心弄了两个一样的主目录,就冲突了,而且这问题排查的时候还很麻烦,但是用数据的地址就不同了,因为每个数据即使类型相同,地址也是不同的。
补充:
如果要以一个string作为主目录储存怎么办?因为目录必须都是整数,所以字符串明显不能作为目录嘛……这时就需要用到StringHash这个东西,这个函数能把一个string转成一个整数地址,当然,相同的string得到相同的整数,不同的自然不同~
比如说StringHash("a")就把a这个字符串转成了一个整数地址,具体是多少我没研究过不太清楚。。想知道的同学可以用 I2S 这个函数把得到的整数再转成string后看一下。
用法非常简单,之前把单位绑计时器的时候是GetHandleId( GetExpiredTimer() )作为主目录,如果要把这个单位绑到“a”这个string上呢就StringHash( "a" )作为主目录即可~
现在同学们可以去看下血戮魔动冰前辈的哈希表教程了,链接在顶楼,虽然可能还是会晕 ^_^
本章完~
回导航楼 |
|