【Unity】UnityWebRequestを使ってCRUD機能を実装する【Ruby on Rails】

Ruby on Rails
マイケル
マイケル
みなさんこんにちは!
マイケルです!
エレキベア
エレキベア
こんにちクマ〜〜〜
マイケル
マイケル
今日は UnityWebRequest の使い方について見ていくよ!
エレキベア
エレキベア
UnityWebRequestとは何クマ??
マイケル
マイケル
UnityからWebサーバと通信するために使われる機能だよ!
今回はこれを使って下記のような
CRUD機能(作成、検索、更新、削除)を作ってみます!
01 api test↑今回作るもの
エレキベア
エレキベア
WEBアプリとかで最初に作ったりするやつクマね
マイケル
マイケル
コードはUnity側だけになるけどGitHubにあげているので
参考にお使いください!

GitHub(masarito617) – unity-web-request-sample

マイケル
マイケル
それではやっていこう!
エレキベア
エレキベア
楽しみクマ〜〜〜〜
スポンサーリンク

参考書籍

マイケル
マイケル
今回は特に書籍は使わずに、
下記の公式リファレンスを参考にしています!

UnityWebRequest – Unityスクリプトリファレンス

マイケル
マイケル
WEBAPIについては下記書籍がおすすめです!
少し古いけどWebの歴史から知ることができるので読み物としても面白いですね。

Webを支える技術 ―― HTTP,URI,HTML,そしてREST WEB+DB PRESS plus

エレキベア
エレキベア
これは定番クマ〜〜〜

UnityWebRequestの使い方

マイケル
マイケル
まずは簡単な使い方についてみていこう!
基本的には下記のようにリクエストを作成してレスポンスを受け取ります。
SendWebRequest は非同期のためコルーチン内で実行する必要があります。
//リクエスト作成
UnityWebRequest request = UnityWebRequest.Get(【URL】);

// リクエスト送信
yield return request.SendWebRequest();

// レスポンスを受け取る
request.downloadHandler.text
↑基本的な使い方
エレキベア
エレキベア
シンプルクマね
マイケル
マイケル
リクエストに関しては GET/POST/PUT/DELETE
それぞれ作成することができます。
この時、POSTに関してはWWWFormを使用して値を送ることができます。
UnityWebRequest.Get(【URL】);
UnityWebRequest.Post(【URL】, 【データ】);
UnityWebRequest.Put(【URL】, 【データ】);
UnityWebRequest.Delete(【URL】);
↑リクエストの作成
// formデータの作成
WWWForm form = new WWWForm();
form.AddField("name1", "value1");
form.AddField("name2", "value2");

// リクエスト作成
UnityWebRequest request = UnityWebRequest.Post(【URL】, form);
↑WWWFormを使用したリクエスト作成
エレキベア
エレキベア
値を送るのもこれなら簡単クマね
マイケル
マイケル
最後にContent-Typeについてですが、
UnityWebRequestはデフォルトでapplication/x-www-form-urlencodedで送信されます。
application/jsonで送信したい場合には、下記のようにUploadHandlerRaw、DownloadHandlerBufferを自身でnewして送る必要があるようです。
// byte配列に変換
var bodyRaw = Encoding.UTF8.GetBytes(【JSON】);

// リクエスト作成
var request = new UnityWebRequest(【URL】, "POST");
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
↑application/jsonでの送信
エレキベア
エレキベア
JSON形式で送りたい場合もよくありそうクマね
マイケル
マイケル
それじゃ使い方はこの辺にしておいて、さっそく使っていこう!

CRUD機能の実装

APIサーバの作成

マイケル
マイケル
送信先が必要なので、簡単なAPIサーバを準備します。
ここではRuby on Railsを使って、下記のBookテーブルを操作するだけの機能を実装しました。

Bookテーブル構成

本ID名前価格
id : integername : stringprice : integer
エレキベア
エレキベア
かなりシンプルなテーブルクマね
マイケル
マイケル
APIサーバの作成について詳細は省きますが、
参考までにコマンドを載せておきます!
# api_testプロジェクト作成
rails new api_test --api --skip-action-mailer --skip-action-mailbox --skip-action-text --skip-action-cable --skip-action-mailer --skip-action-mailbox --skip-action-text --skip-action-cable
cd api_test

# Bookリソースの作成
bin/rails g resource book name:string price:integer

