[ 編集このページを編集する ]

トップ > MAP作成 > トリガーエディタ > Jass Script > Jass講座 > モジュール、クラス



Jass NewGenの最大のウリのひとつ。Jss NewGenを使うと、関数郡を libraryscope にまとめたり、structure を使い変数をまとめたりできる。

各ページのコンパイル順序が前後してしまうトリガーエディタにおいて、どこからでも参照できるlibraryは非常に重要な機能。structureも、要素を簡潔にまとめてくれるので便利。

Jass NewGenの導入が前提です。



globals - グローバル

グローバル変数を定義できる。

  •     globals
            constant real g = 9.8
    
            integer  val = 0
        endglobals

globals は、どこにでも、いくつでも設定できる。トリガーエディタ付属の変数エディタよりもいろいろな面で優れていて、使いやすい。

定数も宣言できる。

library - ライブラリ

ライブラリは、中に関数をいれて利用する。

  • 文法
        library ライブラリ名 [initializer 初期化関数] [requires 必要なライブラリ1, 必要なライブラリ2, ...]
        endlibrary
  •     library mylib
            function libfunc takes nothing returns nothing
            endfunction
        endlibrary

コンパイル時、ライブラリ内の関数は、グローバル変数定義の後〜他の関数の前に読み込まれる。いろいろな場所で何度も呼ばれる関数をいれておくと、便利。

requires - ライブラリの読み込み順序を指定

あるライブラリから別のライブラリ内の関数を呼び出したいときは、requires パラメータを使う。

  •     library liba requires libb
            function libaf takes nothing returns nothing
                call libbf()
            endfunction
        endlibrary
    
        library libb
            function libbf takes nothing returns nothing
            endfunction
        endlibrary

上の例の場合、コンパイル時に、liba の中身より先に libb の中身が、読み込まれる。

複数のライブラリを requires に指定したいときは、, 記号で区切る。

library liba requires libb, libc, libd

また、二つのライブラリがお互いを requires に設定することはできない。

initializer - ライブラリの初期化

ライブラリを読み込む時(MAPロード時)、そのまえに関数を実行しておきたい場合は、initializer パラメータを使う。

  •     library liba initializer init_liba
            function run takes nothing returns nothing
            endfunction
    
            function init_liba takes nothing returns nothing
            endfunction
        endlibrary

initializer に指定する関数は、ライブラリ内に書かなければならない(private も可)。また、initializer は、引数を取らない形( takes nothing 〜)でなければならない。

private - プライベート関数

library 内の function を、 private function 〜 とすると、その関数は同じライブラリの中からしか呼び出せなくなる。そのかわりに、ライブラリの外にも同名の関数を定義できるようになる。

  •     library whee
            private function test takes nothing returns nothing
            endfunction
        endlibrary
    
        function test takes nothing returns nothing
        endfunction

ライブラリの中だけでしか使わない関数は、できるだけ private で宣言しよう。似たような名前の関数がある時など、ミス防止に役に立つ。

scope - スコープ

スコープ は、中に関数をいれて利用する。ライブラリと同じような機能をもつが、コンパイルしても先に読み込まれるといったことはない。

  • 定義
        scope スコープ名
        endscope
  •     scope myscope
            private function scopetest2 takes nothing returns nothing
            endfunction
    
            function scopetest takes nothing returns nothing
                call scopetest2()
            endfunction
        endscope

関数をまとめるのに便利。library 同様、private が使える。

structure - 構造体

複数のデータを持つ要素をまとめるのに、非常に便利。GameCacheよりも処理が軽いので、とても役に立つ。

構造体は、変数型のひとつのように振舞う(正体はinteger)。

  •     struct somestruct
            string message = "Hello!"
        endstruct
    
        function showmsg takes nothing returns nothing
            local somestruct s=somestruct.create()
            call BJDebugMsg(s.message)
            call s.destroy()
        endfunction

上の例では、画面に『Hello!』と表示される。

  • 定義
        struct 名前
        endstruct
  • 構造体の作成
        local 構造体の名前 変数名=構造体の名前.create()
  • 構造体の破棄
        call 変数名.destroy()

