20200224のC#に関する記事は10件です。

『Unity C# ガチ初心者用』 for文

自分やガチ始めたばかりの初心者用

テンプレ

        //iが5未満の時に起動し、iに1を足す
        for (int i = 0; i < 5; i++)
        {
            //iの値をコンソールに表示する
            Debug.Log(i);
        }

int i = 0;
int型のiを作り0を代入
i < 5;
iの値が5より未満の時に中身を起動する
i++
iに1を足す

int型のiが5未満の時に中身を起動し、その後iに1を足す
という処理になる
この時iが0の時も処理が行われ、5未満なので4になるまで繰り替えされるので
0,1,2,3,4の5回処理が行われる

数値の種類は変えられる

        //INTで使用
        //iが5未満の時に起動し、iに1を足す
        for (int i = 0; i < 5; i++)
        {
            //iの値をコンソールに表示する
            Debug.Log(i);
        }

        //floatで使用
        //iが10未満の時に起動し、iに2.5fを足す
        for (float i = 0; i < 10; i += 2.5f)
        {
            //iの値をコンソールに表示する
            Debug.Log(i);
        }

一定の回数などならint型を使用し、
座標などならfloat型を使用する

2重で使用

        //xが5以下の時に起動し、xに1を足す
        for (int x = 0; x <= 5; x++)
        {
            //yが10以下の時に起動し、yに2.5fを足す
            for (float y = 0; y <= 10; y += 2.5f)
            {
                //iの値をコンソールに表示する
                Debug.Log(x);
                //iの値をコンソールに表示する
                Debug.Log(y);
            }
        }

