Search Unity

ScriptableObject を使ってシーンワークフローを改善しよう

, 7月 1, 2020

Unity で複数のシーンを管理することは困難であり、このワークフローを改善することは、ゲームのパフォーマンスとチームの生産性の両方の観点から、非常に重要です。ここでは、大規模なプロジェクトに合わせた方法でシーンのワークフローを設定するためのヒントをいくつか紹介します。

ほとんどのゲームには複数のレベルがあり、レベルには複数のシーンが含まれていることがよくあります。シーンが比較的小さいゲームでは、プレハブを使用して異なるセクションに分割することができます。しかし、ゲーム中にそれらを有効にしたり、インスタンス化したりするには、すべてのプレハブを参照する必要があります。つまり、ゲームが大きくなり、これらの参照がメモリ上でより多くのスペースを占めるようになると、シーンを使う方が効率的になるということです。

レベルは 1 つ以上の Unity シーンに分割することができます。それらをすべて管理する最適な方法を見つけることが重要になります。エディターでは、複数のシーンを開いたり、複数シーン編集を使って実行時に複数のシーンを開くことができます。レベルを複数のシーンに分割することで、Git や SVN、Unity Collaborate などのコラボレーションツールを使う時にマージ時のコンフリクトを回避できるので、チームワークが楽になるという利点もあります。

複数のシーンを管理してレベルを構成する

以下のビデオでは、ゲームのロジックとレベルのさまざまな部分をいくつかの異なる Unity シーンに分割して、レベルをより効率的にロードする方法を紹介しています。そして、これらのシーンをロードするときに、追加シーン読み込みモードを使用して、永続的なゲームロジックと一緒に必要な部分をロードしたりアンロードしたりします。プレハブをシーンの「アンカー」として機能させる形で使っていますが、これは、各シーンがレベルの一部を表し、個別に編集することができるので、チームで作業する際にも高い柔軟性を発揮します。

編集モードでこれらのシーンをロードして、いつでも再生モードに入ることができるのは変わらないので、レベルデザインを作成するときにすべてのシーンを一度に見渡すことも可能です。

これらのシーンをロードするには、2 つの異なる方法があります。最初の方法は距離ベースの方法で、オープンワールドのような屋外のレベルに適しています。このテクニックを使うときは、いくらか視覚効果(例えば霧など)を使ってロードとアンロードのプロセスを隠すことも有効です。

2 番目の方法は、どのシーンをロードするかをチェックするためにトリガーを使用する方法で、これは屋内のレベルに関して最初の方法より効率的な方法と言えます。

これでレベルの内部ですべてのシーンを管理できるようになりました。ここからさらにレイヤーを追加して、レベルのより良い管理を実現することができます。

ScriptableObject を使ってゲーム内で複数のレベルを管理する

私たちは、ゲームプレイの間じゅう、すべてのレベルに加えて、各レベルのさまざまなシーンを追跡したいと考えています。これを行う方法の一つとして、MonoBehaviour スクリプトで静的な変数とシングルトンパターンを使用することが考えられますが、この方法にはいくつかの問題があります。シングルトンパターンを使用すると、システム間が密接に繋がってしまい、厳密な意味でモジュール化されなくなってしまうのです。システムは別々に存在することはできず、常にお互いに依存する形になります。

もう 1 つの問題は、静的な変数の使用です。これはインスペクターには表示されないため、設定のためにコードを変更する必要があり、アーティストやレベルデザイナーがゲームを簡単にテストできなくなります。異なるシーン間でデータを共有する必要があるときは、静的な変数と DontDestroyOnLoad を組み合わせて使うやり方をよく見かけますが、DontDestoryOnLoad は可能な限り避けるべきです。

異なるシーンの情報を格納するには、主にデータを格納するために使われる、シリアライズ可能なクラスである ScriptableObject を使うことができます。GameObject にアタッチされたコンポーネントとして利用される MonoBehaviour スクリプトとは異なり、ScriptableObject は GameObject にアタッチされないため、プロジェクト全体の異なるシーン間で共有することができます。

この構造をレベルだけでなく、ゲーム内のメニューシーンにも使えるようにしたいものです。そのためには、レベルとメニューの間の共通のプロパティを含む GameScene クラスを作成します。

このクラスが継承するのは ScriptableObject であり、MonoBehaviour ではないことに注意してください。ここには、ゲームで必要なだけのプロパティを追加することができます。このステップの後、先ほど作成した GameScene クラスを継承した Level クラスと Menu クラスを作成することができます。これらのクラスもまた、ScriptableObject です。

