19
僕とことり先輩はまだホワイトボードの前にいる。
ゲームの仕組みを教えてもらうためだ。
「しい君、改めて聞くけどこのアプリで作りたいのってどんなものだっけ」
彼女はなぜかわかりきったことを質問してきた。
「そりゃあ、シューティングゲームでしょう」
僕がこう答えても、彼女はまだ僕の方に瞳を投げかけている。その表情は変わらないから、まだ正解にたどり着いていないことがわかる。
仕方ないから、少し顎を引いてとりあえずシンプルなことを口にするしかなかった。
「でも、主人公と敵のキャラクターしか思いつかないのですが」
「それで正解だよ!」
え? 僕は首を傾げてしまった。
さすがに、ことり先輩が何を言いたいかわからない。
「ここでひとつ考えてみてほしい。
作りたいアプリというのは......」
すると、彼女は少し言葉を閉じた。そして、また語ってくれた。
「......アプリだけじゃなくて、ファミコンでもCDゲームでも、その中には世界があるじゃない。
ゲームという世界の中には、私たちみたいな生きている人物がいるんだよ」
......それを実現するのが、<クラス>だよ。
そう言って、彼女はホワイトボードのマーカーを手に取った。
・・・
ゲームの中に存在している人物を表現するために、プログラムの中ではキャラクターを型の種類とした変数を用意する。
彼女はホワイトボードに次のイメージを書いた。
*───────────────────
Character 勇者
Character 村人
────────────────────
キャラクターの変数? 僕は首をかしげた。
「まずはRPGを想像してみて。
最初にイメージするのは冒険する勇者だよね。そして、村人は勇者に情報を教えてくれるとする。
でも、少し俯瞰して見てみると"ゲームの中に登場する人物"としてひとまとめにできないかな」
......私が有坂ことりで、君がしい君であるように。
こう説明されると、現代世界でも僕たちは<人間>という枠の中にまとめられているような気がしてくる。
「そう、その感覚をまずは持つことが大事だよ。
ここから、実際にキャラクターを作り上げることをイメージするんだ」
つまりは、キャラクターの変数を構築する必要があるのだ。
前にもゲームのキャラクターが変数の型になると彼女は言ってくれた。今それが活かされている。
「じゃあ、ここでは勇者と村人の共通点を次のように定義しよう」
彼女はマーカーを魔法のステッキのように回転させると、次のように書いた。
*───────────────────
キャラクターの名前
キャラクターの職業
会話の台詞
────────────────────
なるほど。
勇者なら名前が自由に決められて、村人は"村人"や"名無し"になるのだろう。それ以外でも、登場人物の種類に応じて色々設定できるというわけだ。
「そう、キャラクターの基礎となる仕組みの枠を定義するのが<クラス>というもの。
ゲームの中にいる登場人物の設計図だと思えばいい」
変数と動作。これらを併せ持ちつつ、変数の型として扱うことができるのがクラスのメリットだ。
よりプログラムらしく表すと、次のようになるという。
*───────────────────
クラス名:Character
String name; //キャラクターの名前
String job; //キャラクターの職業
talkメソッド //会話の台詞
────────────────────
nameやjobという変数、talkメソッドという処理を併せ持った設計図。
これが"Character"という型の変数で宣言することによって、勇者や村人という存在を作り上げることができる。
「そう、作りたいものによって必要な変数をクラスの枠の中に定義すれば良い。それが世界をつくる第一歩になるんだよ」
彼女はそう言って、口角を上げた微笑みを見せた。
・・・
僕はふと首を傾げた。
なんだか、もやもやした霧のようなものが頭の中にこもっている。その状態のまま、僕はことり先輩に恐る恐る質問をした。
「talkメソッドって会話ですよね」
「そうだね」
彼女は表情を変えずに答える。僕は少しずつ自分の意見を絞り出していった。
「会話って......、もっと種類があるものじゃないですかね」
そう、それが正解だよ! 彼女はこう言ってマーカーをこちらに向けてそろえた。もう少しで赤いペン先がこちらの鼻に触れそうだ。
「ひとつの設計図からは同じ仕様のキャラクターしか生み出せないんだ」
つまり、名前と職業の情報を持った登場人物でしかなくて。talkメソッドで喋る台詞はどのキャラクターも一緒になってしまう。
勇者なら"鬼ヶ島に冒険に行くんだ"と言いたい。
村人は"鬼ヶ島には姫君が捕らわれている"と勇者に告げたいだろう。
「クラスをただ単に使うだけでは、表現できることに限界があるんだよ。
会話も、動きも」
・・・
勇者も村人も、それぞれ別のことを喋らせるには。
つまりは別の設計図が必要になるのだ、それを<継承>という形で実現できる。
「私が有坂という苗字を受け継いでいくように。
......あ、結婚して苗字が変わるのはちょっと置いといて」
なるほど。
苗字はもとより、性格もどこか引き継がれているのだろう。
これを明示的に設計することで、<クラス>での表現がより広がるわけだ。
*───────────────────
元クラス:Character
↓
継承クラス:Hero(勇者)、Human(村人)
────────────────────
「継承クラスを定義することは、"新しい設計図を作成する"ことができる。
そのメリットは拡張ができることなんだ」
ことり先輩はこう言った。
元クラスCharacterの情報をもとに継承クラスを作成する。
継承クラスであるHeroとHumanは、Characterが持っているnameとjobの変数、talkメソッドを"受け継ぐ"ことができる。
この方法ならtalkメソッドで喋る台詞も自由に決めることができる。
さらに、勇者なら HP や level みたいな冒険に行くための変数を新しく定義できるわけだ。
「元クラスは次のように定義してあるんだ」
彼女は先ほどの説明に少し書き足した。
*───────────────────
<元クラス>
abstract クラス名:Character
String name; //キャラクターの名前
String job; //キャラクターの職業
talkメソッド //会話の台詞
moveメソッド //移動
────────────────────
"abstract"は、抽象的とか概念上のという意味だ。
つまり、元クラスであるCharacterはただの想像的なものとして扱われる。実体を想像できないため、会話や移動といったアクションの内容を決定することができない。
その実際の内容を定義するのがHeroクラスとHumanクラスとなり、はじめて実体を与えられてゲームの中に存在することができる。
次のように"extend"、つまり元クラスのCharacterを拡張する形で継承クラスを作成する。
*───────────────────
<継承クラス1>
クラス名:Hero extends Character
String name; //キャラクターの名前
String job; //キャラクターの職業
int HP; //HP(Heroクラスにて新しく追加)
int level; //レベル(Heroクラスにて新しく追加)
talkメソッド //会話の台詞
moveメソッド //移動(カーソルキーで移動ができる)
────────────────────
*───────────────────
<継承クラス2>
クラス名:Human extends Character
String name; //キャラクターの名前
String job; //キャラクターの職業
talkメソッド //会話の台詞
moveメソッド //移動(ランダムで向きを変える)
────────────────────
moveメソッドは、移動の処理を記述する。ここは当然勇者と村人で内容が違う箇所だ。
勇者はカーソルキーで移動しないと冒険に行くことができない。
村人は移動しない代わりに、ランダムで方向転換をする。
これらは元クラスの資産を受け継ぐ上で記述が必要だ。継承されたクラスは、元クラスの変数とメソッドを必ず実装しないといけない。
「それは余計なものを背負うことじゃないわ」
元クラスには基本となる共通項を集約すること。
継承クラスは元の資産を受け継ぎながらより具現化した実体を作ること。
それが設計をする上での大切なメリットだとことり先輩は説明してくれた。
これまでの話を振り返ると、キャラクターは次のように宣言するのが正解というわけだ。
*───────────────────
Character 勇者, 村人 //×実体がないためエラーになる
Hero 勇者
Human 村人
────────────────────
・・・
「はぁ......」
僕はついため息を漏らした。説明を聞くだけでお腹いっぱいだ。
「まだまだこんなのは序の口よ。
<クラス>の機能はこの言語を象徴するもので、まだまだ奥は深いんだから」
ことり先輩はくすくすと笑いながら言うと、マーカーの蓋を閉めた。
ここで、僕たちもパソコンの方へ向かった。
彼女は一番奥の席に座り、僕はその脇に立った。
「じゃあ、今回作るゲームがどうなっているか見せるわ」
*───────────────────
元クラス:Character
↓
継承クラス:Player、Enemy、Boss、Bullet
────────────────────
Playerは主人公の機体、EnemyとBossは敵の機体。Bulletは弾のことだ。
「ここでいうキャラクターは、何かの拍子によって生まれては移動して衝突するとダメージを受けるもの」
なるほど。
少しシューティングゲームの核心に触れたような気がした。
これらの元となるクラスCharacterには、次の変数とメソッドが設定されている。
*───────────────────
<元クラス>
abstract クラス名:Character
float x,y; //座標(xが画面の横方向、yが画面の縦方向)
float dx,dy; //移動量(dxが横方向へ、dyが縦方向への移動量)
float w,h; //縦横幅(wが横幅、hが縦幅)
int hp; //HP
actionメソッド //行動する処理
createBulletメソッド //弾を生成する処理
hitメソッド //キャラクター同士が衝突したときの処理
────────────────────
*───────────────────
<拡張クラス>
クラス名:Player、Enemy、Boss、Bullet extends Character
~以降割愛~
────────────────────
キャラクターの位置と移動量を表す変数と、各処理をするメソッドがCharacterクラスに用意されている。ちなみに、"float"というのは小数点を表すことのできる変数の型だ。
actionメソッドは、先ほどの勇者と村人との関係と一緒だ。
主人公ならカーソルキーで移動し、敵の機体なら別途移動する処理を書く必要がある。
createBulletメソッドが実行されると、主人公や敵の位置に弾のキャラクターを生成する。
この時、弾が生まれる座標とx方向,y方向の移動量を引数として与える必要がある。
hitメソッドは、主人公や敵・弾と敵などがお互いに衝突したときに発生する処理だ。
ダメージ分HPが減り、HPが0以下になるとこの画面から消える。
ちなみに、主人公と敵の弾はダメージ計算がされるが、主人公と主人公が撃った弾・敵と敵の弾は衝突しない関係になっている。関係の条件式があるということだ。
ことり先輩はこちらを振り返り見上げながら言った。昔みたニュース番組のキャスターみたいだと思ったのは秘密だ。
「で、みんなにやってほしいことが、EnemyクラスとBossクラスの種類を増やしていってほしいんだ」
なるほど。
親クラスにある機能を使いながら、少しずつ拡張していけばいいんだ。
わくわくを積み重ねていく気分になっていた。
ついに、活動がスタートだ。
新規登録で充実の読書を
- マイページ
- 読書の状況から作品を自動で分類して簡単に管理できる
- 小説の未読話数がひと目でわかり前回の続きから読める
- フォローしたユーザーの活動を追える
- 通知
- 小説の更新や作者の新作の情報を受け取れる
- 閲覧履歴
- 以前読んだ小説が一覧で見つけやすい
アカウントをお持ちの方はログイン
ビューワー設定
文字サイズ
背景色
フォント
組み方向
機能をオンにすると、画面の下部をタップする度に自動的にスクロールして読み進められます。
応援すると応援コメントも書けます