20190622のC#に関する記事は9件です。

[Unity]無理矢理OpenCVを使う

0.はじめに

UnityでいざOpenCVを使おうとするとOpenCV for Unityなどのアセットが必要になってくる
しかしこれ$95もするので、安易に手を出しにくい(OpenCV plus Unityという無料のアセットもあるが)
だったら必要なOpenCVのライブラリだけラップすればいいんじゃね?というのが今回の記事
ただ七面倒臭いことを色々しなくちゃいけないのでそいうのが嫌な方は素直にアセットを使うのがオススメ

最終目標はUnity側で撮ったスクリーンショットをOpenCV側に渡してグレースケール化し出力すること
環境はWindows10とVisualStudioを使用

1.ラップする

まず、OpenCVからビルド済みのOpenCVを入手(Releases->windows)後
DLL化するプロジェクトにDLLとLIBとheaderが入ってるopencv2フォルダをぶっこむ
詳しいC++のDLL化はUnityでC++を使う方法

リンカー->入力でlibを設定後、opencv2フォルダがあるパスを構成プロパティ->インクルートディレクトリに加える
ちなみにこれがないとコンパイルエラーになる
(OpenCV側のincludeが一部<>で括られているheaderが存在するため)

C++のコード

DLLAPI unsigned char* __stdcall opencv_sample(unsigned char* img_data, unsigned int img_width, unsigned int img_heingt, unsigned char channel) {
    cv::Mat m_img(img_heingt, img_width, CV_8UC3, img_data);
    cv::Mat tmp_gray_img;
    cv::Mat color_gray_img;
    cv::cvtColor(m_img, tmp_gray_img, cv::COLOR_RGB2GRAY);
    cv::cvtColor(tmp_gray_img, color_gray_img, cv::COLOR_GRAY2RGB);
    int color_gray_img_len = color_gray_img.rows * color_gray_img.cols * color_gray_img.channels();
    unsigned char* dst = (unsigned char*)::CoTaskMemAlloc(color_gray_img_len);
    memcpy_s(dst, color_gray_img_len, color_gray_img.data, color_gray_img_len);

    return dst;
}

配列をグレースケール化して返す関数
Unity側から送られてくるのはbyte配列なのでC++側はunsigned charのポインターとなる
1チャンネルのグレースケールだと扱いにくいので3チャンネルのグレースケールとして返す
CoTaskMemAllocでメモリを確保すると.Net側でのGC対象とすることができるReturning Strings from a C++ API to C#
Release x64でビルドして実際にDLLを作成する

2.Unityでスクリーンショットを撮る

詳しいやり方はHow to save a picture (take screenshot) from a camera in game?

Unityのコード

    private void LateUpdate()
    {
        bool takeScreenShot = Input.GetKeyDown(KeyCode.F1);

        if (takeScreenShot)
        {
            RenderTexture rt = new RenderTexture(resWidth, resHeight, 24, RenderTextureFormat.ARGB32);
            camera.targetTexture = rt;
            Texture2D screenShot = new Texture2D(resWidth, resHeight, TextureFormat.RGB24, false);
            camera.Render();
            RenderTexture.active = rt;
            screenShot.ReadPixels(new Rect(0, 0, resWidth, resHeight), 0, 0);
            camera.targetTexture = null;
            RenderTexture.active = null;
            Destroy(rt);

            byte[] bytes = screenShot.EncodeToPNG();
            string filename = "./Assets/img/original.png";
            System.IO.File.WriteAllBytes(filename, bytes);

            byte[] gray_bytes = opencv_toGRAYscale(screenShot);

            save_bytes(gray_bytes, "./Assets/img/gray.png");
        }
    }

opencv_toGRAYscaleとsave_bytesは後程説明

スクリーンショット

実際に撮ったものがこちら
Unityで実際に撮ったスクリーンショット
この画像をOpenCVに渡してグレースケール化していく

3.UnityにOpenCVを導入する

AssetsにPlugins/x86_64フォルダを作成
先ほどビルドしたDLLとopencv_world410、opencv_world410dをコピーする
後の設定はUnityでC++を使う方法を参照

宣言

static class OPENCV
{
    [DllImport("dll_opencv", CallingConvention = CallingConvention.StdCall)]
    public static extern byte[] opencv_sample(byte[] img_data, uint img_width, uint img_height, byte channel);
}

先ほど作った関数をUnity側で使えるように宣言します、unsigned charのポインターだった仮引数はbyteの配列とし
戻り値も同様にbyteの配列です

Texture2Dをグレースケールのバイト配列として返す関数

    private byte[] opencv_toGRAYscale(Texture2D screenShot)
    {
        byte[] r_bytes = screenShot.GetRawTextureData();

        byte[] gray_bytes = OPENCV.opencv_sample(r_bytes, (uint)resWidth, (uint)resHeight, 3);

        DestroyTexture2D(screenShot);

        return gray_bytes;
    }

    private void DestroyTexture2D(Texture2D texture)
    {
        Destroy(texture);
    }

気を付けて欲しいのは、先ほども説明した通り
グレースケール化したと言っても戻り値の配列は1チャンネルじゃくて3チャンネルということ
動的に確保したTexture2Dは明示的に破棄しないとメモリを圧迫する原因になります。

配列を出力する関数

    private void save_bytes(byte[] bytes, string file_path)
    {
        Texture2D s_texture = new Texture2D(resWidth, resHeight, TextureFormat.RGB24, false);

        byte[] tmp_bytes = new byte[resWidth * resHeight * 3];
        for(int i = 0; i < resWidth*resHeight*3; i++)
        {
            tmp_bytes[i] = bytes[i];
        }

        Debug.Log(tmp_bytes.Length);

        s_texture.LoadRawTextureData(tmp_bytes);
        byte[] png_bytes = s_texture.EncodeToPNG();
        System.IO.File.WriteAllBytes(file_path, png_bytes);

        DestroyTexture2D(s_texture);
    }

わざわざ配列をコピーしているのは、CoTaskMemAllocで確保した配列の長さがUnity側で
参照できずLoadRawTextureData内部でエラーを吐いてしまうそのための策
ただ例外処理はされているのでやらなくても出力は普通にできる。

結果

Unityで撮ったスクリーンショットをOpenCVに渡してグレースケール化した画像
上手くいきました。

まとめ

実際作ってみるとわかるが、やはり結構な手間とある程度のC++の知識が必要となってくるので
ネイティブなOpenCVにはあるのにAssetにはない機能だったり、まだ対応していない機能が欲しかったりする場合
いち早く実装できる可能性はあるかもしれない。
それ以外は安全性から見ても普通にAssetを使った方が無難に思える。

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

[Unity]アセットなしで無理矢理OpenCVを使う

0.はじめに

