vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【Unity5】【iOS】【Android】スクリーンショットを好きな場所に保存したい

はじめに

Unityでは「Application.CaptureScreenshot()」を使うことで、簡単にスクリーンショットをとることができます。
ただ、基本的にアプリ内のフォルダに保存され、iOSでフォトアルバムに保存したり、AndroidでPicturesフォルダに保存したり、ということは難しいようです(少なくとも自分で確認した限りは)。

で、プラグインを作ってみたり試行錯誤したのでそれをまとめたいと思います。

iOS

実のところiOSはそんなに苦労せずになんとかなりました。
この辺と同じように、「UnityGetGLViewController()」を使ってUnityのViewが取得できるので、
それを使ってiOSのネイティブアプリと同じようにスクリーンショットをとればOKです。

#import "ImgController.h"

@interface ImgController()
@property (strong, nonatomic)UIViewController *vwcUnityView;
@end
@implementation ImgController
- (void) initialize
{
    _vwcUnityView = UnityGetGLViewController();
}
- (void) takeScreenshot
{
    UIGraphicsBeginImageContextWithOptions(_vwcUnityView.view.frame.size , NO , [UIScreen mainScreen].scale);

    // スクリーンショットをとる[drawViewHierarchyInRect:afterScreenUpdates]が使えるかを確認.
    if ([_vwcUnityView.view respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
        [_vwcUnityView.view drawViewHierarchyInRect:_vwcUnityView.view.bounds afterScreenUpdates:YES];
    } else {
        [_vwcUnityView.view.layer renderInContext:UIGraphicsGetCurrentContext()];
    }
    UIImage *capture = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    // 画像をフォトアルバムに保存する.
    UIImageWriteToSavedPhotosAlbum(capture, self, @selector(savingImageIsFinished:didFinishSavingWithError:contextInfo:), nil);
}
- (void) savingImageIsFinished:(UIImage *)_image didFinishSavingWithError:(NSError *)_error contextInfo:(void *)_contextInfo
{
    // 画像の保存が終了したら実行.
}
@end

Android

問題はAndroidです。

スクリーンショットをネイティブアプリでとることは、以下のようなコードで可能です。

問題

上記のコードは現在表示しているViewを取得する必要があり、「UnityPlayer.currentActivity」や「UnityPlayer」を使って 「UnityPlayer.currentActivity.findViewById(android.R.id.content)」のようにしてみても、保存される画像は真っ黒になってしまいます。

対策

結局プラグインではうまく解決ができず、UnityのScriptで画面をTexture2Dとして取得してそれを画像として出力することで実現できました。

Unityスクリプト

CtrlAndroidPlugin.cs

using UnityEngine;
using System.Collections;
using System.IO;

public class CtrlAndroidPlugin : MonoBehaviour
{
    readonly string PLUGIN_CLASS_PATH = "jp.takingscreenshot.PluginConnector";

    int _intWidth;
    int _intHeight;
    string _strPath = "";

    void Start()
    {
        _intWidth = Screen.width;
        _intHeight = Screen.height;
    }
    public void TakeScreenshot()
    {
#if UNITY_ANDROID
        using(AndroidJavaClass clsPlugin = new AndroidJavaClass(PLUGIN_CLASS_PATH))
        {
      // プラグインでPicturesフォルダのディレクトリを取得.
            _strPath = clsPlugin.CallStatic("getPictureDir");
            _strPath += "/screenshot.png";

            StartCoroutine (GetCapture());
        }
#endif
    }
  IEnumerator GetCapture ()
  {
    yield return new WaitForEndOfFrame();

        // 出力用のTextureを作成する.
    Texture2D txtScreenshot = new Texture2D (_intWidth, _intHeight, TextureFormat.RGB24, false);
        // スクリーン画面のピクセルデータ読み込み.テクスチャのミップマップを読んだ後に再計算する.
        txtScreenshot.ReadPixels(new Rect (0, 0, _intWidth, _intHeight), 0, 0, true);
        // PNGファイルとしてエンコード.
    byte[] imagebytes = txtScreenshot.EncodeToPNG();
        // DXTフォーマットには圧縮しないのでFalse.
        txtScreenshot.Compress(false);
        // 出力.
    File.WriteAllBytes (_strPath, imagebytes);

#if UNITY_ANDROID
            using(AndroidJavaClass clsPlugin = new AndroidJavaClass(PLUGIN_CLASS_PATH))
            {
                // 保存した画像を反映する.
                clsPlugin.CallStatic("scanMedia", _strPath);
            }
#endif
    }
}

プラグイン

今回実施しているのは以下の2つです。

  • デバイスのPicturesディレクトリのパスを取得する
  • 保存した画像をスキャンして反映させる

PluginConnector.java

package jp.takingscreenshot;

import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;
import com.unity3d.player.UnityPlayer;

public class PluginConnector extends Activity {
  protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
  private final static PluginConnector mPlgConnector = new PluginConnector();

  public static String getPictureDir()
  {
    return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString();
  }
  public static void scanMedia(final String strFilePath)
  {
    UnityPlayer.currentActivity.runOnUiThread(new Runnable() {
      @Override
      public void run() {
        MediaScannerConnection.scanFile(
            UnityPlayer.currentActivity.getApplicationContext(),
            new String[]{strFilePath},
            new String[]{"image/jpeg"},
            mPlgConnector.mScanSavedFileCompleted);
      }
    });
  }
  private MediaScannerConnection.OnScanCompletedListener mScanSavedFileCompleted = new MediaScannerConnection.OnScanCompletedListener(){
    @Override
    public void onScanCompleted(String path, Uri uri){
      UnityPlayer.currentActivity.runOnUiThread(new Runnable()
      {
        @Override
        public void run()
        {
          // 画像保存の完了を伝えるToast表示.
          Toast.makeText(UnityPlayer.currentActivity, "画像を保存しました", Toast.LENGTH_SHORT).show();
        }
      });
    }
  };
〜省略〜

終わりに

これで何とかキャプチャを保存できるようになりました。
ただ、Androidでも今表示中のViewは取得できたらとは思うので、解決策が見つかればまた取りあげたいと思います。

参考

iOS

Android