目次

BBD,日誌

書き換え

tank → characterなど。

キャラ選択

キャラによって銃撃の特性が異なる。爆風がないのもある。 まずは、ポーズメニューからキャラ選択をするようにしたい。いきなりスタート画面から選ぶのもいいのだが、デバッグが面倒になりそうな気がしたので。

カーソル移動

ボーズでの選択画面を作る。 カーソル移動。ふだん直感的に使ってるが、少しやるのも大変だ。

[-1, 2, 2] というようにカーソルの横位置を保持するようにした。

現在yが異なっても、保持しているものを表示するようにしたい。

決定ボタン

あとは決定ボタンでキャラと、難易度を選択して変数に書き込むようにすればOKだ。 でも肝心のほうがまだなので、そっちをやろう。 powerupsが参考になるとは思う。 基本的にtankを継承すればOK、オーバーライドしていく…画像、射撃の効果、移動速度…など。

キャラ、武器

キャラが変われば、その下にある多くが変わる。…bullet, explode、立ち絵…。 ほとんどは武器関連だ。武器によって特定の組み合わせを作る。ピストル、グレネード、刀。 要するに1つ層を追加するということだ。

現在は、characterが直付けされているから、それに1層追加するという形になる?

弾丸数

銃ごとに異なる弾丸数を設定する。

分割

分割の順番。カンタンそうなのからやる。

まずメインになる、GameObjectを継承するクラスを作る。 initializeで、super(object_pool)でobject_poolを渡してGameObjectのinitializeを読み込んでから、下位のcomponentのインスタンスを作る。ExplosionGraphics.new(self)とか。たいてい自分のインスタンスを渡すことになる。

で、それからファイルを作る。分割してコピペしていく。

順番

リファクタリングの手順が結果がメインで載っていてわけわからなくなったので本をもう一度みて再現する。 紙幅のせいで一度にすべてをリファクタリングする手順で書かれていて確認できない。なのでひとつひとつ動かしながら確認できるように書いていく。 追うのが大変だったが、やっと理解できた。自分でやってみるのがいやおうなしに考えることになって一番はやい。

チャプター5のリファクタリング(component)は結局のところ、play_stateで生成したobject_poolを綿々と送り出していく作業にほかならない。心臓のようなものだ。それをどうやるかっていう手段で、継承…initialize,super()をうまく使って最小限でやるというわけだ。インスタンス生成にobject_poolが必要になり、それをinitializeで密かにインスタンス変数にする。それを繰り返して送り出す。そういう影、繰り返すつまらないことをやらせるために継承元を作る。

そして、たくさん入れたobject_poolで一気にupdateやらdrawする?。すごい。

Componentの働きがよくわからない。componentを継承したやつ(bullet_physicsとか)は、initializeでgame_objectを受け取り、objectに代入して使えるようにする。呼び出し元ではselfを渡す。

なぜinitializeでメソッドxやyを定義する?object.xなど呼び出しているのが見える。呼び出し元のxやyにアクセスできるというのはわかるが、単なるx(どこで定義してる)とobject.xがあるのは何??

componentのdrawやupdateは自動で実行される。

ひとつずつがムリ

前回はexplodeだけなんとか分離してリファクタリングできた。 しかし、bullet関係だけというのができない。explodeはbulletだけいじればよかったのだが、bulletはtankをいじる必要があるためだ。tankをいじると相互に関連してすべていじらないといけない…。たとえばplay_stateのupdateをいじるとすると、同じupdateでもやっていることが異なるのでうまくいかない。引数も違う。ほかのupdateは引数がobject_poolだが、characterのupdateはcameraをとる、など。よってinitializeも違う。

新しいのはobject_poolに突っ込むが、以前はそうではない。 リファクタリングを独立してやるのは難しい。 かといって、そのまま移すことはダメな気がする。 gitにも、めちゃくちゃなコミットログが残る。

結局、どう作るかは自分で学ばないといけない

リファクタリングの結果だけがバーっと羅列されてるが、どういう手順でこれらをやったのかがわからない。 順番どおりではない。単に印刷上の都合で並んでいる。 働きを変えずに、内容をいじらないといけないが、それをどうやってこう大胆に変えることができるんだろうか。一つを変えると全部変えないといけないような根本的な変化…。

コピーにならないようになんとか書いていき、途中でエラーを解消していくが、スタックトレースがでなくなる。 たまに出たりするが。よくわからない。また最初からか…。

とりあえずできた

動くようになった。これからどうしていこうか。 残弾数の表示を画面端でやろう。

message.drawの色指定ができない

色がつかない。できた。 1.0を追加した。 @message.draw(x1, y1, 300, 1.0, 1.0, FONT_COLOR) キーワード引数で指定したかったのだがわからなかった。まあいいだろう。

残り弾薬を画面に表示するようにしたら、少しそれっぽいものになった。

衝突エリアの追加

ほとんど本通りになるが、いちおう追加しておく。

puts "#{object.instance_variables}"

⇒[:@components, :@object_pool, :@input, :@x, :@y, :@physics, :@graphics, :@sounds…]

本に間違い?があって、そのままでは動かない。←間違いではなく、@physics.boxのアクセスメソッドをboxで作っていた。本にはそのことが書かれていなかった。 character_graphics.rbのobject.box.each_slice(2)object.physics.box_slice(2)にする。わかってみれば単純だ。なんとなく関係がわかって来た。

