Unityでインディゲームを作る!

Unityでのゲーム制作を目指し、それに関わる話題についてのブログ

Unity UI Toolkitにおける長さ設定 (UIElements.Length)

UI Toolkitにおいて、『長さ』はいくつかのモード(Auto, %, Pixel, None)で設定することが出来ますが、その際に使うことができる構造体がUIElements.Lengthです。

 

 AutoはUI ToolkitにおけるFlex-box レイアウトに従って自動的に長さが決められるモードであり、Flex ShrinkやFlex Growに基づいてその要素の長さが決定されます。

 

button.style.width = 10.0f;

 という風に直接数値(float)を入力する場合には、自動的にPixel(ピクセル)モードでの設定となりますが、Lengthを使えば%(パーセンテージ)で設定できます。

_button.style.width = Length.Percent(50.0f);

 %設定にすると画面や親要素の長さに応じた長さが設定されるので、様々な解像度の画面に対応する場合には、このLength.Percentを活用することになるかと思います。

UI Builder上での%による長さ設定

 

flex-positionをAbsoluteに設定したUI要素の位置を・・・

button.style.left = Length.Percent(10.0f);

button.style.bottom = Length.Percent(5.0f);

という風に自由に(Relativeモードのように他の要素の影響を受けないという意味で)直接的にポジションを%設定することも出来ます。

 

 もちろんハードによって(特にスマホ)アスペクト比が違うので、パーセントによる長さ設定は要素の見た目のバランスを変えてしまう可能性はあります。しかし、パーセント設定を上手く使えば、いわゆるレスポンシブなUIを作ることができるはずです。

 

 そして重要なのが、そのパーセンテージはどこを基準にしてのパーセンテージなのか?という点です。これはその要素を格納している親要素(いわゆるflex container)の長さを基準にしていると考えていいです。

 

 つまり、今見てるモニター画面のスクリーンそのものの大きさ、長さを参照しない場合があります。もし、スクリーンを二つのVisualElementで横に二分割していて、その中にボタンを置き、その横の長さ(width)を%設定した際に参照するのは、既に二分割された画面の横の長さ、ということです。

TestUI_Hierarchy

 下画像は画面を2つのVisual Elementで左右に分割し、左側にWidthを50%に設定したボタンを置いています。Visual Elementのwidthはそれぞれ50%に設定されており、その中の50%ということでボタンの長さは画面の4分の1となっています。

Width:50%Button

 この場合、ボタンの長さを100%に設定しても画面全体の半分の長さにしかならない、ということになります。

 

 こうしたUI Toolkitにおける、%設定の相互作用というのを理解して意識的にコントロールできるようになるには慣れと時間が必要ですが、使いこなせれば強力な機能となるはずです。

 

 アスペクト比が異なる状況でデザインに一貫性を持たせるアイディアについてはまた別の機会。ひとまず今回はUIElements.Lengthという構造体の紹介だけをさせていただきました!

 

MadeWithUnity探検隊! "Subway Surfer City" 最先端のラン・ゲーム!街中を駆け抜けろ!

久々のMadeWithUnity探検隊!この企画は本ブログの初期からやっていまして、MadeWithUnityの作品を実際に遊んでレビューするというもので、今回はSubway Surfer Cityとなります。

SubwaySurferCity_Cover

 元々、"Subway Surfer"というゲームがあり、その続編ということでシンプルなラン・ゲームをメインモードとしつつ、そこに本作からの追加要素としてステージ方式のシティ・モードも存在します。そこでは現在、5つのエリアがプレイ出来てノーマルモードとハードモードがあるので、全部でかなりのステージ数です。

 ハードモードはかなりやりごたえがある内容で、正直ちょっと面倒なステージや条件もあるのですが、Under Tracksエリアまで全クリしてプレイ時間を調べたら38時間ぐらい遊んでいてびっくりしました。

 

 ラン・ゲームは散々出ていますし、散々遊ばれているスマホ向けのジャンルなので、今更語ることはあまり無いかもしれませんが、この"Subway Surfer City"はそんな中でも2026年現在、最も洗練されたラン・ゲームと言っていいと思います。

 かなり作り込まれていますし、個人的には私も今後作るであろうスマホゲーにおいて、ぜひ参考基準にしたいゲームです。

