NinaLabo

個人ゲーム開発者の技術メモ

【LeetCode】1. Two Sum (Easy)

【最初に】

LeetCodeのアルゴリズムの最初の問題を解いてみます

https://leetcode.com/problems/two-sum/

 

 

【問題】

与えられたint配列に対して足すと指定の数値になる配列内の2つの数値のインデックスを返せ。入力には必ず解が1だけある前提。配列の同じ要素は使わないこと。

Example:

Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].

 

 

【とりあえず】

単純に思いつくのは、とりあえずfor文をぶん回して足していって答えに辿り着いたら終了するパターン。

f:id:ninagreen:20191106003853p:plain

 

でもこれはたぶんダメダメなやつ

解答を見ると、上の例だと最後の1個まで解が見つからない場合、ループをn回実行して、各要素ごとにn回ずつ回すので、時間計算量はO(n^2)になるとのこと。

f:id:ninagreen:20191106010830p:plain

 

【違うパターン】

ヒントを見るとhashmapを使ったらとあるので、違うパターンを試してみる

 

f:id:ninagreen:20191106010855p:plain

 

最初に数字をkeyに、配列のインデックスをvalueにした連想配列を作る。次にもう一度配列を回して、補数がマップにあるかどうか検索。存在する場合は結果を返して終了する。

 

速くなった!(メモリは増えたけど)

f:id:ninagreen:20191106010907p:plain

 

