トップ > MAP作成 > トリガーエディタ > Jass Script > Jass講座 > モジュール、クラス
Jass NewGenの最大のウリのひとつ。Jss NewGenを使うと、関数郡を library や scope にまとめたり、structure を使い変数をまとめたりできる。
各ページのコンパイル順序が前後してしまうトリガーエディタにおいて、どこからでも参照できるlibraryは非常に重要な機能。structureも、要素を簡潔にまとめてくれるので便利。
Jass NewGenの導入が前提です。
globals - グローバル
グローバル変数を定義できる。
globals は、どこにでも、いくつでも設定できる。トリガーエディタ付属の変数エディタよりもいろいろな面で優れていて、使いやすい。
定数も宣言できる。
library - ライブラリ
ライブラリは、中に関数をいれて利用する。
コンパイル時、ライブラリ内の関数は、グローバル変数定義の後〜他の関数の前に読み込まれる。いろいろな場所で何度も呼ばれる関数をいれておくと、便利。
requires - ライブラリの読み込み順序を指定
あるライブラリから別のライブラリ内の関数を呼び出したいときは、requires パラメータを使う。
上の例の場合、コンパイル時に、liba の中身より先に libb の中身が、読み込まれる。
複数のライブラリを requires に指定したいときは、, 記号で区切る。
library liba requires libb, libc, libd
また、二つのライブラリがお互いを requires に設定することはできない。
initializer - ライブラリの初期化
ライブラリを読み込む時(MAPロード時)、そのまえに関数を実行しておきたい場合は、initializer パラメータを使う。
initializer に指定する関数は、ライブラリ内に書かなければならない(private も可)。また、initializer は、引数を取らない形( takes nothing 〜)でなければならない。
private - プライベート関数
library 内の function を、 private function 〜 とすると、その関数は同じライブラリの中からしか呼び出せなくなる。そのかわりに、ライブラリの外にも同名の関数を定義できるようになる。
ライブラリの中だけでしか使わない関数は、できるだけ private で宣言しよう。似たような名前の関数がある時など、ミス防止に役に立つ。
scope - スコープ
スコープ は、中に関数をいれて利用する。ライブラリと同じような機能をもつが、コンパイルしても先に読み込まれるといったことはない。
関数をまとめるのに便利。library 同様、private が使える。
structure - 構造体
複数のデータを持つ要素をまとめるのに、非常に便利。GameCacheよりも処理が軽いので、とても役に立つ。
構造体は、変数型のひとつのように振舞う(正体はinteger)。
上の例では、画面に『Hello!』と表示される。
- 構造体の破棄
call 変数名.destroy()
構造体は破棄しなくても、メモリリークの原因になることはない。ただし、一度にあつかえる構造体の数は、最大8190 個までと決まっているので、使い終わった構造体は必ず破棄しよう。
- 構造体の値を参照
構造体の変数名.構造体内の変数名
上の例の場合、画面に "6.000" が表示される。
- 構造体の変数は、null化する必要はない(正体はinteger)。
- 構造体はそれ自身がひとつのインスタンスなので、普通の変数型と同じように、何個でも(最大8190個)宣言できる。
static - スタティック
static とは、構造体の中での、 グローバル変数/定数 の宣言方法。
通常の構造体内の変数とは違い、全ての構造体で共有する変数になる。
どちらの場合でも、同じ変数を指す。
これを実行すると、以下のような出力が得られる。
=== 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 のこと。
- method を呼び出す方法
call 変数名.''method''名(引数)
method内から、同じ構造体に含まれる変数を呼び出したいときは、this.変数名 を使う。
また、 static な method も宣言できる。
- 注意点
- 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()しても、当分は使えてしまう。
この場合、画面には正常に 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)が割り当てられます。
そして、そのインスタンスの変数が扱われるときは、グローバル変数の配列の特定のインデックスに対して値が代入されるという処理が行われます。
- コンパイル後
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() 時にはきちんと初期化されるので、別のインスタンスに引き継がれてしまうということはない。
要は、グローバル変数の配列を利用しているから、ゲームキャッシュを介すよりも、ずっと早いということ。