utymapで現実世界を散歩してみた
現実世界を散歩するとか、当たり前すぎて何言ってるの?という感じですね。
今回は、以前からやりたかった「現実世界の地図データ」を用いて、
アプリに現実世界を取り込んでしまおう!という試みです。
地図データは、オープンソースで公開/制作されている「OpenStreetMap」が使えます。
OpenStreetMap Japan | 自由な地図をみんなの手に/The Free Wiki World Map
このOSMのデータを解析して3Dモデルを描画するしくみとしては、
すでに「ActionStreetMap」というライブラリがあります。
こちらもオープンソースですね。
しかし、このActionStreetMapは開発が終了?しており、
後継の「utymap」を現在開発中のようです。
今回はこのutymapのデモを動かすところまで、を試してみました。
utymapの環境準備と各種ソースのビルド
utymapはプラットフォーム非依存の「core」DLLと、
Unity向けのデモソースが付属されています。
これらの環境を用意するための奮闘記録はWikiにまとめています。(丸2日かかった)
デモで遊んでみる
起動できたら、「PlayGame」を選択します。
「Start location」の「Name」欄に地名を入力し、「Search by name」すると、
検索結果がいくつか出てくるので、訪れたい場所を選びます。
(漢字もうけつけるが、たまに中国の地名と被っていることもある)
「Use device location」というのは恐らく、Androidなんかで
端末から位置情報を取得できるのではないかと思いますが、
今回はWindowsPCでの実行なので使えませんでした。
「Select scene」でデモの種類を選択すると、始まります。
- 「Street level」:キャラクターを操作して歩く3Dデモ
- 「Bird eye level」:真上から見た平面図デモ(操作不可)
- 「Globe level」:地球儀風デモ(操作不可)
↓Street level はこんな感じ
建物はだいたいキューブになっています。
高さが一律で適当なのか、超高層ビル群みたいになっています。
マップはタイル式になっており、端まで行くと
ストリーミングで次のマップが読み込まれます。これぞオープンワールド。
推奨される使い方としては、マップデータをローカルで持っておいて
オフラインで動くようにすること、なので、あくまでデモ用ですね。
よし、家の近所から南下して、淀川越えをやってみよう!
と思って歩いたところ、
橋がない!詰んだ!(もちろん水面ポリゴン上も歩けますが)
プロシージャルメッシュでTerrainを生成している都合上、制約は多いですね。
- トンネルは作れない(高架下をくぐる、とかできない)
- 標高データの反映はまだ(山、坂道、丘)
- 特徴的な建物は自前で差し替えが必要
現実世界を取り込むのは難しい・・・ということがこのデモでわかりました。
ただキューブが並んでいるだけでは、今どこにいるのかさっぱりわからないのです。
しかし、「埋め込まれたスポット情報を基になにかをする」
ということが期待できるので、可能性は広がりましたね!
2016年のふりかえりと未来の話
新年 あけまして おめでとうございます。(2017年、平成29年)
長らく放置しておりました当ブログですが、
方向性を変えて復帰することにしました。
まえおき
まず、ブログに技術情報を載せると、一覧性に欠ける(サイト内検索前提になる)
のが微妙だったので、独自のwikiにまとめることにしました。
http://chronoa.com/wiki/index.html(自宅サーバー移行につき閉鎖)
http://chronoa.dip.jp/chronote-wiki/index.html
それと、資産を外部のWEBサービスに全てのっけるのも微妙かなぁと。
なくなってもいいと思えるもの(宣伝ページなど)を外部に委ねるのが
個人的な理想となりました。
今後ブログには、Unityに限らない雑記とかが増えるかと思います。
Twitterで連投するにはちょっと・・・といった話とか。
ちょっとつぶやいたけど深く追いかけてみた話とか。
2016年のふりかえり
- チュウニズムとチュウニプレイヤーとの出会い
- 旨い酒の楽しみ方を知る(ゲームバーにて)
- 日本人にはウケないが、アジア人にはウケが良いことを知る
- ここでは言えないなにかを知る
- はじめてスマホゲーム(シャドウバース)にハマり課金デビュー
- 個人制作がちょびっと進んでやりたいことが見つかる
- 空き時間でUnreal検証をしたら次の仕事で使えるようになる
- VRの仕事に携わり始める
- 多いので以下略
なんだかんだ、仕事もプライベートも濃い一年でした。
現実逃避で始めた遊びも、いい経験になりました。
2017年の目標
- VR出展で世界に恥じないものに仕上げる
- 社内勉強会の習慣づけ
- 顧客の都合に左右されない開発環境の構想を練る
- 個人制作をなんらかの形で公開
仕事がたいへん充実しております。
プライベートがどうなるかは、まったく予測がつかないです。
人との交流を増やしたい。そのために発信せねば。
その先の未来の話
上記「顧客の都合に左右されない開発環境の構想」の詳細です。
恐らくNDA的な関係で語られてはいませんが、Unrealを採用するには予算的に厳しく、
顧客としては都合が悪い場合があります。
そのため、Unreal採用を前提に組織を作ると、安定性に欠けてしまいます。
(フリー版を採用できれば、それがベストですが・・・ロイヤリティが足を引っ張る)
Unityでワークフローを整えたほうが会社的には(顧客的にも)嬉しいという結論になったので、
Unityのベストワークフローを構築し、アドバンテージを生む以下構想(仮)。
・ビジュアルスクリプティング環境をひとつ確定する(Unity公式が対応するまでの繋ぎ)
→他セクションを巻き込んだワークフローへ移行
→エンジニア工数の削減
・各ジャンル別にプロトタイプが作りやすい環境を用意
→ゲームデザイナー主導によるイテレーション効率化
・グラフィックス表現調整に必要なスタッフを安定化
→ShaderForgeを標準と確定して学習する
・ゲーム系エンジニアは、全員エディタ拡張を学習し、ツール作成と挙動実装に集中する
→エンジニアが本来やるべき仕事の実現
・システム系エンジニアは、汎用ライブラリの設計/実装
→すべてのプロジェクトで使える理想的で安定したシステムを実現
・エンジニア工数が空いたら
→さらに技術研究を進めて組織全体の価値を向上
ここまで完全なものにするには、3~5年くらいかかるのでは、と思っています。
幸いにして、Unity経験者の方が圧倒的に多い時代なので、
「組織的なルールが整っていること」が最初の一歩、でしょうか。
制作進捗11 ~通信同期検証~
前回は、ルームの作成とマッチングを実装しましたので
今回は、PUN による通信同期とデータ送受信について、検証しました。
最初に言っておくと、全然おもしろい話ではありません。
PhotonView リアルタイム通信
エディタでの操作
リアルタイムに同期させたい GameObject には、PhotonView コンポーネントをアタッチします。
ObservedComponents に同期させたい要素を追加。
種類は Animator / Rigidbody / Rigidbody2D / Transform の4種類。
今回はこちらの方法は使わないので割愛。
スクリプトでの操作
Photon.MonoBehaviour 継承クラスなら photonView に直接アクセスできますが、
標準の MonoBehaviour や GameObject であれば、以下のように参照できます。
PhotonView photonView = PhotonView.Get(this);
※PhotonView コンポーネントは、最低1つアタッチしなければならない
スクリプトから任意の値を指定することで、柔軟な同期が可能です。
// 頻繁に任意の値を同期したい場合、ここで指定する // ストリームに書き込まなければ送信はされない void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) { if (stream.isWriting) { //We own this player: send the others our data stream.SendNext((int)controllerScript._characterState); stream.SendNext(transform.position); stream.SendNext(transform.rotation); } else { //Network player, receive data controllerScript._characterState = (CharacterState)(int)stream.ReceiveNext(); correctPlayerPos = (Vector3)stream.ReceiveNext(); correctPlayerRot = (Quaternion)stream.ReceiveNext(); } }
Photonでサポートされている型
どんな型の値を送受信できるのか、という情報はこちらのページにまとまっています。
Photonでシリアル化 | Exit Games
自分で定義した型をやりとりしたい場合、
Photon に対してメソッドを登録してあげる必要があります。
上記のページでは、Vector2 のオブジェクト(8バイト)を
シリアライズ、デシリアライズするメソッドを定義し、
PhotonPeer.RegisterType() で登録する方法が紹介されています。
PhotonPeer はフレームワークの中身をいじることになるため、情報は一般公開されていません。
\Photon Unity Networking\Plugins\PhotonNetwork\CustomTypes.cs
このソースに追記していくことになります。
通信内容の特性上、やりとりするカスタムオブジェクトが多くなる場合は、
オブジェクトの構造を JSON や YAML などに変換して登録する、というのも手です。
これなら、速度は犠牲になっても、フレームワークの汎用性は損なわれません。
最近だと JSON はやめて、MessagePack を使うことを推奨されているようですが、
De/Serialize にかかる負荷が許容できるようなら、これらの選択肢も無くはない?
ただし、リアルタイム通信用途の話ではないと思われます。
構造体をbyte配列にして一括送信
変数を個別に同期するのは手間、かつ応答回数も増えるので、
構造体単位でまとめて送信できないか、検証してみました。
参考文献
BinaryReader・BinaryWriterでの構造体の読み書き (構造体⇔バイト配列の変換) - Programming/.NET Framework/Tips - 総武ソフトウェア推進所
上記の方法では、BitConverter が対応する基本型しか変換できないため、
例えば構造体フィールドに対して配列や構造体の定義には未対応でした。
後から知ったんですが、 BinaryFormatter を使えば、
どんなフィールドでもまるっとシリアライズできるそうな。
【Unity】みなさん、データの保存ってどうやってます?俺はこうやっちゃってます。 - ハルシオンシステムの気ままBlog
以前はこれを使うと iOS では動かない、と言われていましたが、
なんだか回避方法が見つかっている?また時間作って検証してみます。
リモートプロシージャコール(RPC)
リアルタイム通信とは違って、
任意のタイミングで通信を発生させたい場合(ターン制、ステート同期など)や、
ルーム内のプレイヤーにブロードキャストしたい場合なんかには、この RPC を使います。
受信コールバックには [RPC] Attribute の定義が必要となります。
// RPC送信側 photonView.RPC("OnSyncMyDataRPC", PhotonTargets.All, (byte[])data); // RPC受信側 [RPC] public void OnSyncMyDataRPC(byte[] data, PhotonMessageInfo info) { // dataを受け取る処理を記述 Debug.Log(String.Format("Info: from {0} {1} {2}", info.sender, info.PhotonView, info.timestamp)); }
注意点として、RPC は各クライアントの一致する PhotonView に対して行われるため、
シーンロード中に呼び出す時など、状況が揃わない場合は実行されない。
対策としては、シーンロード前にメッセージキューを停止させる。
// これ以降のネットワークメッセージの処理を一時的に停止する PhotonNetwork.isMessageQueueRunning = false; Application.LoadLevel(levelName);
ロードが終わったらメッセージキュー停止を戻しておく。
RPC を使えば、簡易テキストチャットが作れます。
[RPC] void ChatMessage(string name, string msg) { Debug.Log(name + ": " + msg); } photonView.RPC("ChatMessage", PhotonTargets.All, name, msg);
ハマりポイント
素直に考えれば間違えないけど、頭が固い自分が個人的にハマった点をメモ。
PhotonView の同期は、ルーム内共通の同一オブジェクト(ViewID)に対してやりとりされる。
つまり、それぞれの世界ではお互いのプレイヤーが唯一無二の存在となり、
完全に別オブジェクトに分かれないといけない。
(1つのゲーム機で1画面で格闘ゲームをプレイしている、と考えると腑に落ちる)
例えば2人対戦の時、マスターがプレイヤー1、クライアントがプレイヤー2であれば、
クライアントのゲームから見ても自身はプレイヤー2である必要がある。
これを勘違いして、自身はプレイヤー(1P)、相手はライバル(2P)、という考え方にすると、
お互いがプレイヤー(1P)のデータを同期しあって衝突を起こすことになる。
(2人が別々のゲーム機でオンライン対戦ゲームをプレイしていて、
自分は必ず1P側になる、と考えるとみごとにバグる)
そのため、ゲーム内に複数存在するプレイヤーのうち、
操作プレイヤーをどのオブジェクトに振り分けるのか、といったルールが必要になる。
あとがき
ただの PUN の解説日記になってしまった感。
ゴールデンウィーク中に対戦実装まで完了したかったけど
検証だけで過ぎ去ってしまいました・・・
しばらく平日は体力的に作業ができず、進みが遅いです。あと、花粉。
引き続き、同期通信によるターン制御を進めます。