吉里吉里アプリ入門

画像ビューア

画像ビューア

吉里吉里SDK1)の中にはサンプルも兼ねたいくつかのスクリプトが同梱されています。その中から画像ビューアを見てみることにしましょう。

ビューアのプロジェクトフォルダはkirikiri2/viewer/です。KAGシステムを利用してゲーム作成されている方にはおなじみですが、吉里吉里は実行する対象としてフォルダを指定します。このフォルダをプロジェクトフォルダと呼びます。そして、実際に一番最初に処理されるtjsファイルはプロジェクトフォルダの直下にあるstartup.tjsです。見てわかるとおり、このビューアはstartup.tjsのみで構成されています。

startup.tjs

さて、中身を見てみましょう。

おおまかにいうと、MyWindowというウィンドウとそのメンバとしてメニューとレイヤを数枚作っています。そして、actionメソッドで画像ファイルがドロップされたときに画像を読み込み描画する処理を記述しています。

プライマリレイヤとそれ以外

吉里吉里でいうところのレイヤとは、文字や画像を描画する透明なシートです。ノベルゲームでは背景や各キャラクター、メッセージ枠などはレイヤによって実装されています。アニメのセルのようなものです。

そして、レイヤには親となるレイヤをもちます。祖先のレイヤが不可視状態なら自身の状態にかかわらず表示されなかったり、トランジションは親子構造単位で行われたりしますが、KAGでは特に意識する必要もあまりありません。けれど当然ですが、一番上位のレイヤには親となるレイヤが存在しません。逆をいうと一番最初に作成したレイヤは親をもちません。このレイヤをプライマリレイヤと呼びます。プライマリレイヤは位置を移動できなかったり、不可視や不透明にはできないといったような制限があります。

このスクリプトではprimlayerというプライマリレイヤと、primlayerを親とするgraphlayerとcheckersを使用しています。

// kirikiri2/viewer/startup.tjs
0016:     // 下敷き(プライマリレイヤ)
0017:     add(primlayer = new Layer(this, null));
 
0024:     // 画像
0025:     add(graphlayer = new Layer(this, primlayer));
 
0037:     // 市松模様保存場所
0038:     add(checkers = new Layer(this, primlayer));

オブジェクト指向

たとえばレイヤにはサイズや位置といった情報を、それぞれのレイヤごと保持しておく必要があります。また、画像ファイルを読み込んで描画するといったような処理の手順も、個々のレイヤがもっていると管理しやすくなるような気がします。このようにレイヤやウィンドウなどのモノをデータの単位として扱おうとする考え方のことをオブジェクト指向といいます。詳しいこと知りたい方は(google)オブジェクト指向へ。

さて。レイヤなど同じ種類のモノでもprimlayerやgraphlayerといった個々のモノをインスタンスといいます。インスタンスを作成するためには、その設計図となるクラスと呼ばれるものを定義する必要があります。たとえばレイヤクラスはLayerで吉里吉里内部で定義されているものなので、私たちが定義せずに使用できます。

オブジェクト指向の大きな特徴のひとつに、既存のクラスを引き継ぎ変更点のみ記述することで新たなクラスを作成することができます。このスクリプトでは吉里吉里提供クラスのWindowクラスを継承してMyWindowクラスを定義しています。つまり、Windowクラスは画像ファイルをドロップしても何も処理されない汎用的なウィンドウのクラスでした。そこにgraphlayerなどのレイヤをもち、ドロップされた画像ファイルをもつという機能を追加したMyWindowというウィンドウを定義しているわけです。

