20201215のUnityに関する記事は9件です。

float値を文字列にした後、再び数値にするとどれだけずれるか

数値→文字列→数値したときのずれ

C#で、float値を、ToString()して、float.Parseすると若干のずれがあります。
どの程度ずれるのかについて書きました。
当然、これはずれる場合とずれない場合があるのですが、今回はUnityのRandam値で検証しました。

最初に結論

結論から言うと、利用する値の範囲によって変わります。

-9999~9999ぐらいの値の範囲であれば、±0.0009765625ぐらいのずれでした。
-999~999ぐらいの値の範囲だと、6.103516E-05ぐらいですね。
これらの値は、アバウトなので参考程度に。

※コメント欄にToString("G9")すれば、改善するという指摘がありました。
ありがとうございます。
恐ろしいことに、全くずれなくなりました。ずれが0です。
ということで、下記の記事は普通に引数無しのToString()した場合の記事です。
ToString("G9")にしたら、こんな余計なことする必要ないのかもしれません。

どうやって調べたか

かなり適当なんですが、コードです。
float値の2つの値の範囲って、無限にあるのでランダムでやってます。
毎フレーム1000回試してみて、最大誤差が現れた時にDebug.Logしています。

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

public class StringToNumber : MonoBehaviour
{
    float _minDif = 0;
    float _maxDif = 0;

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

    }

    // Update is called once per frame
    void Update()
    {
        for (int i = 0; i < 1000; ++i)
        {
            float f = Random.Range(-9999f, 9999f);
            string s = f.ToString();
            float v = float.Parse(s);
            float d = v - f;
            if (d < 0f)
            {
                if (d < _minDif)
                {
                    _minDif = d;
                    Debug.Log("MIN DIF:" + _minDif);
                }
            }
            else
            {
                if (d > _maxDif)
                {
                    _maxDif = d;
                    Debug.Log("MAX DIF:" + _maxDif);
                }
            }
        }
    }
}

誤差があると何が困るのか

実際に起きた問題として、オブジェクトの位置を保存する場合です。
このオブジェクトは、rangeMinからrangeMaxの間にしか置けないものでした。
範囲外だと問題がでている状態になって、灰色で表示されるようになります。
範囲ぎりぎりの地点は、位置をスナップできるようにしてあったとします。

仮に、rangeMaxの地点で、位置を文字列として保存しました。
で、ロードし直すと誤算によって問題がでている状態になっていることがあるわけです。

対策1

誤差を許容しましょう。
-9999~9999ぐらいの値の範囲であれば、±0.0009765625ぐらいのずれでした。
なので、0.001ぐらいのずれは許容するようなコードにする。
(私だったら怖いので、0.01ぐらいにしますが)

rangeCheck.cs
bool rangeCheck(float value)
{
    float eps = 0.001;
    if ((rangeMin-eps<value) && (value<rangeMax+eps)) return true;
    return false;
}

対策2

バイナリで保存する。

結論

すごくどうでも良い話だったかもしれませんが、文字列化して数値に戻すと少しだけ数値が変わるので、少しだけ許容幅を設けよう。
その際の許容範囲ってどのぐらいにしたら良いのかって話でした。

文字列で数値を保存するのって便利だけど、問題がでることもあるよねって話です。
(というか実際に問題が出たので、記事に残しておこうと思うのでした)

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

Unityの タイムライン(Timeline) が良いの自分なりにまとめてみる

この記事はUnity Advent Calendar 2020 15日目の記事です。昨日は @tan-y さんの「Rust で Unity Native Plugin を実装する」の記事でした。

※ タイムラインの表記ゆれは修正してないです

はじめに

自分は、趣味でUnityを使って友人とゲーム制作をしています。

基本的に、友人主導のプロジェクトでいつもは基本的な大枠を友人がつくって自分が細かいところに修正を入れたり、改善する感じの役割でした。しかし、次を作るものは大枠を自分が作ることになったので、いままで課題となっていた、「シナリオゲームの演出部分のプログラムが読みにくいのでなんとかしたいなぁ」を改善すべく、Timeline機能をつかってみることに。

自分が根っからのエンジニアで、友人がデザイナーよりなので、演出を Unityの機能だけ でなんとかしたい気持ちありました。タイムラインはそれを叶えられそうなものでしたので、自分でまとめておきたいと思った次第です。

実際にこんな感じのものをつくりました。 ↓↓
test1.gif
この記事では、UnityプロジェクトへのTimelineの導入から自作のPlayableBehaviourを作るところまで をまとめて、以下の項目については追々更新して行こうと思います。

  • Unity上での挙動とか(仕組み?)
  • Playableについて
  • Playable APIとかPlayable Graphとか
  • PrefabとかGameObjectとの参照のこと
  • PlayableBehaviourのブレンド
  • Signal&Maker
  • Timelineの流用

タイムラインとは

まず、タイムラインとは一体何者なのかを明らかにしましょう。

Unity Document タイムライン

Unity のタイムラインを利用して、映画的コンテンツ、ゲームプレイシーケンス、オーディオシーケンス、複雑なパーティクルエフェクトを作成できます。
Unity のタイムラインで作成するカットシーン、映画的コンテンツ、ゲームプレイシーケンスはそれぞれ、タイムラインアセットとタイムラインインスタンスから構成されています。 Timeline エディターウインドウ では、タイムラインアセットとタイムラインインスタンスの作成や修正を同時に行えます。

