ブログを移行しました
一年半ほど放置していたこのブログを読む人がどれくらいいるか分かりませんが、ブログを移動したので報告します。
移行の理由に関しては
- はてなIDがいい物が取れた
- メールアドレスを新調した
- 前回の投稿から期間が空きすぎた
ためです。
割と一番目の理由が大きくて、このアカウントを開設時になぜ調べなかったんだろうと後悔しています。 二番目・三番目の理由もあったので思いきって変更しました。
この古い方のブログはもう少しの間残しておきますが、記事の移行は完了しているため、将来的には消すかもしれません。記事執筆時点では新ブログの方も色々と準備中ですが、今後ともよろしくお願いします。
Unityのシーン遷移は遅くない
はじめに
Unityを使ってるとシーン遷移が遅いってよく聞くので調べてみました。
Time.realtimeSinceStartupでシーン遷移前と後の時間を計ってるだけです。
検証
何もないシーンをSceneManager.LoadSceneで読み込んだ場合
カメラ以外のオブジェクトがないシーンを2つ用意します。(Scene0、Scene1と名付けました)
スクリプトはカメラにアタッチします。
// Scene0.cs // 最初に呼び出されるシーンです // ここから他のシーンを呼び出します using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; public class Scene0 : MonoBehaviour { IEnumerator Start() { // Unityが安定するまで10秒待つ yield return new WaitForSeconds(10); SceneManager.LoadScene("Scene1"); Timer.startLoadScene = Time.realtimeSinceStartup; } }
// Scene1.cs // 何も無いシーンです using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; public class Scene1 : MonoBehaviour { IEnumerator Start() { Timer.endLoadScene = Time.realtimeSinceStartup; float time = Timer.endLoadScene - Timer.startLoadScene; Debug.Log($"Scene1の読み込み時間は{time}秒です。 "); } }
[出力結果]
Scene1の読み込み時間は0.07846355秒です。
Cubeが10000個あるシーンをSceneManager.LoadSceneで読み込んだ場合
新たにScene2を作りCubeを複製して10000個作ります。
ソースコードは先程のをちょっと書き換えるだけなので省略します。
[出力結果]
Scene2の読み込み時間は2.428547秒です。
感想
2秒以上かかりかなり遅いです。
何もないシーンとCube10000個あるとシーンだとかかる時間が全然違います。
シーン遷移が重いというより、オブジェクト読み込みに時間がかかっている(広義的にはそれもシーン遷移だけど(笑))
実際のゲームでは3Dモデルとか画像とかのアセットが大量に読み込まれるので、それが原因だと思います。
追加検証
Cubeが10000個あるシーンをSceneManager.LoadSceneAsyncで読み込んだ場合
気になったので、追加で調べてみた。
SceneManager.LoadSceneAsyncで非同期にシーンを読み込んだ後、シーン遷移単体にはどのくらい時間がかかってるのかを測定。
先程のScene0とScene2を流用します。
Scene0.csは非同期処理に合わせて時間の取り方を修正。
Scene2.csは変更なし。
// Scene0.cs // 最初に呼び出されるシーンです // ここから他のシーンを呼び出します using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; public class Scene0 : MonoBehaviour { IEnumerator Start() { // Unityが安定するまで10秒待つ yield return new WaitForSeconds(10); //SceneManager.LoadScene("Scene2"); AsyncOperation asyncOperation = SceneManager.LoadSceneAsync("Scene2"); // シーンのロードが終わるまで待機 while(!asyncOperation.isDone) { // ループ毎に時間を取ります Timer.startLoadScene = Time.realtimeSinceStartup; yield return null; } } }
[出力結果]
Scene2の読み込み時間は0.5203323秒です。
感想
最初の検証と同じ時間になると思ったけど、結構差が開いた。
オブジェクトが多いとシーンの読み込みが終わってても遷移でごたつく?
それでも2番めの検証の5分の1くらいの時間になってるから効果は大きい。
実際に使う時にはLoadSceneMode.Additiveで追加読み込みにしたりやAwakeでゲームオブジェクト全体を非アクティブにするような工夫が必要だと思う。
終わりに
以前から気になってたけど、ようやく調べられた。
シーン遷移を嫌って1シーンでゲームを完結させるプロジェクトに携わったことがあるけど、シーン分けて作る方法を模索した方がいいと思うんですよね。(途中のシーンから再生できないとか動作の確認大変なので)
次は1シーンだけで作る場合と普通にシーン分ける場合の比較記事各予定です。予定なので守らないかもしれないけど。守りませんでした!
追記
この記事を投稿してからちょうど一年半経ちました。 この記事は思ったよりもアクセスがあり、多くの人に見られました。 だいたいアクセス数が5,000くらいですが、このブログへのアクセスのほとんどがこの記事です。
元々の構想では、この記事の続きでUnityのシーンを高速に読み込むプロジェクトの作り方を書く予定でしたが、まだ書けてません。 後々準備ができ次第書こうかと思います。 ブログの方も新しいアカウントに移行しましたので、こちらをチェックして頂けると幸いです。
【Unity】マルチシーンにしたときに警告が出たので調べてみた
はじめに
SceneManager.LoadSceneAsync()でマルチシーン(複数シーンを同時に開いた状態)にしたとき、以下の警告が出たので調べてみた。
Your multi-scene setup may be improved by tending to the following issues:
Multiple scenes baked with Auto enabled can appear differently in Play mode when reloading them. Consider disabling Auto and rebaking.
環境
Unity 2018.3.7f1
解決
マルチスクリーン周りが原因なのは分かるけど、そんなこと言われてもどこを直せばいいか分からない。
いろいろ調べてたら
原因 調べてみるとLightingのエラーでした。 Lightingの設定をAuto Generateにしていると新しくシーンがロードされるたびにライトがベイクされるようで、「複数のシーンをロードすると、シーンごとに自動設定した光の当たり方と異なるかもしれないけど大丈夫か?」という警告のようです。
【Unity2018】マルチシーンを使うと"Your multi-scene setup may be improved by tending to the following issues"というエラーが出た - ぱふの自由帳
という記事をを見つけたので解決!と思ったけどLighting設定がどこか分からずもう少し探すことに…
Window > Rendering > Lighting Settingsの後、開いたWindowの一番下のAuto Generateのチェックボックスをオフにする。
終わりに
最初、原因が全然分からないから時間かかった。
もう少し警告文が分かりやすければ早かったかな。
同じとこで詰まった人がすぐ見つけられるように、キーワード多めに入れたつもりだから参考になればと願う。
参考
ぱふの自由帳
http://pafu-of-duck.hatenablog.com/http://pafu-of-duck.hatenablog.com/entry/2018/11/18/000022
Unity公式 Lighting ウインドウ
https://docs.unity3d.com/ja/2018.2/Manual/GlobalIllumination.html
【Unity】エディタ拡張で全プレハブからRigidbodyを削除する機能を作ってみた【C#】
はじめに
Unity4からUnity2018にアップデートする過程で100個以上あるPrefabからRigidbodyを外さなければなかったので作った。
ToolsからRemove Conponents From Prefabを選べば使えます。
Rigidbodyを書き換えれば他のコンポーネントにも対応できます。
ソース
using System.IO; using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; class RemoveConponentsFromAllPrefab : MonoBehaviour { private static IEnumerator m_iEnumerator = null; [MenuItem("Tools/Remove Conponents From All Prefab")] static private void StartRemove() { var prefabs = GetPrefabs(); foreach (var p in prefabs) { // StartCoroutineがnon staticで使えないため、MoveNext()を使用 m_iEnumerator = RemoveConponentsFromPrefab(p); m_iEnumerator.MoveNext(); } } static private List<GameObject> GetPrefabs() { Debug.Log("Prefabの取得"); string directoryPath = "Assets"; List<GameObject> assets = new List<GameObject>(); // Asset以下の全ファイルを取得 string[] filePathes = Directory.GetFiles(directoryPath, "*", SearchOption.AllDirectories); foreach (var filePath in filePathes) { // Prefabファイルのみ処理を行う if (filePath.Contains(".prefab") && !filePath.Contains(".meta")) { GameObject asset = AssetDatabase.LoadAssetAtPath<GameObject>(filePath); Debug.Log("Loaded " + filePath); if (asset != null) { assets.Add(asset); } } } return assets; } static private IEnumerator RemoveConponentsFromPrefab(GameObject prefab) { // 非アクティブなオブジェクトのコンポーネントを取得するため引数にtrue var components = prefab.GetComponentsInChildren<Rigidbody>(true); if (components.Length > 0) { Debug.Log("Destroy " +components[0].name+ " in " +prefab.name); foreach (var c in components) { // Destroyでは即時反映されないためDestroyImmediateを使用 DestroyImmediate(c, true); } var go = Instantiate(prefab); //待機処理 while (go == null) { yield return null; } go = PrefabUtility.ConnectGameObjectToPrefab(go, prefab); Debug.Log("Replace " + prefab.name + " with " + go.name); PrefabUtility.ReplacePrefab(go, prefab, ReplacePrefabOptions.ConnectToPrefab); Debug.Log("Destroy " +go.name); DestroyImmediate(go, true); } } }
あとがき
StartCorutine()ではなくMoveNext()を使った方法やPrefabUtility等、学ぶ事が多かった。
もう少し使いやすさを上げてRigitbody以外にも対応させたい。
Unity2018.2では問題ないが、Unity2018.3ではPrefabUtilityが旧形式と警告されているので後々対応したい。
参考
StartCoroutine(MonoBehaviour)を使わずにコルーチンを実行する【Unity】【エディタ拡張】
http://kan-kikuchi.hatenablog.com/entry/Coroutine_Editor
【Unity】【エディタ拡張】スクリプトからPrefabを操作する(Unity2018.2まで版)
http://light11.hatenadiary.com/entry/2018/12/19/220554
InputFieldから絵文字を除く方法
はじめに
InputField内に絵文字があると自動で取り除くスクリプトを作成しました。
絵文字とありますが、厳密にはサロゲート文字か判定してるだけなので全ての絵文字に対応してるわけではありません。
副次的に漢字の異字体等、他のサロゲートペアを使ったものにも対応してたりします。
スクリプト
using UnityEngine; using UnityEngine.UI; public class InputFieldController : MonoBehaviour { [SerializeField] private InputField inputField = null; void Start() { inputField.onValueChanged.AddListener(delegate { OnValueChange(inputField); }); } private void Reset() { inputField = GetComponent<InputField>(); } private void OnValueChange(InputField inputField) { string str = string.Empty; foreach (var c in inputField.text) { if (!char.IsSurrogate(c)) { str += c; } } inputField.text = str; } }
使い方
上記をコピペcsファイルを作ってオブジェクトにアタッチするだけです。後はReset()が勝手にやってくれます。
後からInputFieldをアタッチした場合は歯車(設定)からResetを押すか手動でアタッチします。