UnityでいざOpenCVを使おうとするとOpenCV for Unityなどのアセットが必要になってくる
しかしこれ$95もするので、安易に手を出しにくい(OpenCV plus Unityという無料のアセットもあるが)
だったら必要なOpenCVのライブラリだけラップすればいいんじゃね?というのが今回の記事
ただ七面倒臭いことを色々しなくちゃいけないのでそいうのが嫌な方は素直にアセットを使うのがオススメ

最終目標はUnity側で撮ったスクリーンショットをOpenCV側に渡してグレースケール化し出力すること
環境はWindows10とVisualStudioを使用

1.ラップする

まず、OpenCVからビルド済みのOpenCVを入手(Releases->windows)後
DLL化するプロジェクトにDLLとLIBとheaderが入ってるopencv2フォルダをぶっこむ
詳しいC++のDLL化はUnityでC++を使う方法

リンカー->入力でlibを設定後、opencv2フォルダがあるパスを構成プロパティ->インクルートディレクトリに加える
ちなみにこれがないとコンパイルエラーになる
(OpenCV側のincludeが一部<>で括られているheaderが存在するため)

C++のコード

DLLAPI unsigned char* __stdcall opencv_sample(unsigned char* img_data, unsigned int img_width, unsigned int img_heingt, unsigned char channel) {
    cv::Mat m_img(img_heingt, img_width, CV_8UC3, img_data);
    cv::Mat tmp_gray_img;
    cv::Mat color_gray_img;
    cv::cvtColor(m_img, tmp_gray_img, cv::COLOR_RGB2GRAY);
    cv::cvtColor(tmp_gray_img, color_gray_img, cv::COLOR_GRAY2RGB);
    int color_gray_img_len = color_gray_img.rows * color_gray_img.cols * color_gray_img.channels();
    unsigned char* dst = (unsigned char*)::CoTaskMemAlloc(color_gray_img_len);
    memcpy_s(dst, color_gray_img_len, color_gray_img.data, color_gray_img_len);

    return dst;
}

配列をグレースケール化して返す関数
Unity側から送られてくるのはbyte配列なのでC++側はunsigned charのポインターとなる
1チャンネルのグレースケールだと扱いにくいので3チャンネルのグレースケールとして返す
CoTaskMemAllocでメモリを確保すると.Net側でのGC対象とすることができるReturning Strings from a C++ API to C#
Release x64でビルドして実際にDLLを作成する

2.Unityでスクリーンショットを撮る

詳しいやり方はHow to save a picture (take screenshot) from a camera in game?

Unityのコード

    private void LateUpdate()
    {
        bool takeScreenShot = Input.GetKeyDown(KeyCode.F1);

        if (takeScreenShot)
        {
            RenderTexture rt = new RenderTexture(resWidth, resHeight, 24, RenderTextureFormat.ARGB32);
            camera.targetTexture = rt;
            Texture2D screenShot = new Texture2D(resWidth, resHeight, TextureFormat.RGB24, false);
            camera.Render();
            RenderTexture.active = rt;
            screenShot.ReadPixels(new Rect(0, 0, resWidth, resHeight), 0, 0);
            camera.targetTexture = null;
            RenderTexture.active = null;
            Destroy(rt);

            byte[] bytes = screenShot.EncodeToPNG();
            string filename = "./Assets/img/original.png";
            System.IO.File.WriteAllBytes(filename, bytes);

            byte[] gray_bytes = opencv_toGRAYscale(screenShot);

            save_bytes(gray_bytes, "./Assets/img/gray.png");
        }
    }

opencv_toGRAYscaleとsave_bytesは後程説明

スクリーンショット

実際に撮ったものがこちら
Unityで実際に撮ったスクリーンショット
この画像をOpenCVに渡してグレースケール化していく

3.UnityにOpenCVを導入する

AssetsにPlugins/x86_64フォルダを作成
先ほどビルドしたDLLとopencv_world410、opencv_world410dをコピーする
後の設定はUnityでC++を使う方法を参照

宣言

static class OPENCV
{
    [DllImport("dll_opencv", CallingConvention = CallingConvention.StdCall)]
    public static extern byte[] opencv_sample(byte[] img_data, uint img_width, uint img_height, byte channel);
}

先ほど作った関数をUnity側で使えるように宣言します、unsigned charのポインターだった仮引数はbyteの配列とし
戻り値も同様にbyteの配列です

Texture2Dをグレースケールのバイト配列として返す関数

    private byte[] opencv_toGRAYscale(Texture2D screenShot)
    {
        byte[] r_bytes = screenShot.GetRawTextureData();

        byte[] gray_bytes = OPENCV.opencv_sample(r_bytes, (uint)resWidth, (uint)resHeight, 3);

        DestroyTexture2D(screenShot);

        return gray_bytes;
    }

    private void DestroyTexture2D(Texture2D texture)
    {
        Destroy(texture);
    }

気を付けて欲しいのは、先ほども説明した通り
グレースケール化したと言っても戻り値の配列は1チャンネルじゃくて3チャンネルということ
動的に確保したTexture2Dは明示的に破棄しないとメモリを圧迫する原因になります。

配列を出力する関数

    private void save_bytes(byte[] bytes, string file_path)
    {
        Texture2D s_texture = new Texture2D(resWidth, resHeight, TextureFormat.RGB24, false);

        byte[] tmp_bytes = new byte[resWidth * resHeight * 3];
        for(int i = 0; i < resWidth*resHeight*3; i++)
        {
            tmp_bytes[i] = bytes[i];
        }

        Debug.Log(tmp_bytes.Length);

        s_texture.LoadRawTextureData(tmp_bytes);
        byte[] png_bytes = s_texture.EncodeToPNG();
        System.IO.File.WriteAllBytes(file_path, png_bytes);

        DestroyTexture2D(s_texture);
    }

わざわざ配列をコピーしているのは、CoTaskMemAllocで確保した配列の長さがUnity側で
参照できずLoadRawTextureData内部でエラーを吐いてしまうそのための策
ただ例外処理はされているのでやらなくても出力は普通にできる。

結果

Unityで撮ったスクリーンショットをOpenCVに渡してグレースケール化した画像
上手くいきました。

まとめ

実際作ってみるとわかるが、やはり結構な手間とある程度のC++の知識が必要となってくるので
ネイティブなOpenCVにはあるのにAssetにはない機能だったり、まだ対応していない機能が欲しかったりする場合
いち早く実装できる可能性はあるかもしれない。
それ以外は安全性から見ても普通にAssetを使った方が無難に思える。

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

【C#】指定された月から秒単位までで月初と月末を取得する

やること

DateTimeの年月の情報から、秒単位までの月初~月末を取得します。

やり方

月初はnew DateTime(年, 月, 1)で0時0分0秒として取得できます。
月末は月初に1月足して1ms引く(-1ms足す)ことで求められます。

