20210226のUnityに関する記事は15件です。

実サイズ城のMR表示

環境構築

アプリ

Windows 10
Unity 2019.4.16f1以上
ARCoreサポートしたAndroidスマホ
ARKitサポートしたiOSスマホ

Unity Package

Microsoft Mixed Reality Toolkit 2.4.0
https://github.com/microsoft/MixedRealityToolkit-Unity/releases

AR Foundation 2.1.8
ARCore XR Plugin 2.1.11
ARKit XR Plugin 2.1.9

Hanif-Castle-MR
https://github.com/fermanda/CastleInMixedReality/releases

プロジェクト作成

Hanif-Castle-MRを入れる前に、他のパッケージを入れてください。

MRTK (Assets→Import Package→Custom Package)
AR Foundation, ARCore XR, ARKit XR (Window→Package Manager)
TMPro (Window→TextMeshPro→Import TMP Essential Resources)

全部入れたら、Hanif-Castle-MRを入れてください(Assets→Import Package→Custom Package)
ProjectのAssets→Castle MR→ScenesでARシーンを開いてください。

プロジェクト説明

モデル準備

こちらのプロジェクトで伏見桃山城のモデルを使いさせていただきます。 モデルはこちらのリンクから無料でダウンロードできます。

伏見桃山城3Dモデル

Capture.PNG

普段はUnityで3Dモデルを表示するにはobjファイルをそのままHirearcyで入れたら問題ありません。けれど今回は、表示されるモデルはい実のスケールで表示される予定なので、普段の方法でモデルを表示する場合は表示されたモデルをブレになる可能性があります。なぜなら、objを入れた時、MaterialのResolutionを設定は最大8000までだけができます。別の方法はStreamingAssetsやAssetBundleのオプションがあります。

MRTKでGLTFを表示したい場合は、まずGLTFのフォーマットを対応のため、変更してください。変更方法は、ダウンロードしたモデルをMRTKのコンバーターに入れたら完了です。

MRTKのGLTFコンバーターはこちらでダウウンロードできます。exeファイルだけダウンロードは十分です。
MRTK GLTFコンバーター

言語設定

国際ユーザーへアプリを作る予定場合は複数な言語を用意しないといけません。複数の言語を用意するステップはLocalizationと呼ばれます。Localization方法は複数あります。小さいアプリ場合は一つのコードでDictionaryをいればは完了です。けれど、アプリをもっと大きくなる場合は、テキストを管理することは難しいになります。今回は2つのLocalizationを紹介します。

  • Unity PackageのLocalization

現在、UnityパッケージLocalizationはUnityチームよりまだ開発中ので、今のところはpreviewパッケージだけがあります。 興味がある方はこちらのリンクへどうぞ。

Unity Localization パッケージ

インストール方法

Window→Package Manager→(+)アイコン→ Add package from git URL
「com.unity.localization」を入れて、完了です。

  • JSONのLocalization

またはJSONで複数の言語用意して、StreamingAssetから呼びされた。こちらのオプションはカスタム必要場合はもっとできます。全ての言語はStreamingAssetsフォルダーへ入れて各言語でファイルを用意します。

まずJSONの形を準備します。今回は、鍵(Key)と内容(value)で分裂します。

LocalizationData.cs
[System.Serializable]
public class LocalizationData
{
    public LocalizationItem[] items;    
}

[System.Serializable]
public class LocalizationItem
{
    public string key;
    public string value;
}

鍵はGameObjectの名前にします、でないようは対応の言語です。

LocaleManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using TMPro;
using UnityEngine.Networking;

public class LocaleManager : MonoBehaviour
{
    public string CurrentLanguage = "Japanese";
    public Dictionary<string, string> localeText;
    private IEnumerator coroutine;

    private void Awake() {
        LoadLocalizedText();
    }

    public void ChangeLanguage(){
        if(CurrentLanguage == "English") CurrentLanguage = "Japanese";
        else CurrentLanguage = "English";
        LoadLocalizedText();
    }

    public void LoadLocalizedText(){
        localeText = new Dictionary<string, string>();
        string filePath = Path.Combine(Application.streamingAssetsPath, CurrentLanguage+".json");

#if UNITY_ANDROID && !UNITY_EDITOR
        var path = "jar:file://" + Application.dataPath + "!/assets/" + CurrentLanguage+".json";
#else
        var path = filePath;
#endif
        coroutine = GetLanguagePack(path);
        StartCoroutine(coroutine);
    }

    private IEnumerator GetLanguagePack(string path){
        var loadingRequest = UnityWebRequest.Get(path);
        loadingRequest.SendWebRequest();
        while (!loadingRequest.isDone) {
            yield return null;
        }

        if (loadingRequest.downloadHandler.data != null  && loadingRequest.downloadHandler.data.Length > 0){
            Debug.Log("=====>DebugLog : Data Received");
        }
        else{
            Debug.Log("=====>DebugLog : Data is Empty");
        }

        string JSONdata = loadingRequest.downloadHandler.text;
        LocalizationData loadeddata = JsonUtility.FromJson<LocalizationData>(JSONdata);
        for (int i = 0; i < loadeddata.items.Length; i++){
            localeText.Add(loadeddata.items[i].key, loadeddata.items[i].value);
        }

        UpdateLanguage();
        yield break;
    }

    public void UpdateLanguage(){
        foreach(KeyValuePair<string, string> entry in localeText){
            GameObject.Find(entry.Key).GetComponentInChildren<TextMeshProUGUI>().text = entry.Value;
        }
    }
}

