kazuki’s blog

新人エンジニアのブログ

勉強会「LWRPのパス拡張について」を開催しました

こんにちは
かずきです。

お世話になっているサポーターズColabさんで勉強会を開催しました!
今回は前回に引きつづきLWRPのお話です。

・内容はパスの追加について
・カメラスタッキングを利用するには
の2点です!


質疑応答

Q.追加パスで出てくるloadOpのRenderBufferLoadActionのLoadとDon'tCareはどういう意味なのか?

A.SRPでは描画内容をColorとDepthのRenderTextureとしてBufferに担保し描画している。
 Loadは前パスまでの内容を読み込む。
 Don'tCareは前パスまでのパスを気にしない(読み込まなくて良い場合は読み込まない)。

 Qpaqueの場合、前パスの内容を気にしなくて良いのでDon'tCareにすると一部GPUを除きパフォーマンスが向上します。  

勉強会「Amplify Shader Editorを使用したLWRP対応について」を開催しました

こんにちは
かずきです!

Amplify Shader Editorを使用した、Lightweight Render Pipeline対応についてお話しする機会がありました!!


資料

www.slideshare.net


シェーダ置換ツール
github.com


LWRP対応Amplify Shaderテンプレート & カスタムノード
github.com

勉強会「AssetBundleちょっと理解した( )」を開催しました!

こんにちは!
かずきです。

サポーターズさんにてAssetBundleをテーマに勉強会を開かせていただきました〜

資料

www.slideshare.net

サンプルプロジェクト

github.com

質疑応答

  • Q . 2018ユーザはAddressable Assetを使用した方が良いとの事でしたが、
    Caching APIの問題は解決されたのか?
  • A . ちゃんと触ってないので確かな事は言えないが解決して無かったように思う。

  • Q . Caching.isReadyの問題はある程度改善されているが、
    キャッシュの個別削除がちゃんと機能していない方が問題に思う。
    サンプルプロジェクトではDownloadHandlerAssetBundleを使っていたがこの問題にはどう対処したか?
  • A . サンプルプロジェクトは今回最小実装を説明する為に作成したものなので対処はしていない。
    実際に商用タイトルなどで使用するものを実装する際はキャッシュ機構を自作した方が良いと思う。

Amplify Shader Editor触ってみた

こんにちは〜 かずきです。

去年のブラックフライデーセールで買ったのに使っていなかった...
今回少しだけ触ってみた(というかサンプル見てみた)ので備忘録的なもの書いてみます!

Amplify Shader Editorとは

Surface ShaderをノードベースのGUIエディタで作成する事の出来るUnity向けアセットです。

サンプルシェーダAnimatedFireを見てみた

f:id:tc_kazuki:20180324221917g:plain
炎が燃えながら動くシェーダです。

f:id:tc_kazuki:20180318010801p:plain シェーダをノードエディタ画面開いたらこのようになっています。

シェーダの各処理を見てみた

UVスクロール

f:id:tc_kazuki:20180318010842p:plain
全体で見た場合、左下の方にある処理です。
UVスクロールとは時間などの変動する値を利用し、テクスチャの描画位置を移動させていくものの事を指します。
ここでは経過時間を20分の1にしたものと、インスペクターから設定したTileSpeedを元にUVスクロールさせています。

f:id:tc_kazuki:20180324223446g:plain
なのでTileSpeedの値を上げると炎の動きが速くなり、 f:id:tc_kazuki:20180324223631g:plain
0にすると炎は止まります。

テクスチャのアルファ値変化

f:id:tc_kazuki:20180324221708j:plain
経過時間を基準値としたSinカーブ(-1 〜 1)の値を加算していったものと、インスペクターから設定したFireIntensityの値を元にアルファ値を変化させています。

f:id:tc_kazuki:20180325000759g:plain FireIntensityの値を上げると炎が強くなり、
f:id:tc_kazuki:20180325000939g:plain 低くすると炎が弱くなります。

テクスチャ合成