# テーブル作成
bin/rails db:migrate
↑APIサーバの作成
マイケル
マイケル
この状態で起動してhttp://127.0.0.1:3000にアクセスすると、
お馴染みのWelcome画面が表示されます!
# サーバ起動
bin/rails server
ScreenShot 2021 12 11 23 28 26
エレキベア
エレキベア
これだけでサーバ立てられるのは便利クマね〜〜〜
マイケル
マイケル
resourceコマンドで作成したため、ルーティングは下記のように設定されています。
これらの処理をcontrollerクラスに記述しましょう!
GET    /books     books#index
POST   /books     books#create
PUT    /books/:id books#update
DELETE /books/:id books#destroy
↑routes情報
class BooksController < ApplicationController
  # 全てのBook情報を返す
  def index
    @books = Book.all()
    render json: {
      books: @books
    }
  end

  # Bookレコードを作成する
  def create
    @book = Book.new()
    @book.name = params[:name]
    @book.price = params[:price]
    if @book.save
      render json: @book
    end
  end

  # 指定IDのBookを更新する
  def update
    @book = Book.find(params[:id])
    @book.name = params[:name]
    @book.price = params[:price]
    if @book.save
      render json: @book
    end
  end

  # 指定IDのBookを破棄する
  def destroy
    @book = Book.find(params[:id])
    @book.destroy
  end
end
↑各処理の記述
マイケル
マイケル
これでサーバ側の最低限の準備はできました!
あとは、http://127.0.0.1:3000/books にUnity側からリクエストするようにしていきましょう。
エレキベア
エレキベア
楽しみクマ〜〜〜〜

Unity側の実装

UI構成
マイケル
マイケル
UIの構成についてですが、
今回は下記のような本アイテムのプレハブをScrollViewの中に生成する
といった構成にしました。
ScreenShot 2021 12 11 23 30 07↑本アイテムの構成
ScreenShot 2021 12 11 23 30 40↑ScrollViewの中に作成する
マイケル
マイケル
プレハブには下記のようなスクリプトをアタッチしておきます。
事前に各UIを割り当てて置くことでGetComponentする数を減らすことができます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 本アイテムクラス
/// </summary>
public class BookItemContent : MonoBehaviour
{
    public Text idText;
    public InputField nameInput;
    public InputField priceInput;
    public Button updateButton;
    public Button deleteButton;
}
↑本アイテムスクリプトの作成
エレキベア
エレキベア
いちいち各フィールドをGetComponentしていたら効率が悪いクマね
本リストの表示
マイケル
マイケル
まずは本リストの表示機能から!
下記のように、起動時とリロードボタン押下時に取得して表示するようにします。
02 read↑本リストの表示
エレキベア
エレキベア
GETリクエストを送信して
レスポンスから本アイテムを生成するクマね
マイケル
マイケル
まず本リスト全体を管理するスクリプトを作成し、共通で使用する送信処理を用意しました。
下記のように
リクエストとコールバックを渡して、レスポンスが返ってきたらコールバックを実行する
といった処理にします。
    /// <summary>
    /// リクエストを送る
    /// </summary>
    /// <param name="request"></param>
    /// <param name="callback"></param>
    private IEnumerator SendRequest(UnityWebRequest request, Action<string> callback = null)
    {
        // リクエストを送る
        yield return request.SendWebRequest();
        
        // レスポンスを出力
        if (request.result == UnityWebRequest.Result.ConnectionError 
            || request.result == UnityWebRequest.Result.ProtocolError)
        {
            Debug.Log(request.error);
        }
        else
        {
            // コールバックを実行
            if (callback != null)
            {
                callback(request.downloadHandler?.text);
            }
        }
    }
↑送信処理の作成
マイケル
マイケル
そしてこの送信処理をGetBookList()内から呼び出します!
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

/// <summary>
/// 本リスト管理クラス
/// </summary>
public class BookListManager : MonoBehaviour
{
    [SerializeField] private GameObject bookScrollRect; // 本リストスクロールエリア
    [SerializeField] private GameObject bookItemPrefab; // 本アイテムPrefab
    
    /// <summary>
    /// 開始処理
    /// </summary>
    private void Start()
    {
        // 本情報を取得
        GetBookList();
    }