private Tuple<DateTime, DateTime> getPeriod(DateTime targetMonth)
{
    var start = new DateTime(targetMonth.Year, targetMonth.Month, 1);
    var end = start.AddMonths(1).AddMilliseconds(-1.0);
    return new Tuple<DateTime, DateTime>(start, end);
}

参考にさせていただいた記事

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

Photon使ってUnityでカーゲームが作りたい

Unityでオンラインゲーム作れるようになりたい!!!
UnityではPhoton Realtime SDKを使えば無料でできると聞いたので、制作過程を記します。使用したUnityのバージョンは2018.3.6です。

1. photonアカウントとアプリの準備

まずはphotonでアカウントを作成します。
新しいアプリを作成するを選んでください。
スクリーンショット 2019-06-15 18.50.20.png

こんな感じで今回作るゲームの情報を入れておきます。今回使うのはPhoton Realtimeです。
スクリーンショット 2019-06-15 18.55.33.png

作成するを押して、以下の画面になります。詳細を確認しましょう
スクリーンショット 2019-06-15 18.55.48.png

このアプリケーションIDはあとで使うので、コピーしておきましょう!
スクリーンショット 2019-06-15 18.58.57.png

2. Unity側の準備

Unityでオンライン化したいプロジェクトに対し、AssetStoreからPhoton Unity Networking Classic-FreeをImportします。Photonは似たようなAseetが沢山あるので注意してください。
スクリーンショット 2019-06-22 18.19.39.png

Unityのプロジェクトをphotonと繋げるところからやっていきましょう。

Importするとすぐ、AppIDを打ち込む画面が現れます。先ほどコピーしたアプリケーションIDをペーストしてください。
スクリーンショット 2019-06-15 18.59.17.png

入れ損ねた場合や間違えた場合はWindow > Photon Unity NetWorking > HighLight Server Setting から、PhotonServerSettingのInspectorから編集できます。
スクリーンショット 2019-06-20 12.24.03.png
さっきPhotonのマイページで作ったアプリケーションIDをAppidに入力してください。下の画像の青いところです。日本なのでRegionをJpにしておきます。Auto-join Lobbyにチェックをつけて、ゲーム開始時に自動でロビーに入るようにします。
スクリーンショット 2019-06-15 19.00.22.png

Photonに接続するスクリプトを書きます。
Hierarchy Viewに空のオブジェクト(PhotonManager)を作って、下のスクリプト(PhotonManager.cs)をアタッチしてください。

PhotonManager.cs
using UnityEngine;
using System.Collections;

public class PhotonManager : MonoBehaviour {

    void Start() {
        // Photonに接続する(引数でゲームのバージョンを指定できる)
        PhotonNetwork.ConnectUsingSettings(null);
    }

    // ロビーに入ると自動的に呼ばれる
    void OnJoinedLobby() {
        Debug.Log("ロビーに入りました");

        // ルームに入室する
        PhotonNetwork.JoinRandomRoom();
    }

    // ルームに入室すると自動的に呼ばれる
    void OnJoinedRoom() {
        Debug.Log("ルームへ入室しました");
    }

    // ルームの入室に失敗すると自動的に呼ばれる
    void OnPhotonRandomJoinFailed() {
        Debug.Log("ルームの入室に失敗しました");

        // ルームがないと入室に失敗するため、その時は自分で作る
        // 引数でルーム名を指定できる
        PhotonNetwork.CreateRoom("myRoomName");
    }
}

実行してConsoleを確認してみましょう。こんな感じになっていたら成功です!
スクリーンショット 2019-06-22 17.58.35.png

ちなみにphotonに繋げないWi-fiもあるみたいです。ポート番号が解放されていないとそうなるみたいです。Wi-fiを変えたらうまくいくということがありました。

3. プレイヤーを生成する

今のままでは1人プレイしかできないので、各プレイヤーが操作できる車を生成できるようにします。とりあえず2人プレイできるようにしましょう。オブジェクトの位置や角度を同期させるには、PhotonNetworkを用います。

接続中の人数

int PhotonNetwork.countOfPlayers

現在このアプリケーションをプレイしているユーザー数(マスターサーバーで5秒間隔で取得可能)を取得できます。(by Photon Network Class Reference)

ネットワーク越しのPrefabの生成

static GameObject PhotonNetwork.Instantiate(string prefabName, Vector3 position, Quaternion rotation, int group)

ネットワーク越しにプレハブからインスタンス作成します。このプレハブは"Resources"フォルダー直下に配置されている必要があります。Resourcesフォルダーのプレハブを使う代わりに、手動でInstantiateしてPhotonViewを割り当てることもできます。(by Photon Network Class Reference)

PhotonManagerのOnJoinedRoomの中を書き加えていきます。

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

public class PhotonManager : MonoBehaviour
{
    void Start()
    {
        // Photonに接続する(引数でゲームのバージョンを指定できる)
        PhotonNetwork.ConnectUsingSettings(null);
    }

    // ロビーに入ると呼ばれる
    void OnJoinedLobby()
    {
        Debug.Log("ロビーに入りました。");

        // ルームに入室する
        PhotonNetwork.JoinRandomRoom();
    }

    // ルームに入室すると呼ばれる
    void OnJoinedRoom()
    {
        Debug.Log("ルームへ"+ PhotonNetwork.countOfPlayers +"人入室しました。");
        //CarPrefabを生成する
        transform.rotation = Quaternion.Euler(0, 180, 0); //Carの向きが逆になってになってしまう問題を解決

        if (PhotonNetwork.countOfPlayers < 2)
        { 
            transform.position = new Vector3(66, 0, 0);
            GameObject car1 = PhotonNetwork.Instantiate("CarPrefab", transform.position, transform.rotation, 0);
            car1.name = "car1";
        }
        if (PhotonNetwork.countOfPlayers == 2)
        {
            transform.position = new Vector3(60, 0, 0);
            Debug.Log("2台目生成できたよ");
            GameObject car2 = PhotonNetwork.Instantiate("CarPrefab", transform.position, transform.rotation, 0);
        }

    }

    // ルームの入室に失敗すると呼ばれる
    void OnPhotonRandomJoinFailed()
    {
        Debug.Log("ルームの入室に失敗しました。");

        // ルームがないと入室に失敗するため、その時は自分で作る
        // 引数でルーム名を指定できる
        PhotonNetwork.CreateRoom("myRoomName");
    }

}

Resourcesフォルダ

PhotonはResourcesというフォルダからPrefabを生成するというシステムらしいです。

Carの名前をCarPrefab、
Prefabsフォルダの名前をResourcesに変えるか、新たにResourcesというフォルダを作るかしましょう。

Photon Viewコンポーネント

同期するオブジェクトにつけるコンポーネントです。
CarPrefabのInspectorを開き、Photon ViewPhoton Transform Viewをつけます。PositionとRotationにチェックを入れましょう。Photon Transform ViewをPhoton ViewのObserved Componentsにドラッグ&ドロップします。

