アヒルのある日

株式会社AHIRUの社員ブログです。毎週更新!社員が自由に思いついたことを書きます。

オブジェクト単位のスクリーンショット

こんにちは、みにくい社長です。

Unityでスクリーンショットを保存する場合、

ScreenCapture.CaptureScreenshot("filename.png");

をスクリプトで実行するだけで実現できます。

もし画面全体ではなく一部を保存したい場合は、ちょっと面倒です。 方法としては2つ考えられます。

  1. 不要なオブジェクトの描画をOFFにし、必要な部分を切り抜く
  2. 必要なオブジェクトを別のカメラで描画する

どちらが良いかは状況によりますが、2の方がメインコンテンツの実装方法に制限をかけなくて済むので、 今回は2の方法をご紹介します。

スクリーンショット用のカメラを用意する

メインのカメラとは別に、スクリーンショット用のカメラをシーンに作成しておきます。 さらにスクリーンショット用のCanvasも作成し、RenderCameraにScreenShotCameraを設定します。 撮影するときは、メインのCanvasにある必要なオブジェクトをスクリーンショット用のCanvasに接続します。 f:id:minikui_ahiru:20201125110204p:plain

RenderTextureを用意する

スクリーンショットを保存するにはRenderTextureが必要になります。 プロジェクトビューで右クリック>Create>RenderTextureで作成できます。 作成後に、Sizeの値を必要な最大値に設定します。 f:id:minikui_ahiru:20201125110234p:plain 先ほど作成したScreenShotCameraのTargetTextureに、作成したScreenShotTextureを設定します。 f:id:minikui_ahiru:20201125110248p:plain

オブジェクトをコピーして撮影する

まず、ScreenShotCanvasに設定するスクリプトです。

public class ScreenShotCanvas : MonoBehaviour
{
    public static ScreenShotCanvas Instance { get; private set; }

    private ScreenShotCanvas()
    {
        Instance = this;
    }

    public IEnumerator Save(string filePath, GameObject cloneObject, Rect rect)
    {
        // 接続して座標などをリセットする
        cloneObject.transform.SetParent(transform);
        cloneObject.transform.localPosition = Vector3.zero;
        cloneObject.transform.localScale = Vector3.one;

        // 描画されるまで少し待つ
        yield return new WaitForSeconds(0.1f);

        // トリミング
        var renderTexture = GetComponent<Canvas>().worldCamera.targetTexture;
        var texture = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGB24, false);
        RenderTexture.active = renderTexture;
        texture.ReadPixels(rect, 0, 0);
        texture.Apply();

        byte[] bytes = texture.EncodeToPNG();

        // 不要なデータを削除
        Destroy(texture);
        Destroy(cloneObject);

        // ファイル保存
        File.WriteAllBytes(filePath, bytes);
    }
}

次に、呼び出す側のスクリプトです。

private void SaveScreenShot()
{
    // コピーを作成
    var cloneObject = Instantiate(targetObject) as GameObject;
    // 保存
    StartCoroutine(ScreenShotCanvas.Instance.Save($"{Application.persistentDataPath}/{fileDialog.FileName}", cloneObject, targetObject.GetComponent<RectTransform>().rect));
}

TargetObjectのコピーを作成し、ScreenShotCanvasに接続します。 コピーを作成しているので、座標やスケールを変更しても問題ありません。 その後に描画しますが、接続直後は正しく描画されない可能性がある為、0.1秒待っています。 後はトリミングしてファイルに保存します。

この仕組みであれば、アプリ内のどの場所のオブジェクトでも同じカメラとCanvasで対応できますね。

では、またねー!