SubwaySurferCity_Top

 スマホゲーのUIはどうしても要素が多くゴチャゴチャしてしまいがちで、このゲームも最初はスマホゲーをあまりやらない自分としては少し混乱してしまいました。しかし、慣れてみればスマホゲームの中ではかなりUI/UXがまとまっているゲームだと私は思っているんですがどうでしょうか?

 スマホゲーの一大ジャンルである、ラン・ゲームのひとつの到達点であり、そういう意味でも手本にしたいゲームだと思いました。

 

 それでは実際に遊んでみて思ったことを書いていこうと思います。ただし、いくつかの不満点もありますよ!現状5ステージの全てのトロフィーはコンプリートしてるし、世界ランキングでも100位以内に一回入ったこともあるくらいにはやりましたからね!(まぁ80位とかぐらいですけども)

 

遊んでみて思ったこと

 まずグラフィックはカートゥーン調ですが、最近のスマホゲーの中でも特に良く作られていますよね。それでいて私の古いAndroidでもそれなりに動いているのがすごいなと思います。

SubwaySurferCity_V3CTOR

 アートスタイルに関しては、まぁ日本人好みではないかもしれませんが、個人的には何人か気に入ったキャラがいるのでそういう面でも楽しみました。

 

SubwaySurferCity_SecretStar

 メインのラン・モードですが、各エリアの道中にスターが隠されています。上画像はまさにシークレット・スターを取得した場面です。これを取るとスコア倍率が上がったりキャラによっては強化されたりと、ラン・ゲームのやり込み要素として良いなと思いました。

 ラン・モードはランダムでコースが毎回変わるので、ちゃんと場所というかコースのパターンを覚えておいて、スターを取れる道に的確に進まなければならず、各地のスターを確実に取っていくのは実際けっこう難しいです。

 このやり込みはかなり好きですね。ラン・ゲームって結局ラン・ゲームであって、やることは変わらないのですが、こういうのがあると単に走り続けるぞ!以外のモチベが生まれるので良いやり込み要素だと思います。(取れないと悔しい!)

 

 ただ通常のランをやって思うのが、加速しっぱなしがやっててあまり楽しくないなと思いました。ラン・ゲームは通常、時間経過によって加速していって障害物も増えて生き残るのが難しくなる、というゲームですよね?ある程度耐えるとちょっとゆっくりになって、そこからまた速くなっていくみたいな構成のランゲームがありますけど、このゲームはとにかくずっと速くなっていくだけなので、速くなりきった後のゲームがあまり楽しくないというか、スターとかも取りにくくなっていますし、スマホである以上操作性もそんな良くないゲームでそんなスピーディなゲームやらされてもなぁとは思ってしまいました。

 

SubwaySurferCity_SouthLine

 あと、シティ・モード(上画像)の各コースごとに三つのトロフィーを獲得できるのですが、そのためにはそれぞれ条件を満たさないといけません。時間内にクリアしたり、指定の仕掛けを特定回数作動させるとトロフィーがもらえる、というものになりますが、一部の条件があんま面白くはないんですよね。回数制限系の課題は個人的には結構苦痛でしたね。3回以上ジャンプをしちゃいけない、とか10回以上スワップで道(左、中央、右)を切り替えてはいけない、など。

 こういう系の条件は結局正解となる経路をどう見つけるか、という話になり、何度も同じコースを走ってそれを見つけないといけないのです。コースによっては正解ルートを見つけるのにかなり時間が掛かってしまったりして、そこに関してはまぁやり込みとして楽しめた部分もあれば、苦痛な部分もあったかなぁという感じです。

 

 というわけで文句も多少でましたが、しかし最終的に私はトロフィーをコンプリートするほどに遊んだわけですし、ラン・ゲームとしてはかなり集中して楽しめたので良いゲームだったなぁと思います。

 