このようにクライアント間で通信を行いたい際にオブジェクトにPhotonViewコンポーネントを追加し、Obaserved(監視対象)を指定すると、そのオブジェクトが同期されるようになります。
CarPrefabのInspevtorはこんなかんじになります。
スクリーンショット 2019-06-22 21.33.50.png

逆に、同期したくないオブジェクトはPhotonNetwork越しに生成すると厄介なことになります。
カメラとか、、、、、、、、、むやみに同期させない方が良いということを学びました。ネットワーク負荷の軽減のためにも、同期するオブジェクトは必要最低限にすることを心がけましょう。

CarPrefabの子オブジェクトのMainCameraを消してください。カメラはあとでプレイヤー別になるようにしますが、今ついていると2人以上になったとき同期しておかしなことになります。適当な位置にに固定カメラを設置しておきましょう。
CarPrefabの準備が済んだら、CarPrefabをResourcesにいれてPrefab化してください。Hierarchyに残ったCarPrefabは消しちゃって大丈夫です。

スクリーンショット 2019-06-22 21.33.34.png

実行して、Scene Viewにcar1が生成されたのを確認できたら成功です!
ちゃんと2台生成したCarが同期しているか、ビルドして確認しましょう。

☆. 同期の確認方法

Photonは沢山Buildします。Buildしないと1つのパソコンで2人分の動作確認ができないからです。
手始めに今どんな挙動を見せるのか確認してみます。PC向けAppをBuildしてください。
動作確認をする際は、まずAppを起動し、次にUnityの再生ボタンを押すようにしてください。
スクリーンショット 2019-06-22 21.52.01.png
Windowedにチェックを入れておくと楽です。チェックを入れないと全画面で起動します。
スクリーンショット 2019-06-22 21.54.43.png

2台出たら成功です。
しかしこのままでは片方が移動するともう片方も移動してしまっています。次の章で直していきましょう。

4. プレイヤーをユーザーが各自で操作できるようにする

bool PhotonView.isMine

PhotonViewの所有者が「自分」で、このクライアントからコントロール可能の場合trueです。
PUNは所有権という概念があります。それぞれのPhotonViewをコントロールしたり破棄できる主体のことです。 所有者がローカルPhotonPlayerの場合trueです。 シーンのPhotonViewで、Master client上で動作しているならtrueです。(by Photon Network Class Reference)

プレイヤーを操作するスクリプトの部分に、isMineの条件を足します。プレイヤーを操作しているCarUserControl.csを書き換えていきましょう。

CarUserControl.cs
using System;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
using UnityStandardAssets.Vehicles.Car;

public class CarUserControl : MonoBehaviour
{
    private PhotonView myPhotonView;
    private CarController m_Car;
    float h; //horizontal
    float v; //Vertical

    private void Start()
    {
        this.myPhotonView = GetComponent<PhotonView>();
        m_Car = GetComponent<CarController>();
    }
    private void FixedUpdate()
    {

        if (myPhotonView.isMine) //自分のPhotonViewだったら
        {
            if (RaceManager.instance.isRacing == true)
            {
                h = CrossPlatformInputManager.GetAxis("Horizontal");
                v = CrossPlatformInputManager.GetAxis("Vertical");
            }

            m_Car.Move(h, v, v, 0);
        }
    }
}

Builidして、それぞれのCarを動かすことができていれば成功です!

5. 各プレイヤーを追従するカメラを作る

失敗例

その①
PhotonViewのついたCameraを子オブジェクトに持つCarPrefabをPhotonNetworkから生成した場合
→ カメラが同期してちゃんと自分の車につかない

その②
PhotonViewのないCameraを子オブジェクトに持つCarPrefabをPhotonNetworkから生成した場合
→ 操作とカメラが逆転する謎現象が起きた

その③
PhotonViewのついたCameraをPhotonNetworkから生成し、CarPrefabを追従するようにした場合
→ これもカメラが同期して自分の車につかない

全部Buildしないと気づかない失敗です。同期したくないオブジェクトはPhotonNetwork越しに生成すると厄介なことになります。ネットワーク負荷の軽減のためにも、同期するオブジェクトは必要最低限にすることを心がけましょう。

成功例

PhotonViewのついてないCameraをPhotonNetworkを介さずに生成した場合

まずはCameraをCameraPrefabに改名し、CameraScriptをつけてください。CarUserControl.csとCameraScript.csに書き加えていきます。
CarUserControlのAwakeでCameraをCarの子オブジェクトとしてPhotonNetworkを介さずに生成します。CameraPrefabをResourcesに入れてください。

CarUserControl.cs
using System;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
using UnityStandardAssets.Vehicles.Car;

public class CarUserControl : Photon.PunBehaviour
{
    private PhotonView myPhotonView;
    private CarController m_Car;
    float h; //horizontal
    float v; //Vertical

    private void Awake()
    {
        this.myPhotonView = GetComponent<PhotonView>();
        m_Car = GetComponent<CarController>();

        if (myPhotonView.isMine) //Carが自分の車である場合
        {
            GameObject camPrefab = (GameObject)Resources.Load("CameraPrefab"); //ResourcesフォルダからCameraPrefabをとってくる
            Vector3 CameraPosition = new Vector3(0, 3, 5); //カメラの相対座標
            GameObject cam = Instantiate(camPrefab, transform.position + CameraPosition, transform.rotation); //CameraPrefabを生成する
            cam.transform.parent = gameObject.transform; //Cameraの親オブジェクトはCarとする
        }
    }
    private void FixedUpdate()
    {

        if (myPhotonView.isMine)
        {
            if (RaceManager.instance.isRacing == true)
            {
                h = CrossPlatformInputManager.GetAxis("Horizontal");
                v = CrossPlatformInputManager.GetAxis("Vertical");
            }

            m_Car.Move(h, v, v, 0f);
        }
    }
}

再生してカメラがついてくることを確認したら、Buildして動作確認しましょう。
完成品の動画

順位つけたりもしたいですね。

お疲れ様でした!

参考資料

基本説明 - Photon Unity Networking
UnityでPUNを使ったオンラインマルチプレイの実装-準備編-
Unity猫本のサンプルゲームをPhotonでオンライン対戦ゲーム化してみた
Unityカーレース

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

PlayerPrefsにクラスを丸ごと保存/セーブしたい!

PlayerPrefsにクラスを丸ごと保存/セーブしたい!

というわけで作りました。
ジェネリック<T>で型を指定して保存できるクラス、「PlayerPrefsUtil

ダウンロードはこちら(.unitypackage)
デモが置いてあるgithubのリポジトリはこちら← (特に見る意味ない)

それにしても、なんで保存できないんでしょうね。

使い方

①保存のメソッド

PlayerPrefsUtil.SetObj<T>(string key,T obj,[bool compression = false]);

・Tでクラスの型を指定して
・引数keyに文字列を入れて
・T objに指定した型のデータを入れます
・オプション引数のcompressionにtrueを代入することで、内部で文字列の圧縮をかけます

