【Unity】Imgur経由でTwitterにゲーム画面を投稿する

ゲーム画面をTwitterでシェアする機能を作りたいけど、全然分からないの~😭

やり方は一つじゃないけど、Imgurという画像共有サービスを用いてTwitterでの画像シェア機能が作れるわ!Twitter APIを使わない手軽な方法なの。

画像共有サービスImgurを用いてTwitterにゲーム画面を投稿する方法の紹介です。

本記事の内容を理解すると、次のようにTwitterの投稿URLを開いて画像をシェアできるようになります。

ゲームアプリからTwitterに画像を投稿する方法は一通りではありませんが、本記事で紹介する方法には次のメリットがあります。

Imgur経由で画像投稿する方法のメリット
  • Twitterのアプリ認証が不要
  • 画像共有のための独自サーバーが不要
  • WebGL環境でも動作可能

アプリ認証やサーバーが不要なことは、開発者とプレイヤー双方にとって大きなメリットでしょう。

ただし、次のようなデメリットも存在します。

Imgur経由で画像投稿する方法のデメリット
  • Imgurサービスを利用するために電話番号によるSMS認証が必須
  • リンク先のアイキャッチ画像として疑似的な画像投稿となる

電話番号を入力しないといけないのはリスクに感じるかもしれませんが、SMS認証以外では使用されず問題になったことはありません。

2つめの疑似的な画像投稿とは、Twitterのメディア投稿ではなくリンク先のアイキャッチ画像が代わりに表示されることです。よく見ると画像投稿と違いが分かりますが、オリジナルの画像を投稿しているように見せることができます。

Twitterへの画像投稿までの流れは次のようになります。

画像投稿までの流れ
  • 画面をTexture2D形式でキャプチャ
  • キャプチャした画像をImgurサーバーにアップロード
  • アップロードした画像URLをImgurから受け取る
  • 画像URLを含んだツイートURLを開く

本記事では、Imgurサービスを利用して、ゲームからTwitterに画像を投稿する方法を解説していきます。

記事を読み進めるにあたり、UnityやC#スクリプト周りの基本知識が必要になりますので、予めご了承ください。

動作環境
  • Unity2021.1.19f1

ゲーム画面のキャプチャ

ゲーム画面をアップロードできるようにするためには、まず画面をTexture2D形式でキャプチャする必要があります。

次の2種類のキャプチャ方法を紹介します。

  • UIを含む画面全体のキャプチャ
  • UIを含まない特定カメラの画面をキャプチャ

それぞれ実装方法が全く異なるので、個別に解説していきます。

UIを含む画面全体のキャプチャ

ゲーム画面全体をTexture2Dとして取得したい場合は、次のメソッド呼び出しだけで完結できます。

public static Texture2D CaptureScreenshotAsTexture(int superSize);
public static Texture2D CaptureScreenshotAsTexture(
    ScreenCapture.StereoScreenCaptureMode stereoCaptureMode
);

ただし、呼び出しタイミングはレンダリング終了時でなければいけません。それ以外のタイミングで呼び出した場合はエラーとなります。

したがって、実際にキャプチャを実行するコードは次のようになります。

private void Update()
{
    // スペースキーを押したらキャプチャ開始とする
    if (Input.GetKeyDown(KeyCode.Space))
    {
        StartCoroutine(CaptureScreenShot());
    }
}

private static IEnumerator CaptureAll()
{
    // レンダリング終了まで待機
    yield return new WaitForEndOfFrame();

    // 画面全体のスクリーンショット取得
    var screenShot = ScreenCapture.CaptureScreenshotAsTexture();

    // TODO : アップロード処理など
}

コルーチンを用い、WaitForEndOfFrameでレンダリング終了まで待機することがポイントです。

引数は高解像度やステレオ視に関する指定ですが、画面全体の解像度そのままでキャプチャしたい場合は省略できます。引数の詳細については、公式ドキュメントをご確認ください。

UIを含まない特定カメラのキャプチャ

