質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

7091閲覧

Unity CaptureScreenshot() のデータ書き込み終了検知

退会済みユーザー

退会済みユーザー

総合スコア0

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

1クリップ

投稿2015/09/15 05:31

編集2015/09/29 07:16

UnityでAndroidのゲーム画面をスクリーンショットで撮影し,その画像をゲーム内で利用する機能を作成しているのですがCaptureScreenshot()で撮影したスクリーンショット画像をアプリデータ領域に保存完了した際の終了検知ができないで困っています.
現在画像の保存完了の待機を yield return new WaitForSeconds() で1秒待機してから画像を読み込むことで何とか動かしています( 1フレーム待機だとまだデータ書き込み中なのか正常に画像をロードされなかったので ).

動作としてはスクリーンショット撮影のリクエストをコルーチンに渡し,コールバックに指定した関数に撮影した画像のSpriteとファイルパスを送り返すようにしています.

lang

1 private IEnumerator captureScreenshotCor( 2 CallBackSprite callback, 3 string fileName, 4 string folderName, 5 bool fileOverWrite ){ 6 7 8 // 出力パス( 指定したフォルダが無ければ作成 ). 9 folderName = ( folderName != "" ) ? folderName + "/" : Application.productName + "Images/"; 10 { 11 string folderPath = Application.persistentDataPath + "/" + folderName; 12 if ( !Directory.Exists( folderPath ) ){ 13 Directory.CreateDirectory( folderPath ); 14 } 15 } 16 17 fileName += ".png"; 18 Application.CaptureScreenshot( folderName + fileName ); 19 20 yield return new WaitForSeconds( 1.0f ); 21 22 // コールバック用スプライト作成及びコールバック呼び出し. 23 Sprite spr = readSprite( filePath, Screen.width, Screen.height ); 24 callback( spr, filePath ); 25 26 yield return null; 27 }

現状だと画像保存に1秒を超えるタイムラグが発生する場合などに対応は出来ない状態です.
だからと言って待機時間を5秒や10秒に伸ばすのも根本的な解決にならないと思いこちらで質問させていただきました.

このCaptureScreenshot()の終了を検知する方法あるいはファイルの書き込みの完了を検知する方法はあるのでしょうか?
又それらの方法が無い場合の代替案としてはどのようなものが有るのでしょうか?
よろしくお願いします.

sho_csさんの紹介した参考ページの情報をもとにプログラム組み直してみました.
待機する処理は指定のファイルが有るか否か,そのファイルを開くことができるかどうかで二重にチェックをかけ.
ファイルが存在していてファイルへのアクセスができない間は待機する...というようにしました.

lang

1 private IEnumerator captureScreenshotCor( 2 CallBackSprite callback, 3 string fileName, 4 string folderName, 5 bool fileOverWrite ){ 6 7 8 // 出力パス( 指定したフォルダが無ければ作成 ). 9 folderName = ( folderName != "" ) ? folderName + "/" : Application.productName + "Images/"; 10 { 11 string folderPath = Application.persistentDataPath + "/" + folderName; 12 if ( !Directory.Exists( folderPath ) ){ 13 Directory.CreateDirectory( folderPath ); 14 } 15 } 16 17 fileName += PICTURE_EXTENSION; 18 Application.CaptureScreenshot( folderName + fileName ); 19 string filePath = Application.persistentDataPath + "/" + folderName + fileName; // 最終的な保存先. 20 21 // ファイルが存在していて且つアクセス可能になったらファイルの保存処理が終了している. 22 bool isFileComplete = false; 23 while ( !isFileComplete ){ 24 25 Debug.Log( "Saving !! " ); 26 yield return new WaitForEndOfFrame(); 27 28 bool isFileExists = File.Exists( filePath ); 29 bool isFileLocked = false;; 30 FileStream stream = null; 31 try{ 32 stream = new FileStream( filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None ); 33 }catch{ 34 Debug.Log( "ファイルがロックされているか又は開けない." ); 35 isFileLocked = true; 36 }finally{ 37 if ( stream != null ) 38 stream.Close(); 39 } 40 41 42 isFileComplete = ( isFileExists == true && isFileLocked != true ) ? true : false; 43 } 44 45 46 // コールバック用スプライト作成及びコールバク呼び出し. 47 Sprite spr = this.readSprite( filePath, Screen.width, Screen.height ); 48 callback( spr, filePath ); 49 50 yield return null; 51 }

一応これで動くようにはなりました.
ただ稀にこの待機処理を通しても正しく読み込まれない( 又は完全にファイルクローズする前にアクセスしている? )事があるらしく挙動がおかしくなることがあります.
なのでもう少し何かしらの解決策が無いか調べてみようと思います.

今のところCaptureScreenshot()のデータ書き込みを検知する方法が見つからないのでsho_csさんの回答の別の取り方を参考にソースを組み直しました.
コルーチン内で1ピクセルずつデータを取り出しTexture2Dにしてそれをコールバックで返してから別スレッドでそのファイルを保存することで安定して動かすことが出来ました.
別スレッドではなくコルーチン内で画像を作成しているのはUnityのクラスはどうやら別スレッドから操作できないらしくメインスレッド内で画像を作成する必要が出てきたからです.

タイトルとは違う解決方法になりましたが今回はこの方法で解決ということにしました.

C#

1using UnityEngine; 2using System.Collections; 3using System.IO; 4using System.Collections.Generic; 5using System; 6using System.Threading; // スレッド使用. 7 8 9// スクリーンショット撮影クラス. 10// 現在PC上ではできるがAndroidではうまく動かない. 11// スクリーンショットの撮影時点でエラーが発生する. 12public class ScreenCapture{ 13 14 15 //--- クラス宣言 --- 16 private class CaptureInfo{ 17 18 private string path; 19 private string id; 20 private bool isFinished; 21 private Texture2D tex; 22 23 public string Path { get{ return this.path; } } 24 public string Id { get{ return this.id; } } 25 public bool IsFinished { get{ return this.isFinished; } } 26 public Texture2D Tex { get{ return this.tex; } } 27 28 public CaptureInfo( 29 string path, 30 string id, 31 Texture2D tex ){ 32 this.path = path; 33 this.id = id; 34 this.tex = tex; 35 this.isFinished = false; 36 } 37 38 public void finish(){ 39 this.isFinished = true; 40 } 41 42 } 43 44 45 //--- 定数 --- 46 private const string PICTURE_EXTENSION = ".png"; 47 48 //--- 変数&プロパティ --- 49 public delegate void CallBackTex2D( Texture2D tex, string filePath ); // コールバック( Texture2D ver ). 50 public delegate void CallBackSprite( Sprite spr, string filePath ); // コールバック( Sprite ver ). 51 private Dictionary< string, CaptureInfo > captureList; // キャプチャ情報リスト. 52 53 54 //--- メソッド --- 55 56 public ScreenCapture(){ 57 58 this.captureList = new Dictionary< string, CaptureInfo >(); 59 } 60 61 62 63 // スクリーンショットを撮影し,コールバックでTexture2Dとファイルパスを返す. 64 public void captureScreenshot( 65 MonoBehaviour mono, 66 CallBackTex2D callback, 67 string fileName, 68 string folderPath = "", 69 bool isOverWrite = false ){ 70 71 mono.StartCoroutine( captureScreenshotCor( callback, fileName, folderPath, isOverWrite ) ); 72 } 73 private IEnumerator captureScreenshotCor( 74 CallBackTex2D callback, 75 string fileName, 76 string folderName, 77 bool isOverWrite ){ 78 79 #if !UNITY_EDITOR && UNITY_ANDROID 80 // 出力パス( 指定したフォルダが無ければ作成 ). 81 folderName = ( folderName != "" ) ? folderName + "/" : Application.productName + "Images/"; 82 { 83 string folderPath = Application.persistentDataPath + "/" + folderName; 84 if ( !Directory.Exists( folderPath ) ){ 85 Directory.CreateDirectory( folderPath ); 86 } 87 } 88 89 fileName += PICTURE_EXTENSION; 90 string filePath = Application.persistentDataPath + "/" + folderName + fileName; // 最終的な保存先. 91 92 yield return new WaitForEndOfFrame(); 93 94 // 撮影. 95 Texture2D tex = new Texture2D( Screen.width, Screen.height, TextureFormat.ARGB32, false ); 96 tex.ReadPixels( new Rect( 0, 0, Screen.width, Screen.height ), 0, 0, false ); 97 tex.Apply(); 98 99 // キャプチャ用の情報を作成. 100 string captureID = this.getNowTimeString(); 101 CaptureInfo info = new CaptureInfo( filePath, captureID, tex ); 102 this.captureList.Add( captureID, info ); 103 104 // コールバックでスプライトを先に渡す. 105 callback( tex, filePath ); 106 107 // 別スレッドを作成しそっちで保存. 108 Thread thread = new Thread( new ParameterizedThreadStart( this.saveCaptureImage ) ); 109 thread.Start( captureID ); 110 while ( !this.captureList[ captureID ].IsFinished ){ 111 yield return new WaitForSeconds( 0 ); 112 } 113 #endif 114 115 yield return new WaitForEndOfFrame(); 116 } 117 118 119 120 // 指定したパスのバイナリデータをbyte型配列で返す. 121 private byte[] readBinary( 122 string path ){ 123 124 byte[] bytes = ( File.Exists( path ) ) ? File.ReadAllBytes( path ) : null; 125 if ( bytes == null ) 126 Debug.LogError( "ファイルが存在しません." ); 127 return bytes; 128 } 129 130 131 // 指定したパス,サイズのTexture2Dを作成し,返す. 132 private Texture2D readTexture2D( 133 string path, 134 int width, 135 int height ){ 136 137 byte[] data = this.readBinary( path ); 138 Texture2D tex = new Texture2D( width, height ); 139 tex.LoadImage( data ); 140 141 return tex; 142 } 143 144 145 // 指定したパス,サイズのスプライトを作成し,返す. 146 private Sprite readSprite( 147 string path, 148 int width, 149 int height ){ 150 151 Texture2D tex = this.readTexture2D( path, width, height ); 152 Sprite spr = Sprite.Create( tex, new Rect( 0, 0, width, height ), Vector2.zero ); 153 154 return spr; 155 } 156 157 158 159 // 現在時刻のストリングを取得. 160 // フォーマット : YY-MM-DD-OO-MM-SS. 161 private string getNowTimeString(){ 162 163 string ret = DateTime.Now.Year.ToString() + "-"; 164 ret += DateTime.Now.Month.ToString() + "-"; 165 ret += DateTime.Now.Day.ToString() + "-"; 166 ret += DateTime.Now.Hour.ToString() + "-"; 167 ret += DateTime.Now.Minute.ToString() + "-"; 168 ret += DateTime.Now.Second.ToString(); 169 return ret; 170 } 171 172 173 174 // 指定されたIDの画像を保存( 別スレッドで起動 ). 175 private void saveCaptureImage( 176 object id ){ 177 178 string captureID = ( string )id; 179 CaptureInfo info = this.captureList[ captureID ]; 180 181 182 // 画像を保存. 183 byte[] byteData = this.captureList[ captureID ].Tex.EncodeToPNG(); 184 File.WriteAllBytes( this.captureList[ captureID ].Path, byteData ); 185 this.captureList[ captureID ].finish(); 186 } 187 188}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2015/09/16 09:44

待機方法での参考ページをもとにソースを書き直したところファイルが保存された後にそのファイルにアクセスすることが出来るようになりましたが稀に不具合が起こります. なのでもう少し何か解決策が無いか調べてみようと思います. あるいは[別の取り方]のページの方法でも試してみようと思います.
sho_cs

2015/09/16 09:50

File.Existsメソッドを利用して while(!File.Exists("ファイルパス")) { // まだファイルがない } // ファイル作成確認 みたいな感じではどうでしょうか
sho_cs

2015/09/16 09:52

すでに試されているようですね。 すいませんでした。
退会済みユーザー

退会済みユーザー

2015/09/29 07:19

表題とは違う形になりましたがsho_csさんの紹介した別の取り方を参考に目的に近い動作にすることができたのでベストアンサーに選ばせていただきました.
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問