処理の流れは
1.一つ目のfor文のxの値が0の時
2.二つ目のfor文の処理がすべて行われる
3.その後一つ目のfor文のxにx++で1が足され
4.再び二つ目のfor文の処理がすべて行われる
5.その後一つ目の(以下略

このような流れになる

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

『Unity C# 個人用メモ』 for文

自分やガチ始めたばかりの初心者用

テンプレ

        //iが5未満の時に起動し、iに1を足す
        for (int i = 0; i < 5; i++)
        {
            //iの値をコンソールに表示する
            Debug.Log(i);
        }

int i = 0;
int型のiを作り0を代入
i < 5;
iの値が5より未満の時に中身を起動する
i++
iに1を足す

int型のiが5未満の時に中身を起動し、その後iに1を足す
という処理になる
この時iが0の時も処理が行われ、5未満なので4になるまで繰り替えされるので
0,1,2,3,4の5回処理が行われる

数値の種類は変えられる

        //INTで使用
        //iが5未満の時に起動し、iに1を足す
        for (int i = 0; i < 5; i++)
        {
            //iの値をコンソールに表示する
            Debug.Log(i);
        }

        //floatで使用
        //iが10未満の時に起動し、iに2.5fを足す
        for (float i = 0; i < 10; i += 2.5f)
        {
            //iの値をコンソールに表示する
            Debug.Log(i);
        }

一定の回数などならint型を使用し、
座標などならfloat型を使用する

2重で使用

        //xが5以下の時に起動し、xに1を足す
        for (int x = 0; x <= 5; x++)
        {
            //yが10以下の時に起動し、yに2.5fを足す
            for (float y = 0; y <= 10; y += 2.5f)
            {
                //iの値をコンソールに表示する
                Debug.Log(x);
                //iの値をコンソールに表示する
                Debug.Log(y);
            }
        }

処理の流れは
1.一つ目のfor文のxの値が0の時
2.二つ目のfor文の処理がすべて行われる
3.その後一つ目のfor文のxにx++で1が足され
4.再び二つ目のfor文の処理がすべて行われる
5.その後一つ目の(以下略

このような流れになる

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

Service Bus triggerでキューの情報をDBに非同期で書き込む(SQL Database)

Azure Storage Tableへの非同期書き込みがうまくいったので、いよいよ本命のSQL Databaseへの非同期書き込みにチャレンジ!

2020/02/24 追記
KeyVaultシークレット情報(接続文字列)を管理させるようにできました。後半に記載。


いつも通り情報があまりないです…C#の知識があればさくっと書けるのかもしれないですが、私にはないので手探りで進めます。

VisualStudioを利用してDB参照する方法は以下のサイトが非常に参考になります!
参考サイト:Azure FunctionsとSQLデータベースを連携する

1.Azure Storage Tableへの非同期書き込みと同様にテンプレから作成できるかと思いきや、ない。

ブレードから統合を選択し、出力から新規追加(+新しい出力)しても見当たらない。
image.png

2.コーディングの前の下準備(DB作成、テーブル作成)

参考サイトを元にDB作成

クエリエディタを利用
create table zodiac_type(
    id INT IDENTITY(1,1),
    name nvarchar(20)
)

insert into zodiac_type(name) values ('a');
insert into zodiac_type(name) values ('b');
insert into zodiac_type(name) values ('c');
insert into zodiac_type(name) values ('d');
insert into zodiac_type(name) values ('e');
insert into zodiac_type(name) values ('f');
insert into zodiac_type(name) values ('g');
insert into zodiac_type(name) values ('h');
insert into zodiac_type(name) values ('i');
insert into zodiac_type(name) values ('j');
insert into zodiac_type(name) values ('k');
insert into zodiac_type(name) values ('l');

3.DBへの書き込み処理は手で書く

1.アプリケーション設定にSQL Databaseの接続文字列を追加

image.png

2.コード内で先ほど設定した接続文字列が参照できることを確認

run.csx(一部)
var str = Environment.GetEnvironmentVariable("SQLDATABASE_CONNECT");
log.LogInformation($"{str}"); 

3.「Service Busトリガーで起動し、メッセージ内容をDBにINSERTする」処理を加える

Tips. Functionsの.Net Core3.1だとSystem.Data.SqlClientの一部の機能がコンパイルエラーになりました。~2.xに変更することで解消。(それでいいのか・・・)

run.csx(全量)
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;

public static void Run(string myQueueItem, ILogger log)
{
    log.LogInformation($"C# ServiceBus queue trigger function processed message: {myQueueItem}");

   // Get the connection string from app settings and use it to create a connection.
    var str = Environment.GetEnvironmentVariable("SQLDATABASE_CONNECT");
    using (SqlConnection conn = new SqlConnection(str))
    {        
        try{
            // 接続文字列を利用してSQLDatabaseに接続
            conn.Open();
        }catch (Exception exc)
        { 
            log.LogInformation($"connect Error!");    
        }        
        var text = "select count(*) from [dbo].[zodiac_type];";
        using (SqlCommand cmd = new SqlCommand(text, conn))
        {
            // SQLの実行はココ selectなのでExecuteScalarクラスを利用
            var rows = cmd.ExecuteScalar();
            log.LogInformation($"{rows} rows were updated");
        }

        var queryCommand = "insert into [dbo].[zodiac_type] (name) values ('" + ($"{myQueueItem}") +"')";
        log.LogInformation($"{queryCommand}");   
        using (SqlCommand cmdcmd = new SqlCommand(queryCommand, conn))
        {
            // SQLの実行はココ insertなのでExecuteNonQueryクラスを利用
            var rowsrows = cmdcmd.ExecuteNonQuery();
        }
    }
}

4.Service Busに書き込まれた情報が無事SQL Databaseのnameテーブルに追加されたことを確認

ブラウザから打鍵
image.png

Azureポータル(コンソールログ)
2020-02-24T12:27:32.182 [Information] Executing 'Functions.ServiceBusQueueTrigger1' (Reason='New ServiceBus message detected on 'queue01'.', Id=********-e58a-477e-a9e9-1fc248e8f359)
2020-02-24T12:27:32.183 [Information] Trigger Details: MessageId: 589b9068e2a7440886d03930a2ecd770, DeliveryCount: 1, EnqueuedTime: 2/24/2020 12:27:32 PM, LockedUntil: 2/24/2020 12:28:02 PM, SessionId: (null)
2020-02-24T12:27:32.191 [Information] C# ServiceBus queue trigger function processed message: W
2020-02-24T12:27:32.193 [Information] 28 rows were updated
2020-02-24T12:27:32.194 [Information] insert into [dbo].[zodiac_type] (name) values ('W')
2020-02-24T12:27:32.201 [Information] Executed 'Functions.ServiceBusQueueTrigger1' (Succeeded, Id=********-e58a-477e-a9e9-1fc248e8f359)

クエリエディタからセレクトを実行
image.png


2020/02/24 追記

接続文字列情報をアプリケーション設定として持っておくとセキュリティ管理責任をアプリケーションも負わなければいけないので、極力KeyVaultに集約管理したい。では、KeyVaultに保管したシークレット情報を読み取る実装に変更してみよう!というお話です。

1.ServiceBusのキューにデータを書き込む関数(HTTPトリガー)の改修

できていないこと できていること
FunctionsがKeyVaultから接続文字列を取得して、 ServiceBusのキューにデータを書き込む

現状は、Functionsがアプリケーション設定内にある接続文字列を取得するように統合時に設定

image.png

後から確認する場合はアプリケーション設定から確認可能
image.png

でもこの変数名はコード内に出てこないし、接続処理はどうやってるんだ?と思っていろいろあさっているとfunction.jsonに登録されていた模様
image.png

1.マネージドIDの作成と関連付け

ここまでわかればKeyVaultからデータをとってくるのは容易いです。まずはFunctionsでマネージドIDを作成して、KeyVaultのシークレットに一覧・取得を権限付け。詳細は過去記事参照(KeyVaultのシークレット情報をマネージドIDを利用して取得する)

2.シークレット識別子の把握

この時のシークレット情報のURL(シークレット識別子)をメモ

例)ttps://KeyVault名.vault.azure.net/secrets/シークレット名/**********************

3.Key Vault 参照形式に書き換えてメモ帳に用意

参考:App Service と Azure Functions の Key Vault 参照を使用する

参照構文
Key Vault 参照の形式は @Microsoft.KeyVault({referenceString}) です。{referenceString} は次のいずれかのオプションで置換されます。

4.一番最初のアプリケーション設定の値欄にKey Vault 参照形式で貼り付け

緑チェックでキーコンテナの参照 になったらOK

例)@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mysecret/ec96f02080254f109c51a1f14cdb1931)