f:id:tc_kazuki:20180324235237j:plain
Multiplyノードでテクスチャの合成をしています。
UVスクロール処理を加えたTileableFireテクスチャと処理を加えてないMaskテクスチャを合成したのちに、
アルファ値だけ変化させている白色テクスチャを合成する事で炎の強弱を変化させています。

f:id:tc_kazuki:20180325001528g:plain
なので、FireIntensityを0にすると炎が完全に消え、
f:id:tc_kazuki:20180325001732g:plain
TileableFireのテクスチャ画像を真っ白なものにすると動きがなくなります。

まとめ

ノードベースのGUIエディターでシェーダーが組みやすいとはいえシェーダーの基礎知識が無いと、
作りたいシェーダーを作るのは難しいなと思いました。
とりあえず勉強します!

Behavior Designer触ってみた

こんにちは! かずきです。

今回はBehavior DesignerというゲームAIを作成する為のUnity向けアセットを触ってみたので、 備忘録的なものを書きます。

Behavior Designerとは

Task(行動や条件)をノードとしてツリー構造で管理・実行できて、ツリーをGUIエディターで作ることの出来るアセット。
UE4にデフォでついてるBehavior Treeみたいなもの。

ビジュアル化されているので比較的わかりやすく効率のより思考ルーチンを組む事が出来る(と思う)

Behavior Designerのタスク

エディタ内では
Composite
Decorator
Conditional
Action
の4種類で分けられていますが、大きく分けると
Action Task (行動するタスク)
Conditional Task (条件判断するタスク)
の2種類で構成されています。

- Action Task ・・・ Decorator Action
- Conditional Task ・・・ Composite Conditional
といった感じに分けられます。

Compositeタスク

Conditional Taskの1種で、タスクの実行結果を条件判断値として利用し、子ノードのタスクを実行していきます。

主なCompositeタスク

Sequence

f:id:tc_kazuki:20180304180104p:plain エディタ上で見て左から順に子ノードのタスクを実行します。
Failure(失敗)が子ノードのタスクから返ってきた際には自身もFailureを返し、その後に続く子ノードのタスクは実行されません。

Selector

f:id:tc_kazuki:20180304180048p:plain Sequenceの逆でSuccessが子ノードのタスクから返ってきた際には自身もSuccessを返し、
その後に続く子ノードのタスクは実行されません。

Parallel (並列実行版Sequence)

SequenceとSelectorは子ノードのタスクを左から順番に実行していましたが、
Parallel(並行)は文字通り子ノードのタスクを並列実行します。
実行した子ノードのタスクが1つでもFailureを返したタイミングで他の子ノードのタスクを全て中断して、
自身もFailureを返します。

Parallel Selector (並列実行版Selector)

子ノードのタスクを並列実行します。
実行した子ノードのタスクが1つでもSuccessを返したタイミングで他の子ノードのタスクを全て中断して、
自身もSuccessを返します。

Random Sequence (ランダム実行順版Sequence)

Sequenceでは左から順に子ノードのタスクを実行していましたが、Random Sequenceではランダムな順番で子ノードのタスクが実行されます。
その他の動作はSequenceと同様です。

Random Selector (ランダム実行順版Selector)

Selectorでは左から順に子ノードのタスクを実行していましたが、Random Selectorではランダムな順番で子ノードのタスクが実行されます。
その他の動作はSelectorと同様です。

Priority Selector (優先順位指定版Selector)

Selectorでは左から順に子ノードのタスクを実行していましたが、Priority Selectorでは子ノードのタスクに設定されたPriorityが高い順にタスクを実行します。
その他の動作はSelectorと同様です。

まとめ

今回は軽く触ってみただけなので、軽めの内容になってます。
しっかり触ったのちにもうちょっとちゃんとした内容の記事書きます。。

間違いなどございましたらコメント欄でご指摘していただけると幸いです。

では、また!

SRDebuggerのBugReportをSlackに送信する

こんにちは!
かずきです。
 
今回はUnityのアセットSRDebuggerのBugReport機能を改造して、
Slackに送信してみます!