②ロードのメソッド

T PlayerPrefsUtil.GetObj<T>(string key);
・キーに保存されているデータをTでクラスの型を指定して返します

注意ですが、PlayerPrefsのStringに保存しているので、HasKeyなどPlayerPrefsの機能がそのまま使えますが
PlayerPrefsと同じようにPlayerPrefs.Save();を走らせないと保存されません(レジストリに書き込まれない)。

使用例

まず、下のような名前と体力を持ったクラスがあるとします

public class Enemy
{
  //名前
  public string Name;

  //体力
  public int HP;

  public Enemy(string name, int hp)
  {
    Name = name;
    HP = hp;
  }
}

これを保存/ロードする場合

Enemy goblin = new Enemy("ゴブリン",10);

//保存
PlayerPrefsUitl.SetObj<Enemy>("GOBLIN_SAVE_KEY",goblin);

//ロード
goblin = PlayerPrefsUtil.GetObj<Enemy>("GOBLIN_SAVE_KEY");

これで変数goblinの保存とロードができます。

内部の処理など

圧縮(compression)はどういう処理をしているか

保存するときに圧縮するかしないかを選べますが、何をするかというと

①XMLSerializerでデータをXML形式のStringに変換
②文字列をbyte[]に変換しSystem.IO.Compressionの機能を使用して圧縮 (ここが圧縮)
③圧縮された文字列をBase64形式に変換して保存

という感じで圧縮をかけています。
なので、外部から簡単に読まれなくなるという多少の利点もあります。

なぜXMLSerializerを使用したのか

先ほどの工程でXMLSerializerが登場しましたが、なぜJsonUtilityではないのかというと
結論から言うと 複雑なList構造やDictionaryを保存でき、かつUnityEngineのクラスを保存できる という点です。

クラスを丸ごと保存できる機能だと、候補が3つあり
今回使った「XMLSerializer」、Unityでよく使われる「JsonUtility」、バイナリに変換できる「BinaryFormatter」
それぞれ特徴があるのですが、今回いろいろ試して分かったことがありまして、
JsonUtilityは複雑なList構造に弱く、BinaryFormatterはUnityEngineのクラス、例えばVec2、Vec3クラスなどを保存できなかったんです (シリアライザがない?らしい) 。
その両方が可能だったのが、XMLSerializerだった。という訳です。

なので、外部ツールとの互換性やデータの容量が気にならなければXMLSerializerを使用して保存するのがいいと思います。

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

3【*】Four arithmetic by Machine Learning

積英語.png
Qiita民が大好きなPythonで積のパーセプトロンを作成しました。
I created a perceptron of multiplication in Python which Qiita people love.

# coding=utf-8
import numpy as np
import matplotlib.pyplot as plt

#Initial value
#number of learning
N = 500
#layer
layer = [2, 2, 1]
#bias
#bias = [0.0, 0.0]
#learning rate
η = [1.0, 1.0]
#number of middle layers
H = len(η) - 1
#teacher value
mul = [None for n in range(N)]
#Function output value
f_out = [[None for h in range(H + 1)] for n in range(N)]
#Function input value
f_in = [[None for h in range(H + 1)] for n in range(N)]
#weight
w = [[None for h in range(H + 1)] for n in range(N + 1)]
for h in range(H + 1):   
    w[0][h] = np.random.uniform(-1, 1, (layer[h], layer[h + 1]))
print(w[0])
#squared error
dE = [None for n in range(N)]
#∂E/∂IN
δ = [[None for h in range(H + 1)] for n in range(N)]

#Learning
for n in range(N):

    #Input value
    f_out[n][0] = np.random.uniform(-1, 1, (layer[0]))

    #teacher value
    mul[n] = f_out[n][0][0] * f_out[n][0][1]

    #order propagation
    f_in[n][0] = np.dot(f_out[n][0], w[n][0])
    f_out[n][1] = f_in[n][0] * f_in[n][0]
    #f_out[n][1] = np.array([f_in[n][0][0] * f_in[n][0][0], -f_in[n][0][1] * f_in[n][0][1]])

    #output value
    f_in[n][1] = np.dot(f_out[n][1], w[n][1])

    #squared error
    dE[n] = f_in[n][1] - mul[n]#value after squared error differentiation due to omission of calculation

    #back propagation
    δ[n][1] = 1.0 * dE[n]
    δ[n][0] = 2.0 * f_in[n][0] * np.dot(w[n][1], δ[n][1])
    #δ[n][0] = np.array([2.0 * f_in[n][0][0], -2.0 * f_in[n][0][1]]) * np.dot(w[n][1], δ[n][1])       
    for h in range(H + 1):
        w[n + 1][h] = np.array(w[n][h]) - η[h] * np.array([f_out[n][h][l] * δ[n][h] for l in range(layer[h])])

#Output
#Weight
print(w[N])
#Figure
#weight
#area height
py = np.amax(layer)
#area width
px = (H + 1) * 2
#area size
plt.figure(figsize = (16, 9))
#horizontal axis
x = np.arange(0, N + 1, 1)
#drawing
for h in range(H + 1):
    for l in range(layer[h]):
        #area matrix
        plt.subplot(py, px, px * l + h * 2 + 1)
        for m in range(layer[h + 1]):                       
            #line
            plt.plot(x, np.array([w[n][h][l, m] for n in range(N + 1)]), label = "w[" + str(h) + "][" + str(l) + "," + str(m) + "]")        
        #grid line
        plt.grid(True)
        #legend
        plt.legend(bbox_to_anchor = (1, 1), loc = 'upper left', borderaxespad = 0, fontsize = 10)

#save
plt.savefig('graph_mul.png') 
#show
plt.show()

重みの考え方を示します。 \\
I\ indicate\ the\ concept\ of\ weight. \\
 \\
w[0]=
\begin{pmatrix}
△ & □\\
▲ & ■
\end{pmatrix}
,w[1]=
\begin{pmatrix}
○ & ●
\end{pmatrix}\\
 \\
入力値とw[0]の積 \\
multiplication\ of\ input\ value\ and\ w[0]\\
w[0]
\begin{pmatrix}
a\\
b
\end{pmatrix}
=
\begin{pmatrix}
△ & □\\
▲ & ■
\end{pmatrix}
\begin{pmatrix}
a\\
b
\end{pmatrix}
=
\begin{pmatrix}
△a + □b\\
▲a + ■b
\end{pmatrix}\\
 \\
第1層に入力\\
enter\ in\ the\ first\ layer\\
\begin{pmatrix}
(△a + □b)^2\\
(▲a + ■b)^2
\end{pmatrix}\\
 \\