また、characterのアクセサにgraphicsがなぜかないので、追加した。instance_variables()などでインスタンスを調べたりするとよい。一覧にあるのに見つからない場合は、アクセサを見る。たぶん。 無事動くようになった。

マークがつく。

衝突の追加

車体の衝突と、弾丸の衝突。 何かがおかしい。

車体は、横方向がやたら大きい判定が出る。 弾丸は、たまにエラーになる。斜めにぶつかったとき?

全くわからん。 判定範囲の描画はうまくいっている。しかし、現れている問題は範囲の問題に見える。

nearbyの引数max_distanceに目をつけた。これによって前と後の座標を比較して、衝突しているか判定を出している。 distanceを大きくすれば範囲は大きくなる。元の値は100。 なぜか左だけ?影響を受けているように見える。40にすると、左右への変な張り出しがなくなった。 意味不明。

命中すると車体にも当たり判定が発生している?音がして、止まる。重要なヒントだ。 3発以上同時に出るとundefined method boxが出る。????意味不明。

進行方向に発射すると自弾の当たり判定に引っかかる。bulletのcheck_hitをオフにしてても引っかかるから、車体側が何かおかしいようだ。

隅々まで読んだり(あまり理解できない)試してみて、ここしかないというところまで問題のスコープを絞った。 でも問題なさそうだったのでコピペしてみたら動いた。diffを見てみると““j = i”“が”“j = 1”“になっていたという些細なもので笑った。

自弾との衝突

進行方向で自弾と衝突して、そのまま進むとエラーになる。 変な当たり判定と同じ理由かと思っていたが、別問題のようだ。

解決。 game_objectでbox,collideメソッドが用意されてなかったよう。 空のメソッドなのだが、当然オブジェクトがメソッドを持ってないとエラーになる。 このようにして、特定のオブジェクトだけが持つメソッドを実行させたりスルーさせたりするんだな。 これで、衝突判定は出るがエラーがでなくなった。 修正した最初は反映されてなかったような気がする…。こういうのがあるのが怖い。

つぎに”“next”“で発射元の弾丸場合はスルーするようにする!完了。

ガタガタする

地形ペナルティーを追加する。 単に同じコードを書くだけだが、問題発生。 追加した地形、砂浜の上でガタガタする。低速では起きない。

speed,shift関連が怪しいと思う。そこがパタパタしている?

仕組みを見る。 putsで@speedをみると、ほとんど0でたまにアクセルの数値が変わっているのがわかる。 @speed > 0で、shiftに代入して、その分座標に足される。 一度shiftを経由するのはupdate_intervalに合わせて調整するためだ。 @speedは初期化されてる?でも蓄積するよな。

camera.rbを見ると、同じ仕組みで座標を計算していることがわかる。 ところが、引数は@target.physics.speedだ。つまり@speedだ。 character_physics.rbでは、ペナルティ調整後のspeedが渡されている。 つまり、カメラと移動体で渡されるspeedが異なる。…でもガタガタするのはよくわからない。

結局cameraとphysicsのどちらもshiftがほしいらしいので、@shiftにアクセサをつけてcameraも@shiftを使うようにすると解決した。cameraは地形調整前のspeedなんて必要ないから。

しかし、これはたぶん何かが間違っている。サンプルは普通に動いていたのだから。と思ったが、サンプルのcameraを見てみると違う感じになっている。何か別のリファクタリングが重なったようだ。 つまり、それも含めてやるとちゃんと動くが、途中で動かすことはできない種類のなのかもしれない。 …単純な、オレのミスかもしれない。

AIの射撃がおかしい

追っているときに反対側を射撃する。 射撃関連がおかしいのだが、わからない。

砲塔の回転はしっかりしている。drawなので関係ないか?

target_x, target_yを表示するようにすると、やはり明後日の方向を向いている。 target_x, yは自位置と方向と距離で求められた目標座標である。そのどれかがおかしいんだ。

うまい具合に止まる状態を作って角度をチェックしてみると、反射角みたいな角度でおかしくなっていることがわかる。 1時の方向だったら5時みたいなさ。

gun_angleをどこで更新してるのかわからない。自機のときはplayer_inputでマウスカーソルの位置だけど、それじゃないだろ。gun.rbか…でも異常は見当たらない…

結局、Utilsが間違っているのを発見。今までもおかしかったが気づいてなかった模様。 今までできてから考えなくていいだろうというのは捨てたほうがよさそうだ。ちゃんとたどっていけばだいたいの筋道はつく。

弾丸の動作の変更

武器を抽象化する。 爆発は武器によっては使う。

で、どうしたらいいんだろう。

要するに武器によってBulletPhysics,BulletGraphicsの動作を変えたいということだ。あとキャラの外見も。 武器はcharacter生成時に決定する。

まずcharacter.rbでCharacterWeapon.new()。 character_weapon.rbのコンストラクタで@bulletの具体クラスを決定する。Bulletを継承したHandgunBulletとかを入れる。もちろんインスタンスは生成しない。

character#shoot時に、さきほど指定したインスタンスを生成する。

bulletでexplode()をオーバーロードすることで爆発はするorしないができるようになったが、根本的にいじるためにはphysicsやgraphicsをいじる必要がある。再利用性が…。