キャプチャ時にUIを含めたくない場合は、RenderTextureを用いてカメラの描画内容を取得すれば良いです。

対象カメラの描画先を一時的に独自のRenderTextureに指定してキャプチャし、RenderTextureからピクセルデータをTexture2Dオブジェクト側に転送するといった流れになります。

private Texture2D CaptureCamera(Camera target)
{
    // RenderTextureを作成
    var rt = new RenderTexture(target.pixelWidth, target.pixelHeight, 24);

    // カメラのRenderTextureを一時的に変更してキャプチャ
    var prev = target.targetTexture;
    target.targetTexture = rt;
    target.Render();
    target.targetTexture = prev;

    // ピクセルデータ取得用のテクスチャ作成
    var screenShot = new Texture2D(
        target.pixelWidth,
        target.pixelHeight,
        TextureFormat.RGB24,
        false
    );

    // ピクセル転送
    prev = RenderTexture.active;
    RenderTexture.active = rt;
    screenShot.ReadPixels(new Rect(0, 0, screenShot.width, screenShot.height), 0, 0);
    screenShot.Apply();
    RenderTexture.active = prev;

    return screenShot;
}

Imgurを使えるようにする

Imgurサービスを利用して画像をアップロードするためには、Imgurアカウントを作成し、アプリケーション登録する必要があります。

アカウント登録

以下Imgur公式サイトにアクセスし、右上のSign upよりユーザー登録を行ってください。既にImgurアカウントをお持ちの場合は、Sign inよりそのままログインしてください。

ユーザー登録ページでは、メールアドレスのほか、TwitterやGoogleアカウント等で登録することも可能です。いずれの登録方法でも、電話番号は必ず求められますので、入力後に指示に従ってSMS認証を行ってください。

アプリケーション登録

画像を匿名ユーザーからアップロードするために、Imgurにアプリケーションを登録します。以下リンクより登録を行ってください。

https://api.imgur.com/oauth2/addclient

アプリケーション登録では、次の通り項目を入力します。

  • Application name: アプリケーション名
  • Authorization type: Anonymous usage without user authorizationを選択
  • Authorization callback URL: 任意のURL
  • Email: 自身のEメールアドレス

一通り入力したら、reCAPTCHA認証を行い、Submitボタンをクリックしてください。

アプリケーション登録に成功すると、次の画面に遷移します。

今回は匿名ユーザーからの画像投稿機能のみを使うため、Client IDのみ使います。Client IDをメモしておきます。

なお、登録したアプリケーションのClient IDは、以下ページからいつでも確認可能です。

https://imgur.com/account/settings/apps

以上でアプリケーション登録は完了です。

画像データのアップロード方法

Imgurへの画像データのアップロードは、次のアップロード用API呼び出しで行います。

Request URLhttps://api.imgur.com/3/image
Request MethodPOST
HeadersAuthorization: Client-ID [Client ID]
Form Dataimage: [Base64でエンコードされた画像データ]

参考:Imgur API

上記APIを叩く処理は次のようになります。

Texture2D screenShot;

  ・・・(中略)・・・

// Texture2D→バイナリ変換
var imageBytes = screenShot.EncodeToPNG();
// バイナリ→Base64変換
var imageBase64 = Convert.ToBase64String(imageBytes);


// Form Dataの作成
var formData = new WWWForm();
formData.AddField("image", imageBase64);

// リクエスト作成
using var request = UnityWebRequest.Post("https://api.imgur.com/3/image", formData);
request.SetRequestHeader("AUTHORIZATION", "Client-ID " + ImgurClientID);

// リクエスト実行
yield return request.SendWebRequest();

アップロードする画像データは、Base64形式 [1] のデータに変換する必要があります。

Texture2DをBase64形式のデータに変換するには、Texture2D→バイナリ形式→Base64形式の順に変換処理を実行します。

// Texture2D→バイナリ変換
var imageBytes = screenShot.EncodeToPNG();
// バイナリ→Base64変換
var imageBase64 = Convert.ToBase64String(imageBytes);

そして、UnityWebRequestを用いて必要データを指定して上記APIを叩いています。