バグレポートタブを有効化する

まず、BugReportTabControllerを改造します。
適当な名前(今回はMyBugReportTabControllerとします)のC#スクリプト作成し、
StompyRobot/SRDebugger/Scripts/UI/Tabs/BugReportController.csの内容をコピペします。

public bool IsEnabled
{
    get { return Settings.Instance.EnableBugReporter; }
}

ここを以下のように書き換えて保存します。

public bool IsEnabled
{
    get { return true; }
}



編集する為、StompyRobot/SRDebugger/Resources/SRDebugger/UI/Prefabs/Tabs内にあるBugReportTab.prefabをシーン上に配置します。
f:id:tc_kazuki:20180121162138p:plain

 
インスペクターをDebugモードに切り替えて、BugReportTabControllerを先程作ったスクリプトに差し替えます。

 
f:id:tc_kazuki:20180121162159p:plain

  
オブジェクト名・タブ名・キーを変更します。

f:id:tc_kazuki:20180121162232p:plain

StompyRobot/SRDebugger/Resources/SRDebugger/UI/Prefabs/Tabsにプレハブを保存します。(Reaource下のパスが同じなら他のディレクトリでOK)

f:id:tc_kazuki:20180121162245p:plain


f:id:tc_kazuki:20180121162255p:plain
これでバグレポートタブが表示されます。
ですが、このままではレポートの送信が出来ません(;ω;)

バグレポートをSlackに送信する

次はBugReportApiを改造します。
適当な名前(今回はMyBugReportApiとします)のC#スクリプト作成し、
StompyRobot/SRDebugger/Scripts/Internal/BugReportApi.csの内容をコピペします。

作成したスクリプト(MyBugReportApi)を編集していきます。

内容別で送信する為、レポート種別のEnumを作成します。

#region Enums

public enum ReportType : int
{
    ScreenShot = 0,
    SystemInfo,
    Console,
}

#endregion //Enums



次にSlack投稿時に使用する情報を定義します。

       #region Constants

        /// <summary>
        /// Slack API 
        /// </summary>
        private static readonly string SlackApiUrl = "https://slack.com/api";
        private static readonly string UploadEndPoint = "/files.upload";
        /// @note attachment上手行かない。。。
        //private static readonly string PostMessageEndPoint = "/chat.postMessage";
        private static readonly string AccessToken = "YourAPIToken";
        private static readonly string WebHookUrl = "https://hooks.slack.com/services/Txxxxx/Bxxxxxx/xxxxxxxxxxxxxxxxxx";
        private static readonly string Channels = "general";
        private static readonly string UserName = "SRDebuggerBugReport";

        /// <summary>
        /// Attachmentのカラー
        /// </summary>
        private static readonly string SystemInfoColor = "#000000";

        /// <summary>
        /// Log毎にAttachmentを分ける場合のカラー
        /// </summary>
        private static readonly string ErrorLogColor = "#FF0000";
        private static readonly string WarningLogColor = "#FFD700";
        private static readonly string NormalLogColor = "#708090";

        #endregion // Constants



3回に分けて投稿する為、ログIDを生成し保存するための変数を宣言します。

private static uint _logId;



BuildJsonRequestメソッドを削除して、以下の4つのメソッドを作成します。