UnityWebRequestは普段Coroutineの内で発生しますので、今回もStreamingAssetsからデータをもらうため、Coroutineで発生します。言語データを処理したら、UpdateLanguage()が実行された。GameObject.Find() を使って言語設定されるはずテキストオブジェクトを選んだ言語に対応、更新します。

Androidのビルド設定

File→Build Settings。Androidを選択して、Switch Platform.
Player Settingを開いてください。Other Settingsの下に、複数のアイテムを設定します。

  • まずは、Vulkan Graphic APIを消します。
    1.PNG

  • 次はScripting BackendはIL2CPPに変更します。 API Compability Levelは「.NET 4.x」に変更します。Target ArchitectureはARM64を選択してください。
    2.PNG

  • 最後はStrip Engine CodeとOptimize Mesh Dataのチェックを消してください。
    3.PNG

  • 終わりましたら、Buildボタンを押して、APKファイルを生成します。

デモ

編集中

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UniRx使用経験の共有

今回の主な話題:UniRx使用経験の共有、特殊効果はテクスチャの合併最適化に値ませんか、AssetBundleのLZMAやLZ4圧縮、Unity 5.6.5のiOSレンダリングに関するクラッシュ問題、Shadows.RenderShadowMapの時間コストの最適化。


ロジックコード

Q1: 正式なプロジェクトでUniRxを使う経験のある人に感想を求めます。この前にいくつのUniRxドキュメントを見たことがあります。しばらく前にQFrameworkを見た時に、これをお勧めしていることがわかりました。今日オープンソースライブラリを見たところ、まだ会いました、人気も高いです。https://github.com/neuecc/UniRx

なので、ドキュメントをよく読んで興味津々です。新会社の枠に入れたいのですが、正式なプロジェクトでは使っていませんから、何かメモリまたは使用法に関する注意すべき点があるかどうかを知りたいです。

“UniRx (Reactive Extensions for Unity) is a reimplementation of the .NET Reactive Extensions. The Official Rx implementation is great but doesn’t work on Unity and has issues with iOS IL2CPP compatibility. This library fixes those issues and adds some specific utilities for Unity. Supported platforms are PC/Mac/Android/iOS/WebGL/WindowsStore/etc and the library.”

設計理念から言うと、Event(Action)のアイデアに基づいて非同期プログラミングの問題を解決し、同時にUnityコルーチンのいくつかの小さな欠陥を補う方は、良いように見えますが、依然としてポーリングまたは既存イベントのカプセル化に基づいたものであります。その後のメンテナンスコストの増加や、ほんとにパフォーマンスに問題がある場合の最適化のコストが心配なので、コミュニティの皆さんに聞きたいです。誰かがプロジェクトで大規模に使う経験のある人はいませんか?利点と欠点を教えてください。Assets Storeでの評価も比較的高いです。

現在のプロジェクトで小規模に使うことがありましたが、何らかの理由で取り替えました。個人的な意見のみを話します、誤りがございましたら、許してください。

1)上手く慣れるまでには時間がかかり、チーム全体の素養には一定の要件があります。特にあまり知っていない場合は、もっと変なコードを書くこともあります。
2)执行の順序はあまり制御できません。特に执行順序に要求が比較的に高い場合、問題が発生する可能性があります。また、デバッグにもあまり直感的ではなく、コードの維持はより困難です。
3)パフォーマンスに関しては、プロジェクトの開始時に狭い範囲でしか使用されていないため、体系的なテストはありません。


レンダリング

Q2: 私たちのゲームの特殊効果の作成方法は、1つのマテリアルが1つのテクスチャに対応し、それらすべてがハッシュ小さなテクスチャとして存在するため、特殊効果の高いDrawCallの問題が発生します。 特殊効果を最適化するために、UIアトラスのアイデアを参照して、特殊効果で使用される一セットのテクスチャを1つのアトラスにまとめることができますか?

利点:DrawCallを効果的に減らすことができます。

欠点:
1)共通のテクスチャが何度も大きな画像にマージされるか、または共通の画像セットが表示されます。一つの特殊効果に複数の大きなテクスチャが必要になる可能性があります。
2)テクスチャのマージにより、必然的にパッケージ本体が増加し、メモリとビデオメモリが元の状況よりも多く占有されます。それが逆最適化を引き起こすかどうかはわかりません。

2つのTencentゲームを参照すると、それらの特殊効果マップはハッシュされているように見え、マージされていません。皆さんのプロジェクトの特殊効果DrawCallはどのように最適化するか、類似の最適化またはより良い最適化アイデアがあるかをお聞きしたいと思います。 感謝します!

個人的には特殊効果のテクスチャマージの価値はあると思いますが、理想的なマージ方法はちょっと難しいです。 その理由は、特殊効果のテクスチャは通用性がありますが、集約性が不十分であるためです。 たとえば、UIのテクスチャは、共有テクスチャと特有テクスチャを区別できます。複数の共有テクスチャをマージしたり、各システムインターフェイス内のテクスチャをマージしたりできるため、これで論理的に集約性があります。
特殊効果テクスチャの構成構造は、通常、下図に示すフォルダの区別方法など、効果によって区別されます。
1.png
Bloomエフェクトのやつをマージするなど、この種のフォルダメソッドに従ってマージします。しかし、例えば、一つのスキルでスモーク効果とスパークル効果がついてある場合、必ず二つフォルダのテクスチャを使用する必要があり、DrawCallも低下させられません。単一特殊効果にマージをするのは、問題主が言う欠点であります。冗長性が高く、全体的な状況を考慮することはできません。 これは、ほとんどのゲームが特殊効果テクスチャに対してマージをする状況はあまりない理由でもあります。