時間計算量はO(n^2)からO(n)に減少(O(2n)と書かない理由はこちらを参照:https://qiita.com/drken/items/872ebc3a2b5caaa4a0d0)ただし、空間計算量は配列の分だけmapとしてメモリに格納するのでO(1)からO(n)に増える


 【さらに違うパターン】

↑だと配列のループを2回回しているけど、解答見るとループを1回で済ませる方法もある

 

f:id:ninagreen:20191106013544p:plain

 ループを回していって、マップに補数がなければ格納。順にループを回していって過去に追加したマップの値と比較して発見すればインデックスを返して終了する。

 

【最後に】 

気分転換に最初の1個目をやってみましたが面白いですね。アルゴリズムはこれまであまりきちんと勉強して来なかった(正確には大学でやってるはずだけど真面目に授業受けてなかった)ので、もうちょっと続けてみようかと思います。ちなみにC++も大学でやってるはずだけど...(以下同文)C++の作法とかよくわかってないので、その辺りはご容赦を。 

 

【Unity】LWRPプロジェクトの作成とサンプルシーン

Unity2018.1 から LWRP (Lightweight Render Pipeline: 軽量レンダーパイプライン) が導入されました。LWRPはSRP(Scriptable Render Pipeline)の1つで、HDRP (High Definition Render Pipeline: 高画質レンダーパイプライン)に比べ軽量でモバイル向きだそうです。

SRPとはUnity がフレームをどのように描画するかをデベロッパーが C# で制御できるようにするものです。

blogs.unity3d.com

まずは新規にLWRP用のプロジェクトを作成します。 なお、既存のプロジェクトをLWRPに適用させるのはテラシュールさんの記事がわかりやすかったです。

tsubakit1.hateblo.jp

実行環境

Unity: 2018.3.4f1

LWRP: 4.1.0-preview 

LWRPプロジェクトの作成

プロジェクトの新規作成で、Template に Lightweight RP (Preview) が増えているので選択します。ちなみに、LWRPはまだPreview版のようで、2019.1から本格的に使えるようになるようです。

f:id:ninagreen:20190207042036p:plain

 

プロジェクトを作成するとこんなサンプルシーンが開きます。

f:id:ninagreen:20190207043102p:plain

サンプルプロジェクトにはLWRP設定が3種類用意されています。Edit > Project Settings > Graphics の一番上の設定を切り替えれば変更できます。

 LWRP-LowQuality  LWRP-MediumQuality  LWRP-HighQuality

f:id:ninagreen:20190209142453p:plain

f:id:ninagreen:20190209142532p:plain

f:id:ninagreen:20190209142602p:plain

設定の違いは以下のようになっています。

   LWRP-LowQuality   LWRP-MediumQuality   LWRP-HighQuality 
 HDR   OFF  ON  ON
 MSAA(Anti Aliasing)   Disabled  2x  4x

 Main Light

 - Shadow Resolution 

 512  1024  1024

 Additional Lights 

 Disabled

 Per Pixel

 Per Pixel

 Additional Lights 

 - Cast Shadows

 -

 OFF

 ON (1024)

 Shadows

 - Cascades

 No Cascades  Two Cascades  Four Cascades

 Shadows

 - Soft Shadows 

 OFF  ON  ON

 左の絵はHDRがオフなのでスポットライトとブルームが反映されていないのと、真ん中の絵はAdditional LightsのCast Shadowsがオフなので右側にスポットライトの影が出てないのが大きな違いでしょうか。

 なお、LWRPと通常のUnityビルドインパイプラインの違いは下記に記載があります。

blogs.unity3d.com

 

注意点は、CEDEC2018の発表資料にある通り、

① CameraのCallbackが呼ばれない

Camera.OnRenderImageなどのCamera系のコールバックが呼ばれなくなります。 

Surface Shaderが使えない

vertex shader / fragment shader もしくは、2018.1からの新機能ShaderGraphでのShader記述に置き換える必要があります。 

www.slideshare.net

それ以外にも、

③ 現状のLWRPでは複数のカメラに対応していない。Unity2019.1で対応するかもしれないとのこと。

https://forum.unity.com/threads/glitching-with-multiple-cameras-lwrp.592477/

  • Remove multi-camera support. LWRP and HDRP will not support multi-camera layered rendering.

つまり、UI用にMainCameraとは別のカメラを追加する、みたいなことは現状ではうまく動作しないと思われます。

④ LWRPは、通常のビルドインのUnityレンダリングやHDRPとは互換性がない。既存のプロジェクトにLWRPを適用するための一括変換コマンドもあるが、すべて正確に変換できるとは限らずアセットによっては手動で修正しなければいけないかもしれません。

Editor > Render Piepeline > Update Project Materials To Lightweight Materials

 

などがありそうです。そもそもLWRPはまだPreview版で仕様が変更される可能もあります。本格的な使用はUnity2019での正式版まで待った方がいいかもしれません。

 

 

【Unity】async/awaitのフレーム消費

C# 6.0から async/await が使えるようになり、コルーチンでは解決できなかった「何もしてないのにフレーム消費されてしまう」問題が解決できそうです。

まずは今までのコルーチン処理です。IEnumeratorを返すメソッドでは下記のように非同期処理を上から順に実行される形で書くことができました。下記の処理では、coroutine1が終わるのを待ってからcoroutine2が実行されます。コールバック処理を書くよりも見通しの良いコードを書くことができました。

yield return coroutine1;

yield return coroutine2;

もちろん、下記のようにMoveNextを使って書くこともできますが、 上記のほうが1行で書けるためスッキリ書けました。

while (coroutine1.MoveNext())

{

    yield return null;

}

while (coroutine2.MoveNext())

{

    yield return null;

}

 

ただし、yield return coroutine; の書き方は問題もありました。メソッド内部で仮に何も処理していなくても、必ず1フレーム消費されてしまうことです。 

public class AsyncFrameTest1 : MonoBehaviour
{
    private int mFrameNum;
    private bool mIsPlaying;

    private IEnumerator Start()
    {
        // 念のため1フレーム待って計測
        yield return null;

        mIsPlaying = true;
        mFrameNum = 0;

        var previousFrame = mFrameNum;
        yield return TestCoroutine1();
        Debug.LogFormat("TestCoroutine1: (+{0}フレーム)",  mFrameNum - previousFrame);

        previousFrame = mFrameNum;
        yield return TestCoroutine2();
        Debug.LogFormat("TestCoroutine2: (+{0}フレーム)", mFrameNum - previousFrame);
    }

    public IEnumerator TestCoroutine1()
    {
        yield break;
    }
    public IEnumerator TestCoroutine2()
    {
        yield return null;
    }

    private void Update()
    {
        if (mIsPlaying)
        {
            mFrameNum++;
        }
    }
}

 実行結果はご覧の通りです。TestCoroutine1は内部でyield breakしているだけですが、yield return nullしているTestCoroutine2と同様に処理が終わるまで1フレームかかっています。これが結構厄介で、例えばコルーチンメソッド内でキャッシュがある場合はロードせずにyield breakするような処理を書いても最低1フレームはかかってしまうということになります。

f:id:ninagreen:20190207030834p:plain

もちろんMoveNextで書いておけば余計なフレームは消費せずに済みます。

       previousFrame = mFrameNum;
        var testCoroutine1 = TestCoroutine1();
        while (testCoroutine1.MoveNext())
        {
            yield return null;
        }
        Debug.LogFormat("MoveNext(TestCoroutine1): (+{0}フレーム)", mFrameNum - previousFrame);


        var testCoroutine2 = TestCoroutine2();
        while (testCoroutine2.MoveNext())
        {
            yield return null;
        }
        Debug.LogFormat("MoveNext(TestCoroutine2): (+{0}フレーム)"

, mFrameNum - previousFrame); 

f:id:ninagreen:20190207031620p:plain

 

1行でスッキリ書けて、かつ余計なフレームを消費しない書き方があればいいのになあとずっと思っていましたが、async/awaitで実現できるようです。

public class AsyncFrameTest2 : MonoBehaviour
{
    private int mFrameNum;
    private bool mIsPlaying;

    private async void Start()
    {
        mIsPlaying = true;
        mFrameNum = 0;

        var previousFrame = mFrameNum;
        await TestAsync1();
        Debug.LogFormat("TestAsync1: (+{0}フレーム)",  mFrameNum - previousFrame);

        previousFrame = mFrameNum;
        await TestAsync2();
        Debug.LogFormat("TestAsync2: (+{0}フレーム)", mFrameNum - previousFrame);

        mIsPlaying = false;
    }

    public async UniTask TestAsync1()
    {
        // 何もしない
    }

    public async UniTask TestAsync2()
    {
        await UniTask.DelayFrame(1); // 1フレーム待つ
    }

    private void Update()
    {
        if (mIsPlaying)
        {
            mFrameNum++;
        }
    }
}

 実行結果です

f:id:ninagreen:20190207032657p:plain


便利ですね!

 

【Unity】 async/awaitの実行スレッド

C#6.0から async/await が使えるようになり非同期処理がスッキリ書けそうなので興味あるのですが、マルチスレッドとの関連が不明だったので色々試してみました。

実行環境

Unity2018.3.4f1

Taskのasync/await

まずはシンプルなasync/awaitのサンプル。TestAsyncで3秒待っています。

public class TaskTest1 : MonoBehaviour
{
    private async void Start()
    {
        Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " Call Start");
        await TestAsync();
        Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " End Start");
    }

    public async Task TestAsync()
    {
        Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " Call TestAsync");
        await Task.Delay(3000);
        Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " End TestAsync");
    }
}

 結果はこんな感じですべて同じスレッドで実行されます。