static WWWForm BuildScrrenShotUploadRequest (BugReport report)
{
    var form = new WWWForm();

    form.AddField("token", AccessToken);
    form.AddField("channels", Channels);
    form.AddField("username", UserName);
    var title = string.Format("ScreenShot ({0})", _logId);
    form.AddField("title", title);
    form.AddBinaryData("file", report.ScreenshotData, "screenShot", "imag/png");
    form.AddField("initial_comment", report.UserDescription);

    return form;
}
static string BuildJsonRequest (int typeValue, BugReport report)
{
    string json = "";
    switch (typeValue)
    {
        case (int)ReportType.ScreenShot:
            var ht = new Hashtable();
            ht.Add("text", report.UserDescription);
            json = Json.Serialize(ht);
            break;
        case (int)ReportType.SystemInfo:
            json = BuildSystemInfoJsonRequest(report);
            break;
        case (int)ReportType.Console:
            json = BuildConsoleLogJsonRequest(report);
            break;
    }
    return json;
}
static string BuildSystemInfoJsonRequest (BugReport report)
{
    var ht = new Hashtable();
    ht.Add("username", UserName);
    var messageTitle = string.Format("SystemInfo ({0})", _logId);
    ht.Add("text", messageTitle);

    List<Hashtable> attachmentList = new List<Hashtable>();
    List<Hashtable> systemInfoList = new List<Hashtable>();
    foreach (var systemInfos in report.SystemInformation)
    {
        var systemInfoHashTable = new Hashtable();
        systemInfoHashTable.Add("color", SystemInfoColor);
        systemInfoHashTable.Add("title", systemInfos.Key);
        var sb = new StringBuilder();
        foreach (var obj in systemInfos.Value)
        {
            sb.AppendLine(obj.ToString());
        }
        systemInfoHashTable.Add("text", sb.ToString());

        attachmentList.Add(systemInfoHashTable);
    }

    ht.Add("attachments", attachmentList);

    var json = Json.Serialize(ht);

    return json;
}
private static string BuildConsoleLogJsonRequest (BugReport report)
{
    var ht = new Hashtable();
    ht.Add("username", UserName);
    var messageTitle = string.Format("Console ({0})", _logId);
    ht.Add("text", messageTitle);

    List<Hashtable> attachmentList = new List<Hashtable>();

    foreach (var logs in CreateConsoleDump())
    {
        StringBuilder sb = new StringBuilder();
        var consoleHashTable = new Hashtable();

        switch (logs[0])
        {
            case "Assert":
            case "Exception":
            case "Error":
                consoleHashTable.Add("color", ErrorLogColor);
                break;
            case "Warning":
                consoleHashTable.Add("color", WarningLogColor);
                break;
            default:
                consoleHashTable.Add("color", NormalLogColor);
                break;
        }

        // LogType + Log
        var title = logs[0] + " : " + logs[1];
        consoleHashTable.Add("title", title);
        // StackTrace
        for (int i = 2; i < logs.Count; i++)
        {
            sb.Append(logs[i]);
        }
        sb.AppendLine();
        consoleHashTable.Add("text", sb.ToString());
        attachmentList.Add(consoleHashTable);
    }

    ht.Add("attachments", attachmentList);

    var json = Json.Serialize(ht);

    return json;
}



Submitメソッドを以下のように編集します。

public IEnumerator Submit ()
{
    //Debug.Log("[BugReportApi] Submit()");

    if (_isBusy)
    {
        throw new InvalidOperationException("BugReportApi is already sending a bug report");
    }

    // Reset state
    _isBusy = true;
    ErrorMessage = "";
    IsComplete = false;
    WasSuccessful = false;
    _www = null;
    _logId = (uint)System.Security.Cryptography.MD5.Create().GetHashCode(); // ハッシュ値ならそうそう被らないだろうという安直な考え

    foreach (var type in Enum.GetValues(typeof(ReportType)))
    {
        try
        {
            if ((int)ReportType.ScreenShot == (int)type && _bugReport.ScreenshotData != null)
            {
                var form = BuildScrrenShotUploadRequest(_bugReport);
                var url = SlackApiUrl + UploadEndPoint;

                _www = new WWW(url, form);
            }
            else
            {
                string json = BuildJsonRequest((int)type, _bugReport);

                var jsonBytes = Encoding.UTF8.GetBytes(json);
                var headers = new Dictionary<string, string>();
                headers["Content-type"] = "application/json";
                headers["Accept"] = "application/json";
                headers["Method"] = "POST";
                headers["data"] = json;

                _www = new WWW(WebHookUrl, jsonBytes, headers);
            }

        }
        catch (Exception e)
        {
            ErrorMessage = e.Message;
        }

        if (_www == null)
        {
            SetCompletionState(false);
            yield break;
        }

        yield return _www;

        if (!string.IsNullOrEmpty(_www.error))
        {
            ErrorMessage = _www.error;
            SetCompletionState(false);

            yield break;
        }

        if (!_www.responseHeaders.ContainsKey("STATUS"))
        {
            ErrorMessage = "Completion State Unknown";
            SetCompletionState(false);
            yield break;
        }

        var status = _www.responseHeaders["STATUS"];

        if (!status.Contains("200"))
        {
            ErrorMessage = SRDebugApiUtil.ParseErrorResponse(_www.text, status);
            SetCompletionState(false);

            yield break;
        }
    }

    SetCompletionState(true);
}



