找回密码
 点一下
查看: 4040|回复: 7

【手札】用纯Jass编写Skill

[复制链接]
发表于 2008-1-21 12:26:17 | 显示全部楼层 |阅读模式
首先这不是一篇教程,只是我在学习Jass中的一点经验以及这两天制作Skill之中得到的些许收获。现在拿来和Jasser们分享。

接触Jass时间并不长,所以我先认为的技能包含两部分

1•效果:也就是我们在地图上能看到的任何动画也好,特效也好。

2•伤害:该技能对Unit造成的影响,说伤害也不是很确切,因为可能是对己方单位的加血呢。不过大概就是那个意思。

我们现在分别来看待这两部分,先从效果说起。 效果无非是 连续执行 单一绝对时间内对某个点产生特效,说个有点绕因为我语文不好,想不出怎么能一句话概括出来这个意思。呵呵

然我来举个例子来说,比如我们现在要对一条直线上产生一连串的Noval效果。

如图

直线上放Noval

直线上放Noval



产生这样的效果也就是我不断的在某点(x,y)上制造Noval特效,然后把坐标该点坐标改成(x,y+60),由于时间很短所以看起来是在一串上制造Noval(*),由此我们确定下一步要想的小问题

•直线有多长,因为我们不能总执行更改坐标点(x,y+60)吧,这样会让Noval一直放到地图外面去的
•第一个施放Noval的地点在那里,也就是最开始的第一个Noval在那里,换句话说就是我们的特效从那里开始演示

我们需要更改坐标的次数可以用Loop循环控制,让其循环几次我们也就产生了几个Noval,从而也就确定了Noval-Skill的长度了。
技能从那里开始施放,我们只要确定一个参考点即可。因为这个技能是对地施放的,我们只要得到施放点的坐标即可搞定参考点,搞定参考点也就很好确定我们施放特效的起点了。
这里就要用到两个API GetSpellTargetLoc() 这个API返回的是施放技能的点,GetLocationX(which Loc)。GetLocationX是返回一个点所对应的X坐标,其中which Loc处填的自然就是需要判定的那个点了。
我们把两个API联立起来很容易就得到
local real x = GetLocationX(GetSpellTargetLoc())
local real y = GetLocationY(GetSpellTargetLoc())
有了参考坐标点,我们自然也就很容易的套用Loop循环让其施放一连串的Noval。于是我们就可以写到

Loop
Exitwhen N==0
set eff = AddSpecialEffect("Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl",x,y)
set y=y+30
set N=N-1
call DestroyEffect(eff)
set eff=null
endloop

loop—endoop 之间是循环执行的代码,退出循环的条件是满足Exitwhen N==0。我们在这段代码中不断的对某个点x,y(也就是AddSpecialEffect的第二和第三个参数)出产生Noval(第一个参数)效果
并且不断的更新那个点坐标。

开始我最简单的想法是这样的,不过忽略了一些问题。这样产生的Noval是Loop一连串的执行下来产生的中间没有停息速度很快,转眼间就执行完了。而我希望的是执行的时候有些停顿,看起来有点时间感。

所以这里我们就需要重新写这些代码了,并且要考虑更多的东西。当然也就把问题一步步复杂化了,首先我们要碰到的问题是Timer当然用的很浅啦。
两条语句,
首先我们要产生一个Timer local timer tm=CreateTimer()
让Timer开始工作起来 call TimerStart(whichTimer, timeout, periodic, handlerFunc)
让我们来着重看一下第二行语句,让我们调用TimerStart的时候他就会问我们 开始哪个Timer(whichTimer),多少时间执行一次(timeout),是总共执行一次还是一直执行下去(periodic),执行哪个函数(handlerFunc))
很容易的我们会填完前三个变量
local timer tm=CreateTimer()
call TimerStart(tm,0.1,true,function XXXXXX)
哪个函数呢?这时候呢 我们首先要对前面的代码进行一些修改,并且装入一个函数之内。就叫Effect。而执行Timer那个函数我们叫做Main函数。

function Effect takes nothing returns nothing
if N>0 then
set eff = AddSpecialEffect("Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl",x,y)
set y=y+30
set N=N-1
call DestroyEffect(eff)
set eff=null
else
call DestroyTimer(tm)
endfunction
首先我们要去掉Loop而改用if,因为每个特效是分开执行的,而循环是因为Timer按一定的时间间隔不断的调用function Effect从而产生的,那什么时候终止呢,也就是让我们的N不断减小

当不大于零的零时我们去call DestroyTimer(tm)

问题一下就出来了,参数没法传递。当TimerStart不断调用function Effect的时候,function Effect中的点的坐标变化,以及控制执行次数的N也是没有方法变化的。这种情况局部变量是无法解决了,