コンテンツが少ない?

 いやぁ30時間以上遊べているので自分は十分なんですけども、スマホゲーのコンテンツ量としては少ないということになるのでしょうか?配信開始から一か月もやれば現状の全てのコンテンツは一通り網羅できましたし(キャラクターやボードの解放は別として)、自分はスマホゲーソシャゲはほとんどやらないので基準が自分の中でないですが、現状のコンテンツが少ないという評価は確かにそうなのかもしれません。

SubwaySurferCity_CityMode

 2026年5月までに解放されたシティモードの全ステージをコンプリートしましたし、ELLEというキャラの欲しいコス(いわゆるブルース・リーの黄色いライダースーツ)は手に入れたので、現状やりたいことはやってしまって、ひとまず個人的にはゲームをクリアした、という感じです。

SubwaySurferCity_ELLA

 とにかく、現状のスマホゲーム界でもっとも良く出来たゲームのひとつだと思いますし、三十時間以上は余裕で遊べるのですから私としては十分に面白いゲームです。そういう最先端のゲームがUnityで作られているのは素晴らしい!スマホゲームの進化を実感したい人にもおススメです。

 

enabledやSetActiveにまつわるMonoBehaviourの挙動についてまとめる。

MonoBehaviourのenabledとは、そのコンポーネントの無効有効を切り替えるbool値であり、SetActiveはそのゲームオブジェクトの有効無効を切り替えるためのメソッドです。これらによってMonoBehaviourやUnityイベント関数がどういう影響を受けるか?という基本的かつ最重要で面倒くさいトピックを今回の記事でまとめてしまおうと思います!

 ※追記 : SetActiveはコンピュータの電源を切り替えるようなものであり、enabledはそれとはまったく違う性質を持ちます。enabledがfalseでも、そのコンポーネントは完全停止するわけではなく(つまり電源での例えで言うなら通電はしている)、あくまでPlayerLoopからは除外されているだけ、ということになります。説明が難しいですが、これらの性質がかなり違うことは理解しておくべきだと思います。

 

enabledはUnityイベントを止める(Awake以外)

AwakeTest_EnabledFalse

 まずenabledについてですが、インスペクター上で左上のチェックボックスで切り替えることのできる、そのコンポーネントの動作をON/OFFするものですが、完全に停止するものではありません。(つまり、どういうこと?)

 有効か?無効か?というのは実際にはちょっと曖昧な概念ですよね。enabledは厳密に言えば、そのシーン中でそのコンポーネントが自律的に動作するか?Player Loopの干渉を受けるかどうかを決定するbool値です。

 

 そのゲームオブジェクト及びコンポーネントの生成自体は行われているのでAwakeはゲームオブジェクトが有効であれば(つまりactiveSelfがtrueなら) 実行されます。

 ただし、シーン開始時にゲームオブジェクト自体が無効だと、過去記事で言及したような現象が起きるので注意が必要です。

 

 enabledがfalseだと、OnEnable, Start, Update, FixedUpdate, LateUpdateといった、これらのUnityイベント関数は動きません。

AwakeTest_EnabledFalse_Result

 TesterAとTesterBの二つのゲームオブジェクトにそれぞれAwake, OnEnable, StartでDebug.Logを実行するスクリプトをつけ、TestBはエディタ上でenabledを無効にして実行すると、TesterAは全て実行されるのに対してTesterBはAwakeしか実行されていません。ゲーム実行中にTesterBのenabledを有効になると以下のようになります。