image.png

2.ServiceBusのキューからデータを取得する関数(Service Bus trigger)の改修

こちらの関数はServiceBusの接続情報とSQLDatabaseの接続情報を2つ持っているので、変更箇所が2箇所になるだけ。

1.シークレット情報を作成して、シークレット識別子を入手。

2.Key Vault 参照形式に変更して、アプリケーション設定の値を変更。

3.以下のようになればOK

image.png

4.最後にもう一回打鍵して、想定した挙動で動くことを確認して終了

image.png

image.png

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

SeleniumのChromeDriverで起動済みのブラウザを操作する

はじめに

ブラウザ自動テストのデファクトスタンダードであるSelenium
基本的にSeleniumを使用して組んだプログラムは、自分自身でブラウザを起動して自動操作を開始します

しかし、起動済みのブラウザを自動操作したい!というケースもありますよね?
はたしてChromeDriverを使って起動済みのブラウザを操作することができるのか?

動かせた時の感動は省略

やったこと

ブラウザの起動

今回の手法は、Google Chrome を起動するところにポイントがあります。
コマンドライン引数で-remote-debugging-port=9222を指定してあげるのです!
DevTools Protocol が有効な状態でブラウザが起動するので、 Selenium がアタッチできるようになるんですね。

"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" -remote-debugging-port=9222 --user-data-dir=C:\Temp_ForChrome

--user-data-dirの指定も忘れずに。
操作対象のブラウザ以外に Google Chrome を起動している場合、
ユーザプロファイルがあるフォルダとして同一パスをSeleniumで指定することができないようです。
私の環境ではタイムアウトエラーになってしまいました。

OpenQA.Selenium.WebDriverException: The HTTP request to the remote WebDriver server for URL http://localhost:61065/session timed out after 60 seconds. ---> System.Net.WebException: 要求は中止されました: 操作はタイムアウトになりました。

参考:SeleniumでChromeのユーザープロファイルを指定しつつ同時に自分もChromeを使う方法

ソース

ChromeOptions.DebuggerAddressにブラウザ起動時に指定したものと同じポート番号を指定します。
ブラウザ起動済みの状態で当プログラムを実行すると、見事に動き始めます。

var options = new ChromeOptions
{
    DebuggerAddress = "127.0.0.1:9222"
};

using (var driver = new ChromeDriver(options))
{
    var wait = new WebDriverWait(driver, new TimeSpan(0, 0, 5));

    // 任意のブラウザ操作処理 ↓↓↓

    driver.Url = "https://www.google.com";
    var q = driver.FindElementByName("q");
    q.SendKeys("Chromium");
    q.Submit();

    wait.Until(ExpectedConditions.TitleIs("Chromium - Google 検索"));
    ((ITakesScreenshot)driver).GetScreenshot().SaveAsFile($"{DateTime.Now.ToString("yyyyMMddHHmmss")}.png");

    // 任意のブラウザ操作処理 ↑↑↑
}

おまけ

Google Chrome を起動する部分がポイントだったわけです。
ここで、手動ではなく別プログラム経由で起動されたブラウザを自動操作したい場合を考えてみます。
うまいこと-remote-debugging-portを指定してあげる必要がありますよね?

やるとすればこんな感じか。。?

using System.Diagnostics;

/// <summary>
/// chrome.exe という名前のアセンブリとしてビルドする
/// </summary>
namespace DummyChrome
{
    class Program
    {
        static void Main(string[] args)
        {
            var proc = new Process();
            proc.StartInfo.FileName = @"C:\Program Files (x86)\Google\Chrome\Application\_chrome.exe";
            proc.StartInfo.Arguments = @"-remote-debugging-port=9222 --user-data-dir=C:\Temp_ForChrome";
            proc.Start();
        }
    }
}

オリジナルのchrome.exe_chrome.exeにリネームしてしまいます。
Google Chrome の通常利用に支障が出るのはご愛敬。
image.png

おわりに

参考にしたページ:
Selenium: Attach to an existing Chrome browser with C#

参考になりそうでしなかったページ:
Re-using existing browser session in Selenium using C#
Reflectionをつかってprivateメンバーにアクセスしてるので黒魔術感が否めない。
そもそもやりたいことが違う雰囲気もある。

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

【C#備忘録】ランダムな値を出力する

※この備忘録は初学者の私が学習した内容を忘れないために書き起こしたものとなります。

①Randomクラスのインスタンスを生成する

using System;
public class Program{
    public static void Main(){
        var random = new Random();            
    }
}

上記のように、変数randomにRandomクラスのインスタンスを生成し代入する。

②変数randomを、Nextメソッド、WriteLineメソッドを用いて表示させる

