20190203のUnityに関する記事は5件です。

Unity+Firebase RealTimeDatabase Editor上で環境によっては突然接続できなくなる

経緯

コワーキングスペース的な所でドヤリングしながら、Unity+FirebaseRealtimeDatabaseでゲーム作ろうとしたら

「[Error] WebSocket: ws_0 - could not connect」
「[Error] WebSocket: ws_0 - WebSocketException during handshake
Firebase.Database.Internal.TubeSock.WebSocketException: unknown host: ****.firebaseio.com ---> System.Net.Sockets.SocketException: No route to host」

みたいに怒られてなんで?となったので記録しときます

結論

Realtime Database connection is flakey on desktop
https://github.com/firebase/quickstart-unity/issues/106#issuecomment-426835393

desktop上(Unity Editor)だとネット環境によっては上手く接続できない事があるみたいです。

対策は以下
*利用している環境を変える(違うwifiやネット環境を利用する)
*「Firebase/Plugins/FirebaseDatabase.dll」をEditor上で有効にする(但し動作が不安定)

再現環境

MacBook Pro (15-inch, 2017)
OS:10.13.6(17G5019) High Sierra
Unity: 2018.3.0f2
Scripting Runtime Version: .NET 4.x Equivalent

まずそもそも読み書き権限つけ忘れてないか確認

RealtimeDatabaseはルールを設定して読み書きの権限を設定できます。
うっかり

{
  "rules": {
    ".read": false,
    ".write": "auth != null && auth.isAdmin == true"
  }
}

なんてしてたら当然読み込みできません。(自分はうっかりしてたけど・・・)
ちなみにその場合はこういう警告がEditorのコンソールに出力されます。

