三国群英2吧 关注:45,931贴子:1,015,567

【脚本基础教程】第五章:摄像机与标记(武将技:前后伏兵)

只看楼主收藏回复

视频来自:百度贴吧


IP属地:美国1楼2020-03-20 12:12回复
    (因为标题字数限制被迫把章名改了……)
    本章介绍摄像机相关内容、生成士兵的方法,以及标记(Flags)的相关内容。本章主要介绍显示相关的标记。一部分的内容已在之前的章节中介绍过,另一些内容则可能会让读者想起多年前的Things点ini心得。
    惯例,我们先设置好本章的Magic点ini配置:
    [MAGIC]
    SEQUENCE =113
    NAME =前後伏兵
    MP =45
    POWER =100
    ATTACK =25
    SCRIPT =802
    ATTRIB =補兵
    TITLE =
    NOTE =
    ACTIVE =我方士兵


    IP属地:美国2楼2020-03-20 12:41
    回复
      2026-01-19 01:14:02
      广告
      不感兴趣
      开通SVIP免广告
      一、摄像机
      在上一章介绍武将技的标准流程时,我们使用SetViewCamera函数让摄像机对准放技能的武将:
      SetViewCamera(x, (y - 120));
      摄像机在武将技中扮演重要的作用。对于单一武将技,摄像机随着武将技的杀伤目标而移动;对于像鬼哭神号这样的组合武将技,摄像机会跟随多个同时进行的杀伤中的一部分,而放任其他部分自行进行杀伤。例如,鬼哭的摄像机先对准主将,待分身斩发出之后,依此对准三组(每组5个)太极门的杀伤;天地无用的摄像机则先对准主将,接着,对准黄龙天翔的黄龙砸下的位置,最后对准对方武将,同时放出(杀伤缩水的)狂雷天牢。
      在群2中,摄像机的角度和高度是不可变的(除非使用DALL进行视角调整)。当用SetViewCamera设置摄像机坐标时,设置的坐标是摄像机底部中点的坐标。例如:
      SetViewCamera(x, y);
      将会得到如下结果:

      注意到主将的坐标位置处于两马腿的中间阴影处。当设置主将的坐标为(x, y)时,实际上是把这个点和战场上的(x, y)位置对齐:

      因此可以看出,当摄像机的坐标被设置为(x, y)时,指的是把红点位置对齐战场上的(x, y)位置:

      为了使得我们感兴趣的物件被摆在摄像机的大致中央位置,我们应当将y坐标减去120像素左右(在magic点so中通常减去120像素,在system点so中通常减去128像素):
      SetViewCamera(x, y - 120);


      IP属地:美国本楼含有高级字体3楼2020-03-20 12:47
      回复
        二、平移摄像机
        为了使得摄像机被“平移”到目标位置(而非一步到位的“切镜”),我们可以调用位于magic点cpp中的工具函数MoveCamera。它的定义如下:
        void MoveCamera(int x, int y, int duration);
        其中,前两个参数指定了欲指向位置的x/y坐标,第三个参数则指定了摄像机经过多少Tick移动到目标位置。
        (感兴趣的读者可以查看magic_structured点cpp中的MoveCamera函数。简单地说,它是通过和上一章中我们放大、淡出光气环类似的方式实现的:使用一个循环,每个循环移动一次摄像机,然后Delay(1)。)
        例如下面的代码(顺便,我们把武将技的基本框架搭起来):

        (一个细节是,我们将武将技在Magic点ini中指定的伤害值(由预设变量intvMagicAttackValue获取)作为武将技主函数MoreSoldierFrontBack的参数。这样,我们可以直接在Magic点ini中指定伏兵单侧招兵的人数。本章的开头部分中,我们编写Magic点ini时,将ATTACK指定为25,这指定我们制作的武将技将在两侧各招25兵。)
        编译运行。可以看到天空变黑之后,摄像机是一路“滑”到主将面前的。


        IP属地:美国本楼含有高级字体4楼2020-03-20 12:56
        回复
          三、其他摄像机函数
          三国2脚本同样提供了根据物件位置移动摄像机的系统函数。不知道为什么,奥汀官方从未用过这个函数。使用该函数,会使得摄像机对准(参考物件x坐标 + xOffset, 参考物件y坐标 + yOffset) 坐标位置。
          void SetCameraByReference (int objectHandle, int xOffset,int yOffset);
          因此我们完全可以把:
          int x = GetObjectScreenX(intvAttackerMajor);
          int y = GetObjectScreenY(intvAttackerMajor);
          SetViewCamera(x, y - 120); // 摄像机对准武将
          简化为效果完全一样的:
          SetCameraByReference(intvAttackerMajor, 0, -120);
          其次,我们可以用GetCameraX和GetCameraY两个函数获取当前摄像机的位置:
          int GetCameraX();
          int GetCameraY();


          IP属地:美国本楼含有高级字体5楼2020-03-20 13:03
          回复
            四、在战场上生成士兵
            为了实现伏兵效果,我们需要在战场上指定位置召唤出士兵。三国2脚本提供了在指定战场坐标处召唤出士兵的函数:
            int CreateSoldier (int isLeft, int battleX, int battleY);
            其中第一个参数表示召唤红方(1)还是蓝方(0)士兵,后两个参数指定召唤士兵的位置。该函数返回生成的士兵物件。默认情况下,该函数生成的是低级兵,兵种和指定的红方或蓝方兵种一致(注)。如果指定位置已经被主将或士兵占据,则该函数会返回0.
            (注:如果您使用1.05+,或者您的EXE已打上1.05+修改内容说明里提供的脚本支持补丁,则您可以通过将battleX加上0x10000的方式,指定召唤出高级兵。在本章中,我们暂时不召唤高级兵。此外,如果您需要指定士兵的种类,您需要更改相关武将的兵种属性,召唤伏兵后再改回来;相关操作只能通过内存操作实现。)


            IP属地:美国本楼含有高级字体7楼2020-03-20 13:12
            回复
              您可以将伏兵召唤在任何位置,甚至主将身边都没问题。不过,既然我们实现的是“前后伏兵”,那当然就要召唤在双方的底线位置了。
              在群2中,伏兵总是大致呈中心对称分布,且先填满后排再填前排;一排填12人算满,最上和最下两格是空出来的。例如,下图显示了伏兵班阵25人的分布:


              IP属地:美国8楼2020-03-20 13:16
              回复
                我们也可以照此操作。一个直截了当的方式是直接在两边底线处召唤出士兵。我们可以直接写出下面的代码,指定在左方召唤士兵:

                其中,第一段双重 for 循环用来生成满编12人的若干排士兵。我们先计算出给定的兵数 soldierCount 可以填满多少排12人,将这个值储存在 lines 变量中;然后,遍历战场最左侧战场X坐标从0到 lines - 1,战场Y坐标从1到12(空出了最底下的坐标为0和最上方的坐标为13的位置)的所有格子,逐一在这些位置生成士兵。
                第二段for循环用来生成剩余的士兵。生成士兵的战场X坐标为 lines,即上一部分最右侧的 lines - 1 的一排向右一格的位置。第二段 for 循环中指定战场Y坐标为 7 - soldiersLeft / 2 + j ,是为了让这部分士兵尽量呈中心对称分布:战场Y坐标为7的位置,是战场中线上方最接近中线的位置(战场宽度为14格,坐标为0-13);7 - soldiersLeft / 2 得到的是这一排士兵最下方的位置,然后再加上 j ,即可得到第 j 个士兵的位置。例如,最后一排剩下2个士兵时,计算得到的Y坐标为6和7,正好是战场中间的两个坐标。


                IP属地:美国9楼2020-03-20 13:21
                回复
                  2026-01-19 01:08:02
                  广告
                  不感兴趣
                  开通SVIP免广告
                  类似地,我们照葫芦画瓢,写出召唤右方士兵的代码:

                  注意到在GetBattleWidth()之后还要减1,是因为战场坐标的范围是0到 GetBattleWidth() - 1,而不是1到GetBattleWidth()。除此之外没有太大差别。


                  IP属地:美国10楼2020-03-20 13:25
                  回复
                    有了这两个函数之后,剩下的问题就只剩两个了:移动摄像机,以及先从哪边召唤士兵。
                    我们设想该武将技总是先召唤前伏兵。因此,当intvIsLeft为左方时,先往左边移动摄像机,召唤左伏兵,然后再往右边移动摄像机,召唤右伏兵;当intvIsLeft为右方时正好相反。于是我们得到:

                    注意操作摄像机需用屏幕坐标,因此,当使用MoveCamera将摄像机向右边底线平移时,应当用GetBattleWidthInScreenX()而不是GetBattleWidth().


                    IP属地:美国11楼2020-03-20 13:28
                    回复
                      最后,我们在CreateSoldierAtLeft和CreateSoldierAtRight两个函数的开头加上声音。原版伏兵的音效由两部分组成,分别是m002snd01的“魔法效果”音效和m002snd03的士兵叫喊声。
                      PlaySound1("m002snd01", 255);
                      PlaySound1("m002snd03", 255);
                      整个武将技的代码如下:


                      IP属地:美国12楼2020-03-20 13:34
                      回复
                        编译运行——


                        IP属地:美国13楼2020-03-20 13:37
                        回复
                          五、标记
                          对于群2物件而言,标记是一类特殊的属性。它指示了EXE在处理这个物件时,需要进行哪些特殊的处理。在Things点ini的开头,可以看到所有23个标记:
                          OF_MAN =0x00000001
                          OF_MISSILE =0x00000002
                          OF_BACKGROUND = 0x00000004
                          OF_CAVALRY = 0x00000008
                          OF_BLOCKMAN =0x00000010
                          OF_BLOCKMISSILE =0x00000020
                          OF_BIGSHAPE = 0x00000040 ;特殊放大的物件
                          OF_TRIGGERMAN =0x00000100
                          OF_TRIGGERMISSILE =0x00000200
                          OF_NOGRAVITY =0x00010000 ;沒有重力
                          OF_NONSYNCHRONISM = 0x00020000 ;非同步,當物件產生時會使物件的圖不同步
                          OF_MIXER =0x00040000 ;物件的圖形用混色的方式秀圖
                          OF_WHITELIGHT =0x00080000 ;物件的圖形用加色的方式秀圖
                          OF_DARKLIGHT = 0x00100000 ;物件的圖形用減色的方式秀圖
                          OF_ENEMYGENERAL = 0x01000000 ;對敵方的武將有效
                          OF_ENEMYFORCE = 0x02000000 ;對敵方的小兵有效
                          OF_MYGENERAL =0x04000000 ;對我方的武將有效
                          OF_MYFORCE = 0x08000000 ;對我方的小兵有效
                          OF_ATTACKENEMY = 0x03000000 ;只對敵方有效
                          OF_ATTACKMY = 0x0c000000 ;只對我方有效
                          OF_ATTACKALL = 0x0F000000 ;對敵我雙方都有效
                          OF_TARGET = 0x00200000 ;有目標,一旦打中目標就爆炸
                          OF_BOMB = 0x00400000 ;只要打中任何一個物件(有效物件)就爆炸
                          一个物件的标记可以是以上多个标记的组合。甚至上面的一些标记自身就是其它标记的组合;例如,OF_ATTACKENEMY标记其实就是OF_ENEMYGENERAL和OF_ENEMYFORCE的组合。
                          (底层上,标记是一个bitset;OF_ATTACKENEMY = OF_ENEMYGENERAL | OF_ENEMYFORCE。某个标记位存在,意味着该标记对应的二进制位为1。不过,如果读者不熟悉位运算,也不要紧:在编写脚本时,通过直接操作系统函数,可以直接设置或删除对应的标记,而无需关注位运算的细节。)


                          IP属地:美国本楼含有高级字体15楼2020-03-20 13:44
                          回复
                            并不是所有标记都有重要的效果。事实上,这里面有很多标记甚至没有被奥汀使用过,有一些的作用则尚不明确。我们主要关注几个标记的子集,包括:
                            1) 无重力标记 OF_NOGRAVITY
                            2) “特殊放大的物件”标记 OF_BIGSHAPE
                            3) 混合模式标记 OF_MIXER/ OF_WHITELIGHT / OF_DARKLIGHT
                            4) 打击目标标记 OF_ENEMYGENERAL/ OF_ENEMYFORCE / OF_MYGENERAL / OF_MYFORCE / OF_ATTACKENEMY / OF_ATTACKMY / OF_ATTACKALL
                            5) 击中销毁标记 OF_TARGET/ OF_BOMB
                            本章介绍显示相关的标记,即混合模式标记和“特殊放大物件”标记。


                            IP属地:美国16楼2020-03-20 13:47
                            回复
                              2026-01-19 01:02:02
                              广告
                              不感兴趣
                              开通SVIP免广告
                              在上面的代码中,我们的前后伏兵其实已经大概能看了。不过,如果读者自己开一个原版的伏兵对照,可能就会发现问题:似乎我们的武将技里,还少了个淡入效果?
                              一个很自然的想法是用SetObjectOpacity设置不透明度。不过,问题在于,SetObjectOpacity需要物件身上带有OF_MIXER(混色)标记才能生效。我们试着直接设置士兵的不透明度,会发现没有任何效果:


                              IP属地:美国17楼2020-03-20 13:51
                              回复