johnnyGameStudio’s blog

無能なゲームプログラマのぼやき ぎーくになりたい Twitter: https://twitter.com/JGS_Developer

スマホ向けゲームアプリにおける基本的なBattle(インゲーム)設計

はじめに

スマホゲーム業界ではデッキ編成やガチャなどのバトルではない部分を「アウトゲーム」Battleなどのゲーム性がある部分を「インゲーム」などと呼ぶ
※明確な定義はない
※この記事では単語を明確にするためBattleをインゲームと呼称する
どんなジャンルのゲームだろうと共通の抑えておくべき要点がある
ゲーム業界では仕様変更がかなり頻繁に行われる傾向にあり、それの影響か大人数で開発を行っているとクラスが肥大化しまくっていたり明らかに関係のないクラスや関数内に予想外の処理が行われてしまっているソースを読むことになるのは珍しくない
スマホゲームでは長期間の運営が想定された設計を行う必要があり「運営〇年目」となっても変更に耐えうる形にするには最初の設計が最も重要である
ここに書いてあることは人によっては「そんなこと・・・?」と思ってしまうものであるが、ここ最近スマホゲームを作りなれていない人にとっては当たり前ではないということがわかってきた
一人でも多くの人にインゲームの設計について最低限の必要な要点を理解してもらい皆幸せになってもらうために記しておく
要点は以下の通り

