三国群英2吧 关注:45,920贴子:1,015,475

【脚本基础教程】第六章:异步与回调(武将技:毒箭)

只看楼主收藏回复

视频来自:百度贴吧


IP属地:美国1楼2020-03-22 14:22回复
    站着挨射,?


    IP属地:重庆来自Android客户端2楼2020-03-22 14:38
    收起回复
      2026-01-16 07:03:16
      广告
      不感兴趣
      开通SVIP免广告
      本章我们讨论异步调用(asynccall)和回调函数(Callback Procedure),同时介绍打击目标相关标记。
      异步与回调是组织三国2脚本执行的核心工具;由于武将技“多线并行”的天然特性,在执行复杂武将技时,异步与回调会使得武将技的编写变得简单很多。
      我们先在Magic点ini中设置好本章的武将技:
      [MAGIC]
      SEQUENCE =114
      NAME =毒箭
      MP =23
      POWER =100
      ATTACK =30
      SCRIPT=803
      ATTRIB =主將2
      TITLE =
      NOTE =主將射箭
      ACTIVE =敵方主將


      IP属地:美国本楼含有高级字体3楼2020-03-23 04:34
      回复
        一、异步调用
        所谓异步调用,指的是调用某个函数后,不等待该函数返回,继续往下执行的调用。在进行异步调用后,两个函数实际上是并行执行的。借用时序图的格式,下图说明了在执行异步调用后的运行过程:


        IP属地:美国本楼含有高级字体4楼2020-03-23 04:37
        回复
          在三国2脚本中,使用asynccall关键字指定以异步调用方式调用函数:
          asynccall 函数名(参数1, 参数2,...);
          和一般的函数调用不同,异步调用不会等待函数返回,目标函数的返回值没有效果;因此,异步调用必须是独立的语句,即使目标函数有返回值也不能将异步调用放在表达式的中间,不能将返回值赋给任何变量。例如,下面的做法任何情况下都会被编译器拒绝,无论func函数有没有返回值:
          int y = asynccall func();
          一个目标函数可以被多次异步调用。此时,它们具有不同的生命周期和局部变量,互不干扰。例如,上一章的前后伏兵,就通过循环多次异步调用了MyStepShow函数;虽然属于同一个函数,但每个异步调用操作一个单独的士兵,不会弄混。
          受底层限制,异步调用的目标函数,不能超过10个参数。
          下面的例子中,我们先生成了两个木轮攻的木轮,将它们错开了一下位置,一左一右。A函数异步调用了B函数,然后,等待50 Tick,将左边的木桶打上WHITELIGHT标记;B函数执行后,立即等待50 Tick,然后将右边的木桶打上WHITELIGHT标记。编译运行后会发现,两个木桶是同时变色的,说明A函数和B函数是并行执行的。



          IP属地:美国本楼含有高级字体5楼2020-03-23 04:52
          回复
            二、准备工作和设置物件速度
            让我们进入本章的正题。
            本章中我们使用一个“魔改”过的落月弓效果。读者可以在目录贴的资源文件里找到相应的素材,文件放在shape/magic/114/目录下。我们在Things点ini中,接在落月落日部分的下方,定义相应的武将技物件:
            [OBJECT]
            Name = 毒箭的光
            Sequence=13101
            Type = %TYPE_FORCE
            Space = 0,0,0,0
            Flags = OF_WHITELIGHT, OF_NOGRAVITY
            Process =%MagicObjectProcess
            Directory= \magic\114
            Wait = #9999, m003WaveG1
            [OBJECT]
            Name = 毒箭爆炸的光
            Sequence=13102
            Type = %TYPE_FORCE
            Space = 0,0,0,0
            Flags = OF_WHITELIGHT, OF_NOGRAVITY
            Process =%MagicObjectProcess
            Directory= \magic\114
            Wait = #4, M003GreenBallB1, M003GreenBallB2, M003GreenBallB3, M003GreenBallB4, M003GreenBallB5, M003GreenBallB6, M003GreenBallB6, @%OM_REMOVE, SCROVER
            我们还会用到一个现有物件:
            [OBJECT]
            Name = 飛行武器箭(M012)
            Sequence=13001
            Type = %TYPE_FORCE
            Space = 0,0,0,0
            Flags = OF_NOGRAVITY
            Process =%MagicObjectProcess
            Directory= \magic\003
            Wait =#9999, m003WA1


            IP属地:美国本楼含有高级字体6楼2020-03-23 04:56
            回复
              在射出“毒箭”武将技时,实际上会射出两个物件:一个是主要的物件,也即已有的13001号物件;另一个是随着主物件伴飞的光芒物件,即新定义的13101号物件。
              如果根据之前的思路,读者很自然地会想到利用循环移动箭支的方式,让箭支射向敌人。不过,本章中,为了介绍回调的相关内容,我们换一个思路,使用专门的系统函数来设置物件的速度。
              (武将技基本流程部分我们予以略过。)
              首先,完成基本流程后,我们根据主将的位置创建箭支,箭支的方向与主将面朝的方向相同:
              int dir = GetObjectDir(intvAttackerMajor);
              int arrow = CreateObjectByReference(intvAttackerMajor, 13001, dir, 0);
              接着,由于无法在CreateObjectByReference中顺便指定x/y轴方向上的偏移,我们使用SetCoordinateByReference_Cylind系统函数,将箭支向主将面朝着的dir方向(我方放武将技时,向左方)移动80像素,并同时向上方也移动80像素。
              SetCoordinateByReference_Cylind (arrow, intvAttackerMajor, dir, 80, 80);
              将箭支的初始位置确定好后,我们直接以箭支的位置为参考创建光芒:
              int light = CreateObjectByReference(arrow, 13101,dir, 0);
              最后,和SetCoordinateByReference函数类似,我们使用SetObjectSpeed_Cylind函数,设置物件在自身方向上的速度(之前创建物件时已经设置好了物件的方向和主将面朝方向相同),将箭支和箭支的光芒抛出:
              SetObjectSpeed_Cylind(arrow, 16, 0);
              SetObjectSpeed_Cylind(light, 16, 0);


              IP属地:美国本楼含有高级字体7楼2020-03-23 05:00
              回复
                在上面的代码中,我们用到了两个带_Cylind后缀的系统函数。首先,我们在创建箭支后,利用SetCoordinateByReference_Cylind系统函数,以主将为参考点、根据主将面朝着的方向,调整了箭支的位置。该函数的定义如下:
                void SetCoordinateByReference_Cylind (int object, int referenceObject, int dir, int radius, int zOffset);
                这个函数的含义是,将object物件移动到以referenceObject为参考点,dir方向上外移radius像素,并紧接着在z轴方向上(向正上方)移动zOffset像素的位置。具体可以参考下图,原点处为referenceObject的位置,而绿点处则是object物件将要被移动到的位置。

                其次,在创建了光芒之后,我们用SetObjectSpeed_Cylind函数分别设置了箭支和光芒的速度。该系统函数的定义如下:
                void SetObjectSpeed_Cylind (int object, intdirSpeed, int zSpeed);
                其中,第一个参数是被设置速度的物件,第二个参数是物件在(已经设置好的)自身方向上的速度,第三个参数是物件在z轴方向上的速度。在这里,由于两个物件在CreateObjectByReference时,都已经指定好了方向(与主将的朝向相同),因此直接设置速度为16像素每Tick即可。
                如需手动设置方向,可以使用SetObjectDir函数。它和GetObjectDir为一对:
                int GetObjectDir (int object);
                void SetObjectDir (int object, int dir);
                注意到设置物件速度时,SetObjectSpeed_Cylind函数的逻辑和SetCoordinateByReference_Cylind非常相似:它们都是先在平面上指定一个方向,然后设置该方向上的位移量或速度,最后在z轴上设置位移量或速度。事实上,这一套逻辑是柱坐标系的逻辑:这是该系统函数的名字中_Cylind后缀的由来。


                IP属地:美国8楼2020-03-23 05:04
                回复
                  2026-01-16 06:57:16
                  广告
                  不感兴趣
                  开通SVIP免广告
                  目前为止的代码如下。读者会注意到,一开始将镜头对准武将时,有一个180像素的偏移。这一设计和落日/落月是相同的。


                  IP属地:美国9楼2020-03-23 05:09
                  回复
                    编译运行。可以看到,箭支和光芒随着武将的射箭动作而射出,并且以稳定的速度向左飞行。


                    IP属地:美国10楼2020-03-23 05:16
                    回复
                      三、通过异步调用锁定镜头
                      下一个问题是,在箭支飞行过程中,我们希望镜头能一直锁定箭支的位置。
                      奥汀在magic点cpp中提供了一系列将镜头锁定指定物件的函数。不过,这些函数写得多少有一些……匪夷所思。为了演示异步调用,我们按如下方式,自己写一个将镜头锁定物件的函数——

                      这个函数的逻辑稍微比直接将摄像机锁在物件上多了三步。首先,它多了一个参数yOffset,意在说明欲锁定的摄像机目标点不是物件的 (x, y),而是 (x, y + yOffset);回忆一下上一章中我们提到,想要将摄像机对准物件,摄像机的y坐标应该是物件的y坐标减去120左右,因此yOffset我们应当填入-120。
                      其次,我们希望如果物件正在飞向画面中心,则不急着移动摄像机,而是等一会儿,等待物件飞到画面中心再开始锁定。因此,我们判断物件的方向:如果物件朝右飞(dir == 0)且物件在摄像机右侧(dx > 0),或者物件朝左飞(dir == 128)且物件在摄像机左侧(dx < 0),则调整摄像机位置,否则什么都不做,让摄像机等待物件飞过来。
                      最后,为了防止出现弓箭射失,导致类似于五岳华斩卡死的现象,我们加了一个条件,如果超出战场范围则自动退出循环,解除锁定。


                      IP属地:美国本楼含有高级字体11楼2020-03-23 05:23
                      回复
                        完成这个函数后,接下来要做的,就是在主函数中异步调用这个函数:

                        编译运行。可以看到,镜头会自动锁定箭支,并在飞出底线后解除锁定;但是天空会在飞到一半时就变白,说明主函数在asynccall后仍然继续执行。


                        IP属地:美国本楼含有高级字体12楼2020-03-23 05:43
                        收起回复
                          借用时序图的格式,下图显示了本节异步调用的示意过程。其中,橙色方块部分表示代码执行,从上到下表示时间的先后顺序。


                          IP属地:美国13楼2020-03-23 05:47
                          回复
                            四、打击目标标记
                            在第四章中,我们手动使用KillForce函数和DoHarmToMajor函数对敌方士兵和主将进行杀伤。事实上,在群2武将技中这是比较少见的做法;更多情况下是,我们将物件指定杀伤的目标,当物件碰到(刮到)敌方士兵或主将时,由EXE来判定进行杀伤。
                            回顾上一章中我们提到的标记(Flags)。我们特意提到了这样一类标记,即打击目标标记。打击目标标记的作用是:告诉EXE,当物件坐标与战场上哪一类单位重合时(即,刮到哪一类单位时),对该单位造成杀伤——
                            OF_ENEMYGENERAL = 0x01000000 ;對敵方的武將有效
                            OF_ENEMYFORCE = 0x02000000 ;對敵方的小兵有效
                            OF_MYGENERAL =0x04000000 ;對我方的武將有效
                            OF_MYFORCE = 0x08000000 ;對我方的小兵有效
                            OF_ATTACKENEMY = 0x03000000 ;只對敵方有效
                            OF_ATTACKMY = 0x0c000000 ;只對我方有效
                            OF_ATTACKALL =0x0F000000 ;對敵我雙方都有效
                            实际上,打击目标标记只有4个:OF_ENEMYGENERAL、OF_ENEMYFORCE、OF_MYGENERAL、OF_MYFORCE。其它的标记只是这4个标记的叠加(相加)。例如,OF_ATTACKENEMY实际上是OF_ENEMYGENERAL和OF_ENEMYFORCE的叠加, OF_ATTACKMY则是OF_MYGENERAL和OF_MYFORCE的叠加;OF_ATTACKALL则是所有4个标记的叠加。
                            在这些标记中,主将标记和士兵标记又有所不同。如果打上了杀伤士兵的标记,意味着该物件只要擦到敌方(或我方、敌我)士兵,就会直接导致士兵死亡;但是如果打上了杀伤主将的标记,并不会自动对武将造成伤害。对主将造成伤害需要经过回调函数进行。
                            设置标记的方法和第五章中一样:要么,我们在Things点ini中直接设置指定的武将技物件的标记,要么,我们使用代码进行设置。由于我们射出的箭支物件是和落月弓共用的,因此,我们采取代码设置的办法,避免影响落月弓武将技的逻辑。出于演示的目的,我们暂时将打击目标标记设置为OF_ATTACKENEMY,以展示“擦到即死”的效果。我们将以下代码放在主函数中,asynccall所在行的后面一行:

                            不过,仅仅是这样还不够——因为箭支太高了,会从士兵的头上飞过去,这样是打不到士兵的。因此我们临时稍作调整,把之前调整箭支位置时的z轴偏移从80改为0:


                            IP属地:美国本楼含有高级字体14楼2020-03-23 05:51
                            回复
                              2026-01-16 06:51:16
                              广告
                              不感兴趣
                              开通SVIP免广告
                              编译运行。可以看到,小兵“擦到即死”,但主将不会受到任何伤害。


                              IP属地:美国15楼2020-03-23 05:55
                              回复