最初に CreateAssetMenu 属性を追加すると、Unity の Assets メニューから新しいレベルを作成することができます。Menu クラスでも同じことができます。また、インスペクターからメニューの種類を選択できるようにするために列挙型を含めることもできます。

レベルとメニューを作成できるようになったので、参照しやすいようにレベルとメニューを一覧表示するデータベースを追加してみましょう。また、プレイヤーが現在いるレベルを追跡するためのインデックスを追加します。そして、新しいゲームをロードしたり(この場合は最初のレベルがロードされます)、現在のレベルをリプレイしたり、次のレベルに進むためのメソッドを追加できます。これら 3 つのメソッドの間ではインデックスのみが変化するので、インデックス付きのレベルをロードするメソッドを作成して、それを使い回せるということに注目してください。

メニューのためのメソッドもあり、前に作成した列挙型を使用して、必要な特定のメニューをロードできます。列挙型の順序とメニューリストの順序が同じであることを確認してください。

これで、プロジェクトウィンドウで右クリックして Assets メニューからレベル、メニュー、データベースの ScriptableObject を作成できるようになりました。

あとは、必要なレベルやメニューを追加して、設定を調整してから Scenes データベースに追加していくだけです。下の例では、Level1、MainMenu、そして Scenes Data を追加したところを示しています。

では、これらのメソッドを呼び出してみましょう。この例では、プレイヤーがレベルの最後に到達したときに表示されるユーザーインターフェース(UI)上の次のレベルボタンが NextLevel メソッドを呼び出しています。ボタンにメソッドをアタッチするには、Button コンポーネントの On Click イベントの「+」ボタンをクリックして新しいイベントを追加し、次に Scenes Data の ScriptableObject をオブジェクトフィールドにドラッグアンドドロップして、以下のように ScenesData から NextLevel メソッドを選択します。

他のボタンについても同じ処理を行うことができます。ボタンごとに、レベルをリプレイしたり、メインメニューに移動したりなどの動作を設定することができます。また、他のスクリプトから ScriptableObject を参照して、BGM 用の AudioClip や後処理のプロファイルなど、さまざまなプロパティにアクセスし、レベル内でそれらを使用することもできます。

プロセスのエラーを防止するためのヒント

  • ロード/アンロードをできるだけ控える

動画で示されている ScenePartLoader スクリプトでは、プレイヤーがコライダーに複数回出入りし続けて、シーンのロードとアンロードが繰り返しトリガーされていることがわかります。これを避けるために、スクリプト内のシーンのロードとアンロードのメソッドを呼び出す前にコルーチンを追加し、プレイヤーがトリガーから離れた場合にコルーチンを停止させることができます。

  • 命名規則

もう 1 つの一般的に通用するヒントは、プロジェクトの中でしっかりとした命名規則を使うことです。スクリプトやシーン、マテリアルやその他諸々の物に至るまで、プロジェクト内のさまざまな種類のアセットにどのように名前を付けるかについて、チームで事前に合意しておく必要があります。そうすることで、皆さんご自身だけでなく、チームメイトがプロジェクトで作業したり、プロジェクトを保守したりすることが簡単になります。このような習慣はどのような場合にも良いアイデアと言えるものですが、今回の例のような ScriptableObject を使ったシーンの管理を行う場合には特にシビアに守るべきものとなります。今回示した例ではシーン名をベースにした簡単なアプローチを使用しましたが、シーン名に依存しない方法も多数あります。文字列ベースのアプローチは避けるべきです。それは、ある特定の文脈に基づいて Unity シーンの名前を変更すると、ゲームの別の部分ではそのシーンがロードされなくなってしまうからです。

  • カスタムツールの作成

ゲーム全体で名前の依存関係を回避する方法の 1 つは、スクリプトを用意してシーンを Object タイプとして参照することです。これにより、インスペクターでシーンのアセットをドラッグアンドドロップして、スクリプトで安全に名前を取得することができます。しかし、これはエディタークラスであり、実行時には AssetDatabase クラスにアクセスできません。エディターで動作させてヒューマンエラーを防ぎつつ、実行時にも動作するソリューションとするために、両方のデータを組み合わせる必要があります。シリアライズ時にシーンのアセットから文字列パスを抽出し、実行時に使用するためにそれを保存できるオブジェクトを実装する方法の例については、ISerializationCallbackReceiver インターフェースを参照してください。

さらに、カスタムインスペクターを作成して、メニューから手動でシーンを追加して同期を保つのではなく、ボタンを使って素早く簡単にシーンをビルド設定に追加できるようにする方法もあります。

このタイプのツールの例として、JohannesMP による素晴らしいオープンソース実装をチェックしてみてください(これは Unity の公式リソースではありません)。