f:id:ninagreen:20190203050126p:plain

つまり、async/awaitってただ書くだけでは非同期処理の待ち合わせはされるが、マルチスレッドにはならないということですかね。

UniTaskのasync/await

UniTask(UniRx.Async)でも同様になります。

public class TaskTest2 : MonoBehaviour
{
    private async void Start()
    {
        Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " Call Start");
        await TestAsync2();
        Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " End Start");
    }

    public async UniTask TestAsync2()
    {
        Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " Call TestAsync2");
        await Task.Delay(3000);
        Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " End TestAsync2");
    }
}

 結果、すべて同じスレッドで実行されています。

f:id:ninagreen:20190203052246p:plain

Taskをマルチスレッドで実行

別のスレッドで実行させたい場合は、Task.Runが使えます(マルチスレッドで実行する方法は他にもあるようですが割愛します)

public class TaskTest3 : MonoBehaviour
{
    private async void Start()
    {
        Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " Call Start");
        await TestAsync();
        Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " End Start");
    }

    public async UniTask TestAsync()
    {
        Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " Call TestAsync");

        await Task.Run(() =>
        {
            Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " Start Run");
            Thread.Sleep(3000);
            Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " End Run");
        });
        
        Debug.Log("Id: " + Thread.CurrentThread.ManagedThreadId + " End TestAsync");
    }
}