AwakeTest_EnabledTrueAtRuntime

 まずTesterBのコンポーネントが有効になったことでOnEnableが実行され、次にStartが実行されました。つまり、こういうことです。

 

 少しややこしいこと言うと、Awakeでenabledを無効にすればOnEnable以下のイベント関数は実行されませんし、OnEnableで無効にすればStartや各Update関数が実行されない、ということになります。つまり、どこまで実行してどのイベント関数を止めておくか?ということを選択できるわけですが、混乱の元ではあります。

 

 そして、そのスクリプトが完全に停止したわけではなく、外部から呼び出されるpublicメソッドや、OnTriggerEnterなどの物理システムから呼び出される処理は実行されます。つまり、自律的に動いていないだけで存在する以上動くことはできる、ということになります。

 

 ある物理オブジェクトのFixedUpdateによる処理をenabled = falseで止めても、他の物理オブジェクトと衝突してOnCollisionEnterが呼び出される、という挙動を実現したいのならenabledは便利です。もし、そうでなければOnCollisionEnterにて、

if (enabled == false)

    return;

のような早期リターンを使うか、コライダーを無効にするなどの対策が必要になります。これはつまり、そのコンポーネントが動かなくなっただけで、そのオブジェクトのRigidbody自体は生きているので、そこから物理イベントは呼び出されるということなのですが、enabledをfalseにしたんだから動かないんじゃ!?と混乱しがちなんですよね。

 

SetActiveはゲームオブジェクト全体のON/OFF

AwakeTest_ActiveSelfFalse

インスペクター左上のチェックボックスで切り替え可

 そして、SetActiveはそのゲームオブジェクトをゲームオブジェクト全体の有効無効を切り替えるメソッドであり、activeSelfでその状態を確認できます。つまりゲームオブジェクトの動作が停止しているということで、実質そのゲーム中から消えたようになります。(ヒエラルキー上では灰色で名前が表示される)

 全てのコンポーネントの動作が停止しているので、前述のenabledを無効にしたけど、Rigidbodyは健在だからOnTriggerEnterやOnCollisionEnterが呼び出される、というような挙動はなくなります。ただし存在自体はしているし、publicメソッドは呼び出されたら動く、ということには注意が必要です。

 

 なので、activeSelfがfalseの時に動作させたくないメソッドがあるなら、やはりそういう風にコーディングする必要があります。SetActive(false)はそのゲームオブジェクト全体の動作を停止させるものであり、消えたように見えるのはレンダラーが機能停止し、物理システムから外れるのはRigidbodyが停止したからです。しかし、ゲームオブジェクト自体はシーン中に残っているし、スクリプトも健在である、ということを忘れてはいけません。

 

OnEnableについての補足

 OnEnableについて再確認すると、そのコンポーネントが有効になった時(つまりenabled = trueかSetActive(true)された時)に毎回実行されるイベント関数です。何度もenabledを切り替えるような場合は何回も実行されることになり(OnDisableも)、初期化のためのコードが書かれることが多いと考えられます。

 最初から有効だったらAwakeの後に一回目が実行されますし、無効だと最初に有効にされた時に実行されることになります(そしてStart関数も)。

 

まとめ

 今回書いたことは自分への戒めとして書きました。これらは基本的な挙動ではありますが、やはり忘れがちですし、混乱しがちだからです。実際に私は最近、ゲームオブジェクトが無効でもAwakeは実行されるんだ、とenabledが無効の時の挙動と勘違いしちゃいましたからね。

 最近はenabledを切り替えることでゲームオブジェクトをコントロールするコードを書くことが多かったので、これらの挙動は特に問題になる、ということで整理をしてみました。

 以前書いたシーン実行時の状態によってイベント関数の順序が親子間で変わる問題も合わせて気に留めておきたい挙動だと思います。

 

 

ゲーム制作者本人がやるテストプレイの落とし穴について

ゲームを作っていて当然、自分でテストプレイして試したり確認するわけですが、テストプレイ自体に様々な注意点が存在することを痛感したので少し書きます。

 