ご意見をお聞かせください

本記事では、複数のシーンと Prefabs を組み合わせて作業する際に ScriptableObject を使うことでワークフローを改善する方法の例を紹介しました。ゲームによってシーンを管理する方法は大きく異なります。すべてのゲームの構造に適した、たった 1 つのソリューションというのはありません。プロジェクトの構成に合わせて独自のカスタムツールを実装することは、非常に理にかなっています。

今回お届けした情報が皆さんのプロジェクトにとって有益に働いたり、あるいは皆さんが独自のシーン管理ツールを作成するためのヒントになったりすることがあれば幸いです。

26 replies on “ScriptableObject を使ってシーンワークフローを改善しよう”

This article was interesting. Particularly the idea of dropping build time from 3 hours to 3 minutes. Can Unity rework some of it’s previous tutorials (rollerball, stealth, flyers) around this code model. It would be nice to see compare the architectures and see how using SO’s clean up the code, and what new ones they introduce.

Hi, it’s a bit off-topic, but could you please add likes/dislikes to the comment sections of blog posts? A lot of the readers have Unity accounts, which could be used to make our reactions.

It is always good to read and learn new information. Keep writing such good and informative articles so others can benefit from it.

That’s nice, sadly you cannot use ScriptableObjects as project-wide independant singleton (at least for WebGL), because it gets stripped! So you waste a day trying to understand why you access a null ref when not in Editor Play. To workaround that you absolutely need to reference the ScriptableObject instance in a GameObject somewhere.
For more information about ScriptableObjects you should check:

https://unity3d.com/how-to/architect-with-scriptable-objects
https://www.youtube.com/watch?v=raQ3iHhE_Kk
https://devansh.space/scriptable-objects-and-event-systems
https://medium.com/@adamramberg/unity-atoms-tiny-modular-pieces-utilizing-the-power-of-scriptable-objects-e8add1b95201

You can. You’d have to ensure that the SO is loaded from the start. One way to do it is using SetPreloadedAssets. A good example of that is Unity’s own Input System:
https://github.com/Unity-Technologies/InputSystem/blob/56e5c49289402898806927c1783d265f4989b9cc/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsBuildProvider.cs

If you already had another way to load the script (like resources, or an asset bundle, or referencing it from a scene) you can prevent it from being unloaded by setting the DontUnloadUnusedAsset Hideflag.

That being said, if you’re already able to put your singleton in a SO, I bet it wouldn’t be hard to get rid of the singleton pattern in the first place. Singletons can be very evil when you try to expand the mechanics of your game. SOs substitute them very gracefully, because you can just drag them to the inspector when you need to reference Singleton-Like object and at the same time you can have many of them available in any moment.

Glad to see multi scenes are getting some attention, as well as ScriptableObjects! (My favorite thing). This post is a good start, although it avoids most of the complications some others have mentioned. Regardless, I am a major proponent of ScriptableObject based scenes, as it is what I use in my game. In my case, I have multiple maps, which are split into chunks. In the end, I have around 500 chunks at the moment.

The key part of using this system is implementing editor tools to automate these sorts of things, which was mentioned.

If anyone’s interested, here’s some explanations on a scaled up example of using this strategy: https://www.ardenfall.com/blog/world-streaming-in-unity

That’s a great article, Joshua. I think one of the beauties of Unity is exactly that you can read about all of these different solutions, and then really make one that fits your needs. So thanks for sharing yours!

Great article, glad you’re advocating for the multi-scene approach. So many benefits!

Another HUGE benefit of breaking your game into lots of scenes is Asset Bundles. The workflow of sticking scenes related to a piece of content into an AssetBundle and just downloading it and Loading that scene is incredibly powerful in Unity, one of the most forward-scaling features imaginable, especially for small content developers. It’s just really easy to make ABs using Scenes. I’ve not tinkered with Addressibles yet, but I assume they’re analogous.

this is a Article i want to see on the Unity Blog. Pls more!
Is it possible to extend this tutorial for huge open world levels withe a Float Point Problem?

Nice start, but feels and looks like you’re solving a problem in isolation without considering all of the other complexities you might encounter in a game. How do you tackle lighting bakes? AI pathing? Persistent changes in scenes that get unloaded? How does it scale with really complex scenes?

I’d love to see something like this but built on top of a much more complex project – you can gloss of the technical details of how you solved some of those additional issues and maybe even cover some as additional blog posts, but the bare bones demo doesn’t give me confidence that this concept would survive coming into contact with a real game.

This, sadly. The only problem was the lack of details, so it’s more like a toy or an experiment than a solution. It’s a good idea, but knowing Unity some small piece of it may not like this technique without a lot of boilerplate code and corner cases.