using System;
public class Program{
    public static void Main(){
        var random = new Random();
        var number = random.Next(1,25);  //1以上25未満の数値を指定
        Console.WriteLine("C#を1日に" + number + "時間勉強した");            
    }
}

※「C#を1日に【1~24の中のランダム値】時間勉強した」と出力される

注意点

Nextメソッドの引数を指定しないととんでもない桁数の数値からランダムで出力されてしまう。
そのため、Next(〇〇,△△)のように引数を指定するとよい。
また、引数は、〇〇以上、△△未満を意味している。

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

【Unity】RenderTextureを使ってオブジェクトにアウトラインを付ける

こんにちはっ?八ツ橋まろんです

今回はUnityのアウトラインシェーダー&マテリアルの話です。

Unityで3Dモデルにアウトラインを付ける方法はいくつかありますが、以下の条件をお求めの人におすすめです。

・アウトラインを付けたい対象のオブジェクトのシェーダーは何でもOK
 (アウトライン機能のないスタンダードシェーダーとかでもOK)
・アウトラインを極太にしてもヘンな描画にならない(ただし描画コストが上がる)

アウトライン描画の概要

本記事でのアウトラインの描画の流れを以下の画像に示します。
図1.png

こんな感じで、破綻しづらく太いアウトラインができました。
RenderTextureに対してシェーダーで処理をするので、対象の3Dモデルはどんなシェーダーでも良いというのがgoodです✨
(よくあるアウトライン描画の方法では、Stencilを仕込んだり、メッシュを拡張するためににシェーダーをいじる必要があるのですよね)

また、Render Textureを後ろに重ねるだけなので、二重や三重にすることもできるので、上の画像の一番右のように3Dモデル→白枠→細い灰色枠→影と言う風に3枚のRender Textureでより豪華な表現ができます。

シェーダーコード

以下、シェーダーコードです。

MaronOutlineShader.cs
Shader "Custom/MaronOutlineShader"
{
    Properties
    {
        [NoScaleOffset]_MainTex ("Render Texture", 2D) = "white" {}
        _SubTex ("Pattern Texture", 2D) = "white" {}
        _Color ("Main Color", Color) = (1,1,1,1)
        _Outline ("Outline thickness", Range (0,1)) = 0.1
        _DrawNum ("Drawing times", Range (2,32)) = 2
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent" }
        LOD 100
        ZWrite off
        Blend srcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainTex_TexelSize;
            sampler2D _SubTex;
            float4 _SubTex_ST;
            float4 _SubTex_TexelSize;
            float4 _Color;
            float _Outline;
            int _DrawNum;

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

            float2 PolarToCartesian(float r, float theta)
            {
                float x = r * cos(theta);
                float y = r * sin(theta);
                return float2(x, y);
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);

                float2 tiling_offset_uv = float2(i.uv.xy *  _SubTex_ST *float2(1, _MainTex_TexelSize.x/_MainTex_TexelSize.y) +  _SubTex_ST.zw);
                fixed4 subcol = tex2D(_SubTex, tiling_offset_uv);

                float2 outline = _Outline*0.1;

                float PI = 3.14159265358979323;
                int n = _DrawNum;
                float theta;
                for(int j = 0; j < 2 * n; j++)
                {
                    theta = PI * j / n;
                    col += tex2D(_MainTex, i.uv + PolarToCartesian(outline, theta)*float2(_MainTex_TexelSize.x/_MainTex_TexelSize.y, 1));               
                }
                //境界をなめらかに
                col = smoothstep(0.5, 0.51, col);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);




                //一定以下のalphaはゼロにする
                _Color.a *= step(1-col.a, 0.01);

                return subcol*_Color;
            }
            ENDCG
        }
    }
}

このシェーダーのキモは、以下のfor文の部分と、PolarToCartesianと定義した関数部分です。

for文
for(int j = 0; j < 2 * n; j++)
{
    theta = PI * j / n;
    col += tex2D(_MainTex, i.uv + PolarToCartesian(outline, theta)*float2(_MainTex_TexelSize.x/_MainTex_TexelSize.y, 1));               
}
PolarToCartesian関数
float2 PolarToCartesian(float r, float theta)
{
    float x = r * cos(theta);
    float y = r * sin(theta);
    return float2(x, y);
}

for文は2n回だけループしますが、ループのたびにthetaがπ/nだけ変化し、PolarToCartesian関数に代入されます。
そして、PolarToCartesian関数は極座標→直交座標の変換をする関数です。( (r, θ)表記を(x, y)表記に直しています)
rはアウトラインの太さ(ずれの大きさ)です。
つまり、例えばn = 2のとき、π/2 = 90度なので、90度ずつrだけずれた描画が2n = 4回なされて、RenderTextureと同じものが上下左右に描画されます。n = 2なのでX軸Y軸の2軸分ずらしたものを描画しているわけです(下画像:左)
qiita1.png