テストプレイの罠

 テストプレイと言っても、バグを見つけるためだったり、実際のゲームプレイを確認するためだったり、追加した機能を確かめるものであったりと目的は様々です。

 Unityなどのゲームエンジンでは制作エディタからゲームを実行してプレイできる機能があり、作りながら随時プレイして色々とこまめに確認していきます。

 

 まず、ゲームプレイ自体を確かめるためのテストプレイの場合、制作者は全ての情報を知っているし、制作意図や答えを知っているわけで、実際のプレイヤーのプレイとは違うものになり得る、というのはお馴染みですよね。これはテストプレイにおける代表的な落とし穴なわけですが、やはり作りながらテストプレイするとこの意識が抜けてしまうのが現実だと思います。

 そして製作者は意識的にも無意識的にも自分の理想的なプレイを想定しており、それに沿ったテストプレイをしてしまう、というのが一番の注意点だなと最近よく思います。

 

 こういう風にプレイしてもらいたい、こんなプレイをすると楽しいはずだ!と思うのはいいんですけど、プレイヤーはきっとこういう風に遊んでくれるはず!と思い込んでしまうのは危ないですよね。

 実際のプレイヤーはそんなの知らないし、関係ないし、従う必要もない。仕様上できることは何でもやろうとするのが、現実世界のゲームプレイヤーなのですから。

 

都合のいいテストプレイ

 次に(これが今回の本題になります)、単純に制作のためのテストプレイにも落とし穴は存在すると気づきました。それはつまり新しい機能を作ったのでそれがちゃんと動くか確認しよう、というテストプレイです。

つまり、制作者は自身の都合の良いようにプレイしてしまう、ということです。前述した、神の視点でプレイしてしまい、現実のプレイヤーの視点とはかなり異なるテストプレイをしてしまう、というものとはまた別の問題になります。

 

 詳細は省きますが、私が体験した実例を書きます。3Dアクションを作っていて、プレイヤーキャラがやられると死亡演出が入り、リスポン地点にワープしてゲームを再開する、という流れを作っていました。

 とりあえず、プレイヤーの体力がゼロになればいいので敵の攻撃が当たるところまで来て放置して確認、というテストプレイです。それで無事に動作が上手くいったのですが、じゃあ現実的なゲームプレイではどうか?となると当然に問題が発生しうる状態で、この機能を追加してしばらく後にこれに気づきました。

 

 つまり、この3Dゲームはマウスでカメラを操作するのですが、死亡する瞬間にマウスを動かしていると、その入力が処理の都合で残ってしまい、復活した後にその入力によってカメラが思いっきりズレてしまうのでした。

 なぜこうなったのかと言えば、単純にまず死亡演出は新しい機能であり、既存のコードがその処理を考慮していなかったので、それに対応する追加コードを書く必要があったということになります。まぁこれは当然のことなのですが、問題は何故気づくのに遅れたのか?ということです。

 

 それは機能を追加した時のテストプレイでちゃんと現実的にプレイしていなかったから。なぜならそっちの方が私の都合が良かったからです。とりあえず、プレイヤーがただ敵の攻撃にやられてくれれば良かったので。

 実際のプレイではプレイヤーは当然動くし、カメラも動かしているので、その最中にやられることの方が多いと思います。しかし、私は単にプレイヤーがやられて死亡演出が起き、ワープをしてゲームを再開する、という流れを確認したいだけだったし、カメラを動かさず単に敵の攻撃に突っ込んでやられればそれで良かったのです。

 

まとめ

 というわけで、制作者は自分の都合が良いようにプレイしてしまう、ということはこういうことです。それは精神的に楽だから、かもしれませんし、技術的な理由からかもしれません。あるいはここは面倒だから見て見ぬふりをしてとりあえず表層的な部分だけ確認しよう、かも。

 とは言え、今確認すべきことだけを確認する、というのは間違いでもありません。問題は無意識的にこれをやってしまうことだろうなと思います。

 

 個人制作である以上、こうした危険性は常に意識しつつゲーム制作を行っていきたいですね。最近はかなり真剣にゲームを作っているので、こういう切実な学びが多いと実感しています!

 

 

シーン読み込み時のオブジェクトのアクティブ状態によって、親子間のAwake(OnEnable)実行順が変わる件について

