PR

【Unity】複数解像度スクリーンショットを一括で撮影する拡張機能

Unity複数解像度スクリーンショットを一括で撮影する拡張機能 Unity

転載防止用

TedLab_CaptureScreenshotEditor.cs

using System;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace TedLab
{
    public class CaptureScreenShotWindow : EditorWindow
    {
        private const float settingSizeWaitTime = 1f;

        // iOS用の解像度リスト
        static CaptureResolution[] resolutions_IOS =
        {
            new CaptureResolution(1242, 2208),
            new CaptureResolution(1242, 2688),
            new CaptureResolution(2048, 2732),
        };

        [MenuItem("TedLab Editor/スクリーンショットキャプチャ")]
        static void OpenWindow()
        {
            GetWindow(true, "[TedLab]スクリーンショットキャプチャ");
        }

        [Serializable]
        class CaptureResolution{
            public CaptureResolution(int _width, int _height){
                width = _width;
                height = _height;
            }
            public int width = 1920;
            public int height = 1080;
        }

        static readonly string KeyPrefix = "TedLab_CaptureScreenShot_";
        static readonly string PathKey = KeyPrefix + "Path";
        static readonly string ReverseKey = KeyPrefix + "Reverse";
        static readonly string StopTimeKey = KeyPrefix + "StopTime";
        static readonly string ResoCountKey = KeyPrefix + "ResoCount";
        static readonly string ResoWidthBaseKey = KeyPrefix + "ResoW_";
        static readonly string ResoHeightBaseKey = KeyPrefix + "ResoH_";

        string m_path = "";
        bool m_started = false;
        bool m_reverse = false;
        bool m_stopTime = true;

        [SerializeField] List captureResolutions = null;
        SerializedObject resolutionSerializedObj;
        SerializedProperty resolutionProp;

        EditorCoroutine m_coroutine = null;
        int m_selectedIndexOld = -1;
        GameViewSizeGroupType m_currentGroupType;

        private void OnEnable()
        {
            captureResolutions = new List(32);

            // 読み込み
            m_path = EditorPrefs.GetString( PathKey, "" );
            m_reverse = EditorPrefs.GetBool( ReverseKey, false );
            m_stopTime = EditorPrefs.GetBool( StopTimeKey, true );

            int reso_count = EditorPrefs.GetInt( ResoCountKey, 0 );
            for( int i=0; i<reso_count; ++i ) { string indexText = i.ToString("D2"); int width = EditorPrefs.GetInt( ResoWidthBaseKey + indexText, 0 ); int height = EditorPrefs.GetInt( ResoHeightBaseKey + indexText, 0); if( width > 0 && height > 0 ){
                    captureResolutions.Add( new CaptureResolution(width, height));
                }
            }

            resolutionSerializedObj = new SerializedObject(this);
            resolutionProp= resolutionSerializedObj.FindProperty("captureResolutions");
        }

        private void OnDisable()
        {
            if( m_coroutine != null )
            {
                m_coroutine.Stop();
                m_coroutine = null;
            }

            if( m_timeScaleOld >= 0f ){
                Time.timeScale = m_timeScaleOld;
                m_timeScaleOld = -1f;
            }

            // 保存
            EditorPrefs.SetString( PathKey, m_path );
            EditorPrefs.SetBool( ReverseKey, m_reverse );
            EditorPrefs.SetBool( StopTimeKey, m_stopTime );

            int reso_count = captureResolutions.Count;
            EditorPrefs.SetInt( ResoCountKey, reso_count );
            for( int i=0; i<reso_count; ++i )
            {
                var reso = captureResolutions[i];
                string indexText = i.ToString("D2");
                EditorPrefs.SetInt( ResoWidthBaseKey + indexText, reso.width );
                EditorPrefs.SetInt( ResoHeightBaseKey + indexText, reso.height );
            }
            
        }

        void OnGUI()
        {
            resolutionSerializedObj.Update();

            if( m_started )
            {
                GUILayout.Label("スクリーンショットを撮影中...");
            }
            else
            {
                GUILayout.Label("設定:");
                m_reverse = GUILayout.Toggle( m_reverse, "縦横反転" );
                m_stopTime = GUILayout.Toggle( m_stopTime, "撮影時に止める(TimeScale)" );

                GUILayout.Space(16f);

                GUILayout.Label("解像度リスト:");
                EditorGUILayout.PropertyField(resolutionProp, true);
                resolutionSerializedObj.ApplyModifiedProperties();

                if (GUILayout.Button("解像度をクリア")) {
                    captureResolutions.Clear();
                }

                if (GUILayout.Button("iOS解像度を追加")) {
                    AddResolutions( resolutions_IOS );
                }

                GUILayout.Space(16f);

                GUILayout.Label("保存フォルダ:");
                m_path = GUILayout.TextField(m_path);

                if (GUILayout.Button("フォルダを開く"))
                {
                    string filePath = m_path;
                    filePath = filePath.Replace( '/', Path.DirectorySeparatorChar );
                    EditorUtility.RevealInFinder(filePath);
                }

                if (GUILayout.Button("マニュアルを開く")) {
                    OpenManual();
                }
                if (GUILayout.Button("スクリーンショットを保存")) {
                    Capture();
                }
            }
        }

        void AddResolutions( CaptureResolution[] resolutions )
        {
            foreach( var reso in resolutions ){
                captureResolutions.Add( reso );
            }
        }

        void Capture()
        {
            if( m_started){
                Debug.Log( "Already Started Capture..." );
                return;
            }

            // 解像度指定がない場合はそのまま
            if( captureResolutions.Count <= 0 )
            {
                string dateString = DateTime.Now.ToString("yyyyMMddHHmmss");
                string filePath = string.Format( Path.Combine(m_path, "ScreenCapture_{0}.png"), dateString );
                Debug.Log(string.Format("Saved a new screenshot as {0}", filePath));
                ScreenCapture.CaptureScreenshot(filePath);
            }
            // 複数キャプチャ
            else
            {
                m_coroutine = EditorCoroutine.Start( MultipleCaptureScreen() );
            }
        }

        void OpenManual()
        {
            string url = "https://tedenglish.site/unity-ex-multi-screenshots/";
            Application.OpenURL( url );
        }

        
        float m_timeScaleOld = -1f;

        IEnumerator MultipleCaptureScreen()
        {
            m_started = true;

            string TmpLabel = "TedLabScreenCaptureResoTemp";

            m_timeScaleOld = Time.timeScale;
            if( m_stopTime ){
                Time.timeScale = 0f;
            }

            foreach( CaptureResolution reso in captureResolutions )
            {
                int width = m_reverse ? reso.height : reso.width;
                int height = m_reverse ? reso.width : reso.height;

                string dateString = DateTime.Now.ToString("yyyyMMddHHmmss");
                string filePath = string.Format( Path.Combine(m_path, "ScreenCapture_{0}_{1}.png"), dateString, width.ToString() + "x" +  height.ToString() );
                Debug.Log( string.Format("Save a new screenshot as {0}", filePath) );

                StartCustomSize( TmpLabel, width, height );
                yield return settingSizeWaitTime;

                // キャプチャ&待つ
                ScreenCapture.CaptureScreenshot(string.Format(filePath));

                while( !IsSaved( filePath ) ){
                    yield return 0.1f;
                }

                EndCustomSize( TmpLabel, width, height );
            }

            Debug.Log( "Saved All Captures!!" );

            if( m_stopTime ){
                Time.timeScale = m_timeScaleOld;
                m_timeScaleOld = -1f;
            }

            m_started = false;
            m_coroutine = null;

            yield return 0f;
        }

        // UnityEditor.GameViewSizeTypeと合わせる
        public enum GameViewSizeType
        {
            AspectRatio = 0,
            FixedResolution = 1,
        }

        void StartCustomSize( string name, int width, int height )
        {
            var asm = typeof(UnityEditor.Editor).Assembly;
            Type gameViewType = asm.GetType("UnityEditor.GameView");
            Type gameViewSize = asm.GetType("UnityEditor.GameViewSize");
            Type gameViewSizes = asm.GetType("UnityEditor.GameViewSizes");
            Type gameViewSizeType = asm.GetType("UnityEditor.GameViewSizeType");
            Type gameViewSizeGroup = asm.GetType("UnityEditor.GameViewSizeGroup");

            EditorWindow gameView = EditorWindow.GetWindow(gameViewType, false, "Game", false);

            PropertyInfo currentSizeGroupType = gameViewType.GetProperty("currentSizeGroupType", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);
            m_currentGroupType = (GameViewSizeGroupType)currentSizeGroupType.GetValue(gameView, null);

            MethodInfo getGroup = gameViewSizes.GetMethod("GetGroup");
            Type scriptableSingleton = typeof(ScriptableSingleton<>).MakeGenericType(gameViewSizes);
            PropertyInfo scriptableSingletonInstance = scriptableSingleton.GetProperty("instance");
            object gameViewSizesInstance = scriptableSingletonInstance.GetValue(null, null);
            object group = getGroup.Invoke(gameViewSizesInstance, new object[] { m_currentGroupType });

            Type[] paramTypes = new Type[] { gameViewSize };
            MethodInfo addCustomSize = gameViewSizeGroup.GetMethod("AddCustomSize", BindingFlags.Public | BindingFlags.Instance, null, paramTypes, null);
            ConstructorInfo constructor = gameViewSize.GetConstructor( new Type[] { gameViewSizeType, typeof(int), typeof(int), typeof(string) } );

            // インデックスを保持しておく
            PropertyInfo selectedSizeIndex = gameViewType.GetProperty("selectedSizeIndex", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            m_selectedIndexOld = (int)selectedSizeIndex.GetValue( gameView, null );

            // 追加
            {
                object newSize = constructor.Invoke(new object[] { (int)GameViewSizeType.FixedResolution, width, height, name });
                addCustomSize.Invoke(group, new object[] { newSize });
            }

            // 追加したのをインデックス設定
            int index = FindSameResolution( group, name, width, height );
            if( index >= 0 ){
                selectedSizeIndex.SetValue( gameView, index, null);
            }
        }

        void EndCustomSize( string name, int width, int height )
        {
            var asm = typeof(UnityEditor.Editor).Assembly;
            Type gameViewSize = asm.GetType("UnityEditor.GameViewSize");
            Type gameViewSizes = asm.GetType("UnityEditor.GameViewSizes");
            Type gameViewSizeType = asm.GetType("UnityEditor.GameViewSizeType");
            Type gameViewSizeGroup = asm.GetType("UnityEditor.GameViewSizeGroup");

            MethodInfo getGroup = gameViewSizes.GetMethod("GetGroup");
            Type scriptableSingleton = typeof(ScriptableSingleton<>).MakeGenericType(gameViewSizes);
            PropertyInfo scriptableSingletonInstance = scriptableSingleton.GetProperty("instance");
            object gameViewSizesInstance = scriptableSingletonInstance.GetValue(null, null);
            object group = getGroup.Invoke(gameViewSizesInstance, new object[] { m_currentGroupType });

            Type[] paramTypes = new Type[] { typeof(int) };
            MethodInfo removeCustomSize = gameViewSizeGroup.GetMethod("RemoveCustomSize", BindingFlags.Public | BindingFlags.Instance, null, paramTypes, null);

            // 同名のインデックスを探す
            int index = FindSameResolution( group, name, width, height );
            if( index >= 0 ){
                removeCustomSize.Invoke( group, new object[] { index } );
            }

            // 元に戻す
            Type gameViewType = asm.GetType("UnityEditor.GameView");
            PropertyInfo selectedSizeIndex = gameViewType.GetProperty("selectedSizeIndex", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            EditorWindow gameView = EditorWindow.GetWindow(gameViewType, false, "Game", false);
            selectedSizeIndex.SetValue( gameView, m_selectedIndexOld, null);
        }

        // 同名のインデックスを探す
        int FindSameResolution( object group, string name, int width, int height )
        {
            int index = -1;
            var getDisplayTexts = group.GetType().GetMethod("GetDisplayTexts");
            var displayTexts = getDisplayTexts.Invoke(group, null) as string[];
            string targetName = string.Format("{0} ({1}x{2})", name, width, height);
            for(int i=0; i<displayTexts.Length; ++i)
            {
                string textTmp = displayTexts[i];
                if( textTmp == targetName ){
                    index = i;
                    break;
                }
            }
            return index;
        }

        // 保存されているか
        bool IsSaved( string path )
        {
            bool isExist = File.Exists( path );
            bool isLocked = false;
            FileStream stream = null;
            try{
                stream = new FileStream( path, FileMode.Open, FileAccess.ReadWrite, FileShare.None );
            }catch{
                isLocked = true;
            }finally{
                if( stream != null ){
                    stream.Close();
                }
            }
            return isExist && !isLocked;
        }

        // コルーチン用
        public class EditorCoroutine
        {
            public static EditorCoroutine Start( IEnumerator _routine )
	        {
		        var coroutine = new EditorCoroutine(_routine);
		        coroutine.Start();
		        return coroutine;
	        }
            readonly IEnumerator routine;
            float _waitTimer = 0f;
            double _prevTime = 0;
		    EditorCoroutine( IEnumerator _routine ){
			    routine = _routine;
		    }

            void Start(){
			    EditorApplication.update += Update;
                _prevTime = EditorApplication.timeSinceStartup;
		    }
		    public void Stop(){
			    EditorApplication.update -= Update;
		    }
		    void Update()
            {
                var time = EditorApplication.timeSinceStartup;
                var deltaTime = (float)(time - _prevTime);
                _prevTime = time;

                if(_waitTimer > 0f){
                    _waitTimer -= deltaTime;
                    if(_waitTimer > 0f){
                        return;
                    }
                }
			    if (routine.MoveNext()){
                    _waitTimer = routine.Current;
                }else{
				    Stop();
			    }
		    }
        }
    }

} // namespace TedLab

 

コメント

スポンサーリンク
タイトルとURLをコピーしました