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

Unity Standalone File Browser がMac OSで動作しなくなった件

Unity Standalone File Browserとは Unity Standalone File BrowserはUnityでFile Dialogを出したいときに便利に使えるライブラリであり、Windows, MacOS, Linuxのいずれでも使えるため人気が高いです。 Mac OSで動作しなくなった しかし、いつの間にかMac OSで動かなくなっているのに気づきました。必死で調べてみたところ、issue #109 で同じ現象の人がいるのに気づいたので、どうも私がやらかしたわけではないようです。Windowsでは問題ないのですが、M1 MacもiMac2014も動かなくなっています。 とはいえ、時期的に現象の発生はM1 Mac発売の後のような気がしたので、Build設定を変えて試したところ、Build Settings>PC, Mac & Linux Standalone > Architecture > "Intel 64-bit" だけは問題なく動き、"Apple silicon"または"Intel 64-bit + Apple silicon"では動作しないことがわかりました。ということで、このライブラリをMac OSで使う必要がある人は、"Intel 64-bit"でビルドするしか今のところ解はなさそうです。 考察 MacOS用は StandaloneFileBrowser.bundleというディレクトリの下にDLLのようなものが存在しています。これは多分XCodeでビルドされたと思われます。でもって、現状(2021/7/31)は"Intel 64-bit" 以外のアーキテクチャではこのDLLを見つけることができないようす。適当にXCodeでリビルドしたり色々やってみても解決しなかったのですが、下記記事を見つけたのでもうちょっと頑張ってみたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityのLocalizationPackageでビルド後にロケールが変更されない不具合

 検証環境 Unity2020.3.9f1、Localization-1.0.0-pre.10で検証しました。 現象(問題: build後のロケールが英語にならない) 問題が発生していたプロジェクトについて、この記事の手順でLocalizationの環境を一から作り直し、テキストを少しづつLocalizeしていきました。テキスト数が少ない間はロケールが英語に変更され問題が発生しないのですが、ある数を超えるとbuild後のロケールが英語にならない問題が発生しました。Unityの内部的にはLocaleの設定(en,jp)が見つけられずnullを返していることが問題のようです。 解決方法 気まぐれで下図のようにLocalization Scene Controlsを設定したら、上記現象が発生していたプロジェクトでもbuild後のロケールが英語になりました。Active Localeがnullでもある程度は頑張ってくれるのだけど、ある数を超えると諦めてしまうというような不具合?のようですね。 最後に これで本当に解決しているのかまだ半信半疑なのですが、検索した範囲ではLocalization関連の記事にこのような情報はなく、似たような現象に悩まされている人に向けては役に立つかもしれないので共有させていただきました。 追記(7/31 23:30)  時々変な現象が発生してはDiscardしたりバージョンを戻したりして、結果としてはビルド後でも完全に英語版が表示できるようになりました。LocalizationパッケージはPreviewなので、いつまたおかしくなるかわかりませんが、とりあえず約束していた仕事が果たせて安心しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ESP32とUnityをWiFi送受信で連携する

