ゲームを作ってみよう 〜その10〜
うっす。
さて、文章を書く際に、いつ読んで貰えるのかわからない物って挨拶が書きづらいですよね。
こんばんわって書いて朝に読まれちゃったら目もあてられない。
そんな時便利なのが「うっす」。いつの間にかNIGOROではうっすを使うことが増えてしまったduplexです。
さてはて、関係無い話を書いても仕方が無いので今回のお題ですが、前回作ってみて何かが違うと思った所はありませんでしたか?
って弾がまっすぐしか飛ばないと言うのは無しよ。
わからない人は最初の頃に公開したBouceShotの企画書を見てみましょう。
よーく見てください、画面内に弾が2発と制限が指定されてますね。
というわけで今回は弾数の制御をやります。
弾が画面内に2発という事は、現在の弾数をどこかで管理してやらなくては行けません。
まぁそれは今回やるとして、管理してる場合それ以上の発射要求をユーザーから求められた場合はどこかに弾はもう出せないと言う処理を入れなくてはいかんのです。
その場合、ぱっと思いつくやり方は何がある考えて見ましょう。
- 自機(Shipクラス)で弾を出す処理を飛ばす。
- 弾(Myshotクラス)の方で限界を超えてた場合は自動で消える。
- 第3の勢力に弾を消滅させる。
等が思いつきます。ほかのやり方とか考えてみると良いかもしれませんが、とりあえず今回思いついたのはこれだけ。
こういう風に手法がいくつも出てくるのは当然なんですが、それじゃ一体どの方法でやればいいのか?
そんな時は利点と欠点をさらさらと出してみて考えます、ワシは。
1の場合
利点
- 無駄な処理(いらない弾タスクの生成)が無い。
- 処理の流れがわかり易い
- Myshotクラスに何も変更を入れる必要が無い。
欠点
- 弾数管理のタスクとのやり取りが必要になる
2の場合
利点
- Shipクラスに何も変更を入れる必要が無い。
欠点
- いらない処理(最初のフレームで弾数をチェックして限界を超えてたら消える)が出る
- 弾数管理のタスクとのやり取りが必要になる
3の場合
利点
- 弾数制御の処理がクラス内で行われるので、弾数が可変になっても変更を入れやすい。
欠点
- そもそも、このタスクだけを見ても何をやってるのかわからない。
- Myshotクラスを管理する必要が出てくる。
ざっとこんな感じです。
まだまだ利点や欠点はあると思いますが、あまり深く考えても仕方ないので先に進みます。
これを見ると1が利点が多くて作りやすそうです。
ただここで注意したいのが、弾数管理をShip内でやっちゃおうとする事。
これは良くやっちゃうんですが、大抵の場合こういうのは後で「これ変更よろしく」とか来ちゃいます。
てか来ます。
それとプレイヤーキャラクターのプログラムは長くなりがちで、後半てか終盤あたりに変更が入ると泣きを見る事が必死なんです。
そんなわけで、弾を管理する場所は別に作成し、後半で変更が入ったとしてもその別の場所をいじるようにする方が得策ってもんです。
実例でも書かないとわかり辛い所ではありますが、長くなるから却下。
まぁ小さく部品毎に分けた方が「泣かないで済む」と憶えてください。
よく言うオブジェクト指向なんでしょうが、理屈なんてどうでも良いんです、要は自分が楽するためです。
後は、誰が弾管理タスクに弾の増減を通知するかと言う問題があります。
Shipクラスだと弾が発射された事は分かりますが弾が消えた事は分かりません。
Myshotクラスであれば、発射された時と消える時が分かります。
他のクラスは何もわかりません。となればもう考える必要も無く、弾の増減の通知はMyshotクラスで行うのがベストだと思われます。
さらっとおさらいで、ここで決まった事を箇条書きにしてみます。
- 弾数管理タスクを作る。
- 弾の発射制限はShipクラスで行う。
- 弾数管理タスクへの増減の通知はMyshotクラスで行う。
それでは弾数管理タスクを作ります。
- import nigoro.lib.duplex.Task.*;
- class source.task.Maxshot implements Task2{
- private var max_now:Number; //現在の最大弾数
- private var now_shot:Number; //現在画面上に出ている弾数
- public function Maxshot(max:Number)
- {
- max_now = max; //このタスク生成時の最大弾数を決める
- now_shot = 0; //現在の弾数初期化
- }
- //タスクシステムに登録した際に呼び出される。
- public function init(no:Number, tc:TaskCenter2):Void{
- tc.regstData(”maxtask”,”no”,no); //データベースに自分の番号を入れる
- }
- //タスクシステムからの常時の呼び出しはここ。返り値がtrueだとそのままだが、falseを返すとタスクの削除と
- なる
- public function run(tc:TaskCenter2):Boolean{
- return true;
- }
- //タスクを削除する際に必ず呼ばれる。なんか処理したい事を入れておけ
- public function finalize(tc:TaskCenter2):Void{
- tc.elaseData(”maxtask”);
- }
- //このタスクとやり取りしたい場合これを使え
- public function talkTask(mess:Array, tc:TaskCenter2):Array{
- switch(mess[0])
- {
- case 0: //弾が発射された時に呼び出す
- now_shot++;
- break;
- case 1: //弾が消える時に呼び出す
- now_shot–;
- break;
- case 2: //新しく弾を発射出来るかのboolean値を返す
- mess[0] = (now_shot < max_now);
- break;
- }
- return mess;
- }
- //非常に特殊処理、通常は使わない
- public function frun(tc:TaskCenter2):Boolean{
- return true;
- }
- }
クラス名はMaxshotとしました、この辺にワシのボギャブラの無さを感じますが、気にしちゃいけない。
よくサミエルさんに「師匠~そのつづり間違ってますよ~」と言われるワシです。
簡単な英単語の組み合わせしか出来ないのです。
それと今回から行番号を付けてみました。
今回からは嫌がらせではありません、別の「大人の事情」です。
今までと同じ様な所は説明しません、あい変わらすワシの作ったタスクシステムを使ったタスクです。
8行目のコンストラクタを見てください。
引数が指定してあり、その引数で最大数を決めるように作ってみました。この場合このクラスを生成する場所で指定する必要があります。
16行目に今まで使った事が無かった命令が出ています。
この命令はTaskCenter2 が管理するMiniDBと言うクラスに「maxshot」と言うカテゴリを作り、その中にさらに「no」と言うタグを付けた配列を作り、そしてその中にタスクシステムが通知した自分の管理番号を入れると言う物です。
そして対になるのが26行目でして、こいつはMiniDBからmaxshotと言うカテゴリとその以下に連なるタグの中身をすべて消去すると言う命令です。
今回はnoと言うタグしかないのでそれだけの消去って事になります。
これは前から出てるのですが49行目にfrunと言うメソッドがあります。
これは以前説明した頃には無かった物でして、以前の説明の後に追加された命令です。
実装されたTask2に定義がしてあるので必ず書かなくてはいけないメソッドなのですが、不要なので何もせずにreturn true;と定義しています。
こいつは他のクラスにも、ワシのタスクシステムを使うものであれば必ず書かれてるので無視してください。
30行の talkTaskメソッドを説明する前に次はShipの改造に入ります。
- if(mouse_f){
- //弾を発射してよいかMaxshotタスクに問い合わせる
- if(tc.acssesTask(tc.checkData(”maxtask”,”no”),[2])[0]){
- //弾を発射
- tc.setTask(new Myshot(dmc,mc._x,mc._y));
- }
- }
Ship.as内にあったマウスが押されたら弾を出す部分の修正変更です。
2行めで使われているacssesTaskメソッドは(タスク番号、配列)とやることによって、タスクシステムが管理する番号のタスクの talkTaskに指定の配列を送ると言うものです。また返り値として配列が返ってきます。
次にcheckDataメソッドは(MiniDB内のカテゴリ、その中のタグ)を入れることでデータを取り出せます。
これは(”maxtask”,”no”)となってます、この文字は上で説明したMaxshotクラス内の16行目で設定してますね。つまりMaxshotクラスのタスク番号が入っているわけです。
acssesTaskの第一引数はこれでわかりました、次の[2]って何でしょうか?
これ、1っこだけの配列なんです。別のやり方で書いてみれば
var tmp:Array = new Array(1);
tmp[0] = 2;
のtmpと同じ物になります。
では、この戻り値はどうなるかというとMaxshotクラスの30行からを見てみましょう。
配列の先頭(0番)には2が入ってtalkTaskが呼び出されます。
talkTaskメソッド内では配列の0番をみてswitch文で振り分けを行ってますので、40行目が処理される事になります。
41行目では配列の0番にBoolean値を入れています。
さっきまで数値が入っていたのに、今度はBoolean値を入れるってのも気持ち悪い所がありますが、出来るんだから仕方ない。
とまぁ、その返り値がShipクラス側の3行目に戻ってきてるわけです。分かりにくいかもしれませんが
tc.acssesTask(tc.checkData(”maxtask”,”no”),[2])
この部分が配列名と同等になります。
そして、先ほどのMaxshotクラスから返された配列は0番に目的のデータが入っているので
tc.acssesTask(tc.checkData(”maxtask”,”no”),[2])[0]
と、お尻に配列番号を付けるってわけです。
これでif文がBoolean値を見ている事がわかりました。
後はtrueが返ってくれば弾を発射し、falseであればスルーする分けです。
次にMyshotに、弾数の増減を通知する部分を付け足します。
- //タスクシステムに登録した際に呼び出される。
- public function init(no:Number, tc:TaskCenter2):Void{
- tc.acssesTask(tc.checkData(”maxtask”,”no”),[0]); //Maxshotタスクに、画面上に弾が増えた事を伝える。
- }
まずはinitメソッドに一文を追加です。
initはこのタスクが生成された時に呼び出されるのでMaxshotタスクに弾が増えた事を通知します。
そのための処理はShipで説明した方法と同じなので割愛。
次にこのタスクが消える際に、必ずfinalizeメソッドが呼び出されるのでそこに弾が減った事を通知する物を追加します。
- //タスクを削除する際に必ず呼ばれる。なんか処理したい事を入れておけ
- public function finalize(tc:TaskCenter2):Void{
- tc.acssesTask(tc.checkData(”maxtask”,”no”),[1]); //Maxshotタスクに、画面上から弾が減った事を伝える。
- mc.removeMovieClip();
- }
4行目がそうですね。こいつも何をやっているのかもうわかりますよね?
さてと、最後になりますが、そもそもMaxshotを生成する場所はどこになるか?という問題が出ます。
今現在存在するクラスは
- bouce
- Maxshot
- Myshot
- Play
- Ship
の5つ、このうちMaxshotは自分自身なんで除外して、残りの4つの内のどれでMaxshotを生成すれば良いのか。
まずMyshot、こいつが生成するとなると、弾が出るたびに弾管理タスクが生成されます。
どう考えても変ですよね。
次にShip、自機なら良さげですが後々自機は死ぬと言う現象が入ります。
死んだ場合、今の自機タスクは破棄され新しくタスクが生成されます。
と言うことはどうもこいつでも不味そうです。
次はBounce、こいつはゲーム中、タイトル、などの大まかにシーンを管理するためにいます。
一応次々生成されたり破棄されたりするわけではないので、条件的にはMaxshotを生成する事も可能ではありますが、ゲーム中の1タスクを生成するには不適切ではあります。
と言うわけでPlayが残りました。
というかPlayはそもそもゲーム中のタスクを管理するために作ったタスクですので、最初からPlay以外あり得ません。
これが分かっていた人はもうそろそろ自分でゲームを作れそうです。
ではPlayクラスにMaxshotタスクを生成する行を追加しましょうか。
- //タスクシステムに登録した際に呼び出される。
- public function init(no:Number, tc:TaskCenter2):Void{
- tc.setTask(new Maxshot(2)); //最大同時弾数を2に設定して弾管理タスクを生成
- tc.setTask(new Ship(mmc)); //自機を生成
- }
Shipを生成していた場所の一行上にMaxshotを生成する一文を追加しました。
この順番は少しだけ意味があります。
このタスクシステムは登録された順番で処理されるのですが、Shipは弾を発射する際にMiniDBにmaxshotのnoのデータを参照します。
ですが、そのデータを登録しているのはMaxshotです。
もし、Shipが先に生成された場合、存在しないデータを参照する可能性があります。
そんなわけでShipの生成が後になるわけです。
とは言っても、実の所この場合はどっちが先でも問題は起こりえません。
ただ、こういった依存関係はどんなに減らしても多少は出てくる物なので、いつも頭の隅に置いて処理を組み込まないと重いもしないBugに遭遇してしまうもんです。
日々訓練、これに尽きます。
と言うわけで今回はこれでおしまいです。完成品はこんな感じになります。
次回は弾に角度を付けたいと思います。
関連ページ
- 第1回『ゲーム製作の流れ、一から見せます』
- 第2回『企画内容を吟味しよう』
- 第3回『仕様を詰めておこう』
- 第4回『では、仕様書をまとめましょうか』
- 第5回『ゲーム制作のためのツールを作る。・・・・いやじゃ!』
- 第6回『外部ファイルを読み込ませてみよう』
- 第7回『動く物を作ろう』
- 第8回『グラフィックデザインを考えるのです』
- 第9回『弾が出ます』
ここまで出てきた資料
トラックバック URL :





お疲れ様です!
内容にまったく関係ない話で申し訳ないんですが・・・。
ttp://wonderfl.kayac.com/
ブラウザ上で、ActionScriptを書いて、サーバサイドでコンパイル・実行できるワンダーフルなサイトが有るみたいです。
経堂の目的にもマッチしているかな、と言うことで。お暇があれば~。
コメント by せっき~ — 2008/12/22 月曜日 @ 23:31:40
第38行は、バグがあるでしょう。
now_shot–;
以下のは正しいでしょう。
now_shot–;
コメント by Robert — 2010/6/5 土曜日 @ 12:34:55