私たちのやり方はデフォルトでハッシュすることであり、長期間存在し、DrawCallに大きな影響を与える特殊効果については、特定の最適化(特定アトラスの使用、高低配置のパーティクル表示を制御することなど)を行います。一般的な特殊効果にはマージされません。

開発時間が許せば、ゲーム特殊効果のDraw Callをマージすることをお勧めします。特にほぼ同じ頻度の特殊効果に対します。パーティクルシステムについては、Texture Sheet Animation機能を考慮することをお勧めします。これについては、今年のUWA DAYで詳しく説明しました。Googleでその使用法を検査できます。
2-1.jpg
ただし、説明すべきことは、複雑な状況(Mobaでのチームバトルなど)では、依然として特殊効果の重なり合うことが回避できません。この場合、Draw Callの占用は高める可能性もまたあります。したがって、上記の方法は確率的にDraw Callの使用を減らすとしか言えず、どれだけ減らすことができるか、開発チームが自分のプログラム次第で確認してください。


クラッシュ

Q3: iPhone Xを使用して外部ネットワークで実機テストを行うと、クラッシュが発生しました。具体的な表現は、ゲームが突然フリーズし、時には画面が真っ暗になり、しばらくすると自動的に終了することです。クラッシュ後すぐにBuglyを更新すると、クラッシュの数は+1になり、クラッシュ時間は一致します。

ワールドボスと戦う途中にフラッシュしました。どのモデルに換えてもフラッシュしましたが、内部ネットワークで再現できず、外部ネットワークも閉鎖されました。一見するとGPUレンダリングに関するフラッシュのようですが、内部ネットワークで再現できないため、Bugが解決されているかどうかを検証できません。皆さんはこのような問題に遭うことがありませんか?どうやって解決しましたか?

具体的な状況:
Unity 5.6.5
XCode10.x
iPhone 7Plus、iPhone X、iPhone XS Maxで全部フラッシュしました。

私は以下の方法を試しました:
1)Unity 5.6.6にアップグレードしても、外部ネットワークでまだクラッシュしました。
2)Unityの設定を確認しました。OpenGLES 2.0でレンダリングされていました。

私の質問は、Metalレンダリングを使用しないように設定しましたが、Metalレンダリングのインターフェイスのように見えるAppleMetalGLRendererにアクセスするのはなぜですか?

多くの調査と検証の結果、ついに原因を見つけました。

command bufferを使ってシャドウをレンダリングすると、レンダリングしたいMeshを取得する必要があります。変更後、再びCombineMeshを行います。CombineMeshのフレームで、Meshは「中間状態」にあります(UnityのBugである可能性があります)。このフレームにcommand bufferを使ってレンダリングされたシャドウも一つの不規則な形状になり、XCodeがエラーを報告します。また、クラッシュを引き起こす可能性もあります。

解決方法:CombineMeshのフレームでシャドウをレンダリングしなくなり、次のフレームにMeshをレンダリングする準備ができるまで待機します。 このようにして、シャドウの不規則な形状は表示されず、XCodeはエラーやクラッシュを報告しません。


アセット管理

Q4: UnityはLZMAファイルのダウンロードプロセス中にファイルをLZ4の形式でキャッシュに保存するので、LZMA形式のファイルをダウンロードした後にファイルを保存するときに、LZ4のAssetBundleパッケージ圧縮形式に変更してローカルに保存することはできますか?
3-1.jpg

ファイルを保存するときに、LZ4のAssetBundleパッケージ圧縮形式に変更してローカルに保存します。このアイデアは非常に新鮮で、考えたことはありませんでした。

ただし、LZMAからLZ4に切り替える場合は、直接のインターフェイスがないはずです(これまで見たことがありません)。次に、LZMA解凍してから、LZ4に圧縮する必要があります。LZMAの圧縮率は非常に高く、解凍時間のコストは少し高いです。圧縮プロセスを追加するとより長くなりやすいです。ですから、このソリューションは変換時間のコストが高いという問題があると思います。問題主はそれが実装されているかどうかを評価してから、テストデータに基づいてソリューションを評価する必要があります。テストデータが最も説得力があります。

一般的なアプローチは、LZ4を直接使用し、再び処理や圧縮を行わないことです。1つは非圧縮形式で、7zipなどのツールを使用して圧縮します。圧縮率は自分で選択し、高い圧縮率を使ったらLZMAよりも小さくなる可能性があります。もちろん代償もあります、圧縮率が高いほど解凍時間が長くなります。現在、非圧縮を使用しております。圧縮ツールで直接圧縮することは、解凍時間が少し長いと感じているため、圧縮率のバランスをとったり、LZ4に変更したりすることができます。


レンダリング

Q5: コードスタックのShadows.RenderShadowMapは時間がかかりますが、どのように最適化するのですか?
4.png

リアルタイムシャドウを使用する場合、シャドウを投影するオブジェクトは追加のレンダリングを一回受け、シャドウを受け取るオブジェクトもサンプリングされたシャドウ強度の計算を増やすため、レンダリング時間への影響は比較的大きくなります。問題主の時間コスト比率はまだ比較的高いので、シーン内でシャドウを投影/受けるオブジェクトの数を手動で制御することを考慮できます。キャラクターだけがシャドウを必要とする場合は、FastShadowReceiverでProjectorを使用することを考慮できます。

リアルタイム投影のコストはまだ比較的多く、一般的にできることは、シャドウの品質を下げたり、投影および受けるシャドウの数を制御したりして、コストを減らすことです。