// Form Dataの作成
var formData = new WWWForm();
formData.AddField("image", imageBase64);

// リクエスト作成
using var request = UnityWebRequest.Post("https://api.imgur.com/3/image", formData);
request.SetRequestHeader("AUTHORIZATION", "Client-ID " + ImgurClientID);

APIの実行に成功すると、次のようなレスポンスがJSON形式で返されます。

{
  "data": {
    "id": "orunSTu",

    ・・・(省略)・・・

    "link": "http://i.imgur.com/orunSTu.gif"
  },
  "success": true,
  "status": 200
}

このJSONデータの中に、アップロードされた画像のURL等の情報が含まれています。画像URLは、data.linkに格納されています。

Imgur APIを利用する際の注意点

頻繁にアップロードのAPI呼び出しを行いすぎると、アップロード処理に失敗することがあります。そのため、エラー処理を行うことを強く推奨します。

// リクエスト実行
yield return request.SendWebRequest();

// レスポンスチェック
if (request.result != UnityWebRequest.Result.Success)
{
    onError?.Invoke(request.error);
    yield break;
}

// レスポンスデータ(JSON)をパース
var response = JsonUtility.FromJson<Response>(request.downloadHandler.text);

// 成否チェック
if (!response.success)
{
    onError?.Invoke($"アップロードエラー (status : ${response.status})");
    yield break;
}