[Warn] SyncTree: Listen at /******* failed: DatabaseError: Permission denied
UnityEngine.Debug:LogWarning(Object)

権限ちゃんとついてるのに何故かエラーになるんだけど・・・

で、うっかり権限を修正した後に冒頭のエラーに出くわして、なんでえええええっとなってたわけでした。
ネットの海を泳いで、結論のページにたどり着きました。

実際にやって見た所

1.ポケットwifiを持っていたので、コワーキングスペースのwifiからそちらを利用するように切り替えるとちゃんと応答返ってきてデータ取れました。
→お家に帰った後に有線、無線両方の環境で再度確認した所両方データ取れました。
 ピンポイントでハズレ引いたみたい・・・

2.コワーキングスペースのwifiに戻して、「Firebase/Plugins/FirebaseDatabase.dll」をEditor上で有効にしたら同様にデータ取れました
 →Import時に特に設定を変更していない場合「Assets/Firebase/Plugins/FirebaseDatabase.dll」に対象のファイルがいます。

スクリーンショット 2019-02-03 19.51.27.png

但しissueに記載の通り動作は不安定で、時々UnityEditor毎落ちます

However, if you have to use it, you should expect the instability. This is why it is disabled for Editor by default.

↓↓↓Google 翻訳
しかし、あなたがそれを使わなければならないなら、あなたは不安定さを期待するべきです。エディターではデフォルトで無効になっているのはこのためです。

感想

もし、制作する側の立場の場合、こういう環境に依存するようなやつが一番めんどくさいだろうなとは思います。
そもそもFirestore使えと言われたら、ハイ、スイマセンって感じですが・・・

参考

https://stackoverflow.com/questions/50651865/unityfirebase-databaseerror-websocket-ws-0-could-not-connect
https://github.com/firebase/quickstart-unity/issues/106#issuecomment-426835393

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

Unityアセット AnySync を使ってネットワークを介したゲームオブジェクトの位置のシンクロを簡単に

はじめに

ネットワークゲームでは、ネットワーク上の各マシン上で共有されているゲームオブジェクトの位置が常にシンクロ(同期)していないと困りますが、それをきっちり作るのに意外と苦労しています、、、。最初はシンクロしているようでも、時間がたつと段々ズレてしまったりして。

そこで便利なのがAnySyncというUnity用アセットです(残念ながら有料。15ドル)

https://assetstore.unity.com/packages/tools/network/anysync-114580

これは、ネットワーク上のマシン間でゲームオブジェクトのPosition, Rotationなどを共有するためのアセットです。UNET、Photonなど幅広い種類のライブラリと一緒に使うことができます。ただ、使用の際には少しコーディングが必要なので、その方法について解説します。なお、今回はLAN内P2P(peer to peer)に限定してお話するのでご注意ください。

準備

UnityのネットワークライブラリはUNET(2018.4LTSを最後に提供終了予定)が
最も身近ですが、Izmさんのこちらの記事を参考にした結果、UNETと近い感覚で使えて動作が安定している Mirror という無料アセットを使うことにしました。

Unityプロジェクトを作ったら、MirrorとAnySyncをダウンロード、インポートしてください。

シンクロさせるゲームオブジェクトの設定

今回は、2つのPCで一方にHost,もう一方にClientを実行し、Host側でスペースキーを押すとBallというプレハブがインスタンス化され、それをHost側で動かすとClient側でも動くようにします。

image.png

まず、メニューからGameObject/3D Object/Sphereを選んでゲームオブジェクトを作り、Ballという名前を付けてください。

次に、BallにNetwork Identityコンポーネントをアタッチします。Server Only,Local Player Authorityのチェックは不要です。更に、Sync Bufferもアタッチします。これには、シンクロさせたい位置情報などが保存されます。

image.png

それでは、シンクロに関する処理のためのスクリプトBallSync.csを準備しましょう。AnySyncのサンプルプロジェクトに付属のものを基にしています。準備できたらBallにアタッチしてください。

BallSync.cs
using UnityEngine;
using Mirror;

public class BallSync : NetworkBehaviour
{

    private const float MinimumSendInterval = 0.05f; 

    private float _timeSinceLastSync;
    private Vector3 _lastSentPosition;
    private bool _idle;

    private SyncBuffer BallSyncBuffer;

    private void Awake()
    {
        BallSyncBuffer = GetComponent<SyncBuffer>();
    }

    private void Update()
    {
        if (hasAuthority)
        {
            _timeSinceLastSync += Time.deltaTime;
            if (_timeSinceLastSync >= MinimumSendInterval)
            {
                if (_lastSentPosition != transform.position)
                    _idle = false;

                if (!_idle)
                {
                    CmdSync(_timeSinceLastSync, transform.position);
                    if (_lastSentPosition == transform.position)
                        _idle = true;

                    _lastSentPosition = transform.position;
                    _timeSinceLastSync = 0f;
                }
            }
        }
        else
        {
            if (BallSyncBuffer.HasKeyframes)
            {
                BallSyncBuffer.UpdatePlayback(Time.deltaTime);
                transform.position = BallSyncBuffer.Position;
            }
        }
    }

    [Command]
    private void CmdSync(float interpolationTime, Vector2 position)
    {
        BallSyncBuffer.AddKeyframe(interpolationTime, position);
        RpcSync(interpolationTime, position);
    }

    [ClientRpc]
    private void RpcSync(float interpolationTime, Vector2 position)
    {
        if (isLocalPlayer || isServer)
            return;

        BallSyncBuffer.AddKeyframe(interpolationTime, position);

    }
}

この中で、[Command]アトリビュートのついているCmdSyncはホスト側で、[ClientRpc]アトリビュートのついているRpcSyncはクライアント側で実行されます。

Update関数内はhasAuthorityによって分岐しています。hasAuthorityがTrue、つまりAuthorityを持っている場合(今回はホスト側)はCmdSyncを実行して現在の位置情報をSync Bufferに送ります。更にその中でRpcSyncを実行して、クライアント側のSyncBufferにも位置情報を送ります。これでホスト、クライアント両方で同じ位置情報がバッファーに保存されます。hasAuthorityがFalseの場合(今回はクライアント側)はSync Bufferに保存されているデータを読み取ってクライアント側のBallの位置として代入しています。

これでBallの準備はできたので、Ballをプロジェクトウィンドウにドラッグして、プレハブ化してください。ヒエラルキー内のBallは消しましょう。

image.png

他のゲームオブジェクトの設定

次に、BallSpawnerという空のゲームオブジェクトを作ってください。Network Identityコンポーネントをアタッチして、Server Onlyにチェックをいれます。これにより、BallSpawnerはホスト側だけで動作するようになります。

image.png

次に、スクリプト BallSpawner.csを作ってBallSpawnerオブジェクトにアタッチしてください。

BallSpawner.cs
using UnityEngine;
using Mirror;

public class BallSpawner : NetworkBehaviour {

    public GameObject Ball;

    void Update () {
        if (Input.GetKeyDown(KeyCode.Space))
        {
                Fire();
        }
    }

    void Fire()
    {
        var go = Instantiate(Ball);
        NetworkServer.Spawn(go);
    }
}

インスペクタ上で変数BallにBallプレハブをアサインするのを忘れずに。

これで、スペースキーを押すとホスト側でBallがインスタンス化されて、さらに、NetworkServer.Spawn(go)によりクライアント側でもBallオブジェクトが生成されます。

最後に、NetworkManagerという空のゲームオブジェクトを作ってください。そちらに、Network Managerコンポーネント、Network Manager HUDコンポーネントをアタッチしてください。さらに、Network Managerコンポーネントの中のSpawn Info中のRegistered Spawnable PrefabsにBallプレハブをアサインしてください。

image.png

完成

これで完成です。スタンドアロンとしてビルドして、エディターと一緒に実行してみましょう。

image.png

この画面になったら、Editor側ではLAN Host, ビルドしたアプリ側ではLAN Clientをクリックしてください。次に、スペースバーを押せばBallが出てきて、シーンビューか何かでBallを移動させれば、Client(スタンドアロンアプリ)側でも同じように移動するはずです。

AnySync.gif

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

LeapMotion+Unityでグー・チョキ・パーを認識する

はじめに

VRMの流行の兆しが見えたので推しの子とじゃんけんが出来るソフトでも作っておこうかな、と思ったのでLeapMotionでじゃんけんの手を検知する仕組みを作りました。知見共有。


制作

新しい方のLeapMotionSDKとUnityの知見は少な目な気がします…もしかしてもうLeapMotion界隈下火だったりします…?

LeapMotionSDKの準備

まずLeapMotion公式からUnity向けSDKをダウンロードしておきます。中にあったUnityPackageをProjectにインポートします。

Unity側の準備

Assets/LeapMotion/Core/Examples/Capsule Hands(Desktop)からLeapMotionControllerとHandModelsをコピペして自分のシーンに貼り付けます。コレさえあればとりあえず動くはずなので、試しに実行してみて手がトラッキングできればとりあえず成功です。

手の形を認識する

HandRSP.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Leap;
using System.Linq;
public class HandRSP : MonoBehaviour {
    private Controller controller;
    private Finger[] fingers;
    private bool[] isGripFingers;
    public enum RSP
    {
        Rock, Scissors, Paper
    };
    public RSP rsp;
    // Use this for initialization
    void Start () {
        controller = new Controller();
        fingers = new Finger[5];
        isGripFingers = new bool[5];
    }

    // Update is called once per frame
    void Update () {
        Frame frame = controller.Frame();
        if(frame.Hands.Count != 0)
        {
            List<Hand> hand = frame.Hands;
            fingers = hand[0].Fingers.ToArray();
            isGripFingers = Array.ConvertAll(fingers, new Converter<Finger, bool>(i => i.IsExtended));
            Debug.Log(isGripFingers[0]+","+ isGripFingers[1] + "," + isGripFingers[2] + "," + isGripFingers[3] + "," + isGripFingers[4]);
            int extendedFingerCount = isGripFingers.Count(n => n == true);
            if(extendedFingerCount == 0)
            {
                rsp = RSP.Rock;
            }
            else if(extendedFingerCount < 4)
            {
                rsp = RSP.Scissors;

            }
            else
            {
                rsp = RSP.Paper;

            }

        }
    }
}

コレをシーン内の好きなオブジェクトにアタッチすればrspにグーチョキパーの認識結果が表示されます。

解説

usingステートメント

LeapMotionの機能を利用するにはusing Leap;のステートメントを利用します。

Controller

LeapMotionの制御はControllerで行い、これがメインのインターフェースとなります。

Frame

Controller.Frameには手の挙動の情報が含まれています。

Hand

Hand型には手の位置、角度や各指の情報が存在しています。Controller.Handsで認識されている全ての手についてのHandを返します。認識範囲内に一つも手がなければ当然要素数0になるので例外処理をしっかりしましょう。

Finger

各手についている指の情報を保有する型です。位置、指し示す向き、握っているか否かなどの情報を取得することができます。Hand.Fingersですべての指のFingerを取得することができます。また、各Fingerに対してFinger.IsExtendで指の開きを検知することができます。指が開いているときTrue、指が曲がっているときがFalseです。上記の実装例ではisGripFingers = Array.ConvertAll(fingers, new Converter<Finger, bool>(i => i.IsExtended));で全ての指の曲がり方のブール値を取得したのち、int extendedFingerCount = isGripFingers.Count(n => n == true);(LinQを利用しています)で曲がっている指の本数を取得しています。

RSP

曲がった指の本数を基準にグーチョキパーのいずれの手が出されているか判断します。RSPは手の種類をまとめたEnumで、今回は曲がった指の本数が5本でグー、4~2本でチョキ、1本以下でパーとしています。
キャプチャ164.PNG
キャプチャ165.PNG
キャプチャ163.PNG

問題点

取り敢えず雑に認識させることができましたが、チョキとグーの認識が結構厳しく、ちゃんと手を下に向けないとうまく認識してくれなかったりします。これについては部屋が明るいのとか僕の手がオタク特有のまっしろハンドなのとかいろいろ原因がありそうです。

解決のアイデア

まぁ別にガチじゃんけんするわけでもないしコンピュータ側の出す手に合わせてユーザーを勝たせる側に強めの認識補正をかけてあげればいいんじゃないでしょうか。勝てる手を出したのに認識ミスで負けるってのはプレイフィール最悪なのでそれだけは避ける方向で行くといいと思います。

最後に

ちょうどVroidHubSDKも頂けたのでこれを利用して好みのモデルとじゃんけんが出来るシステムを組んでいきたいです。やるぞ~~

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

はじめての非同期処理 call backは,送信機と受信機のイメージで.(OcuGo.Games)

非同期処理とは。

このボタンが押されたら、とか。

敵が3体倒されたら、とか。

一般論でいうと、この人から電話がきたら、とか。

自分のタイミングではなく、外部からのトリガーで処理を実行したい際に「非同期処理」を使います。

送り手と受け手がいる.

送り手: 条件を満たしました!
keitai_mukashi.png

受け手: 条件満たしたのね.連絡ありがと! 次の処理流そっと.
denwa_business_woman.png

callbackは System.Action型

C#では、System.Action型の Callback という関数が用意されています.
以下では、「スペースキーが3回押されたら」という条件をトリガーに、次の処理を流すという設定でコードを紹介します。

送り手側

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CountSpace : MonoBehaviour {
    System.Action Callback;
    int spacenum = 0;
   // Update is called once per frame
    void Update()
    {    //スペースキーを押すとspacenumをカウントアップする.
        if (Input.GetKeyDown(KeyCode.Space)){
            this.spacenum++;
        }
          //スペースキーが3回押されたら
        if (spacenum == 3) {
            this.Callback();  //送信機を使用して連絡をする.
        }
     }

    //送信機の設置
    public void SetCallback(System.Action Callback) 
    {
        this.Callback = Callback; //
    }



受け手側

  public CountSpace A; //CountSpaceという classの Aという変数

    // Use this for initialization
    void Start () 
   {     //電話が来たらカッコ内の処理を流す.
        A.SetCallback(連絡を受け取ってから流したい処理);
    }

ここでは仮に変数名をAとしましたが,なんでも構いません.
イメージ的にはAさんからかかってきたらということですが,そのAさんが誰であるか限定する必要がないためです。

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

CVVTuberExample の VRMCVVTuber を動かす

概要

Unity アセットの CVVTuberExample の中に同梱されている、
VRMCVVTuberExample を動かすのに少し修正が必要だったのでメモ

CVVTuberExample とは

OpenCV と Dlib を使って簡単に VTuber システムを作るもの。
その中でも VRMCVVTuberExample は、VRMモデルを利用したもの。

利用したもの

OpenCV for Unity(有料)
Dlib FaceLandmark Detector(有料)
CVVTuberExample
UniVRM(今回は現時点で最新の0.49を利用)
VRMモデル(今回は AliciaSolid.vrm を利用、お好きなキャラクターでお試しください VRoid Hub オススメ)
Webカメラ Logicool C270

セットアップ

  1. Asset Store から CVVTuberExample をインポート
  2. CVVTuberExample/CVVTuber/Addons/VRMCVVTuber.unitypackage をダブルクリックしてインポート
  3. CVVTuberExample/CVVTuber/Addons/VRMCVVTuber/Examples/VRMCVVTuberExample.unity をダブルクリックしシーンを読み込み
  4. Asset Store から OpenCV for Unity をインポート
  5. Asset Store から Dlib FaceLandmark Detector をインポート
  6. インポートした OpenCVForUnity, DlibFaceLandmarkDetector の中にある StreamingAssets フォルダを、Assets 配下に移動する
  7. UniVRM をインポート

VRM モデルを読み込む

Assets 配下の適当なところに、ダウンロードしてきた vrm ファイルを D&D
出来上がった prefab を Hierarchy に D&D

その後、Hierarchy の VRMCVVTuberExample 内の VRMControllManager を選択し
Inspector に表示される VRM Loader (Script) 内の Meta に読み込んだモデルを選択する

コードの修正

今のままだと、エラーが出たままで実行できないので、修正します。
原因はおそらく UniVRM の仕様が昔から変わってるからだと思います。

VRMCVVTuberControllManager.cs を修正する

67行目あたり

VRMCVVTuberControllManager.cs
((VRMHeadRotationController)item).target = vrmLoader.lookAtHead.Head.Transform;

VRMCVVTuberControllManager.cs
((VRMHeadRotationController)item).target = vrmLoader.lookAtHead.Head;

VRMLoader.cs を修正する

56行目あたり

VRMLoader.cs
var context = new VRMImporterContext (path);
context.ParseVrm (bytes);

VRMLoader.cs
var context = new VRMImporterContext ();
context.ParseGlb(bytes);

これで実行できるようになります。

おまけ

VRMLoader.cs の修正部分は、ランタイムロードを使う部分なので、
そのまま動かす場合は関係ないですが、実行できるようにするために必要です。

ランタイムロードを確認する場合は、StreamingAssets 配下に vrm ファイルを配置します。
また、Hierarchy の VRMCVVTuberExample 内の VRMControllManager を選択し
VRMCVV Tuber Controll Manager (Script) の Use Runtime Loader にチェックをいれて
VRM Loader (Script) 内の VRM File Name StreamingAssets 配下からの vrm ファイルのパスを入力してください。
StreamingAssets/Model/AliciaSolid.vrm と配置していたら Model/AliciaSolid.vrm と入力する。
スクリーンショット 2019-02-03 0.04.30.png
これで VRM モデルのランタイムロードができます。
この場合は、モデルの prefab を Hierarchy においておく必要がありません。

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