構造体は破棄しなくても、メモリリークの原因になることはない。ただし、一度にあつかえる構造体の数は、最大8190 個までと決まっているので、使い終わった構造体は必ず破棄しよう。

  • 構造体の値を参照
        構造体の変数名.構造体内の変数名
  •     struct test
            real one=1
            real two=2
            real three=3
            real sum
        endstruct
    
        function add takes nothing returns nothing
            local test blah=test.create()
            set blah.sum=blah.one+blah.two+blah.three
            call BJDebugMsg(R2S(blah.sum))
            call blah.destroy()
        endfunction

上の例の場合、画面に "6.000" が表示される。

  • 構造体の変数は、null化する必要はない(正体はinteger)。
  • 構造体はそれ自身がひとつのインスタンスなので、普通の変数型と同じように、何個でも(最大8190個)宣言できる。
  • 構造体の中に配列を宣言するときは、大きさを明示する必要がある。
        struct SampleStruct
            real array sample[2]
        endstruct

    上の例の場合、sample[0]sample[1] が利用可能。インデックスではなく、長さを記す点に注意。

    配列を宣言すると、その構造体のインスタンスを同時に利用できる最大数が、[構造体内の配列の、最大の長さ]で割った数まで減る。上の例の場合、同時に利用できるインスタンスの最大数は 8190/2 = 4095個。

static - スタティック

static とは、構造体の中での、 グローバル変数/定数 の宣言方法。

通常の構造体内の変数とは違い、全ての構造体で共有する変数になる。

  • 参照方法
        構造体名.staticな変数名
    
        // もしくは
    
        構造体のインスタンス名.staticな変数名

どちらの場合でも、同じ変数を指す。

  •     struct we
            string msg       = "Hello!"
            static integer X = 3
        endstruct
    
        function SetX takes nothing returns nothing
            local we NewStruct1  =  we.create()
            local we NewStruct2  =  we.create()
    
            // NewStruct1の、msg を編集
            // これはインスタンスそれぞれが持つ変数なので、NewStruct2.msg は変更されない。
            set NewStruct1.msg    = "Goodbye!"
    
            // we構造体の、X を編集
            // これは、全てのwe構造体のインスタンスで共通して参照する値なので、
            // NewStruct1.X と NewStruct1.X は、常に同じ値 = 10 を返すようになる。
            set we.X              = 10
    
            // set NewStruct1.X   = 10    等、we構造体のインスタンスから参照しても同じ。
    
    
       
            // 値を表示
            call BJDebugMsg("=== Common(Static) ===")
            call BJDebugMsg("X   = " + I2S(we.X))
            call BJDebugMsg("=== NewStruct1 ===")
            call BJDebugMsg("msg = " + NewStruct1.msg)
            call BJDebugMsg("X   = " + I2S(NewStruct1.X))
            call BJDebugMsg("=== NewStruct2 ===")
            call BJDebugMsg("msg = " + NewStruct2.msg)
            call BJDebugMsg("X   = " + I2S(NewStruct2.X))
    
        endfunction

これを実行すると、以下のような出力が得られる。

        === Common(Static) ===
        X   = 10
        === NewStruct1 ===
        msg = Goodbye!
        X   = 10
        === NewStruct2 ===
        msg = Hello!
        X   = 10

つまり、globals 内で定義した変数と同じように扱える。

また、static な定数を宣言する方法は、以下のとおり。

    static constant real e  =  2.71828

つまり、globals 内で定義した定数と同じように扱える。

method - メソッド

method とは、構造体の中で定義できる function のこと。

  •     struct we
            string msg="Hello!"
            method sayit takes nothing returns nothing
                call BJDebugMsg(this.msg)
            endmethod
        endstruct
    
        function test takes nothing returns nothing
            local we s=we.create()
            call s.sayit()
            call s.destroy()
        endfunction
  • method を呼び出す方法
        call 変数名.''method''名(引数)

method内から、同じ構造体に含まれる変数を呼び出したいときは、this.変数名 を使う。

また、 staticmethod も宣言できる。

  • 注意点
    • method内で、GetTriggerUnit()を使ってはいけない
    • method内で、TriggerSleepAction/PolledWaitを使ってはいけない

