はじめに
- とあるハッカソンに出場した際、バックエンドを担当しUnityとAI(Stable Diffusion)の連携等を担当したので、備忘録として残す。
- 本記事では、AI側のコードを載せていません。
Unityのバージョンが最新バージョンと違うため(以降記述)、コードが一部違う可能性があります。
ターゲット
- UnityとAI間の連携を行う際にUnity側の挙動を知りたい人。
- そもそもUnityでHTTP通信をどうするか知りたい人。
著者の悩み
- UnityでHTTP通信を試みたが,コードの参考例を見ても余りよく分からなかったので。また、応用を利かせるためにどこを改変すればいいのか分からなかった。
開発環境
ソフトウェア | バージョン |
---|---|
OS | Windows10 |
Unity | 2021.3.14f |
実際に動かして確認してみよう
- Unityを立ち上げます。立ち上げると以下の画像のようになります。
- 画像が表示されたらNew projectをクリックします。クリックすると以下の画像が表示されます。
- 画像のように2Dまたは3D,2D(URP)等のテンプレートが表示されます。この画面はゲームの種類を選択する画面です。今回は3Dを選択します
(2Dでも可能です)。 - 3Dを選択したら,Project nameとLocationを変更します。
名前 | 役割 |
---|---|
Project | プロジェクトの名前です。初期ではMy Projectと設定されています。 |
Location | Unityのプロジェクトの保存場所です。特に変更する必要がなければそのままにしましょう。 |
- 設定が終わったらCreate projectをクリックします。
- 次の画像のようになればプロジェクトの作成完了です。
- 色々なフォルダが用意されていますが、今回はAssets下のみでの作業なので変更するものはありません。
- Assetsは最初Scenesフォルダのみ用意されています。このフォルダには,拡張子.sceneのシーンファイルが入っています。主にゲームの画面を用意するときに使われます。HTTP通信を行うためにはC#のスクリプトが必要になります。今回はC#のスクリプトは1つだけですが複数になった時に管理に困るのでScriptフォルダで管理します。Unity上で右クリックし、Create→Folderをクリックします。参考に画像を示しておきます。
- フォルダ名は任意ですが、今回はScriptとしています。このScriptフォルダ直下にC#のスクリプトを作っていきます。
- Scriptフォルダをクリックします。そうするとScriptフォルダ内に入ります。ここで右クリックしCreate→C#Scriptをクリックします。参考に画像を示しておきます。
- C#のファイルが作成できたらそこにコードを記述していきます(著者は、エディタはVS Codeを使っているのでそこで記述していきます。)C#ファイルをダブルクリックします。以下にC#ファイルをクリックしたときの画面を示します。(VS Codeの場合です)
- C#ファイルに以下のコードをコピペして入りつけてください。
画像ファイルはAssets直下に置きましょう。(Scenes、Script、画像ファイルが並ぶ状態)
ConnectionTest.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.Networking;
using System.IO;
public class ConnectionTest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
StartCoroutine(UPloadFile());
}
// Update is called once per frame
void Update()
{
}
IEnumerator UPloadFile(){
string fileName = "hoge.png";
string filePath = Application.dataPath + "/" + fileName;
// 画像ファイルをbyte配列に格納
byte[] img = File.ReadAllBytes (filePath);
// formにバイナリデータを追加
WWWForm form = new WWWForm ();
form.AddBinaryData ("image", img, fileName, "image/png");
// HTTPリクエストを送る
UnityWebRequest request = UnityWebRequest.Post("https://example.com", form);
yield return request.SendWebRequest ();
//"Save Texture"ダイアログを表示し、選択されたパスを取得する
var path = EditorUtility.SaveFilePanelInProject(title: "Save Texture", defaultName: "test", extension: "png", message: "Save Texture");
//新規の空テクスチャを作成する
var texture = new Texture2D(1, 1);
byte[] bytes = System.Convert.FromBase64String(request.downloadHandler.text);
texture.LoadImage(bytes);
var png = texture.EncodeToPNG();
//PNG形式でエンコード
File.WriteAllBytes(path, png);
//オブジェクトに画像を表示
GetComponent<Renderer>().material.mainTexture = texture;
}
}
- コードについて説明していきます。まずこのコードは画像ファイルをexample.comに送信し、変更された画像をオブジェクトに表示する。という流れになっています。では、順にコードを読み解いていきます。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
これらはC#ファイルを作成時に自動で用意されます。ここに
- using UnityEditor;
- using UnityEngine.Networking;
- using System.IO;
これらを新しく追加します。詳しい説明は省きます。今回の目的に必要なんだな程度の認識で大丈夫です。
- public classの後のConnectionTestは自分が名付けたファイル名と一致します
(今回はConnectionTest.csなのでConnectionTestになっている) - classの後の名前と名付けたファイル名が一致しないとUnity上で上手く動作しません!!
- void Start()とvoid Update()もC#ファイル作成時に自動で用意されます。
関数名 | 挙動 |
---|---|
Start | Unityのプロジェクトを実行した際に1度限り呼ばれる関数 |
Update | Unityのプロジェクトを実行した際に毎フレームごとに呼び出される関数 |
- 今回はリクエスト結果をオブジェクトに表示するだけなので,1度だけのStartのみにコードが書き込まれています。
- IEnumeratorは、戻り値とするメソッドをコルーチンをとして扱うことができます。という特徴があります。コルーチンはいったん処理を中断した後、続きから処理を再開できるという構造になっています。いわばコルーチン内のコードの途中でyield returnがあれば(IEnumeratorを戻り値に持つメソッドの場合returnの前にyieldが必要)途中の結果を返して(途中中断),中断したところから引き続き実行を行うということです。以下に参考画像を示しておきます。
- コードが10数行に分けて書かれていますが,今回の目的はUnityでHTTP通信をおこなうということなので関係するコードのみ説明をします。
- WWWFormは空のWWWFormオブジェクトを作成しています。WWWFormはこの後に出てくるAddBinaryDataを扱うために定義しています。
- AddBinaryDataは空のWWWFormオブジェクトにバイナリデータを追加するメソッドである。主に以下のようシグネチャを持ちます。シグネチャはメソッドの特徴を表す情報です。
public void AddBinaryData(string fieldName, byte[] contents, string fileName = null, string mimeType = null)
- 以下に要素と特徴を示しておきます。
要素 | 特徴 |
---|---|
fieldName | フィールドの名前(キー)。サーバー側でこれを使用してデータを識別します。 |
contents | 追加するバイナリデータ自体を指定します。通常はファイルのバイト配列です。 |
fileName | (オプション): ファイルの名前。サーバーへの送信時に使用されます。 |
mimeType | ファイルのMIMEタイプ(コンテンツタイプ)。例えば、"image/png"や"application/octet-stream"などの値を指定します。 |
- 今回は画像を扱うため、以下のように指定します。
要素 | 指定したパラメータ等 |
---|---|
fieldName | "img" |
contents | img(バイナリデータ) |
fileName | fileName(画像ファイル) |
mimeType | "image/png"(コンテンツタイプ) |
- これでフォームにデータを追加することができました。次に実際にURLに対してPOSTリクエストを送ります。以下がそのコードとなります。
UnityWebRequest request = UnityWebRequest.Post("https://example.com", form);
- このコードは、リクエストのパラメーターやデータが含まれるformを第2引数とし、https://example.com というURLに対してPOSTリクエストを作成します。そしてその結果が変数requestに保存されます。
yield return request.SendWebRequest ();
- これはrequest.SendWebRequestメソッドを呼び出し、リクエストを非同期送信する。前述した通りyield return文を使用してコルーチンを一時停止し、リクエストが完了するまで待機します。リクエストが完了すると次の処理が実行されます。
これでHTTP通信は完了です。
HTTP通信後の処理
- 今回はAI側で画像に変更が加えられているので、変更が加えられている画像の保存をします。
必ずしも保存する必要はありません
var path = EditorUtility.SaveFilePanelInProject(title: "Save Texture", defaultName: "test", extension: "png", message: "Save Texture");
- 以下に上記のコードの具体的な処理を示しておきます。
1. "Save Texture"というタイトルを持つファイル保存ダイアログを表示します。
2. defaultNameパラメーターで指定されたデフォルトのファイル名("test")を使用します。
3. extensionパラメーターで指定された拡張子("png")を持つファイルを保存するように設定します。
4. ダイアログに表示するメッセージ("Save Texture")を設定します。
5. ユーザーがファイルを選択または新しいファイル名を入力し、保存ボタンがクリックされると、選択されたファイルパスがpath変数に格納されます。 - このコードは、ユーザーがテクスチャを保存する場所とファイル名を選択するために使われる。
- 次に、データをロードするテクスチャを用意します。
var texture = new Texture2D(1, 1);
- これで幅1ピクセル、高さ1ピクセルの新しいオブジェクトを作成しています。
byte[] bytes = System.Convert.FromBase64String(request.downloadHandler.text);
上記のコードはダウンロードされたデータがBase64エンコードされている条件で、バイナリデータに戻しているので注意
- 変数requestのダウンロードハンドラからテキストデータを取得し(downloadHandler.text) System.Convert.FromBase64Stringメソッドを使用して、テキストデータをBase64文字列として解析し、バイナリデータ(バイト配列)に変換します。これをbytes変数に格納します。
- バイナリデータからテクスチャデータを復元します。
texture.LoadImage(bytes);
- texture.LoadImage(bytes);メソッドを使用して、texture変数にバイト配列のデータをロードします。このメソッドは、バイナリデータをテクスチャのピクセルデータとして解釈し、テクスチャに適用します。この処理により、変数textureにテクスチャデータがロードされます。
- 後は、AIによって変更された画像をUnity内に保存し、Unity上のオブジェクトに表示します。
var png = texture.EncodeToPNG();
- textureの画像データをもとに拡張子.pngにエンコードしたものを変数pngに格納します。
File.WriteAllBytes(path, png);
-
第2引数で画像ファイル(.png)を指定し、保存先を第1引数に指定しています。これによって、変数pngが変数pathで指定したUnity内の場所に保存されます。
-
最後に、画像をオブジェクトに投影します。
GetComponent<Renderer>().material.mainTexture = texture;
- 以下に上記のコードの具体的な処理を示しておきます。
1. GetComponent()を使用して、オブジェクトにアタッチされたRendererコンポーネントを取得します。GetComponent()は、オブジェクトにアタッチされたレンダラーコンポーネントへのアクセスを可能にする。
2. materialプロパティを使用して、レンダラーが使用しているマテリアルを取得します。
3. 取得したマテリアルのmainTextureプロパティに、指定したtextureを設定します。これにより、マテリアルが使用するメインテクスチャが指定したテクスチャに変更されます。つまり、マテリアルに画像が映し出されます。 - 作成したC#のスクリプトをオブジェクトにドラックアンドドロップをします。(アタッチと言います。)
- 以上でHTTP通信後の処理の説明も終了です。
最後に
ハッカソンというか、みんなで夜な夜な時間かけてゲーム作るのって想像の先をいく楽しさだな~って感じました。