理解のためのメモその2 やること 前回記事の続きです。 ESP32とUnityをWiFiのアクセスポイント経由で連携します。 前回はESP32-UnityのUDPパケットを用いた一方向通信でしたが、 今回はそれに加え、送信・受信の両方をおこないます。 送受信するデータ形式は符号あり2バイトのShort型が配列で10個並んだものです。 これを1バイトずつ送り、ESP32,Unityそれぞれで受信後に2バイト数値に戻します。 ESP32からは一定間隔で送信、Unity側からは毎フレーム送信を行います。 Unity側ではインスペクターに受信データ、送信データを表示します。 ESP32側はシリアルモニタに送受信結果を表示します。 ポイント ESP32側では2バイトを1バイトに変換するために共用体を利用 Unity側では近い処理をBitConverterで実施 Unity側の受信処理はスレッドを立てて実行 参考 今回の記事はほぼこちらのトレースであります。ありがたく勉強させていただきます。 ESP32用スクリプト Arduino //ESP32 to Unity via UDP #include <WiFi.h> #include <WiFiUdp.h> const char* ssid = "xxxxxxxxxx";//接続するアクセスポイントのSSID const char* password = "xxxxxxxxxx";//上記のパスワード const char* send_address = "192.168.xxx.xxx"; //送り先 const int send_port = 22222; //送り先 const int receive_port = 22224; //このESP32 のポート番号 WiFiUDP udp; //送るデータのサイズ。データはショート型の符号付き2バイト、送信時は1バイトに変換。 static const int MSG_SIZE = 10;//送信データの数 static const int MSG_BUFF = MSG_SIZE * 2;//送信データのバイト数 //共用体の設定。共用体はたとえばデータをショートで格納し、バイト型として取り出せる typedef union { short sval[MSG_SIZE];//ショート型 uint8_t bval[MSG_BUFF];//符号なしバイト型 } UDPData; UDPData s_upd_message_buf; //送信用共用体のインスタンスを宣言 UDPData r_upd_message_buf; //受信用共用体のインスタンスを宣言 int count = 0;//送信変数用のカウント void setup() { Serial.begin(115200); delay(500);//シリアル準備待ち用ディレイ //WiFi 初期化 WiFi.disconnect(true, true);//WiFi接続をリセット Serial.println("Connecting to WiFi to : " + String(ssid));//接続先を表示 delay(100); WiFi.begin(ssid, password);//Wifiに接続 while ( WiFi.status() != WL_CONNECTED) {//https://www.arduino.cc/en/Reference/WiFiStatus 返り値一覧 delay(100);//接続が完了するまでループで待つ } Serial.println("WiFi connected.");//WiFi接続完了通知 Serial.print("WiFi connected. ESP32's IP address is : "); Serial.println(WiFi.localIP());//デバイスのIPアドレスの表示 //UDP 開始 udp.begin(receive_port); delay(500); } //受信用の関数 //パケットが来ているか確認し、r_upd_message_buf配列に保存する void receiveUDP() { int packetSize = udp.parsePacket(); byte tmpbuf[MSG_BUFF];//パケットを一次受けする配列 //データの受信 Serial.print("[RESV] "); if (packetSize == MSG_BUFF) {//受信したパケットの量を確認 udp.read(tmpbuf, MSG_BUFF);//パケットを受信 for (int i = 0; i < MSG_BUFF; i++) { r_upd_message_buf.bval[i] = tmpbuf[i];//受信データを共用体に転記 } for (int i = 0; i < MSG_SIZE; i++) { Serial.print(String(r_upd_message_buf.sval[i]));//シリアルモニタに出力 Serial.print(", "); } } else { Serial.print("none.");//受信していない場合はシリアルモニタにnone.を出力 } Serial.println(); } //送信用の関数 void sendUDP() { String test = "";//表示用の変数 udp.beginPacket(send_address, send_port);//UDPパケットの開始 for (int i = 0; i < MSG_BUFF; i++) { udp.write(s_upd_message_buf.bval[i]);//1バイトずつ送信 if (i % 2 == 0) {//表示用に送信データを共用体から2バイトずつ取得 test += String(s_upd_message_buf.sval[i / 2]) + ", "; } } Serial.println("[SEND] " + test );//送信データ(short型)を表示 udp.endPacket();//UDPパケットの終了 } void loop() { //もしデータパケットが来ていれば受信する receiveUDP(); //送信するデータを作成 for (int i = 0; i < MSG_SIZE; i++) { s_upd_message_buf.sval[i] = (short)( i + count); } sendUDP();//UDPで送信 count += 10;//データ作成用のカウントを追加 if (count > 10000) { count = 0; } delay(500); } まずこちらのスケッチをESP32に書き込みます。 実行直後(リセット直後)に、シリアルモニタにESP32のIPアドレスを表示し、 その後は送信データを表示し続けます。 Unity用スクリプト udp_receive_test.cs using UnityEngine; using System.Net; using System.Net.Sockets; using System.Threading; using System; public class udp_send_receive_test : MonoBehaviour { const string HOST = "192.168.xxx.xxx";//送信先(ESP32)のIPアドレス const int SEND_PORT = 22224;//送信用ポート設定 const int RECEIVE_PORT = 22222;//受信に使うポートの番号 static UdpClient udp_receive;//UDP受信を使う準備 static UdpClient udp_send;//UDP送信を使う準備 Thread thread;//スレッドを使う準備 const int MSG_SIZE = 10;//受信するshort型データの個数 static char[] r_char_buf = new char[MSG_SIZE * 2];//データ受信用のchar型変数(1バイト) static short[] r_short_buf = new short[MSG_SIZE];//データ格納用のshort型変数(2バイト) public int[] r_showdata = new int[10];//受信データインスペクタ表示用 static byte[] s_char_buf = new byte[MSG_SIZE * 2];//データ送信用のchar型変数(1バイト) static short[] s_short_buf = new short[MSG_SIZE];//データ送信格納用のshort型変数(2バイト) public int[] s_showdata = new int[MSG_SIZE];//送信データインスペクタ表示用 static long count = 0;//データ受信用のchar型変数(1バイト) string s_string; void Start() { udp_receive = new UdpClient(RECEIVE_PORT); udp_receive.Client.ReceiveTimeout = 1000;//UDP通信のタイムアウト設定 うまくいかない場合は0に? udp_send = new UdpClient(); udp_send.Connect(HOST, SEND_PORT); thread = new Thread(new ThreadStart(ThreadMethod));//受信用スレッドを準備 thread.Start();//受信用スレッドを開始 } void Update() { //受信データをインスペクターに反映 for (int i = 0; i < MSG_SIZE; i++) { r_showdata[i] = r_short_buf[i];//受信データをインスペクター表示用配列に転記 } senddataUDP();//毎フレームデータを送信してみる } void OnApplicationQuit()//アプリ終了時の処理 { udp_send.Close();//UDPの送信を終了 thread.Abort(); } private static void ThreadMethod()//受信スレッド用の関数 { while (true) { IPEndPoint remoteEP = null; byte[] data = udp_receive.Receive(ref remoteEP); //パケットサイズをチェックし、受信データをUnityに反映 if (data.Length == MSG_SIZE * 2) // { for (int i = 0; i < MSG_SIZE; i++) { r_short_buf[i] = BitConverter.ToInt16(data, i * 2); } } } } public void senddataUDP() { //送信するデータを作成 for (int i = 0; i < MSG_SIZE; i++) { s_short_buf[i] = (short)(i + count); } //ショート型をバイトに変換 for (int i = 0; i < MSG_SIZE; i++) { byte[] byteArray = BitConverter.GetBytes(s_short_buf[i]); s_char_buf[i * 2] = (byte)byteArray[0]; s_char_buf[i * 2 + 1] = (byte)byteArray[1]; s_showdata[i] = s_short_buf[i];//インスペクタ表示に送信データを反映 } udp_send.Send(s_char_buf, MSG_SIZE * 2); count += 5;//データ作成用のカウントを追加 if (count > 10000) { count = 0; } } } 使い方と実行 ・Unityの新規プロジェクトを立ち上げます。 ・アセットに新規スクリプトを作成してファイル名を「udp_send_receive_test」とし、上記のスクリプトをコピペします。 ・ヒエラルキーウィンドウに空のオブジェクトを作成し、スクリプトをアタッチします。 ・空のオブジェクトを選択し、インスペクターを表示します。 ・スケッチが書き込まれたESP32を起動します。 ・インスペクターにカウントアップする数値が表示されます。 実行画面 ■ Unity側の実行画面(インスペクター) ■ ESP32のシリアルモニタ(Arduino IDE) おわりに まだ変なところや無駄なところがあるかもしれません。 お気づきの点ございましたらご指摘いただければ幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ThreeDPoseTracker Ver.0.4.0アップデート

