三日坊主日記その2
![]()
![]()
![]()
・ゲーム作成日記始動
1日目とりあえず、以前作ったゲームの習作(ダウンロードのページにある)から要らない部分を取って使う事にしました。
その時に使ったツリー状タスクのクラス(詳細は後程)を使うか使わないかちょっと迷いましたが、二人用も後々作るかもしれないという事で今回もそのクラスを使う事にしました。
PCはメモリが多いので、何も考えずにフィールド用の配列を色々と作る。連鎖数用やら繋がり判定用やら。とりあえずフィールド全体を埋めて表示できるようにする(画面全体に書くので比率がおかしいけど)。が、最初から4つ繋がっている部分がある。
現段階のスクリーンショットスタートと同時に勝手に消えるのはなぁ……。これならもっとブロックの色数増やして、それに伴いフィールドも広くした方が良いな。テンポ悪くなりそうだけど。
「選択して、移動して×移動する距離、選択解除する」ではテンポが悪いのでなんとかならないかと思っていたが、改善案を思い付く。「キーを押して選択、移動してキーを押して解除」ではなく、「キーを押してる間選択、移動してキーを離す」にすることでキーを押す回数が2回から1回に減る。これは最初にも考えてたけど、面倒くさいかなということでボツにしていた案。フィールドが広くなったからテンポの悪さを解消しないといけないので、この案を復活。
まず繋がりの判定から手を着ける。バグ出しのため、毎フレーム全てのブロックの繋がりをチェックするようにする。時間があれば後で最低限のチェックにしよう。かなりてこずると思われたが、アルゴリズム自体はそれほど難しくなかった。一発目でそれなりにちゃんと判定してくれる。でもそこからがなかなか進まない。いろいろ調べた結果、探索位置が二手に分かれる時の処理がうまくいってなかった。結構時間を食ったので急がないと。何せ三日なんだから(しつこい)。
そうだ、今の状態でFPSがいくつか見てみよう。フィールドは16×16ぐらいにしてウィンドウモードで実行。この時点で90fpsくらい行ってないと後々厳しいなぁ。期待と不安を抱きつつ実行。「74fps」。うーん、後が辛いかもしれないというかこんなもんだろうなというか……。1つのブロックに1回のDrawPrimitiveを呼んでるせいだということを祈ろう。
そういえば以前パーティクルのテストをした時、計算してDrawPrimitive×1000回と、1000個分計算してDrawIndexedPrimitive×1回を比べた結果、DrawPrimitiveの方が速かったです。うまく並列動作してたのかな?
そうなのかどうか検証のため、繋がり判定を一切しないと……「72fps」。おいおい、なぜ下がる?テストの状態(起動してるソフトや状況など)はさっきと同じなのに。こうすると並列動作がうまく行かないとか、キャッシュがうまく働かないとか、そういうのだろうか。それでも下がる事はないと思うけど……。
ついでに、処理する順番を反対にして、描画してから繋がり処理をすると……「68fps」。うわ、下がった。
うーん、謎だ。VTuneとか使ってみれば一発で原因がわかるかもしれないですけどね。
あ、そうだ、周りのブロックの色を見て、周りには無い色になる確率を上げればいいんだ。いきなり話が戻りますけど、最初から4つ繋がるという問題の話です。やってみた結果、毎回2、3つ繋がってたのが、4回に1回まで減りました。これくらいならさほど問題にはならないな。
繋がり判定ができたから、次は消す処理だな。落ちるのはまだ後。
む、ここで後から追加できるシステムが意外に邪魔になる。消えてる途中のと追加されたのを同じとしなきゃいけないんだよなぁ。消えはじめたらくっついた部分は離れない、という性質を利用して、またフィールド用配列1つ増やすか。なんて短絡的。
でもこっちの方が早く作れるし計算量も減るし。問題なのはメモリ関連だけど、どうせ最大16×16のフィールドだから(増やせるようにはしてるけど)、フィールド用配列1つ増やした所で食うメモリは256byte。PCの潤沢なメモリ容量に比べれば塵みたいなものさー。全然リスク無し。と、自分に言い聞かせる。
ということで、フィールド用配列に新しいお友達が加わりました。
消えるまでの情報を保持するのがまた面倒だなぁ。アクティブ連鎖に加えて追加できるシステムだからある程度は覚悟してたけど。単方向リストとそれ用のインデックスを使うか。何個くっつくかも何個所同時に消去待機状態になるかもその時によって変わるし、追加によって2つの個所の消去待機のブロックが1つになったりして複雑だから、ちょっとしたバグでもデバッグしにくそう。時間あったら効率も考えたい所ですね。
そうだ、消去待機状態で下のブロックが先に消えたらどうしよう。配列の使い方から言って、そのまま落ちるのは難しい。どうしても「くっついてて消去待機状態のブロックでも一度それが解ける」としないと難しいな。新たなテクニック……でごまかせるだろうか。厳しいな。「消えはじめたら(消去待機状態なら)落ちない」とした方が、「下を先に消したら追加してる最中のブロックがバラバラになった」という嫌な状況にならないからそうするか。面倒だけど。
やっぱりこれは三日じゃ無理ですなぁ。とりあえずくっつけて消せる所までは行きたいですね(弱気)。やってみてわかりましたけど、ぷよぷよ作る方が3、4倍楽ですね……。ぷよぷよだったらもうぷよが落ちる処理まで行ってると思います。まぁちょっとしたプログラムの勉強になっていいですけどねぇ。
あ……後から追加するのをテストするにはカーソルで移動できるようにしないと無理だなぁ。とりあえず追加できるようにはしておいて、デバッグはカーソル移動を先に作ってからにしよう。
2日目
昨日は以前作ったゲームの習作の要らない部分を取るだけで時間を食ってしまったが、今日は最初から最後までそんな作業みたいなことはしなくていい事を祈る。
どこが消去待機状態かを把握するための単方向リストとそれ用のインデックスの処理を作る。誤動作も無くすんなり成功。コードの量の割には一発で動いてくれると嬉しいですね。追加の部分も作るが、カーソルでブロック入れ替えがまだできないので確認できない。じゃあ次はカーソルか。カーソルで移動させているブロックの扱いはどうしようかな……。
カーソル移動の前に、リストの中身(配列の中身)を見てみる。おかしい部分がちらほらと。一発で動いたは良いが、それはバグが表だって出てこないだけだったようだ。うりゃ。2つほどバグを潰す。
バグの種類には、「プログラム設計上の見落とし・配慮の不足」や「勘違い・ケアレスミス・書き間違い」などがありますが、今回は後者は気を付けている(配列の構造をわかりやすくするなど、ミスの発生がなるべく少なくなる方法をとっている)ので、そういうバグはまだ1、2個ですが、システムが複雑ゆえに前者の配慮不足のバグが判明しただけでも5つほど出ています。今は特にバグは確認できませんが。まぁ時間かければ必ず見つけられる種類のバグなのでまだ良いですけど、何せ時間が無い。
バグは、早期発見早期治療。「今は問題無いからこれのデバッグは後回しー」という結果、後の方でバグ同士手を組んで出てきたら……あぁ嫌だ。
カーソル移動の処理を作りはじめる。空白の部分(ブロックがない部分)をカーソルで選択できるようにするかどうかで迷う。ちょっとだけ奥が深くなるかもしれないから、一応できるようにしておくか。バグっぽく見えるけど。
カーソル移動処理完成(「選択しながら移動」はまだ)。動き方は、「押してる間は移動、離したら止まる」「移動には少しの時間がかかる(1/3秒くらい)」。テトリスやぷよぷよだと「押すと1マスだけ移動、そのまま押し続けると高速移動」「移動に時間はほとんど変わらない」というキーボードのキーを押したような感じだけど、今回はそれとは違うようにしたかった。
押すと1マス移動だと連打勝負になるし、押しっぱなしで高速移動だと目標の所を通り過ぎる可能性もある。だから、最初から中速移動で済ますことにしました。今回のような動かし方は、慣れると最小限の移動で済ますことができるようになるし、連鎖を組むのにも慣れればカーソル移動用のキーを離す事が無い移動の仕方もできる。それで奥が深くなるかな、と。
まず最初に、押したら移動元から移動先まで等速でカーソルを移動させてみた。こうすると移動し終わるのと移動しはじめるタイミングがわかりにくいために移動しにくい。次に移動元を0、移動先を1とした数値を2乗して、初めは速く、移動先に行くにつれて遅くなるようにした。移動しはじめはわかりやすいが、移動し終わるタイミングがわかりにくいために先行入力をしてしまう。今度はこの2つの移動速度を足して0.5を掛けて(2で割って)みた。お、これなら最初の2つの移動速度よりも移動しやすい。あとは音を付ければ大丈夫だろう。
そうそう、ユーザーインターフェースを考える時、視線の移動を少なくさせる方法として音が有効だと最近よく思います。音は視線とは別に認識できますから。あと「これ以上は無駄な操作」を音で表現して伝えると(例えば端近くまでスクロールさせた時音が変わると)、反射的に操作を止めさせることができるので、これもデジタルな十字キーによる操作には有効かと。
消去待機状態は単方向リストで管理しやすくなったけど、今度は落ちている途中のブロックの処理が結構面倒だという事が判明。落ちるブロック同士ぶつかる処理とか、カーソルが移動したい所に落下中のブロックがあると移動できない処理とか、落下しはじめたブロックの上のブロックも落下させる処理とか……。でも、それを超えるときっと基本システム完成のゴールが見えるはず。
落下中のブロックの扱いをなんとか解決。今回のプログラムの設計で、今のところ一番悩みました。一見フィールドの扱いの設計ミスに見える所があったので「しまった」とも思いましたが、「〜は起こり得ない」という条件を色々と探して考えた結果、設計はうまく行っていました。……自分にしかわからない解説ですね。とにかく、最初の方の設計ミスのせいで、後になってどうにもこうにも進められなくなるほど嫌な事はないですね。今までの苦労が水の泡になるので。
あとは何も考えずにコーディング。ということでカーソルによるブロック移動完成。テスト。一発で動く。またバグが潜んでるとも限らないですけど。同時に消去待機状態のブロックに追加する処理もうまく行く事が判明。残された大きな部分は後2つ。「消去待機状態から消去へ」と「ブロック落下」。その前に「2個所の消去待機状態のブロックを1つに繋ぐ」という時の処理を作らないと。いろいろできる分いろいろ作らないと。
最上段に空白を作ったら、その空白同士がくっついて消去待機状態になってしまった。これほど原因がわかりやすいバグも久しぶり。
「2個所の消去待機状態のブロックを1つに繋ぐ」、これといった問題も無く完成。とうとうブロックを消去する時が来た……。この分なら今日か明日の午前中に基本システムは完成だな。これなら文字だけのタイトルと得点付けて三日で完成か?だったらいいなぁ。現時点では微妙ですね。得点がまた手間かかりそう。
そういえば今日はまだスクリーンショットがありませんが、これは全然見た目が変わってないためです。昨日のスクリーンショットと違うのはカーソルがあることぐらい。完成する時は勿論ウィンドウいっぱいにブロックを書く訳ではありませんが(それはそれでいいかも)、今はとりあえず画面いっぱいにブロック表示。
うーむ、こうしてどこにも行かずテレビも見ないでプログラムばっかりしてると、今日が何曜日なのか全然実感が湧かない。頭では土曜日だとわかっているのに、土曜日という感覚が無いなぁ。
消える処理完成。全然時間がかからなかった。「2個所の消去待機状態のブロックを1つに繋ぐ」という処理の方が時間かかってる。しかし、バグ発見。消える処理のおかげでカーソルによるブロックの移動で新たなバグを発見できた。おぅ、「これでも大丈夫だろうけど後でちゃんとしておくか」と思ってた所だ。こうして今バグとして出てこなかったら忘れてたかも……。
最後。「ブロック落下」。これができれば晴れて基本システム完成!だけど、このブロック落下処理が一番複雑で面倒でバグが出そうで難しい……。一発で成功する確率は2%くらいだろう。デバッグに時間を費やす事必至。がんばるぞー。
いやー、予想通り下にブロックがあろうがボトボト落ちまくりです。なんでですかね。それがわかってれば直してますが。
落ちまくりは直したが、まだ落下するブロック同士がぶつかった時と、消えたブロックの上にブロックがあった時のそのブロックの落下がうまく行きません。カーソルで入れ替えてからの落下はうまく行くようになったんですが。これが直れば基本システム完成と言ってもいいと思うんですけど、あと少しが……。
やった、落下するブロック同士がぶつかる処理が直せた!あとは消えたブロックの上のブロックの落下だけだ……。今日中にこれを終わらせてから寝たいなぁ。と、思ったらすぐに直せた。ただの書き間違い……。確かにここはごちゃごちゃしてたしカット&ペースト使いまくった場所だからなー。とにかく、2日で基本システム完成。動かして繋げて消せます。
明日は得点・連鎖数判定、時間があればタイトルも。そこまでいけばゲームとして成り立つので一応成功なんですが、そううまくはいかないんでしょうねぇ。そうだ、ブロックが上から降ってきて補充できるようにしないと。
寝る前に連鎖数判定を少しやっておく。消去待機状態を把握している単方向リストのインデックスに、連鎖数も把握させておかなければいけない事に気付く。今頃インデックスの構造体の変数1つ増やして大丈夫か?
今日は、面倒な事はあったけど作業のようなことはしなくてよかったので、結構楽しんでやってました。明日は再び作業のような事をする必要が出てくるでしょうが。
3日目
2連鎖目からの追加の問題を忘れていた。こんな問題でした。
4つで消えるとする。赤と青を消し、黄色が落ちていくと左で4つ揃った時点で追加できなくなり、右の黄色が落ちてもつながらない決めていた「2連鎖目とそれ以降は入れ替えて追加はできないが、落ちてきて追加は可能」は、それなりに繋がり判定の修正が必要。ブロックが2連鎖目の場合、くっついてるブロックが1連鎖目なら無視するという感じにして解決できると思うが、ちょっと面倒ですね。まぁ今回は「面倒かなー」と思ってやってみたら意外と簡単だったということが多いから、これもなんとかできるでしょう。
予想に反して、かなり面倒なことが判明。時間ないから今の所はとりあえず「2連鎖目からは追加できる時間が短いが、追加はできる」と仕様変更。でも2連鎖目も追加できるというのは、それはそれで良さそう。
テストプレイらしきことをデバッグ目的以外ではまだやっていないが、試しに連鎖を組んでみる。……あっ、連鎖組んでる途中に勝手に消えて、連鎖組んでるブロックの並びがバラバラに。予想はしていたけどこれって結構頭に来ますね。後でブロック選択のキーを押してる間はその選択しているブロックでは消せないようにしておくか。
ブロック移動(カーソルでブロックを移動)の複合バグ発見。ブロック移動のバグが潜んでいて、ブロック落下とブロック移動を同時にした時に表面化してきました。まぁ簡単に直ったから良いですけど。ついでにもう一つのバグを潰す。バグというほどの物ではないが。
これでもうバグらしいものは潰した。早速連鎖数判定に取り掛かるか。
ここでゲームデザインな話。落ち物で、テトリスなら4つのブロック、ぷよぷよでは2つ・様々な色のぷよが落ちてきますけど、今回のような入れ替えが基本のシステムを作ってみてその組で落ちてくるシステムの有用さがよーくわかりました。やっぱり、直感的に操作できるんですよね。入れ替えだと、慣れるまでは偶然1個直感的1個移動ですが、組で落ちてくるなら全てを直感的に移動させる事ができます。
あと時間の経過を伝える事で時間に追われる感じの緊張感を与える、というアプローチが違いますね。組で落ちてくる場合は操作しなくてもだんだん落ちていくので、その組のブロック自体が時間の経過を伝えます。パネルでポン(入れ替えが基本のシステム)だと段々ブロックがせりあがっていくのが時間の経過を伝えてますね。今回のゲームは、現段階では操作をしないと時間が止まります。ブロックが落ちるくらい。ブロックが上から落ちてきて補充される処理を作った時、これが解消される事を祈る。
入れ替えに加えて「組で落ちてくるモード」も作るか?連鎖中でもブロックが組で落ちてくるという。結構面白そう……。できないことはないし。
連鎖数判定完成ー。最初の頃から準備してたので楽でした。
「2連鎖目とそれ以降は入れ替えて追加はできないが、落ちてきて追加は可能」を実現する方法を思い付く。やっぱりこうしないと連鎖のつもりで何故か連鎖になってくれない時があるので。まぁそれなりに手間はかかりますけど仕方が無い。
で、昼飯食って(今日は一食で終わり)コーディング。バグった。時間がかかったが直った。あぁ、時間食っってしまった。これじゃあ得点までできるかどうか。次に作るのは……あ、次が得点の番か。いや、それよりもブロックの補充の方を先にやっておくか。これと得点、そしてタイトルさえできればゲームとして完成か!?やった、三日でできるかも!
連鎖数判定でバグ発見。修正にちょっと時間がかかる。あとブロック補充のためにビューポート関連の部分をいじる必要が出たため、ついでにウィンドウのリサイズに対応。ちょっとカット&ペーストしただけだけど。さて、やっとブロック補充の処理を作れる。
10分ほど睡魔に襲われる。座ってて眠りそうになる。一思いに寝たい……でもプログラムする。
睡魔が去る。とても頭がすっきりする。居眠りすると頭が冴えますよね。
ブロック補充処理完成、別に難しい所も大きなバグも無く。落ちてくるまでの状況の表示は某ゲームを意識……してはいませんが、最低限の情報の表示をした結果ちょっと似ました。次は、得点処理の前に、先にブロック追加による消去待機時間の延長を作ることにします。まだやってませんでした。
消去待機時間延長処理完成セリ。1行変えただけ。でもこれだけでぐっとゲームらしくなりました。くっつけるたびに制限時間が増える量が少なくなるという仕様を考えてましたが、「くっつけると制限時間が元に戻る」としました。よくよく考えると、くっつけるたびに、くっつけることができるブロックがどんどん近くに無くなっていくのでそれなら「元に戻る」でいいな、と。
テストプレイしてもバグらしい挙動はしないので(2、3個は潜んでると思うが)、得点の処理を作る……前に、得点を表示する場所と処理が無いので、先にタスクの種類を増やす。今まではアクションパズルなタスクだけだったが、そのアクションパズルなタスクを管理する一人用モードタスク(後でいろいろ派生させる予定)とタイトル用タスクを追加するつもり。タイトルは文字書いて終わりにしよう。
タスクは作った。次にタイトルの文字。白地に黒にするか。タイトルは……そうそう「でざいあぶる」だ(忘れてた)。その下に「Push Button」という文字を表示して完成。あ、ボタンじゃないから「Press Key」だ。
やってみて思う。「2連鎖目とそれ以降は入れ替えて追加はできないが、落ちてきて追加は可能」よりも、「2連鎖目とそれ以降も追加可能」の方がわかりやすい。ので、そうする。苦労が無駄になるけど、「苦労したからそのままにする」よりも「ゲームをとっつきやすくする」という方が良いのは当然なので修正。あーあ。
ブロックの補充ができないほど詰まったら、ゲームオーバーにする処理を作成。詰まった時に「ゲームオーバー」という文字を出すだけ。後でちゃんとします。
最後の、得点計算を作った。暫定的な得点計算だけど。4つ以上くっついたら個数×連鎖数×100点、追加したら連鎖数に関わらず80点。
タイトルとゲームオーバーができて、得点も計算されるようになったので、これで目標の「ゲームとして成り立つ所」まで行きました。というわけで、「三日で落ち物を作る」は「成功したと言えば成功した」という結論でどうでしょう。
かなり久しぶりに肩が凝ってます。そのせいで後頭部の下辺りが頭痛みたいにちょっと痛いというか。この症状になったのは何年ぶりだろう。三日間ただただプログラミングしてたからなぁ。そのためちょっと今は気力が出ないので、このページとドキュメント書きは予告通り明日やります。
現段階のスクリーンショット
ドキュメント無いけどダウンロード:dr010.lzh(62KB)
操作方法:テンキーと[Z]キーと[X]キー使ってください
・補足
1日目で書いた「ツリー状タスクのクラス」の説明を。
ダウンロードのページに置いてあるDirect3DIMの習作を作る時に一緒に作った物で、以下のような構造になってます。
今回のゲームでの使い方なんだかよくわかりませんが、この四角1つを1つのタスクとして、ルートからたどって実行しています。最初はルートが生成され、ルートがタイトルを子のタスクとして作ってすぐ待機状態(呼ばれても子のタスクを呼ぶ以外何もしない状態)になり、タイトルのタスクがキーを押したという情報を受け取った時エンドレスモード用タスクを子として作った後タイトルが待機状態になります。そして、エンドレスモードはアクションパズル用タスク(ブロックを消したりカーソルを移動させたりするタスク)を生成して、それからは自分とその子のタスクを実行します。
四人用モードがあったとしたら、こうなります。作る予定はないですけど。
”四人用モード”の子のタスクは4つテクスチャの管理とかモードの選択とかいろいろと面倒になったので、こういうタスク単位にして管理するようにしました。タスクの生成や削除などはそのタスクのクラスにやってもらうようにしています。
なんか、説明は短く済ますつもりが長くなってしまいました。
・ゲーム作成日記後書き
ここの部分を書いてるのは月曜日です(金曜から日曜がプログラミング)。
いやしかし、ほんとに三日でできるとは思ってなかったです。と言ってももちろんこれが完成ではありません。これは無理矢理ゲームとして成立する部分まで持っていった段階であって、面白くなるかどうかはこれからです。
今の時点で、まだやってない処理や改善点はいろいろとあります。Direct3DIM専用のため3Dアクセラレータが必要、音が無い、演出が無い、連鎖数がわからない、水色と緑の色調が似ている、背景が無い、地味、エンドレス(延々とやる)モードしかない、オプション機能が無い、ウィンドウモードだと60fpsを超えてしまう、対戦ができない、得点システムが単純、タイトルが貧相、ゲームオーバー表示が貧相、ゲームオーバー判定が厳しい、などなど……。自分が忘れないようにここに書いておきます。
最初は三日でやめるつもりでしたが、3D関連のネタがかなり少なかったりこれからやるネタとこのゲームでこれからやる事で重複する部分があったり、「このゲーム、そこらのシェアウェアの落ち物より面白くできるかもしれん」などと思ってたりするので、次の回もこのゲームの話で行きます。
ということで次回は「地味なゲームを派手にする」をテーマにします。派手さと効果音でどれだけこのゲームが面白くなるか?あ、ドキュメント書きしてないや。
同時に募集。このゲームのタイトル(今は「でざいあぶる」)で何か良いのがあれば掲示板に書き込んでください。あと、ブロックや壁、背景の絵、ブロックに使うテクスチャ、効果音などあれば、過去に使った物でも良いのでください。まずは掲示板に。絵心があれば自分でもできるんですけどね……。
![]()
![]()
![]()
written by Y.Ohde e-mail : oode@alles.or.jp