    /// <summary>
    /// 全ての本アイテムを取得する
    /// READ
    /// </summary>
    private void GetBookList()
    {
        // リクエストを送る
        var url = "http://127.0.0.1:3000/books";
        var request = UnityWebRequest.Get(url);
        StartCoroutine(SendRequest(request, response =>
        {
            var schema = JsonUtility.FromJson<BookSchemaArray>(response);
            foreach (var bookSchema in schema.books)
            {
                // 取得した本アイテムをリストに追加
                var obj = Instantiate(bookItemPrefab, bookScrollRect.transform);
                var bookItemContent = obj.GetComponent<BookItemContent>();
                bookItemContent.idText.text = bookSchema.id.ToString();
                bookItemContent.nameInput.text = bookSchema.name;
                bookItemContent.priceInput.text = bookSchema.price.ToString();
                bookItemContent.updateButton.onClick.AddListener(() => { PushUpdateButton(bookItemContent); });
                bookItemContent.deleteButton.onClick.AddListener(() => { PushDeleteButton(bookSchema.id); });
            }
        }));
    }

・・・略・・・

    /// <summary>
    /// 本リストをクリアする
    /// </summary>
    private void ClearBookList()
    {
        foreach (Transform child in bookScrollRect.gameObject.transform)
        {
            Destroy(child.transform.gameObject);
        }
    }

    /// <summary>
    /// JSON型定義
    /// </summary>
    [Serializable]
    private class BookSchema
    {
        public int id;
        public string name;
        public int price;
    }
    [Serializable]
    private class BookSchemaArray
    {
        public BookSchema[] books;
    }

    // ---------- 各ボタン押下処理 ----------
    public void PushReloadButton()
    {
        ClearBookList();
        GetBookList();
    }

・・・略・・・

}
↑取得処理の実装
マイケル
マイケル
コールバック内で返ってきたJSONをclassに変換してUIに設定しています。
JsonUtility.FromJsonを使って変換することができますが、classには[Serializable]を付ける必要があることには注意です。
エレキベア
エレキベア
これで表示機能はできたクマね
本アイテムの追加
マイケル
マイケル
次にアイテムの追加について!
入力された内容をPOSTリクエストで送信しますが、
ここではapplication/jsonを使用した送信処理を実装してみました。
03 create↑本アイテムの追加
    /// <summary>
    /// 本アイテムを追加する
    /// CREATE
    /// </summary>
    /// <param name="bookItemContent">追加する本情報</param>
    private void AddBookItem(BookItemContent bookItemContent)
    {
        // jsonデータの作成
        var schema = new BookSchema
        {
            name = bookItemContent.nameInput.text,
            price = int.Parse(bookItemContent.priceInput.text)
        };
        var json = JsonUtility.ToJson(schema);
        
        // byte配列に変換
        var bodyRaw = Encoding.UTF8.GetBytes(json);
        
        // リクエストを送る
        var url = "http://127.0.0.1:3000/books/";
        var request = new UnityWebRequest(url, "POST");
        request.uploadHandler = new UploadHandlerRaw(bodyRaw);
        request.downloadHandler = new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "application/json");
        StartCoroutine(SendRequest(request, response =>
        {
            // 本情報を再取得
            ClearBookList();
            GetBookList();
        }));
    }

・・・略・・・
    
    public void PushAddButton(BookItemContent bookItemContent)
    {
        AddBookItem(bookItemContent);
    }
↑追加処理の実装
マイケル
マイケル
最小限のため、入力チェック処理が入っていないことには注意です!
エレキベア
エレキベア
(手抜きクマ・・・。)
本アイテムの更新
マイケル
マイケル
次に本アイテムの更新処理について!
これもapplication/jsonでPUTリクエストを送信します。
04 update↑本アイテムの更新
    /// <summary>
    /// 本アイテムを更新する
    /// UPDATE
    /// </summary>
    /// <param name="bookItemContent">更新する本情報</param>
    private void UpdateBookItem(BookItemContent bookItemContent)
    {
        // jsonデータの作成
        var schema = new BookSchema
        {
            name = bookItemContent.nameInput.text,
            price = int.Parse(bookItemContent.priceInput.text)
        };
        var json = JsonUtility.ToJson(schema);
        
        // byte配列に変換
        var bodyRaw = Encoding.UTF8.GetBytes(json);
        
        // リクエストを送る
        var url = "http://127.0.0.1:3000/books/" + bookItemContent.idText.text;
        var request = new UnityWebRequest(url, "PUT");
        request.uploadHandler = new UploadHandlerRaw(bodyRaw);
        request.downloadHandler = new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "application/json");
        StartCoroutine(SendRequest(request, response =>
        {
            // 本情報を再取得
            ClearBookList();
            GetBookList();
        }));
    }