このように書いて実行すると以下のように、Task.Runに渡したActionは別スレッドで実行されます。 

f:id:ninagreen:20190203053208p:plain

 

ただ、ゲームのUI処理やロジック処理でマルチスレッドを使う機会はほとんどないのでこれ以上は追わないことにします。

UniTaskの作者のこの意見には私も同意です。

UniTaskはどちらかというとJavaScript的(シングルスレッドのための非同期の入れ物)に近いです。Taskは、そうした非同期の入れ物に加えてマルチスレッドのためなどなど、とにかく色々なものが詰まりすぎていて、あまりよろしくはない。非同期とマルチスレッドは違います。明確に分けたほうが良いでしょうし、UnityではC# JobSystemを使ったほうが良いので、カジュアルな用途以外(まぁラクですからね)ではマルチスレッドとしてのTaskの出番は少なくなるでしょう。

neue.cc

 

【Unity】MagicaVoxel で Export した obj ファイルのマテリアルが変更できなくなった

先日、Unity2017からUnity2018にアップデートしたのですが、MagicaVoxel で エクスポートした obj ファイルのマテリアルを変更するために、objファイルを選択し、Inspector の Materials タブを選択すると Import Materials 以下の項目が何も出なくなりました。FBXファイルを選択しても同様に出てきません。

f:id:ninagreen:20181107052442p:plain

 

前までは、ここに Location という項目があり、いつもはこれを Use Embedded Materials にして、defaultMat の項目を変更しているのですが... 何も出てきません...

 

よく見るとConsoleタブにエラーが大量に吐き出されていました。

NullReferenceException: Object reference not set to an instance of an object

UnityEditor.PropertyHandler.OnGUILayout (UnityEditor.SerializedProperty property, UnityEngine.GUIContent label, Boolean includeChildren, UnityEngine.GUILayoutOption options) (at /Users/builduser/buildslave/unity/build/Editor/Mono/ScriptAttributeGUI/PropertyHandler.cs:203)

UnityEditor.EditorGUILayout.PropertyField (UnityEditor.SerializedProperty property, UnityEngine.GUIContent label, Boolean includeChildren, UnityEngine.GUILayoutOption options) (at /Users/builduser/buildslave/unity/build/Editor/Mono/EditorGUI.cs:8791)

UnityEditor.EditorGUILayout.PropertyField (UnityEditor.SerializedProperty property, UnityEngine.GUIContent label, UnityEngine.GUILayoutOption options) (at /Users/builduser/buildslave/unity/build/Editor/Mono/EditorGUI.cs:8780)

UnityEditor.ModelImporterMaterialEditor.DoMaterialsGUI () (at /Users/builduser/buildslave/unity/build/Editor/Mono/ImportSettings/ModelImporterMaterialEditor.cs:364)

UnityEditor.ModelImporterMaterialEditor.OnInspectorGUI () (at /Users/builduser/buildslave/unity/build/Editor/Mono/ImportSettings/ModelImporterMaterialEditor.cs:162)