于是我们首先想到了全局变量,产生几个全局变量记录不久可以么,但我这里不准备采用全局变量而采用GameCahe。因为对这个概念理解不是很深,我只能说说我对GameCahe的浅薄理解。

我认为GameCahe是用来做函数与函数之间的数据交换的,用全局变量不好的地方我认为就好比我要我和老婆说点私事却把要说的事情写在了公用题板上面(全局变量),任何人都可以看见,这显然不好么。而我老婆
就不在我身边,我没法和他直接说话交流啊(局部变量)。于是乎呢,我给他写信也好发短信也好(GameCahe)。而我更习惯的是专业一点的类比,GameCahe就好比面向对象(C++/Java)里面里面的私有类变量。
对整个类里面的成员函数是可见的,而对类外是不可见。这样封装性好。
用GameCahe,首先我们要建立一个GameCahe
如图

建立GameCahe

建立GameCahe



建立好了以后我们就需要涉及到存储以及调出两个部分了。
存储GameCahe StoredXXXX(gamecache, missionKey , key, value) 其中XXXX部分是你要填写的什么类型 包括Real ,Integer等等
ganmecache部分就是你要存储进入的哪个Gamecache. 这里就是我们建立的udg_GC(因为是全局变量,所以前面加入udg_)
missionKey 就好比我们吧Gamecahe分成了许多页,我们要把东西存到第几页呢?那个页码就是missionKey
Key 就是我们又把分好页的GameCahe分了行,用Key确定是放在第几行的
Value就是我们要存进去的数值了。

读取GameCahe GetStoredXXXX(gamecache, missionKey , key)这个就不难理解了吧

现在回忆一下我们的初衷,我们是因为不想使用全局变量来传递参数,所以使用了GameCahe,需要传递参数的是function Effect 他需要记住自己不断变化的N以及x,y的坐标。
我们先放放这里,掉过头来考虑之前淡化了的概念。
触发器是怎么工作的呢? 我们使用了一个技能---判断这个技能是不是我们所设计的技能(NovalSkill)---不是 什么都不做
----是 执行我们要执行的代码。

这里是,执行我们要执行的代码,所执行的入口点是InitTrig_XXXX xxxx为我们为该触发器起的名字,我起的名字交Noval,所以他会自动生成三个函数
function InitTrig_Noval takes nothing returns nothing
endfunction

function Trig_Noval_Actions takes nothing returns nothing
endfunction

function Trig_Noval_Conditions takes nothing returns boolean
if ( not ( GetSpellAbilityId() == 'A000' ) ) then
return false
endif
return true
endfunction

而我们需要执行Timer的Main函数其实就是Trig_Noval_Actions这个函数,并且之前我们进行获取x y坐标的那个API也在这里面才有用(好像是,我没有尝试放在别的地方-。-)
综合我之前说的GameCahe ,Timer 等等 我们写出真正的代码(可以加高亮了 呵呵)
[codes=jass]function Trig_Noval_Conditions takes nothing returns boolean
if ( not ( GetSpellAbilityId() == 'A000' ) ) then
return false
endif
return true
endfunction
//==============================================================
//判断施放的技能的正确性

function Effect takes nothing returns nothing
local timer tm=GetExpiredTimer()
local integer N=GetStoredInteger(udg_GC,"Var", "N")
local real x=GetStoredReal(udg_GC,"Var", "LocX")
local real y=GetStoredReal(udg_GC,"Var", "LocY")
local effect eff
if N>0 then
set eff = AddSpecialEffect("Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl",x,y)
set x=x+30
set y=y+30
set N=N-1
//插入Attack(伤害函数放在这里)
call StoreInteger(udg_GC,"Var", "N",N)
call StoreReal(udg_GC,"Var", "LocX", x)
call StoreReal(udg_GC,"Var", "LocY", y)
call DestroyEffect(eff)
else
//插入Attack 后写这句话,是因为当运行到else的时候表示 整个技能已经施放完毕了 call GroupClear(udg_Group)
call DestroyTimer(tm)
endif
set tm=null
endfunction
//==============================================================================
//设置特效


function Trig_Noval_Actions takes nothing returns nothing
local timer tm=CreateTimer()
local real x = GetLocationX(GetSpellTargetLoc())
local real y = GetLocationY(GetSpellTargetLoc())
local real ty = y
local real tx = x
call StoreInteger(udg_GC,"Var", "N", 10)
call StoreInteger(udg_GC,"Var", "GroupMax",0)
call StoreReal(udg_GC,"Var", "LocX", x)
call StoreReal(udg_GC,"Var", "LocY", y)
call StoreInteger(udg_GC,"Var", "Unit", H2I(GetTriggerUnit()))
call TimerStart(tm,0.1,true,function Effect)

