20200725のUnityに関する記事は8件です。

UnityでFirestoreを使いたい方へ

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

sora-unity-sdkがAndroidに正式対応したらしいのでサンプルを動かしてみた

a.gif

株式会社時雨堂様が提供してくださっているsora-unity-sdkがAndroidに正式に対応したらしいので、サンプルを動かしてみました。

試した環境

sora-unity-sdk 2020.5

PC
Windows10
Unity2019.1.14f1

スマホ
Android10
Motog8

1.サンプルの入手

GitHubから入手。
https://github.com/shiguredo/sora-unity-sdk-samples

READMEに依ると

sora-unity-sdk のインストール
Windows の場合は install.bat を、macOS の場合は install.sh を実行して下さい。 これで各種サンプルを実行するために必要になる sora-unity-sdk をインストールできます。

とのことなのでinstall.batを実行します。しかしこの実行ファイルの中身を見てみるとsora-unity-sdk v2020.4をインストールする設定になっているようです。今回、試したいのは2020.5だったのでinstall.ps1の$SORAUNITYSDK_VERSIONを修正します。

install.ps1
$SORAUNITYSDK_VERSION = "2020.5"

修正した上でコマンドプロンプトでinstall.batを実行して、sora-unity-sdk v2020.5をプロジェクトにインストールします。

mojikyo45_640-2.gif

2.Sora Laboを使うように設定

とりあえず動かしてみたいので、時雨堂様が提供してくださっているSora Laboを利用します。検証目的なら無償で利用できますが、商用利用はできないようです。簡単な利用規約があるのでよく確認してから利用しましょう。今回は検証目的なので大丈夫です。
https://sora-labo.shiguredo.jp/
mojikyo45_640-2.gif

サインアップしてログインしたらUnityに戻って設定していきます。

適当なサンプルシーンを開きます。今回はmulti_sendrecvにします。
mojikyo45_640-2.gif

ScriptというオブジェクトにアタッチされているSora SampleコンポーネントにSora Laboに接続するための設定を記入していきます。

Sora Laboのダッシュボードに記述してあるシグナリングURL, シグナリングキーをコピペしてUnityのSora Sampleコンポーネントに貼り付けます。

mojikyo45_640-2.gif

次にChannel Idを設定します。sora-laboのドキュメントに依ると

チャネル ID を <自分の GitHub Username>@<好きな Room ID> のように指定してください

とのことなので適当に設定します。ここでは便宜上<GITHUB_USERNAME>@sora-laboとします。

また、今回はUnityのカメラを送受信したいので、Capture Unity Cameraのチェックを入れます。

mojikyo45_640-2.gif

ここまでの設定でUnity Editor上で挙動を確認できるようになったはずです。Sora Laboにマルチストリーム受信というページがあるので、そこから確認してみます。Unityカメラの映像がブラウザから確認できるはずです。

mojikyo45_640-2.gif

3.Androidで確認できるように設定

Android向けにビルドして実機で確認できるように設定していきます。まずビルドターゲットをAndroidに変更します。

sora-unity-sdkのドキュメントによると

  • arm64-v8a のライブラリしか入れていないので armeabi-v7a の端末では動きません。
    • libSoraUnitySdk.so のインスペクタ -> Platform settings -> Android の設定で CPU が ARM64 になっていることを確認して下さい。
    • Player Settings -> Other Settings -> Target Architectures で ARM64 にチェックが入っていることを確認して下さい。
  • Vulkan で動かす必要があります。
    • Player Settings -> Other Settings の Graphics APIs で Vulkan が先頭にあることを確認して下さい。
  • OpenGLES モードでの動作は未確認です。
  • 最低でも API LEVEL 24 (Android 7.0)が必要です。
  • Pixel 3 で解像度が 16 の倍数じゃない時に映像が乱れる問題があります。

とのことなので、書いてある通りに設定していきます。

まずAssets\Plugins\SoraUnitySdk\android\arm64-v8a\libSoraUnitySdk.soのTarget ArchitecturesをARM64に変更します。変更したら忘れずにApplyしましょう。

mojikyo45_640-2.gif

