|
(被自己忍无可忍的和谐掉了)
一.排泄
排泄,即避免内存泄漏的发生,而内存泄露则是指什么呢?
内存泄露 = 程序 (创建实例 和 变量指针) 占用了内存,但是你却不释放它,且造成了这块内存的不可释放性(在程序结束以前,这块内存无法被访问、读取/存入数据,即无法操作这块内存)。这样导致 这块内存 成了废内存(用不了那还不废了 ~_~ ),虽然这没有对电脑造成什么【不可回复性的物理性损伤】,但是如果不断的执行导致内存泄露的代码,那么内存的开销将越来越大,直到再也无法申请到新内存以供给新的实例和变量指针,接下来就是程序强制退出。
你们可能看不懂吧~~(一大堆东西谁不烦那~)。其实呢,简单的放在魔兽里来说,就是: 你创建了N个【点】(Location)实例(它们都占用内存),却没有删除它们,使用的内存越来越大,最后,因为没有更多的内存,魔兽卡掉了~~(仅仅是举个例子)。
(1) 一个典型的“点”【Location】泄露:
这是一个典型的“点”泄露trigger,大家看看。
[trigger]Location
事件
单位 - 任意单位 死亡
条件
动作
单位 - 创建 1 个 步兵 给 玩家1(红色) 在 ((触发单位) 的位置) ,面向角度为 默认建筑朝向 度
[/trigger]
这个泄露就在 ((触发单位) 的位置) 上。为什么呢?因为 (XXXXX 的位置) 是这样的: 创建一个 X坐标 为 单位X坐标 Y坐标 为 单位Y坐标 的 “点”。而这个点(即一个实例) 则传递给 (创建单位)。 然后,它没有被(清除点)【RemoveLocation】给清除(即没有释放这个点的内存),也没有一个变量指针指向它(造成这个点的内存的不可访问性),这样就形成了内存泄露。
修改错误的方法是这样的:
[trigger]Location
事件
单位 - 任意单位 死亡
条件
动作
设置 loc = ((触发单位) 的位置)
单位 - 创建 1 个 步兵 给 玩家1(红色) 在 loc ,面向角度为 默认建筑朝向 度
点 - 清除 loc
自定义代码: set udg_loc = null
[/trigger]
这回是弄了个变量(其实是变量指针),把它指向为这个点实例((触发单位) 的位置)(这样就使这个点实例占用的内存有可释放性)。然后,引用这个变量指针指向的 点, 创建单位。然后,再引用这个变量指针指向的点, 用清除点【RemoveLocation】, 删除这个点, 并释放这个点占用的内存。最后,释放这个变量指针的内存(设置为空,即null)
当然,几乎每个人都可能犯这样的可爱又引人发笑的错误:
[trigger]Location
事件
单位 - 任意单位 死亡
条件
动作
单位 - 创建 1 个 步兵 给 玩家1(红色) 在 ((触发单位) 的位置) ,面向角度为 默认建筑朝向 度
点 - 清除 ((触发单位) 的位置)
自定义代码: set udg_loc = null
[/trigger]
为什么这是一个错误呢?因为这创建了2个点,一个点是用来创建单位的,而另一个则逗了。创建出来之后,就给删了,什么也没干,还降低效率。
举一反三,依此类推,什么单位组(group),特殊效果(effect)、闪电效果(lightning)、计时器(timer)…………都需要清除(用DestroyGroup/DestroyTimer/DestroyLightning/DestroyEffect)
还有一些小技巧。如set bj_wantDestroyGroup = true等,很多人都介绍过,在此就不多说了。
(2) 局部变量泄露:
怎么说呢~~,这个局部变量泄漏,其实是局部变量指针在函数执行完前没有清空(指针也要有内存以存储它指向的实例的ID):
[codes=jass]function A takes nothing returns nothing
local unit u = GetTriggerUnit()
//some codes here
endfunction[/codes]
,而也造成了内存泄露。解决的方法很简单,就是这样:
[codes=jass]function A takes nothing returns nothing
local unit u = GetTriggerUnit()
//some codes here
set u = null
endfunction[/codes]
设置为空(null)即可。
造成局部变量泄漏的人为原因有二:一是不知道有这么个泄露,二是忘记在函数【执行】完前设置为空(set XXX = null)。
当然,这样的情况也请注意:
[codes=jass]function A takes nothing returns nothing
local unit u = GetTriggerUnit()
//some codes here
if GetUnitLifePercent( u ) > 100.0 then
return
endif
//some codes here
set u = null
endfunction[/codes]
需要在return前也清空局部变量指针:
[codes=jass]function A takes nothing returns nothing
local unit u = GetTriggerUnit()
//some codes here
if GetUnitLifePercent( u ) > 100.0 then
set u = null
return
endif
//some codes here
set u = null
endfunction[/codes]
小技巧:大家最好在函数的开头声明局部变量时,就在函数最后写上释放局部变量的代码,这样也不太会忘记。
(3) 全局变量“泄露”:
可能看帖的大多数人都只知道局部变量泄漏,而不知道全局变量“泄露”。主要呢是因为全局变量“泄露”并不是不可恢复性的。(其实这是GA 某个人发的贴,我才知道的。本人在此强烈建议kook大人加这个人(精华)或(威望+10)。)
魔兽的变量全是指针,局部变量是,全局变量也是。所以他们在用完后都应设置为空。全局变量和局部变量的不同在于,全局变量可以重复使用,而局部变量则在函数结束之后,除非是被设置为空,否则魔兽并不会自动删除它们。但是如果全局变量没有设置为空,它则会一直占用内存用来指向一个错误数据,直至重新给它赋值(重新指向一个实例)。(PS:貌似在这点上,玻璃渣的所有地图都没有对大家起到示范作用。)解决方法也很简单,将变量设置为空就可以了。
[trigger]Location
事件
单位 - 任意单位 死亡
条件
动作
单位 - 创建 1 个 步兵 给 玩家1(红色) 在 ((触发单位) 的位置) ,面向角度为 默认建筑朝向 度
点 - 清除 ((触发单位) 的位置)
自定义代码: set udg_loc = null (!!!)
[/trigger]
(4) GameCache“泄露”:
这个泄露几乎对魔兽没有影响,因为这个东西占用的内存实在是太小了。说白了就是吃力不讨好,但是为了真正的环保,大家还是努力吧。就是在GameCache里存储数据,但在他们失效后,不清空他们,就形成泄露。用来清除的就是这些函数,相信大家都会用吧:
[codes=jass]
FlushStoredMission
FlushStoredInteger
FlushStoredBoolean
FlushStoredString
FlushStoredUnit
FlushStoredReal
[/codes]
在这里,顺便说一下HaveStoredXXXX和GetStoredXXXX != 0/null/false 的区别。
HaveStoredXXXX的意思是:这里是否有数据( 【没有存储过/已用FlushStoredXXXX清空】 时 为 true)
而GetStoredXXXX! = 0/null/false 只是对比存储的数据(如果没有数据则返回0/null/false)是否为 0/null/false, 不是有没有数据的意思。
现已给出测试代码(本人编译):
[codes=jass]function TestForHaveStoredXXXXAndGetStoredXXXX takes nothing returns nothing
local gamecache gc = null
call FlushGameCache( InitGameCache( thegc.w3v ) )
set gc = InitGameCache( thegc.w3v )
if HaveStoredBoolean( gc, a, b ) then
call BJDebugMsg( 1:True! )
else
call BJDebugMsg( 1:False! )
endif
call StoreBoolean( gc, a, b, false )
if HaveStoredBoolean( gc, a, b ) then
call BJDebugMsg( 2:True! )
else
call BJDebugMsg( 2:False! )
endif
call FlushStoredBoolean( gc, a, b )
if HaveStoredBoolean( gc, a, b ) then
call BJDebugMsg( 3:True! )
else
call BJDebugMsg( 3:False! )
endif
call FlushGameCache( gc )
set gc = null
endfunction[/codes]输出:
1:False
2:True
3:False
(5) **** String“泄露” ****:
注:此为JASS班毕业以上学历(熟练使用Return Bug+GameCache+Timer)者的课程,有兴趣就看看,没兴趣就别难为自己了。
String“泄露”是很早就被众多JASSer所“知道”,但却没有意识到的一种对内存可重复使用但不可释放的“泄露”。
(所白了就是使用string)
魔兽所有的string都具有 【不可释放性】 但极为有趣的是,string还具有 【可重复使用性】,这跟全局变量很相像。在魔兽中,所有string全要用变量储存它,且一旦申请到内存,就无法释放。但是如果在函数A里使用了A,在函数B里又使用了A,那么这回只是引用A,而不是再创建一个A并占用内存。
实例:
[codes=jass]function A takes nothing returns nothing
call BJDebugMsg( A )
endfunction
function B takes nothing returns nothing
call BJDebugMsg( B )
call BJDebugMsg( A )
endfunction
function Init takes nothing returns nothing
call A()
//此时【创建】了A
call B()
//此时【创建】了B
//此时【引用】了A
endfunction[/codes]
这段话请尽量理解。
注1:魔兽给string分配内存的方式是:已使用的string,达到了25个,就再申请25个string的内存,直到这次分配的内存被新的25个string充填,就再次申请25个内存(步骤循环)………………
注2:魔兽string数量的上限是10万条(再多虽不会强制退出,但每一次引用/创建string将会把人的电脑卡死:速度:3帧/s)
注3:一条string最多860字符(1汉字 = 3字符)
string基础
① 字符串连接+: a+b
就是引用/创建a和b,再引用/创建ab。
② 字符串截断SubString: SubString( ab, 0, 1 )
就是引用/创建ab,再引用/创建a
③ 字符串大小写转换StringCase: StringCase( ab, true )
就是引用/创建ab, 再引用/创建AB
④ I2S: I2S( 100 )
就是引用/创建100
⑤ R2S: R2S( 123.4567 )
就是引用/创建123.457(请注意,R2S只有小数点后3位数的转换且会四舍五入)
⑥ R2SW: R2SW( 123.4567, 1, 3 )
就是引用/创建123.457
注:R2SW参数说明:1.要转换的实数 2.宽度(貌似没有用) 3.精度
几乎所有的在编译阶段无法估计数量的string泄露都是这个东东产生的:
I2S( H2I( <a handle value> ) )
为什么呢?因为每一个handle都有自己独一无二的Handle编号(其实是地址),而这个东东就是把这个编号转换成string,这无疑是一场灾难!
假设:一个地图里【出现】过1000个单位,那么就会有 小于或等于1000个string被创建,他们都是10XXXX的形式。而且这种string在游戏中除了使用在GameCache读取数据时被引用以外,是不可能被正常使用的。所以,我更愿意叫他们为“灰色的黄金”。
注1:参考正常使用string范畴:1000条中/英文信息+“0”~“100”(非小数)
注2:参考隐性使用string范畴:在游戏初始化/开始XXX秒时,创建的Handle的地址的string。
注3:参考尽量避免string范畴1:在游戏进行时,创建Handle并转换地址为string。
注4:参考尽量避免string范畴2:- “-- ”“--- ”“----”不应写成这样:
[codes=jass]function A takes nothing returns nothing
//some codes here
loop
set i = i + 1
exitwhen i > 4
set s = s + -
endloop
//some codes here
endfunction[/codes]
最好使用全局变量数组:
[codes=jass]
globals
string S[3]
endglobals
function InitS takes nothing returns nothing
set S[0] = -
set S[1] = --
set S[2] = ---
set S[3] = ----
//此函数在地图开始时初始化就可以了。
endfunction
[/codes]
既然我们在前面都说了要尽量避免使用H2S,但是,ReturnBug+GameCache要是没了它,估计就没得玩了。那怎么办呢?
我在这里给你们一个10000年也不会处Bug的方法,大家要记住了!:
(6) **** 改进的H2S(指针算术) ****:
[codes=jass]globals
integer const_InitHandleNumber = 0
endglobals
function H2I takes handle h returns integer
return h
return 0
endfunction
function InitHandleNumber takes nothing returns nothing
local lighting l = Lighting( 0, 0, 0, 0 )
set const_InitHandleNumber = H2I(l )
call RemoveLighting( l )
set l = null
endfunction
//此函数要在地图初始化时运行。
function H2S takes handle h returns string
return I2S( H2I( h ) - const_InitHandleNumber )
endfunction[/codes]
这是我改进后的H2S。原理是用需要转换的Handle的地址再减去地图初始化时创建的Location的地址再I2S。
比如在运行InitHandleNumber之后,你再创建一个Timer,输出H2S( < The Timer > ),你会发现输出的是0。
这样的话至少把地图初始化后创建的一部分的Handle的地址转换成string时,得到的是“0”~“100”的正常适用范围。
这个技术被人们称为“指针算术”。
当然,还有另一种从根本上避免使用H2S但同时又能继续像GameCache存储数据的方式。
PS:如果使用lighting的话。
这样改H2I也可以:
[codes=jass]
function H2I takes handle h returns integer
return h
return 0
endfunction
function H2I_Debug takes handle h returns integer
return H2I( h ) - const_InitHandleNumber
endfunction[/codes]
其实这样没多大用………………
(7) **** 利用全局变量数组替代GameCache+H2S ****:
[codes=jass]globals
integer g_UnitDataNumber = 0
trigger array g_UnitDamageTrigger
triggeraction array g_UnitDamageTriggerAction
endglobals
function createUnitData takes unit u returns integer
local trigger t = CreateTrigger()
set g_UnitDataNumber = g_UnitDataNumber + 1
set g_UnitDamageTrigger[ g_UnitDataNumber ] = t
call TriggerRegisterUnitEvent( t, u, EVENT_UNIT_DAMAGED )
set g_UnitDamageTriggerAction[ g_UnitDataNumber ] = TriggerAddAction( t, function XXXXXXXXXXX )
set t = null
return g_UnitDataNumber
endfunction
function InitUnitData takes unit u returns nothing
call SetUnitUserData( u, createUnitData( u ) )
endfunction
function RemoveUnitData takes unit u returns nothing
local integer id = GetUnitUserData( u )
local trigger t = null
if id != 0 then
set t = g_UnitDamageTrigger[ id ]
call TriggerRemoveAction( t, g_UnitDamageTriggerAction[ id ] )
call DestroyTrigger( t )
set g_UnitDamageTrigger[ id ] = null
set g_UnitDamageTriggerAction[ id ] = null
endif
set t = null
endfunction[/codes]
当然,数组是有使用限制的,每一个变量数组最多只能有8192个元素(即指针),也就是说:XXXXX[0-8191]才是合法的。剩下的[<0]and[>8191]就不行了。
演示中并没有考虑这个问题。
要解决这个问题很难。但是————————
嗯~~~~~上天总是对懒人没有眷顾,但是貌似这次他开恩了~~
(8) **** 利用VJ中的struct(结构)替代自己的全局变量数组 ****:
不得不说,VJ实在是绝对的懒人工具。本人现在就深深沉迷于其中…………~~~
VJ的struct更是将我们绞尽脑汁也较难以解决的全局变量数组变得极为简单了(自己就有创造/释放的功能)~~~~~
算了不废话,VJ还是各位自己去找找看吧~~我只能说它是个好极了的东东~~我现在只给出演示的VJ型代码
[codes=jass]struct UnitData
trigger DamageTrigger
triggeraction DamageTriggerAction
static method create takes unit u returns UnitData
local UnitData temp = UnitData.allocate()
local trigger t = CreateTrigger()
set temp.DamageTrigger = t
call TriggerRegisterUnitEvent( t, u, EVENT_UNIT_DAMAGED )
set temp.DamageTriggerAction = TriggerAddAction( t, function XXXXX )
set t = null
call SetUnitUserData( u, temp )
return temp
endmethod
method onDestroy takes nothing returns nothing
call TriggerRemoveAction( this.DamageTrigger, this.DamageTriggerAction )
call DestroyTrigger( this.DamageTrigger )
set this.DamageTrigger = null
set this.DamageAction = null
call this.destroy()
endmethod
endstruct
function GetUnitData takes unit u returns UnitData
return GetUnitUserData( u )
endfunction[/codes]
当然,这仅仅适用于有SetXXXUserData的Handle(目前为止,只有unit和item有这个成员函数:SetUnitUserData()and SetItemUserData() )
(9) **** 利用H2I使诸如Trackable或Destruction这些没有SetXXXUserData的Handle使用全局变量数组 ****:
问题总是有可能解决的。
[codes=jass]globals
integer Trks_InitHandleNumber = 0
real array Trks_X
real array Trks_Y
endglobals
function H2I takes handle h returns integer
return h
return 0
endfunction
function InitTrksHandleNumber takes nothing returns nothing
local location loc = Location( 0, 0 )
set Trks_InitHandleNumber = H2I( loc )
call RemoveLocation( loc )
set loc = null
endfunction
//此函数必须要在地图初始化(且在其他一切创建Handle动作之前)时运行。
function GetTrkId takes trackable trk returns integer
return H2I( trk ) - Trks_InitHandleNumber
endfunction
function CreateTrackableXL takes string trackableModelPath, real x, real y, real facing returns trackable
local trackable trk = CreateTrackable( trackableModelPath, x, y, facing )
local integer id = GetTrkId( trk )
set Trks_X[ id ] = x
set Trks_Y[ id ] = y
return trk
endfunction[/codes]
这个…………原理和【改进的H2S】基本一样(指针算术)只不过……换了一种方式………………且没有涉及string………………
*小小的脑力题:
本人曾经这样想过能不能这样:?
警告!!下面的代码有Bug!请勿运用于魔兽中!!
[codes=jass]globals
integer Trks_InitHandleNumber = 0
trigger array Trks_Trig
triggeraction array Trks_Action
endglobals
function H2I takes handle h returns integer
return h
return 0
endfunction
function InitTrksHandleNumber takes nothing returns nothing
local location loc = Location( 0, 0 )
set Trks_InitHandleNumber = H2I( loc )
call RemoveLocation( loc )
set loc = null
endfunction
//此函数必须要在地图初始化(且在其他一切创建Handle动作之前)时运行。
function GetTrkId takes trackable trk returns integer
return ( H2I( trk ) - Trks_InitHandleNumber ) / 4
//注意,这里/4是因为创建一个trackable还要创建trigger,triggeraction,event总共4个handle,所以这里/4就可以了。
endfunction
function CreateTrackableXL takes string trackableModelPath, real x, real y, real facing, code action, boolean IsHit returns trackable
local trackable trk = CreateTrackable( trackableModelPath, x, y, facing )
local integer id = GetTrkId( trk )
local trigger t = CreateTrigger()
set Trks_Trig[ id ] = t
if IsHit then
call TriggerRegisterTrackableHitEvent( t, trk )
else
call TriggerRegisterTrackableTrackEvent( t, trk )
endif
set Trks_Action[ id ] = TriggerAddAction( t, action )
set t = null
return trk
endfunction[/codes]
恩~~~~最后本人发现貌似这种方法不可行,很容易导致数据覆盖,所以就没有用。
问题就出在GetTrkId的/4上了。
想知道为什么吗?(*^__^*) 嘻嘻……自己想想吧
想出来的奖励:
恭喜你,能想出这道题的人,凭你的能力你在GA应该能拿到12+声望了~~~~
答案在下面:
答案:
很简单,举个例子来说,你先运行InitTrksHandleNumber(),然后创建了2个别的handle,接着又用CreateTrackableXL()创建了一个trackable<trkA>及他的附属trigger,triggeraction,event。再把那两个handle中后面创建的那个删了。然后你再用CreateTrackableXL()创建了一个trackable<trkB>及他的附属trigger,triggeraction,event。恩…………这时你就会发现有了数据覆盖了……………………
TrkA的地址-Trks_InitHandleNumber = 2
TrkB的地址-Trks_InitHandleNumber = 1
2/4 = 1
1/4 = 1
一个实例:
[codes=jass]globals
integer Trks_InitHandleNumber = 0
trigger array Trks_Trig
triggeraction array Trks_Action
real array Trks_Facing
endglobals
function H2I takes handle h returns integer
return h
return 0
endfunction
function InitTrksHandleNumber takes nothing returns nothing
local location loc = Location( 0, 0 )
set Trks_InitHandleNumber = H2I( loc )
call RemoveLocation( loc )
set loc = null
endfunction
//此函数必须要在地图初始化(且在其他一切创建Handle动作之前)时运行。
function GetTrkId takes trackable trk returns integer
return ( H2I( trk ) - Trks_InitHandleNumber ) / 4
//注意,这里/4是因为创建一个trackable还要创建trigger,triggeraction,event总共4个handle,所以这里/4就可以了。
endfunction
function CreateTrackableXL takes string trackableModelPath, real x, real y, real facing, code action, boolean IsHit returns trackable
local trackable trk = CreateTrackable( trackableModelPath, x, y, facing )
local integer id = GetTrkId( trk )
local trigger t = CreateTrigger()
set Trks_Trig[ id ] = t
if IsHit then
call TriggerRegisterTrackableHitEvent( t, trk )
else
call TriggerRegisterTrackableTrackEvent( t, trk )
endif
set Trks_Action[ id ] = TriggerAddAction( t, action )
set Trks_Facing[ id ] = facing
set t = null
return trk
endfunction
function main takes nothing returns nothing
local trackable trkA = null
local trackable trkB = null
local location temp = null
call InitTrksHandleNumber()
call CreateTrackable( , 0, 0, 0 )
set temp = Location( 0, 0 )
set trkA = CreateTrackableXL( , 30, 60, 22, null, true )
call BJDebugMsg( Before create, TrkA.Facing = + R2S( Trks_Facing[ GetTrkId( trkA ) ] ) )
call RemoveLocation( temp )
set temp = null
set trkB = CreateTrackableXL( , 30, 60, 11, null, true )
call BJDebugMsg( After created, TrkA.Facing = + R2S( Trks_Facing[ GetTrkId( trkA ) ] ) )
call BJDebugMsg( And TrkB.Facing = + R2S( Trks_Facing[ GetTrkId( trkB ) ] ) )
endfunction
//游戏从这里开始(假定)[/codes]
理论输出:
Before create, TrkA.Facing = 22.000
After created, TrkA.Facing = 11.000
And TrkB.Facing = 11.000
(10) **** 用unit+全局变量数组+响应单位死亡事件替代Timer ****:
恩………………想来肯定有很多很多WEer们使用Timer吧。用我的方法虽然不会涉及到string。但是………………整个游戏的handle最高峰的数量没准就>8192呢?那我们前面介绍的方法岂不是很危险………………
嗯…………现在本人又想出了一个办法,但是…………鱼与熊掌不可兼得……我这个方法不仅效率比Timer低,还有可能与别的什么响应单位XX事件的动作混合。
原理很简单,unit不是有个生命周期吗?把timer换成 unit(控制时间) + 全局变量数组(储存数据) + 任意单位死亡的trigger(执行动作) 不就行了?当然既然说到了全局变量数组,就没有理由不用VJ的struct…………………………
实例讲解:XX时间后,创建TimerData.unit_num个TimerData.unit_id在(TimerData.x,TimerData.y)
首先,是在自定义脚本代码中的代码:
[codes=jass]struct TimerData
integer unit_id
integer unit_num
integer player_id
real x
real y
static method create takes integer id. integer num, real x, real y, integer pid returns TimerData
local TimerData temp = TimerData.allocate()
set temp.unit_id = id
set temp.unit_num = num
set temp.x = x
set temp.y = y
set temp.player_id = pid
return temp
endmethod
method onDestroy takes nothing returns nothing
set this.unit_id = 0
set this.unit_num = 0
set this.x = 0.0
set this.y = 0.0
set this.player_id = 0
call this.destroy()
endmethod
endstruct
function StartTimer takes real time returns nothing
local unit u = CreateUnit( Player( 13 ), 'hZZZ', 0.0, 0.0, 0.0 )
local TimerData TD = TimerData.create( 'hZZO', 5, 0.0, 0.0, 0 )
call ShowUnit( u, false )
call UnitApplyTimedLife( u, 'BHwe', time )
call SetUnitUserData( u, TD )
set u = null
endfunction[/codes]
然后是在响应单位死亡的trigger里的代码:
[codes=jass]function Trig________________u_Conditions takes nothing returns boolean
return GetUnitTypeId(GetTriggerUnit()) == 'hfoo'
endfunction
function Trig________________u_Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local TimerData TD = GetUnitUserData( u )
local integer i = 0
loop
set i = i + 1
exitwhen i > TD.unit_num
set bj_lastCreatedUnit = CreateUnit( Player( TD.player_id ), TD.unit_id, TD.x, TD.y, 0.0 )
endloop
call TD.OnDestroy()
call RemoveUnit( u )
set u = null
endfunction
//===========================================================================
function InitTrig________________u takes nothing returns nothing
set gg_trg________________u = CreateTrigger( )
call TriggerRegisterPlayerUnitEventSimple( gg_trg________________u, Player(13), EVENT_PLAYER_UNIT_DEATH )
call TriggerAddCondition( gg_trg________________u, Condition( function Trig________________u_Conditions ) )
call TriggerAddAction( gg_trg________________u, function Trig________________u_Actions )
endfunction[/codes]
最后补充一下,这种东西只适合于不循环的Timer。虽然可循环的Timer也可用这个做,但是效率会大大降低………………而且像延时复活小兵的RPG的系统。一般来说。其实用一个不断循环的Timer即可(理论:每0.5秒减少地图中所有已死亡单位的一个值0.5,然后如果这个值小于0,则复活单位)
(11) 几个普通点的节省string的小技巧:
嗯~~总之~~………………我不得不承认………………除非是在GameCache+H2S用得极为习惯的情况下,积累了大量的魔兽经验,量变引发质变,才有可能真正理解我说的是什么意思(其实就是对GameCache+H2S发牢骚)。
下面几个小技巧则是基本没什么技术含量的。但是会比较管用。
Ⅰ【Real2Integer2String】
这个想理解也很简单,就是用I2S(R2I(XXXX))替代R2S(XXXX)。这是为什么呢?
主要是因为R2S会把实数的小数点后3位也转换成string。举个最简单的例子:转换 单位的生命百分比 为字符串。则可能有:
“0.000”~“100.000”共100001个string会出现。
但是如果用:转换 转换 单位的生命百分比 为整数 为字符串。则可能有:
“0”~“100”共101个个string会出现。
Ⅱ【分离TextTag法】
我做【全屏装备栏】时发现的一个技巧,就是将要显示包含数字的文字分离开,这样前面的文字算一个string,后面的数值再单算string,而不是前面的文字、后面数值也算string、结果两者连结起来还要算string。
比如这样一个东东:
[codes=jass]function ShowStr takes nothing returns nothing
local integer value = GetRandomInt( 1, 100 )
local texttag tt = CreateTextTag()
call SetTextTagText( tt, 装备增加力量: + I2S( value ), 0.023 )
call SetTextTagPos( tt, 0.0, 0.0, 10.0 )
call SetTextTagColor( tt, 200, 100, 122, 255 )
set tt = null
endfunction[/codes]
为了避免涉及更多的string。我们可以这么做:
[codes=jass]function ShowStr takes nothing returns nothing
local integer value = GetRandomInt( 1, 100 )
local texttag tt_1 = CreateTextTag()
local texttag tt_2 = CreateTextTag()
call SetTextTagText( tt_1, 装备增加力量:, 0.023 )
call SetTextTagPos( tt_1, 0.0, 0.0, 10.0 )
call SetTextTagColor( tt_1, 200, 100, 122, 255 )
call SetTextTagText( tt_2, I2S( value ), 0.023 )
call SetTextTagPos( tt_2, 10.0, 0.0, 10.0 )
call SetTextTagColor( tt_2, 200, 100, 122, 255 )
set tt_1 = null
set tt_2 = null
endfunction[/codes]
嗯~~~可能每次Show一下Str可能都要创建两个texttag。所以我们简化一下:
[codes=jass]globals
constant texttag Str_tt = CreateTextTag()
endglobals
function Init takes nothing returns nothing
call SetTextTagText( Str_tt, 装备增加力量:, 0.023 )
call SetTextTagPos( Str_tt, 0.0, 0.0, 10.0 )
call SetTextTagColor( Str_tt, 200, 100, 122, 255 )
call SetTextTagVisibility( Str_tt, false )
endfunction
function ShowStr takes nothing returns nothing
local integer value = GetRandomInt( 1, 100 )
local texttag tt_2 = CreateTextTag()
call SetTextTagVisibility( Str_tt, true )
call SetTextTagText( tt_2, I2S( value ), 0.023 )
call SetTextTagPos( tt_2, 10.0, 0.0, 10.0 )
call SetTextTagColor( tt_2, 200, 100, 122, 255 )
set tt_2 = null
endfunction[/codes]
好了~~~~泄露就此告一段落。现在我们再来看看怎样让我们的效率提高些吧~~~
二.提高效率
编了很多系统,也积累了不少提高效率的经验。发现很多提高效率的方法,但大多数都是视情况而定,才能提高效率。真正的不应该做的事情很少。而严禁使用的更少………………
其实也有人发不过关于提高效率的贴,但是那贴只介绍了不涉及到纯Jass/编程领域的优化方法。
(1) 严禁:递归:
恩………………在我看来,使用递归并不是什么可怕的事情,但是,在正规地图甚至于发布在网上的演示/系统,如果使用递归(尤其是用了递归还不告诉别人的),这就有点…………。其实想不用递归也很简单。使用loop当函数体,全局变量当函数参数即可。诸如:
[codes=jass]function Loop takes unit u, real v returns real
local real oldlife = GetUnitState( u, UNIT_STATE_LIFE )
local real newlife = oldlife - v
if newlife < 0.0 then
return GetUnitX( u )
else
call SetUnitX( u, GetUnitX( u ) + v )
endif
return Loop( u, v )
endfunction
function main takes nothing returns nothing
call BJDebugMsg( Unit.X = + R2S( Loop( CreateUnit( Player( 0 ), 'hfoo', 0.0, 0.0, 0.0 ), 10.0 ) ) )
endfunction[/codes]
完全可以这样,不使用递归:
[codes=jass]globals
real g_x
boolean g_b
endglobals
function Loop takes unit u, real v returns nothing
local real oldlife = GetUnitState( u, UNIT_STATE_LIFE )
local real newlife = oldlife - v
if newlife < 0.0 then
set g_b = true
else
call SetUnitX( u, GetUnitX( u ) + v )
endif
set g_x = GetUnitX( u )
endfunction
function main takes nothing returns nothing
local unit u = CreateUnit( Player( 0 ), 'hfoo', 0.0, 0.0, 0.0 )
loop
call Loop( u, 10.0 )
exitwhen g_b
endloop
call BJDebugMsg( Unit.X = + R2S( g_x ) )
set g_b = false
set g_x = 0.0
set u = null
endfunction[/codes]
思想就是利用loop,把原递归函数需要传递给自己的参数/返回的值变成全局变量,每一次调用函数都将数据写入全局变量,覆盖上一次的数据,再让下一次调用使用这些数据即可。注意:在“伪”递归完成后,请将所有的有关于这个“伪”递归的变量清空,否则会引起Bug。
递归的可怕性你可以去算法区找关于雪花函数的贴,看看你的电脑能挺过多少次递归。
当然,不能用这个方法替代递归的,那就放弃做这个作品吧,毕竟使用递归在正规游戏中是会引起lag的(大多数玩家最讨厌的就是这个情况)。
(2) Timer三节:
1.N个Timer 2 一个 Timer
这个方法其实很简单,就是将多个Timer替换成一个不断循环的Timer。
下面是复活死尸系统中一小段截取:
[codes=jass]function B takes nothing returns nothing
local timer ti = GetExpiredTimer()
local unit u = GHU( ti, TheUnit )
call CreateUnit( GetOwningPlayer(u), GetUnitTypeId(u), GetUnitX(u), GetUnitY(u), GetUnitFacing(u) )
set u = null
call PauseTimer(ti)
call DestroyTimer(ti)
set ti = null
endfunction
function A takes nothing returns nothing
local timer ti = CreateTimer()
call SHH( ti, TheUnit, GetTriggerUnit() )
call TimerStart( ti, 10.0, false, function B )
set ti = null
endfunction[/codes]
我们可以改成:
[codes=jass]globals
timer DeathTimer = CreateTimer()
group DeathGroup = CreateGroup()
endglobals
function C takes nothing returns nothing
local unit u = GetEnumUnit()
call SetUnitUserData( GetTriggerUnit(), GetUnitUserData( u ) - 1 )
if GetUnitUserData( u ) <= 0 then
call CreateUnit( GetOwningPlayer(u), GetUnitTypeId(u), GetUnitX(u), GetUnitY(u), GetUnitFacing(u) )
call GroupRemoveUnit( DeathGroup, u )
call RemoveUnit( u )
endif
set u = null
endfunction
function B takes nothing returns nothing
call ForGroup( DeathGroup, function C )
endfunction
function Init takes nothing returns nothing
call TimerStart( DeathTimer, 1.0 ,true, function B )
endfunction
function A takes nothing returns nothing
call SetUnitUserData( GetTriggerUnit(), 10 )
call GroupAddUnit( DeathGroup, GetTriggerUnit() )
endfunction[/codes]
这样的话,在地图中死亡单位数量特别多的时候,这个算法会比刚刚的算法的效率要好很多。
2.尽量少用循环间隔小于0.01的循环性Timer
很简单,如题,避免使用这种Timer就可以,尽量把他的循环间隔调的大一点,比如:
[codes=jass]call TimerStart( ti, 0.02, true, function XXXXXX )[/codes]
明显比下面的效率高了2倍。
[codes=jass]call TimerStart( ti, 0.01, true, function XXXXXX )[/codes]
3.Timer不用时就暂停他
这其实是直觉的判断………………(我一直在怀疑魔兽关于时间控制很可能是共用一个0.001循环的timer,也就是不管创建出多少不执行函数的Timer,效率不会增加多少)我是觉得这会影响到效率。
废话少说,给出实例:
[codes=jass]globals
timer DeathTimer = CreateTimer()
group DeathGroup = CreateGroup()
endglobals
function C takes nothing returns nothing
local unit u = GetEnumUnit()
call SetUnitUserData( GetTriggerUnit(), GetUnitUserData( u ) - 1 )
if GetUnitUserData( u ) <= 0 then
call CreateUnit( GetOwningPlayer(u), GetUnitTypeId(u), GetUnitX(u), GetUnitY(u), GetUnitFacing(u) )
call GroupRemoveUnit( DeathGroup, u )
call RemoveUnit( u )
endif
set u = null
endfunction
function B takes nothing returns nothing
if ( FirstOfGroup(DeathGroup) == null ) then
call PauseTimer( DeathTimer )
return
endif
call ForGroup( DeathGroup, function C )
endfunction
function Init takes nothing returns nothing
call TimerStart( DeathTimer, 1.0 ,true, function B )
endfunction
function A takes nothing returns nothing
if ( FirstOfGroup(DeathGroup) == null ) then
call Init()
endif
call SetUnitUserData( GetTriggerUnit(), 10 )
call GroupAddUnit( DeathGroup, GetTriggerUnit() )
endfunction[/codes]
(就是上面的改进版本罢了)
(3) 触发器动作写条件里去:
如题,相信会Jass的,就知道这一点吧…………………………
(4) 抛弃GameCache的储存方式,使用全局变量数组/struct:
………………不得不说的,事实证明,GameCache储存数据的速度确实没有全局变量数组/struct快,所以说,快使用【全局变量数组/struct】呼呼哈咦!
(5) 特殊的函数替换:
很简单,比如:SetUnitPosition换成SetUnitX+SetUnitY…………………………效率会很高。
(6) 使用宏:
一个VJASS的特权:宏。
一般来说,一个你肯定不会超过一行的函数,都改成宏写,效率会有些许提升。
比如:
[codes=jass]function H2S takes handle h returns string
return I2S(H2I(h))
endfunction[/codes]
你就可以写成:
[codes=jass]//! define H2S(h) I2S(H2I(h))[/codes]
这主要是提高了一次调用函数的效率
好了……………………总算写完了………………万里长征总算结束了…………………
最后声明:可能会有错的地方,也可能有跟大家思想有冲突的地方。如果大家有异议,可以在下面回帖告诉我,我会尽量改正错误/与你进行深度探讨。
最后请求:…………如果可以的话…………把这贴设成精华吧…………要不然我就要泪奔+跳楼了&_& &_&
5,700多中文字啊…………………………还不算Jass呢&_& |
|