겨울팥죽 여름빙수
Published 2023. 7. 12. 09:38
Unity Android Pip 게임을 만들자/Unity
  • AndroidManifest.xml 수정
    <activity android:name="Activity"
        android:supportsPictureInPicture="true"
        android:configChanges=
            "screenSize|smallestScreenSize|screenLayout|orientation"
        ...
    
  • Assets/Plugin/Android/Activity.java설정
    • 아래 코드는 Android의 홈메뉴를 눌렀을 시, Pip모드로 전환하는 코드이다.
      • 홈이 아닌 메뉴 버튼에서는 동작하지 않는다.
    public class Activity extends UnityPlayerActivity
    {
        boolean usePiPMode = false;    //pip를 사용하는 경우에 대한 플래그
        boolean isConfigurationInPip = false; //pip창에서 닫기 이벤트 인지를 확인하기 위한 플래그
        
        @Override
        protected void onUserLeaveHint()
        {                
    		//home메뉴 터치 시, 동작.
            super.onUserLeaveHint();
            if(usePiPMode)
            {
                enterPipMode();
            }                    
        }
    
        @Override
        protected void onPause()
        {
            super.onPause();
            if(IsInPipMode())
            {
    			//onUserLeaveHint -> onPause 호출됨
    			//UnityPlayer가 onPause시 내부에서 pause된다. 이것을 다시 resume으로 바꿔 활성화 시켜줘야한다.
                mUnityPlayer.resume();
            }
        }
    
    	//pip전환 함수
        private void enterPipMode()
        {        
            if (!IsSupportedPIP())
            {
                return;
            }
            
            if (!IsAvailablePIP())
            {
                return;
            }                            
        
            if (mUnityPlayer == null)
            {
                return;
            }
            
            int width = mUnityPlayer.getWidth();
            int height = mUnityPlayer.getHeight();
            float ratio = width/(float)height;
    
            if(ratio > 2.39f)
            {
                //비율이 2.39이상인 경우 에러가 발생한다. 때문에 강제로 2.39f아래로 맞춰준다.
                width = (int)(height*2.38f);
            }
            
            Rational rational = new Rational(width, height);
            
            PictureInPictureParams params = new PictureInPictureParams.Builder()
            .setAspectRatio(rational)
            .build();
            
            //pip화면으로 전환. 명시적 전환
            enterPictureInPictureMode(params);
            
            // target 32부터 사용가능한 함수
            // 자동전환
            // setPictureInPictureParams 호출 시, pause로 넘어갈 시 자동으로 pip로 전환됨
            // 이 경우엔, onCreate나 초기화 해주는 함수에서 사용하는게 좋다.
            //PictureInPictureParams params = new PictureInPictureParams.Builder()
    		//	.setAspectRatio(rational)
    		//	.setAutoEnterEnabled(usePiPMode) //false이면 전환 안함.
            //	.build();
            //setPictureInPictureParams(params);
        }
    
        private boolean IsSupportedPIP()
        {
            // Android 8.0 (API level 26)이상부터 지원한다.
            return Build.VERSION.SDK_INT >= 26;
        }
    
        private boolean IsAvailablePIP()
        {
            // 메모리 사용가능한지 확인. Ram부족 시 사용 안됨.
            boolean supported = getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
            return supported;
        }
    
        private void UsePiPMode(boolean _usePiPMode)
        {
    		//Unity에서 호출하는 함수. pip모드를 사용할지 결정하는 함수이다.
            usePiPMode = _usePiPMode;
        }
    
        private boolean IsInPipMode()
        {
            if(!IsSupportedPIP() || !IsAvailablePIP())
            {
                return false;
            }
        
            return isInPictureInPictureMode();
        }     
        
        @Override
        public void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            //Configuration 차이 확인
            int diff = 0;
            if(oldConfig != null)
            {
                diff = oldConfig.diff(newConfig);
            }
            oldConfig  = new Configuration(newConfig);
    
            if(IsInPipMode())
            {    
    			//pip모드에서 x버튼(닫기버튼)을 눌렀을 경우 처리를 위한 코드        
    			//UnityPlayer의 surfaceView가 날아가기 때문에, 그 전에 pause로 바꿔서 렌더링을 안하게 해야한다.
    			//unity세팅에서 multithread redering을 비활성화 하면, 안해줘도 된다.
                if(isConfigurationInPip)
                {
    				//x버튼을 눌러 pip창을 닫은 경우
    				//UnityPlayer를 pause로 만들어줘야한다. 안하면 에러 발생
    				//onPictureInPictureModeChanged 가 나중에 호출된다. 
    				//때문에 onPictureInPictureModeChanged 대신, onConfigurationChanged를 활용하게됨.
    				//이떄 유저가 pip창 사이즈 조절하는 행동은 무시하기 위해, diff값에 CONFIG_SCREEN_LAYOUT가 포함되었는지 확인해야한다.
                    if((diff & ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0 &&
                    (diff & ActivityInfo.CONFIG_ORIENTATION) != 0)
                    {
                        isConfigurationInPip = false;
                        mUnityPlayer.pause();
                    }
                }
                else
                {
                    //pip모드로 들어온 경우.
                    //onPictureInPictureModeChanged 가 먼저 호출되었기 때문에, IsInPipMode가 true이다.
                    isConfigurationInPip = true;
                }          
            }
            else
            {
                //원래 화면으로 복구되는 경우.
                // Restore the full-screen UI.
                isConfigurationInPip = false;
            }
        }
        
        @Override
        public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) {
            if (isInPictureInPictureMode) {
                // Hide the full-screen UI (controls, etc.) while in picture-in-picture mode.
    						// pip모드로 전환된 경우 처리
            } else {
                // Restore the full-screen UI.
    						// pip모드에서 원래 창으로 돌아가는 경우 처리.
    						// 닫기 버튼 클릭시에는 호출 안됨.
            }
        }
    }
    • 위 코드에서, Pip창에서 x버튼을 눌러 닫는 경우에 대한 처리를 onConfigurationChanged통해 하고 있다. x버튼을 누르는 경우
      • onConfigurationChanged → surfaceDestroyed → onStop → onPictureInPictureModeChanged → 순으로 호출된다.
        • surfaceDestroyed 가 되기 전에, mUnityPlayer를 pause로 해줘야 한다. 아니면 에러가 발생함. (surfaceview가 destroy됐는데, 렌더링을 시도해서 발생함)
          • 아래와 같은 에러 발생함
          Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x2d8 in tid 15182 (UnityGfxDeviceW), pid 15065 
          
        • pip모드로 들어올 때에는 onPictureInPictureModeChanged → onConfigurationChanged 으로 호출된다.
        • 원래 화면으로 복구 하는 경우에도 onPictureInPictureModeChanged → onConfigurationChanged 으로 호출된다.
      • isConfigurationInPip 변수를 두어, pip모드에서 닫기를 한 것인지 체크할 수 있도록 로직을 설계했다.
      • 최신 Unity 버전에서는 해당 에러가 발생하지 않아서, 저렇게 처리 하지 않아도 된다
        • 2021.3.28f1이상
profile

겨울팥죽 여름빙수

@여름빙수

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!