次にPlayer Settingsの設定を変更していきます。Other SettingsのTarget ArchitecturesをARM64に。Graphics APIsのAuto Graphics APIのチェックをはずしてVulkanに設定します。さらにMinimum API LevelをAPI LEVEL 24にしておきましょう。

mojikyo45_640-2.gif

4.オブジェクトが映らない問題

一応、以上で設定はできたはずなんですが、私の環境だと問題が発生しました。Unityのカメラにオブジェクトが映らないという問題です。

mojikyo45_640-2.gif

本来なら球体が映らはずだけど、映らない。この問題はAndroidの実機だけじゃなくてUnity Editor上でも発生しました。

結論から言うと、Project Settings->Graphics->Tier SettingsのUser DafaultsのチェックをはずしてUSE HDRのチェックを入れると解決します。

mojikyo45_640-2.gif

この問題が起こった理由は特定できていませんが、検証してみた結果、ShaderのRenderTargetがOpaqueのオブジェクトが映らないという症状のようです。RenderTargetをTransparentにすると映るようになりました。まだBuild Settingsで対象プラットフォームをPCにしても治るようです。たぶんVulkan関連の何かが原因な気がします。

何か必要な設定を忘れていたのか、自分の実行環境の問題なのか、よく分かりません。

なんか描画順とか、Unityのカメラをキャプチャしてテクスチャにする処理とかに何かヒントがある気がします。WaitForEndOfFrameのタイミングでUnityのカメラの映像をVulkanを直接叩いてキャプチャするっていう実装になっているようなんですが、これでなんで映らないのか謎です。WaitForEndOfFrameは描画系の処理の最後に実行されるようなので、キャプチャするタイミングとしては適切な気がします。

原因を探るならたぶんこの辺のキャプチャする仕組みがどうなってるのか見る必要がある気がするんですが、Vulkanとか低レイヤーのことは門外漢なので、私にはよく分からなかったです。今回はとりあえずUSE HDRのチェックを入れたら写ったので良しとしました。
https://github.com/shiguredo/sora-unity-sdk/blob/develop/src/unity_camera_capturer_vulkan.cpp

5.結果

ということでsora-unity-sdkをAndroidで使うことができました。

a.gif

使っているスマホはMotog8というローエンド端末なんですが、想像以上にヌルヌル動きます。遅延もほとんどないと言っていいんじゃないでしょうか。すごいですね。

6.最後に

先日、NDI SDKをAndroidで使うっていう記事を書きました。NDIはローカルネットワーク上で映像や音声の送信を行うのに対してsora-unity-sdkはSoraを介してどこからでもアクセスできます。

またNDI SDKのAndroid実装は送信にしか対応してませんでしたが、こちらは送信も受信もできます。ただSoraを商用利用するにはお金がかかります。(NDI SDKは無料だったはず)

NDIもWebRTCも使うなら、表面的な理解だけでは駄目で、チューニングして軽量化したりする必要がありますね。

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

Unityで作成したアプリにおいて、ネットワーク接続を確認する場合に使うメソッド「Application.internetReachability」!

筆者の環境:OS:Windows10, Unity 2019.3.5f1 使用言語:C#
利用Asset:DOTween,Destructible2D
筆者のプログラミング歴:2020年1月よりUnity上でC#の勉強を開始。

ネットワークに接続しているかどうかを確認してくれる「Application.internetReachability」

初制作ゲームにて、Admob広告を掲載したのですがその際に、

『広告のLoadを待たずにゲームを開始すると、バナー広告の操作が上手くいかず、消すべき時に消せない。』

という不具合が出たので、広告のLoadが完了するまで、スタートボタンを表示させない、という無理やりな方法で
不具合を回避していました。
その為、インターネット接続していない端末はスタート出来ないので、それに対しての回避策を用意する、という更に面倒な事になっています。
今思えば、愚策中の愚策ですね。

現在開発中のゲームでもランキングを搭載予定なのもあり、ネットワーク接続しているかどうかの確認が出来れば、この問題は簡単に解決出来ると思い、調べてみました!
すぐ見つかりました(というか、一度調べてました)。