Timeline で手軽に演出を構築しよう

自分の思い通りに、リッチでシネマティックなコンテンツ、カットシーンやゲームプレイシーケンスを作成しましょう
アーティストやデザイナーに最適
Timeline を使うと、コンテキスト内で作業することができ、コンテンツが思った通りの形になるまでイテレーションすることができます。 これは強力でユーザーフレンドリーなツールです。以下のことができます。
- リッチなコンテンツを作成: ゲームオブジェクト、アニメーション、サウンド、パーティクルなどのシーン要素を用いてリッチなコンテンツを作成する
- 作成、演出や調整などを行う: アニメーション、カメラ、オーディオ、および、その他のあらゆるゲームオブジェクト
- コンテンツをより速く作成: 使い慣れたマルチトラックインターフェイスを用い、オブジェクトを「ドラッグアンドドロップ」して、アニメーションを制御して記録、リッチなシーンを素早く作成する

要は Unityのシーン空間をスタジオとして考えて、タイムラインウインドウでカメラやアニメーションやGameObjectを制御できる ものでしょう。
(動画マンならAdobe After Effectsで映像を作るような気持ち)

タイムラインのここがいい

タイムラインというだけあって、すべてが時系列でわかりやすいです。

Before: 今までの演出は、コルーチンを使ってyieldでタイミングを図ってAnimation再生したりでした。これではすべての動きをプログラムからは想像することは難しいですね。
image.png
After: シナリオテキストやアニメーション、GameObjectなどの挙動のタイミングが一目でわかります。並行で行われる動きもこれらならわかりやすい。
image.png
Unity Editorからタイミングも長さも変えられる のは最高!!
test3.gif

Unity EditorでPlayをしなくても確認できるのも良い!これなら任意の時間から演出を確認したり、好きな再生速度や1フレームずつ確認ができるのでゲーム制作がはかどります!
test4.gif

タイムラインのここは良くない

これで、演出をすべてプログラムで組み上げるみたいなことから開放されると思いきや、Unityのタイムラインでは プログラムを作成しない と以下の基本トラックしか使えない。( Default Playables を追加すると表現の幅が増えます )

トラックの種類
Activation Track 対象オブジェクトのアクティブ化/非アクティブ化
Animation Track 対象オブジェクトのモーション制御/移動/回転など
Audio Track オーディオ制御
Control Track オブジェクトの生成
Playable Track スクリプト処理 従来のMonoBehaviourをタイムラインに組み込める?

つまり、凝った表現にはどうしてもコーディングは避けられないですね。(Adobe Premiere Proのエフェクトコントロールみたいにできるといいのかな、 これでも十分な機能だとは思う)

また、Unity Editorでは問題なく表示されるTimelineですが、実態は、YAMLで記述された .playable ファイルです。
image.png
複数人で開発するとなると、コンフリクトしそうですね。ここはしっかりプロジェクトで開発ルールや仕組みを整えなければなりません。コンフリクトを避ける一例として、 Timelineから他のTimelineを呼び出して再生する という技で担当ごとに作成したタイムラインをまとめて1つのタイムラインにするものもあります。

あとは、ちょっと前に Unity EditorでPlayをしなくても確認できるのも良い! とコメントを書いたが、場合によっては、Unity EditorでPlayをしなくても確認できるようにするには、すこし工夫が必要だったりします。

Unity の Timeline をカスタマイズするための詳細#Play中、エディタ上、両方で同じ処理を実行する
https://qiita.com/hadashiA/items/566f0d0222cb9a4e209b#Play中、エディタ上、両方で同じ処理を実行する

UnityのTimelineTrackを実装する#初期値の記憶と復帰
https://qiita.com/ousttrue/items/ac2f0b3847e76a36b1f0#初期値の記憶と復帰

Timeline を作成

2019.2.1f1 で検証しています。