構造体の利点

通常、値をオブジェクトに関連付けたい場合は、GameCacheを使う。

  • 構造体を使わない
        //よくない例 
        function AdddUnitInfo takes unit whichUnit, integer A, real B, unit C returns nothing
            
            call SetHandleInteger(whichUnit,"A",A)
            call SetHandleReal   (whichUnit,"B",B)
            call SetHandleHandle (whichUnit,"C",C)
    
        endfunction
    
        //ユニットの値を取得&編集、更新
        function ShowUnitInfo takes unit whichUnit returns nothing
            local integer A = GetHandleInteger(whichUnit,"A")
            local real    B = GetHandleReal   (whichUnit,"B")
            local unit    C = GetHandleUnit   (whichUnit,"C")
    
            //たとえば、AとBに1たして更新する。
            //GameCacheの中身と変数の中身は別なので、登録しなおす必要がある。         
            call SetHandleInteger(whichUnit,"A",A+1)
            call SetHandleReal   (whichUnit,"B",B+1)
    
            //再びこの変数郡を使わないなら、GameCacheから削除すること。
            //call SetHandleInteger(whichUnit,"A",0)
            //call SetHandleReal(whichUnit,"B",0)
            //call SetHandleHandle(whichUnit,"C",null)
    
            //ユニット型変数を、開放
            set C = null
        endfunction
    

GameCacheの処理は、構造体を使った処理よりも遅い。しかも複数のパラメータがあるとき、それぞれを呼び出さねばならず、更新の処理も必要だ。

構造体を使うと、下のようになる。

  • 構造体を使った
        //よい例
    
        //構造体を定義する
        struct UnitInfo
            integer A = 0
            real    B = 0
            unit    C = null
        endstruct
    
        function AdddUnitInfo takes unit whichUnit, integer A, real B, unit C returns nothing
            local UnitInfo UI = UnitInfo.create()
    
            set UI.A = A
            set UI.B = B
            set UI.C = C
    
            //構造体(integer扱い)を、SetHandleIntegerを使って格納。
            call SetHandleInteger(whichUnit,"UI",UI)
    
        endfunction
    
        //ユニットの値を取得&編集、更新
        function ShowUnitInfo takes unit whichUnit returns nothing
            local UnitInfo UI = UnitInfo(GetHandleInteger(whichUnit,"UI"))
    
            //これだけで、 UI.A、UI.B、UI.C 全てにアクセスできる。     
            //たとえば、AとBに1たして更新する。
            set UI.A = UI.A+1
            set UI.A = UI.A+2
            //GameCacheの手法とは違い、UI に格納してあるのは構造体の識別番号のみ。
            //従って、GameCacheを更新する必要はない。
            
    
            //再びこのインスタンスを使わないなら、destroy()すること。
            //call UI.destroy()
            //call SetHandleInteger(whichUnit,"UI",0)
    
            //よくない例と違い、ダイレクトに構造体のインスタンスに格納するので、
            //ローカル変数の開放をしなくてよい
        endfunction

GameCacheを呼び出す回数が圧倒的に減るので、処理も軽快になる。また、スクリプトを書きながら要素を増やした時なども、非常に対応がしやすい。

利用例

ノックバック のサンプルをどうぞ。

注意点

構造体は、.destroy()しても、当分は使えてしまう

  • 使えてしまう例
        //のちのち不具合がでる、悪い例
        struct abuseStruct
            integer A = 0
        endstruct
    
        function abusesample takes nothing returns nothing
            local abuseStruct S = abuseStruct.create()
    
            //変数を設定 
            set S.A = 100
    
            //構造体を破棄
            call S.destroy()
    
            //構造体.A が、参照できてしまう
            call BJDebugMsg(I2S(S.A))
    
        endfunction

この場合、画面には正常に 100 という数字が表示される。しかし、大量の、同じ構造体が作られた場合、このデータは上書きされ、違う数値が出力される。

これはとても不具合の原因になりやすいので、十分に注意が必要だ。なまじ参照できてしまうだけに気づきにくいが、十分に注意すること。

