chronote

ゲーム創作活動の備忘録、日々の雑記など。

utymapで現実世界を散歩してみた

 

現実世界を散歩するとか、当たり前すぎて何言ってるの?という感じですね。

今回は、以前からやりたかった「現実世界の地図データ」を用いて、

アプリに現実世界を取り込んでしまおう!という試みです。

 

地図データは、オープンソースで公開/制作されている「OpenStreetMap」が使えます。

OpenStreetMap Japan | 自由な地図をみんなの手に/The Free Wiki World Map

 

このOSMのデータを解析して3Dモデルを描画するしくみとしては、

すでに「ActionStreetMap」というライブラリがあります。

こちらもオープンソースですね。

actionstreetmap.github.io

 

しかし、このActionStreetMapは開発が終了?しており、

後継の「utymap」を現在開発中のようです。

github.com

 

今回はこのutymapのデモを動かすところまで、を試してみました。

 

utymapの環境準備と各種ソースのビルド

utymapはプラットフォーム非依存の「core」DLLと、

Unity向けのデモソースが付属されています。

これらの環境を用意するための奮闘記録はWikiにまとめています。(丸2日かかった)

utymap [chronote.wiki]

 

デモで遊んでみる

起動できたら、「PlayGame」を選択します。

f:id:chronote:20170104180258p:plain

「Start location」の「Name」欄に地名を入力し、「Search by name」すると、

検索結果がいくつか出てくるので、訪れたい場所を選びます。

(漢字もうけつけるが、たまに中国の地名と被っていることもある)

 

「Use device location」というのは恐らく、Androidなんかで

端末から位置情報を取得できるのではないかと思いますが、

今回はWindowsPCでの実行なので使えませんでした。

 

「Select scene」でデモの種類を選択すると、始まります。

  • 「Street level」:キャラクターを操作して歩く3Dデモ
  • 「Bird eye level」:真上から見た平面図デモ(操作不可)
  • 「Globe level」:地球儀風デモ(操作不可)

↓Street level はこんな感じ

f:id:chronote:20170104181226p:plain

建物はだいたいキューブになっています。

高さが一律で適当なのか、超高層ビル群みたいになっています。

マップはタイル式になっており、端まで行くと

ストリーミングで次のマップが読み込まれます。これぞオープンワールド

 

推奨される使い方としては、マップデータをローカルで持っておいて

オフラインで動くようにすること、なので、あくまでデモ用ですね。

 

よし、家の近所から南下して、淀川越えをやってみよう!

と思って歩いたところ、

f:id:chronote:20170104181610p:plain

橋がない!詰んだ!(もちろん水面ポリゴン上も歩けますが)

 

f:id:chronote:20170104181927p:plain

 

プロシージャルメッシュで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
このソースに追記していくことになります。

通信内容の特性上、やりとりするカスタムオブジェクトが多くなる場合は、
オブジェクトの構造を JSONYAML などに変換して登録する、というのも手です。
これなら、速度は犠牲になっても、フレームワークの汎用性は損なわれません。

最近だと 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 の解説日記になってしまった感。
ゴールデンウィーク中に対戦実装まで完了したかったけど
検証だけで過ぎ去ってしまいました・・・
しばらく平日は体力的に作業ができず、進みが遅いです。あと、花粉。
引き続き、同期通信によるターン制御を進めます。