(下方向の影は見づらいけど、脚が下方向に伸びていますね)
n = 2では、ごく短い距離にしないとアウトラインとして成立しないですが、これを、n = 4で影が8つ、n = 8で影が16という風に増やしていくと、太くてもきれいなアウトラインになります。

シェーダの核の部分はこんな感じです。
あとは、シェーダーをTransparentにしたり、模様を付けたいとき用にサブのテクスチャを設定できるようにしてtiling, offsetに対応したり、境界部分をsmoothで多少滑らかにしたりという感じです?

以上、アウトラインシェーダーの話でしたっ?
もし良ければイイネ押していってくださいね?

八ツ橋まろん

Twitter
https://twitter.com/Maron_Vtuber
Booth(シェーダーも売ってる)
https://maronvtuber.booth.pm/

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

FinalIKでキャラクターに思いのままにインタラクションさせる方法

使用アセット

Final IK

Final IK
https://assetstore.unity.com/packages/tools/animation/final-ik-14290

背景

Fantasy Forest Environment
https://assetstore.unity.com/packages/3d/environments/fantasy/fantasy-forest-environment-34245

モデル

京狐
https://booth.pm/ja/items/1826902

アニメーション

Magician-Motion Pack
https://assetstore.unity.com/packages/3d/animations/magician-motion-pack-133515

インタラクションの参考

Final IKインタラクションのチュートリアル
https://www.youtube.com/watch?v=r5jiZnsDH3M

足を接地

Sceneに配置したモデルにFull Body Biped IKを設定
※基本的には自動で設定されるがモノによっては設定が上手くいかないものがある。
01.png

上手く設定できるとこんな感じになる
02.png

床用のレイヤーを追加し、接地させるようのメッシュにレイヤーを設定する。
今回はGroundレイヤーを追加し、地面に設定した。
03.png

Grounder Full Body Bipedをモデルにアタッチする。
Grounder Full Body BipedのIKにFull Body Biped IKをアタッチする。
Solverを開き、Layersに接地用レイヤーを設定する。今回はGroundを追加した。
04.png

設定するとこんな感じになる。
01.gif

IKだけでオブジェクトに片手を触れて放す

アイドルモーション用に使っていたAnimatorコンポーネントをオフにする
05.png

オブジェクトにInteractionObjectをアタッチ
06.png

Weight Curvesを追加しTypeはPosition Weightを選ぶ
Curvesは(Time,Value)でKeyを(0, 0) > (0.5, 1) > (1, 0)と設定した。
※0秒から0.5秒にかけて目標座標に到達し、0.5秒から1秒にかけて元に戻るようなカーブです。
07.png

インタラクションさせるモデルにInteractionSystemをアタッチする
Full Bodyにはモデルに設定したFull Body Biped IKを設定する
08.png

InteractionSystemTestGUIをアタッチする
※ゲームビューにInteractionSystemを実行するためのボタンが追加されます。

FinalIKの動作テスト用に同梱されたスクリプトであり、任意のタイミングでインタラクションするにはスクリプトの修正が必要です。
Interaction ObjectにInteraciton Objectをアタッチしたのを設定する
Effectorsを増やしRight Handを選択する
※これで右手にInteractionを適用することができる
09.png

動かすとこんな感じ
4.gif

IKだけでオブジェクトに両手を触れて放す

上の章(IKだけでオブジェクトに片手を触れて放す)で紹介した工程をしたあとに
InteractionSystemTestGUIのEffectorsを増やし
RightHandとLeftHandを設定するだけです。
11.png

動かすとこんな感じ
7.gif

IKだけでオブジェクトに片手を触れ続ける

上の章(IKだけでオブジェクトに片手を触れて放す)で紹介した工程をしたあとに
Interaction ObjectのEventsを追加し
Timeを0.5
Pauseにチェックを入れる
10.png

動作としてはWeightCurvに従って0.5秒かけてオブジェクトに手を近づけ、
Evetntsで設定されたPauseが呼ばれる動作です
Curvを伸ばし、Timeを調整することでタイミングを調整できます。

動かすとこんな感じ
5.gif

IKだけでオブジェクトに両手で触れ続ける

上の章(IKだけでオブジェクトに片手を触れ続ける)で紹介した工程をしたあとに
InteractionSystemTestGUIのEffectorsを増やし
RightHandとLeftHandを設定するだけです。
11.png

動かすとこんな感じ
6.gif

IKだけでオブジェクトを片手で掴む

上の章(IKだけでオブジェクトに片手を触れて放す)で紹介した工程をしたあとに
12.png
動かすとこんな感じ
8.gif

掴むオブジェクトの回転をモデルのボーンに適用する

Interaction ObjectのMultipliersを追加し以下のように設定する
Curve:Position Weight
Multiplier:1
Result:Rotate Bone Weight
13.png
動かすとこんな感じ
9.gif
MultiPliersのCurveはWeight Curveで指定したCurveから選ぶ

オブジェクトに手の位置を合わせる

