20200322のUnityに関する記事は13件です。

【Unity(C#)】TrailRendererの頂点座標を取得する方法

TraliRenderer

TrailRendererを使えば簡単なお絵描き機能がサクッと実装できます。

【参考リンク】:【Unity(C#)】ハンドトラッキングで簡易版VRお絵かきアプリ

今回はTrailRendererの頂点座標を取得していきます。

最終的には頂点座標をシリアライズしてJson形式で保存、
セーブデータとして外部リソースに保存したのち、
デシリアライズしてロード、、、までやります。

デモ

今回行ったデモはこちらです。

PaintTest.gif

中央のボタンを押すと、各TrailRendererの頂点にオブジェクトが生成されます。

コード

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// お絵描き機能 適当なオブジェクトにアタッチ
/// </summary>
public class Paint : MonoBehaviour
{
    [SerializeField] private Transform _paintObjParent;

    [SerializeField] private GameObject _paintObjPrefab;

    [SerializeField] private GameObject _vertCube;

    private GameObject _tmpPaintObj;

    private void Update()
    {
        //スクリーン座標をワールド座標に変換
        Vector3 worldClickPos = Camera.main.ScreenToWorldPoint(Input.mousePosition +  Camera.main.transform.forward);

        //マウス押した瞬間
        if (Input.GetMouseButtonDown(0))
        {
            _tmpPaintObj = Instantiate(_paintObjPrefab, worldClickPos, Quaternion.identity);
            _tmpPaintObj.transform.parent = _paintObjParent;
        }

        //マウス押し続けている間
        if (Input.GetMouseButton(0))
        {
            _tmpPaintObj.transform.position = worldClickPos;
        }
    }

    /// <summary>
    /// 全てのTrailRendererの全頂点を取得 Buttonに登録
    /// </summary>
    public void GetVert()
    {
        //Paintオブジェクト(TrailRenderer)のリストを作成
        List<TrailRenderer> trList = new List<TrailRenderer>();

        foreach (Transform child in _paintObjParent.transform)
        {
            TrailRenderer tr = child.GetComponent<TrailRenderer>();

            if (tr != null)
            {
                trList.Add(tr);
            }
        }

        //リストのPaintオブジェクト(TrailRenderer)それぞれから頂点を取得
        List<Vector3[]> posList = new List<Vector3[]>();

        foreach (TrailRenderer element in trList)
        {
            int posCount = element.positionCount;
            Vector3[] posArray = new Vector3[posCount];

            //全ての頂点を取ってくる
            int vertCount =  element.GetPositions(posArray);

            //描画した頂点座標を確認
            for (int i = 0; i < vertCount; i++)
            {
                Debug.Log(posArray[i]);
                Instantiate(_vertCube, posArray[i], Quaternion.identity);
            }
        }
    }
}

今回の記事内においては本質ではないので詳細には触れませんが、
クリック時にTrailRederer付きゲームオブジェクトを生成しています。

そのせいで、Listを使って若干複雑になっていますが、
下記リンク内で詰まっていた、
消しゴム機能(Undo,Redo)の実装時に役立ちます。

Save,Load時に活用します。

【参考リンク】:【Unity(C#)】ハンドトラッキングで簡易版VRお絵かきアプリ

GetPositions

TrailRendererコンポーネントに実装された、
GetPositionsメソッドを使用して頂点を取得しています。
引数に頂点座標分の数だけ要素が存在する配列を渡してあげれば、
すべての頂点を取得できます。

まとめ

今回の頂点の取得により、TrailRendererのシリアライズの準備が整いました。

次回はシリアライズから、デシリアライズまで一気に進めようと思います。

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

【Unity】NavMesh でエラー "Failed to create agent because it is not close enough to the NavMesh" が出た時の解決策

概要

UnityでNavMeshを使っていて以下のようなエラーが出ました。

Failed to create agent because it is not close enough to the NavMesh

この解決策が検索してもなかなか見つからなかったため、記録しておきます。

特に Agent Type を Humanoid 以外にしている場合はこの記事と同じ原因である可能性が高いです。

環境

Unity 2019.3.0a5
Windows10

解決策

このエラーを直訳すると、「エージェントがナビメッシュに十分に近くないため生成に失敗している」ですが、この原因として以下の2つが考えられます。

  1. エラー文通り、エージェントとナビメッシュが離れている
  2. エージェントのTypeに合ったナビメッシュが生成されていない

それぞれについて解決策を説明します。

1. エラー文通り、エージェントとナビメッシュが離れている

この場合、NavMeshAgentのコンポーネントのBase Offsetの値を変えることで直ることがあります。
Scene上にNavMeshAgentの円柱が出ているので高さが合うように調整してください。

2. エージェントのTypeに合ったナビメッシュが生成されていない

今回のメインはこちらです。
NavMeshはAgent Type を複数作ることが可能です。これはキャラクターごとに異なる高さや太さを設定するための機能です。しかし、現状Unityでは、NavigationウィンドウでBakeした場合、デフォルトのHumanoidにしか適用されません。

つまり、各Agent TypeごとにナビメッシュをBakeする必要があります。
これをするには、ステージとなるオブジェクトに、Nav Mesh Surfaceコンポーネントを追加します。そしてAgent Typeの値を指定してBakeすることで、そのAgent Typeのエージェントが動くようになります。

image.png

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

【Unity】MessagePack for C#とEntitiesを両方入れた時にエラーが出る

環境

エラーの内容

MessagePack for C#とEntitiesをInstallすると次のようにエラーが出ます。

スクリーンショット 2020-03-22 16.28.18.png

PrecompiledAssemblyException: Multiple precompiled assemblies with the same name System.Runtime.CompilerServices.Unsafe.dll included for the current platform. Only one assembly with the same name is allowed per platform. Assembly paths: Assets/Plugins/System.Runtime.CompilerServices.Unsafe.dll
Assets/Scripts/MessagePack/SequenceReaderExtensions.cs(39,21): error CS0433: The type 'Unsafe' exists in both 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a' and 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
Assets/Scripts/MessagePack/Formatters/GenericEnumFormatter`1.cs(39,107): error CS0433: The type 'Unsafe' exists in both 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' and 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'

これらのエラーはSystem.Runtime.CompilerServices.Unsafe.dllが重複して存在することに起因しているようです。

解決法

Assets/Plugins/ 以下にある System.Runtime.CompilerServices.Unsafe を削除します。

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

UniRx と DoTween で簡単にドーナツを追随するニワトリをつくる

ドーナツを追い続ける、アニメーション付きのニワトリを、UniRx と DoTween で作りましょう。
Update から切り離して、UniRx を使うことで、値がメソッドで閉じて、Time.deltaTime などの和算も不要になります。

こんな感じで動きます

tracing.gif

UniRx と DoTween でつくる Walk メソッド

    private void Walk()
    {
        // Walk アニメーションを開始
        animator.SetBool("Walk", true);

        // ここから UniRx、 Interval を使うことで 0.1秒毎に Subscribe が呼ばれ続けます
        var duration = 0.1f;
        Observable
            .Interval(TimeSpan.FromSeconds(duration)) 
            .Subscribe(_ =>
            {
                // 移動距離を計算、早く動かしたければ 0.01f の値を大きくすればOK
                var add = transform.rotation * new Vector3(0, 0, 0.01f);
                // DoTween の Move で移動させます
                transform.DOMove(transform.position + add, duration);
                // DoTween の LookAt でドーナツの方向を向かせ続けます
                transform.DOLookAt(target.position, duration, AxisConstraint.Y);
            });
    }

その他

ニワトリのAsset

  • こちら
  • 無料です。モデルとアニメーションが付いています。

停止方法

  • var hoge = Observable.Interval... みたいな感じで書いて、hoge.Dispose(); すれば、止まります。
  • 本当は Observable.Interval は AddTo(this) などをして、不要になったらリソースを破棄するべきです。ここでは省略しています。

全コード

ほとんど代わり映えしませんが、全コードです。

using System;
using DG.Tweening;
using UniRx;
using UnityEngine;

public class Chick : MonoBehaviour
{
    [SerializeField] private Animator animator; // 自分自身を設定してください
    [SerializeField] private Transform target;  // 向かっていくドーナツを設定してください

    void Start()
    {
        Walk();
    }

    private void Walk()
    {
        animator.SetBool("Walk", true);
        var duration = 0.1f;
        Observable.Interval(TimeSpan.FromSeconds(duration)).Subscribe(_ =>
        {
            var add = transform.rotation * new Vector3(0, 0, 0.01f);
            transform.DOMove(transform.position + add, duration);
            transform.DOLookAt(target.position, duration, AxisConstraint.Y);
        });
    }
}

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

C# で出来ること一覧

更新履歴

  • 2020/03/23
    • IoT について追記
    • その他に帝国兵さんのツイートを追加
    • サーバーレスカテゴリーを追加して AWS Lambda を追加
    • ASP.NET Core Razor Pages を追記
  • 2020/03/24
    • kennakamu さんの「個人的に C# が向かないと思うこと」へのリンク追加

本文

昔ブログにこんな記事を書きました。

C# で何か出来るのか?まとめてみた

あれから 2 年が経って昔からある Windows 専用の .NET Framework に対する新機能の提供が終わり、クロスプラットフォームに対応した .NET Core が今後のメインストリームとして .NET 5 → .NET 6 のように進化していくことが 2019 年 5 月の Build 2019 で発表されました。以下の Blog 記事がアナウンス後に発表されています。

Introducing .NET 5

ということで情勢が変わったので、ここらへんで一度まとめなおしてみようと思います。

そもそも C# とは

2002 年に .NET Framework 1.0 上で開発するための言語として登場。最初見た感想は「命名規約が Java はメソッド名は小文字始まりだけど大文字始まりになってる言語なんだな」でした。
言語仕様的にはプロパティが言語仕様に組み込まれている、デリゲートという今では、ほぼ全ての言語でできるメソッドを変数に代入するための型があって、それをつかってイベントという仕組みも言語仕様に組み込まれてる違いがありましたが、当時の自分はマイクロソフトが作った Java と認識してました。

その後主な機能だけ列挙すると:

  • 2005 年に C# 2.0 でジェネリクス/Nullable 型
  • 2007 年に C# 3.0 で LINQ
  • 2010 年に C# 4.0 で動的型付け
  • 2012 年に C# 5.0 で非同期処理(async/await) (8 年も前から async/await 使えてたの凄い)
  • 2015 年に C# 6.0 で null 条件演算子
  • 2017 年に C# 7.0 でタプルや型スイッチ
  • 2019 年に C# 8.0 で null 許容参照型や switch 式、パターンマッチ系

のように、進化してきて、最初に抱いていた Java クローンという印象はすっかり消えて、自分の中で最高に書きやすい言語になりました。細かい所では TypeScript とか Dart とか、Kotlin とか見てうらやましいなぁと思う言語仕様もあるけれど、これから書く守備範囲の広さと合わせて、しばらくは C# メインでやっていこうと思える感じになってます。

ちなみに、あくまで私個人の認識なので、知識に偏りはあるし、もしかしたら間違った認識をしているものもあるかもしれないので、そういうものはバンバン編集リクエストなりコメントで指摘ください m(_ _)m

コンソールアプリケーション開発

やっぱり、余分な技術的な要素を排除しつつ、やりたいことだけを検証するときとかはコンソールアプリケーションが最強ですよね。.NET Core は、グローバルツールというのがあって dotnet tool install -g dotnetsay のようなコマンドでツールをインストールして、インストール後は CLI からパチパチと叩けたりします。npm とかでやってたようなことが C# でも出来るようになってます。.NET Framework も、もちろんコンソールアプリはいけますね。

プラットフォーム/フレームワーク コメント ドキュメント
.NET Core これでやってれば、とりあえず良さそう .NET Core ツールの管理方法
.NET Framework .NET Framework にしか対応してないものがあるならこっちでも

ここから先の全てに言えることですけど、.NET Framework を検討無しで新規開発に選ぶのは辞めた方がいいと思います。.NET Core でもいけるものなら、今後も新機能追加が行われる .NET Core 系を選ぶほうが個人的にはお勧めです。

塩漬けこそ正義の環境なら .NET Framework もいいかもしれないですが

Web アプリケーション開発

Web アプリは ASP.NET Core/ASP.NET あたりを使えば出来ますね!
今から始めるなら、過去のしがらみとかが無い限りは .NET Core 3.1 で ASP.NET Core MVC とかあたりで作り始めることになると思います。

.NET Core で作っておくと、今後のメインストリームの .NET 5, .NET 6... の流れに乗れるので間違いないです。Windows でも Linux でも動くしね。Azure でも AWS でも GCP でも、何処でもデプロイ可能だし、Visual Studio 2019 を使って最高の環境で開発出来るし、Visual Studio Code で手に馴染んだエディター上でもコード補完などが効いた状態で開発もできて、実際にクラウドへのデプロイも、そこから可能でお手軽に始められる。CI/CD パイプライン組んで自動デプロイとかも楽しい。

プラットフォーム/フレームワーク コメント ドキュメント
ASP.NET Core MVC 第一候補。これでいけるなら行くべきだと思う ASP.NET Core MVC の概要
ASP.NET Core Razor Pages View(cshtml) の書き方は、MVCと同じだが、Viewに対するアクションをコントローラーではなく、cshtmlと1対1に対応した ページクラス(cshtml.cs) に定義していくスタイル。MVC がコントローラーへのアクションが起点なのに対して、こちらはページを起点に考えて作っていくスタイルなので、コントローラーをどう分割するかという問題から解放される。DI などの機能も使えるので、結構良さそう。SPA じゃない Web ページが主体のアプリだと ASP.NET Core MVC よりも作りやすい雰囲気を感じる(自分は、これでしっかり作りこんだことはない) ASP.NET Core での Razor ページの概要
ASP.NET MVC .NET Framework に縛られるなら、これを使ってなるべく ASP.NET MVC への依存を低く作っておくのが良さそう ASP.NET MVC 5 の概要
ASP.NET WebForms 過去の資産がない限りは採用する理由は、あまりないと思う。
ノンコーディングで DB からデータ取ってきて表形式で表示できるとかの強みが活かせるなら強いかもしれないけど、そういう用途なら PowerApps 系とかそっちのほうが今では良さそうに思う。
ASP.NET Web Forms

その他にも Blazor サーバーというものもあって、SPA みたいなのを作れるけどブラウザは HTML/CSS でレンダリングしてるだけで、実態は WebSocket とかでサーバーに繋いでいてボタンを押したときの処理やらステートやらは全部サーバーでもってるというのもあります。

プラットフォーム/フレームワーク コメント ドキュメント
Blazor サーバー 1,000 人クライアントがいたら 1,000 人ぶんのデータをサーバーのメモリ上に持つので、大規模な B2C とかに使うのは、個人的にはちょっと不安がある(検証はしたことないです)。社内アプリとかでは、C# でロジックとかが書けるので幸せになれるかもしれないと思ってる。 ASP.NET Core Blazor の概要

Web API 開発

これも Web アプリケーション開発とほぼ同じです。.NET Core 系が今後のメイン。

プラットフォーム/フレームワーク コメント ドキュメント
ASP.NET Core これが一番機能充実してそう ASP.NET Core を使って Web API を作成する
ASP.NET WebAPI .NET Framework ならこっち ASP.NET Web API 2 (C#) を使ってみる

Web API を作るという観点では、共通の前処理を仕込んだりとか便利機能が豊富な ASP.NET Core が一番開発効率は高いような気がしてますが、サーバーレスプラットフォームの Azure Functions の Http trigger を使っても Web API は作れます。HTTP で受けてメッセージキューにメッセージ投げて、その裏でごにょごにょやるようなアプリとかの場合は、Web API 部分も後述する Azure Functions で作ってもいいと思います。

サーバーレスプラットフォーム上での開発

C# でサーバーレスプラットフォーム上での開発も出来ます。Azure Functions は当然ネイティブで対応しています。C# 以外の言語は、Azure Functions のランタイムとは独立したプロセスとして起動していて、gRPC でプロセス間通信しているのですが、C# のほうはランタイムに組み込まれてるので、C# が書けるなら C# で書いたら何か安心感があります。AWS Lambda も、C# に早くから対応していて .NET Core が発表されて暫くして「AWS Lambda が C# をサポート」のニュースとともにサポートされたことにびっくりしたのを覚えています。

プラットフォーム/フレームワーク コメント ドキュメント
Azure Functions Azure のサーバーレスサービスの代名詞的な Azure Functions で動かしたいならこれ。 Azure Functions の概要
AWS Lambda AWS のサーバーレスの代名詞的な AWS Lambda で動かしたいならこれ。 C# による Lambda 関数のビルド

Web フロントエンド

最近は C# でも Web フロントエンドいけるんですねぇ。JavaScript 系フレームワークの方が実績もあるし、やってる人も多いので、それはそれで正義なのですが C# が好きなチームとかでは、ここら辺のも候補に入るかもしれない。

プラットフォーム/フレームワーク コメント ドキュメント
Blazor WebAssembly 正式リリースされてないけど、WebAssembly 対応のが正式版としてリリースされたら、恐らく最強。
WebAssembly 系の奴ら全般にあることだろうけど、初回起動時の遅さが懸念事項。
ASP.NET Core Blazor の概要
Uno Platform これは UWP アプリのコードをビルドしてクロスプラットフォームで動くようにするコンセプトのプラットフォームです。WebAssembly にも対応しています。MS 製品ではありませんが、MS ともコミュニケーションをとってやってるようです。GitHub のページのコントリビューターに Microsoft アイコンがあるので。
2020年3月時点では、安定した盤石なプラットフォームだと思って触らないのが吉です。面白いプラットフォームではありますが、日進月歩でバグフィックス、新機能実装が行われています。
What is the Uno Platform?

モバイルアプリケーション開発

Android や iOS といったモバイル OS 用のアプリ開発です。何も考えずに C# でやりたいなら Xamarin を選ぶといいでしょう。次点で Web フロントエンドでも紹介した Uno Platform です。ワンチャン Unity。

プラットフォーム/フレームワーク コメント ドキュメント
Xamarin ネイティブ Android/iOS の API を、ほぼそのまま全て C# でラップしてあるので、いざとなったら Android/iOS のネイティブアプリ開発向け情報を参考に C# に翻訳して開発もできるし、Xamarin のドキュメントにも結構色々書いてある。
各プラットフォームで出来ることは、多分ほぼ全て出来る上に基本的に全て C# で書けます。Java, Kotlin, Swift, Objective-C を書かないといけないシーンは、他のクロスプラットフォーム系の製品に比べて極端に少ないと思います。それこそ、既存の自社製 or OSS のネイティブ向けライブラリーを Xamarin から使いたい時とかに見るくらいだと思います。
最近は、Uno Platform や Mobile Blazor Bindings の Android/iOS 対応は Xamarin 上で動くようになっているので、安心感があります。
Xamarin とは
Xamarin.Forms Xamarin ネイティブとまとめて紹介しようか悩んだけどわけました。Xamarin ネイティブの上に各プラットフォーム共通の UI コントロールや API 群を提供しています。うまいことやればワンソースで Android/iOS/UWP に対応できます。うまいことやればね。現実は Xamarin.Forms の API や .NET Standard の API では出来ないことをしたいこともあるので、その時は Xamarin ネイティブの方に頼って各 OS ネイティブの API を叩くコードをプラットフォーム毎に書かないといけません。その場合でも C# で書けるのは個人的には神。XAML と呼ばれる Windows アプリ開発者にはなじみ深い XML ベースの言語で画面を作れるのも〇。でも、最近は Flutter などの影響なのかは知らないけど C# で UI を組めるようにする CSharpForMarkup というのも出てきているので、そっちはそっちで楽しみ。 Xamarin.Forms とは
Uno Platform UWP アプリのソースを Andorid/iOS/WebAssembly/macOS 向けアプリとしてビルド出来るようにしようという、とても野心的なプラットフォームです。OSS で開発されています。まだこなれてないけど、そのぶん進化が早くて追いかけるのが楽しいです。 What is the Uno Platform?
Mobile Blazor Bindings これは、完全に名前しか知らないけど Blazor で開発したものがモバイルでも動くんだって。しかも基盤は Xamarin らしい。まだ実験段階なので、こういうのがあるんだな~という位には知っておけばいいと思います。もちろん興味がある人は触ってみよう! Announcing Experimental Mobile Blazor Bindings
Unity ゲーム開発用途が主ですが、AR 系アプリとか、ワンチャン画面アプリとかもいけるかも。個人的にはモバイル用の普通の画面を作るならUIWidgetsを使うと Flutter と同じノリで画面が作れるのでお勧め。 Unity

Windows アプリケーション開発

デスクトップ アプリですね。Windows 7 のサポートも終わり、Windows 8.1 を使っている人が少なめとうことで、運がいいと最近は Windows 10 のみをターゲットとして開発できるようになってきました。UWP or WPF |超えられない壁| WinForms の順番でお勧めかなぁ。

プラットフォーム/フレームワーク コメント ドキュメント
UWP Windows 10 が出たときに出てきたプラットフォーム。昔は、これに非ずんば Windows 10 アプリに非ず。とも受け取れるくらいごり押しをしてきたけど、最近は落ち着いて他のプラットフォームでもデスクトップアプリ開発してもらって大丈夫みたいなメッセージを感じる。
一番セキュアな環境で動かせるので、ユーザーとしてはメリットが大きいのですが、開発者目線では、昔は出来たあれが出来ないとか、別の方法を使わないといけない、使う機能を事前に示的に宣言しないといけない、配布するには証明書が必要(オレオレでもいいけど、その場合はユーザーにオレオレ証明書を入れてもらわないといけない)という点で不自由を感じる。
でも、一番今のところ今風の UI を作りやすいプラットフォーム。私は好き。ユーザーの意図に反する動きが作りにくいように出来てるので、例えばユーザーがウィンドウを最小化しようとしたら止めることは出来ないし、ユーザーが閉じようとしたら基本的には閉じられる。個人的にはあるべき姿だと思うけど、業務アプリでユーザーが間違ったことしようとしても力ずくで止めるようなものを実装するのがシンドイ。
一部新機能の API は、UWP からしか呼べないものもある。
ユニバーサル Windows プラットフォーム (UWP) アプリとは
WPF on .NET Core .NET Core 3.0 以降で WPF がサポートされた。実行速度も早い、自由度も高い、配布形式も柔軟に選べる(従来通りの方法もいけるし、MSIX にパッケージングして UWP っぽい感じに配布も出来る)。最近は Windows 10 向け API も NuGet パッケージ追加するだけでサポートされているものは呼べるようになったり、UI コントロールも XAML Islands などを使って UWP の UI コントロールのホストも可能。個人的には一番バランスが取れてるところにいるのではないかと思う 概要 (WPF)(.NET Core 向けの WPF リファレンスとかは無い)
Migrating WPF apps to .NET Core が .NET Framework の WPF から .NET Core の WPF へのマイグレーションのドキュメント
WPF on .NET Framework 基盤が .NET Framework であることを除くと WPF on .NET Core と同じ。.NET 6 くらいの時代になると、段々と使えない新機能が出てきだすのではないかと個人的には思ってる。 概要 (WPF)
Windows Forms on .NET Core
&
Windows Forms on .NET Framework
めんどくさいので纏めた。古き良き世代の .NET での Windows アプリ開発のフレームワーク。正直、高 DPI とか Per monitor DPI とか、今風のレイアウトのリストとか出そうとすると労力が高くなるので辛い。Windows Forms で提供されているコントロールとサードパーティー製コントロール内で収まるように設計して作れば普通に使えるとは思うけど、新規では Windows Forms 以外を選ぼう。 Windows フォーム

macOS 向けアプリケーション開発

Xamarin.Mac でいけるらしい。実際に freee さんで実績があるけど、自分は macOS がメインじゃないので触ったことがない。でも、C# で作れちゃうの凄い。

プラットフォーム/フレームワーク コメント ドキュメント
Xamarin.Mac よく知らない Xamarin.Mac の概要

機械学習系

ML.NET で行ける。これも触り程度しか使ったことがないのでコメントできるほど知らないけど、こういうのがあるよっていうのは知っておいて、いざというときに覗いてみよう。もし機械学習をやることになったら ML.NET で勉強すると Python の学習と機械学習系の勉強の二重苦にならずに、C# は知ってるので機械学習系の勉強にフォーカス出来る。

もし、Python 系のでやらないといけなくなったら、ML.NET で軽く雰囲気掴んでから Python 系のほうを触ってみるというラーニングパスもありかもしれない。

プラットフォーム/フレームワーク コメント ドキュメント
ML.NET よく知らない ML.NET のドキュメント

ゲーム開発 & MR(VR/AR) 開発

Unity 一強の世界ですなぁ。(開発言語を C# に絞らなければ、他もありますけど)

プラットフォーム/フレームワーク コメント ドキュメント
Unity 昔 DirectX の本をみたときは、本で数ページに渡るコードを書いて実行した結果がティーポッドが出るだけ、みたいな世界だったんですが、今ではポトペタと、Behaviour を作って GameObject にアタッチして動作をカスタムできるという、とっても簡単に作れるような世界になってるので最初に触った時は感動しました。
でも、UI を作ろうとすると、個人的には辛い…、あと Unity と利用ライブラリー間のバージョン合わせチャレンジが辛い点が解消される未来が来たら、より幸福度が上がりそうだと思ってます。
Unity

IoT 開発

Azure IoT Edge で C# を使った開発が出来ます。(瀬尾さんに教えてもらいました!)

プラットフォーム/フレームワーク コメント ドキュメント
Azure IoT Edge Azure IoT Edge のモジュール開発に C# が使えます。Docker イメージにして展開する形になります。私は、やったことないのですが、Docker 強いですね。
さらに、パブリックプレビューだけど Azure Functions が動くのも凄い
Azure IoT Edge とは

その他

何に入れていいか迷ったので、その他をここに。

プラットフォーム/フレームワーク コメント ドキュメント
gRPC サーバー/クライアント .NET Core 3.0 から gRPC がサポートされています。マイクロサービスのサービス間を gRPC で繋いでとかいうのもよく聞く話なので、そこにも C# を仲間に入れてやってください。.NET Framework でも前からライブラリー使えば出来たけど、ASP.NET Core に含まれたのが大きいです。.NET Framework の WCF の移行先としても案内されています。 チュートリアル: ASP.NET Core で gRPC のクライアントとサーバーを作成する
マイクロサービス 旬ですよね。Azure アーキテクチャーセンターにドキュメントも上がってます。なんとなく、この記事に書くような粒度の話じゃないと思ったのですが、その他カテゴリーを後で追加したので、備忘録的に書いておきます。 .NET マイクロサービス: コンテナー化された .NET アプリケーションのアーキテクチャ
Azure App Service の開発 Microsoft Azure の App Service の製品チームの帝国兵さんから App Service も、ほとんど C# で作ってるとコメント貰いました。

C# が向かないこと

この記事を受けて kennakamu さんが以下の記事を書いてくれました。個人的に同意です。

個人的に C# が向かないと思うこと

因みに、この記事にポジショントークというはてぶコメントが付きましたが Android アプリの開発がしたいんです!っていう人に Xamarin にいきなり勧めたり、機械学習の勉強したいんです!っていう無垢な人に ML.NET を勧めようという意図で書いてる記事ではないということを書いておきます。

例えば Android ネイティブアプリ開発したいなら Kotlin、iOS アプリ開発したいなら Swift、そうじゃなくて Xamarin を選ぶなら C# のノウハウがあるとか、C# で開発している資産の再利用だとか、マルチプラットフォーム対応したいとかっていう理由が無いなら、ただ辛いだけな気がするので…。C# Love なら Xamarin 一択。

まとめ

C# は、相変わらず色々なことが出来て言語的にも他の最新言語と比較して遜色ない状態をキープし続けてくれてる素敵な言語だなぁと思いました。

システムを作る上で、システム内の○○はXX言語、△△はYY言語のような複数言語が乱立するよりも 1 つの言語で統一できるといいなぁと思ってる人には C# は結構いい選択なのではと思ってます。
マイクロサービスの文脈では、各々のチームが一番適してると思った言語で開発すればいいという流れもありますがケースバイケース?

C# に興味を持った人は、以下のちょまどさんのツイートから行ける Microsoft Learn のラーニングパスもみてみてください。

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

ADX2 for Unityを他のアセットと一緒に使う(DOTween, UniTask, DoozyUI)

よく利用されるUnityアセットとADX2の連携

ゲーム向け統合サウンドミドルウェア「ADX2」をプロジェクト内で使う際、ほかのUnityアセット(ライブラリ)との連携をしたい場合があります。

MusicEngineなど、ライブラリ自体がADX2との連携機能を提供しているパターンもありますが、サウンド機能に直接のかかわりがないアセット類は特に連携機能が用意されていません。

そこで、Unityでよく使われるアセットをピックアップし、連携拡張クラスの作り方を考えてみます。今回は「DOTween」「UniTask」「Doozy UI」の連携について紹介します。

DOTweenでADX2のAISAC値を滑らかに変化させる

DOTweenはUnity向けのTweenライブラリです。Unity Asset Storeで販売されています。

DOTween.png

https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676?locale=ja-JP

DOTweenを使えば、さまざまな値の設定メソッドをTween化できます。ADX2と一緒に使うとしたときに一番恩恵があるのが、「SetAisac」メソッドのTween対応です。

ADXはサウンドデータの中に様々な制御情報を埋め込むことができます。AISAC(アイザック)は、0から1までのパラメータ範囲の中で音の変化を設定できる機能です。
たとえば、「スポーツスタジアムの歓声」というループ効果音が強中弱の3種類あったとき、3つを同時に流しつつそれぞれのボリュームを調整して、普通の盛り上がり~逆転ゴールの超盛り上がり、のような変化を作る処理を考えます。ADX2のAISAC機能を使って、状況に応じてボリュームなどの各パラメーターがどのように変化するか、グラフで設計できます。

Aisacの例.png

上の図はADX2に同梱のツールAtom CraftのAISAC設定画面です。黄色い線がボリュームの変化を表しています。Unityからは「0から1まで」の1つのパラメータを渡してあげるだけで、3つのループ音声それぞれのボリュームを同時に変更できます。

便利なAISAC機能ですが、ADX2側にはAISACの値をTweenで変化させる機能がありません。キューの再生中にAISAC値を変更する場合は、Tween処理を入れてなだらかに数値を動かすほうがよいです。これは、DOTweenの導入と、次の拡張メソッドを使うことで実現できます。

CriAtomDotweenExtension.cs
    public static class CriAtomDotweenExtension
    {
        public static TweenerCore<float, float, FloatOptions>  DOSetAisacControl(this CriAtomSource target, CriAtomExPlayback playback, string aisacName, float startValue,float endValue, float duration)
        {
            return DOSetAisacControl(target.player, playback, aisacName, startValue, endValue, duration);
        }

        public static TweenerCore<float, float, FloatOptions>  DOSetAisacControl(this CriAtomExPlayer target, CriAtomExPlayback playback, string aisacName, float startValue,float endValue, float duration)
        {
            TweenerCore<float, float, FloatOptions> t = DOTween.To(()=> startValue, x=>
            {
                target.SetAisacControl(aisacName, x);
                target.Update(playback);
            }, endValue, duration);
            t.SetTarget(target);

            return t;
        }
   }

このスクリプトは、CriAtomSourceクラスとCriAtomExPlayerクラスに拡張メソッド「DOSetAisacControl」を追加しています。
内部ではDOTween.Toを使い、AISACの値を更新するSetAisacControlメソッドと、その更新を反映するUpdateを呼んでいます。

呼び出し側は次の通りです。

呼び出し側.cs
    public CriAtomSource loopSeAtomSource;

    private void Start()
    {
        var atomPlayback = loopSeAtomSource.Play();
        loopSeAtomSource.DOSetAisacControl(atomPlayback ,"YellHeatUp",0f, 0.5f, 2f);
    }

この例ではAisac名「YellHeatUp」のパラメータを、0から0.5まで2秒かけて変化させています。
急に音の聞こえ方が変わってしまうと気になります。ADX2とDOTweenを併用することで、滑らかに変化せることができます。
ソースコード全体はgistにアップロードしています。

DOTweenでADX2の制御をするための拡張

https://gist.github.com/TakaakiIchijo/ae8ed1f778cf4e3f7eeff5c396fcb0f8

DOTweenを使ったADX2サウンド再生のフェードイン・アウト

DOTweenはUnity標準サウンドのAudioSourceに「DoFade」というフェードイン・アウトのメソッドを追加します。上記ソースコードではCriAtomSourceメソッドにもDOFaderを用意してあります。同様に、ピッチもDOTween経由で使えるようにしてあります。

ADX2は元からフェードの機構(AttachFader)を持っているほか、データ側にフェードイン・アウトの設定ができるので基本そちらの機能を使います。

ADX2でフェードインフェードアウト
https://w.atwiki.jp/soundtasukeai/pages/37.html

ただFaderの利用ができない場面がまれにあるため(ADX2側の特殊機能を使っている場合など)や、スクリプト側からシンプルに操作したい場合はDOFadeも使えます。

UniTaskでキューシートの読み込みを非同期待ちする

UniTaskは、C# 5.0から導入された「async/await」をUnityで使いやすくするライブラリです。Unityオブジェクトとの連携や、Unityが提供する処理の多くを非同期待ちできるようになります。

Cysharp/UniTask
https://github.com/Cysharp/UniTask

これをADX2でも使ってみることを考えます。ADX2の利用において非同期で待ちたい処理といったらば、「キューシートのロード」です。StreamingAssetsディレクトリ(または端末内の任意のディレクトリ)からADX2の圧縮ファイルであるACBファイルの読み込みを非同期待ちしたいと思います。

CriAtomCueSheetクラスにGetAwaiterを追加することでawait可能にします。

ADX2Extensions.cs
using UniRx.Async;

public static UniTask<CriAtomCueSheet>.Awaiter GetAwaiter(this CriAtomCueSheet cueSheet)
{
    UniTask<CriAtomCueSheet> task = new UniTask<CriAtomCueSheet>(async ()=> 
    {       
        await UniTask.WaitWhile(() => cueSheet.IsLoading);
        return cueSheet;
    });

    return new UniTask<CriAtomCueSheet>.Awaiter(task);
}

上記のスクリプトをどこかにおいておけば、CriAtom.AddCueSheetAsyncメソッドがそのままawaitできるようになります。しかも戻り値CriCueSheetなので、すぐCriAtomExPlayerに渡して再生できます。便利。

呼び出し側.cs
CriAtomCueSheet cueSheet = await CriAtom.AddCueSheetAsync("cueSheetName","cuesheet.acb","cuesheet.awb");

criAtomExPlayer.SetCue(cueSheet.acb, cueName);
criAtomExPlayer.Start();

Doozy UI のノードエディターからADX2のデータを鳴らす

Doozy UIは、UI開発の統合ライブラリです。UI遷移をノードベースで設計できるほか、豊富なUIアニメーションやプレイヤーカスタマイズ機能などを持ちます。

doozyuistore.png

https://assetstore.unity.com/packages/tools/gui/doozyui-complete-ui-management-system-138361?locale=ja-JP

Doozy UIはUIの遷移を専用のノードエディタNodyで組んでいきます。サウンド再生にはSoundyというこのアセットにビルドインされた管理の仕組みがあるのですが、ADX2のデータを鳴らすためには機能の拡張が必要です。
こちらはサンプルソースだけでは収まらなかったので、githubにパッケージを用意しました。

ADX2forDoozyUI

https://github.com/TakaakiIchijo/ADX2forDoozyUI

この.unitypackageを Doozy UIとADX2が導入されているプロジェクトにインポートすると、連携機能が利用できます。
ちなみにDoozy UIの動作には前述のDOTweenが必須です(無料版で動きます)。こちらのパッケージでは、さきほどCriAtomDotweenExtension.csも一緒に導入されます。

Doozy Nodeとの統合

Doozy UIが元から用意しているノードと同じようなADX2のノードを用意しました。
キューシートの読み込みとその待ち処理、キューの再生・停止ができます。

ADX2Nodes.png

Nody上の右クリックメニューから「ADX2」を選べばノードが生成できます。Actionを「Load Cue Sheet」に変更して、キューシート名、acbファイルのパス(ストリーミング再生の場合はawbのパスも)を指定します。

LoadCueSheetAction.png

キューシートのロードはCRIAtomのインスタンスで起動時にロードしてもかまいませんが、キュー再生のノードを処理する前に読んでおく必要があります。
キュー再生ノードにはキューシート名とキュー名を設定します。

PlayAction.png

これで、Nodyの中で任意のタイミングでADX2のキューを再生できます。

処理の実態としては、シーン内に「DoozyAtomSourceManager」というCriAtomSourceのマネージャークラスを生成して再生を実行しています。

Doozy UI のボタン入力からADX2のデータを鳴らす

Doozy UIに同梱のサウンド管理ライブラリSoundyは、AudioSouceをベースに作られています。UIの遷移やアニメーションをトリガーとしてサウンドを再生できます。
ADX2を使って音を鳴らしたい場合は、Doozy UIのUI Viewクラスに定義されているUnity Eventのトリガーをフックして音を再生します。

DoozyActionAtomPlayer.cs
using Doozy.Engine.UI;
using UnityEngine;

namespace Doozy.Engine.ADX2
{
    [RequireComponent(typeof(UIView))]
    public class DoozyActionAtomPlayer : MonoBehaviour
    {
        public string cueSheetName;

        public void PlayCue(string cueName)
        {
            DoozyAtomSourceManager.Instance.Play(cueSheetName, cueName);
        }
    }
}

OnClickイベントにキューの再生を紐づける設定は次の画像の通りです。

DoozyActionAtomPlayer.png

このスクリプトを使うためには、前述のDoozyAtomSourceManager.csのインスタンスがシーン内に存在している必要があります。

ADX2と他Unityアセットの連携

今回「DOTween」「UniTask」「Doozy UI」を選んだのは、私が開発中のゲームで導入しているためです。(プロジェクトからADX2との連携部分のみを取り出しました)
他にもUIやアニメーションに関するたくさんのアセットがUnityにはありますが、それらに関してADX2を連携させたい場合は、ここで紹介する実装が参考になると思います。

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

Unity入門:今週悩んだこと。イベントクラスの改良。

はじめに

今週勉強したことをまとめています。
初めまして、HITOMI(男)です。現在Unityを勉強しています。今週勉強したこと、悩んだことをここに書き残します。
今週はほとんど読み込みクラスの拡張で終わりました。今の段階で一通り遊べる形にはなっています。4月の完成を予定していましたが、ちょっと難しいかなと思います。多分4月15日ぐらいの完成になると考えています。

今週(3月15〜3/20日)詰まったところ。
・イベント管理クラス使用の変更
・カメラについて

イベント管理クラスの改良、変更について

イベント等を外部ファイルから読み込み、変更できるようにプログラムを書いています。その読み込むフォーマットの記述方法をどうするか、試行錯誤と改良を繰り返していました。

例えば、
ゲーム中ではチェックポイントなどを配置して、そこに到達した場合、何かしらのイベントを追加しようと考えています。
そして今まではこのように書いていました。

<EventData name="event04",type="event",next="event05">
<TriggerObject type="box",event="speech",position="5.4,1.0,0.0",size="1.0,1.0,1.0"></TriggerBox>
        <SpeechBubble position="12.4,7.0,0.0",fix="bottom">
            <Speech speech="今なんかイベントが起きています。",interval="0.1",auto="true",autoInterval="1.0"></Speak>
            <Speech speech="どんなインベントなんでしょうか?",interval="0.1",auto="true",autoInterval="1.0"></Speak>
            <Speech speech="楽しみですね。",interval="0.1",waitForInput="Enter"></Speak>
        </SpeechBubble>
    </EventData>

このイベントを読み込むと、トリガーにプレイヤーがヒットすると、吹き出しが現れてspeechに書かれている内容が自動で流れます。そして、最後の文章が読み終わった後にエンターキーを押すと終了する。という処理が実行されます。

しかし、吹き出しが出る前に「カメラの視点をプレイヤー向ける」や「イベント中に動作できないようにする。」という処理が必要だと思っているので、今週はこのイベントクラスにカメラや操作制御なども設定できるようにプログラムを拡張していました。

結果として下記のように書くことに変更しました。

<EventData name="event01",type="start",next="event02">
        <TriggerObject type="box",event="speech",position="5.4,1.0,0.0",size="1.0,1.0,1.0"></TriggerBox>
        <Camera type="smoothLookAt",position="0.7,7.0,-10.0",target="enemy",time="0.03"></Camera>
</EventData>

<EventData name="event02",type="event",next="event03">
        <Restriction target="player" type="move"></Restriction>
        <SpeechBubble position="12.4,7.0,0.0",fix="bottom">
            <Speech speech="今なんかイベントが起きています。",interval="0.1",auto="true",autoInterval="1.0"></Speak>
            <Speech speech="どんなインベントなんでしょうか?",interval="0.1",auto="true",autoInterval="1.0"></Speak>
            <Speech speech="楽しみですね。",interval="0.1",waitForInput="Enter"></Speak>
        </SpeechBubble>
</EventData>

<EventData name="event03",type="event",next="event04">
       <Camera type="follow",position="0.0,0.0,-6.0",target="Player"></Camera>
</EventData>

<EventData name="event04",type="event",next="end">
        <Restriction target="player" type="all"></Restriction>
        <Fade type="out",time="2.0"><Fade>
</EventData>

上記のコードだと以下のような動作となります。
①トリガーにプレイヤーがヒットすると、カメラを「enemy」の方に注視点を移動させます。
②吹き出しで文章を表示させ、プレイヤーを追随する位置にカメラを更新。
③フェードアウトする。
④記述されているイベントを全て終了するとシーン切り替え。

この書き方だとプレイヤーのHPが0になったらゲームオーバーに移行するという処理も、

<EventData name="gameover01",type="start",next="gameover02">
        <Trigger type="check",event="gameover",target="player"></TriggerBox>
        //ここにイベントの内容
</EventData>

このように追加すればいいだけで、簡単にカスタムができるのではないかなと考えています。
という感じで、データ読み込みの改良をしていました。

このデータはいろんなゲームを作る時に再利用できそうなので、できるだけ納得できるまで作り込みたいと思っています。
ひとまずこれで完成として、後で時間が余った時に「選択肢でイベント先を変更できる」ことも追加しようと思っています。

カメラについて

今までLookAt()を利用して、注視点を操作していましたが、これを滑らかに移動させたいな。と考えたので滑らかに注視点を移動するカメラのプログラムを試行錯誤していました。

下記のURLで方法が紹介されていたのでこれを改良しました。
https://www.sejuku.net/blog/69635

改良したコードは下記です。

Quaternion m_Rotation
void SetCamera(GameObject _targetObj)
{
   Vector3 relativePos = targetObj.transform.position - this.transform.position;
   m_Rotation = Quaternion.LookRotation (relativePos);
}

Update(){
   m_Time += m_Speed;
   if(m_Time > 1.0f)
   {
       m_Time=1.0f;
       m_State = //次のステート
   }
   transform.rotation  = Quaternion.Slerp (this.transform.rotation, rotation, m_Time);
}

こうすることによってタイマーが終わればシーンが切り替わるようになっていますが、ちょっと動きがおかしい・・・
何か見落としている気がする。

さいごに

以上今週悩んだことです。記述していること以外はほとんどバグチェックでした。プログラムの部分はほぼ完成で、あとは細かい部分の改良で済みそうです。
来週はプログラムが完成して、モデリングに入っているのではないかと思います。

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

unityでノンコーディングでオブジェクトを動かしたいとき

Animationコンポーネントを使います。
Animationコンポーネントがハジメマシテの方は、初回は大変ですが、一度試して、いくつかオブジェクトを動かせば慣れるのではないかと!!


動かしたいオブジェクトに、インスペクターウィンドウでAnimationを検索してAddする。
a01.png

Animationウィンドウを開く。
a02.png

ウィンドウ右側に[create]というボタンがあるので、それを押下する。
a02a.png

保存ダイアログが出てくるので、適当な名前を入力する。
a03.png

アセット内にこういうのができる
a04.png

録画ボタンを押す。
a05.png

最初のpositionを入力する。(これが0秒目。初期位置)
a06.png

フレームを移動する。
移動後のpositionを入力する。(下図スクショだと1秒目。移動後の位置)
a07.png

録画ボタンを再度押下して、停止する。
0秒目に戻す。
アセット内のanimationコンポーネントを、インスペクターウィンドウのAnimationにドラッグアンドドロップする。
a08.png

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

Unityのチュ-トリアル ML-Agents:ペンギンを動かしてみた

はじめに

Unityのチュ-トリアルML-Agents:ペンギンを動かしてみました。
https://learn.unity.com/project/ml-agents-penguins
最初、アセットのバージョンをチュ-トリアルに合わせて以下を使用したところ、特に引っかかるところもなく動かすことができました。
ML-Agents 0.13.1
Barracuda 0.4.0
これを、2020/3/21時点で最新版の以下に更新したところ、いろいろ修正が必要で引っかかるところもあったので書いておきます。
ML-Agents 0.15.0
Barracuda 0.6.1
修正は、変更ガイドに書いてある内容に沿って実施しています。

開発環境

Unity 2019.3.5f1

修正したところ

・UnityのPackage ManagerからBarracuda 0.6.1に更新します。
・ML-Agentsの0.15.0をダウンロードし、チュ-トリアルのAssets/ML-Agentsフォルダを差し替えます。
インストールガイドに従い、com.unity.ml-agentsUnityパッケージをインストールします。
・Areaクラスの階層移動に伴いPenguinArea.csに1行追加します。

PenguinArea.cs
using MLAgentsExamples;

・Academyは継承できなくなったので、PenguinAcademy.csを以下のように書き換えます。

PenguinAcademy.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MLAgents;


public class PenguinAcademy : MonoBehaviour
{
    /// <summary>
    /// Gets/sets the current fish speed
    /// </summary>
    public float FishSpeed { get; private set; }

    /// <summary>
    /// Gets/sets the current acceptable feed radius
    /// </summary>
    public float FeedRadius { get; private set; }

    /// <summary>
    /// Called when the academy first gets initialized
    /// </summary>
    void OnAwake()
    {
        FishSpeed = 0f;
        FeedRadius = 0f;

        // Set up code to be called every time the fish_speed parameter changes 
        // during curriculum learning
        Academy.Instance.FloatProperties.RegisterCallback("fish_speed", f =>
        {
            FishSpeed = f;
        });

        // Set up code to be called every time the feed_radius parameter changes 
        // during curriculum learning
        Academy.Instance.FloatProperties.RegisterCallback("feed_radius", f =>
        {
            FeedRadius = f;
        });
    }
}

・Agentクラスのメソッド変更等に伴いPenguinAgent.csを以下のように書き換えます。

PenguinAgent.cs
using UnityEngine;
using MLAgents;
using MLAgents.Sensors;

public class PenguinAgent : Agent
{
    [Tooltip("How fast the agent moves forward")]
    public float moveSpeed = 5f;

    [Tooltip("How fast the agent turns")]
    public float turnSpeed = 180f;

    [Tooltip("Prefab of the heart that appears when the baby is fed")]
    public GameObject heartPrefab;

    [Tooltip("Prefab of the regurgitated fish that appears when the baby is fed")]
    public GameObject regurgitatedFishPrefab;

    private PenguinArea penguinArea;
    private PenguinAcademy penguinAcademy;
    new private Rigidbody rigidbody;
    private GameObject baby;

    private bool isFull; // If true, penguin has a full stomach
    private float feedRadius = 0f;

    /// <summary>
    /// Initial setup, called when the agent is enabled
    /// </summary>
    public override void Initialize()
    {
        base.Initialize();
        penguinArea = GetComponentInParent<PenguinArea>();
        penguinAcademy = FindObjectOfType<PenguinAcademy>();
        baby = penguinArea.penguinBaby;
        rigidbody = GetComponent<Rigidbody>();
    }

    /// <summary>
    /// Perform actions based on a vector of numbers
    /// </summary>
    /// <param name="vectorAction">The list of actions to take</param>
    public override void OnActionReceived(float[] vectorAction)
    {
        // Convert the first action to forward movement
        float forwardAmount = vectorAction[0];

        // Convert the second action to turning left or right
        float turnAmount = 0f;
        if (vectorAction[1] == 1f)
        {
            turnAmount = -1f;
        }
        else if (vectorAction[1] == 2f)
        {
            turnAmount = 1f;
        }

        // Apply movement
        rigidbody.MovePosition(transform.position + transform.forward * forwardAmount * moveSpeed * Time.fixedDeltaTime);
        transform.Rotate(transform.up * turnAmount * turnSpeed * Time.fixedDeltaTime);

        // Apply a tiny negative reward every step to encourage action
        AddReward(-1f / maxStep);
    }

    /// <summary>
    /// Read inputs from the keyboard and convert them to a list of actions.
    /// This is called only when the player wants to control the agent and has set
    /// Behavior Type to "Heuristic Only" in the Behavior Parameters inspector.
    /// </summary>
    /// <returns>A vectorAction array of floats that will be passed into <see cref="AgentAction(float[])"/></returns>
    public override float[] Heuristic()
    {
        float forwardAction = 0f;
        float turnAction = 0f;
        if (Input.GetKey(KeyCode.W))
        {
            // move forward
            forwardAction = 1f;
        }
        if (Input.GetKey(KeyCode.A))
        {
            // turn left
            turnAction = 1f;
        }
        else if (Input.GetKey(KeyCode.D))
        {
            // turn right
            turnAction = 2f;
        }

        // Put the actions into an array and return
        return new float[] { forwardAction, turnAction };
    }

    /// <summary>
    /// Reset the agent and area
    /// </summary>
    public override void OnEpisodeBegin()
    {
        isFull = false;
        penguinArea.ResetArea();
        feedRadius = penguinAcademy.FeedRadius;
    }

    /// <summary>
    /// Collect all non-Raycast observations
    /// </summary>
    public override void CollectObservations(VectorSensor sensor)
    {
        // Whether the penguin has eaten a fish (1 float = 1 value)
        sensor.AddObservation(isFull);

        // Distance to the baby (1 float = 1 value)
        sensor.AddObservation(Vector3.Distance(baby.transform.position, transform.position));

        // Direction to baby (1 Vector3 = 3 values)
        sensor.AddObservation((baby.transform.position - transform.position).normalized);

        // Direction penguin is facing (1 Vector3 = 3 values)
        sensor.AddObservation(transform.forward);

        // 1 + 1 + 3 + 3 = 8 total values
    }

    private void FixedUpdate()
    {
        // Test if the agent is close enough to to feed the baby
        if (Vector3.Distance(transform.position, baby.transform.position) < feedRadius)
        {
            // Close enough, try to feed the baby
            RegurgitateFish();
        }
    }

    /// <summary>
    /// When the agent collides with something, take action
    /// </summary>
    /// <param name="collision">The collision info</param>
    private void OnCollisionEnter(Collision collision)
    {
        if (collision.transform.CompareTag("fish"))
        {
            // Try to eat the fish
            EatFish(collision.gameObject);
        }
        else if (collision.transform.CompareTag("baby"))
        {
            // Try to feed the baby
            RegurgitateFish();
        }
    }

    /// <summary>
    /// Check if agent is full, if not, eat the fish and get a reward
    /// </summary>
    /// <param name="fishObject">The fish to eat</param>
    private void EatFish(GameObject fishObject)
    {
        if (isFull) return; // Can't eat another fish while full
        isFull = true;

        penguinArea.RemoveSpecificFish(fishObject);

        AddReward(1f);
    }

    /// <summary>
    /// Check if agent is full, if yes, feed the baby
    /// </summary>
    private void RegurgitateFish()
    {
        if (!isFull) return; // Nothing to regurgitate
        isFull = false;

        // Spawn regurgitated fish
        GameObject regurgitatedFish = Instantiate<GameObject>(regurgitatedFishPrefab);
        regurgitatedFish.transform.parent = transform.parent;
        regurgitatedFish.transform.position = baby.transform.position;
        Destroy(regurgitatedFish, 4f);

        // Spawn heart
        GameObject heart = Instantiate<GameObject>(heartPrefab);
        heart.transform.parent = transform.parent;
        heart.transform.position = baby.transform.position + Vector3.up;
        Destroy(heart, 4f);

        AddReward(1f);

        if (penguinArea.FishRemaining <= 0)
        {
            EndEpisode();
        }
    }
}

・Penginプレファブを開いて、DecisionRequesteコンポーネントを付加します。付加しないとビルドは通るけれど初期配置から全く動かない状態になってしまいます。自分はこれになかなか気が付かなかったので結構はまりました。

以降は、python側の修正です。
・ML-Agents 0.13.1で作成した環境が残っている場合は、名前の衝突を避けるために環境名にバージョン付加するなどします。

conda create -n ml-agents_0.15.0 python=3.7
conda activate ml-agents_0.15.0

・カリキュラム構成ファイルはYAML形式になったので、PenguinLearning.jsonをPenguinLearning.yamlに書き換えます。

PenguinLearning:
  measure: reward
  thresholds: [ -0.1, 0.7, 1.7, 1.7, 1.7, 2.7, 2.7 ]
  min_lesson_length: 80
  signal_smoothing: true
  parameters:
    fish_speed: [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.5 ]
    feed_radius: [ 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.5, 0.2 ]

・trainer_config.yamlはsummary_freqを5000から50000に増やしておいたほうがレポートが見やすいです。
・トレーニング開始は以下コマンドです。

mlagents-learn config/trainer_config.yaml --curriculum config/curricula/penguin/PenguinLearning.yaml --run-id penguin_01 --train

・トレーニング開始した後、本来は"Start training by pressing the Play button in the Unity Editor."
が表示されたあと、Unity側でPlayを実行するのですが、なぜかログ出力されていなくなっているので、
"Instructions for updating:
non-resource variables are not supported in the long term"が表示されたあたりでPlayを実行します。

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

Unity ECSで自前Worldを複数動かす

【Unity2019.1】Entity Comonent Systemで自前Worldを作成する方法【ECS】
https://qiita.com/pCYSl5EDgo/items/fd08d54422b232b92e28

現在のUnity ECSで複数の自前Worldを動かせるようになっていたので、こちらの記事を参考にしつつ方法をまとめます。

環境

  • Unity 2019.3.5f1
  • DOTS Editor 0.4.0-preview
    • Entities 0.8.0-preview.8

デフォルトのWorld生成を無効にする

Project Setting>Player>Scripting Define Symbols
UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAPを追記してください。
セミコロンでシンボルを複数指定できます。

自前Worldを動かす

var world = new World("MyWorld");

var systemTypes = new List<Type>();
systemTypes.Add(typeof(HogeSystem));
systemTypes.Add(typeof(FugaSystem));
systemTypes.Add(typeof(AwesomeSystem));
// ルートの3つのComponentSystemGroupにSystemを登録してくれる便利なメソッド
DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, systemTypes);

ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world);

UpdatePlayerLoopを呼ぶことで自前Worldを動かすことができます。
AddSystemsToRootLevelSystemGroupsはデフォルトWorldにSystemを登録する際に使用されていますが自前Worldでも使えます。

自前Worldを追加する

var worldA = new World("MyWorldA");
var worldB = new World("MyWorldB");
var worldC = new World("MyWorldC");

// Systemなど登録

ScriptBehaviourUpdateOrder.SetPlayerLoop(PlayerLoop.GetDefaultPlayerLoop());
foreach(var world in World.All)
    ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world, ScriptBehaviourUpdateOrder.CurrentPlayerLoop);

UpdatePlayerLoopには、既存のPlayerLoopにWorldの更新処理を挿入するためのパラメータがあるので、CurrentPlayerLoopを指定してあげましょう。
https://docs.unity3d.com/Packages/com.unity.entities@0.8/api/Unity.Entities.ScriptBehaviourUpdateOrder.html#Unity_Entities_ScriptBehaviourUpdateOrder_UpdatePlayerLoop_Unity_Entities_World_System_Nullable_PlayerLoopSystem__

まとめ

APIが大幅に変わってたりしてわからん。日本語の情報がまだまだ少なくてわからん。なんもわからん。
(デフォルトでCurrentPlayerLoop.subSystemListがnullなのはバグですか…?)

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

Unityでシーンを読み込むたびに静的フィールドを初期化するたったひとつの冴えたやりかた

この記事で述べていること

  • Unityでシーンを読み込むたびに、静的フィールドの初期化メソッドを一度だけ呼び出したい。
  • そんなときは静的コンストラクタSceneManager.sceneLoaded に初期化メソッドを追加すればよい。
  • この方法に辿り着く前に検討した方法と、採用しなかった理由はこういうものです。
  • もっといいやり方あったら情報ください。

使用バージョン

Unity 2018.4.5f1 Personal

説明

Unityでは静的フィールドの中身はシーンが変わっても保持されます。
しかし、時には「シーンを読み込むたびに初期化したい!」「でも静的フィールドにはしておきたい!」と思うときもあると思います。少なくとも私はありました。
そんなときは、以下の方法を使えばシーンを読み込むたびに静的フィールドの初期化メソッドを一度だけ呼び出すことができます。

1.まず、以下のようにクラスの静的コンストラクタを用意します。
これは、静的フィールドを初めて参照した際、参照する直前に呼び出されるコンストラクタです。これを使えば静的フィールドが参照される前に一度だけ、処理を行うことができます。

静的コンストラクタ
public class Example{
    private static int staticField_;

    static Example(){

    }
}

2.次に初期化用のメソッドを用意し、そのメソッドを静的コンストラクタ内でSceneManager.sceneLoadedに追加します。
これはUnityAPI側で用意されているイベントで、シーンが読み込まれた際にここに追加されたメソッドが呼び出されます。
第一引数には読み込まれたシーン、第二引数にはシーンの読み込みモードが入ります。この引数は追加するイベントに合わせて付ける必要があるため、省くことはできないことに注意してください。
また、SceneManagerクラスはUnityEngine.SceneManagement名前空間にあるため、インポートする必要があることにも注意してください。

シーンロード時のイベント
using UnityEngine.SceneManagement;

public class Example{
    private static int staticField_;

    static Example(){
      SceneManager.sceneLoaded += init;
    }

    private static void init(Scene loadingScene, LoadSceneMode loadSceneMode){
        staticField_ = 0;
    }
}

実行タイミング

「シーンを読み込んだ際に呼び出される」と書きましたが、その実行タイミングは
1.Awake
2.sceneLoadedに追加したメソッド(ここで実行される)
3.Start
となっているらしいです。つまりこの方法でも、自他のスクリプトのAwakeで静的フィールドを参照するようなコードを書くと、未初期化の値を参照してしまう恐れがあるので、その点は注意してください。

こうしなかった理由

これを考えつく前に検討した方法と、そうしなかった理由です。

1.わざわざ初期化メソッドを追加しなくても、静的コンストラクタで初期化すればいいんじゃないの?

静的コンストラクタは、初めて静的フィールドを参照した際に一度だけ実行されます。
「ならそれで初期化すればよくない?」と思われるかもしれませんが、Unityでは静的フィールドの値が保持される関係で、最初の一回のみ「初めて参照した」と判断されるらしいです。つまるところこれで初期化してしまうと、最初の一回以外はいくらシーンを切り替えようがゲームを終了しない限りはもう二度と初期化されません。

2. Awakeで初期化すればいいんじゃない?

このスクリプトが1つしか張り付いてない・確実にシーンにそのスクリプトが張り付いているオブジェクトがアクティブで存在している保証があるならまあそれでもいいんですが…いくつか以下のようなデメリットが出る場合があるので採用しませんでした。

デメリット1:Awakeは各インスタンス毎に実行されるため、必然的に複数張り付いていればそれだけ初期化メソッドが実行されます。 初期化が重ければ、それだけシーンのロードが重くなってしまいます。

デメリット2:「なら静的フィールドのフラグを作って一度だけ実行されるように管理すれば…」と思われるかもしれませんが、それだけの理由でフィールドが1つ増えてしまうことになりますし、そのフラグをStartなりでfalseにしてやる手間が増える(シーンの最初で再度初期化を走らせるため)ので、好ましくありません。

デメリット3:Awakeは「オブジェクトがアクティブのとき、インスタンスがロードされたタイミング」に実行されます。つまり、何か理由があって「オブジェクトを最初は非アクティブにしておき、後でアクティブにする」ということをしていた場合、初期化メソッドが走るタイミングはシーンの最初ではなく、そのタイミングになってしまいます。(オブジェクトがアクティブならスクリプトが非アクティブでもAwakeは実行されます)

デメリット4:そのスクリプトが張り付いているオブジェクトがなければ、必然Awakeも実行されません。つまりそのスクリプトが張り付いているオブジェクトが存在しないシーンでその静的フィールドを参照した場合、未初期化の値が取得されてしまいます。

最後に

これ本当に一番いい方法なんでしょうか?もし「違うよ」って人がいましたら連絡ください。

参考サイト

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

[Unity] UniversalRenderPipelineのMSAAをC#から変更する

はじめに

下記の続きです。
https://qiita.com/jnhtt/items/4814d629bcd98dc28a38

UniversalRenderPipelineのMSAAを実行時にC#から変更したいのでコードを書きました。
URPを使っている人向けの情報なので、URPのセットアップは説明は省略します。
ソースコードはここです。

↓結果画面
msaa.gif

目的

実行時にC#からMSAAを変更して見え方を確認したできるようにします。

Editor操作で変更

インスペクタ上でMSAAを変更する場合、使っているUniversalRenderPipelineAssetのインスペクタを開きます。
Quality > AntiAliasing(MSAA)のボタンを押すとMSAAのサンプル数が変更されます。
スクリーンショット 2020-03-22 0.58.17.png

C#から変更

GraphicsSettings.currentRenderPipelineでUniversalRenderPipelineAssetを取得すれば、あとはmsaaSampleCountにアクセスできます。
Default値は1です。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.UI;

public class MSAA : MonoBehaviour
{
    [SerializeField]
    private Slider renderScaleSlider;
    [SerializeField]
    private Button msaaButton;
    [SerializeField]
    private TMPro.TMP_Text text;

    private static string[] MSAA_TEXT = new string[] { "NONE", "x2", "x4", "x8" };
    private UniversalRenderPipelineAsset pipelineAsset;
    private int msaaValue;

    private void Start()
    {
        pipelineAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset;
        if (pipelineAsset != null)
        {
            msaaButton.onClick.AddListener(OnPushMSAA);
            msaaValue = (int)Mathf.Log(pipelineAsset.msaaSampleCount, 2);
            renderScaleSlider.onValueChanged.AddListener(OnValueChanged);
            SetMSAAText();
            renderScaleSlider.value = pipelineAsset.renderScale;
        }
    }

    private void OnValueChanged(float value)
    {
        if (pipelineAsset != null)
        {
            pipelineAsset.renderScale = value;
        }
    }

    private void OnPushMSAA()
    {
        if (pipelineAsset != null)
        {
            msaaValue = (msaaValue + 1) % 4;
            pipelineAsset.msaaSampleCount = 1 << msaaValue;
            SetMSAAText();
        }
    }

    private void SetMSAAText()
    {
        text.text = "MSAA " + MSAA_TEXT[msaaValue % 4];
    }
}

さいごに

URPのMSAAを変更できるようになりました。
UniversalRenderPipelineAssetの他のパラメーターも変更できます。

URPの使い方をもっと知りたい方はこちらをどうぞ。
技術書典8 新刊
Unity実践UniversalRenderPipeline
技術書典応援祭
BOOTH

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

[Unity] UniversalRenderPipelineのMSAA設定をC#から変更する

はじめに

下記の続きです。
https://qiita.com/jnhtt/items/4814d629bcd98dc28a38

UniversalRenderPipelineのMSAAを実行時にC#から変更したいのでコードを書きました。
URPを使っている人向けの情報なので、URPのセットアップは説明は省略します。
ソースコードはここです。

↓結果画面
msaa.gif

目的

実行時にC#からMSAAを変更して見え方を確認したできるようにします。

Editor操作で変更

インスペクタ上でMSAAを変更する場合、使っているUniversalRenderPipelineAssetのインスペクタを開きます。
Quality > AntiAliasing(MSAA)のボタンを押すとMSAAのサンプル数が変更されます。
スクリーンショット 2020-03-22 0.58.17.png

C#から変更

GraphicsSettings.currentRenderPipelineでUniversalRenderPipelineAssetを取得すれば、あとはmsaaSampleCountにアクセスできます。
Default値は1です。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.UI;

public class MSAA : MonoBehaviour
{
    [SerializeField]
    private Slider renderScaleSlider;
    [SerializeField]
    private Button msaaButton;
    [SerializeField]
    private TMPro.TMP_Text text;

    private static string[] MSAA_TEXT = new string[] { "NONE", "x2", "x4", "x8" };
    private UniversalRenderPipelineAsset pipelineAsset;
    private int msaaValue;

    private void Start()
    {
        pipelineAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset;
        if (pipelineAsset != null)
        {
            msaaButton.onClick.AddListener(OnPushMSAA);
            msaaValue = (int)Mathf.Log(pipelineAsset.msaaSampleCount, 2);
            renderScaleSlider.onValueChanged.AddListener(OnValueChanged);
            SetMSAAText();
            renderScaleSlider.value = pipelineAsset.renderScale;
        }
    }

    private void OnValueChanged(float value)
    {
        if (pipelineAsset != null)
        {
            pipelineAsset.renderScale = value;
        }
    }

    private void OnPushMSAA()
    {
        if (pipelineAsset != null)
        {
            msaaValue = (msaaValue + 1) % 4;
            pipelineAsset.msaaSampleCount = 1 << msaaValue;
            SetMSAAText();
        }
    }

    private void SetMSAAText()
    {
        text.text = "MSAA " + MSAA_TEXT[msaaValue % 4];
    }
}

さいごに

URPのMSAAを変更できるようになりました。
UniversalRenderPipelineAssetの他のパラメーターも変更できます。

URPの使い方をもっと知りたい方はこちらをどうぞ。
技術書典8 新刊
Unity実践UniversalRenderPipeline
技術書典応援祭
BOOTH

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