UnityEditor.AssetImporterTabbedEditor.OnInspectorGUI () (at /Users/builduser/buildslave/unity/build/Editor/Mono/Inspector/TabbedEditor.cs:147)

UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor editors, Int32 editorIndex, Boolean rebuildOptimizedGUIBlock, System.Boolean& showImportedObjectBarNext, UnityEngine.Rect& importedObjectBarRect) (at /Users/builduser/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1367)

UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)

 

エラー内容を調べてはみたものの、よくわからず...

Unity 2018.2.13f1 だったのですが、Unity のサイトを見ると最新が Unity2018.2.14f1 になっていたので、ダメもとでアップデートしてみたところ、

 

なおりました・・・

 

f:id:ninagreen:20181107053028p:plain

 

Unity 2018.2.14f1のリリースノートを見ましたが、ズバリコレというのは見つからず。関連してそうなのはこれぐらい・・?

 

f:id:ninagreen:20181107053050p:plain

 

このまま解決できなかったらどうしようかとすごく焦りましたが、とにかく直って良かったです。Unity 2018 はまだ細かい不具合がありそうですね。

 

 

【Unity】.vs ファイルを削除する

Unity 2018 にして Visual Studio を使用し始めたところ、ソースコードを修正するたびに以下の差分が出るようになりました。

modified:  [プロジェクトフォルダ]/.vs/rogue2-unity/xs/UserPrefs.xml

modified:  [プロジェクトフォルダ]/.vs/rogue2-unity/xs/sqlite3/storage.ide

modified:  [プロジェクトフォルダ]/.vs/rogue2-unity/xs/sqlite3/storage.ide-shm

modified:  [プロジェクトフォルダ]/.vs/rogue2-unity/xs/sqlite3/storage.ide-wal

 

.gitignore に .vs/ を追加しても消えません。

一度リポジトリに追加してしまうと .gitignore に .vs/ の記述を追加しても、差分を追跡し続けてしまうようです。

stackoverflow.com

 

記事にある通り、

git rm --cached -r [プロジェクトフォルダ]/.vs/

を実行すると

deleted:   [プロジェクトフォルダ]/.vs/rogue2-unity/xs/UserPrefs.xml

deleted:   [プロジェクトフォルダ]/.vs/rogue2-unity/xs/sqlite3/db.lock

deleted:   [プロジェクトフォルダ]/.vs/rogue2-unity/xs/sqlite3/storage.ide

deleted:   [プロジェクトフォルダ]/.vs/rogue2-unity/xs/sqlite3/storage.ide-shm

deleted:   [プロジェクトフォルダ]/.vs/rogue2-unity/xs/sqlite3/storage.ide-wal

と削除差分が出てくるのでコミットしたところ、以降は差分が出なくなりました。

 

 

【Unity】iPhone に実機転送すると development team がないエラー

久しぶりに iPhone に実機転送しようとしたところ、XCode実行時に以下のエラーが出ました。

Signing for "Unity-iPhone" requires a development team. Select a development team in the project editor. (in target 'Unity-iPhone')

 

この記事によれば、Xcode8 からXcodeエディタ上でTeamを設定する必要があるそうです。最近、iOSでリリースしてなかったので知りませんでした...

nn-hokuson.hatenablog.com

 

詳しい説明は上の記事のほうが画像付きで詳しいので、簡単に手順だけまとめます

1. General > Signing > Add Account

2. Apple ID Password を入力して Sign In

3. Team のドロップダウンで自分のアカウントを選択

 

実行すると、別のエラーが2個発生しました。

Your development team has reached the maximum number of registered iPhone devices.

Provisioning profile "iOS Team Provisioning Profile: (Bundle Id)" doesn't include the currently selected device "(端末名)".

 

そういえば、最近 iTunes Connect 開いてないなと思って確認してみたら、

デベロッパプログラムのメンバーシップの有効期限切れ

でした...