モデルを複製し、複製したモデルの手(Hand)をオブジェクトの置きたい座標に移動させる
※この時手が伸びてるように見えるが問題はない
14.png
複製した手を置きたいオブジェクトの子階層に配置する
15.png
コピーしたモデルは削除してよい
オブジェクト直下に配置したHandにInteractionTargetをアタッチし以下のように設定
EffectType:RightHand
※合わせるのに使用した部位を選ぶ
16.png
動かすとこんな感じ
10.gif

ポジションはあってるが回転があってないのでWeight Curveを追加し、Rotation Weightを追加する
17.png
動かすとこんな感じ
11.gif

オブジェクトに指の位置を合わせる

モデルの合わせたい指がある手(Hand.R)にHandPoserをアタッチ
18.png

調整するときはゲームを再生し、IK適用後にオブジェクト直下に配置してるHandの指を動かす
12.gif

調整したHandオブジェクトをコピーして再生を終了する
コピーしたオブジェクトに置き換える

位置によって手首がねじれるのでオブジェクトのInteractionTargetに
Pivot:インタラクションするオブジェクト
※再生中にTwistAxisを調整してコピペして角度合わせるのも有効
Rotate Once:チェックをつけると常に角度が修正されるようになる
19.png

動かすとこんな感じ
13.gif

手を動かすときのY座標のオフセットを指定する

オブジェクトのInteractionObjectのWeightCurvを追加する
Y座標のオフセットを修正する際にはPosition OffsetYにする
Curvesは(Time,Value)でKeyを(0, 0) > (0.25, 1) > (0.5, 0)と設定した。
※0秒から0.25秒にかけてオフセットのYに到達し、0.25秒から0.5秒にかけて目的座標に戻るようなカーブです。
20.png

動かすとこんな感じ
14.gif

手を動かすときの腕の伸ばし具合のオフセットを指定する

オブジェクトのInteractionObjectのWeightCurvを追加する
腕の伸ばし具合のオフセットを修正する際にはReachにする
沢山伸ばしたい場合はValueを0に近づける
21.png
調整するときは再生してコピペでCurveを設定する
16.gif
Pauseを0.5としてるので
Curvesは(Time,Value)でKeyを(0, 0) (0.5, 0.08)と設定した。
動かすとこんな感じ
15.gif

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

ゼルダの伝説BotWのカメラ研究 with Unity 其の一「基本の動き」

はじめに

  • ゼルダの伝説BotWはここでは「ゼルダの伝説 ブレスオブザワイルド」を指します。
  • 場合によっては誤りがある場合があります。十分に気を付けてはおりますが、筆者は特に数学が苦手ですのでその部分で誤りがあるやしれません。
  • 解説を分かりやすくするために一部初級者向けの内容が含まれています。
  • Unityで動きを演出する部分があります。
  • 何回かに分けて考察を書いていきます。今回は基本的な動きに目を付けたいと思います。
  • 現在、この項に関して考察は完了していますがそれを実現するための処理は完成していません。予めご了承ください。
  • (2020年2月25日追記)処理が完成しました。

Unityの動作のテスト環境

Windows10 Home
Unity 2019.3.0f6
VisualStudio 2019 Professional

基本の動き

基本の映し方としては一般的な三人称視点のカメラと変わりはありません。

20200224Zelda-image.jpg

注目していただきたいのはカメラの回転速度(左右の軸周り)です。

動画はこちらを確認してください

動画を確認された方は「何を言ってるんだ、普通の動きじゃないか」などと思われるかもしれませんが、例えばUnityのRotateAround関数の処理をC#で書き表すとこんな感じになります。

ソースコード

RotateAround.cs
using UnityEngine;

public class RotateAround : MonoBehaviour
{
    [SerializeField]
    GameObject parent;

    float speed;


    // Start is called before the first frame update
    void Awake()
    {
        speed = 50.0f;
    }

    // Update is called once per frame
    void Update()
    {
        float x = Input.GetAxis("RightHorizontal");
        float y = Input.GetAxis("RightVirtical");

        //初期値
        float rad = 2 * Mathf.PI;

        //RotateAroundと同等の動きをする
        Vector3 radSpeed = x * transform.right * rad;

        transform.position += radSpeed * Time.deltaTime;

        //振動を防ぐためにMathf.Clampは利用していない
        //振動を防ぐ方法として一定以上もしくは以下になったらそれ以上動きを足さない処理を利用する
        float eulerAnglesX = transform.eulerAngles.x;

        if (!((eulerAnglesX < 180 && eulerAnglesX > 89.0f && y > 0) || (eulerAnglesX > 180 && eulerAnglesX < 271.0f && y < 0)))
            transform.RotateAround(parent.transform.position, transform.right, y * speed * Time.deltaTime);

        transform.LookAt(parent.transform);
    }
}

簡単なコード解説

  • 使っているのはXboxController for Windows
  • 上下の軸周りはそのままUnityのRotateAround関数を利用しています。
  • 動きを分かりやすくするためにLookAt関数を利用しています。
  • カメラの角度制限を設けた際の振動を抑える処理を追加しています。

動かすとこうなる