私たちのやり方は、キャラクターにFastShadowReceiverを採用します。静的オブジェクトにベイク処理を採用します。他の動的オブジェクトは特定の状況によって異なって、下部に中央から端に向かってオブジェクトと一緒に移動するシャドウまたはFastShowReceiverをつけます。あるいは直接に投影されていません。


UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析最適化ソリューション及びコンサルティングサービスを提供している会社でございます。

UWA公式サイト:https://jp.uwa4d.com
UWA公式ブログ:https://blog.jp.uwa4d.com

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コールドスタート時間を最適化する方法

今回の主な話題:UniRx使用経験の共有、特殊効果はテクスチャマージの最適化に値ませんか?、AssetBundle LZMAとLZ4圧縮、iOSレンダリング関するクラッシュ問題、Shadows.RenderShadowMapの時間のかかる最適化。


ローディング

Q1: プロジェクトを最初に開始するときのコールドスタート時間が長すぎる可能性がありますが、どうすれば最適化できますか?

まず、コールドスタートプロセス中にゲームが何をしているのかを判断するのに時間がかかり、特定的に最適化を行う必要があります。コールドスタートの限界も定義する必要があります。ゲームをクリックしてからUnity画面が表示されるまでの時間は通常コールドスタート時間として定義されますが、私たちのプロジェクトでスタートの最適化をする後、リソースの初期化にまだ長い時間が費やされていることが発見されました。

起動時間を最適化する過程に、特に何かのツールを使っていませんでした。主にmlogcat.exeに基づいてデバイスの情報を出力するlogをチェックし、自分が追加したlogを組み合わせて問題を検出します。

単純にコールドスタート時間が長すぎると、Resourcesディレクトリ内のリソースに関係があります。リソースが多くと遅くなります。私たちは主にAssetBundleメソッドを使っているので、この部分にも注意しました、あるプラグインが導入した不要なリソースをクリーンアップしました(パッケージ化されたapkのコンテンツを直接見て確認します)。

ゲームの起動時間がかかりすぎる問題に遭った時に解決したいくつの問題についてお話しします。私たちが定義した起動時間は、appアイコンをクリックしてからゲームPatchインターフェイスに入るまでの時間であります(つまり、ゲームロジックの引き継き)。

1)Shaderのコンパイル時間が長すぎです。ゲームがインストールされた後に初めて起動する時の時間だけが長すぎる場合、一つの大きな可能性はShaderコンパイルであります。この後にCacheがあるため、ゲームの起動時間ははるかに早くなります。この場合には、Always IncludeのShader内容と変体を確認して、代わりにshadervariantcollectionなどの方法を使用することをお勧めします。

2)ToluaバインディングとLuaリソースのローディング。これらのは毎回ゲームを起動する時にあるものです。ToLuaインターフェイスのバインドには一定の時間がかかります。私たちは、初期にLuaが使わず状況を確保してマルチスレッド方式でバインドとローディングを行い、メインスレッドがジャムしないことを保証します。

3)Webリクエストのタイムアウト期間の設定に注意してください。ゲームが起動する時にいくつかのhookを行ったところ、Webリクエストが発生しました。その後、多くのマシンで黒い画面が30秒または60秒以上待つ状況に遭いましたから、マシンのデフォルトのタイムアウト時間を使用しました。デバイスによって異なります、たとえば、Redmi2Aのタイムアウト制限は1分近くです。これは厄介なものです、ただあの必要ではないWebサービスが正しく開始されなかったという理由だけで、調査に長い時間がかかりました。

ネイティブレイヤーでインターフェイスを増やし、黒い画面の待機時間を減らし、プレーヤーのエクスペリエンスを向上させます。実際に、これは問題を解決する方法ではなく、単なる緩和方法です。最適化が実施されれば、それは必要なくなります。

言及されている内容のほとんどはコールドスタートではなく起動時間ですが、問題主が参照できます。問題主にUnityプロセスの出力logをよく確認することをお勧めし、何か予期以上のものが発生する可能性があります。一般的に、Resourcesの方式を使用しないなら、Bugがなければコールドスタート時間はそれほど長くないはずです。この方法を使うことがないため、あまり明確ではありません。


動画

Q2: 現在 はSective(false)を介してPoolに配置し、使う時にPoolからSetActive(true)を取得やコールしてアニメーションを再び再生します。欠点は、Animator初期化などの時間のかかる操作を引き起こしやすいです。SetActive操作をしなくで代わりにScaleを0に設置すれば、特殊効果の続きのレンダリングを回避できますが、AnimationとParticleがずっと実行している、同じにCPUリソースをいくつ浪費しませんか?どうしたらいいですか?

AnimatorとAnimationが配置されているGameObjectがActivateする時に確かにより明らかななコストがあります。

一般的に、アニメーションコンポーネントに適用されるパーティクルエフェクトが少ない場合、この部分の影響はそれほど大きくありません。この2つの部分の具体的な消費率を確認することをお勧めします。高くない場合は、現在の計画を維持できます。 この部分に時間がかかる場合は、アニメーションコンポーネントとパーティクルシステムコンポーネントTransformノードをできる限りに分離します。例えば、親ノードでAnimator/Animationコンポーネントをぶら下げ、子ノードにParticleSystemをぶら下げ、Activate/Deactivateの時に子ノードでのみ操作します。親ノードでEnable/Disable Animator/Animationコンポーネントの方式を採用すればこの部分のコストを回避できます。


Monoメモリ