全部継承を用意するのはクソに思える。少くとも3*3ファイルが増えるのはひどい。 必要なのは、

  • 発射音(sound)
  • 弾丸の画像(graphics)
  • 弾丸の軌跡(graphics)
  • ダメージ(physics)
  • 範囲(physics)
  • 速度(main)
  • 爆発物か否か(main)

ほとんど全部数値が変わるだけだ。要するに武器とはこの数値の組み合わせのことだ。 object_poolで全部渡せばよくないか?

各派生弾丸クラスでinitializeで数値を定義したい。super()に追加していたい。でも引数を5つ書くのがいやだ。

発射音が再生されていない問題

今まで気づいてなかったが、発射音が出ていない。 爆発音は発生しているが…。サンプルでも発射音は出てないようだ。同じ仕組みで書けるように思える。そして同じようにかかれているのに、音が出ていない。

音の問題か?単に聞こえづらかっただけ。変えたら聞こえる。

画像の変更(未)

オリジナル感がないので画像を変えよう。どういじったらいいかわからないのでそこからだが…。

  • キャラ画像

→画像を8方面単に回転させているかわりに、画像を読み込むようにする。横、上、下、斜めの4種類?

  • マップ

→都市マップに。障害物も生成しないといけない…。

  • 弾丸

→それぞれ用意。完了。

よく元のゲームを観察してみると、基本はキャラクターの進行方向に向かって画像が変化する1)。けっこうアニメーションする。 で、銃撃した場合は銃を上げたアニメーションになって銃撃方向を1秒くらい向く。だから移動方向とキャラの絵が逆だってこともある。でもそれでなんか臨場感がある。

  • 方向の違う画像を読み込むようにする。
  • キャラによってセットを変える
  • 銃撃したときの挙動を追加

キャラ画像

RPGのキャラの歩行画像を見ると、24個だ。 8方向で、それぞれに1つの静止時と2つの歩行グラフィックがある。 …メンドクサイ。ソフトで生成するが、jsonで指定するのも面倒な作業だ。一つ一つ…。一度作ればキャラに関しては再利用できるんだろうが、それにしたって。ソフトでできないんだろうか?調べるが出てこない。

ところで画像をjsonから読み込んでいる元のソースを見てると、たぶんこれはそのままweaponにも応用できるんだろうと思う。

  • 角度: 静止, 歩行1, 歩行2
  • 0: 4, 5, 6
  • 45: 7, 8, 9
  • 90: 14, 15, 16

… みたく指定していくようにしたい。

砂浜地形で弾丸が描画されない

ほかは問題ないので、重なりとかではない?完全なる保護色か? 色を変えると見えるのを確認した。完全に同じだったな…。

JSONに注意

JSONでよみこむようにしてなぜかLauncherだけ動かない問題。発射した弾が動かない。爆発してない。 最初はどっか変数の修正ミスで、explosion関係だと思っていた。 丸々中身を変えてみると動く。考えてみれば変なことで、全部データは数値が違うだけなんだから間違ってはいないと踏んだ。いろいろ試してみて原因はspeedだと判明。to_iにしていたため、0.6のスピードが0になっていた、というだけのものだった。もしほかの値が1より小さかったらわからなかったかも。

JSONで数字を読みこむときは十分注意しよう。

当たり判定が見えるようにする(未)

当たり判定をどうするのかが問題だ。 今のままでは弾丸が小さすぎる。単なる円だからな。画像と当たり判定は、一致してるか?そこらへんの実装がよくわかっていない。

武器によるエフェクトの変更

パラメータを変えることですべて対応する予定だが、できるんだろうか…。 読み込む画像を変えてできた。jsonでやればカンタンに。

横向きのキャラクター

今は戦車だから単に画像を回転させるだけでOKだが、キャラでそれやると頭の上しか見えないのでやや斜めにする。そうすると回転だけじゃダメなので絵や仕組みを追加する必要がある。

とりあえず完成させよう

細かいことはあとにして、まずゲームとして完成させよう。クリア条件の追加とか、キャラ選択だ。

木が揺れない

本の通りだが揺れない。 キャラの当たり判定はある。つまりcharaのon_collisionは働いているが、treeのon_collisionは働いていない。 そもそもon_collisionを調べてみる。どこから実行されてるんだろう?