動画はこちらを確認してください

y軸と、基準点とカメラ位置のなす直線がなす角が狭まれば狭まるほど高速に回転を始めることがわかると思います。
(下記の画像と上記の動画で言いたいことがわかっていただけると幸いです。)
nasukaku.png

なお、下から覗くような形でも同様の現象が確認できます。

考察

まず必要な条件として、カメラが基準となる位置を周回するときに回転数が半径に関わらず常に一定であるということが挙げられるかと思います。

周回する際の半径に関わらず回転数が一定である条件

まず、Unityで動かした方は半径に関わらず速度が一定という条件での動作でした。
これでは半径が短くなることで円周の長さも短くなった場合に一周する速さが変わり、半径によって回転数が変化してしまします。

そこで必要になるのが角速度を利用するという考え方です。

そもそも角速度とは

角速度は回転運動の速度を単位時間当たりの角度で表したものになります。
回転運動(円運動)をしている物体の角速度が一定である場合、その物体は等速円運動を行っていると定義できます。

結論:ゼルダBotWのカメラを再現するための条件

角速度が一定である運動 = 等速円運動を行わせることでゼルダのカメラの基礎的な部分は再現できる!!
と、条件を定めました。

結論が出たうえでの再現処理

RotateAround.cs
    [SerializeField]
    GameObject parent;

    float speed;
    float distance; //追加 基準オブジェクトとの距離。半径の役割を持つ
    Vector3 distancePosition; //追加 半径設定用のベクトル

    // Start is called before the first frame update
    void Awake()
    {
        speed = 50.0f;

        //半径設定 高さをカメラと同一にすることで距離をそのまま半径として利用できる
        distancePosition = new Vector3(parent.transform.position.x, transform.position.y, parent.transform.position.z); 
        distance = Vector3.Distance(distancePosition, transform.position);
    }

    // Update is called once per frame
    void Update()
    {
        float x = Input.GetAxis("RightHorizontal");
        float y = Input.GetAxis("RightVirtical");

        //等速円運動をする物体の角速度(rad/s)の公式は 角速度(ベクトル) = 速度 / 半径 もしくは 角速度(ベクトル) = 角度 / 時間 (2π * 半径 / 時間)
        //今回は角速度(ベクトル) = 2π * 半径 / 時間 を利用する
        //今回の場合、秒間72°移動する処理である
        float radSpeedValue = 2 * Mathf.PI * distance / 5.0f; //変更

        //RotateAroundと同等の動きをする
        Vector3 radSpeed = x * transform.right * radSpeedValue;

        transform.position += radSpeed * Time.deltaTime;

        //振動を防ぐためにMathf.Clampは利用していない
        //振動を防ぐ方法として一定以上もしくは以下になったらそれ以上動きを足さない処理を利用する
        float eulerAnglesX = transform.eulerAngles.x;

        if (!((eulerAnglesX < 180 && eulerAnglesX > 89.0f && y > 0) || (eulerAnglesX > 180 && eulerAnglesX < 271.0f && y < 0)))
            transform.RotateAround(parent.transform.position, transform.right, y * speed * Time.deltaTime);

        transform.LookAt(parent.transform);

        //半径設定 高さをカメラと同一にすることで距離をそのまま半径として利用できる
        distancePosition = new Vector3(parent.transform.position.x, transform.position.y, parent.transform.position.z);
        //y軸となす角度によって距離が変動するため、毎フレーム更新
        distance = Vector3.Distance(distancePosition, transform.position);
    }

簡単なコード解説

  • Update関数の一部を変更し、半径を設定することで角速度の計算を行えるように変更しました。
  • 距離を取得するための変数を追加しました。

関連する記事

まだ書いていません。

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

Azure Queue Storage triggerでキューの情報をDBに非同期で書き込む(Azure Strage テーブル)

以下のサイトを参考(そのままコピペ)にして、Functionsで受け付けた情報をAzure Storage キューに書き込み出来るようになりました。
Functions を使用して Azure Storage キューにメッセージを追加する

ちょっとステップアップして、非同期テーブル書き込み処理を実現してみました。
1.HTTPのGETリクエストのパラメータをAzure Storage キューに書き込み
2.Azure Storage キューをトリガーにAzure Storage テーブルに書き込み

1.HTTPのGETリクエストのパラメータをAzure Storage キューに書き込み

<参考にした手順はこちら>
Functions を使用して Azure Storage キューにメッセージを追加する

1. Azure ポータルから関数アプリ(Functions)を作成し、HTTPトリガーのテンプレートを利用すると自動でコードが作成される

2. さらにブレードから統合を選択
image.png

3. 出力から「+新しい出力」をAzure Queue Storageを選択
image.png