はじめに この記事はUSBカメラモーションキャプチャーThreeDPoseTrackerのバージョン0.4.0のアップデートについての記事です。ThreeDPoseTrackerってナニ?という方は先に 「USBカメラモーションキャプチャーThreeDPoseTrackerの説明」 https://qiita.com/yukihiko_a/items/43d09db5628334789fab 「ThreeDPoseTracker Ver.0.2.0アップデート」 https://qiita.com/yukihiko_a/items/e5b07bd045611c73bbbe 「ThreeDPoseTracker Ver.0.3.0アップデート」 https://qiita.com/yukihiko_a/items/e5b07bd045611c73bbbe をご覧ください。 この記事ではとりあえず変更点だけ記載しておきます。 v0.4.0について ダウンロード先 下記URLからダウンロードしてください。 https://digital-standard.com/threedpose/ThreeDPoseTracker_Win_x64_v0_4_0.zip 更新内容 今回の主な更新内容です。iOS版アプリTDPTと学習モデル・アバターの動きを合わせるのがが主たる目的のリリースです。 姿勢推定の学習モデルの変更。入力画像の中心から外れた際の精度の低下を改善 アバターとカメラのデフォルトの向きを変更 デスクトップのウインドウをThreeDPoseTracker内にキャプチャする機能を追加 VMDフォーマットの保存機能の追加 足の動きの固定機能を強化 デフォルトアバターのりゅんちゃんの衣装変更 今回、アバターの向きを変更しました。前のバージョンまでは起動時にアバターが後ろ向きでしたが、今回から正面を向きます。これもiOS版に合わせての修正です。ちなみにiOS版と言うのは↓こちらです。 https://apps.apple.com/jp/app/tdpt/id1561086509 この変更に伴い、VMC Protocolで送信されたデータも逆に向く事になります。VMCPを利用されている場合は、アプリケーション内のVMC Protocolの設定画面のRotで調整してください。 ウインドウをキャプチャした動きはこんな感じです。(これはXBox360(+Kinect)でダンエボを動かしてそのHDMI出力をPCで受けてウインドウに表示して、そのウインドウをThreeDPoseTrackerでキャプチャしてます) 設定画面 Predict Lock Foot、Lock Leg 上半身モードが欲しいという要望をよく頂くので足のロック機能を少し見直しました。決して上半身モードになる訳ではありません。PCの前に座ったまま使うとかは無理です。そういう用途は他のアプリを使ってよと思ってます。(荒業としてSourceCutScaleを0.5くらいにしてYの値を-0.5くらいにするという手段もあります。そうすると小さく写った上半身が入力画面の上側に位置されます。上半身が大きく映っているよりも、全身が写っている感じのスケール感で下半身が写っていないという状態の方が精度が上がります。これは学習データに上半身のみで大きく写った画像が含まれていないからですね)。とりあえず、股関節くらい(太もも)までカメラに写っていれば比較的安定するんじゃないかと思いますが、ライティングなどの環境面にかなり影響されるので一概にはいえません。LockLegにチェックを入れると太もも以下が真直ぐに固定されます。なかなか違和感があるのでAvatarSettingの画面でアバターの位置を調整して足が見えないようにしてください。前後にうごく必要もないでしょうからDepthScalInZの値を0にしてしまえば前後の移動がほぼなくなります。 Record VMDでのモーションの保存を追加しました。要望があったので追加しましたが、ここだけの話、私はほとんどMMDを触ったことがありません。全てUnityVMDRecorderという素晴らしいライブラリのおかげです。腰が多段ボーンのMMDであれば問題なく動く様ですが、そうでないモデルはでは体が回転しないようです。たぶん私のコードのせいだと思いますがよくわかってないので、MMD側で対応してください。 Output at VMD Format VMDでモーションを保存したい時に選択します。BVHかVMDのどちらかでしかモーションの保存はできません。 Use Bottom Center センターが足元にあるMMDを使用する場合には、このオプションを設定してください。どういう効果があるのかはよくわかりません。ごめんなさい。 WindowCapture デスクトップに表示しているウインドウをキャプチャしてThreeDPoseTrackerのアプリ内に画面表示する事ができます。それってOBS使えば良いんじゃないの?と言われればその通りですが、アプリ内に表示されるので表示位置を調整すれば体を隠して画面から手だけを出したり、顔だけ出したりできます。パワポも表示できますが、ページ送りに難があります。スライドを使いたい場合はスライドショーの設定で”出演者として閲覧する(ウィンドウ表示)”でスライド表示してください。右矢印キーでページ送りはできるのですが戻る事ができません(Office365で確認)。各ページにマクロを仕込むなどをすれば戻る事も可能にできますが、ここでは説明しません。聞きたい人は個別に聞いてください。 また、重めのゲームのキャプチャもキビシイです。このアプリ自体が重いので。 Use Window Capture ウインドウのキャプチャ機能を有効にします。プルダウンメニューにデスクトップに表示されているウインドウのタイトルが表示されます。表示したいウインドウを選択してください。表示されていない場合は、Reloadボタンを押下するかアプリケーションを再起動してください。それでも表示されない場合は認識できないウインドウですのでキャプチャできません。 Position、Rotation、Window Scale キャプチャされた画面の表示位置を調整します Frame Rate アプリケーション内でキャプチャを更新するフレームレートです。 Draw Cursor ウインドウ内にマウスカーソルがある場合、そのカーソルをキャプチャ画面にも表示するかどうかを設定します。 最後に Ver0.3をリリースしてから随分経ちました。久しぶりのリリースです。ほんとは昨年の末にリリースするつもりでしたが、学習モデルの再学習を始めてしまい、その後iOS版のリリースでタイミングを逃してしまってました。ソースもその頃からプライベートなリポジトリに移動しています。Githubで公開しているコードとはだいぶ乖離しているので消してしまいたいのですが、とりあえずそのままにしています。質問されても答えません。 Licence このThreeDPoseTrackerの実行ファイルについては営利・非営利問わずご自由にお使いください。この実行ファイルを使用した配信、モーションデータも同様にご自由に利用可能です。 GithubにあるソースコードについてはGithubにあるライセンスに従ってください。 免責事項 本ソフトウエアの使用に起因する、ソフトウエア、ハードウエア上の事故その他の損害、または使用できなかったことから生じる一切の損害に関して、製作者はいかなる責任も負わず、修正する義務を負いません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity]ARCollaborationDataを使った空間共有メモ[ARKit]