StompyRobot/SRDebugger/Scripts/Services/Implementation内にあるBugReportApiServiceを編集します。

まずはメンバ変数_reportApiの型を作成したスクリプト(MyBugReportApi)の型に変更します。

private BugReportApi _reportApi;
// ↓
private MyBugReportApi _reportApi;



SendBugReportメソッド内でBugReportApiをnewしている場所を作成したスクリプト(MyBugReportApi)に変更します。

public void SendBugReport (BugReport report, BugReportCompleteCallback completeHandler,
    BugReportProgressCallback progressCallback = null)
{

    // 省略


    _reportApi = new BugReportApi(report, Settings.Instance.ApiKey);
    // ↓
    _reportApi = new MyBugReportApi(report, Settings.Instance.ApiKey);
}

これでSlackにバグレポートを送信できます!
f:id:tc_kazuki:20180121172322p:plain
f:id:tc_kazuki:20180121172421p:plain
f:id:tc_kazuki:20180121172426p:plain

追記

bitbucketにソースコードとunitypackage公開しました〜
tc_kazuki / SRDebugger BugReportSend To Slack — Bitbucket

まとめ(というか感想)

  • なるべくSRDebugger側のソースコードは編集しないように作りましたが、
    BugReportApiServiceのみ手を加えることになってしまった。

  • Slack Web API でAttachmentを使おうとしたが上手く表示出来きずWebhookで投稿してます(わかる方教えて下さい!)


    今回はログ種別などの見やすさを重視しましたが
    大きなプロダクトになると、逆に流れて見にくくなってしまうので、
    SystemInfoとConsoleはテキストファイルに1まとめにしてUploadする形にした方が良さそうなので、
    時間がある時作ってみます(後Reflectionも)。

RxSwift紹介と基本の使い方

こんにちは、かずきです!

今回の記事で一旦Swiftはおしまいにしようかと思います。
そんでもってReactiveExtension(Rx)の布教記事ですw
プログラミング初心者にはオススメしません...

Reactive Extensionsとは

非同期処理で非常に扱いやすくするライブラリで、
主にIObservableインタフェースとIObserverインタフェースから成り立ちます。
学習コストは高いものの、他の言語のRxライブラリでもほぼ使い方が変わらないため学習効率は良いと思います。
 
Observerから発火される通知をメッセージと言い、メッセージ外部に流すのがObservableとなり、
通知を受け取る側IObservableを実装したクラスをSubscribe(購読)する事になります。
 
Observer -> Observable -> (Operator) -> Subscriberの流れをストリームと言い、
Operatorはメッセージの加工や通知タイミングの変更・フィルタリング、ストリームの合成などを行えます。

Reactive Extensionsで出来る事

非同期

Observerからの通知をトリガーに動作しているので、当然の事ながら非同期を扱う事が出来ます。

時間

何秒後or毎に通知を送るObservable、Timer・Intervalが用意されていたり、
通知を遅らせるDelayや一定時間通知が無かった場合に購読を終了するTimeoutなどのOperatorがあります。

合成

複数のObservableから送られてくるストリームを合成し新しいストリームを作成する事が出来ます。
単純にストリーム合成したストリームを作成したいならMarge
それぞれのObservableから1つづつメッセージが流れ来た時に通知を受け取りるストリームを作成する場合はZipなど...etc
   
 
 