That’s a fair consideration. As with many other comments on this post, the answer is that for the blog post we had to strike a balance between simple (and easy to explain) and realistic. We wanted to give the reader not a complete solution, but the tools to get started with one – which only they can make.
When you move more into the realistic realm, you inevitably also stumble in solutions that are very specific to the type of game, and we didn’t want to go there – for now.

Now the good news is: we will do something in that vein soon, so please keep an eye on the blog for announcements :)

While I appreciate these sort of posts for “intermediate level” users, I wasn’t entirely convinced by the quality of the pointers here. In fact, I fear it contains a few choices that’ll confuse people who really try to follow (or base work off of) these instructions.

First off, I commend the reliance on ScriptableObjects, which are a very useful tool that doesn’t always get due attention in tutorials, which are still dominated by the omnipresent MonoBehaviour. I think a fundamental note that could be added is that ScriptableObjects (like other assets) contain data on a project level, rather than on the level of a single scene.

Beyond that, however, the rest of the framework promptly forgets about that. The ScenesData ScriptableObject has a list of the previously created Level objects and… doesn’t use it at all. For loading scenes, it suddenly relies on some rigid naming convention even though the Level objects themselves store their associated scene name. It doesn’t take much imagination to picture a designer experimenting with switching items 3 and 5, only to find they’re still loading scene 3, but with the mismatched Level data of the former 5.

In fact, they’ll also be confused at always having to insert a dummy Level object at the beginning of the list, because the first item is somehow always skipped. It’ll take them some time to figure out that’s because the level index starts at 1, even though there’s also an element at index 0.

Also, they’ll probably be somewhat disappointed at how the text hints at an explanation of how to load a scene in different pieces, but the code doesn’t really deal with that. In fact, it only allows them to load Level0Part0, Level1Part1, Level2Part2 and so on, but no other permutations.

Later on, they may well be scratching their head why suddenly all their menus are linked wrong, even though those methods do actually use the scene name stored on the Menu object. Only later on will they discover their colleague added a new menu type at the top of the “Type” enum and now the entire list of Menu objects need to be reordered. Even later, they’ll remember the text warned against that instead of preventing it.

I apologize if I come across as a little overly critical here. It’s just that this same information can be found with higher quality and accessibility elsewhere, which is a weird thing to conclude about the engine’s official blog. I completely understand the desire to not want to overcomplicate things for a tutorial like this, but the code here introduces quite a few practices that are evidently fragile and that will confuse people who use it as a source of information. The linked video comparatively feels like a different level of quality.

Would you be so kind to elaborate on where exactly one would find the “other” source of information about scriptable objects used with scenes? I would be very much obliged.
Thanks!

Aw jeez, I might have been a little hyperbolic with that statement. It’s been quite a while since I’ve looked up tutorials on this particular topic myself. With regard to ScriptableObjects after a cursory internet search, these strike me as quite accessible and “realistic” applications, though really the potential is endless.

https://www.raywenderlich.com/2826197-scriptableobject-tutorial-getting-started
http://gyanendushekhar.com/2019/07/07/working-scriptable-objects-unity-tutorial/
https://www.youtube.com/watch?v=aPXvoWVabPY

With regard to breaking scenes into smaller bits: that tends to very specific to the game at hand. I came across this blogpost which has a succinct description of a world-streaming system. I don’t know if it uses ScriptableObjects anywwhere, but I could easily see them being used much in the same way as this blog post does: to wrap another object with extra data. In this case the “Cell ScriptableObject”, beyond a reference to the scene/prefab itself, could hold information such as “what are the coordinates of this cell”, “is there a settlement here”, “is this hostile territory”, “what music track should play”? This way, you could easily have a list of data for every cell in the world, at the memory cost of just a bunch of tiny ScriptableObjects.

https://www.ardenfall.com/blog/world-streaming-in-unity

Wow, thanks for actually scrutinizing the code, it gave me a good laugh. My bet is that it was written by an overworked engineer who had a million other things to do but was somehow roped into doing a blog post.

hire this man!

blog level target is no excuse for oversimplification. You can target every level by encapsulating in-depth explanation in a “detail” collapsable, like catlikecoder

I wish there was a way to add a scriptable object to the pre-loaded assets list from the scriptable object’s inspector. (without me having to write it)

It is posible with this approach to bake Realtime global illumination information, for each scene separatelly, and then load it additivelly? We have a big open world, and bake the lighting, but we have to load all scenes and bake it togheter in a lighting scene, to then load that scene and keep it loaded all the execution time. There is a better way to achieve this?

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です