Q&A

  • 構造体にhandle型変数(たとえばユニット)を指定した場合、.destroy()時に破棄しなくていいのか?
    • おまけコラムに記すが、構造体の中身は全てグローバル変数に定義してある。領域は確保されたままにはなるが、ローカル変数とは違い再利用される領域なので、破棄する必要はない。

おまけコラム

構造体の正体は、グローバル変数(配列)の集まりです。たとえば

    struct test
         integer a
         unit b
    endstruct

このような構造体があった場合、以下のようなグローバル変数が定義されます。

    //構造体内の、最大の配列の長さ
    constant integer si__test=1

    //インスタンス別に、インデックスを操作する変数
    integer si__test_F=0
    integer si__test_I=0
    integer array si__test_V

    //それぞれのインスタンスの変数を格納する配列
    integer array s__test_a
    unit    array s__test_b

まず、インスタンスが作成されると、そのインスタンスに特定のインデックス(integer)が割り当てられます。

そして、そのインスタンスの変数が扱われるときは、グローバル変数の配列の特定のインデックスに対して値が代入されるという処理が行われます。

  • コンパイル前
        struct test
             integer a = 1
             unit    b = null
        endstruct
    
        function sample takes unit unitA, unit unitB returns nothing
            local test T1 = test.create()
            local test T2 = test.create()
    
            set T1.a = 100
            set T1.b = unitA
            set T2.a = 300
            set T2.b = unitB
    
            call T1.destroy()
        endfunction
  • コンパイル後
        globals
            //構造体内の、最大の配列の長さ
            constant integer si__test=1
    
            //インスタンス別に、インデックスを操作する変数
            integer si__test_F=0
            integer si__test_I=0
            integer array si__test_V
    
            //それぞれのインスタンスの変数を格納する配列
            integer array s__test_a
            unit    array s__test_b
        endglobals
    
        //test.create() に対応。構造体のインスタンスを作る処理。
        //まだ使っていないインデックスを割り当てる。
        function s__test__allocate takes nothing returns integer
            local integer this=si__test_F
            if (this!=0) then
                set si__test_F=si__test_V[this]
            else
                set si__test_I=si__test_I+1
                set this=si__test_I
            endif
            if (this>8190) then
                return 0
            endif
    
            //値を初期化
            set s__test_a[this]=   1
            set s__test_b[this]=   null
            set si__test_V[this]=-1
            return this
        endfunction
    
        //test.destroy() に対応。構造体のインスタンスを削除する処理。
        //まだ使っていないインデックスを割り当てる。
        function s__test_destroy takes integer this returns nothing
            if this==null then
                return
            elseif (si__test_V[this]!=-1) then
                return
            endif
            set si__test_V[this]=si__test_F
            set si__test_F=this
        endfunction
    
    
        function sample takes unit unitA, unit unitB returns nothing
            local integer T1 = s__test__allocate()
            local integer T2 = s__test__allocate()
    
            set s__test_a[T1] = 100
            set s__test_b[T1] = unitA
            set s__test_a[T2] = 300
            set s__test_b[T2] = unitB
    
            call s__test_destroy(T1)
        endfunction

このような処理に変換されます。グローバル変数の操作はGameCacheの操作より遥かに早いので、より効率のいいスクリプトとなるわけです。

また、構造体の仕様も、上のソースから説明できます。

  • 構造体をnull化しなくていい理由
    • 構造体のインスタンスの正体は、integerだから
  • 『構造体の利点』の項目にある、『set UI.A = UI.A+1』だけで、GameCacheに入っている構造体のインスタンスそのものが変更できる理由
    • GameCacheに入っているのはインデックスだけで、実データはグローバル変数の配列。それを直接操作しているだけだから。
  • 『注意点』の項目にある、.destroy() 後もしばらく使えてしまう理由
    • 配列のインデックスを再利用可能にするだけなので、データ自体は消失していないため。なので、同じインデックス番号を指定すれば、上書きされるまでは使えてしまう。
      • .create() 時にはきちんと初期化されるので、別のインスタンスに引き継がれてしまうということはない。

要は、グローバル変数の配列を利用しているから、ゲームキャッシュを介すよりも、ずっと早いということ。