・・・略・・・

    private void PushUpdateButton(BookItemContent bookItemContent)
    {
        UpdateBookItem(bookItemContent);
    }
↑更新処理の実装
エレキベア
エレキベア
追加処理とほぼ同じクマね
本アイテムの削除
マイケル
マイケル
そして最後に削除処理について!
これはシンプルでidを付与したURLをDELETE送信するだけになります。
05 delete↑本アイテムの削除
    /// <summary>
    /// 本アイテムを削除する
    /// DELETE
    /// </summary>
    /// <param name="id">削除する本ID</param>
    private void DeleteBookItem(int id)
    {
        // リクエストを送る
        var url = "http://127.0.0.1:3000/books/" + id;
        var request = UnityWebRequest.Delete(url);
        StartCoroutine(SendRequest(request, response =>
        {
            // 本情報を再取得
            ClearBookList();
            GetBookList();
        }));
    }

・・・略・・・

    private void PushDeleteButton(int id)
    {
        DeleteBookItem(id);
    }
↑削除処理の実装
マイケル
マイケル
以上で最低限のCRUD機能の実装完了です!!
エレキベア
エレキベア
楽勝だったクマね
WWWFormを使用する場合
マイケル
マイケル
ちなみにWWWFormでPOST、PUTリクエストを送る場合には
下記のようにリクエストを作成します!
PUTメソッドではWWWFormを使用できないため、
POSTリクエスト作成後にrequest.methodにPUTを指定するようにします!

    /// <summary>
    /// 本アイテムを追加する(WWWForm使用)
    /// CREATE
    /// </summary>
    /// <param name="bookItemContent">追加する本情報</param>
    private void AddBookItemForm(BookItemContent bookItemContent)
    {
        // formデータの作成
        var form = new WWWForm();
        form.AddField("name", bookItemContent.nameInput.text);
        form.AddField("price", int.Parse(bookItemContent.priceInput.text));
        
        // リクエストを送る
        var url = "http://127.0.0.1:3000/books";
        var request = UnityWebRequest.Post(url, form);
        StartCoroutine(SendRequest(request, response =>
        {
            // 本情報を再取得
            ClearBookList();
            GetBookList();
        }));
    }

    /// <summary>
    /// 本アイテムを更新する(WWWForm使用)
    /// UPDATE
    /// </summary>
    /// <param name="bookItemContent">更新する本情報</param>
    private void UpdateBookItemForm(BookItemContent bookItemContent)
    {
        // formデータの作成
        var form = new WWWForm();
        form.AddField("name", bookItemContent.nameInput.text);
        form.AddField("price", int.Parse(bookItemContent.priceInput.text));

        // リクエストを送る
        var url = "http://127.0.0.1:3000/books/" + bookItemContent.idText.text;
        var request = UnityWebRequest.Post(url, form);
        request.method = "PUT"; // PUTを指定
        StartCoroutine(SendRequest(request, response =>
        {
            // 本情報を再取得
            ClearBookList();
            GetBookList();
        }));
    }
↑WWWFormを使用した送信
マイケル
マイケル
というのも、htmlのformでもPUT、DELETE送信がサポートされていないためだと思われます。
htmlから送る場合にもhiddenパラメータとして_methodを指定して送るのが定番となっているため、同じようなイメージで使用するといいと思います。
エレキベア
エレキベア
とりあえずWWWFormを使う時にはPOST送信クマね

おわりに

マイケル
マイケル
というわけで今回はUnityでCRUD機能を実装してみました!
どうだったかな?
エレキベア
エレキベア
クライアントサーバ連携がめんどくさそうだったクマが、
やってみたら思っていたよりも簡単に出来てびっくりしたクマ
マイケル
マイケル
通信処理はソシャゲを作る際には避けて通れないので
どんどん触って慣れていこう!
マイケル
マイケル
それでは今日はこの辺で!
アデュー!!!
エレキベア
エレキベア
クマ〜〜〜〜〜

【Unity】UnityWebRequestを使ってCRUD機能を実装する【Ruby on Rails】〜完〜

コメント