ChatGPTとCesiumで世界中を空中散歩【Cesium for Unity + Google Photorealistic】

<br>
入力した場所にワープして空中散歩できるものを作りました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">【ChatGPT連携】<br>先日試してみた「Cesium + Unity + Google Photorealistic」にChatGPT連携してみた。<br>テキスト入力した場所の緯度経度をChatGPTのAPIで調べて、その場所に行けるようした。<br>世界中を空中散歩。 <a href="https://twitter.com/hashtag/cesium?src=hash&amp;ref_src=twsrc%5Etfw">#cesium</a> <a href="https://twitter.com/hashtag/ChatGPT?src=hash&amp;ref_src=twsrc%5Etfw">#ChatGPT</a> <a href="https://twitter.com/hashtag/unity?src=hash&amp;ref_src=twsrc%5Etfw">#unity</a> <a href="https://t.co/aJu8YV2A4G">pic.twitter.com/aJu8YV2A4G</a></p>&mdash; たつや (@tatsuya1970) <a href="https://twitter.com/tatsuya1970/status/1680818346252705794?ref_src=twsrc%5Etfw">July 17, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


Google Photorealistic 3D Tiles　をCesium for Unity に反映させて、ChatGPTを連携させてます。

## 私の環境

・Windows 11
・Unity 2021.3.4f1



## 作りかた

まずは、Cesium に Google Photorealistic　を反映してUnity上で動かすための設定をします。
こちらのサイトを見ながらやりました。

https://lilea.net/lab/how-to-setup-photorealistic-3d-tiles-for-cesium-for-unity/


### カメラをセット

次に、WSAD で操作できるカメラをセットします。

MainCameraを削除して、
DynamicCamera を 