タイトルの付け方が難しく、こう書くしかなかったのですが、まさにこの通りなのです。つまり、シーンが読み込まれた時にそのオブジェクトが非アクティブ状態だとゲーム中にアクティブになった際に、そのオブジェクトと子オブジェクトのAwakeが実行される順番が変わってしまう、というのが今回の内容になります。

 

 まずAwake関数について。『インスタンス生成時に実行される』ということですが、実際にはシーン読み込み時に非アクティブ状態だと実行されません。

AwakeTest_InactiveObjectInScene

非アクティブ状態のゲームオブジェクトは灰色で表示される

 つまりシーンに置かれたゲームオブジェクトが非アクティブだと、そのシーンが読み込まれ、そのオブジェクト及び各コンポーネント生成されたとしてもまだAwakeは実行されないということです。まさにAwake(目覚め)なのですから、オブジェクトがまだ目覚めていない時に実行されないという挙動について納得はできます。

 

 しかし、子オブジェクトを持つオブジェクトでは大きな挙動の変化が発生し、それが今回の本題です。というわけでUnity上での実験を例に見ていきます。スクリプトは省略しますが、Awake, OnEnable, StartそれぞれにDebug.Logを書いただけのもので関数名とオブジェクト名、時間をコンソールに表示します。TesterAとTesterBを用意してBには子オブジェクトのTesterB_Childを追加し、これらのゲームオブジェクトに実験用のスクリプトを追加しています。

AwakeTest_Hierarchy

 アクティブ状態だと、まず子オブジェクトのAwake, OnEnableが実行された後で親オブジェクトつまりルートにあるオブジェクトのAwake, OnEnableが実行されることになります。この例の場合TesterAから実行され、その後TesterBの子オブジェクトのAwake, OnEnableから実行されています。

AwakeTest_ResultOfActive

 つまり、親子関係を逆向きに実行されるということですね。これは多くの場合、親オブジェクト的に子オブジェクトが初期化された後で、GetComponentsInChildで参照を取得する等のことをやりたいわけですから都合が良い挙動になります。

 

 ここからが問題です!つまり、非アクティブ状態で読み込まれていたオブジェクトをゲーム実行中にアクティブになるとどうなるか?ということです。TesterBをエディター上で非アクティブにしてシーンを実行してみます。

AwakeTest_InactiveState

 シーンが読み込まれ、そのオブジェクトが生成されるもヒエラルキー上では灰色の文字で表され、Awake, OnEnableはまだ実行されません。これは前述の説明の通りです。

AwakeTest_OnlyTesterA

TesterAのAwake, OnEnable, Startだけが実行される

 では、非アクティブ状態で始まったオブジェクトをゲーム中にアクティブにするとどうなるか?このオブジェクトが子オブジェクトを持っている時、つまり親子関係がある時に今回の現象が発生します。今回の実験では、つまりTesterBをシーン実行中にアクティブにするとどうなるか、ということです。

AwakeTest_ActivateTesterB_AtRuntime

 上画像のように、まず親オブジェクト(例ではTesterB)のAwake, OnEnableが実行された後で、子オブジェクトのAwake, OnEnableが実行されます。つまり、逆の順番で実行されたことになります。これは明らかに問題を引き起こし得る挙動です。

 

 AwakeやOnEnableには初期化するコードが書かれること多いと思います。もし、子オブジェクトの初期化が済まないうちに親オブジェクトが子オブジェクトに対して何らかの処理を実行すれば参照エラーを発生するでしょう。

 Unityのイベント関数のタイミング問題というのは代表的な落とし穴ではありますが、実行順が逆になる、というのは今まで意識したことがありませんでした。(遭遇していたけど認知は出来ていなかったのかもしれません)

 

 これに対する一番現実的な対策はエディタ上でアクティブ状態を設定しないで、スクリプト上のStart内でSetActiveを使う、だと思います。つまり、初期化を行った後で非アクティブにする、ということですね。

 

 ゲーム中にSetActive(true)で初めてアクティブにしたゲームオブジェクトが何かおかしい!という時にはこの挙動が原因かもしれない、ということをお伝えできればなと思います。