大まかな方針として、ゲームウィンドウクラス、ゲームマスタークラス、モグラクラスの3つに分けることにします。それぞれのクラスの役割は以下の通りです。
|
クラス |
役割 |
備考 |
|
ゲームウィンドウクラス |
ウィンドウの表示、メニューの表示、残り時間の表示 |
Windowクラスを継承 |
|
ゲームマスタークラス |
ゲームの進行役:得点の管理、モグラの管理を行う |
|
|
モグラクラス |
モグラ(の画像)の表示、マウスイベントの処理、効果音の再生 |
Layerクラスを継承 |
まずは骨組みを実装してみましょう。メソッドの中身は徐々に実装していきます。最初はゲームウィンドウクラスの骨組みです。
|
// ゲームウィンドウクラス class GameWindow extends Window { var file_menu; // ファイルメニュー var start_menu; // ゲーム開始メニュー var exit_menu; // 終了メニュー
var master; // ゲームマスター
// コンストラクタ function GameWindow() { super.Window(); // スーパークラスのコンストラクタを呼び出す }
// デストラクタ function finalize() { super.finalize(); // スーパークラスのデストラクタを呼び出す }
// メニュー選択を処理するaction()メソッド function action(ev) { } }
// ゲームウィンドウオブジェクトを作成 var win = new GameWindow(); |
■メソッドaction()の説明は、「吉里吉里2リファレンス」の「イベントシステム」を参照してください。MenuItemオブジェクトがaction()メソッドを呼び出すとは明記されていませんが、同梱のTJSサンプル(viewerなど)から、それが推測できます。□
Windowクラスのオブジェクトは、それ単体ではメニューなどを一切持っていません。そこで、Windowクラスを継承し、必要なメニューなどを加えたゲームウィンドウクラスを作成するのです。
メンバ変数は次のように使うことにします。
|
メンバ変数 |
役割 |
備考 |
|
file_menu |
[ファイル]メニューを作る |
|
|
start_menu |
[ファイル‐ゲーム開始]メニューを作る |
|
|
exit_menu |
[ファイル‐閉じる]メニューを作る |
|
|
master |
ゲームマスターオブジェクト |
後述 |
次はゲームマスタークラスの骨組みです。タイマーオブジェクトと、タイマーイベントを処理するメソッドが2つずつあることに注意してください。タイマー1の役割は、モグラを動かし、得点を更新することです。また、タイマー2の役割は、残り時間を更新することです。
■1つのタイマーオブジェクトでも事足りますが、判りやすくするために2つに分けています。□
|
// ゲームマスタークラス(モグラオブジェクトの管理などを行う) class GemeMaster { var window; // ゲームウィンドウオブジェクト var parent; // 親レイヤー:(ゲームウィンドウの)プライマリレイヤー var fore; // 親レイヤーのコピー:表画面として使う var moles = []; // モグラオブジェクトの配列 var timer1; // タイマー1:モグラ用 var timer2; // タイマー2:残り時間用
var rest_time; // 残り時間 var point; // 得点
// コンストラクタ function GemeMaster(win, par) { }
// デストラクタ function finalize() { }
// ゲームを開始 function start() { }
// ゲームを終了 function stop() { }
// タイマーイベントを処理:モグラを動かす function onTimer1() { }
// タイマーイベントを処理:残り時間を更新する function onTimer2() { } } |
最後にモグラクラスの骨組みです。モグラクラスはレイヤークラスを継承します。もちろん、現実のモグラがレイヤーを継承することなどあり得ません。レイヤーの持つ機能――画像の表示・非表示やマウスイベントの処理――を特化し、ゲーム用のモグラに仕立てているだけです。モグラクラスではonMouseDown()メソッドをオーバーライドします。
モグラクラスではKAGの関数intrandom()を使います。この関数はKAGからしか使えないため、あらかじめkag3\template\system\Util.tjsから該当コードをコピーしておきます。
■intrandom()では、いくつか説明していない演算子を使っていますが、各自で調べてみてください。□
|
// Util.tjsからintrandom()のコードを拝借 function intrandom(min = 0, max = 0) { // min 以上 max 以下の整数の乱数を返す // 引数が一個だけの場合は 0 〜 その数までの整数を返す if(min>max) { min <-> max; } return int(Math.random() * (max-min+1)) + min; }
// モグラクラス(のスケルトンコード) class Mole extends Layer { var master; // ゲームマスター var hit_sound; // 叩かれたときの効果音 var counter; // イベントのカウンター(-hidden_time〜visible_time) var hidden_time; // 隠れている時間 var visible_time; // 出現している時間
function Mole(gm, window, parent) // コンストラクタ { super.Layer(window, parent); // スーパークラスのコンストラクタを呼び出し }
function finalize() // デストラクタ { super.finalize(); // スーパークラスのデストラクタを呼び出し }
function reset() { } // パラメータを再設定 function move() { } // モグラの画像を表示したり非表示したりする
function onMouseDown(x, y, button, shift) { } // マウスイベントを処理 } |
メンバ変数は次のように使うことにします。
|
メンバ変数 |
説明 |
備考 |
|
master |
ゲームマスターオブジェクト |
|
|
hit_sound |
WaveSoundBufferオブジェクト。モグラが叩かれたとき、効果音を鳴らす |
|
|
counter |
タイマーイベントのカウンター。-hidden_timeからvisible_timeの間で変化する。負ならモグラを非表示、0以上なら表示させる |
|
|
hidden_time |
モグラが隠れている時間。reset()が呼び出されるたびに乱数で設定 |
単位はタイマーイベントの回数 |
|
visible_time |
モグラが出現している時間。reset()が呼び出されるたびに乱数で設定 |
同上 |
ゲームウィンドウクラスのコンストラクタ、デストラクタを実装します。
|
// コンストラクタ function GameWindow() { super.Window(); // スーパークラスのコンストラクタを呼び出す
// [ファイル]、[ファイル‐ゲーム開始]、[ファイル‐閉じる]メニューを作成 menu.add(file_menu = new MenuItem(this, "ファイル(&F)")); file_menu.add(start_menu = new MenuItem(this, "ゲーム開始(&S)")); file_menu.add(exit_menu = new MenuItem(this, "閉じる(&C)"));
// プライマリレイヤーを作成し、ゲームウィンドウ自身に登録 add(new Layer(this, null)); with (primaryLayer) { // 背景画像を読み込み、そのサイズに設定 .loadImages("stage"); .setSizeToImageSize(); }
// ゲームウィンドウをプライマリレイヤーのサイズに合わせる setInnerSize(primaryLayer.width, primaryLayer.height);
caption = "モグラ叩きゲーム"; // ゲームウィンドウのタイトルを設定 visible = true; // ゲームウィンドウを表示
// ゲームマスターオブジェクトを作成 master = new GemeMaster(this, primaryLayer); }
// デストラクタ function finalize() { // ゲームマスターを無効化 invalidate master;
// メニューを無効化 invalidate exit_menu; invalidate start_menu; invalidate file_menu;
super.finalize(); // スーパークラスのデストラクタを呼び出す } |
デストラクタの説明は必要ないでしょうから、コンストラクタのみ説明します。
最初にメニューアイテムオブジェクトを作り、[ファイル]、[ファイル‐ゲーム開始]、[ファイル‐閉じる]メニューを作成しています。これらのメニューを選択したら、(後述の)メソッドaction()が呼ばれるようになります
次にプライマリレイヤーオブジェクトを作り、add()メソッドゲームウィンドウ自身に登録します。以降、プライマリレイヤーはprimaryLayerプロパティでアクセスできるようになります
プライマリレイヤーに背景画像などを読み込ませます。プライマリレイヤーは、後で裏画面として使います
最後にゲームマスターオブジェクトを作成します(引数はゲームウィンドウ自身とプライマリレイヤー)
以下のトピックも参照してください。
|
リファレンス |
トピック |
サブトピック |
|
吉里吉里2リファレンス |
Windowクラス |
メソッドadd()、setInnerSize() プロパティcaption、visible |
|
MenuItemクラス |
コンストラクタ メソッドadd() | |
|
Layerクラス |
コンストラクタ メソッドloadImages()、SetSizeToImageSize() プロパティwidth、height |
プライマリレイヤーを作るときの引数nullは、「オブジェクトではあるが、何のオブジェクトも示していない」ことを示す、TJSのキーワードです。voidとは少し意味が異なります。
ゲームウィンドウクラスのメソッドaction()を実装します。引数evは辞書オブジェクトで、キー"type"にイベントの種類が、キー"target"にイベントを発生させたオブジェクトが渡されます。
|
// メニュー選択を処理するaction()メソッド function action(ev) { if (ev.type == "onClick") { switch (ev.target) { case start_menu: // [ゲーム開始]メニューを選択 master.start(); break; case exit_menu: // [閉じる]メニューを選択 close(); } } } |
イベントの種類が"onClick"、イベント発生させたオブジェクトがstart_menuかexit_menuの場合のみ、処理します。
■メソッドaction()の詳細は、「吉里吉里2リファレンス」の「イベントシステム」を参照してください。□
ゲームマスタークラスのコンストラクタとデストラクタを実装します。
|
// コンストラクタ function GemeMaster(win, par) { window = win; parent = par;
// 親レイヤーのコピーを作る fore = new Layer(win, par); with (fore) { .visible = true; .assignImages(parent); .setSizeToImageSize();
// フォントの高さを30ピクセルに設定(残り時間、得点の文字に使う) .font.height = 30; }
// モグラオブジェクトを作成 moles.add(new Mole(this, window, fore)); moles.add(new Mole(this, window, fore)); moles.add(new Mole(this, window, fore));
// モグラの表示位置を設定 // (モグラの画像サイズによって調整してください) moles[0].top = 100; moles[0].left = 50; moles[1].top = 100; moles[1].left = 250; moles[2].top = 100; moles[2].left = 450;
// モグラ用タイマーオブジェクトを作成 timer1 = new Timer(onTimer1, ""); timer1.interval = 100; // タイマーイベントは100ミリ秒間隔で発生
// 残り時間用タイマーオブジェクトを作成 timer2 = new Timer(onTimer2, ""); timer2.interval = 1000; // タイマーイベントを1秒間隔で発生させる }
// デストラクタ function finalize() { // タイマーを無効化 invalidate timer2; invalidate timer1;
// モグラを無効化 for (var i = 0; i < moles.count; i++) { invalidate moles[i]; }
// 表画面を無効化 invalidate fore; } |
デストラクタの説明は必要ないでしょうから、コンストラクタのみ説明します。
親レイヤー(プライマリレイヤー)のコピーforeを作ります。以降、foreを表画面レイヤーとして扱い、モグラ、残り時間、得点の表示を行います。また、親レイヤーは裏画面として扱うことにします(後述)
モグラオブジェクトを作成し、表示する座標を設定します
タイマーオブジェクトを2つ作成します。timer1はモグラを動かすため、timer2は残り時間をカウントするためのタイマーです
以下のトピックも参照してください。
|
リファレンス |
トピック |
サブトピック |
|
吉里吉里2リファレンス |
Layerクラス |
メソッドassignImages() プロパティvisible、font、top、left |
|
Fontクラス |
プロパティheight | |
|
Timerクラス |
コンストラクタ プロパティinterval |
親子関係を持つレイヤーは、特に指定しない限り、親レイヤーが奥、子レイヤーが手前に表示されます。今回のスクリプトでは、奥から順番にプライマリレイヤー、表画面レイヤー、モグラ(レイヤー) となるようにします。
ゲームマスタークラスのメソッドstart()とstop()を実装します。メソッドstart()では、得点や残り時間を設定し、タイマーを発生させています。メソッドstop()ではタイマーの発生を停止させます。
|
// ゲームを開始 function start() { point = 0; rest_time = 20; timer1.enabled = timer2.enabled = true; }
// ゲームを終了 function stop() { timer1.enabled = timer2.enabled = false; } |
以下のトピックも参照してください。
|
リファレンス |
トピック |
サブトピック |
|
吉里吉里2リファレンス |
Timerクラス |
プロパティenabled |
ゲームマスタークラスのメソッドonTimer1()、onTimer2()を実装します。
|
// タイマーイベントを処理:モグラを動かす function onTimer1() { // モグラを動かす for (var i = 0; i < moles.count; i++) { moles[i].move(); }
// 得点を更新する with (fore) { .copyRect(500, 0, parent, 500, 0, 160, 30); .drawText(500, 0, "得点:"+point, 0x000000); } }
// タイマーイベントを処理:残り時間を更新する function onTimer2() { // 残り時間を更新する with (fore) { .copyRect(0, 0, parent, 0, 0, 160, 30); .drawText(0, 0, "残り時間:"+(rest_time--), 0x000000); }
if (rest_time < 0) { stop(); } } |
どちらのメソッドも、copyRect()で親レイヤー(プライマリレイヤー)の画像の一部をコピーしていることに注意してください。drawText()で同じ座標に文字を描くと、以前の文字が残ったまま上書きされてしまうので、あらかじめ消しておく必要があるのです。
以下のトピックも参照してください。
|
リファレンス |
トピック |
サブトピック |
|
吉里吉里2リファレンス |
Layerクラス |
メソッドcopyRect()、drawText() |
■copyRect()、drawText()への引数(左上端の座標、幅、高さ)は、背景画像ファイルのサイズに合わせて調整してください。このスクリプトでは640×480ピクセルの画像ファイルを想定しています。□
メンバ変数rest_timeは、メソッドonTimer2()が呼び出されるたびに-1され、負になった時点でメソッドstop()を呼び出します。
メンバ変数pointを変更するのはゲームマスターオブジェクトではありません。後述のモグラオブジェクトが値を変更します。
モグラクラスのコンストラクタ、デストラクタを実装します。
■コンストラクタ引数のparentには、ゲームウィンドウのプライマリレイヤーではなく、ゲームマスターのforeが渡されます(ゲームマスタークラスのコンストラクタを参照)。モグラクラスにとっての親レイヤー、と言う意味です。□
|
// コンストラクタ function Mole(gm, window, parent) { super.Layer(window, parent); // スーパークラスのコンストラクタを呼び出し
master = gm;
// 効果音ファイルを読み込む hit_sound = new WaveSoundBuffer(window); hit_sound.open("hit.wav"); // ←拡張子もつけないとダメらしい
// マウスイベントを受け取る hitType = htMask; hitThreshold = 0;
// モグラの画像を読み込み、レイヤーのサイズを画像のそれと同じにする loadImages("mole"); setSizeToImageSize(); }
// デストラクタ function finalize() { invalidate hit_sound;
super.finalize(); // スーパークラスのデストラクタを呼び出し } |
デストラクタの説明は必要ないでしょうから、コンストラクタのみ説明します。
WaveSoundBufferオブジェクトを作り、効果音ファイルを読み込んでおく
モグラ自身の設定を行う:マウスイベントを拾うように設定し、モグラの画像を読み込む
以下のトピックも参照してください。
|
リファレンス |
トピック |
サブトピック |
|
吉里吉里2リファレンス |
WaveSoundBufferクラス |
コンストラクタ メソッドopen() |
|
Layerクラス |
プロパティhitType、hitThreshold |
モグラクラスのメソッドreset()、move()を実装します。
|
// パラメータを再設定 function reset() { visible = false; // 非表示にする
var interval = master.timer1.interval;
// 隠れている時間は1〜5秒 hidden_time = intrandom(1*1000\interval, 5*1000\interval);
// 出現している時間は0.5〜2秒 visible_time = intrandom(1\2*1000\interval, 2*1000\interval);
counter = -hidden_time; }
// モグラの画像を非表示したり表示したりする function move() { if (counter > visible_time) { // counterがtimeより大きい reset(); } else { if (counter == 0) { // counterが0なら、以降からモグラを表示 visible = true; } counter++; } } |
メソッドreset()では、隠れている時間と出現している時間を乱数で決定します。メソッドmove()では、モグラ(の画像)をどのタイミングで表示させるかを決定します。counterが負の間は画像を非表示に、0以上になったら画像を表示させるようにします。
■メンバ変数hidden_timeには、大して意味がありません。継承したときに使い道があればどうぞ と言う程度のものです。□
モグラクラスのメソッドonMouseDown()を実装します(スーパークラスLayerのメソッドonMouseDown()をオーバーライド)。引数buttonにはマウスのどのボタンを押したかの情報が渡されます。
|
// マウスイベントを処理 function onMouseDown(x, y, button, shift) { if (button == mbLeft && visible) { // モグラが表示中にマウス左ボタンがクリックされた hit_sound.play(); // 効果音を鳴らす master.point++; // 得点を加算 reset(); } } |
もし、モグラが表示中に、モグラ上をマウス左クリックしたら、効果音を鳴らし、得点を加算します。また、メソッドreset()を呼び出し、非表示状態から再開します。
以下のトピックも参照してください。
|
リファレンス |
トピック |
サブトピック |
|
吉里吉里2リファレンス |
WaveSoundBufferクラス |
メソッドplay() |
|
Layerクラス |
メソッドonMouseDown() |
完成したモグラ叩きゲームの全コードを示します。プロジェクトフォルダ名はgame、ファイル名はstartup.tjsとします。
|
// モグラ叩きゲーム
// Util.tjsからintrandom()のコードを拝借 function intrandom(min = 0, max = 0) { // min 以上 max 以下の整数の乱数を返す // 引数が一個だけの場合は 0 〜 その数までの整数を返す if(min>max) { min <-> max; } return int(Math.random() * (max-min+1)) + min; }
// モグラクラス class Mole extends Layer { var master; // ゲームマスター var hit_sound; // 叩かれたときの効果音 var counter; // イベントのカウンター(-hidden_time〜visible_timeの間で変化) var hidden_time; // 隠れている時間 var visible_time; // 出現している時間
// コンストラクタ function Mole(gm, window, parent) { super.Layer(window, parent); // スーパークラスのコンストラクタを呼び出し
master = gm;
// 効果音ファイルを読み込む hit_sound = new WaveSoundBuffer(window); hit_sound.open("hit.wav"); // ←拡張子もつけないとダメらしい
// マウスイベントを受け取る hitType = htMask; hitThreshold = 0;
// モグラの画像を読み込み、レイヤーのサイズを画像のそれと同じにする loadImages("mole"); setSizeToImageSize(); }
// デストラクタ function finalize() { invalidate hit_sound;
super.finalize(); // スーパークラスのデストラクタを呼び出し }
// パラメータを再設定 function reset() { visible = false; // 非表示にする
var interval = master.timer1.interval;
// 隠れている時間は1〜5秒 hidden_time = intrandom(1*1000\interval, 5*1000\interval);
// 出現している時間は0.5〜2秒 visible_time = intrandom(1\2*1000\interval, 2*1000\interval);
counter = -hidden_time; }
// モグラの画像を非表示したり表示したりする function move() { if (counter > visible_time) { // counterがtimeより大きい reset(); } else { if (counter == 0) { // counterが0なら、以降からモグラを表示 visible = true; } counter++; } }
// マウスイベントを処理 function onMouseDown(x, y, button, shift) { if (button == mbLeft && visible) { // モグラが表示中にマウス左ボタンがクリックされた hit_sound.play(); // 効果音を鳴らす master.point++; // 得点を加算 reset(); } } }
// ゲームマスタークラス(モグラオブジェクトの管理などを行う) class GemeMaster { var window; // ゲームウィンドウオブジェクト var parent; // 親レイヤー:(ゲームウィンドウの)プライマリレイヤー var fore; // 親レイヤーのコピー:表画面として使う var moles = []; // モグラオブジェクトの配列 var timer1; // タイマー1:モグラ用 var timer2; // タイマー2:残り時間用
var rest_time; // 残り時間 var point; // 得点
// コンストラクタ function GemeMaster(win, par) { window = win; parent = par;
// 親レイヤーのコピーを作る fore = new Layer(win, par); with (fore) { .visible = true; .assignImages(parent); .setSizeToImageSize();
// フォントの高さを30ピクセルに設定(残り時間、得点の文字に使う) .font.height = 30; }
// モグラオブジェクトを作成 moles.add(new Mole(this, window, fore)); moles.add(new Mole(this, window, fore)); moles.add(new Mole(this, window, fore));
// モグラの表示位置を設定 // (モグラの画像サイズによって調整してください) moles[0].top = 100; moles[0].left = 50; moles[1].top = 100; moles[1].left = 250; moles[2].top = 100; moles[2].left = 450;
// モグラ用タイマーオブジェクトを作成 timer1 = new Timer(onTimer1, ""); timer1.interval = 100; // タイマーイベントは100ミリ秒間隔で発生
// 残り時間用タイマーオブジェクトを作成 timer2 = new Timer(onTimer2, ""); timer2.interval = 1000; // タイマーイベントを1秒間隔で発生させる }
// デストラクタ function finalize() { // タイマーを無効化 invalidate timer2; invalidate timer1;
// モグラを無効化 for (var i = 0; i < moles.count; i++) { invalidate moles[i]; }
// 表画面を無効化 invalidate fore; }
// ゲームを開始 function start() { point = 0; rest_time = 20; timer1.enabled = timer2.enabled = true; }
// ゲームを終了 function stop() { timer1.enabled = timer2.enabled = false; }
// タイマーイベントを処理:モグラを動かす function onTimer1() { // モグラを動かす for (var i = 0; i < moles.count; i++) { moles[i].move(); }
// 得点を更新する with (fore) { .copyRect(500, 0, parent, 500, 0, 160, 30); .drawText(500, 0, "得点:"+point, 0x000000); } }
// タイマーイベントを処理:残り時間を更新する function onTimer2() { // 残り時間を更新する with (fore) { .copyRect(0, 0, parent, 0, 0, 160, 30); .drawText(0, 0, "残り時間:"+(rest_time--), 0x000000); }
if (rest_time < 0) { stop(); } } }
// ゲームウィンドウクラス class GameWindow extends Window { var file_menu; // ファイルメニュー var start_menu; // ゲーム開始メニュー var exit_menu; // 終了メニュー
var master; // ゲームマスター
// コンストラクタ function GameWindow() { super.Window(); // スーパークラスのコンストラクタを呼び出す
// [ファイル]、[ファイル‐ゲーム開始]、[ファイル‐閉じる]メニューを作成 menu.add(file_menu = new MenuItem(this, "ファイル(&F)")); file_menu.add(start_menu = new MenuItem(this, "ゲーム開始(&S)")); file_menu.add(exit_menu = new MenuItem(this, "閉じる(&C)"));
// プライマリレイヤーを作成 add(new Layer(this, null)); with (primaryLayer) { // 背景画像を読み込み、そのサイズに設定 .loadImages("stage"); .setSizeToImageSize(); }
// ゲームウィンドウをプライマリレイヤーのサイズに合わせる setInnerSize(primaryLayer.width, primaryLayer.height);
caption = "モグラ叩きゲーム"; // ゲームウィンドウのタイトルを設定 visible = true; // ゲームウィンドウを表示
// ゲームマスターオブジェクトを作成 master = new GemeMaster(this, primaryLayer); }
// デストラクタ function finalize() { // ゲームマスターを無効化 invalidate master;
// メニューを無効化 invalidate exit_menu; invalidate start_menu; invalidate file_menu;
super.finalize(); // スーパークラスのデストラクタを呼び出す }
// メニュー選択を処理するaction()メソッド function action(ev) { if (ev.type == "onClick") { switch (ev.target) { case start_menu: // [ゲーム開始]メニューを選択 master.start(); break; case exit_menu: // [閉じる]メニューを選択 close(); } } } }
// ゲームウィンドウオブジェクトを作成 var win = new GameWindow(); |