on_collisionがわからない。ためしにputsを入れてみても反応しない。 衝突音がなってるから確実に実行されてるのに?音を出してるのはcharacter_physicsだった。(#character_sound.collide)

つまり…charaのon_collisionも働いていない。

衝突を判定する変数を導入してないためだった。本に載ってないパターンのやつ。

木に命中しない

木に命中しない。

いっぽう新しく追加した箱には命中しHPが減少し、爆発する。

tree.rbの少しの違いだと思うが、わからない。healthは共通のものを使用しているし…。 mapでgenerate_treesでTree.newの代わりにBox.newを入れると問題なく箱は爆発する。Tree以下の何かがおかしい…。

boxがひとつのときの動作が定義してなかったのが原因だった。 Utils#point_in_polyは複数のbox間に、座標が入っているか試す。 treeのboxはひとつしかないため(試しにboxを複数にすると動く)、point..は常にfalseを返す。

そのため、1つの場合としてifにobject.treeか?を追加する。これはboxが含まれているかでなく、距離 < 10 で判断する。

同様の視点からexplosionを見てみると、treeには動いていないことがわかる。 見てみるとこっちはobj.class == Characterとなっているので動かない。HPを持つあらゆるものに当たるハズなので、利便性からobj.respond_to?を用いる。

レーダー

視野外にいる敵の方向を示すカーソルを表示するようにしたい。

敵の座標と自分の座標で角度を求め、その角度にあたる画面端にアイコンを表示する。 ある距離に達すると消去するようにしたいが…。

四本木

を導入。ひとつひとつ本に掲載されていないのであたりをつけてdiffしてみて行く…。 object_poolをいじるのでかなり広い影響があった。 game_objectがobject_poolを使い、ほかのオブジェクトがgame_objectをつかう。コンストラクタに渡すのが変わるのでnewするすべてで変える必要がある。

で、移動が関係するすべてをmoveを使ったものに書き換える。object#moveを通してしか移動できない。今まではobject.x, object.yをつかっているため、書き換える。同時にattr_readerなどもすべて削除する。 取得するだけのやつ、レーダーとか、の動作にはまったく影響はない。座標を書き換えるときに代わりにメソッドが必要だということ。

Array#sample

Array#sampleは配列から1つ選んで返すらしい。超便利。例で使われているのに、今まで気づかなかった。gosu_texture_packerのメソッドかと思っていた。

health_powerupでrequireエラー

ほかは同じなのに、なぜかhealth_powerup.rbだけ読み込めない。 継承元のPowerupを認識してくれない。 ひとまずコメントアウトして、Mapでの読み込みもなくしておいた。

require_relativeでのエラーなので、継承の順番的に先にPowerupを読み込めということなのだろうと思ったが、ほかのは問題なくできてるからな。なぜHealthPowerupだけ問題が…。意味がわからない。

アイテム取得時の音がでない

PowerupSoundsでplayは動作していることを確認した(putsで)。 一応再生はしている? 音が入っていないか、ボリュームが0になっているのだと思う。

再生音を変えるとできた。解決はできたが謎だ…。 何回かチェックしたがやっぱりpowerup.mp3ではできない…。

爆発後に跡が残る

たまに爆発後に跡になにか画像が残るときがある。謎の。

被弾時に音を鳴らす

character_health#inflict_damageで音を鳴らすことができた。 ずっとほかのcollideでいろいろやってできなかった…physicsにあるほうが自然な気がして。 まだまだ新しい仕組みを理解してなさすぎる。

音を鳴らす仕組み

character_healthで鳴らしたのはテストで、StereoSampleを使ったものではない。 なので本式のそっちにしようと思うのだが、どうもUtilsで引っかかってしまう。

(bullet-hit.mp3) 被弾音はとりあえず https://sonicwire.com/product/26621/single?page=86 のサンプルがイメージに近い。フリーではないので後で差し替える。

被弾、与ダメージ

被弾と与ダメージをスコアとして記録する。 healthのinflict_damageに追加していく。

今のhealthの仕組みだと被弾しか記録できない。sourceを導入する。つまりダメージは誰が与えているのが何かということ。bulletがsourceを持っているので、それを渡す。で、体力を処理するとともにsourceのスコアも処理する。 explosionにも影響がある。

スプライト

サンプルのtreeのスプライト処理のやり方。ランダムに生成したseedからスプライト内の画像を読み取る方法。画像の個別の名前を知る必要がない

frame_list = trees().frame_list # jsonから読み込む。.frame_listで配列にまとめる。
i = (frame_list.size * seed).round # seedは0.00~0.99, perlin_noiseで生成
frame = frame_list[i] # 画像の名前になる
@tree = trees.frame(frame) # 画像を読み込む

ただし、画像数が少ないと(51枚以下?)、インデックスiが配列の要素数より大きくなってしまうことがある。当然エラー。51枚のとき、0.99をかけても50.49になり、四捨五入して50。元の数と同じになることはない。これが50枚のときx0.99で49.5で50。50になることがある。

load_tiles

mapやexplosionでは画像の読み込みにload_tilesを使っている。 load_tilesはピクセル数で分割して配列に入れるよう。128*128で分けるとか。 jsonがいらなくてOK。

1→2→3/ 4→5→6/… という風に配列に格納されるようだ。

爆風と地面へのエフェクトの時間差

Thread.newとsleepで実行されていることがわかった。 sleepを普通に使うと全体が止まってしまう。 Threadで爆風と地面への処理部分を並列化してsleepすると全体が止まらず、爆風と地面へのエフェクトの時間差を表現できる。

ということはわかったのだが、画像の表示がおかしくなる。画面内の何か一部分が表示される(HUDのどこかの部分が多い)。表示される場所は都度かわり、意味不明。 前から起こっていた問題だった。

この処理はhealth#after_deathにかかれていたため、launcherの爆発では発生していなかった。とりあえずThreadの部分を削除したが、同時に表示されるのはやっぱり変に思えてきた。

問題のポイントは、damageが初期化されてないうちに呼び出されることだと思う。 damageは最初だけファイルから読み込みを行う。後はそれをメモリから呼び出すのだが、どうもスレッド中で最初に呼び出されると後はずっとおかしくなる。 まだhealthにスレッド処理があったときは、launcherのときは問題がおきないことが多かった。たぶんスレッドを使わない処理のときに初期化されていたためだ。

試しに、play_state.rbにdamage.new(@object_pool, 0, 0)を書いてみた。画面の1番端が焦げて始まるのだが、スレッドを使っても問題なく描画され、もちろんsleepも効いている。

初期化とアニメーション生成は同時である。 キャラとか、マップとかカメラは、最初に決まった数が一気に処理される。

いっぽうでbullet、explosion、damageは都度生成される。画像のロードは初回のみ行われる。…ここが厄介だ。ロードのみ初回で一気にしてほしいと思う。

スレッドをいじる方向か、最初で実行する2つがあるが、後者が素直に思える。 無害な方法で最初に一通り生成したい。new(..).mark_for_removalで跡は残らない…。ダサい方法だが、これで良い?

銃弾にboxをつける

画像の大きさで銃弾の大きさを決定するようにする。 それだけではできない。銃弾の当たり判定は単に中心からの距離で求めるようになっているので、boxの中に入っているかで判定するようにする。

車体の当たり判定を参考にして書き換えた。collides_with_polyはboxとboxの当たり判定をしてくれるのでうまくやる。

boxと画像が微妙にずれてる

微妙に左にズレていて気持ち悪い。なぜ?

ヒット時に白くする

Gosuの機能だけではできそうにないので、Rmagickを使う。 @bodyを詠み込んで加工して、@bodyに再び入れればよさそうだ。 適当にやったらできた。 @bodyを1ピクセルの画像にすれば消えたように見える。

こうなるとキャラだけでなくほかの木とか箱にもできるようにしたいが、今のままだと仕組みが違って再利用しにくい。 キャラ画像は常に向かっている方向を読み込まんでいるので、一瞬挿し込んでもすぐにまた新しい画像を読み込む。

木や箱は一度変えると二度と戻らないので元を保持しておく必要がある。

重なりがおかしい

レーダーと木が重なったとき、木が上になる。木のzは5、レーダーのzは1。 レーダのzを200にした。あ、数字が高いほうが上に来るんだな。今までテキトーにやってた…。後から困るかもしれない。

木の揺れ

ダメージを受けたときに揺れるようにする。 bulletにちょうどtree専用の(boxが一つしかないので)判定があるので、そこに書く。 爆発でも揺れるようにしたい。より根本的には、healthでいじる必要があるな。bulletを参考にして同様に…

これは木だけじゃなくて箱にも使えそうだ。キャラクターは変えたい。

ヒット時の点滅

前作ったやつだが、1フレーム限定なので改良したい。FPS60になると見にくいし。 health#inflictで@hit=2とかにして、graphics#updateでif @hit..でデクリメントしてやればできそう。

射程距離を伸ばしたい

クリックした点で消えるんでなくて、しばらく伸ばしたい。 自分とクリックした点、2点の延長線上の座標で求められる。

やや汚い感じだ。

以前使ったような気がして調べてみると、AIの射撃線の表示はpoint_at_distanceだ。 これはangleがあれば距離を指定して利用できて便利だが、今回は見送る。

勝利条件

バージョン1.0にするために、勝利条件をまず作る。それから細部を追加していこう…。 敵を全滅させたら勝ち、自機のHPがゼロになったら負け。 勝ちと負けの条件はかんたんにできた。OOPすごいなぁ…。

で、あとは勝ち負けのアニメーションするメッセージをサウンドとともに描画する。 エンターを押して終了だけできるようにする。 キーボードの意味が変わるのでplay_stateの切り替えとしてやればいいかもしれない? その場合はinitializeでメッセージを生成すればいい。

だが…背景はそのまま動くようにしたい。今の考えだと、背景は真っ黒になってしまうように思える。インスタンスの変数は保持したままで、というとこだからgame_stateの変更(switch)は違うな。 メインメニューのコンティニューがヒントになるように思える。

Gosu::KbC && @play_state

で、@play_stateがあるときだけ動作を受け付けるようになっている。 同様に…play_stateで死んだときのキーを条件追加すればいいように思える。

  • updateで終了を検知→アニメーション(かちorまけ)を表示→enter待ち→終了

アニメーション専用のクラスを定義する。…ほかにはスタート時とか表示する機会がある。 graphicsを定義したほうがいいのか? announceをどんなクラスにすればいいんだろう。

there is no rendering queue for this operation

勝利や敗北に応じて描画できた。が、非常に使い勝手がよくない。

announce

↑の考え方は間違っている気がする。 annouceは常にインスタンスがある状態だが、アナウンスのインスタンスを都度生成するというほうが自然だ。 そのときごとに音と画像が出る。bulletのようなものだ。

今announceに書いている条件をplay_stateに書いて、生成をannounceに任せるのがいいかもしれない?

まあいい。従来の方式、Annouceインスタンス常時生成方式で。 音を出すのはどうしよう。

終了後の移動音を消したい

win!!が出たときに移動音がそのまま出たままになるのをどうにかしたい。 moving?が真のとき出る。それを判断しているのは@speed > 0。 だから@speed = 0とするが変わらない。

チェックしてみると、どうも爆発した相手から出ている?普通に倒したときは音は、というかcharacterオブジェクトは削除されるので消えないが、最後に倒したときは音が残る。自分は歩いてないのに音がする。

オブジェクトごと消えてsoundもなくなるはずだが再生されているということは、オブジェクト削除がうまくいっていないということだ。

…結局、after_deathにスレッドでexplosionとobject.mark_for_removalの間にsleepを入れることで足音が消えるようになった。正直よくわからないが、とにかく同時だとよくないらしい。

クリア表示をアニメーションさせたい

上から出てくる感じで。 powerupsのアニメーションと同じようにした。

死んで(勝って)から2秒くらいしてから出してアニメさせたくなった。この遅延に少し手こずる。 drawは常に動いてるから(パラパラ漫画だ)、それぞれをスレッドを作って遅延させるわけにはいかない。 最初だけ、遅延させたい状態。

これは、@startをスレッド内で遅延させて生成し、drawで@startがあるときだけ描画させるようにすることで解決した。効果音のように一回きりで遅延させることはできるので、そこに@startの処理を追加する。

音は1回きりだが、画像は常に描画しないいけない違いがある。この性質の違いを認識しておく。

キャラのデータ構造

データ構造を決める。 選択肢の前に、データを外部から決定できるようにすることが必要。

シナリオ

  • 敵キャラ

キャラ

  • グラフィック
  • 武器
  • 体力
  • AI

武器

  • グラフィック
  • 弾数
  • 攻撃力etc

今は、画像に1~24の番号をふり、character.jsonでそれぞれの番号を3つごとに配列に格納している。 3つずつ8方向。 これで画像を対応付けている。[2][1][3]…拡張性のない方法だ。

元から画像に名前を振っておいて、それを対応づけに利用したい。 静止はs-0を利用、歩行はd-0,d-1,d-2…を利用etc…いくらでも増やせるし、再利用も容易だ。 名前で分けてグループ分けする必要がある。それはそれで、ダサいような気もするが…。

jsonで読み込んだ画像の名前の一覧を取得するにはどうしたらいいんだろう? まずそれが必要だ。

いやまずは小さくいこう。character.jsonの方向別配列をいちいち指定するのが面倒だから、そこを共通化しよう。

銃弾を撃った方向に向く

今は押した方向にそのまま向いている。それに、撃った方向を向くための処理を挿入する。

画像を180度反転させる必要がある。 rmagickを使えばよさそうだが、どうやって?Gosuではtexture_packerから詠み込んでいる。 Gosuのオブジェクトが使えたらよさそうだが、うまくいかない?

方向転換の仕方が判明した。 scaleをマイナスにすればよい。scale_xをマイナスにすれば水平方向の反転になる。 だから…

@weapon.draw_rot(x, y, 2, object.direction + 90, 0.5, 0.5, -1)

みたいになる。(0.5はcenter_xで、デフォルトが0.5)

選択肢を進む

再利用可能な形にするために、選択肢データ渡しは引数でやる。引数を元にjsonを検索し、テキスト、画像を見つける。 さらに、選択肢一つ一つで元のstateに戻り、結果を格納するようにする。

@difficulty = branch("difficulty")

state間でどうやりとりしたらいいんだろう。 menu_stateのアクセサに書き込むようにした。 menu_stateではそのアクセサの値によって分岐するようにして、選択肢を進めるように。 updateにその分岐を書くと、なぜかcharacter_graphicsのupdateがおかしくなる。よくわからない。 オーバライド?うーん。

ほかのところに置くと問題なく動く。 別に何回も繰り返すわけではないので、updateに置くのはおかしいと思ってenterに置いておいた。 stateの実装を理解していない…。

選択肢を経由するとannouce後enterで戻れないことが判明。 play_stateを生成する処理は何も変わらないように見えるが、重大な違いがあるのだと思う。 → 単純なことで、選択肢のインスタンス変数を初期化してないだけだった。

ハッシュの扱い

[:key] はキーという意味。:がずっと謎だったんだよな。 キーが文字列で設定されている場合は、[“key”]とする。 ふつうのは文字列じゃなくてシンボル,というらしい。

isometricビュー

isometricにしたい。 とんでもなく難しい気もするし、少し変更すればいいだけの気もする。

  • xは、yが奇偶数でオフセットするので、雷のようにジグザグしたものになる。
  • yは偶数のダイヤの中心、奇数のダイヤの中心かで2種類ある。
  • サーフェスは大きな一枚の画像で覆っておいて、その上にオブジェクトを配置していく。
  • 偶数行と奇数行で描画を開始する座標が異なる。たとえば、タイルの(0,0),(0,1)を考える。

y = 0(偶数行)だと、ウィンドウピクセル(0,0)で、 y = 1(奇数行)だと、ウィンドウピクセル(0 + TILE_WIDTH / 2, 0 + TILE_HEIGHT / 2)で、(0,0)に比べて斜めにオフセットされることがわかる。

奇数行、偶数行は実装することができたが、縦の距離が空いている。 本来ならばそこにある奇数行が半分上に移動しているため、0.5の空白ができる。 空いた分1上に詰めないといけない。 が、インクリメンタルの基本単位は範囲オブジェクトで回しているので1だ。中をいじったところで相対的距離は変わらない。全体がずれるだけだ。1,2,3 .. 0.5, 1, 1.5

描画できない問題

とりあえず外見だけisometricに敷き詰めることに成功したが、問題だらけだ。 歩ける海、速度ペナルティ無視、暗闇…

明らかなのは、敷き詰めたことによってタイル全体の高さが変わったことだ。余って下半分が暗闇になる。 タイルと座標を対応づけているのはどこだろう。たぶん1:1だったからそんなに考えられたことはない…。

いろいろ試してみた結果、viewport周りが怪しい? viewportをいじると、暗闇でない範囲が広がる。そこは、動くとチラチラと画像が変わる。 動きをよく観察すると、本来プレイヤーにくっついているはずのカメラの映像に見える。 よくゲーム内に設置されてるモニターみたいなのがあるけど、そんな感じ。

周りが暗闇になるのは画面にカメラがくっついていなかったらそう表示されるということなんだろう。

タイルをいじるとどうしてそんな怪奇現象が起こるのか?

mapのdrawの仕組みは、viewport(つまり、プレイヤーの座標とウィンドウの幅で↑のバーチャルなモニターをつくる)を受け取ってプレイヤーの見える範囲にタイルを描画することだ。 つまり、viewportを利用せずに描画すると↑みたいなことが起こる。 解決策は、かならずviewportを利用することだ…。

画面と一致しない問題

マップの上半分しか描画されないこと、タイルの効果範囲が下にずれていること、…これは同じ問題に根ざしているように思える。 今までのタイルにくらべ、敷き詰めるため高さが実質半分になっていることだ。だから見かけと判定がずれている。

根本的な部分をいじらないといけない。 それは、座標とタイルを関係づける式だ。 逆にいえばそれを変えればあとは変える必要がない。 正方形タイルだと、座標とタイルは完全に一致していた。

isometricだと、プレイヤー座標がどのひし形の中に位置しているか?

drawするのは簡単だったのだが、座標からどのひし形の中に位置しているかを求めるのはどうしたらいいのかわからない。

テキトーにやってうまくいかないので、仕組みを詳しく見る。

  • マスター座標 → プレイヤー、弾丸などの、すべての仕組みの基盤になる座標。
  • マップ座標 → タイルを決定する座標。MAP_..で大きさが定義されている。(64×64)
  • タイル座標 → 敷き詰められたタイルの座標。マップ座標と対応する。(64*tile x 64*tile)

がある。 マップ座標は64×64しかないが、ひとつひとつに画像が割当られて、描画される。画像の大きさがわかれば相互に変換できる。tile_atを使うときは、プレイヤーの座標からタイル座標を求め、マップ座標に問い合わせることで対応するタイルを得ている。

要するにプレイヤーの座標からタイル座標を求めればいいのだが、従来のタイルとは異なり斜めだ。

本を参考にしてどうにかした。 マップの描画と、変換を式を用いて表した。

また、生成の都合で(0,0)を起点にして大きなひし形を形づくっていくため、面積の半分のx座標がマイナスになる。 そうすると移動できないので、オフセットで最初の生成位置を調整した。

それに係り…viewportにも影響があり見えなくなる範囲が多かったので調整。 90度描画よりも広い面積が必要になるためか?

ダメージ量ポップアップ

ダメージを与えたときに。 安直にcharacter_healthやhealthにメソッドを追加しようと思ったが、一定期間表示するという都合上、インスタンス生成方式のほうがいい。

うーん。厳密には自分がダメージを与えた敵のダメージを表示するんであって、ほかのは必要ないんだよな。 HUDと関連が深いような気もする。見かけだけだし。

あ、でも致命的な違いが…自分のパラメータじゃない。どこに分類すればいいのかわからなくなってきた。

物理的なのを持たないのでgame_objectではない。component

ComponentとGameObjectの違い…今までよくわかってなかった。 GameObject → boxを持つ。初期化でobject_pool,x, yが必要。@x, @yを持つ。 object_pool。mark_for_removalが使える。 Component → メソッドx, yで組み立て先の@x,@yにアクセスする。初期化は組み立て先のみ。

あまりのテキトーな理解に全然成長してない!と落ち込むがそういうものだ。 実際にいじらないと仕組みを理解したことにならない。

落とし所

そろそろ、完成への道筋が見えてきた。 とりあえず、ハイスコアを目指すミニゲームとして完成させよう。その後、フォークして仕切り直せばいい。

射撃時のアニメーション

射撃時は、武器だけでなく、本体もスプライトを変える。 各方向の、構えてる体勢へ。 で、0度のときは武器を非表示にする。(角度的に見えないのが自然なため)

だが、本体もスプライトを変えるとすると数が2倍になる。それは、間違っている気がする。拡張性も皆無だ。 ばんばんどーんをよく見てみると、常に射撃体勢だ。 そうするか。ミニゲームなので、ふつうの状態はいらないということで。

画像の作成数がとんでもない分量

白瀬の0方向アニメーションだけとりあえずやってみるが、時間がかかる。 全部やるとなるととんでもない作業数になりそう。

24 * 4 か。 96枚。最初のキャラのセットでかなり流用できると思うが、それでも大変だ。 何より問題なのは、プログラムを書くより熱中できない。うまくできないので時間をむだにしているように感じる。 平行してちょっとずつ進めていこう…。

マップ作成

マップは、タイルとその上に配置するオブジェクトの2つで構築する。

壁: 弾丸を通さないタイルで表現。 その他の障害物: 弾丸を通さない配置物オブジェクト 普通オブジェクト: 破壊可能、爆発性もある

タイルはできた。だが、壁を表現するのはまた別の層の問題だ。タイルと関連が深いが、混同してはいけない。 壁とタイルは1体1対応していない。壁は複数のタイルにまたがる。…と思ったが、じつは描画したい座標は同じなのでタイルシステムを流用できる。

タイルのロード、壁のロード。性質が異なる。

複数人のisometric上下関係

プレイヤーの位置によってタイルのz座標を変えることで、影に隠れたりするのをやったが、AIがいるとうまくいかないことが判明。 そりゃそうだ。一人でも、下から銃弾を撃つと影に隠れない。 自キャラの位置によってタイルの重なりを変えるのは明らかに間違ったアプローチだ。キャラクターが複数で、銃弾等があるとうまくいかない。

かといってほかのも考えつかないが…。

すこしずつ考える。

まずタイルを変更するのが間違ってる?だってもし、同じタイルの下と上にオブジェクトがいた場合、どちらかしか表示できないことになる。つまりオブジェクト側を変更するしかない。 しかし、だ…影響はあらゆるオブジェクトに及ぶ。それらをすべて変更しないといけない…。

深度ソーティングdepth sorting。 重なった場合x座標、y座標が大きいものが常に上に描画される。(0,0) < (0,1) == (1,0) < (1,1) このルールはタイル、オブジェクト問わず適用される。

基本z座標を定めたうえ(同じ座標だと常に弾丸は上にくる)で、座標によって計算することで違う

位置と描画

タイルにdepthを導入して、下にいくほどzを大きくするようにした。 このままだと、角の部分、つまり(0,0)から(1,1)にいくときにキャラが隠れる。 下がzが大きいからだ。

これは、キャラの中心の位置でタイル判定をして、描画しているためだ。 足の位置でタイル判定をして、描画は足からすればいい。

…はずで、うまくいったのだが、移動する前の最初に描画されない問題。下になっているようだ。 powerupsもうまく表示されない。移動するものはうまくいくのだが…。

単純にdepth+1してないためだった。 状況から考えると、同じz座標を持つものが重なった場合、よくわからない動作をする。 どちらを上に描画するか常に考えないといけない。

まだ根本的解決してない?たまに一部見えなくなってるやつがある。

難易度別スコア

難易度, スコア, キャラの順に格納されている。 難易度ごとに、最高点を求めたい。

easy,40,pawapoke
powerful,0,pawapoke
easy,160,pawapoke
easy,170,pawapoke
easy,500,haibara
やさしい: 1500, 石中
ふつう: 2000, 石中
むずかしい: 3040, 白瀬
パワフル: 3200, パワポケ

音とアニメーション

スタートのアナウンスをするときに、STARTの文字が下に降りてきて、真ん中でとまったときに笛の合図を鳴らしたい。 スタート時の指定はsleepで1秒間行っている。この間だけ@startedがtrueになる。 スタートになると、まず音を鳴らし、それからifでdrawが実行される。音と画像がバラバラだ。なのでdraw内でsoundをやるようにする。

ルール変更

いままでAI敵との一対一を想定してきたが、変える。 延々と敵が出てきて徐々に難易度を上げていく方式にする。そのほうがハイスコアを競うシステムと合うように思うからだ。

賢い少数のAIから、ザコ敵を大量に出す方式へ。単純にプレイヤーを追いかけて接触したらダメージを受けたり、カンタンな遠距離攻撃をしてくる方式にする。 身も蓋もなくいえば…『Risk of rain 2』みたいな感じにしたい。

新しい敵のイメージは、今までの敵とふるまいが少し異なる。

  1. プレイヤーに接触するとダメージを与える → physics
  2. 接触型と遠距離攻撃型がいる。遠距離攻撃型は従来を流用。 → AI
  3. 画像の描画は簡易化(回転させるだけ)、上から見て形がわかる敵にする…ゴキブリ、戦車… → graphics

AI以外は、単にcharacterをいじればいいだけだ。新しい属性を作る。与接触ダメージか否か、画像は簡易型が否か。

武器

characterは武器を持つものとして考えてきたが、そうではないものもある。玉(とりあえず最初に追加する敵)は、武器を持たず、接触しようとしてダメージを与えてくる。直線移動。 武器は「なし」となりそうだが、weaponは、要するにcharacterのshootの挙動を変えるということに思える。そうすると武器は「体当たり」になる。 直線に高速で移動する、ということを実行すればいい(もともと敵の接触には与ダメージ)。これは、緊急回避の動作と同じだ。再利用できそう。

遠距離攻撃に従属している要素が多い。近距離攻撃として単にゼロにすれば両用できる、というわけではない。 まず武器のタイプを分ける必要がある。

緊急回避を追加する

した。

体当たり攻撃

緊急回避とおなじだが、directionではなくtarget_x, target_yで算出した方向に向かっていくのが違う。

1)
マウスの位置は関係ない。そもそも元のゲームはタッチペンだからどこを狙ってるか決めようもないのだが
article/ban_ban_don_diary.txt · 最終更新: 2020/08/06 09:40 (外部編集)