image.png
4. 便利なのでStorage Explorerをインストール(接続するときは接続文字列を使うと便利、ストレージ表示名は接続文字列を入力すると自動補完してくれた
image.png
5. HTTP Triger1のコードに以下を追加

実行するとHTTPのGETリクエストのクエリパラメータをキューに保存される。

  • ブラウザを打鍵した結果

image.png

  • Storage Explorerの内容

image.png

<一部抜粋>
public static async Task Run(HttpRequest req, ICollector outputQueueItem, ILogger log)
outputQueueItem.Add(name);

<全量>

run.csx
#r "Newtonsoft.Json"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

public static async Task<IActionResult> Run(HttpRequest req, ICollector<string> outputQueueItem, ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    string name = req.Query["name"];

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    name = name ?? data?.name;

    // パラメーターを使用してキュー メッセージを作成するコードを追加
    outputQueueItem.Add(name);

    return name != null
        ? (ActionResult)new OkObjectResult($"Hello, {name}")
        : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}

2.Azure Storage キューをトリガーにAzure Storage テーブルに書き込み

1. 関数(Azure Queue Storage trigger)の追加を行う

image.png

※キュー名はINPUTとするトリガーキューの名前なので、先ほどHTTP Trigger1で出力したキュー名を指定する必要有り
image.png
2. 統合>出力からAzure Table Storageを選択

image.png
3. Storage Explorerからテーブル「outTable」を作成

image.png
4. 以下のコードに書き換え
<全量>

run.csx
using System;

public static void Run(string myQueueItem, ICollector<Person> outputTable, ILogger log)
{
    log.LogInformation($"C# Queue trigger function processed: {myQueueItem}");

    log.LogInformation($"Adding Person entity {myQueueItem}");
    outputTable.Add(
        new Person() { 
            PartitionKey = "Test", 
            RowKey = $"{myQueueItem}"}
        );
}

public class Person
{
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
}

5. 実行するとHTTPのGETリクエストのクエリパラメータがキューに保存されて、DBに格納される。キューの情報は取り出したら自動で削除してくれるっぽい

  • ブラウザを打鍵した結果

image.png

  • Storage Explorerの内容

image.png

image.png

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

Azure Queue Storage triggerで非同期テーブル書き込み

以下のサイトを参考(そのままコピペ)にして、Functionsで受け付けた情報をAzure Storage キューに書き込み出来るようになりました。
Functions を使用して Azure Storage キューにメッセージを追加する

ちょっとステップアップして、非同期テーブル書き込み処理を実現してみました。
1.HTTPのGETリクエストのパラメータをAzure Storage キューに書き込み
2.Azure Storage キューをトリガーにAzure Storage テーブルに書き込み

1.HTTPのGETリクエストのパラメータをAzure Storage キューに書き込み

<参考にした手順はこちら>
Functions を使用して Azure Storage キューにメッセージを追加する

1. Azure ポータルから関数アプリ(Functions)を作成し、HTTPトリガーのテンプレートを利用すると自動でコードが作成される

2. さらにブレードから統合を選択
image.png

3. 出力から「+新しい出力」をAzure Queue Storageを選択
image.png

image.png
4. 便利なのでStorage Explorerをインストール(接続するときは接続文字列を使うと便利、ストレージ表示名は接続文字列を入力すると自動補完してくれた
image.png
5. HTTP Triger1のコードに以下を追加

実行するとHTTPのGETリクエストのクエリパラメータをキューに保存される。

  • ブラウザを打鍵した結果

image.png

  • Storage Explorerの内容

image.png

<一部抜粋>
public static async Task Run(HttpRequest req, ICollector outputQueueItem, ILogger log)
outputQueueItem.Add(name);

<全量>

run.csx
#r "Newtonsoft.Json"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

public static async Task<IActionResult> Run(HttpRequest req, ICollector<string> outputQueueItem, ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    string name = req.Query["name"];

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    name = name ?? data?.name;

    // パラメーターを使用してキュー メッセージを作成するコードを追加
    outputQueueItem.Add(name);

    return name != null
        ? (ActionResult)new OkObjectResult($"Hello, {name}")
        : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}

2.Azure Storage キューをトリガーにAzure Storage テーブルに書き込み

1. 関数(Azure Queue Storage trigger)の追加を行う

image.png

※キュー名はINPUTとするトリガーキューの名前なので、先ほどHTTP Trigger1で出力したキュー名を指定する必要有り
image.png
2. 統合>出力からAzure Table Storageを選択

image.png
3. Storage Explorerからテーブル「outTable」を作成

image.png
4. 以下のコードに書き換え
<全量>

run.csx
using System;

public static void Run(string myQueueItem, ICollector<Person> outputTable, ILogger log)
{
    log.LogInformation($"C# Queue trigger function processed: {myQueueItem}");

    log.LogInformation($"Adding Person entity {myQueueItem}");
    outputTable.Add(
        new Person() { 
            PartitionKey = "Test", 
            RowKey = $"{myQueueItem}"}
        );
}

public class Person
{
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
}

5. 実行するとHTTPのGETリクエストのクエリパラメータがキューに保存されて、DBに格納される。キューの情報は取り出したら自動で削除してくれるっぽい

  • ブラウザを打鍵した結果

image.png

  • Storage Explorerの内容

image.png

image.png

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