はじめに UnityでiOS向けにARを使った空間共有をしてみたいと思ったのだが、ARWorldMapを使った記事はいくつか見つかるのだが、Collaborative Session(ARCollaborationData)を使った記事は、せいぜいUnity純正のサンプルを使ってみた記事ぐらいしか見つからなかった(探し方が悪い説はある)。 ただし、Unity純正のサンプルでは、アンカーを共有するだけで、動的にオブジェクトを生成したりはしないので、今回作りたかったものには参考にならなかった。 なので、Collaborative Sessionを使って空間共有する方法を模索してみた。 作るもの 今回作るものは、スマホ画面をタップした時に、タップした場所にCubeを生成し、また、生成したCubeは空間共有している別の端末にも表示するものとする。 方針 ・Unity純正のサンプルを基に作成する。 ・ARWorldMapを使わずに、ARCollaborationDataを使う。 ・通信はMultipeerConnectivityを使う。これは、Unity公式サンプルのものをそのまま使用する。 解決すべき問題 いくつか解決すべき問題があるので、問題点と解決方法をあげていく。 問題1:端末ごとの座標系が違う これはどういうことかと言うと、AR空間上の原点の位置や回転が、端末ごとにバラバラだということである。 原点が違うので、端末Aで作成したオブジェクトの位置を別の端末Bに送信した場合、端末Aの座標を端末Bでそのまま使うとまるっきり意図しない場所にオブジェクトが生成されてしまう。 問題1の解決方法 Unity純正のサンプルに、ARCollaborationDataを使ったサンプルがあるのだが、そのサンプルは複数の端末でアンカーを共有するというものになっている(アンカー以外のオブジェクトは共有しない)。 ここで重要なのは、 アンカーを共有できること である。 アンカーが共有できるのであれば、アンカーからの相対位置や回転を送信すれば、別の端末でも共有しているアンカーを使って元の位置を特定できる。 また、共有するアンカーはたかだか1つだけで良いだろう(複数あっても、どのアンカーからの相対座標なのか混乱するだけで益はない)。 というわけで結論は、 「端末それぞれにアンカーを1つだけ作成し、そこからの相対座標を通信でやり取りする」 ということになる。 実装:アンカーの作成 端末1台につき1つ、座標の基準点となるアンカーを作成する。 AnchorCreator.cs public class AnchorCreator : MonoBehaviour { // ... (略) [SerializeField] float m_minDistance = 0.2f; ARAnchor m_Anchor; public ARAnchor MainAnchor { get => m_Anchor; } void Update() { // 基準となるアンカーがすでに存在する場合には、何もしない。 if(m_Anchor != null) return; // Raycast against planes and feature points const TrackableType trackableTypes = TrackableType.FeaturePoint | TrackableType.PlaneWithinPolygon; // Perform the raycast var centerPosition = new Vector3(m_Camera.transform.position.x, m_Camera.transform.position.y, 0); if (m_RaycastManager.Raycast(centerPosition, s_Hits, trackableTypes)) { // Raycast hits are sorted by distance, so the first one will be the closest hit. var hit = s_Hits[0]; Debug.LogFormat("hit.distance: {0}", hit.distance); if(hit.distance <= m_minDistance) return; // 端末からの距離が近すぎるアンカーは生成しない。 // 基準となるアンカーを作成する m_Anchor = CreateAnchor(hit); } } } コメントに「端末からの距離が近すぎるアンカーは生成しない」とあるが、距離が近すぎるデメリットがあるのかどうかは定かではない。 これをしないと、アプリ起動時に端末がある位置そのものがアンカーになりやすいので、位置がずれやすそうな気がして、いれてある。 問題2:独自データの判別 通信するデータは同じMCSessionを利用して通信しているのだが、データには、 "空間共有するためのARCollaborationData" と、 "独自のデータ(動的に生成したCubeの位置と回転のデータ)" がある。 そのため、2種類のデータを区別するための手段が必要になる。 問題2の解決方法 今回は、独自データの最初の4バイトに固有の値を入れておくことにより、区別出来るようにした。 実装 独自データを送るときの最初の4バイト ObjectDataSerializer.cs public static class ObjectDataSerializer { // 送信データの最初の4バイト private static byte[] AppId = {(byte)'D', (byte)'E', (byte)'M', (byte)'O'}; // ... (略) } 送信側 Spawner.cs Vector3 pos = m_camera.transform.position; Quaternion rot = m_camera.transform.rotation; Instantiate(m_LocalCubePrefab, pos, rot); // // Send to Peers // MCSession session = m_CollaborativeSession.Session; if (session != null && session.ConnectedPeerCount > 0){ // 基準となるアンカーを取得 ARAnchor anchor = m_anchorCreator.MainAnchor; // ARAnchor からの相対座標を求める。 var localPos = anchor.transform.InverseTransformPoint(pos); var localRot = rot * Quaternion.Inverse(anchor.transform.rotation); SerializedObjectData serializedObjectData = ObjectDataSerializer.Serialize(anchor.trackableId, ObjectType.Cube, localPos, localRot); NativeArray<byte> ary = serializedObjectData.GetNativeArray(); var data = NSData.CreateWithBytesNoCopy(ary); session.SendToAllPeers(data, MCSessionSendDataMode.Reliable); } 受信側 受信したデータは、ARCollaborationDataと独自データの2種類にわかれる。 2種類のデータを区別するために、独自データをDeserializeするTryDeserialize関数を呼んで、ヌルでなければ独自データだったと判断し、ヌルだった場合は、ARCollaborationDataであると判断して処理をする。 CollaborativeSession.cs void Update() { // ... (略) // Check for incoming data while (m_MCSession.ReceivedDataQueueSize > 0) { if(OnIncomingDataReceived != null){ OnIncomingDataReceived(); } using (var data = m_MCSession.DequeueReceivedData()){ // TryDeserializeで独自データを復元できないときは、ARCollaborationDataと判断する。 if(m_tryDeserializable == null || (! m_tryDeserializable.TryDeserialize(data.Bytes))){ using (var collaborationData = new ARCollaborationData(data.Bytes)) { if (collaborationData.valid) { subsystem.UpdateWithCollaborationData(collaborationData); if (collaborationData.priority == ARCollaborationDataPriority.Critical) { Debug.Log($"Received {data.Bytes.Length} bytes of collaboration data."); } } else { Debug.Log($"Received {data.Bytes.Length} bytes from remote, but the collaboration data was not valid."); } } } } } } Spawner.cs public bool TryDeserialize(NativeSlice<byte> bytes){ ObjectData data = ObjectDataSerializer.TryDeserialize(bytes); if(data == null){ return false; } ARAnchor anchor = m_anchorManager.GetAnchor(data.Id); if(anchor == null) return false; // ARAnchorからの相対座標をワールド座標に変換する。 var globalPos = anchor.transform.TransformPoint(data.Position); var globalRot = anchor.transform.rotation * data.Rotation; switch(data.Type){ case ObjectType.Cube: Instantiate(m_RemoteCubePrefab, globalPos, globalRot); break; } return true; } ObjectDataSerializer.cs public static ObjectData TryDeserialize(NativeSlice<byte> slice){ if(slice.Length != 56){ // throw new Exception($"illegal data size {slice.Length}."); return null; } byte[] bytes = slice.ToArray(); // // Check AppId // if(bytes[0] != AppId[0] || bytes[1] != AppId[1] || bytes[2] != AppId[2] || bytes[3] != AppId[3]) { Debug.LogFormat("unmatched AppId. {0},{1},{2},{3}", (char)bytes[0], (char)bytes[1], (char)bytes[2], (char)bytes[3]); return null; } // // Check Protocol Version // Debug.LogFormat("Protocol Major Version: {0}, Minor Version: {1}", BitConverter.ToInt16(bytes, 4), BitConverter.ToInt16(bytes, 6)); // // Check Checksum // Debug.LogFormat("Checksum... bytes[8]: {0}, bytes[9]: {1}", bytes[8], bytes[9]); byte checksum = bytes[9]; byte calcedChecksum = CalcChecksum(bytes); if(checksum != calcedChecksum){ Debug.LogFormat("unmatched checksum. {0} not equal {1}", checksum, calcedChecksum); return null; } ObjectType typ = (ObjectType)BitConverter.ToInt16(bytes, 10); ulong id1 = BitConverter.ToUInt64(bytes, 12); ulong id2 = BitConverter.ToUInt64(bytes, 12 + 8); float pos_x = BitConverter.ToSingle(bytes, 12 + 8 + 8); float pos_y = BitConverter.ToSingle(bytes, 12 + 8 + 8 + 4); float pos_z = BitConverter.ToSingle(bytes, 12 + 8 + 8 + 4 * 2); float rot_x = BitConverter.ToSingle(bytes, 12 + 8 + 8 + 4 * 3); float rot_y = BitConverter.ToSingle(bytes, 12 + 8 + 8 + 4 * 4); float rot_z = BitConverter.ToSingle(bytes, 12 + 8 + 8 + 4 * 5); float rot_w = BitConverter.ToSingle(bytes, 12 + 8 + 8 + 4 * 6); TrackableId id = new TrackableId(id1, id2); Vector3 position = new Vector3(pos_x, pos_y, pos_z); Quaternion rotation = new Quaternion(rot_x, rot_y, rot_z, rot_w); return new ObjectData(id, typ, position, rotation); } 出来たもの https://github.com/JunSuzukiJapan/ARKitCollaboration 参考 ARKit3のCollaborative Sessionという機能 カメラの向いている方向とオブジェクトの角度の差を計算する 【Unity】ワールド座標(グローバル座標)・ローカル座標 ARKitを用いてAR空間共有を試してみる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityでVisualStudioからVisualStudioCodeに変更する。

変更の理由 VisualStudioは全体的に重かったり、コメントアウトのショートカットがめんどくさかったり、 いろんなデメリットがあるため、VSCodeに移行することを決心した。 早速変更する .NET CLI toolsをインストール .NET CLI toolsをインストールします。 このリンク先の .NETCore をインストールしてください。 Download.NET Core SDK x64 でいいかと思います。 Windowsの場合はPCを再起動します。(%PATH%を通すため) .NET Framework 4.5 Targeting Packをインストール(Windowsのみ) VisualStudioを開きます。 コードなしで実行-> メニューのツール -> ツールと機能を取得 をクリックします。 Visual Studio Installerが起動し、そこから個別のコンポーネントへ移動します。 .NET Framework 4.5 Targeting Packをチェックしたうえで、変更をクリックすることでインストールされます。 (僕はデフォルトでチェックされていました。) VSCodeに拡張機能を入れる。 必要な拡張機能は6つ Debugger for Unity Unity Tools Unity Code Snippets Unity Snippets c# Mono Debug 拡張機能の入れ方は割愛させていただきます。。 VSCodeでの設定 設定を開きます(ctrl + ,でもよい) 設定の検索に、 useGlobalMono と入力し、 autoからalwaysに変更してあげてください。 Unity側でVSCodeを標準のEditorにする。 Edit -> Preferences Preferences -> External Tools External Script Editorで Visual Studio Codeを選択し(Visual Studio Codeがない場合、External Script EditorからBrowse...で直接VSCodeのパスを指定してあげます)、 Generate .csproj files for: となってる ところをすべてチェックしてあげてください。 これでUnityでvscodeを使えるようになります。 VSCodeでUnityの構文チェック 拡張機能を入れたことによって左下の ⊗~ ⚠~ となってるところをクリックすれば構文チェックができます。 番外編 コメントが文字化けする。 さっそく、VSCodeでゲーム開発していこうとしたら、コメントだけが文字化けしていた。 文字コードをShift-jisにする VSCode側で設定に行き、 Encondingと検索し、 Files: Enconding を Japanese (Shift JIS) とします。 VSCodeを再起動して設定を反映します。 マークしたところがShift JISになってれば完了です。(なっていない場合クリックして、文字コードを変更してあげましょう。) おわりに 公式ドキュメントが最強ですね。 参考文献 https://www.javadrive.jp/vscode/setting/index4.html https://teech-lab.com/unity-howto-vscode/189/#index_id1 https://code.visualstudio.com/docs/other/unity https://qiita.com/riekure/items/c45868f37a187f8e1d69 https://qiita.com/kamanii24/items/399f956f2bf6bec76884
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity 2020.3で作るWebVRゲーム【フードキャッチ】

概要 Unity 2020.3でカンタンなVRのゲームを作成します 食べ物をカゴでキャッチするゲームを作成します 最終的にWebGLでデプロイしてブラウザで楽しめるVRのコンテンツを作成します 一日で完成できたモノなので手順を参考にして作ってもらえれば幸いです 素材(Asset) WebVR用のパッケージ 完成物 Bowlを操作して落ちてくる食べ物をキャッチするゲームです 作成手順 ゲームの作成 3Dのプロジェクトを新規作成する 素材(Asset)を全てインポートする 上記の素材(Asset)を全てインポートします サンプルのシーンをコピーする RPGPP_LTのサンプルシーンを使用します シーン自体をコピーして複製し任意の名前をつけます 複製したシーンを加工していきます サンプルのシーンのオブジェクトを整理する Environmentという空のオブジェクトを作成します 作成したオブジェクトを親にして全てのオブジェクトを子にします Environmentの位置を調整します Position X:0 Y:-35 Z:-85 Rotation X:0 Y:110 Z:0 カメラの視点をマウスで動かせるようにする DragMove.csというスクリプトを作成して以下の内容を記述します 作成したスクリプトをMainCameraにアタッチします MoveObjにカメラのオブジェクトであるMainCameraをアタッチします アタッチ後に実行するとマウスのドラッグでカメラの視点を動かすことができます MainCameraの位置を調整します Position X:0 Y:0 Z:0 using System.Collections; using System.Collections.Generic; using UnityEngine; public class DragMove : MonoBehaviour { public GameObject moveObj; private Vector3 newAngle = new Vector3(0, 0, 0); private Vector3 lastMousePosition; // Update is called once per frame void Update() { if (Input.GetMouseButtonDown(0)) { // マウスクリック開始(マウスダウン)時にカメラの角度を保持(Z軸には回転させないため). newAngle = moveObj.transform.localEulerAngles; lastMousePosition = Input.mousePosition; } else if (Input.GetMouseButton(0)) { // マウスの移動量分カメラを回転させる. newAngle.y -= (Input.mousePosition.x - lastMousePosition.x) * 0.1f; newAngle.x -= (Input.mousePosition.y - lastMousePosition.y) * 0.1f; moveObj.transform.localEulerAngles = newAngle; lastMousePosition = Input.mousePosition; } } } ゲーム全体を管理するスクリプトを作成する TextMechProのTextでScoreTextとTimeTextというオブジェクトを作成します 作成するとCanvasが自動的に生成されるのでアンカーなどで位置を調整します 空のオブジェクトでGameControlを作成します GameControlに以下のスクリプトを作成してアタッチします スクリプトにScoreTextとTimeTextをアタッチします using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using TMPro; public class GameManager : MonoBehaviour { public TextMeshProUGUI scoreText; public TextMeshProUGUI timeText; public static float time = 60; public static int score = 0; // Update is called once per frame void Update() { GameEndCheck(); scoreText.text = $"SCORE: {score}"; } void GameEndCheck() { if (time > 0) { time -= Time.deltaTime; timeText.text = $"TIME: {Math.Floor(time)}"; } else { timeText.text = "TIME: 0"; } } } 各食べ物のオブジェクトの設定 各食べ物のオブジェクトに対して回転させたり点数を設定できるようにします 食べ物のオブジェクトをCtrl + Aで全て選択している状態で以下のScaleを設定します Scale X:15 Y:15 Z:15 RigidbodyとSphereColliderを設定します Fruitsというタグを設定します FruitのスクリプトをAdd Componentで追加します スクリプトのscoreでそれぞれに個別の点数を設定することができます using System.Collections; using System.Collections.Generic; using UnityEngine; public class Fruit : MonoBehaviour { public int score = 1; float rotateSpeed = 1.0f; void Update() { transform.Rotate(new Vector3(0, rotateSpeed, 0), Space.Self); } } 食べ物を繰り返し生成する 先ほど作成したGameControlに以下のスクリプトを作成してアタッチします スクリプトのFruitsは配列なので任意の数を設定してください それぞれのElementにFREE Food PackのPrefabをアタッチします カメラの周囲(水平の円状)に食べ物のオブジェクトが生成されます Unityで動作させているときのみキーボードのスペースキーで食べ物を生成できます using System.Collections; using UnityEngine; public class FruitsGenerater : MonoBehaviour { public GameObject[] fruits; void Start() { StartCoroutine("CoroutineFruitsGenarate"); } #if UNITY_EDITOR void Update() { if (Input.GetKey(KeyCode.Space)) { GenerateFruit(); } } #endif private Vector3 RandHorizonCircleVec3( float aMin = 0, float aMax = 180, float rMin = 5, float rMax = 5, float yHeight = 1 ) { var angle = Random.Range(aMin, aMax); var radius = Random.Range(rMin, rMax); var rad = angle * Mathf.Deg2Rad; var px = Mathf.Cos(rad) * radius; var pz = Mathf.Sin(rad) * radius; return new Vector3(px, yHeight, pz); } private void GenerateFruit() { int enemyRnd = Random.Range(0, fruits.Length); GameObject fruit = Instantiate( fruits[enemyRnd], // RandHorizonCircleVec3(60, 100, 10, 10, 5), RandHorizonCircleVec3(50, 110, 10, 10, 5), Quaternion.identity ); Destroy(fruit, 4f); } IEnumerator CoroutineFruitsGenarate() { while (true && GameManager.time > 0) { GenerateFruit(); yield return new WaitForSeconds(1); } } } カメラを親にしてボウルを操作できるようにする 以下のBowl.000のオブジェクトを使用します MainCameraの子にしてBowlにリネームします Bowlの位置とスケールを調整します Position X:0 Y:-5 Z:-10 Scale X:50 Y:60 Z:50 ボウルの中で食べ物をキャッチして点数を加算する ボウルのSphereという球形のオブジェクトを作成します Sphereの位置を調整します Position X:0 Y:0.01 Z:0 Scale X:0.005 Y:0.005 Z:0.005 Sphereに落ちてくる食べ物が接触することで点数が加算されます Sphereを作ることでボウルの中に入らないと点数が入らないようになります using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public GameObject catchVFX; void OnCollisionEnter(Collision col) { if (col.gameObject.tag == "Fruits") { GameManager.score += col.gameObject.GetComponent<Fruit>().score; GameObject tempVfx = Instantiate( catchVFX, new Vector3( col.gameObject.transform.position.x, col.gameObject.transform.position.y, col.gameObject.transform.position.z ), catchVFX.transform.rotation ); Destroy(tempVfx, 0.3f); Destroy(col.gameObject); } } } キャッチしたときのパーティクルの設定をする EffectTexturesAndPrefabsのパーティクルの大きさを調整します Scale X:5 Y:5 Z:5 親ではなく子のパーティクル2つのスケールを変更します SphereにパーティクルのPrefabをアタッチします WebVR化する 作成したシーンの複製(シーンのバックアップ) 別のシーンファイルでWebVR化していきます シーンファイルを選択してCtrl + Dで複製できます 複製したシーンファイルはWebVR用とわかるよう名前を変更しておきます WebVRのパッケージをインストールしサンプルのシーンの追加 以下の記事に手順が記載されています パッケージの追加から各種設定 サンプルシーンの追加 サンプルシーンからWebXRCameraSetのコピー WebXRCameraSetをCtrl + Cでコピーします WebVR化するシーンにWebXRCameraSetの貼り付けて設定する 先ほど複製したWebVR化のシーンを開きます 開いてHierarchyでCtrl + VでコピーしたWebXRCameraSetを貼り付けます 貼り付けた後は元からあるMainCameraは無効にしておきます MainCameraからBowlをコピーして同じ構造でWebXRCameraSetのCameraFollowerの子にします WebXRCameraSet/Cameras/CameraFollower/Bowl/Sphere WebVRとしてビルドする Build Settingsで複製したシーンを追加してビルドします 元からあるシーンは削除します ビルドでできたファイルをウェブサーバーにアップロードするとWebVRとして遊べます ふりかえり シューティング以外にも一視点型のゲームができました WebVRとなるとコントローラーの採用が難しいのでカメラで操作させるゲーム性がよさそうです 制限がありそうですが制限があるうえでいろいろなゲーム性を考えられると思います 実際に作ったモノでCameraFollowerの子にしないと動作しないのでアセらず設定しましょう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityプロジェクトをGitHub Desktopで管理する手順

はじめに UnityプロジェクトをGitHubで管理できるようにする手順を毎回忘れてひどい目に合うので、備忘録として残しておくことにしました。今回はUnity 2021.1.15f、GitHub Desktop ver2.9.0を利用しました。例題として、多言語対応のためのLocalizationPackageの動作確認をしてみようと思います。この辺の記事が参考になります。 この記事で作ったリポジトリはこちらです。git cloneすればUnityで動作確認ができるはずです。合わせて参考にしていただければと思います。 Unityプロジェクトを作る。  とりあえずCanvasにTextを置いて、テキストとでも入力しておきましょうか。 GitHub Desktopで新規リポジトリを作成する。  Unityプロジェクトの名前をNameに、パスをLocal pathに設定します。Local pathに名前を含めてしまうと 名前/名前 というようなパスが出来上がって残念なことになります。(1敗目)  ここでGit ignoreでUnityを選べますが、このままだとLogs/ が管理対象になってしまい、余計なファイルをコミットすることになるので.gitignoreを早めに下記に変更する方がよいと思います。(2敗目) /[Ll]ibrary/ /[Tt]emp/ /[Oo]bj/ /[Bb]uild/ /[Bb]uilds/ /[Ll]ogs/ /[Mm]emoryCaptures/ # Never ignore Asset meta data !/[Aa]ssets/**/*.meta # Uncomment this line if you wish to ignore the asset store tools plugin # /[Aa]ssets/AssetStoreTools* # Autogenerated Jetbrains Rider plugin [Aa]ssets/Plugins/Editor/JetBrains* # Visual Studio cache directory .vs/ # Gradle cache directory .gradle/ # Autogenerated VS/MD/Consulo solution and project files ExportedObj/ .consulo/ *.csproj *.unityproj *.sln *.suo *.tmp *.user *.userprefs *.pidb *.booproj *.svd *.pdb *.mdb *.opendb *.VC.db # Unity3D generated meta files *.pidb.meta *.pdb.meta *.mdb.meta # Unity3D generated file on crash reports sysinfo.txt # Builds *.apk *.unitypackage # Crashlytics generated file crashlytics-build.properties ~* .DS_Store .gitignoreの編集はいろんな方法がありますがGitHub Desktopの場合、下図のようにすればできます。 大丈夫そうだなと思ったらPublish repositoryを実行してください。私は1敗目のときの名前/名前 というパスを作ってしまったせいで、Publish時に既存のリポジトリが存在するとエラーが出て、WEB上でリポジトリを削除する必要がありました。(3敗目) 無事にPublishできたらこんな画面が見れるはずです。 Localizationパッケージを導入する。 com.unity.localization を Addしてください。私はlocalizationの後ろにスペースを入れてしまったため、エラーが出てなかなかインポートできませんした。(4敗目) こんな感じでLocalizationが追加されました。 Logsのignoreを忘れているとGitHub Desktopがこんなことになります。.gitignoreを修正し、GitHub側のLogsを削除して、このChangeが出なくなるまで頑張りましょう。どうにもならない場合は最悪、最初からやり直しましょう。 Localization Tableの作成 テキストを英語ロケールのときにはtextに置き換えて表示したい、という例を考えます。まずはProject Settings>Localizationを見て、EnglishとJapaneseを選びます。 これだけでも44個のファイルが追加されます。 Localization Tableを作りましょう。KeyはGUI上から選択する名前になるので命名規則をしっかり考えておくほうがよいでしょう。今回はテストなので適当です。 Localization String Eventの設定 Textオブジェクトに対してAdd ComponentでLocalizate String Eventを追加し、String Referenceで先ほど作ったテーブルとキーを選択します。 そうしてGizmosの下にあるロケールをEnglishに変更すると、ほら、変わらない...。(5敗目) 正しいやり方は Text付近で右クリックをしてLocalizeをクリックすることです。こうすると、Update String イベント発生時にTextの値を変更する設定が自動追加されます。 再度挑戦。無事textに変換されました。 Buildしてみた。 ビルドしてちゃんと英語ロケールになるかを確認しましょう。Project Settingsを下記のように変更して、 実行、しても日本語のままです。(6敗目) ということで、ここからが本当の地獄の始まりなのでした。 最後に 某案件で多言語対応の必要性がありLocalizationPackageを使ってテーブルを作ったものの、ビルド時に英語にならない不具合に遭遇したため、検証用環境を作ろうと思ったのが本記事の執筆の動機でした。GitHub Desktopを駆使してLocalizationPackageがまともに動くようになりましたら、その内容についても備忘録として記事化しようと思います。 (7/31追記)解決への道筋 まず、この単純なサンプルについては、9.Build Preview and configuration の通りに実行することで英語ロケールになることが確認できました。 本当の問題はLocalization-0.9で対応してしまった別プロジェクトで、この方法を実行するとロケール設定がぶっ壊れる現象を何とかしたかったのです。が、ここから学び、出した結論は「一からやり直し」でした。幸いmasterはLocalization対応していない状態なので、改めてブランチを切って、Localization-0.10をインストールして、一ヶ所だけ英語に変えてみたら問題なく動きました。残り85個分のテキストを再度Localize設定する作業にこれから入ります...。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】エディター拡張の右クリック

概要 エディター拡張時に右クリックのメニューをやってみました。 新しいUIElementsとGUI システム (IMGUI)の両方のパターンがあります。 UIElementsの場合 // ラベルを右クリックした時の動作 VisualElement label = new Label("Hello World! From C#"); label.RegisterCallback<MouseDownEvent>((evt) => {   if (Event.current.button == 0) { // 左クリック } else if (Event.current.button == 1) { GenericMenu menu = new GenericMenu(); menu.AddItem(new GUIContent("右クリックメニュー"), false, () => Debug.Log("右クリックメニュー")); menu.ShowAsContext(); } }); UI システム (IMGUI)の場合 if(GUILayout.Button("ボタン")) { if (Event.current.button == 0) { // 左クリック } else if (Event.current.button == 1) { // 右クリック GenericMenu menu = new GenericMenu(); menu.AddItem(new GUIContent("右クリックメニュー"), false, () => Debug.Log("右クリックメニュー")); menu.ShowAsContext(); } } 終わりに どちらにしても判定の後にEvent.current.buttonで分けるみたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む