Q3: Unity公式のMemory Profilerを使ってテストしたところ、集まったManagedObjectSizeとProfilerが顕示するマネージドヒープの使用済みのUsedHeapSizeは大きく異なります。UsedHeapSizeはManagedObject Sizeの約10倍であります、何から統合されていませんか?
1.png
2.png

テストを行いました、同じような状況を再現できます。

ProfilerのUsed Monoはやく500KBであります。
Unity 公式のMemory Profilerはやく50KBであります。
UWA GOTのDirect モードを使ってテストした結果もやく50KBであります。

したがって、この約450KBの違いがMono自体によって生成されたものかどうかを推測することしかできません。それを確認するために、Monoのソースコードを確認する必要があります。また、この場合は確かに「約10倍」ですが、Monoメモリが比較的に大きい場合のテストをお勧めします。差の割合が非常に小さい可能性があります。


適応

Q4: ゲームは他の携帯電話では正常ですが、デフォルトでMi MIX 2の右側に黒いバーがあります。携帯電話でフルスクリーンをオンに設定する必要があります。ただし、いくつか他のゲームの場合、手動で設定しなくても正常でありました。デフォルトで全画面設定をオンにするにはどうすればよいですか?

AndroidManifest.xmlでmax_aspect値を声明します。

全画面スマホのアスペクト比が以前よりも大きいため、適応しない場合、デフォルトでAndroidの最大アスペクト比は1.86であり、全画面スマホのアスペクト比よりも小さくなります。ですから、全画面スマホで一部のアプリを開くと、上下にスペースがあり、黒いバーで表示され、視覚体験に大きな影響を与えます。一方、全画面が提供する余分なスペースも使用されないため、これらのアプルが相関適応を行う必要があります。

これに対して、Android公式が適応ソリューションを提供します。つまり、アプリが支持する最大画面アスペクト比を上げることです。実装も比較的簡単です。AndroidManifest.xmlで次の構成を行うことができます。
< meta-data android:name=“android.max_aspect” android:value=“ratio_float”/ >

その中で、ratio_floatは浮動小数点数であり、18.5:9 = 2.055555555 ……であるため、公式の推奨値は2.1または以上です。将来、アスペクト比がより大きいスマホが出現する場合、この値を大きく設定する必要もあります。

したがって、開発者にアプリAndroidManifestのApplicationタグの下に次のコードを追加することをお勧めします。


Lua

Q5: Lua部分、functionを使って一つのdelegateを作成し、C#に入力してイベントリスナーとします。ただし、監視が終了したら、delegateを空にしただけ、LuaFunctionを解放しない場合、メモリリークが発生します。 これに良い解決策はありますか?
3.png
先に+=を使い、後に-=を使うことは問題がないはずで、LuaFunctionを解放できます。ただし、ビジネスロジックの原因で、Luaレイヤーで加算と減算操作を同時にするのは難しいです。C#の部分で全ての追加されたものを安全に解放するのが一番いいです。

FairyGUIは委任されたインターフェイス(class EventListenerのみ)を公開しないため、一部の底層コードを変更する必要があります(変更は大きくないです)。

下記に一つのソースコードを変更する必要のない実現方法を紹介します。

1)Lua DelegateMgrに委託がどれのC#に引用されることを記録して、そして不要な委託を定期的にクリーンアップします。
4-1.jpg
2)いくつかのFairyGUIをカバーする方法でFairyが作成する委託をDelegateMgrに納格して管理します。
5-1.jpg


UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析最適化ソリューション及びコンサルティングサービスを提供している会社でございます。

UWA公式サイト:https://jp.uwa4d.com
UWA公式ブログ:https://blog.jp.uwa4d.com

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityのサブスレッドで5msごとに処理

Unityのサブスレッドで5msごとに処理 多分こんな感じ 以下を参考にしました。 https://teratail.com/questions/109591 using UnityEngine; using System.Threading; using System; public class UnitySubThreadTimer : MonoBehaviour { Thread thread; // Start is called before the first frame update void Start() { thread = new Thread(new ThreadStart(SubThread)); thread.Start(); } void SubThread() { //Process once every 5ms long next = DateTime.Now.Ticks + 50000; long now; while (true) { try { Debug.Log(Time.time); } catch { } do { now = DateTime.Now.Ticks; } while (now < next); next += 50000; } } void Stop() { thread.Abort(); } void OnApplicationQuit() { thread.Abort(); } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OnGUI() で重い処理をしたい

概要

 エディタ拡張で、ボタンをクリックときに重い処理をすると出るエラーの回避法について書いてあります。

背景

 kumaS-nu/NuGet-importer-for-Unity
 ↑のプロジェクトで、ボタンをクリックしたらインストールするようにしたらエラーが出て、動くけど気持ち悪かったので回避法を見つけました。また、このプロジェクトは以下の記事で宣伝しています。
Unity のプロジェクトに NuGet のパッケージをインポートするエディタ拡張を作ったよ

エラー内容

 こんな感じのコードを書くと、BeginLayoutGroup must be called first. とエラーが起きる。

public async Task OnGUI()
{
    using(new EditorGUILayout.VerticalScope())
    {
        // 重い処理の代わり。
        await Task.Delay(10000);
    }
}

解決法

 結局、スコープ内で待っているのが悪いので最後に待つようにすればいい。順番とかあるときはメソッドとして切り出せばいい。
 あと、例では asyncawait を使っているけどコルーチンにしても問題ない。

public async Task OnGUI()
{
    var tasks = new List<Task>();
    using(new EditorGUILayout.VerticalScope())
    {
        tasks.Add(Heavymethod);
    }
    await Task.WhenAll(tasks);
}

