[第16回UE4ぷちコン]振り返り:部屋作成

ここは[第16回UE4ぷちコン]振り返りの子頁です。

概要

  • EUWで部屋パーツを自動配置して部屋Level作成

  • 敵や罠を配置してテスト用GameModeで検証

  • Level Instanceで直列に配置

要件定義

敵の銃弾をよけてぶん殴るまでの「みち」すじを素早く描くゲームです!ビュンビュンってしてドカーンって感じになります!マップ制作お願いします!

うん、楽しそうだね!でも一旦落ち着いて、他にどんな事が決まってるか聞いてもいいかな?そうだな、敵はどんな奴?え?決まってない?人型とかタレットとか色々やりたい?ハハ、いいね!とりあえず仮でブルーマン先生でもいいかな?いいよね?そうすると部屋もリアルめのスケールかな?まだわかんないって?じゃあその銃弾っていうのはどんな速さ?決まってない?プレイヤーの動く速度は?それも決まってないって?いつ決まるかな?遊びながら決める?じゃあ部屋の大きさは仮でいいかな?後から変えられるようにしておけばいいよね!…

(深呼吸)…とりあえず適当な立方体を持ったActorを四方の壁と床天井で6つ、サイズ指定して置けばいいかな。いや、これぐらいなら全部入れて1Actorで済ますか。Construction Scriptで生成すりゃいいだろ。でも完全な箱だと光源どうするかな。天井部分に照明っぽくPoint Light置けば…いや、Lumen照明も使って…

あとChaosによる破壊表現をふんだんに使うゲームです!ドカーンバラバラーって感じで!

あ?(切替)いいね!でもまずはゲームプレイが完成してからじゃない?え?破壊がゲームプレイにも絡むかもって?一度着地した壁を壊すかも?ハハハ、そりゃいいかもだ!それっていつ…うん、遊びながら決めるんだよね?じゃあ、ブロックがいっぱい並べて部屋の形にして、触ったら消せるようにしておいたらいいかな?え、1ブロックがいっぺんに壊れるんじゃなくて部分的な破壊をするかもって?マイクラみたいにブロックの境界が見えるのはヤダ?オーケー、それはちょっと検証が必要だね!まずは1ブロックずつ壊れるバージョンを作って、それを見ながら話さないかい?今みたいに虚無を見ながら話すより、ずっと楽しいはずだよ!

(深呼吸)(深呼吸)…

設計

実話を元にしたフィクションはさておき、何も決まっていなさそうな中に制約を見出していく。

  • とりあえず最小構成を考える

    • 狭めの部屋、敵が一人

      • ゲームの基本アクションをやるだけ

    • どれぐらいが狭めになるかわからないので部屋サイズはすぐ変えられるようにする

    • 壊れるかもしれないので部屋を複数のパーツで構成する

  • 基本ゲームシステムが落ち着いたら、部屋を増やしていく形になるだろう

    • 作りやすさをどう確保するか

      • 1つのLevelに全部詰め込んでいく形は取り回しが悪く、誤編集が起こりうる

        • 1部屋1Levelにする

      • 部屋間の移動はどういう演出になるのか

        • 勿論決まっていない

        • ワープするならPersistent Level移動で済むが…

        • Chaos利用が裏テーマなので「壁や床天井を突き破って次の部屋に行く」をやれる余地は必要

          • Sub Levelとして読みこんで並べる

部屋パーツ

部屋の構成部品としてActor派生の"BP_RoomParts"を定義した。

基本はただの黒い板(Static Mesh)。白い板は、種類(後述)によって動的に生成されたライトパネル用Static Mesh。

Int Vector型で指定された大きさにConstruction Scriptで調整。

Enumで種類を定義しておき、Expose on Spawnな変数にしておく。

EntranceとExitは、いよいよ締切が迫ってきた時に「部屋の出入りは所定の壊れる壁を自動で破るってことで!」と決まった時に差し込まれた。

天井か壁ならライトパネル用Meshを生成。

出入り口はシャッターみたいな縞々の見た目に変更。実は「グレイロボ変」のロボの蛇腹手足Material流用。

Editor Utility Widgetで部屋パーツ配置

1部屋内の全BP_RoomPartsを管理するBP_RoomをこれまたActor派生で定義した。Editor Utility Widget(EUW)で縦横に何枚ずつ壁を置くか指定させ、

※画像は拡大できません
BP_Roomの配置処理。均等にSpawnしていくだけ。側面は壁と柱が交互に配置されるようになっているのでやや複雑。柱は「仮に壁が全部壊れるとゲームが成立しなくなるし天井も浮いちゃう」というゲーム的な都合と「全面同じ壁の繰り返しだと見た目のっぺりしすぎ」という見た目の都合から存在する。

EUWは縦横の壁の枚数だけ指定。壁そのものの大きさはBP_Room側に初期値があり、そちらを触る事で調整を済ませてしまった。

Createボタンを押すと前述の配置処理が走る。

部屋を作ったら敵や罠を配置する。必要に応じて部屋のサイズも調整する。

固定ビームと敵の部屋。Lumenパワーがすごい。部屋内に置こうと思っていたPoint LightはLumenパワーで不要になった。というか試しにライトパネル一枚に付き1つMovableなPoint Lightを床や敵が視認できるような有効半径で置いてみたら、Light同士の干渉がしこたま発生して処理落ち不可避だった。