// kirikiri2/viewer/startup.tjs
0002: class MyWindow extends Window
0003: {
0004:   var primlayer;
0005:   var graphlayer;
0006:   var checkers;
0007:   var filemenu;
0008:   var exitmenu;
0009: 
0010:   function MyWindow()
0011:   {
0012:     super.Window();

そして、ビューアの機能を備えたウィンドウを作成しています。本質的な意味(?)での実行文は104行目の1文のみです。もちろんこの行によってMyWindow.MyWindowが呼び出され、初期化などの処理が実行されているのですが。(このクラスと同名のメソッドはコンストラクタと呼ばれ、インスタンスが作成されるときに呼ばれます。)

// kirikiri2/viewer/startup.tjs
0104: var a = new MyWindow();

イベント駆動

プログラムは、基本的に記述した順に上から下へ処理されていき、最後まで処理されたら終了します。

しかし、吉里吉里のウィンドウもそうですがグラフィカルなインタフェースをもつアプリケーションは、イベント駆動と呼ばれ、クリックしたりキーが押されたりすると、それに応じた処理が実行されるようになっています。このクリックした、キーが押された、マウスカーソルが動いたといったような情報をイベントといいます。イベントが発生したときに実行させたい処理はactionメソッドに記述します。(別の方法もあります。詳しくは(kr2doc)イベントシステムを参照してください。)

actionメソッドはイベントが発生すると呼ばれ、引数にイベントの情報が入った辞書配列が与えられます。ev.typeとev.targetでイベントの種類で分岐させて、ファイルがドロップされたときと終了メニューがクリックされたときの処理を書いています。ファイルメニューがクリックされると子メニューが表示されるというメニューとしてあるべき処理などは、あらかじめMenuクラスに組み込まれています。

// kirikiri2/viewer/startup.tjs
0091:   function action(ev)
0092:   {
0093:     if(ev.type == "onFileDrop" && ev.target == this)
0094:     {
0095:       loadImage(ev.files[0]);
0096:     }
0097:     else if(ev.type == "onClick" && ev.target == exitmenu)
0098:     {
0099:       close();
0100:     }
0101:   }

ev.files[0]はドロップしたファイルの配列です。複数のファイルをドロップすることもでき、各ファイル名が配列として格納されますが、このスクリプトではひとつのファイルしか読み込まないようにしています。やろうと思えば、サムネイル(縮小した画像)で一覧のように表示することもできます。

市松模様

趣旨から離れるため蛇足となりますが、気になる人もいるかもしれないので。

chekersというレイヤに320ピクセル四方の市松模様を描画し、画像を読み込んだタイミングでprimlayerにコピーしています。43, 44行目でxとyは0から8ずつ増加させています。すなわち、0, 8, 16, 24, 32, 40...となります。この数値を2進法で表現すると以下のようになります。

10進法 2進法
0 0b00000000
8 0b00001000
16 0b00010000
24 0b00011000
32 0b00100000
40 0b00101000

右から4桁目を見てもらうとわかるのですが、0, 1, 0, 1, 0, 1...となっています。そして、x^yの^はビット(2進法の桁)ごとに排他的論理和を返す演算子((tjs2doc)ビットXOR演算子)です。xとyのうちどちらかが1(true)もう一方が0(false)ならば1、両方とも1もしくは0ならば0となり、それを各桁ごと演算します。つまり、x^yの結果の表は以下のようになります。カッコ内は4桁目の数値で、結果も4桁目のみです。

XOR 0(0) 8(1) 16(0) 24(1) 32(0) 40(1)
0(0) 0 1 0 1 0 1
8(1) 1 0 1 0 1 0
16(0) 0 1 0 1 0 1
24(1) 1 0 1 0 1 0
32(0) 0 1 0 1 0 1
40(1) 1 0 1 0 1 0

なんとなくやりたいことが見えてきたでしょうか。xとyはレイヤの座標ですので、ビットXOR演算子を使うことで市松模様を描くことができそうです。とはいっても、上の表は4桁目のみに注目したものです。たとえば、xが8でyが32のときのx^yの結果(0b00001000^0b00011000)は0b00010000すなわち16というれっきとした数値なので、まだこれだけでは市松模様にすることはできません。

(x^y)&0b1000の&はビットごとの論理積を返す演算子((tjs2doc)ビットAND演算子)です。各桁ごと比較し、両方とも1ならば1に、一方のみ1もしくは両方とも0ならば0となります。すなわち以下のような表となります。

AND 0 1
0 0 0
1 0 1

ここで、0b1000は4桁目以外は0ですので、ビットANDをとると4桁目以外は必ず0になります。そして4桁目は、x^yの4桁目によって1になるか0になるかが決まります。x^yの4桁目が1のときは(x^y)&0b1000全体として0b00001000すなわち8となり、x^yの4桁目が0のときは全体で0となります。整数の場合0以外なら真として、0なら偽として扱われますので、(tjs2doc)条件演算子によりcolorRectメソッドの第5引数は0xc0c0c0(白っぽい灰色)か0x808080(黒っぽい灰色)のどちらかとなり、結果的に市松模様が描き出されます。

なぜ、市松模様をprimlayerに描画するかというと、graphlayerに読み込んだ画像が透過情報を持っていたとき、それがわかりやすいようするためです。

// kirikiri2/viewer/startup.tjs
0043:     for(var y = 0; y<320; y+=8)
0044:       for(var x = 0; x<320; x+=8)
0045:         checkers.colorRect(x, y, 8, 8, ((x^y)&0b1000)?0xc0c0c0:0x808080, 255);

タグ情報

もいっちょ蛇足です。

// kirikiri2/viewer/startup.tjs
0057:       var start = System.getTickCount();
0058:       var dic = graphlayer.loadImages(file);
0059:       Debug.message((System.getTickCount() - start) + " ms");
0060:       graphlayer.type = ltAlpha;
0061:       if(dic)
0062:       {
0063:         var ar = [];
0064:         ar.assign(dic);
0065:         for(var i = 0; i < ar.count; i+= 2)
0066:           Debug.message(ar[i] + '=' + ar[i+1]);
0067: 
0068:         if(dic.mode !== void)
0069:         {
0070:           var layertypeinfo = imageTagLayerType[dic.mode];
0071:           if(layertypeinfo !== void)
0072:             graphlayer.type = layertypeinfo.type;
0073:         }
0074:       }

この部分は何をしているかというと、TLG/PNG形式の画像が保持できるタグ情報というものがあり、それがあれば出力しているのです。詳しくは(kr2doc)タグ情報を参照してください。

最後に

吉里吉里には画像の独自形式としてTLGという形式があります。PNG形式に比べると圧縮率が高く、また展開速度も速い形式ですが、その画像内容の確認がしづらいことが欠点でした。この画像ビューアは単なるサンプルとしてだけでなく、TLG画像の確認が可能であるので覚えておくと便利かもしれません。といっても最低限の機能しかないビューアですので使い勝手はあまりよくないですけどね。

ということで、KAGを使わない吉里吉里の使い方についておおまかにわかったことだと思います。KAGのシステム部分も規模は大きいですが、これと同じように作られています。次から少しずつ見ていくこととしましょう。

1) Software Development Kit
 
inside/viewer.txt · 最終更新: 2008/01/25 15:53 by tohka