インゲーム設計で抑えるべき要点

  1. インゲームだけで起動できるようにする(アウトゲームとの疎結合
  2. ゲームデザインレベルデザインイテレーションを速く回せるようにする
  3. なんでもやるクラスは作らない(singletonは一つまで)

1-1.インゲームだけで起動できるようにする(アウトゲームとの疎結合

インゲームはアウトゲームとは完全に独立させておくのが最も重要である
理由としては

  • 開発時のテストが楽
  • ゲームデザイン班が確認するのが楽
  • スマホゲームおなじみのバトル再開機能の実装が楽になる

などが挙げられる
逆にこれができていないとこれから紹介する要点がすべて無理になるといっても過言ではない

Unityを使用しているなら、インゲームのSceneからPlayして直接最低限の通信のみで起動できるようにしておくべきであり、UE4を使用しているなら同じようにインゲームのLevelから再生すればバトルを実行できるようにするべき
「タイトル画面を通ってマスターデータを取得し、Topページへ遷移後クエスト選択画面で特定のデバッグエストを選択」などという狂ったことを毎度繰り返さなくてはインゲームの動作確認ができないなんてことは断じてあってはならないのである

1-2.任意のステージ(クエスト)を任意のキャラクター編成で実行できるようにする

ただ実行できるだけでは意味がないのでUnityやUE4で実行していない状態でエディタ上乃至外部エディタで任意のステージ構成できるようにしたうえで実行することが重要である
これがないと新規のキャラクターや新規のスキルを実装しようとした際に都度開発サーバ上のマスターデータを弄らなくてはならなくなる(そんな面倒なことやってられないでしょ?)
具体的にどんな方法で読み込むかは人それぞれであるが、一般的なのはローカルディレクトリに「インゲーム開始のためのなんらかのデータ」を置いておき、SceneやLevelの実行時に読み込むことでサーバとの通信で持ってきたマスターデータと読み込んだデータでバトルを開始する手法ではないだろうか
UnityならJsonUE4ならCSV辺りがベター
※もちろんこれらの処理を行うためのコードはすべてリリース時には含まれないようにすること

2-1.ゲームデザインレベルデザインイテレーションを速く回せるようにする

ゲーム開発の難しいところは一度完成したと思っても容赦なくちゃぶ台がひっくり返るところである
「作ってみたけど思っていたほど面白くない」
「もっと要素を追加したい」
「面白いこと思いついちゃった!」
など日常茶飯事である
ゲームは面白くなくては意味がなく、「面白さ」などという不確かなモノのクオリティを担保する上でゲームデザイナーのトライアンドエラーイテレーションを速くすることがかなり重要であることは言うまでもない
また、エンジニア的視点でみてもゲームデザイナー側で出来ることを増やすことでエンジニアに過度なタスクが降ってこないようにすることができる(メンテナンスは必須になるが)

2-2.ゲームステージの変更をエディタ上で行えるようにする(可能ならばホットリロード)

1-2で述べた通り、任意のステージを任意の編成で行えるようにしたところで、非エンジニア職に「さあJsonを編集するのだ」などと言ってもそれを実行できるのは極わずかである
JsonCSVを直接触るのはバグの元なので、理想はそれらの編集エディタを用意してあげるべきだろう
可能であれば、ゲーム実行中にローカルのマスターデータを編集して動作を変更させたりする環境を構築すると楽(仕様変更がたくさんくるであろう開発序盤でこれを実装するのは時期尚早だと思うが考慮はしておいたほうが良い)
以上を踏まえたバトルの開始時の簡単なシーケンス図(慣れてないのでUML的に正しい書き方かは保証しない)
BattleStartFunc.png

2-2-2.Nodeベースのビジュアルスクリプトを使うことを選択肢に入れよう

エンジニアにありがちなのだが「すべての処理が同じソリューション内のコードで見ることができればそれが一番わかりやすい」といった幻想に取りつかれがちである
エンジニアだけで完結するタスクならばそれは正しいが、ゲームデザイナーやレベルデザイナーと密に連携を取らなくてはならない場合には誤りである

Unityには標準ではないが有名な有料AssetでPlaymakerやArbor、UE4には標準でBluePrintやBehaviorTreeが用意されている
これらはうまく活用すれば非常に強力なToolであり、ゲームを実行しながら値を変更したりNodeを繋ぎ変えたりすることが可能である
そうすることでレベルデザイン班などが自由にエディタ上で試すことが可能になり、イテレーションが高速になる(もちろん学習コストはかかるが)
その際注意しなくてはならないのは、非エンジニアが触るビジュアルスクリプト部分はゲーム処理の高レイヤー部分に限定するように注意して設計する必要がある

例えば、AIのターゲッティング処理にビジュアルスクリプトを採用する場合、「敵AIのターゲット判定の処理」のみを弄ることができるようにPlaymakerなりBehaviorTreeなりを使用しておくべきでターゲッティングを行った次の処理などとは蜜結合にならないようにするべきである(あくまで例でありターゲッティングはビジュアルスクリプトを用いるべきという話ではない)
つまりは非エンジニアがうっかり間違った編集をしてもゲームが止まったり致命的なバグが発生しないような状態にしておく

※便利になる代わりにエンジニアの知らないところで想定していたより複雑且つ大規模なNodeが組まれていたりすることになるので注意
※これらはゲーム性により使うべきかそうでないかは大きく異なってくるので必ず使うべきという話ではない

2-2-3.演出部分はプログラマーではなくても編集して動作確認できるようにする

演出部分は必ず予想外の変更や微調整が入る
プログラマーが演出部分まで担当していた場合によくあるケースだと、当初はアニメーションシーケンスを流すだけだったのが時間がたつにつれて、やれQTE要素入れたいだの特定の条件下では一部のエフェクトを変えたいだのやっぱりやめただのアニメーションの開始時と終了時に特別処理を特定のタイミングの時だけ行ってほしいだの仕様変更が入りコードの保守がとても難しくなる

プログラマが都度対応する必要がある設計になっていると手がいくらあっても足りなくなってしまうのでアーティスト側だけで「編集→動作確認」が完結するように設計する必要がある
UnityならTimeLineなどを使って細かな編集ができるようにしておくべきだし、同様にシーケンサを用いるべきである
また、PlaymakerやBlueprintなどのビジュアルスクリプトと併用して使うことでより広い範囲をアーティスト側だけで実装することが可能になる
演出部分に関してはエンジニア側が行うことは細かな実装ではなく、ビジュアルスクリプトで使う便利な機能をコマンド(Node)単位で作成していく程度にとどめておくのがちょうどよいだろう
以下は演出部分だけをビジュアルスクリプトにした場合のざっくりとしたスクリプト処理イメージ
UnityならPlaymakerやArbor、UE4ならBlueprintなどで脳内保管してほしい
Untitled.png

プログラマが実装するべき青いコマンドをゲームデザイナー側で処理させてしまうとNode全体が煩雑化してしまってバグの元なので注意。モックアップで仮に実装してしまうのもありだが必ずC#乃至C++で書き直すことをお勧めする
このような形にしておけば、ゲームデザイン側でコマンドに渡す引数と条件式の作り方次第で色々なことが試すことができる

3-1.なんでもやるクラスは作らない(singletonは一つまで)

これは別にインゲームに限った話ではないが、ゲーム内のあらゆる処理をなんでも行ってしまうクラスがあるとコードの保守が一気に苦しくなる
そのクラスがsingletonでさらに他にもsingletonがある場合にはもう悲惨である
インゲームは他のシーンと比較して複雑化しやすい傾向にあるため一番最初の設計が特に重要である

3-2.クラスの名前をできる限り具体的にする

そのようなクラスの誕生を防ぐ簡単な方法としてはクラスの名前を具体的にすることである
保守期間がそんなに長くないのにも関わらずスパゲッティ化したコードの多くは曖昧な名前のインゲーム統括クラスが存在するケースが多い
例えば以下のような名前のクラスファイルを見たことはないだろうか

  • Battle.cs(cpp)
  • Game.cs(cpp)
  • BattleManager.cs(cpp)
  • GameManager.cs(cpp)
  • RPGManager.cs(cpp)
  • ...

最初に名言しておくとこれらのクラスの存在意義を否定しているわけではない
おそらくは開発初期ではManagerクラスの上位クラスとして全体の管理や橋渡しだけを行うクラスを想定していたのだと考えられる
しかし、開発も進み関わる人員が増加したり納期に追われたりしていくにつれて知らず知らずのうちにこれらのクラスに具体的な処理が追加されていき、気がついた時には何でもこなす便利クラスが爆誕していたのだろう
例えば「BattleGeneralManager」といった名前にすれば初見で「managerクラスの上位クラス」と明示することができるのでスパゲッティ化する可能性は低くなる(それでもなるときはなるものではある。結局は運用体制とメンバー次第なところは否めない)
同じようにBattleGeneralManagerのメンバで保持する子Managerクラスも具体的な名前をつけていくとでいくらか保守性は高まる
例えばコマンド選択式RPGゲームの場合には具体的には以下のようなイメージである

Class Diagram.png

どれくらいの粒度で具体的に名前をつけるかとかそもそも「~Manager」といったクラス名はつけるなという意見もあるとは思うが、とりあえずは〇〇Managerのような名前では責任が曖昧になりがちなので、もう一単語増やして「〇〇△△Manager」といった名前にするように心がけるだけでだいぶ事故は減るはずである

あとがき

こういう記事を書くと色々なところからマサカリが飛んできそうなので書くべきか悩んだけど書いてみた
同じジャンルのゲームを設計しても人によって設計手法は違うしなにが正解なのかという答えはないものだと思うので、マサカリを投げたくなった人は
「こういうことを気にしている人がいる」
程度でとらえてほしい
でも個人的にはこれらは絶対に抑えておいてほしいしそのほうがみんな幸せになると思っている
そうそうふいつ