private async Task Heavymethod()
{
    await Task.Delay(10000);
}

あとがき

 やっぱり消せるエラーは無くした方がいい。対処しないといけないエラーを埋もれさせちゃうので。あと、Unity2019 以前はどうやってもエラーは起きるのでどうしようもない。(Unity 公式の C# のリファレンスのソースコードに「ここは私の部門じゃどうしようもないよ」とか書いてあった。)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクトがBoxColliderの中に完全に入っているかどうか判定する

オブジェクトが完全にコライダーの中に入っているかどうか?を判定するためのコードを組みました。

BoxCheck01.png
BoxCheck02.png
BoxCheck03.png

BoxColliderの各面をPlaneとして取得するスクリプト

Vector3操作スクリプト(ランダム方向、要素の最大値を取得)
はこの処理をするためのものです

ColliderFullyInsideCheck.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ColliderFullyInsideCheck: MonoBehaviour
{
    private Material material;
    private Color DefaultColor;
    private Color ChangeColor = Color.green;

    public SphereCollider TargetCollider;

    void Awake()
    {
        material = GetComponent<MeshRenderer>().material;
        DefaultColor = material.GetColor("_Color");
    }

    public bool isColorChange
    {
        set
        {
            material.SetColor("_Color", value ? ChangeColor : DefaultColor);
        }
    }

    public bool isColliderFullyInside(BoxCollider box, SphereCollider sphere)
    {
        float radius = Calc.MaxElementOfVector3(sphere.transform.lossyScale) * sphere.radius;
        Vector3 center = sphere.transform.TransformPoint(sphere.center);

        int inside_count = 0;
        Plane[] planes = Calc.GetBoxColliderPlanes(box);
        for (int i = 0; i < 6; i++)
        {
            float inside_distance = planes[i].GetDistanceToPoint(center);
            if (-inside_distance >= radius)
            {
                inside_count++;
            }

        }
        return inside_count == 6;
    }

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        isColorChange = isColliderFullyInside(GetComponent<BoxCollider>(), TargetCollider);
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vector3操作スクリプト(ランダム方向、要素の最大値を取得)

非常に簡単な計算なのでその都度コードを組めばいいだけなんですが、けっこう頻度が高いのでStaticスクリプトにしてます。

Vector3でランダムな方向を取得するRandomVector3()
Vector3のxyz要素のうち最大の絶対値を持つ要素を取り出すMaxElementOfVector3(Vector3 v)

C#Calc.cs
    public static Vector3 RandomVector3()
    {
        Vector3 v = Vector3.zero;
        v.x = Random.Range(-1.0f, 1.0f);
        v.y = Random.Range(-1.0f, 1.0f);
        v.z = Random.Range(-1.0f, 1.0f);
        return v;
    }
    public static float MaxElementOfVector3(Vector3 v)
    {
        float[] element_value = new float[] { Mathf.Abs(v.x), Mathf.Abs(v.y), Mathf.Abs(v.z) };
        return Mathf.Max(element_value);
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VS CodeでC#やってたら出てきたこいつ→some projects have trouble loading.

はじめに

Unityでゲーム作るぞ!!と意気込んで早一か月。VS Codeを使ってコード書くのに慣れてきたかなぁとか言う時にエラーにぶち当たった。

エラー内容

some projects have trouble loading. please review the output for more details.
6b990ce30fa9193e296dd37902816f4beb6723b42b45500904d5380f8ced963ae6c86a6c10d1eb11ec0295aef78cccdc.png

解決方法

とりあえず詳細は出力を見ろと言われたもんだから見たよ。
無題.png
読み込めんとか失敗したとか言われてるけどよく見るとこんなことが。

The reference assemblies for .NETFramework,Version=v4.7.1 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks

つまり.NETFrameworkのバージョン4.7.1が見つからないらしい。
そりゃそうよ。インストールしてないもの。
ていうことでインストールします。ご丁寧にURLまで書いてくれてますね。

https://aka.ms/msbuild/developerpacks

ページ下部にバージョン選択があるので私はv4.7.1を選択してダウンロード&インストール。
無題.png
インストールが終わってVS Codeを再起動してみる。

エラー表示が出てこない!!成功!!

最後に

まぁ、エラーの時は詳細をよく読めってことだよね。
見ればわかるようなエラーだけど初めてこういうの触る人の助けになればいいかなと思ってこの記事を書きました。お役に立てれば幸いでござる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BoxColliderの各面をPlaneとして取得するスクリプト

BoxColliderの各面のPlaneが欲しい時があるのですが直接取得する方法がわからないのでスクリプトを組みました。
UnityのBoxColliderの頂点を取得するスクリプト をPlaneにしたものです

GetBoxColliderPlanes
    public static Plane[] GetBoxColliderPlanes(BoxCollider Col)
    {
        Transform trs = Col.transform;
        Vector3 sc = trs.lossyScale;

        sc.x *= Col.size.x;
        sc.y *= Col.size.y;
        sc.z *= Col.size.z;

        sc *= 0.5f;

        Vector3 cp = trs.TransformPoint(Col.center);

        Vector3 vx = trs.right * sc.x;
        Vector3 vy = trs.up * sc.y;
        Vector3 vz = trs.forward * sc.z;

        Plane[] planes = new Plane[6];
        planes[0] = new Plane(trs.right, cp + vx);
        planes[1] = new Plane(-trs.right, cp - vx);
        planes[2] = new Plane(trs.up, cp + vy);
        planes[3] = new Plane(-trs.up, cp - vy);
        planes[4] = new Plane(trs.forward, cp + vz);
        planes[5] = new Plane(-trs.forward, cp - vz);

        return planes;

    }

親の回転、縮小に対応しています
BoxPlane.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PlaneとRayの交差する点を求めるスクリプト

マウスポインターをコライダー内部に閉じ込める方法 内に書いたスクリプトですが、記事を分けておかないと探せないので別にしました。

CrossPointOnPlane
    //Planeで取得
    public static Vector3 CrossPointOnPlane(Plane plane, Ray ray)
    {
        float enter = 0.0f;
        plane.Raycast(ray, out enter);

        return ray.GetPoint(enter);
    }
    //平面上の点PlanePointと法線PlaneNormalで取得
    public static Vector3 CrossPointOnPlane(Vector3 PlaneNormal, Vector3 PlanePoint, Ray ray, float distance = 1000.0f)
    {
        Vector3 result = Vector3.zero;

        Vector3 v1 = ray.origin - PlanePoint;
        Vector3 v2 = ray.origin + (ray.direction * distance) - PlanePoint;

        float d1 = Mathf.Abs(Vector3.Dot(v1, PlaneNormal));
        float d2 = Mathf.Abs(Vector3.Dot(v2, PlaneNormal));

        float denom = d1 + d2;

        if (denom > 0)
        {
            result = ray.GetPoint(distance / denom * d1);
        }
        return result;
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

移動する方向にオブジェクトを傾けさせたい【Unity】

タイトルの通り。
UFOとかドローン、ゴーストみたいな浮遊してる敵とかに使えるかも?
Transform.InverseTransformDirectionという存在を初めて知った。

Simple_Tilt.cs
using UnityEngine;
public class Simple_Tilt : MonoBehaviour
{
    [SerializeField] private float xMultiple;
    private Vector3 latestPos;

    private Vector3 tiltVector;
    private Vector3 localDiff;

    void Update()
    {
        //ワールドでの移動量
        Vector3 diff = transform.position - latestPos;
        latestPos = transform.position;
        //ローカル用に変換する
        localDiff = transform.InverseTransformDirection(diff.normalized);

        tiltVector = new Vector3(
        localDiff.z * xMultiple,
        transform.eulerAngles.y,
        localDiff.x * -xMultiple);

        transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler(tiltVector), 0.1f);
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】Packageのインポートで同一のファイル名のシーンやスクリプトがあると上書きされるので注意

Packageをインポートしたらデータが上書きされてびっくり

タイトルの通りなので注意したい。

なんとなく別プロジェクトのUIとかスクリプトを使い回そうとしてインポートしたら起きました。

一旦上書きされてしまうと、バックアップ以外に戻す方法がないっぽい。

テストしてみると、とりあえずsceneとscriptは上書きされてしまいました。

一応警告は出るが、、

パッと情報がなかったので、何かの間違いかもと思ってテストしてみた。

同じファイル名のシーンとスクリプトとがあるプロジェクトを二つ用意

スクリーンショット 2021-02-26 0.50.37.png

スクリプトはこんな感じのやつの表示テキスト部分だけが違を変えたものに。

Hello.cs
    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("上書きされた");
    }

エクスポート側

スクリーンショット 2021-02-26 0.59.21.png

インポート側
スクリーンショット 2021-02-26 1.01.45.png

あ、ちっさく警告でてる!。
ここで気づけてチェックを外せばOKだけど、
このままimportを押してしまうと上書きされてしまう。

スクリーンショット 2021-02-26 1.14.19.png

もうちょっとババーンと警告出して欲しいな。

ちなみに昔は警告も出なかったそうな。コワイ。
https://answers.unity.com/questions/1006498/accidentally-overwritten-scripts-with-imported-pac.html
https://answers.unity.com/questions/594502/help-scene-file-overwritten-after-import-package.html
https://www.reddit.com/r/Unity3D/comments/adlhzg/does_unity_just_overwrite_your_scene_if_you/

ちなみにプロジェクトウィンドウの中に同一ファイル名のファイルをコピーしても上書きされない。
スクリーンショット 2021-02-26 1.43.10.png

Finderなどでコピペすると上書き、というか新しいファイルに置き換えられるらしい。
https://amagamina.jp/overwriter/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】Packageのインポートは同一のファイル名のシーンやスクリプトを上書きするっぽいので注意

Packageをインポートしたらデータが上書きされてびっくり

タイトルの通りなので注意したい。

なんとなく別プロジェクトのUIとかスクリプトを使い回そうとしてインポートしたら起きました。
GameManagerとかOpenURLとか一部使い回してるけど中身が違うスクリプトが上書きしてしまって焦った。

とりあえずsceneとscriptは丸々上書きされてしまいました。

一旦上書きされてしまうと、バックアップ以外に戻す方法がないっぽい。
今回はDropBoxから復元しました。

ファイル操作のミスとかでも間違って上書きしちゃうことはあるけど、
気をつけたい。

一応警告は出るが、、

パッと情報がなかったので、何かの間違いかもと思ってテストしてみた。

同じファイル名のシーンとスクリプトとがあるプロジェクトを二つ用意

スクリーンショット 2021-02-26 0.50.37.png

スクリプトはこんな感じのやつの表示テキスト部分だけを変えたものに。

Hello.cs
    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("上書きされた");
    }

エクスポート側

スクリーンショット 2021-02-26 0.59.21.png

インポート側
スクリーンショット 2021-02-26 1.01.45.png

あ、ちっさく警告でてる!。
ここで気づけてチェックを外せばOKだけど、
このままimportを押してしまうと上書きされてしまう。

スクリーンショット 2021-02-26 1.14.19.png

もうちょっとババーンと警告出して欲しいな。

ちなみに昔は警告も出なかったそうな。コワイ。
https://answers.unity.com/questions/1006498/accidentally-overwritten-scripts-with-imported-pac.html
https://answers.unity.com/questions/594502/help-scene-file-overwritten-after-import-package.html
https://www.reddit.com/r/Unity3D/comments/adlhzg/does_unity_just_overwrite_your_scene_if_you/

その他メモ

あとプロジェクトウィンドウの中に同一ファイル名のファイルをコピーしても上書きされない。
別のファイルとして召喚される。
スクリーンショット 2021-02-26 1.43.10.png

Finder上からの操作であれば上書きできる。
https://amagamina.jp/overwriter/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】Packageのインポートで同一ファイル名のシーンやスクリプトが上書きされる

Packageをインポートしたらデータが上書きされてびっくり

タイトルの通りなので注意したい。

なんとなく別プロジェクトのUIとかスクリプトを使い回そうとしてインポートしたら起きました。
GameManagerとかOpenURLとか一部使い回してるけど中身が違うスクリプトが上書きしてしまって焦った。

とりあえずsceneとscriptは丸々上書きされてしまいました。

一旦上書きされてしまうと、バックアップ以外に戻す方法がないっぽい。
今回はDropBoxから復元しました。

ファイル操作のミスとかでも間違って上書きしちゃうことはあるけど、
気をつけたい。

Unityのバージョンは2019.4.1f

一応警告は出るが、、

パッと情報がなかったので、何かの間違いかもと思ってテストしてみた。

同じファイル名のシーンとスクリプトとがあるプロジェクトを二つ用意

スクリーンショット 2021-02-26 0.50.37.png

スクリプトはこんな感じのやつの表示テキスト部分だけを変えたものに。

Hello.cs
    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("上書きされた");
    }

エクスポート側

スクリーンショット 2021-02-26 0.59.21.png

インポート側
スクリーンショット 2021-02-26 1.01.45.png

あ、ちっさく警告でてる!。
ここで気づけてチェックを外せばOKだけど、
このままimportを押してしまうと上書きされてしまう。

スクリーンショット 2021-02-26 1.14.19.png

もうちょっとババーンと警告出して欲しいな。

ちなみに昔は警告も出なかったそうな。コワイ。
https://answers.unity.com/questions/1006498/accidentally-overwritten-scripts-with-imported-pac.html
https://answers.unity.com/questions/594502/help-scene-file-overwritten-after-import-package.html
https://www.reddit.com/r/Unity3D/comments/adlhzg/does_unity_just_overwrite_your_scene_if_you/

その他メモ

あとプロジェクトウィンドウの中に同一ファイル名のファイルをコピーしても上書きされない。
別のファイルとして召喚される。
スクリーンショット 2021-02-26 1.43.10.png

Finder上からの操作であれば上書きできる。
https://amagamina.jp/overwriter/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[C#]コンストラクタを呼ばずにインスタンスを作る

以下のようなクラスがあるとき、インスタンスの作成にとても時間がかかってしまいます。

public class Hoge
{
    private int v;
    public int Value => v;

    public Hoge()
    {
        // Application.isPlayingはメインスレッドで呼ばなければ例外が発生する
        Debug.Log($"Application.isPlaying:{Application.isPlaying}");
        // コンストラクタで重い処理をしている
        v = HeavyFunc();
    }

    private int HeavyFunc()
    {
        // 重い初期化処理
        Thread.Sleep(5000);
        // 計算結果
        return 42;
    }
}

インスタンスを作成するときにメインスレッドで行ってしまうとUIが止まってしまうのでできれば別スレッドで作成したいところです。
しかし、コンストラクタ内部でApplication.isPlayingにアクセスしてしまっているため通常はメインスレッドでしか作成することができません。

Task.Run(() =>
{
    try
    {
        var h = new Hoge();
        Debug.Log(h.Value);
    }
    catch (Exception e)
    {
        // UnityException: get_isPlaying can only be called from the main thread.
        // Constructors and field initializers will be executed from the loading thread when loading a scene.
        // Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
        Debug.LogException(e);
    }
});

このクラスを書いたのが自分であれば修正すればいいですがソースコードを修正できない場合もあります。
そのような場合、最終手段として
System.Runtime.Serialization.FormatterServices.GetUninitializedObject
を使うことでコンストラクタを呼ばずにインスタンスを作成することができます。
インスタンスさえ作成できれば後はリフレクションで何とかなります。

// privateなフィールド、メソッドにアクセスするためにリフレクションを使用する
var vInfo = typeof(Hoge).GetField("v", BindingFlags.NonPublic | BindingFlags.Instance);
var heavyFuncInfo = typeof(Hoge).GetMethod("HeavyFunc", BindingFlags.NonPublic | BindingFlags.Instance);
_ = Task.Run(() =>
{
    try
    {
        // コンストラクタを呼ばずにインスタンスを作成
        var hoge = FormatterServices.GetUninitializedObject(typeof(Hoge)) as Hoge;
        // HeavyFuncを実行する
        var result = heavyFuncInfo.Invoke(hoge, null);
        // フィールドに値をセット
        vInfo.SetValue(hoge, result);
        // 正しくインスタンスが作成されている
        Debug.Log(hoge.Value);
    }
    catch (Exception e)
    {
        Debug.LogException(e);
    }
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む