さて、これからプロジェクトに新しくタイムラインを作成します。
タイムラインは次の2つの方法で作ることができます。

  1. GameObjectを選択して、Timelineタブを開いてCreate image.png
  2. 右クリック/AssetsからCreate > Timeline を選択(この場合はPlayable Directorが自動で生成されません。 image.png

作成したタイムラインをアタッチしたGameObjectを選択するとTimelineタブに作成したタイムラインが表示されます。(簡単ですね!)これで、様々なトラックやクリップを並べてリッチなコンテンツを目指していけそうです
image.png

タイムラインでゲームのどの演出/ロジックを組むかはプロジェクトしだいなので、この一つのタイムラインで、一つのシーンのすべてを作り込むのもヨシ!一つの小さなカットシーンや演出を作るのもヨシ! けれど、何でもかんでもここに投げ込むのは良くないので、ルールは必要そうですね。

Unity EditorのTimelineタブから編集できる部分はいろんな方がまとめているので、この記事では割愛して、参考にした記事をまとめておきます。

Timelineの基本機能とCinema Directorとの違い#4-代表的な5つの機能と使い方
https://www.crossroad-tech.com/entry/Unity2017_2_0_timeline#4-代表的な5つの機能と使い方

タイムラインの基礎を理解する【Unity】
https://styly.cc/ja/tips/timeline_unity_kaki/

【Unity】Unityのタイムラインの基本的な使い方を総まとめ!最短でTimelineを使いこなす
https://light11.hatenadiary.com/entry/2018/09/18/235615

【Unity】Timeline上でビデオクリップを再生したりフェードで切り替えたりする方法
http://tsubakit1.hateblo.jp/entry/2018/08/30/200000

タイムラインで使えるカスタムしたクリップを実装する

クリップ(図中赤枠)とは、タイムライン上のトラックの上に並んだ、 その時間での挙動を表すようなもの です。基本的には、アニメーションや動画、音楽を再生したり、プロパティを変更したりすることができます。実体は PlayableAsset で、タイムラインの再生に必要な情報を保持してくれます。タイムラインの編集ではこのクリップを並べて行きます!(動画編集で言う、素材を並べる)
image.png
これからいろんな演出を考えると、スクリプトで自由にクリップを作成できるに越したことないはず。

※ お手軽にControl Trackを使ってMonoBehaviourを作る感じでするのも良さそうだけど...
【Unity】ITimeControlで、Timelineから"コンポーネント"を操作する
http://tsubakit1.hateblo.jp/entry/2017/09/21/234138

テンプレート

きっと、これから
スクリプティングでタイムラインをより創造的に活用しよう のdemoプロジェクトから拝借して、最小限の何もしないクリップを作りました。

TemplatePlayableAsset.cs
using UnityEngine;
using UnityEngine.Playables;

[System.Serializable]
public class TemplatePlayableAsset : PlayableAsset
{
    [SerializeField]
    private ExposedReference<GameObject> templateGameObject;

    public TemplatePlayableBehaviour template = new TemplatePlayableBehaviour();

    // Factory method that generates a playable based on this asset
    public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
    {
        var playable = ScriptPlayable<TemplatePlayableBehaviour>.Create(graph, template);

        // Get PlayableBehaviour
        var behaviour = playable.GetBehaviour();

        // Resolve Reference
        behaviour.templateGameObject = templateGameObject.Resolve(graph.GetResolver());

        return playable;
    }
}
TemplatePlayableBehaviour.cs
using UnityEngine;
using UnityEngine.Playables;

[System.Serializable]
// A behaviour that is attached to a playable
public class TemplatePlayableBehaviour : PlayableBehaviour
{
    private PlayableDirector director;
    public GameObject templateGameObject { get; set; }

    [SerializeField]
    public bool boolValue = false;

    public override void OnPlayableCreate(Playable playable)
    {
        director = (playable.GetGraph().GetResolver() as PlayableDirector);
    }

    // Called when the owning graph starts playing
    public override void OnGraphStart(Playable playable)
    {

    }

    // Called when the owning graph stops playing
    public override void OnGraphStop(Playable playable)
    {

    }

    // Called when the state of the playable is set to Play
    public override void OnBehaviourPlay(Playable playable, FrameData info)
    {

    }

    // Called when the state of the playable is set to Paused
    public override void OnBehaviourPause(Playable playable, FrameData info)
    {

    }

    // Called each frame while the state is set to Play
    public override void PrepareFrame(Playable playable, FrameData info)
    {

    }
}

テンプレートなのでファイル名にTemplateを冠していますが、このPlayable系のクラスとファイルは名前の付け方があるので、確認しておきましょう。
UnityのTimelineTrackを実装する#名前の付け方
https://qiita.com/ousttrue/items/ac2f0b3847e76a36b1f0#名前の付け方

TrackAssetを作成していないので、このテンプレートクリップはPlayable Trackへ置きます。
image.png
Inspectorで確認するとこんな感じで、クリップにGameObjectを渡したり、値を設定できることがわかります。
image.png

あとはPlayableBehaviourを継承したクラスに、やりたいことを記述していきます。PlayableBehaviourには次のメソッドが用意されているので必要に応じた場所に書いていきましょう。

メソッド タイミング
OnGraphStart タイムライン開始時
OnGraphStop タイムライン停止時
OnBehaviourPlay PlayableTrack再生時
OnBehaviourPause PlayableTrack停止時
PrepareFrame PlayableTrack再生時毎フレーム

※ PlayableBehaviourとMonoBehaviourを比べた図みたいなのを書くとわかりやすいので今度作ります。

さて、このテンプレートをつかってバリエーションを増やしてみます

Textを1文字ずつ表示するやつを作成

TextMeshProを使うと、 maxVisibleCharacters を設定するだけで1文字ずつ表示を増やしていけます。これをタイムラインと組み合わせると、PrepareFrameでクリップの割合を計算して、入れてあげれば良い。(以下のコードは初期化を省略しています

NarrationBehaviour.cs
public class NarrationBehaviour : PlayableBehaviour
{
    public TMP_Text mTextUI { get; set; }

    public override void PrepareFrame(Playable playable, FrameData info)
    {
        if (mTextUI != null)
        {
            var progress = (float)(playable.GetTime() / playable.GetDuration());
            var current = Mathf.Lerp(0,mParsedText.Length, progress);
            var count = Mathf.CeilToInt(current);

            mTextUI.maxVisibleCharacters = count;
        }
    }
}

スクラブすると1文字ずづつ表示されます。戻るとちゃんと1文字づつ減っていくのがまたいいですね。
test5.gif

ユーザーの入力を待つ

Textを1文字ずつ表示するやつができると、次は文章の一番最後で入力を待って(タイムラインを止めて)、ユーザーからの入力があったら次の文章へすすむ(タイムラインを動かす)やつがほしくなります。実装は至って簡単で、 OnBehaviourPause で動いているタイムラインのスピードを0にしてあげます。これで、タイムラインが一時的に停止します。ユーザーの入力に応じて、スピードを1にもどせれば、またタイムラインは動き始めます。

NarrationBehaviour.cs
public class NarrationBehaviour : PlayableBehaviour
{
    private PlayableDirector director;

    private bool pauseScheduled = true;
    public override void OnPlayableCreate(Playable playable)
    {
        director = (playable.GetGraph().GetResolver() as PlayableDirector);
    }

    public override void OnBehaviourPause(Playable playable, FrameData info)
    {
        if (pauseScheduled)
        {
            director.playableGraph.GetRootPlayable(0).SetSpeed(0d);
            pauseScheduled = false;
        }
    }
}

入力がスペースなのでわかりにくいですが、クリップの終わりでタイムラインが一時的に停止しています。これで、ユーザーの入力をタイムラインで待てるようになりました。
test6.gif

最後に

まだ、触りはじめで作例みたいのが豊富じゃなくて、タイムラインの良さを伝えきれないのが残念ですが、これから伝えられるようにしていけたらいいのかなと思います。あとは、タイムラインを触りながら、今日の時点でまとめられなかったことを追記していけたらと思います。(課題がかぶってギリギリで書いてしまった

最後に、友人と作ったゲームを宣伝しておきます。
ハタイケダのゲーム作品 Google PlayStore
ハタイケダのゲーム作品 Apple AppStore

+参考にした記事

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

Google Maps SDKをWebGLで動かす

本記事は、株式会社ピー・アール・オーアドベントカレンダーの15日目です。

単なるTipsです。

表題のとおりです。実際にやってみたらハマったので備忘録を兼ねて書いておきます。

基本的なやりかた

Unity側でWebGLを有効にします。

File > Build Setting...から、
image.png

WebGLを選択し、Development Buildにチェック入れます。

WebGL有効にしてなかった人はここでインストール求められるはず。
image.png

Build And Runで実行

出力先フォルダ指定したら、ビルドが始まります(結構待たされる)
image.png

ブラウザ起動

ビルドが終わると勝手にブラウザが立ち上がってビルドしたものが見れます。
image.png

うまくいかないとき

何のエラーも出ないけど描画がされない

Cache OptionのMax Disk Bytesをゼロにするといいらしいです。
image.png

CORS関連のエラーが出る

これが厄介かもしれません。
image.png

とりあえず動かしたいという向きには、こちらを参考にブラウザの方でチェック無効にしちゃうと言う手があります。

最後に

Google Maps SDKはWebGLは公式にはサポートされてないらしいので、その前提で使いましょう。

ダウンロード.gif

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

Unityを用いたアニメーション撮影表現

アニメーション撮影とは?

 単語だけで言えばカメラワークなども含めかなり広義に捉えることができると思います。
 今回扱うのは編集の過程で用いられるエフェクトなどで、ゲーム的な見方で言えば、ポストプロセスエフェクトにあたるものです。

経緯

 先日までアニメーションRPG『カラクリショウジョの涙と終』を制作していました。
 その過程で利用した手法をこちらの動画(02:01:27頃)でプレゼンさせていただいたのですが、その内容をもう少し掘り下げてまとめたものになっております。
(動画ではワークフローも同時に説明させていただきましたが、そちらはまた別の記事にしたいと思います。)
 (2020/12/16追記 こちらにまとめました)

目次

1.パラ表現
2.カメラ表現
3.パーティクル

1. パラ表現

 パラ表現とは、画面に対しグラデーションをかけるシンプルな表現手法です。3Dで言う所のライティングに近いと思われます。
 今回は基本の直線グラデーション、もしくは円状のグラデーションを板ポリに描画し、それを重ねることで表現しました。

//乗算パラ

Shader "Custom/Para_Multiply"
{
    Properties
    {
        _Color("Tint",Color) = (1,1,1,1)
        [KeywordEnum(RADIUS,LINEAR)] _TYPE("Type",Float) = 0
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Transparent"
            "Queue"="Transparent"
        }
        LOD 100

        Cull Off
        Lighting Off
        Blend Zero SrcColor //乗算ブレンド

        Pass
        {
            CGPROGRAM
            #pragma multi_compile  _TYPE_LINEAR _TYPE_RADIUS
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            fixed4 _Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = (0,0,0,0);

                #ifdef _TYPE_RADIUS //円形グラデーション描画
                    half distance = clamp(length(i.uv - 0.5) * 2,0,1)  ;
                    col = _Color * (1-distance);

                #elif _TYPE_LINEAR //線形グラデーション描画
                    col = _Color * i.uv.x;

                #else

                #endif

                return  col;
            }
            ENDCG
        }
    }
}

 『乗算ブレンド』と書かれている部分をBlend One Oneに書き換えることで加算ブレンドを行うように変更できます。
 今回は乗算用、加算用の二つのMaterialを用意し、スクリプトから二つの描画を切り替えることで発光、影両方でグラデーション表現を行いました。
(発光は恐らくパラ表現とは別に区分されますが、今回は実装方法の都合上同じ括りにまとめさせていただきます。)

使用例

<線形グラデーション + 乗算合成>
線形グラデーション+乗算

パラ表現 なし

スクリーンショット 2020-12-14 18.01.15.png

パラ表現 あり

スクリーンショット 2020-12-14 18.01.02.png|


<円形グラデーション + 加算合成>
円形グラデーション+加算

パラ表現なし

スクリーンショット 2020-12-14 17.57.38.png

パラ表現あり

スクリーンショット 2020-12-14 17.57.25.png


 手法だけで言えば、効果を適用したい形状のテクスチャやマップ画像を用意し、パーティクル用のShaderで板ポリに描画する方法が簡単だと思いますが、「全て一からShaderでやったらこうなった」という感じで見ていただけると幸いです。

2. カメラ表現

 今回は手書きアニメーションを用いたため、カットシーン表現において重要とされることが多い2つのエフェクトが使用できず擬似的に再現する必要がありました。

1. 被写界深度(2Dにはもちろん適用されない。奥行きを用いて配置してもいいが、程度とカメラワークによっては表現に破綻が起きる)
2. モーションブラー(キャラクターは定義された大きさの画像の中で動くため、オブジェクト自体が動かない)

被写界深度

 いわゆるピント表現です。ピントが合わないものはぼやけて表示されるため、まずは特定のオブジェクトのみぼかしをかけるといった形を取れるようにしました。
(ぼかし処理自体はこちらの記事を参考にさせていただいたため省きます。)
 今回はマップ画像を用いた発光処理を行っているため、元の絵とマップ画像両方にブラーをかけその結果を描画を行うShaderに流し込むといった形で実装しています。

スクリーンショット 2020-12-14 20.34.35.png

 また、特定のキャラクターと背景を一緒に、などまとめてぼかしをかけたい状況も発生しました。そのためGrabPassを用いてレンダリングされた結果に対してブラーをかける効果も実装しています。
ぼかし処理自体は参考にさせていただいた記事を見ていただければと思いますが、ブラーをかける形状を指定できるよう独自に処理を加えています。

//---中略---

            half4 _BlurCenterOffset; //中心位置
            half _MaskRadius; //円状にくり抜く際の半径

            half4 frag(v2f i) : SV_Target
            {

                //中略

                #ifdef Mapping_Default //全体にかかるブラー処理(省略)



                #elif Mapping_Radial //円形に繰り抜くようにかかるブラー処理

                //円の中心 + 半径からどれだけ離れているかを求める
                half distance = clamp(length(i.uv -_BlurCenterOffset) -MaskRadius,0,1) ;

                [loop]
                for (float x = -blur; x <= blur; x += 1)
                {

                /*
                  サンプリングする座標のオフセットに先ほどのdistanceをかける
                  =>より広い範囲をサンプリングするほどブラーはかかり方が大きくなる
                  =>かけるdistanceが大きいほどオフセットは大きくなる
                  =>円から距離があるほどブラーのかかり方も大きくなる
                /*

                }

                #elif Mapping_Linear //線形に強度が変わるブラー処理

                //基準とした点からどれだけ右にいるかを求める
                half distance = clamp(i.uv.x - _BlurCenterOffset.x,0,1) ;

                [loop]
                for (float x = -blur; x <= blur; x += 1)
                {

                /*
                  サンプリングする座標のオフセットに同じくdistanceをかける
                  =>以下の仕組みは円形ブラーと同じ
                /*

                }

                #endif

//---以下略--
標準ブラー

奥行きの表現などに用いることができます。
スクリーンショット 2020-12-14 21.40.46.png

円形、線形ブラー

視線誘導を行うことができます。

スクリーンショット 2020-12-14 21.42.04.png

スクリーンショット 2020-12-14 21.46.00.png

モーションブラー

 高速で動くものをカメラで捉えた際、シャッターが開いている間の動きがそのまま切り取られるためブレが生じる現象です。つまり移動の軌跡が残像として描画できれば良いということになります。
 今回は絵のオブジェクトごとに移動ブラーを用い、その強度と角度を動的に変更することで実装しました。
 ブラー実装自体はこちらの記事を参考にさせていただいております。

スクリーンショット 2020-12-14 23.37.39.png

3. パーティクル

 手軽に美麗なエフェクト表現を行うことができますが、今回手書きアニメーションと組み合わせるに当たって一つ問題がありました。
 手書きアニメーションは意図的に動きのフレームレートを落とすリミテッドアニメーションに分類されますが、これに対しパーティクルは各フレームで動きの更新が行われるフルアニメーションに属しています。
 このように動きの滑らかさの差があるため、どちらか片方の表現が画面に馴染まず浮いてしまう現象が発生しました。
 これを解決すべく、今回パーティクルを動的にリミテッド化する処理を実装しております。

ParticleController.cs
using UnityEngine;

[ExecuteAlways]//エディタでの確認用

public class ParticleController : MonoBehaviour {

    [SerializeField] ParticleSystem particle;

    [SerializeField] float currentTime;
    float beforeTime;

    [SerializeField]AnimationCurve limitedCurve;

    int interval;

    int frameCount;


    private void Update() {

        if (Mathf.Approximately(beforeTime, currentTime)) return;

        if(frameCount >= interval) {

            interval = (int)Mathf.Round(limitedCurve.Evaluate(currentTime);
            frameCount = 0;

            particle.Simulate(currentTime);

        }

        frameCount++;

        beforeTime = currentTime;

    }  


    public void SetCurrentTime(float currentTime) {

        this.currentTime = currentTime;

    }


}

 実際にゲームに使用したパーティクル周りのコードから必要な部分だけを抜粋しました。
 肝となるのはUpdate()内部のこの部分です。

        if(frameCount >= interval) {

            interval = (int)Mathf.Round(limitedCurve.Evaluate(currentTime);
            frameCount = 0;

            particle.Simulate(currentTime);

        }

        frameCount++;

 Simulate()を使い、パーティクル更新処理を全てプログラム内に移譲しています。そして更新ごとの間隔を開けてフレームレートを落としました。
 次に何フレーム間隔を開けるか(アニメ用語ではタメツメと呼ばれる部分のはずです)を指定するパラメータにはAnimationCurveを用いています。時間経過による値の変化がサポートされており、動きの緩急がつけやすいです。
(例:火花エフェクトの始まりは勢いを良くするために大胆にフレームレートを落とし、消え始めたらフレームを増やしてなめらかな動きにする)

比較は記事冒頭に載せたプレゼン動画の(02:06:24)頃で見ることができます。

※注意※

 先ほど載せたコードにはエディタでの確認ができるよう[ExecuteAlways]の記述がありますが、これを行うとUnityのSceneビューに用意されたパーティクルのプレビュー機能が使用できなくなります。
 エディタで再生したい場合はエディタ拡張を行うなど、対応をお願いいたします。

拡張例

(汚いコードですがご容赦を)

ParticleControllerEditor.cs
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(ParticleController))]
public class ParticleControllerEditor : Editor {

    bool isPlaying;

    float startedTime;


    bool isPaused = false;
    float pausedTime = 0;


    public override void OnInspectorGUI() {

        base.OnInspectorGUI();

        if (!isPlaying) {
            if (GUILayout.Button("Play")) {
                if (isPaused) {
                    startedTime = (float)EditorApplication.timeSinceStartup;
                    isPlaying = true;
                    EditorApplication.update += UpdateParticle;
                }
                else {
                    Play();
                }
            }
        }

        if (isPlaying) {
            if (GUILayout.Button("Pause")) {
                isPlaying = false;
                isPaused = true;
                pausedTime = pausedTime + (float)EditorApplication.timeSinceStartup - startedTime;
                EditorApplication.update -= UpdateParticle;
            }
        }

        if (GUILayout.Button("Restart")) {
            EditorApplication.update -= UpdateParticle;
            Play();
        }

        if (GUILayout.Button("Stop")) {
            Stop();

        }


    }


    private void OnDisable() {
        Stop();
    }


    private void Play() {
        pausedTime = 0;
        isPlaying = true;
        isPaused = false;
        startedTime = (float)EditorApplication.timeSinceStartup;
        EditorApplication.update += UpdateParticle;
    }


    private void Stop() {
        pausedTime = 0;
        isPaused = false;
        isPlaying = false;
        (target as ParticleController).SetCurrentTime(0);
        EditorApplication.update -= UpdateParticle;
    }


    private void UpdateParticle() {
        if (!isPlaying) return;

        if (isPaused) {
            (target as ParticleController).SetCurrentTime(pausedTime + (float)EditorApplication.timeSinceStartup - startedTime);
        }
        else {
            (target as ParticleController).SetCurrentTime((float)EditorApplication.timeSinceStartup - startedTime);
        }


    }


}

 パーティクルの再生、一時停止、頭出し、停止をInspectorから行うことができるようになる拡張です。

まとめ

 今回扱った内容は全て『手書きアニメーションをどこまでUnity上で豪華に魅せられるか』という点に重きを置いたものですので、多くの方のためになる内容ではないかもしれませんが、少しでも参考になる部分がありましたら幸いです。

参考サイト



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

Unity - Arduino間でBluetooth Serial通信をするとUnity終了時にSerialを閉じるのに失敗する

はじめに

生じた問題はタイトルのとおりです.
Arduino(私の場合はTeensy4.0)からUnityにシリアルでデータを送る際,有線だとUnity終了時にSerialPort.Close()が機能したのですが,Bluetooth経由だと機能せずにアプリケーション終了後もゾンビスレッドが爆誕してしまった.といった感じです

環境

Windows10
Unity2019.4.3f1

マイコン : Teensy4.0
Bluetoothモジュール : BlueSMiRF Gold

使用スクリプト

Unity-Arduino間のシリアル通信であまりにも有名なスクリプトを用いています.
今回は一昔前のThreadを使用したものではなく,Taskを用いた以下の記事を参考にさせていただいています.

[Unity]非同期処理(Task)の利用例(UDP送受信,SerialPortのRead)

原因

なぜSerialPort.Close()が失敗するかですが,それはSerialPort.Close()を呼ぶタイミングでも裏で走っている以下のReadAsync()タスク内のSerialPort.ReadLine()が終了していないからです.
もっと正確に言うと,SerialPort.Readline()でシリアルからの入力を待ち続けてしまっており,そのときにSerial.Close()を呼び出してしまうとこのTaskがゾンビTaskになってアプリケーション終了後も走り続けてしまう,といった感じでしょうか...(Unityを落とせばこのTaskも消えます.)

SerialHandler.cs
async Task ReadAsync()
    {
        await Task.Run(() => {
            while (isRunning && serialPort != null && serialPort.IsOpen)
            {
                try
                {
                    string message = serialPort.ReadLine();
                    OnDataReceived.Invoke(message);
                }
                catch (System.Exception e)
                {
                    Debug.LogWarning(e.Message);
                }
            }
        });
    }

何も入力がないときはSerialPort.ReadLine()が終了するように,Serial.Open()時に以下のようにタイムアウトを設定してあげましょう.(タイムアウトの間隔はアプリに合わせて変えてあげましょう.)

SerialHandler.cs
    public void Open(string portName)
    {
        if(isRunning) return;

        currentPortName = portName;

        serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);

        // ここでReadのタイムアウトを設定する.
        serialPort.ReadTimeout = 500; //[milliseconds]

        serialPort.Open();
        isRunning = true;
    }

これでSerial.Close()がちゃんと機能し,ゾンビタスクが爆誕することはなくなりました.

なぜ有線だとこのタイムアウトの設定がいらなくてBluetoothだと必要なのかが未だにわかっていませんが,とりあえず良しとします.

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

C# staticを大雑把に理解する

使い方① ---using static ディレクトリ---

これを使うとコードが冗長にならず、読みやすくなる。
例を出すと、

System.Console.Write("");       // 改行なしで出力
System.Console.WriteLine("");   // 改行ありで出力
System.Console.Read();          // 1文字だけ読み込む
System.Console.ReadLine();      // 1行を読み込む

という4つのものがある場合、いつもなら

using System;   // 名前空間の使用

と付けて

Console.Write("");
Console.WriteLine("");
Console.Read();
Console.ReadLine();

こうして終わりだろう。
だが、ここを

using static System.Console;

と入力してあげることで

Write("");
WriteLine("");
Read();
ReadLine();

ここまで短縮することができる。
これは「System.Consoleという部分までは全て共通で使うから、メモリに事前に確保しておいてね」という意味あいになる。
プロパティに使うものとこれは全くの別物らしい。知らなかった……

Unity使用者向け

using UnityEngine;
Debug.Log("");
Mathf.Clamp();
Input.GetAxis();

これが

using static UnityEngine.Debug;
using static UnityEngine.Mathf;
using static UnityEngine.Input;
Log("");
Clamp();
GetAxis();

これになる。
基本的な部分は同じで、『 . 』で区切られている部分まではこの方法で省略することができる。
よく使用するものに関してはこれでいいが、たまにしか使わないものの場合ずっとメモリが確保され続けてしまうので、staticは使用しない方がいい。

使い方② ---通常のstatic---

・staticはひとつしかないものに使う。
・よく呼び出される場合に、事前にメモリを確保してあげる。

説明①

力尽きたのでまた今度……

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

Unity – UnityHubで「ライセンスサーバーへの接続に失敗しました」と表示される。

UnityHub > ライセンス管理 > 新規ライセンスでProを選択+シリアル番号を入力したあと[実行]ボタンを押したら「ライセンスサーバーへの接続に失敗しました」と表示される。

何回やっても、時間をおいても、PC再起動しても起こる。その場合以下の
C:\ProgramData\Unity\Unity_lic.ulf
ファイルを消して再度ライセンス認証をするとうまくいく場合がある。

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

[ViveProEye]アイトラッキングのキャリブレーションをスクリプトから実行する

はじめに

HTC社から販売されているHMD「ViveProEye」は視線をトラッキングしてその情報をゲームなどで使用することができます。
しかし、目の位置や動きには個人差があるため, 事前にキャリブレーション(校正)をおこなう必要があります。

これは基本的にはViveのメニュー上からおこなうのですが、スクリプトでもこれを開始することができるのでその方法を紹介します。

実行環境

  • Unity 2019.4.4f1
  • SteamVRPlugin 2.6.1 (sdk 1.13.10)
  • ViveEyeTrackingSDK(SRanipal) v1.3.1.0

実装方法

以下のスクリプトのコンポーネントを任意のオブジェクトに追加します。
スペースキーを押したらキャリブレーションが始まるようにしています。

EyeCalibrator.cs
using System;
using UnityEngine;
using ViveSR.anipal.Eye;

public class EyeCalibrator : MonoBehaviour
{
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            SRanipal_Eye_API.LaunchEyeCalibration(IntPtr.Zero);
        }
    }
}

HTC社が配布しているEyeTracking対応デバイスのためのSDK「ViveEyeTrackingSDK(SRanipal)」が提供する
SRanipal_Eye_API.LaunchEyeCalibration(IntPtr.Zero)を使用します。

これを使用することでViveのメニュー上から実行するのと同じ画面に切り替わり、キャリブレーションをおこなうことができます。

詳しくはSDKに同封されているリファレンスを確認してみてください
(バージョンによっては仕様が変わっている可能性があります。)

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

uGUIのGridLayoutGroupがオートレイアウト対応してないので対応させるやつ書いた

概要

GridLayoutGroupのCellSizeを自動調整します。

使い方

GridLayoutGroupと同じオブジェクトに付与してください。
もしくは、付与した際に自動でGridLayoutGroupが連鎖的に付与されます。
なお、Constraintを何に設定するかで、調整項目が違うので注意。

  • Flexible
    • CellSizeが完全に自動調整されます。事前入力値は無視します。
  • FixedColumnCount
    • CellSizeのXを自動調整します。Yは事前入力値を継承。
  • FixedRowCount
    • CellSizeのYを自動調整します。Xは事前入力値を継承。
AutoGridLayoutOverrider.cs
using UnityEngine;
using UnityEngine.UI;

namespace CustomUIParts
{
    /// <summary>
    /// UnityEngine.UI.GridLayoutGroup をAutoLayout仕様にする
    /// </summary>
    [RequireComponent(typeof(GridLayoutGroup))]
    public class AutoGridLayoutOverrider : MonoBehaviour
    {
        /// <summary>
        /// オートレイアウト対応するGridLayoutGroup
        /// </summary>
        [SerializeField] GridLayoutGroup grid = default;

        /// <summary>
        /// サイズ調整に使用するRectTransform
        /// </summary>
        RectTransform rect = default;

        #region サイズ変動要素の比較用キャッシュ
        // Grid内オブジェクトの個数
        int beforeChildCount;
        // RectTransformからのデータ
        float beforeWidth, beforeHeight;
        // GridLayoutGroupからのデータ
        Vector2 beforeCellSize;
        Vector2 beforeSpacing;
        GridLayoutGroup.Axis beforeAxis;
        GridLayoutGroup.Constraint beforeConstraint;
        int beforeConstraingCount;
        #endregion


        /// <summary>
        /// InspectorでのReset動作定義
        /// </summary>
        void Reset()
        {
            grid = GetComponent<GridLayoutGroup>();
        }

        /// <summary>
        /// Unityライフサイクル関数
        /// </summary>
        void Awake()
        {
            // サイズ調整の基準となるRectTransformを取得
            rect = GetComponent<RectTransform>();
            while (rect.rect.width == 0 && rect.rect.height == 0)
            {
                // Streach前提でサイズが取得できないときは、親・祖先オブジェクトに遡って取得しにいく
                rect = rect.transform.parent.GetComponent<RectTransform>();
            }
        }

        /// <summary>
        /// Unityライフサイクル関数
        /// </summary>
        void Update()
        {
            ResetLayoutSize();
        }

        /// <summary>
        /// レイアウトサイズ変更
        /// </summary>
        void ResetLayoutSize()
        {
            // 実行途中で配置モードが変化したかどうかチェック
            bool modeChangeFlag = ();

            // レイアウト状態に変更が無ければスキップ
            bool skipFlag =
                beforeWidth == rect.rect.width &&
                beforeHeight == rect.rect.height &&
                beforeChildCount == grid.transform.childCount &&
                beforeSpacing.x == grid.spacing.x &&
                beforeSpacing.y == grid.spacing.y &&
                beforeAxis == grid.startAxis &&
                beforeConstraint == grid.constraint;

            // 幅固定指定の場合は、前回サイズから変更がないか追加チェック
            switch (grid.constraint) {
                case GridLayoutGroup.Constraint.FixedColumnCount:
                    skipFlag &= beforeCellSize.y == grid.cellSize.y;
                    skipFlag &= beforeConstraingCount == grid.constraintCount;
                    break;
                case GridLayoutGroup.Constraint.FixedRowCount:
                    skipFlag &= beforeCellSize.x == grid.cellSize.x;
                    skipFlag &= beforeConstraingCount == grid.constraintCount;
                    break;
            }

            if (skipFlag)
            {
                return;
            }

            // 何かしら状況が変わったのでレイアウト設定を変える

            float x = grid.cellSize.x;
            float y = grid.cellSize.y;

            switch (grid.constraint)
            {
                // x,y:ともに可変(領域全体を埋める)
                case GridLayoutGroup.Constraint.Flexible:
                    int childCount = grid.transform.childCount;
                    int i = 0;
                    while (Mathf.Pow(++i, 2) < childCount) { }

                    int tateNum, yokoNum;
                    if (Mathf.Pow(i, 2) == childCount)
                    {
                        tateNum = yokoNum = i;
                    }
                    else if (grid.startAxis == GridLayoutGroup.Axis.Horizontal)
                    {
                        yokoNum = i;
                        tateNum = childCount / i + (childCount % i != 0 ? 1 : 0);
                    }
                    else
                    {
                        // 縦方向優先に並べる動作は挙動が特殊
                        yokoNum = i;
                        if(childCount <= i * (i-1))
                        {
                            tateNum = childCount / i + (childCount % i != 0 ? 1 : 0);
                        }
                        else
                        {
                            tateNum = i;
                        }
                    }

                    x = (rect.rect.width - grid.spacing.x * (tateNum - 1)) / tateNum;
                    y = (rect.rect.height - grid.spacing.y * (yokoNum - 1)) / yokoNum;
                    break;

                // width のみ可変
                case GridLayoutGroup.Constraint.FixedColumnCount:
                    x = (rect.rect.width - grid.spacing.x * (grid.constraintCount - 1)) / grid.constraintCount;
                    break;

                // height のみ可変
                case GridLayoutGroup.Constraint.FixedRowCount:
                    y = (rect.rect.height - grid.spacing.y * (grid.constraintCount - 1)) / grid.constraintCount;
                    break;
            }
            beforeCellSize = grid.cellSize = new Vector2(x, y);

            // 更新処理スキップ判定用に諸々キャッシュ
            beforeChildCount = grid.transform.childCount;

            beforeWidth = rect.rect.width;
            beforeHeight = rect.rect.height;

            beforeSpacing = grid.spacing;
            beforeAxis = grid.startAxis;
            beforeConstraint = grid.constraint;
            beforeConstraingCount = grid.constraintCount;
        }
    }
}

変更ログ

2020/12/16
最初はUpdateにプラットフォーム依存処理を入れていたが、
別プラットフォームでビルドしたら反映されなかったので修正。

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