Application.internetReachabilityを使えば、ネットワーク接続しているかどうかの確認どころか、接続しているネットワークがWi-Fiなのかキャリアネットワーク(4Gとかですね)なのか?も、わかるそうです。

 ●NotReachable : インターネットに接続していない
 ●ReachableViaCarrierDataNetwork : キャリアネットワークで接続している
 ●ReachableViaLocalAreaNetwork : Wi-Fiでネットワークに接続している

以下、実装例。

void CheckNetwork()
    {        
        if (Application.internetReachability == NetworkReachability.NotReachable)
        {            
            Debug.Log("ネットワーク接続無し");
        }
        else if(Application.internetReachability == NetworkReachability.ReachableViaCarrierDataNetwork)
        {
            Debug.Log("キャリアネットワークに接続");
        }
        else if((Application.internetReachability == NetworkReachability.ReachableViaLocalAreaNetwork)
        {           
            Debug.Log("Wi-Fiネットワークに接続");
        }
    }

実際スマホアプリで使用する場合は、ネットワーク接続が無ければ、警告を表示するとかになりますね。
今回の筆者の場合は、スタートボタンは最初から表示しておいて、ボタンを押した時に広告Loadとネットワーク接続の確認をして、それぞれ処理を行う、って感じで実装する事になると思います。

ここまで読んでいただき、ありがとうございました。

参考にさせていただいたサイト:エクスプラボ 様
https://ekulabo.com/network-reachability

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

Unityで作成したアプリにおいて、ネットワーク接続を確認する場合に使う「Application.internetReachability!」

筆者の環境:OS:Windows10, Unity 2019.3.5f1 使用言語:C#
利用Asset:DOTween,Destructible2D
筆者のプログラミング歴:2020年1月よりUnity上でC#の勉強を開始。

ネットワークに接続しているかどうかを確認してくれる「Application.internetReachability」

初制作ゲームにて、Admob広告を掲載したのですがその際に、

『広告のLoadを待たずにゲームを開始すると、バナー広告の操作が上手くいかず、消すべき時に消せない。』

という不具合が出たので、広告のLoadが完了するまで、スタートボタンを表示させない、という無理やりな方法で
不具合を回避していました。
その為、インターネット接続していない端末はスタート出来ないので、それに対しての回避策を用意する、という更に面倒な事になっています。
今思えば、愚策中の愚策ですね。

現在開発中のゲームでもランキングを搭載予定なのもあり、ネットワーク接続しているかどうかの確認が出来れば、この問題は簡単に解決出来ると思い、調べてみました!
すぐ見つかりました(というか、一度調べてました)。

Application.internetReachabilityを使えば、ネットワーク接続しているかどうかの確認どころか、接続しているネットワークがWi-Fiなのかキャリアネットワーク(4Gとかですね)なのか?も、わかるそうです。

 ●NotReachable : インターネットに接続していない
 ●ReachableViaCarrierDataNetwork : キャリアネットワークで接続している
 ●ReachableViaLocalAreaNetwork : Wi-Fiでネットワークに接続している

以下、実装例。

void CheckNetwork()
    {        
        if (Application.internetReachability == NetworkReachability.NotReachable)
        {            
            Debug.Log("ネットワーク接続無し");
        }
        else if(Application.internetReachability == NetworkReachability.ReachableViaCarrierDataNetwork)
        {
            Debug.Log("キャリアネットワークに接続");
        }
        else if((Application.internetReachability == NetworkReachability.ReachableViaLocalAreaNetwork)
        {           
            Debug.Log("Wi-Fiネットワークに接続");
        }
    }

実際スマホアプリで使用する場合は、ネットワーク接続が無ければ、警告を表示するとかになりますね。
今回の筆者の場合は、スタートボタンは最初から表示しておいて、ボタンを押した時に広告Loadとネットワーク接続の確認をして、それぞれ処理を行う、って感じで実装する事になると思います。

ここまで読んでいただき、ありがとうございました。

参考にさせていただいたサイト:エクスプラボ 様
https://ekulabo.com/network-reachability

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

UniRx 間隔の短いタップを検出する

概要

ポインターが押された時と離された時の間隔が一定以下のときだけ発火する物をUniRxで作成しました。

赤のエフェクトは押されたこと、青は離されたことを示します。その間隔が短いときだけLogが出力されてます。
shorttap.gif

プロジェクト全体はこちらから
https://github.com/Arihide/unirx-shorttap

説明

短いのでソースコード丸ごと乗っけます。

using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UniRx;
using UniRx.Triggers;

public class ShortTap : UIBehaviour
{
    protected override void Awake()
    {
        base.Awake();

        const double shortTapThreshold = 200d;

        this.OnPointerUpAsObservable()
            .Timestamp() // 時間情報付与
            .WithLatestFrom( // ポインターが離れたとき、最後に押された時の情報と合体させる
                this.OnPointerDownAsObservable().Timestamp(),
                (l, r) => new { up = l, down = r }
            )
            // 押した時と離れたときの間隔が shortTapThreshold 以下のときだけ通す
            .Where(pair => (pair.up.Timestamp - pair.down.Timestamp) <= TimeSpan.FromMilliseconds(shortTapThreshold))
            .Subscribe(_ => Debug.Log("Short Tap!"));
    }

}

やってることとしては、
- ポインターが離れたときに、最後にポインターが押されたときの情報とニコイチにする
- 押された時と離れたときの時刻を比較し、閾値以下なら通す
という感じですね。

この方法は押されたときではなく、離れたときに発火するのがミソです。
また、条件式を調整すればロングタップなどにも対応できます。

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

【エディタ拡張】UI BuilderでC#コードを生成できるようにしてみる【UI Builder Extensions】

概要

UI Builder+UI Elementsでエディタ拡張(やランタイム)のUIを視覚的に実装できるようになりました。
UIBuilderではレイアウトの構造(UXML)とスタイル(USS)を生成できます。

  • 構造=>UXML(UI Builderで生成可能)
  • スタイル=>USS(UI Builderで生成可能)
  • ロジック=>C#(UI Builder 未対応??)

今回UI Builder Extensionsというものを作り、C#ロジックを生成できるようにしてみましたので紹介します。
UIBuilderExtensions.gif

環境

  • Unity2019.4.1f1~
  • UI Builder 1.0.0 preview

インストール

manifestに下記を追加してください。

{
  "dependencies": {
    "com.t-macnaga.ui-builder-extensions": "https://github.com/t-macnaga/UIBuilderExtensions.git",
  }
}

コード生成

UI Builder Extensionsをインストールしますと、
UIBuilderにEventのインスペクターがあらわれます。
2020-07-25_00h54_50.png

Eventsを開きますと、
ButtonならOnClick、TextFieldやToggleならOnValueChanged、といった具合に
イベント一覧があります。

追加したいコールバックを ダブルクリック しますと、
そのUXMLと同じ階層にC# EditorWindow コードが作成される仕掛けとなっております。

※ダブルクリックするまえに下記の注意点があります。

  • VisualElementのNameを登録してください
  • Name`に登録したら、UXMLを保存してください

生成されるコードサンプル

using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor;
using UnityEditor.UIElements;

public partial class SampleWindow : EditorWindowBase
{
    [MenuItem("Window/SampleWindow")]
    static void Init()
    {
        GetWindow<SampleWindow>();
    }

    void OnEnable()
    {
        InitializeComponents();
    }

    void OnClick_button1()
    {
        Debug.Log("button1 Clicked");
    }
}

//AUTOGENERATED
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor;
using UnityEditor.UIElements;

public partial class SampleWindow
{
    protected override string TreeAssetPath => "Assets/Samples/Editor/SampleWindow.uxml";
    protected override void RegisterCallbacks()
    {
        rootVisualElement.Q<Button>("button1").clicked += OnClick_button1;
    }
}

プロジェクトデータ

GitHubで公開しました。
https://github.com/t-macnaga/UIBuilderExtensions

まとめ

UIBuilder+UIElementsと、さらに今回作成のUIBuilderExtensionsを使えば
エディタ拡張ウィンドウのコード作成が楽になるかもです。

参考記事

下記参考にさせていただきました。
* UIElements+UI BuilderでEditor拡張を作ろう

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

DOTween完全に理解するその5 Sequence編

前回:DOTween完全に理解するその4 DOPath編

今回解説するアニメーション

今回はDOTween最重要と言っても過言ではないSequenceについて解説します。
Tweenを繋げて1つのアニメーションとして連続実行させることができ、
複雑なアニメーションを作ることができます。

開発環境
Unity:2019.4.0f1
DOTween:v1.2.335

Append

sequence11.gif

var sequence = DOTween.Sequence(); //Sequence生成
    //Tweenをつなげる
    sequence.Append(transform.DOMoveX(7, 1))  
            .Append(transform.DOMoveX(-7, 1)) 
            .Append(transform.DOMoveX(0, 1)); 

Sequenceの末尾にTweenを追加します。
前のTweenが終わり次第次のアニメーションが実行されます。
内部的にはTweenの終了を待っているわけではなく、
前のTweenが終わる時間までDelayしている感じです

AppendInterval / AppendCollback

sequence12.gif

//TweenをつなげつつAppendIntervalとAppendCallbackを使う
sequence.Append(transform.DOMoveX(7, 1))
        .AppendInterval(0.25f)
        .AppendCallback(() =>
        {
            targetRenderer.material.color = Color.yellow;
        })
        .Append(transform.DOMoveX(-7, 1))
        .AppendInterval(0.25f)
        .AppendCallback(() =>
        {
            targetRenderer.material.color = Color.cyan;
        })
        .Append(transform.DOMoveX(0, 1));

Appendと同様に末尾に待機時間やコールバックを追加します。

Join

sequence21.gif

//Joinで並列動作するTweenを追加
sequence.Append(transform.DOMoveX(7, 1))
        .Join(transform.DORotate(new Vector3(0, 180), 1.5f, RotateMode.WorldAxisAdd)) //0 ~ 1.5
        .AppendInterval(0.25f) //1.5 ~ 1.75
        .AppendCallback(() =>
        {
            targetRenderer.material.color = Color.yellow;
        })
        .Append(transform.DOMoveX(-7, 1)) 
        .Join(transform.DORotate(new Vector3(0, 180), 1.5f, RotateMode.WorldAxisAdd)) //1.75 ~ 3.25
        .AppendInterval(0.25f) //3.75 ~ 4
        .AppendCallback(() =>
        {
            targetRenderer.material.color = Color.cyan;
        })
        .Append(transform.DOMoveX(0, 1))
        .Join(transform.DORotate(new Vector3(0, 180), 1.5f, RotateMode.WorldAxisAdd)); //4 ~ 5.5

直前のTweenと並行して動作するようにTween追加を行います。
Joinの後Appendした場合もっとも時間のかかったTweenの後に実行されます。

API アニメーション時間 開始時間〜終了時間
Append 5秒 0〜5秒
Join 10秒 0〜10秒
Append 5秒 10~15秒

Insert / InsertCallback

sequence31.gif

//Method2と同じ動きをInsertで行う
sequence.Append(transform.DOMoveX(7, 1.5f))  //0 ~ 1.5
        .AppendInterval(0.25f)                                 //1.5 ~ 1.75
        .Append(transform.DOMoveX(-7, 1.5f)) //1.75 ~ 3.25
        .AppendInterval(0.25f)                                 //3.25 ~ 3.5
        .Append(transform.DOMoveX(0, 1.5f)); //3.5 ~ 5

sequence.Insert(0, transform.DORotate(new Vector3(0, 0, 1800), 5.5f, RotateMode.WorldAxisAdd));

sequence.InsertCallback(1.75f, () =>
{
    targetRenderer.material.color = Color.yellow;
});
sequence.InsertCallback(3.5f, () =>
{
    targetRenderer.material.color = Color.cyan;
});

時間を指定して、Tweenerやコールバックを挿入します。
AppendやJoinとは異なり前後のTweenerなどに関係なく並列で動作します。

Prepend / PrependCallback / PrependInterval

sequence41.gif

var sequence = DOTween.Sequence();
sequence.Append(transform.DOMoveX(7, 1))
        .Append(transform.DOMoveX(-7, 1))
        .Append(transform.DOMoveX(0, 1));

//前に追加する
sequence.PrependInterval(0.5f);
sequence.PrependCallback(() => { targetRenderer.material.color = Color.red; });
sequence.Prepend(transform.DORotate(new Vector3(0, 0, 180), 1, RotateMode.WorldAxisAdd));

Sequenceの先頭にTweenerやコールバックを追加します。
先頭に追加されていくので、最後に追加したものから最初に実行されます。

Sequence同士をSequenceで繋ぐ

sequence51.gif

//SequenceにSequenceに追加する
var move = transform.DOMoveX(6, 0.5f)
                    .SetRelative(true)
                    .SetLoops(2, LoopType.Incremental);
var rotate1 = transform.DORotate(Vector3.forward * 90, 0.5f, RotateMode.WorldAxisAdd);
var sequenceA = DOTween.Sequence()
                       .Append(move)
                       .Join(rotate1);

var scale = transform.DOScale(1, 0.5f);
var rotate2 = transform.DORotate(Vector3.forward * 90, 0.5f, RotateMode.WorldAxisAdd)
                       .SetRelative(true);
var sequenceB = DOTween.Sequence()
                       .Append(scale)
                       .Insert(0.5f, rotate2)
                       .SetLoops(2, LoopType.Yoyo);

_tween = DOTween.Sequence()
                .Append(sequenceA)
                .Join(sequenceB);

_tween = DOTween.Sequence()
                .Append(_tween)
                .Append(transform.DORotate(Vector3.forward * 180, 1f, RotateMode.WorldAxisAdd));

SequenceもTweenerなので、Sequence同士をSequenceで繋ぐこともできます。
またSetLoops()をしたTweenerをSequenceに含めることもできますし
Sequence自体にSetLoops()をすることもできます。

Sequenceを使用する際の注意点

無限ループが使えない

例えば以下のようなSequence内のTweenerが無限ループする設定は無視されます。
Sequenceが無限ループする場合は問題ありません。

var move = transform.DOMoveX(1, 0.5f)
                    .SetRelative(true)
                    .SetLoops(-1, LoopType.Incremental);
var sequence = DOTween.Sequence()
                      .Append(move);

無効化される設定

SetAutoKill(true)やSetSpeedBased(true)などの設定は、
Sequence追加時にfalseに設定されてしまいます。

普通に考えればSeuqnceが終わっていない以上
PlayBackwordなどで実行済みのTweenを実行する必要が出てくるので
その際にKillされていたら困る為、設定が無効化されるのでしょう。

SpeedBaseもSequence自体は全て時間ベースで追加や挿入が行われる為
設定が無効化されるのだと思われます。

Sequenceに追加する前に設定を終わらせる

Tweenに対する設定はSequenceに追加する前に終わっている必要があります。
以下のようにSequence追加後のTweenへの設定は無視されます。
Sequence追加前に行った設定は正しく反映されます。(もちろん無効化されるもの以外)

var move = transform.DOMoveX(1, 0.5f)
                    .SetRelative(true);
var sequence = DOTween.Sequence()
                      .Append(move);
move.SetDelay(3f);
move.SetLoops(2, LoopType.Incremental);
move.From(0);

まとめ

Sequenceは作りが単純な分できることがかなり多い為、様々な場面で活躍します。
UnityはデフォルトではAnimator/Animationを使ってアニメーションを作ると思います。
ただ、Animatorの場合は固定化されたアニメーションを作るのには向いていますが
状況によって微妙に変化するアニメーションなど
コードから動きを細かく制御したい場合はSequenceが有効です。
Inspectorにアニメーション用のパラメータを出しておけば調整も簡単です。
あとこれは全てのTweenに該当しますがTimeScaleで個別に速度を変えられる点も強力だなぁと最近思いました。

最後に最近作ったどこで使うかわからないアニメーションを置いておきます。ご査収ください。

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

UnityでもRoslynで構文解析やコード評価したい

はじめに

RoslynはC#の構文解析やコード評価を行えるライブラリでC# 6以降のコンパイラでも使われています。Roslynを利用すると構文解析された結果を利用できるので文字列比較や正規表現と比べると解析漏れをなくせます。

RoslynのインストールはNuGetで使えますがUnityだと重複するdllがありそのままでは導入できません。それをUPMで簡単に導入する方法が分かったのでまとめてみました。

Unityでは以下のプロダクトでRoslynが使われています。

環境構築

Unity2018.3以上

  • Package Managerから Add package from git URL... できるバージョンでは com.unity.code-analysis を追加する
  • Package Managerから Add package from git URL... できないバージョンでは Packages/manifest.json"com.unity.code-analysis": "0.1.2-preview", を追加する

インストールできると以下のようにPackage Managerに追加されます。

image.png

インストールだけではDLLを参照できないのでRoslynを使いたいスクリプトフォルダにアセンブリ定義を作り、以下の設定を変更します。

  • 一般 > リファレンスをオーバーライドをチェック
  • アセンブリ参照に Microsoft.CodeAnalysis で始まるdllを追加
  • エディタ拡張で使うならプラットフォームの Editor のみをチェック、アプリ内で使うならデフォルトの 任意のプラットフォーム のままでOK

image.png

Roslynのサンプル

Scripting API SamplesGetting Started C# Syntax Analysisを参考にサンプルを実行してみます。それぞれcode欄に実行または解析するコード、result欄にその結果を表示しています。また実行できるUnityプロジェクトは https://github.com/shiena/UnityRoslynSample にありメニューの Tools > Roslyn Sample を選択するとウインドウが開きます。

Evaluate a C# expression

コードを実行します。

string code = "1 + 2";
var result = CSharpScript.EvaluateAsync(code);

image.png

Evaluate a C# expression (strongly-typed)

ジェネリクスで結果の型を指定してコードを実行します。

string code = "1 + 2";
var result = CSharpScript.EvaluateAsync<int>(code);

image.png

Parameterize a script

クラス定義したパラメータをコードに適用して実行します。

public class Globals
{
    public int X;
    public int Y;
}
string code = "X+Y";
var globals = new Globals {X = 1, Y = 2};
var result = CSharpScript.EvaluateAsync<int>(code, globals: globals);

image.png

Query Methods

コードを解析してMainメソッドの最初の引数を出力します。

string code =
            @"using System;
using System.Collections;
using System.Linq;
using System.Text;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(""Hello, World!"");
        }
    }
}";
SyntaxTree tree = CSharpSyntaxTree.ParseText(code);

var root = (CompilationUnitSyntax) tree.GetRoot();
var firstMember = root.Members[0];
var helloWorldDeclaration = (NamespaceDeclarationSyntax) firstMember;
var programDeclaration = (ClassDeclarationSyntax) helloWorldDeclaration.Members[0];
var mainDeclaration = (MethodDeclarationSyntax) programDeclaration.Members[0];
var argsParameter = mainDeclaration.ParameterList.Parameters[0];
var firstParameters = from methodDeclaration in root.DescendantNodes()
                .OfType<MethodDeclarationSyntax>()
            where methodDeclaration.Identifier.ValueText == "Main"
            select methodDeclaration.ParameterList.Parameters.First();
var argsParameter2 = firstParameters.Single();

image.png

SyntaxWalkers

コードを解析してSystemまたはSystem.以外で始まるusingを出力します。

class UsingCollector : CSharpSyntaxWalker
{
    public readonly List<UsingDirectiveSyntax> Usings = new List<UsingDirectiveSyntax>();

    public override void VisitUsingDirective(UsingDirectiveSyntax node)
    {
        if (node.Name.ToString() != "System" &&
            !node.Name.ToString().StartsWith("System."))
        {
            this.Usings.Add(node);
        }
    }
}
string code =
            @"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace TopLevel
{
    using Microsoft;
    using System.ComponentModel;

    namespace Child1
    {
        using Microsoft.Win32;
        using System.Runtime.InteropServices;

        class Foo { }
    }

    namespace Child2
    {
        using System.CodeDom;
        using Microsoft.CSharp;

        class Bar { }
    }
}";

SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
var root = (CompilationUnitSyntax) tree.GetRoot();

var collector = new UsingCollector();
collector.Visit(root);

image.png

参考リンク

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