また、WebGL環境で画像投稿を行う場合、ローカルホスト(http://localhostなど)で実行すると次のようなエラーが返され必ず失敗してしまう状況です(2021/9/11現在)。

{"data":{"error":"Imgur is temporarily over capacity. Please try again later."},
"success":false,"status":403}

原因は、APIを実行するときに、リファラにlocalhostが指定されていると、Imgurサーバー側が必ずエラーを返す挙動になっているためです。

WebGL環境でのテストの際は十分ご注意ください。

Twitterへの投稿

Imgurにアップロードされた、JSONのレスポンスに含まれている画像URL(data.link)から拡張子を除いたURLをツイート文言に含めることで、疑似的に画像投稿ができます。

URLの例

http://i.imgur.com/R9pPUku

Twitterの投稿画面は、ツイート文言を含んだURLを直接開くことで実装できます。

WebGL環境の場合は、新しいウィンドウを開くJavaScriptプラグインを実装して対応する必要があります。Application.OpenURL()メソッドを使うと現在プレイしているゲームが中断されてしまうためです。

WebGL以外の環境では、Application.OpenURL()メソッド呼び出しで対応可能です。

【Unity】WebGL実行時に新しいタブでURLを開く
本記事では、UnityのWebGL製のブラウザゲーム内から新しいタブでURLを開く方法を解説します。 結論を述べると、新しいタブでリンクを開く処理をプラグインとしてJavaScriptで実装し、C#スクリプトからそのJa ...

サンプルスクリプト

ゲーム画面をキャプチャし、Imgur経由でTwitterに画像投稿するまでの流れを実装したサンプルスクリプトです。スペースキーを押すと処理が開始されます。

コード量が多いため、機能ごとにクラス分けしました。

ScreenCapturer.cs
using System.Collections;
using UnityEngine;
using UnityEngine.Events;

public static class ScreenCapturer
{
    /// <summary>
    /// 画面キャプチャ
    /// </summary>
    /// <param name="target">キャプチャ対象のカメラ(nullの場合はUIを含む全画面)</param>
    /// <param name="onCompleted">キャプチャした画像データをTexture2D形式で受け取るコールバック</param>
    public static IEnumerator Capture(
        Camera target,
        UnityAction<Texture2D> onCompleted
    )
    {
        if (target == null)
        {
            yield return CaptureAll(onCompleted);
        }
        else
        {
            yield return CaptureCamera(target, onCompleted);
        }
    }

    // UIを含む全画面キャプチャ
    private static IEnumerator CaptureAll(UnityAction<Texture2D> onCompleted)
    {
        // レンダリング終了まで待機
        yield return new WaitForEndOfFrame();

        // 画面全体のスクリーンショット取得
        var screenShot = ScreenCapture.CaptureScreenshotAsTexture();

        // 結果返却
        onCompleted?.Invoke(screenShot);
    }

    // UIを含まない特定カメラキャプチャ
    private static IEnumerator CaptureCamera(Camera target, UnityAction<Texture2D> onCompleted)
    {
        // RenderTextureを作成
        var rt = new RenderTexture(target.pixelWidth, target.pixelHeight, 24);

        // カメラのRenderTextureを一時的に変更してキャプチャ
        var prev = target.targetTexture;
        target.targetTexture = rt;
        target.Render();
        target.targetTexture = prev;

        // ピクセルデータ取得用のテクスチャ作成
        var screenShot = new Texture2D(
            target.pixelWidth,
            target.pixelHeight,
            TextureFormat.RGB24,
            false
        );

        // ピクセル転送
        prev = RenderTexture.active;
        RenderTexture.active = rt;
        screenShot.ReadPixels(new Rect(0, 0, screenShot.width, screenShot.height), 0, 0);
        screenShot.Apply();
        RenderTexture.active = prev;

        // 結果返却
        onCompleted?.Invoke(screenShot);

        yield break;
    }
}
ImgurUploader.cs
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;

public static class ImgurUploader
{
    // アップロードAPIのレスポンスデータ(必要分のみ定義)
    [Serializable]
    private struct Response
    {
        [Serializable]
        public struct Data
        {
            // アップロードされた画像URL
            public string link;
        }

        public Data data;
        public bool success;
        public int status;
    }

    /// <summary>
    /// 画像をImgurにアップロードする
    /// </summary>
    /// <param name="clientID">Imgurに登録したClient ID</param>
    /// <param name="image">投稿する画像データ</param>
    /// <param name="onCompleted">アップロードしたURLを受け取るコールバック</param>
    /// <param name="onError">エラーメッセージを受け取るコールバック</param>
    public static IEnumerator UploadToImgur(
        string clientID,
        Texture2D image,
        UnityAction<string> onCompleted,
        UnityAction<string> onError = null
    )
    {
        // Texture2D→バイナリ変換
        var imageBytes = image.EncodeToPNG();
        // バイナリ→Base64変換
        var imageBase64 = Convert.ToBase64String(imageBytes);

        // Form Dataの作成
        var formData = new WWWForm();
        formData.AddField("image", imageBase64);

        // リクエスト作成
        using var request = UnityWebRequest.Post("https://api.imgur.com/3/image", formData);
        request.SetRequestHeader("AUTHORIZATION", "Client-ID " + clientID);

        // リクエスト実行
        yield return request.SendWebRequest();

        // レスポンスチェック
        if (request.result != UnityWebRequest.Result.Success)
        {
            onError?.Invoke(request.error);
            yield break;
        }

        // レスポンスデータ(JSON)をパース
        var response = JsonUtility.FromJson<Response>(request.downloadHandler.text);

        // 成否チェック
        if (!response.success)
        {
            onError?.Invoke($"アップロードエラー (status : ${response.status})");
            yield break;
        }

        // リンク返却
        onCompleted?.Invoke(response.data.link);
    }
}
TwitterShare.cs
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;

public static class TwitterShare
{
    /// <summary>
    /// Twitterシェアウィンドウを開く
    /// </summary>
    /// <param name="text">ツイート文言</param>
    /// <param name="tags">ハッシュタグ</param>
    public static void Share(string text, IEnumerable<string> tags = null)
    {
        // ツイート用URL作成
        var tweetURL = "http://twitter.com/intent/tweet?text=" + Uri.EscapeUriString(text);

        if (tags != null)
        {
            // ハッシュタグがあればパラメータに追加
            var strTag = string.Join(",", tags);

            if (!string.IsNullOrEmpty(strTag))
                tweetURL += "&hashtags=" + strTag;
        }

        // ツイート画面を新しいウィンドウで開く
        OpenWindow(tweetURL, 600, 300);
    }

    // 新しいウィンドウでURLを開く
#if !UNITY_EDITOR && UNITY_WEBGL
    // WebGLビルドで有効になる
    [DllImport("__Internal")]
    private static extern void OpenWindow(string url, int width, int height);
#else
    // UnityエディタやWebGL以外のプラットフォームで有効になる
    private static void OpenWindow(string url, int width, int height) => Application.OpenURL(url);
#endif
}
ShareExample.cs
using System.Collections;
using System.IO;
using UnityEngine;

public class ShareExample : MonoBehaviour
{
    [Header("キャプチャ対象のカメラ(nullの場合は全画面キャプチャ)")]
    [SerializeField]
    private Camera _target;

    [Header("ImgurアプリケーションのClient ID")]
    [SerializeField]
    private string _imgurClientID = "ここにClient IDを入力";

    [Header("ツイート文言")] 
    [SerializeField]
    private string _tweetText;

    [Header("ツイートのハッシュタグ")]
    [SerializeField]
    private string[] _hashTags;

    private void Update()
    {
        // スペースキーを押したらアップロード開始
        if (Input.GetKeyDown(KeyCode.Space))
        {
            StartCoroutine(UploadAndTweet());
        }
    }

    private IEnumerator UploadAndTweet()
    {
        // 画面キャプチャ
        Texture2D image = null;
        yield return ScreenCapturer.Capture(_target, x => image = x);

        // Imgurへの画像データアップロード
        string imageUrl = null;
        string errorMessage = null;

        yield return ImgurUploader.UploadToImgur(
            _imgurClientID,
            image,
            x => imageUrl = x,
            x => errorMessage = x
        );

        // アップロードの成否チェック
        if (!string.IsNullOrEmpty(errorMessage))
        {
            // 失敗の場合は処理中断
            Debug.LogError(errorMessage);
            yield break;
        }

        // 拡張子を除いた投稿用URLに加工
        imageUrl = Path.ChangeExtension(imageUrl, null);

        // ツイート画面を開く
        TwitterShare.Share(
            string.Format(_tweetText, imageUrl),
            _hashTags
        );
    }
}

一連の流れを動かすためには、上記スクリプトすべてをUnityプロジェクトに保存する必要があります。

また、WebGL環境では次のスクリプトをPlugins配下に置く必要があります。

OpenWindowPlugin.jslib
var OpenWindowPlugin = {
    OpenWindow: function(url, width, height) {
        url = Pointer_stringify(url);
        window.open(url, '_blank', 'width=' + width + ', height=' + height);
    }
};

mergeInto(LibraryManager.library, OpenWindowPlugin);

ビルド対象にWebGLが指定されていることも念のため確認しておきます。

使い方

ShareExampleスクリプトのみを適当なゲームオブジェクトにアタッチし、インスペクタより必要項目を設定します。

ツイート文言は、string.Format()メソッドを使って文字列{0}の部分に画像URLが入るようにしました。

このスクリプトは記事用の例としたサンプルですので、実際のゲームで使う際はこのままではなくクラス設計してから使うことをお勧めします。

あくまでもサンプルなので、クラス分けや設計などは各自でしっかり行ってね!

実行結果

ここまでの手順を成功させると、スペースキーを押して暫くするとTwitterへの投稿画面が開きます。

もし開かない場合は、Imgur APIの実行に失敗しているか、手順に不備がある可能性があります。

API実行に失敗している場合、しばらく時間をおいてから再びスペースキーを押すと上手くいくことがあります。

さいごに

ゲーム画面をキャプチャし、Imgur経由でTwitterに画像投稿するまでの一連の流れを実装する方法を解説しました。

画面キャプチャ、Imgurの使い方、Imgur APIの使い方、HTTPリクエスト送信、JSON解析、JavaScriptプラグイン作成(WebGL環境の場合)、Twitter送信URL作成などを行う必要があります。

それぞれの要素技術の使い方を理解できれば、ここから様々な応用ができるようになるでしょう。

参考サイト