Packages > Cesium for Unity > Runtime > Resources > DynamicCamera
[![Image from Gyazo](https://i.gyazo.com/6efd3b0ac048b144d463ddb7c3582786.png)](https://gyazo.com/6efd3b0ac048b144d463ddb7c3582786)

Hierarchy の　CesiumGeoreferenceの中へ
[![Image from Gyazo](https://i.gyazo.com/8515fc2c3319b0d6a2c4f0c7b83df3cc.png)](https://gyazo.com/8515fc2c3319b0d6a2c4f0c7b83df3cc)


### カメラの位置をセット

DynamicCamera のInspector にある
Cesium Globe Anchor のPosition に緯度・経度・高さを入力します。

[![Image from Gyazo](https://i.gyazo.com/ceda86eb0b150ddb0a34b168d87c5bfc.png)](https://gyazo.com/ceda86eb0b150ddb0a34b168d87c5bfc)

この緯度・経度は広島駅上空です。

こんな感じでカメラを自由自在に移動することができます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">広島を空中散歩 <a href="https://twitter.com/hashtag/Cesium?src=hash&amp;ref_src=twsrc%5Etfw">#Cesium</a> <a href="https://twitter.com/hashtag/Unity?src=hash&amp;ref_src=twsrc%5Etfw">#Unity</a> <a href="https://twitter.com/hashtag/Photorealistic?src=hash&amp;ref_src=twsrc%5Etfw">#Photorealistic</a> <a href="https://t.co/lolZgsMTqe">pic.twitter.com/lolZgsMTqe</a></p>&mdash; たつや (@tatsuya1970) <a href="https://twitter.com/tatsuya1970/status/1677977523714691072?ref_src=twsrc%5Etfw">July 9, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



### 入力用のUIを作る

こんな感じでUIを作ります。
[![Image from Gyazo](https://i.gyazo.com/99100f644f3c283d459c821aec211b3e.png)](https://gyazo.com/99100f644f3c283d459c821aec211b3e)

resText : 都市名やエラーメッセージを表示させるところ
InputField: 行きたい場所を入力するフィールド
Button ：　ボタン


### コード書く前の下準備

JSONを使うために
JSON Object アセットをインストールします。
https://assetstore.unity.com/packages/tools/input-management/json-object-710?locale=ja-JP

非同期処理をするために、
以下のGithubから「Unitask」ライブラリ をインポートします。
https://github.com/Cysharp/UniTask/releases

OpenAIのAPI keyを取得します。
https://platform.openai.com/


### スクリプト

コードはこちら
https://gist.github.com/tatsuya1970/42bfb8722e5dc2ac26ea2cb156f5953b

スクリプトを　ButtonのInspectorへアタッチし、
Button > OnClick() を以下のように設定

[![Image from Gyazo](https://i.gyazo.com/3821f99e48325dd2f3ec27e2f2c83c45.png)](https://gyazo.com/3821f99e48325dd2f3ec27e2f2c83c45)


### コードの解説

緯度・経度を取得できるAPIは他にあると思いますが、今回はChatGPTに取得してもらいました。

ChatGPTに以下のプロンプトを投げます。（この例では東京）

```
Please answer the latitude, longitude and elevation of Tokyo in the following units and formats.
#Units 
Latitude and longitude: decimal system 
Elevation: meters
#Format 
latitude:xxx.xxxxx  
longtitude:xxx.xxxxx  
elevation:xxxx.x
```

このあたりのコードはこちら　（[こちらのgist](https://gist.github.com/tatsuya1970/42bfb8722e5dc2ac26ea2cb156f5953b)の45行目から71行目）

```chat.cs

string mes = inputField.text;　//ユーザーが入力した内容を取得
string mes2 = mes;　//一時的に格納（あとで使う）
        
//ChatGPTに投げるプロンプト
mes = "Please answer the latitude, longitude and elevation of " + mes + " in the following units and formats."
+ "#Units Latitude and longitude: decimal system Elevation: meters"
+ "#Format latitude:xxx.xxxxx  longtitude:xxx.xxxxx  elevation:xxxx.x";

var url = "https://api.openai.com/v1/chat/completions";
        
//OpenAIに投げるJSON
string itemJson
= "{ \"model\": \"gpt-3.5-turbo\"," +
  "\"messages\": [" +
  "{ \"role\": \"user\", \"content\": \"" + mes + "\"}]}";
            
//以下、OpenAIのAPIへJSONを投げる
body item = JsonUtility.FromJson<body>(itemJson);
string serialisedItemJson = JsonUtility.ToJson(item);
UnityWebRequest request = new UnityWebRequest(url, "POST");
byte[] postData = System.Text.Encoding.UTF8.GetBytes(serialisedItemJson);
request.uploadHandler = new UploadHandlerRaw(postData);
request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
request.SetRequestHeader("Authorization", "Bearer OPENAI_API_KEY"); //OPENAIのAPIキーを入れる

var operation = request.SendWebRequest();
```


すると、こう返ってきます。

```
Latitude: 35.68950\nLongitude: 139.69171\nElevation: 17.0 meters
```

これから数字だけ取り出します。

コードは以下　（[こちらのgist](https://gist.github.com/tatsuya1970/42bfb8722e5dc2ac26ea2cb156f5953b)の87行目-129行目）

※コードの半分くらいはChatGPTと対話して作りました。

```chat.cs

string text = request.downloadHandler.text;
JSONObject json = new JSONObject(text);
JSONObject choices = json.GetField("choices");
JSONObject message = choices[0].GetField("message");
string content = message.GetField("content").ToString();         
string content2 = content.Replace("\"", "");
  
//以下、 ChatGPTの返答から数字のみ抽出
string pattern = @"(-?\d+\.\d+)"; //数字のみ          
MatchCollection matches = Regex.Matches(content2, pattern);
if (matches.Count >= 3)
    {
     if (double.TryParse(matches[0].Value, out latitude) && double.TryParse(matches[1].Value, out longitude) && double.TryParse(matches[2].Value, out height))
         {
          // 文字列を正常に数字に変換できた場合の処理
          Debug.Log("Latitude: " + latitude);
          Debug.Log("Longitude: " + longitude);
          Debug.Log("Elevation: " + height);
          GameObject dynamicCamera = GameObject.Find("DynamicCamera"); 
          //DynamicCameraの緯度経度を入力（なぜか緯度と経度の順番が逆）  
          globeAnchor = dynamicCamera.GetComponent<CesiumGlobeAnchor>();              globeAnchor.SetPositionLongitudeLatitudeHeight(longitude, latitude, height+500);  //height+500とは500メートル上空という意味
         }
         else
         {
         // 数字への変換に失敗した場合の処理
            responseText.text = "Failure";
            await Task.Delay(3000);
            responseText.text = "";
          }
      }
      else
      {
        // パターンに一致する数字が足りない場合の処理
          responseText.text = "Failure";
          await Task.Delay(3000);
          responseText.text = "";
       }
}
```

これで、好きな場所にワープできます。



以上