GameModeに登録する

Sub Levelとして複数の部屋を同時に読み込むことを考えると、「どの部屋にどんな敵がいるか」「特定の部屋の破壊状況を巻き戻す」など、部屋単位でのActor管理が必要になる。いくつか方法は考えられるが…

Sub Levelのロード後にGet All Actors of Classからの上記の方法でフィルタすれば、確かに所属を割り出せた。だが毎回Get All Actorsしまくるのヤバイ。今回はもっとシンプルな方法で解決した。

パワー・オブ・ザ・ゴリラ。

1部屋につき1つ存在するBP_Room以下に全ての構成要素をAttachし、部屋単位での制御はGet Attached Actorsを使う。

ちなみにカメラはリプレイ用。

そしてBP_RoomはBegin Playで「オレを登録しろ!」という形でGameModeに自身の参照を渡す。これで、GameMode側はBP_Roomに指示を出すだけで敵を起動したり壊れた壁をリセットしたり出来る。

Level Instanceの機能で、一つの部屋の出口の隣に次の部屋を読み込む処理を繰り返して部屋を連結させる。

テスト用GameMode

ゲーム本編で使うGameModeはタイトルを出したり全ステージ読み込んだりといった処理が入っているので、部屋を1つずつ作る時のGameModeには向いていない。そこでゲーム本編用GameModeのChild Blueprintを作り、関数をOverrideしてテスト用のGameModeを作った。

処理を関数化しておくことで、関数Overrideで振る舞いを多様にできる。

全レベルクリア判定関数もOverrideした。1ステージクリアするだけでリプレイやエンディングが確認できるようになる。

反省・所感

  • EUW側にもっと機能を出すべき

    • とりあえず壁のサイズは出しとけ

    • 敵や罠をBP_Room配下にするボタンとかもあるべき

  • さすがに全部BP_Roomの子にするのはゴリラすぎる

    • もともと、Level Instanceの使い方がよくわかんなかった時用の保険で子にしていたというのはある

    • しかし敵や罠などギミックを追加する時に子にし忘れる事故がありそう

    • 所属ActorをBP_RoomのActor配列に格納する「仕上げボタン」みたいなのをEUWにつけるのがいいかな

      • 階層的な子にする必要がなくなる

  • 柱を活かせなかった

    • ライトパネルもなく、ノーリアクションなのがよくない

      • 色違いのライトとか置くとか

      • 壁は壊れるが柱は壊れないといった違いをつけるとか

  • 部屋パーツの構成はあれでいいのか

    • Enumでswitchしまくるなら、種類をそれぞれ派生Blueprintにして処理を書いていく手もあったのでは

    • ただ処理の分岐的には「する/しない」しか無いのであれでいい気もする

      • ライトパネルをつけるか、つけないか

      • マテリアルを変えるか、変えないか

    • この程度の複雑さならenumのほうが管理しやすいというのはあるかも

  • 部屋の作りそのものが最低限すぎる

    • ライトパネルデカすぎないか

    • 上下二段がいいんじゃないか

    • 天井の高さはこれでいいのか

    • 無限に発散しそうなので、発展させていくにも制約が必要…

  • GameModeに全部ルールのっけて作る以外の手法も試すべき?

    • 今までUEで作ってきたのが小規模かつ一人用ゲームだから問題はなかったが…

    • GameModeを差し替えれば「同じActorを使った異なるルールで遊べる」というのが設計思想なはず

      • ActorやUIがなんでもかんでもGameModeに通知を投げるのは良くない?

        • GameModeの責任がでかすぎる

      • GameState、PlayerStateなど、UEで用意されている仕組みを使っていくべき

        • でも通知する側が「これはGameModeに通知」「これはGameControllerに通知」といちいち知っているのも変?

          • 全部Event Dispatcherにすれば、誰に使われるかについて無知でいられるかもしれない

            • 「聞きたい人がチャンネルを合わせる」というのは自然ではある

          • Bind管理の面倒臭さと、どれだけ抽象的に扱いたいかとの兼ね合いか

            • 各ActorにRuler Interfaceみたいなのを注入して、そいつに通知させるというのもありか

              • じゃあその実装はどこに、ってなるとまぁGameModeだよね…

      • 一応、現状でもInterfaceを介して通知が飛ぶので、GameMode側の実装によってどうとでも出来はする

        • ただ、やはりGameModeがデカくなっていくと派生の管理もしんどくなっていく。なった。

        • GameModeがPlayerControllerやPawnなどの基本クラスを選べる設定になっている様に、ゲームデザインに応じた機能分割を行い、モジュール的に差し替えられるようにするのが良いのかもしれない

          • 新規に突貫で作っているProjectで、モジュールの粒度が見えてくる頃には開発末期でそんな根っこの設計をいじってる場合じゃないとかいう現実問題は正直ある

          • 素早くたくさん失敗して、初期設計の精度アゲ↑↑てこ☆(若作り失敗)

  • GameModeをOverrideしてテスト用に使うのは今後も使っていきたい

    • GameModeの初期Stateを変えてCompileして実行、みたいな手間がなくなるのがイイ

    • 処理を意味毎に分離する動機になる

    • ベースとなるGameModeからテスト用と本編用を派生して、共有される機能をベースGameModeに詰め込んでいく、というアプローチが正しい気はする

      • 今後はそうする