いくつか説明しましたが、他にも色々あるので残りは割愛。
ざっくり言うと通知を受け取って色々と処理を走らせる事がとても便利なライブラリって事ですw
 
スマホアプリなどではユーザの操作があった際に処理をしたいが多々あると思います。
そんな時に常に監視せずに実装できるのがReactive Extensionsって訳です。
(もちろんライブラリ内部では監視してるけど監視タイミングなどが集中管理されているので、個別監で視処理書くよりパフォーマンスが良買ったりする)

RxSwiftとは

Reactive ExtensionのSwift向けライブラリです(RxはC#(.Net)で最初に登場した)以上!

CocoaPodsの導入

RxSwiftをプロジェクトに追加するためには、CocoaPodsを利用します(他の方法もありますがコレが1番楽)

まずmacのLanchPadなどからターミナルを開き次のコマンドを叩きます。

sudo gem install cocoapods

次にCocoaPodsセットアップのコマンドを叩きます。

pod setup

導入は以上!

RxSwiftの導入

まだターミナルからは逃れられません! cdでプロジェクトのディレクトリまで移動して次のコマンドを叩きます。

pod init

Podfileというファイルが出来ているはずなので編集します。

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'プロジェクト名' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for ProjectName
  pod 'RxSwift'
end

編集し終わったら、ターミナルでコマンドを叩きます。

pod install

これで導入終了! CocoaPodsでインストールしたライブラリを利用する場合は拡張子が.xcworkspaceのファイルでプロジェクトを開いてください!!

プロジェクトでRxSwiftを利用する

RxSwiftを利用するソースコードの上部で

import RxSwift

と記述する事で利用できます。

ObserverとObservable両方の機能を持つSubject

IObserverインタフェースとIObservableインタフェースの両方を1つクラスで実装しているものをSubjectと言い、
メッセージの発火・購読の両方を1つのクラスで行う事が出来ます。
最初の内はSubjectを利用する事が多くなるかと思います。
SubjectはPublishSubject BehaviourSubject ReplaySubject3種類あります。

PublishSubject

1番単純な機能のSubjectです。 購読を開始してから発火されたメッセージを受け取る事が出来ます。  

var publishSubject = PublishSubject<T>()  // Tはメッセージで送るデータの型

BehaviorSubject

購読開始時に購読直前に発火されたのメッセージの内容を受け取る事が出来ます。
(購読開始時にまだ何も発火されていない状態だった場合は初期値が通知されます)

var behaviorSubject = BehaviorSubject<T>(value: default) // defaultは初期値

ReplaySubject

BehaviorSubjectでは購読開始時に購読直前のメッセージ内容を1つだけ受け取れましたが、 ReplaySubjectでは指定したバッファサイズ分過去のメッセージを受け取る事ができ、 初期値の通知などはありません。

 // ReplaySubjectのみcreateメソッドで作成する
var replaySubject = ReplaySubject<T>.create(bufferSize: size)

RxCocoaとは

UIKitを扱うためのRxSwift向けの拡張ライブラリで、
ボタンのタップや、テキストフィールドへの入力内容の変化をメッセージとして受け取れます。

インストールはRxSwiftと同様CocoaPodsを利用して、インストールします。 Podfileを編集します。

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'プロジェクト名' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for ProjectName
  pod 'RxSwift'
  pod 'RxCocoa' # <- これを追加する
end

追加したので、コマンドを叩いてインストールする

pod install

以上で導入完了です!
 
 
Rx初心者にはとってRxCocoaはわかりやすく使いやすいものだと思いますので、
RxSwiftと一緒にRxCocoaも導入する事をオススメします!

まとめ

  • Reactive Extensionは非同期とか時間とか扱える便利なヤツ!
  • RxSwift・RxCocoaはCocoaPodsを使ってインストール
  • RxCocoaはタップなどのUIのイベントを取って処理を書ける  
     
    これでSwiftに関する記事は一旦終了です。
    ありがとうございましたm( )m
     
    次回は備忘録的にUnityとかの記事書こうかなって思ってます!