set tm=null
endfunction
//===========================================================================
//技能初始化后执行的主程序


function InitTrig_Noval takes nothing returns nothing
local trigger trg = CreateTrigger( )
call TriggerRegisterUnitEvent( trg, gg_unit_Hpal_0000, EVENT_UNIT_SPELL_CAST )
call TriggerAddCondition( trg , Condition( function Trig_Noval_Conditions ) )
call TriggerAddAction(trg,function Trig_Noval_Actions )
set trg = null
endfunction[/codes]

用这几个函数就可以在地图上产生你喜欢的特效了。我们的工作已经完成了80%,剩下的只需要对上面的代码进行修改,让放特效的同时能够产生伤害。

产生伤害肯定是对Unit产生的伤害,所以我们要获取到所有被技能影响到的Unit,并且对其进行伤害。

我用到的API是GroupEnumUnitsInRange(whichGroup , x , y , r , filter)他的本意是把以某一个点(x,y)为中心,半径为r的区域内的所有单位 添加到Group(whichGroup)当中
其中filter是一个判断函数,返回TURE则代表符合---添加 返回FALSE则表示不符合----不添加。 我的做法是直接把伤害放到filter里面,对那些符合的Unit直接给予伤害
而对于到底是不是放在Group里面我并不关心,所以每次判断完了 我就清空该Group

其函数表达如下:
[codes=jass] function Damage takes nothing returns boolean
if(IsUnitEnemy(GetFilterUnit(),Player(0))and not(IsUnitInGroup(GetFilterUnit(),udg_Group))) then
call UnitDamageTarget( GetFilterUnit(), GetFilterUnit(), 300, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS )
call GroupAddUnit(udg_Group,GetFilterUnit())
return TRUE
endif
return FALSE
endfunction
//==========================================================
//选取符合需要进行伤害的单位(在所选单位组内)

function DamageAttcak takes real x,real y returns nothing
local real r=100.00
local group gp = CreateGroup()
local boolexpr cond = Condition(function Damage)
call GroupEnumUnitsInRange(gp,x,y,r,cond)
call DestroyGroup(gp)
endfunction
//==========================================================
//对所释放的技能进行伤害判定[/codes]
实际造成伤害的地方是在Damage函数之中,那我们要对那些Unit造成伤害呢? 我们要对受到技能影响的单位,且没有被造成过伤害的单位。没有被造成过伤害的是因为我们的判定是随着
坐标的不断改变变化的(后面会解释),所以可能两次或者多次对同一个Unit造成伤害。因此我们要保证这个Unit是没有被造成伤害过的Unit
IsUnitEnemy( GetFilterUnit(), Player(0) )保证那个Unit一定是Player(0)的敌人 (这里我就简单的使用了Player(0),当然也可以写成释放技能的玩家)
not( IsUnitInGroup ( GetFilterUnit(),udg_Group ) )保证的那个单位一定不在udg_Group 这里我用了一个全局变量udg_Group,因为整个Group我还不知道怎么传递。
如果判断条件满足了,就给该单位造成伤害,并且把该单位放在udg_Group里面,放置被二次伤害。
而伤害怎么计算 使用的是UnitDamageTarget 它的使用参照T里面对应的函数就不难理解了如图:

UnitDamageTarget

UnitDamageTarget


我们现在只要在上面 //插入Attack(伤害函数放在这里)那个位置插入call DamageAttcak(x,y) 并且在else那里写上call GroupClear(udg_Group)就一起Ok了

后记

我是一个初学者,不敢说写什么教程。只是这两天一直在弄这个Skill,并且论坛上面关于使用Jass制作技能的相关资料又为0 所以我想把这篇文章献上和大家共勉。
技能试验了几次没有出现什么问题。不过我觉得还不是最好的写技能的方式,希望看到这篇文章的高手能告诉我一下更好的方式,从而我好进一步的修改
当然在其中我也有些不懂的地方,比如GetFilterUnit(),我就不是很理解,觉得这个概念很虚幻,不知道在什么地方用对应的还有 选取的单位GetFilterUnit()是匹配的单位
技能中即使很注意还是会有一些泄露,比如多人使用同名GameCahe等等,希望大家能够一起帮我修改修改。

在此再一次的要感谢zhuzeitou 这几天对我回答的问题一一解答,最后还帮我修改了一些小问题。也要感谢Goblin Academy 给了我这个交流的平台。

转载请注明出处 xx


Noval_Skill.w3x (22 KB, 下载次数: 54)
261001126 该用户已被删除
发表于 2008-1-21 14:39:28 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复

使用道具 举报