第1層出力とw[1]の積=出力値\\
product\ of\ first\ layer\ output\ and\ w[1]\ =\ output\ value\\
\begin{align}
&
w[1]
\begin{pmatrix}
(△a + □b)^2\\
(▲a + ■b)^2
\end{pmatrix}\\
=&
\begin{pmatrix}
○ & ●
\end{pmatrix}
\begin{pmatrix}
(a△ + b□)^2\\
(a▲ + b■)^2
\end{pmatrix}\\
=&〇(△a + □b)^2 + ●(▲a + ■b)^2\\
=&\quad (○△^2 + ●▲^2)a^2\\
&+(○□^2 + ●■^2)b^2\\\
&+(2〇△□+2●▲■)ab
\end{align}\\
 \\
なんだか、ややこしくなってしまってますが\\
とりあえずは、下記条件を満たせば積abを出力することができます。\\
It's\ getting\ confusing\\
For\ the\ moment,\ the\ multiplication\ ab\ can\ be\ output\ if\ the\ following\ conditions\ are\ satisfied.\\
\left\{
\begin{array}{l}
○△^2 + ●▲^2=0 \\
○□^2 + ●■^2=0 \\
2〇△□+2●▲■=1
\end{array}
\right.

毎度恒例、初期値を乱数(-1.0~1.0)の間で決めてから学習を繰り返すと目標値に収束するか試してみました。
Every time, after deciding the initial value between random numbers (-1.0~1.0),
I tried to repeat the learning to converge to the target value.

目標値\\
Target\ value\\
w[0]=
\begin{pmatrix}
△ & □\\
▲ & ■
\end{pmatrix}
,w[1]=
\begin{pmatrix}
○ & ●
\end{pmatrix}\\

\left\{
\begin{array}{l}
○△^2 + ●▲^2=0 \\
○□^2 + ●■^2=0 \\
2〇△□+2●▲■=1
\end{array}
\right.
\\
初期値\\
Initial\ value\\
w[0]=
\begin{pmatrix}
-0.06001642 & -0.85252436\\
-0.80560397 & 0.14216594
\end{pmatrix}
w[1]=
\begin{pmatrix}
-0.01316071\\
0.03798114
\end{pmatrix}\\
\left\{
\begin{array}{l}
○△^2 + ●▲^2=0.0246022702 \\
○□^2 + ●■^2=-0.0087975322 \\
2〇△□+2●▲■=-0.0100466654
\end{array}
\right.
\\
計算値\\
Calculated\ value\\
w[0]=
\begin{pmatrix}
-0.65548785 & -0.66341526\\
-0.62435684 & 0.63186139
\end{pmatrix}
w[1]=
\begin{pmatrix}
0.61080948\\
-0.59638741
\end{pmatrix}\\
\left\{
\begin{array}{l}
○△^2 + ●▲^2=0.0299584277 \\
○□^2 + ●■^2=0.0307223832 \\
2〇△□+2●▲■=1.0017919987
\end{array}
\right.

graph_mul.png

成功しましたが課題を抱えています。
It was successful but with challenges.

1,収束する時と収束しない時がある
1,There are cases when it converges and when it doesn't converge

初期値\\
Initial\ value\\
w[0]=
\begin{pmatrix}
0.96898039 & -0.13805777\\
0.5250381 & -0.457846
\end{pmatrix}
w[1]=
\begin{pmatrix}
0.17181768\\
0.27522847
\end{pmatrix}\\

計算値\\
Calculated\ value\\
w[0]=
\begin{pmatrix}
0.37373305 & -0.38455151\\
0.53163593 & -0.54773647
\end{pmatrix}
w[1]=
\begin{pmatrix}
0.34178043\\
0.33940928
\end{pmatrix}\\

graph_mul失敗.png

上記のように値が収束しない場合がときたまあります。
本当はプログラムを組んで収束しない時の統計を出すべきなんでしょうが
そこまでの気力がありませんでした。
Occasionally, the values don't converge as described above.
Properly, it should be put out the statistics when doesn't converge by programming
I didn't have the energy to get it.

2,収束したらしたでいつも同じぐらいの値で収束する
どういう訳か、例えばw[0][0,0]=±0.6...ぐらいの値に落ち着く事がほとんどです。
2,It always converges with the similar value
For some reason, for example, it is almost always settled to the value of w [0][0,0] = ±0.6...

一見、簡単そうなパーセプトロンでも謎が多いです。
At first glance, even the seemingly easy perceptron has many mystery.

NEXT
【/】

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

Pythonでファイル名に現在時刻を付ける【初心者向け】

Pythonでログ出力ファイルを作成するときに、ファイル名に現在時刻を入れたときのメモです。
あわせて、csv出力に関するメモも記載しておきます。

現在日時の取得

datetime.datetime.now()で現在日時を取得できます。

print(datetime.datetime.now())

>>2019-06-22 09:44:54.569669

サンプル

ファイル名に現在時刻を入れて作成するサンプルです。
datetime.strftime()で日付フォーマット指定します。

 now = datetime.datetime.now()
 filename = './output/log_' + now.strftime('%Y%m%d_%H%M%S') + '.csv'
 f = open(filename, 'w')
 writer = csv.writer(f, lineterminator='\n') 

実行すると、log_20190622_094828.csvのようなファイル名で作成されます。

日付フォーマットの指定

ついでなので、C#でのDateTimeのフォーマット指定と比較します。

フォーマット pyhton C#
西暦(4桁) %Y yyyy
西暦(2桁) %y yy
月(2桁) %m MM
日(2桁) %d dd
時(2桁) %H HH
分(2桁) %M mm
秒(2桁) %S ss

月と分の大文字、小文字が逆なので、C#からPythonに移行する人はちょと注意が必要ですね。

csvファイル出力

writer.writerow(リスト配列)を使います。

#リスト配列はそのまま、指定する
a = [1,2,3]
writer.writerow(a)

>>1,2,3

#Numpy配列は、tolist()でリスト配列に変換してから
b = np.array([10, 20, 30])
writer.writerow(b.tolist())

>>10,20,30

#文字列などは、[]でリスト配列にしてから
writer.writerow(["日付",now])
writer.writerow(["日付",now.strftime('%Y/%m/%d'),now.strftime('%H:%M:%S')])

>>日付,2019-06-22 11:38:23.506582
>>日付,2019/06/22,11:38:23

datetime形式をそのまま出力したときは、エクセルで開いたときにキレイじゃないです。
(エクセルのバージョンにもよるかも知れないですが。)
csv日付.JPG

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

React + TypeScript + Webpack を既存プロジェクト(ASP.NET MVC5)に導入してみた

はじめに

 「JQueryもいいけれど、そろそろReactを使ってみたいな」
 そう思って挑戦してみたところ、なかなかに難儀でしたので、それをまとめてみます。「React + TypeScript + Webpack 」をASP.NET MVC5に適用する内容になっています。

 次にような方に役立つのではないでしょうか。

  • JQueryは使ったことあるけれど、Reactを始めてみたい方
  • 1から作るのはサンプルやcreate-react-app をやってはみたものの、既存プロジェクトへの適用の仕方がよく分からない方

各パッケージの役割を整理

 正直なところフロントエンドがここまで複雑化しているのとは思いませんでした。
 たくさん登場人物がいて、さらに誰が何を担当しているのが分かりにくいです…。そもそもなんでフロントエンドの話に、サーバーサイドである、Node.js が出てくんねん!って感じですよ。過渡期なのでしょうけど、ちょっと魔境すぎません?

色々な登場人物が出てくる流れ

 大まかにこういう流れだと理解しています。

1. React を使いたい

  • やることは React.jsをインポートする。JQuery導入と同じ。怖くない。

2. JSXが登場

  • これまでのJavaScriptはHTMLに紐づくものだったけど、WEBサイト機能が複雑になってきたし機能単位でまとめた方がいいよね。JavaScript主体のJSXで書く方が便利。
  • JSXは Babel でコンパイルしないと使えない。Babel 登場。
  • Babelを使うのに Node.jsが必要。Node.jsも登場。

3. TypeScriptが登場

  • JavaScriptを直接書くより、最近のプログラミング言語っぽく書けるTypeScriptを使った方がいいよね。JSXじゃなくてTSXにしよう。
  • TSXの導入に伴い Babel は必要なくなった。でも TSXファイルをコンパイルするのに Node.js が必要なので、こいつは残ります。

4. webpackが登場

  • 機能の拡大と共にJavaScriptファイルが沢山増えてしまった。沢山あると読み込みが遅くなる。そこでwebpackです。こいつは一括で取り込めるようにまとめたJavaScriptを作成できます。ついでに圧縮(miniファイル作成)もできるよ。
  • TypeScriptのコンパイルを実行したり(コンパイルそのものは ts-loaderが行う)、どこにまとめたJavaScriptを置くかも担当します。
  • Webpackを使うのにもやっぱりNode.jsが必要です。

5. Reactを使う

  • 4で出力した JavaScriptを読み込めば使用できます。

 要はブラウザのスクリプト言語としてはJavaScriptがデファクトスタンダード。でもJavaScriptは使いづらいから、それを何とかしようと色々している結果、今現在のような複雑なことになっているのだと思います。

前提

 既存プロジェクトとして、ここでは、ASP.NET MVC5とします。.NET Coreじゃなく.NET Framework 4.7.2 の方にしてみます。(その方が既存プロジェクトっぽいし、.NET Coreの方はテンプレートあるしね……)
 IDEは Visual Studio 2019 Community で確認していますが、Visual Studio 2017でも大丈夫でしょう。

テンプレート通りに作成(既存プロジェクトのつもり)

ASPNet.PNG
MVC5.PNG
開始.png

npmを導入

npmとは

 各種パッケージを管理するものです。Visual Studioのデフォルトとしてある Nugetと同じような機能です。
 同じような機能を二つ使うのはややこしいですが、Nugetに無いので仕方がありません。フロントエンドのパッケージを管理するのが npm、サーバーサイドのパッケージを管理するのが、Nugetと分けるのがよさそうです。
(※デフォルトでは NugetでJQueryやBootstrapを管理しているので、後々削除する必要があります)

拡張機能のインストール

 Visual Studioではデフォルトでは npm を使えないので、拡張機能の管理を呼び出します。
拡張機能開始.png

 ここでは、NPM Task Runner と Package Installerを入れておきます。
NPM関連.png

 WebPack Task Runner もあとで使うので入れておきます。
webpackTaskRunner.png

 一度 Visual Studioを閉じてインストールを実行します。
拡張機能インストール確認png.png

npmでReactをインストール

 ソリューション エクスプローラー で右クリックをして、Quick Install Packagesを選択します。
QuickInstallPackage.png

 npm を選び、react のバージョンを16.8.6(現時点の最新版)を選択し、Installを実行します。同じように react-domもインストールします。
npmInstall.png

 すると、プロジェクトの直下に packeage.json が作られているのが確認できます。これがパッケージを管理するファイルになります。プロジェクト管理にはなっていませんが、package-lock.jsonファイルと node_modulesフォルダも作成されていることが確認できます。node_modulesにパッケージの実体が存在します。

NPMInstall後.PNG
(参考)
package-lock.jsonについて知りたくても聞けなかったこと

package.jsonの変更

 このまま必要なパッケージをnpmコマンドでインストールしていってもいいのですが、直接package.jsonを書き換えた方が早いのでそうします。

packagejson書き換え.PNG

修正後のpackage.json
{
  "name": "myproject",
  "version": "1.0.0",
  "scripts": {
    "build": "webpack",
    "watch": "webpack -w"
  },
  "devDependencies": {
    "ts-loader": "^5.4.3",
    "typescript": "^3.4.4",
    "webpack": "^4.30.0",
    "webpack-cli": "^3.3.1"
  },
  "dependencies": {
    "@types/react": "^16.8.14",
    "@types/react-dom": "^16.8.4",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  },
  "private": true
}

 package.jsonの中身の詳細については下記ページが勉強になります。

変更後のpackage.jsonのパッケージをインストール

 Visual Studio のメニューの 表示 -> その他のウィンドウ -> タスクランナー エクスプローラーを表示します。
taskRunner.png

 Defaultsでインストールを実行します。
taskRunner2.png
 インストール終了。
taskRunner3.png

 インストールが成功すると、node_modulesフォルダ内のパッケージが増えていることが確認できます。
npmのinstall後のnodemodule.PNG

(注釈)ここまで「あれ?」と思った方もいると思います。package.jsonを作成して、直接書き換えることが出来るならば「Package Installer」は必要なさそうと思うかもしれません。確かにそうなのですが、今後追加でインストールしたいものが出来た時に便利です。

TypeScriptの設定

 新しい項目の追加で、tsconfig.json を追加します。
typescriptの設定ファイル.png

 そして、tsconfig.json を次のように変更します。

{
  "compilerOptions": {
    "sourceMap": true,
    "target": "es5",
    "module": "es2015",

    "jsx": "react",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "lib": [
      "es2019",
      "dom"
    ]
  }
}

 tsconfig.json の中身の詳細については package.json と同じく下記ページを参考にしています。

webpackの設定

 新しい項目の追加で、webpack.config.js を追加します。

webpackの設定ファイル.png

 そして、webpack.config.js を次のように変更します。
 ここでは「entry」に設定されている "./src/main.tsx"のファイルを ./dist/main.js として変換すると設定しています。

module.exports = {
    // モード値を production に設定すると最適化された状態で、
    // development に設定するとソースマップ有効でJSファイルが出力される
    mode: "development",

    entry: "./src/main.tsx",

    // ファイルの出力設定
    output: {
        //  出力ファイルのディレクトリ名
        path: `${__dirname}/dist`,
        // 出力ファイル名
        filename: "main.js"
    },

    module: {
        rules: [
            {
                // 拡張子 .ts もしくは .tsx の場合
                test: /\.tsx?$/,
                // TypeScript をコンパイルする
                use: "ts-loader"
            }
        ]
    },
    // import 文で .ts ファイルを解決するため
    resolve: {
        extensions: [".ts", ".tsx", ".js", ".json"]
    }
};

 webpack.config.js の中身についてもこちらを参考に(というかコピー)しています。

TSXファイルの追加と変更

 新しい項目の追加で、TypeScript JSXファイルを追加します。場所は上記 webpack.config.js で指定した./src/main.tsxです。

tsxの作成.png

 内容は create-react-app のHello Worldみたくしてみます。

main.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

 同じくTypeScript JSXファイルを./src/App.tsx に追加します。

App.tsx
import React from 'react';

const App: React.FC = () => {
    return (
        <div className="App">
            <header className="App-header">
                <p>
                    Hello, World!!
                </p>
                <a
                    className="App-link"
                    href="https://reactjs.org"
                    target="_blank"
                    rel="noopener noreferrer"
                >
                    Learn React
                </a>
            </header>
        </div>
    );
}

export default App;

WebPack Task Runnerでコンパイル

 WebPack Task Runnerを使って、TypeScriptのコンパイルを実行します。
webpackTaskRunner実行.PNG

実行結果
webpackTaskRunner実行結果.PNG

 コンパイルが無事に実行されると ./dist/main.js が作成されます。
distが出来る.PNG

 以後、TSX(TypeSciprt) が変更されるたびにコンパイルするのは面倒なので、ビルドする前にWebPack Task Runnerでコンパイルが走るようにバインドしておきます。
バインド設定.png

main.jsをHTML側で呼び出す

 main.jsを使うために、適当な index.cshtml を変更します。

index.cshtmlを変更
@{
    ViewBag.Title = "Home Page";
}

<div id="root"></div>
<script src="~/dist/main.js"></script>

ビルドして動作確認

 ビルドして、Hello World! が確認できれば無事に React が使えていることになります。

動いた.PNG

おわりに

 登場人物がたくさん出てきて混乱してしまいますが、一つ一つ誰が何をやっているかを理解していければ大丈夫だと思います。色々やってますけど、最終的にはJavaScriptになるというだけです。
 逆に理解しないまま「とりあえずサンプルあるし~」と進めていくと、上手く行っている時はいいのですが、トラブル発生時にどこに問題が発生しているのか分からないことになります。というか、なった。

参考資料

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

[Unity]SteamVR Plugin v2.2でviveのタッチパッドを使ってみる

steamVR Pluginを触り始めたとき何が何だか分からなかったので書きます。
拙い点もあると思いますがよろしくお願いします。
参考
SteamVR Unity Plugin v2.2.0でのインプット

入力について

steamVR Plugin 2.2では元から設定されている入力があったり、なかったりします。
例としては、
InteractUI : グリップの部分
Teleport : タッチパッドのボタン
GrabPinch : グリップの部分
GrabGrip : 側面のボタン
などがすでに設定されています。
sgdhjgjdhdghjf.png
 
sfgjsfgjsfjg.png
(SteamVR_Input_Actions.csの中身)

設定の追加

タッチパッドの位置の入力は設定されていないので追加する必要があります。

追加するには2工程ほどあり、
①SteamVR Inputに追加
②入力バインドで追加
をしなければいけません。

SteamVR Input

UnityのWindow → SteamVR Input で開けます。
image.png
下の画像のように ActionsのInで+を押して新たな入力を追加します。
名前はTrackPadとしています。
この入力はx軸とy軸で与えられるためTypeをvector2にする必要があります。
image.png
追加出来たら左下のSave and generate を押します。

入力バインド

SteamVR Inputで作成できたので入力バインドで設定をします。
SteamVR Inputウィンドウの右下にあるOpen binding UI を押します。
下の画像のようなウィンドウが表示されるので 編集 を押します。
image.png
下の画像のように表示されたらトラックパッドのところで+を押します。
image.png
以下のように設定していきます。
image.png
image.png
最後に 個人用バインドを保存 を押して追加完了です。

入力テスト

pad_test1.csというスクリプトを作成してテストしてみます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Valve.VR;

public class pad_test1 : MonoBehaviour {
    private SteamVR_Action_Vector2 TrackPad = SteamVR_Actions.default_TrackPad;

    private Vector2 pos;
    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {
            pos = TrackPad.GetLastAxis(SteamVR_Input_Sources.RightHand);
            Debug.Log(pos.x +" " + pos.y);
    }
}

結果
image.png
x座標とy座標が表示されました。

次に適当にキューブを動かしてみます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Valve.VR;

public class pad_test1 : MonoBehaviour {
    private SteamVR_Action_Vector2 TrackPad = SteamVR_Actions.default_TrackPad;

    private Vector2 pos;
    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {
        pos = TrackPad.GetLastAxis(SteamVR_Input_Sources.RightHand);
        transform.localPosition = new Vector3( pos.x * 0.1f,0, pos.y * 0.1f);
    }
}

ヒエラルキーで右手にキューブを追加してスクリプトを付けます。
image.png
パッドをぐりぐり動かしてみるとキューブも動きました。
image.png

極座標表示にする

極座標についての簡単な図
image.png
まず極座標にして何が嬉しいか を説明します。
今回の場合で言うと、
・パッドを3分割してそれぞれの処理をしたい場合、θを使えば簡単になる
  if分の条件で0<θ<120°のようにすることができます。
  x,yで表した場合は少し難しいことになります。
・rを使うことで中心からどれだけ離れているかがわかりやすい
  x,yで表した場合は縦か横の場合にしかわかりませんね。
  中心から0.7以上離れた場合などを考えたい場合などに便利です。

コードは以下のようになります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Valve.VR;

public class pad_test1 : MonoBehaviour {
    private SteamVR_Action_Vector2 TrackPad = SteamVR_Actions.default_TrackPad;

    private Vector2 pos;
    float r,sita;
    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {
        pos = TrackPad.GetLastAxis(SteamVR_Input_Sources.RightHand);
        transform.localPosition = new Vector3( pos.x * 0.1f,0, pos.y * 0.1f);
        r = Mathf.Sqrt(pos.x* pos.x + pos.y* pos.y);
        sita = Mathf.Atan2(pos.y, pos.x) / Mathf.PI * 180;

        Debug.Log(r +" "+ sita );
    }
}

(クソダサ変数 sita (θ))
結果
image.png

ちゃんと表示されました。
トラックパッドの下側だと角度がマイナスになってしまいます。
気になる人は初期値として+180をしましょう。

式は以下の通りです。
image.png
プログラムではAtanが2種類あるので気をつけましょう。
角度は式のまんまだとRad表示になるのでDegに変換するために、3.14で割って180で掛けています。

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