发表于 2008-1-21 15:19:30 | 显示全部楼层
呵呵,刚才试了下,发现技能还有些bug:
1、共用Group单位组,导致在一次技能释放完之前释放第二次,同一单位只会受到1次伤害
2、伤害来源是受伤害单位本身,导致无法获得经验
还有就是一些泄漏问题,其实这里没有必要使用局部变量触发

所以做了一些修改,正在校验,稍后传上,不过总觉得改得不好,不知道多人会不会有问题………………

Noval_Skill改.w3x (23 KB, 下载次数: 42)
回复

使用道具 举报

 楼主| 发表于 2008-1-21 17:52:37 | 显示全部楼层
非常感谢在此帮我改Skill,你该地方我看。还是有些地方不是很明白
你是在Effect之中又生成了一个Timer
call TimerStart(tm2,0,false,function DamageAttack)
然后用这个Timer来计算Damage吧,那既然这个False且不循环执行,和直接Call DamageAttack有什么区别么?是不是我直接Call会导致泄露啊。。。
还有就是储存变量的时候使用的是 类似于 call StoreReal(udg_GC,I2S(H2I(tm)), "LocY", y)其中
用到I2S(H2I(tm)),我想知道这个是为什么,还有就是 用 local timer tm = GetExpiredTimer()
去得到的是什么东西。
我的理解是GetExpiredTimer()去得到的是Timer循环执行的时候,每次执行的timer号,然后用I2S(H2I(tm))将其转化后,放入GameCahe之中,这样就可以产生多个LocX LocY 之类的东西,每个Loc也不冲突,那也就是说Timer的执行是相当于进程的执行的那样(希望不会也有死锁问题-。-),每次创建一个Timer就相当于创建了一个进程。是这个意思么?

还有就是 I2TM I2G 这些函数我本地好像没有啊,我用JassShop也查不到,可是WE就认为可以运行,那张地图里面也没有编写好的相关ReturnBug啊。 是不是我的WE有些问题?

万分的感谢 呵呵
回复

使用道具 举报

发表于 2008-1-21 17:57:23 | 显示全部楼层
引用第3楼zealotwang于2008-01-21 17:52发表的  :
非常感谢在此帮我改Skill,你该地方我看。还是有些地方不是很明白
你是在Effect之中又生成了一个Timer
call TimerStart(tm2,0,false,function DamageAttack)
然后用这个Timer来计算Damage吧,那既然这个False且不循环执行,和直接Call DamageAttack有什么区别么?是不是我直接Call会导致泄露啊。。。

这个么………………我是在这里通过把原来的timer绑定在这个上面传递到后面的函数中去的………………
引用第3楼zealotwang于2008-01-21 17:52发表的  :
还有就是储存变量的时候使用的是 类似于 call StoreReal(udg_GC,I2S(H2I(tm)), "LocY", y)其中
用到I2S(H2I(tm)),我想知道这个是为什么,还有就是 用 local timer tm = GetExpiredTimer()
去得到的是什么东西。
我的理解是GetExpiredTimer()去得到的是Timer循环执行的时候,每次执行的timer号,然后用I2S(H2I(tm))将其转化后,放入GameCahe之中,这样就可以产生多个LocX LocY 之类的东西,每个Loc也不冲突,那也就是说Timer的执行是相当于进程的执行的那样(希望不会也有死锁问题-。-),每次创建一个Timer就相当于创建了一个进程。是这个意思么?

这个和你说的差不多,同时存在的每一个timer的handle值都是不一样的,通过这样可以互不干扰的存储需要的数据
引用第3楼zealotwang于2008-01-21 17:52发表的  :
还有就是 I2TM I2G 这些函数我本地好像没有啊,我用JassShop也查不到,可是WE就认为可以运行,那张地图里面也没有编写好的相关ReturnBug啊。 是不是我的WE有些问题?

这个我写在了自定义脚本下面了………………
回复

使用道具 举报

 楼主| 发表于 2008-1-21 18:13:11 | 显示全部楼层
谢了 呵呵 突然觉得WE有点深了 连万恶的操作系统的知识都用上了。。。。。。。
我自己想了想Timer的工作原理应该就是类似与进程一样了。
那现在可能出现Bug的问题是不是就该剩下那个udg_Group和udg_GC了?
还有什么地方你觉得可能出现泄露的呢?
回复

使用道具 举报

发表于 2008-1-21 18:28:32 | 显示全部楼层
在我改过的那个里面,caster和group这两个全局变量应该都是没什么问题了,只要没有另外的技能去调用
泄漏么要看下,很多局部变量在使用后要设置为null,还有一部分要及时清除的,否则会造成内存泄露的
回复

使用道具 举报

发表于 2011-4-8 11:30:27 | 显示全部楼层
  [s:166]  [s:166]    纯新手  有点迷茫。
回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-5-3 23:29 , Processed in 0.177295 second(s), 22 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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