ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 최적화 - 12 (코드수정 #Canvas Group, #DOTween)
    Galaxy Ball/5. 최적화 2024. 10. 30. 02:51

    지난글까지는 설정에서 건드릴 수 있는 부분들을 손봤다면 이번에는 코드들을 수정해볼 생각이다

    여러차례 만들었지만 이 게임은 게임개발을 배워나감과 동시에 만든 게임이기 때문에

    초창기에 만들어둔 코드들은 아무것도 모르고, 최적화를 염두조차 해두지 않고 만든 코드들이 대부분이다

     

    그러니 이번엔 코드들을 싹 다 검토하고, 필요없는 코드들은 지워버리고, 최적화가 필요한 부분들은

    최적화를 해주고 코드를 수정해주는걸로 하겠다

     

    가장 먼저 생각나는건 바로 페이드인 효과. 게임을 맨 처음 시작할때 새로하기든, 이어하기든 뭐든 눌렀을때

    화면이 검은색으로 페이드인 된뒤 알맞는 씬으로 로드되도록 하였는데

     

    이게 컴퓨터에서는 부드럽게 페이드인 효과가 이루어지는데 폰에서는 대놓고 뚝뚝 끊긴다는것이 보인다는것..

    그렇다고 둘의 사양이 천지차이니 폰에서 페이드인 효과가 뚝뚝 끊기는건 당연한거냐? 그건 절대 아니다

    이미 폰에서 부드러운 페이드인 효과를 봐버렸으니 그건 이유가 될 수 없다. 한번 최적화 해보자

     

    using System.Collections;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    using UnityEngine.UI;
    
    public class SinglePlayerSetting : MonoBehaviour
    {
        public Image FadeIn;
    
        void Start()
        {
            
            this.ContinueBtn.onClick.AddListener(() =>
            {
                else if (stageGameManager.StageClearID <= 6.5f && stageGameManager.StageClearID >= 2)
                {
                    StartCoroutine(FadeInAndLoadScene("Stage"));
                }
                else if (stageGameManager.StageClearID >= 7)
                {
                    StartCoroutine(FadeInAndLoadScene("Main Stage"));
                }
            });
        }
    
        void ResetStageClearIDAndLoadScene(StageGameManager stageGameManager, string sceneName)
        {
            StartCoroutine(FadeInAndLoadScene(sceneName));
        }
    
        IEnumerator FadeInAndLoadScene(string sceneName)
        {
            FadeIn.gameObject.SetActive(true);
            Color originalColor = FadeIn.color;
            while (FadeIn.color.a < 1)
            {
                float newAlpha = FadeIn.color.a + Time.deltaTime / 3;
                FadeIn.color = new Color(originalColor.r, originalColor.g, originalColor.b, newAlpha);
                yield return null;
            }
            fadeInComplete = true;
            if (fadeInComplete)
            {
                yield return new WaitForSeconds(3f);
                SceneManager.LoadScene(sceneName);
            }
        }
    }

     

    이게 페이드인 효과의 핵심이다.

    버튼을 누르면 페이드인 효과가 발동된 뒤, 3초 대기후 버튼의 종류에 맞게 씬이 이동되는 방식이다

     

    이제 여기에 Canvas GroupDOTween이라는 기능을 사용해 모바일에서 부드럽게 구현되도록 해볼것이다

    그전에 둘의 의미를 알고 가보도록 하자

     

    1. Canvas Group

    더보기

    CanvasGroup은 Unity UI 시스템에서 전체 UI 요소의 투명도, 상호작용 가능 여부, 그리고 Raycast(터치 입력) 차단을 관리하기 위한 컴포넌트입니다.

    특정 UI 요소나 그 하위에 있는 모든 UI 요소에 일괄적으로 설정을 적용할 수 있어 편리한데요, 특히 페이드 인/아웃 효과를 구현할 때 활용도가 높습니다.

     

    CanvasGroup을 사용하면 하위 UI 요소들의 투명도와 입력 차단을 쉽게 제어할 수 있습니다. 대신 검정 이미지 하나만 투명도를 조절할 계획이라면 굳이 CanvasGroup을 사용하지 않고도, UI 이미지의 색상 알파 값을 조절하는 방식으로 충분히 페이드 인 효과를 줄 수 있습니다.

    정확히 이해가 되진 않더라도 이미지 같은 UI 요소에 붙일 수 있는 컴포넌트이고,

    특히 페이드인/아웃에 적합하다는건 알겠다

     

    2. DOTween

    더보기

    DOTween은 Unity에서 DOTween은 Unity에서 애니메이션을 간편하게 구현할 수 있도록 도와주는 도구로, 복잡한 애니메이션 효과를 쉽게 처리할 수 있게 해줍니다. 성능이 중요한 게임에서도 원활하게 사용할 수 있는 강력한 라이브러리입니다. 기본적으로는 오브젝트의 속성(위치, 회전, 크기, 색상 등)을 시간에 따라 변하게 하는 방식으로 애니메이션을 구현할 수 있게 해줍니다.

    가장 중요한건 맨 첫줄에 적혀있다. 애니메이션을 간편하게 구현할 수 있도록 도와주는 도구.

    패키지 매니저에서 따로 다운받아야 하며 성능 최적화를 다각도로 적용하여

    복잡한 애니메이션을 가볍게 관리하여 낮은 성능의 기기에서도 부드럽게 구현할 수 있다고 한다

     

    자 그럼 이 두가지를 이용하여 한번 최적화를 해보자

     

     

    우선 유니티 에셋스토어에서 DOTween을 다운 받은뒤 패키지매니저에서 Import 해주자

     

    그리고 페이드인 용으로 쓰일 이미지에 Canvas Group 컴포넌트를 부착해주고 Alpha값을 0으로 초기화

     

    using System.Collections;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    using UnityEngine.UI;
    using DG.Tweening;   // DOTween 사용 가능
    
    public class SinglePlayerSetting : MonoBehaviour
    {
        public CanvasGroup fadeCanvasGroup; // CanvasGroup 추가
        ........
        void StartFadeIn(string sceneName)
        {
            fadeCanvasGroup.alpha = 0; // 초기 알파값을 0으로 설정
            fadeCanvasGroup.gameObject.SetActive(true); // CanvasGroup 활성화
            fadeCanvasGroup.DOFade(1, 3f) // 3초 동안 알파값을 1로 애니메이션
                .SetUpdate(true) // TimeScale 영향을 받지 않도록 설정
                .OnComplete(() =>
                {
                    SceneManager.LoadScene(sceneName); // 페이드 인 완료 후 씬 로드
                });
        }
    }

     

    그럼 이제 DOTween을 사용 가능하게 된다. fadeCanvasGroup에는 아까 본 페이드용 이미지를 부착해주면 된다

    버튼을 누르자마자 Canvas Group에서 알파값이 올라가는것을 실시간으로 확인할 수 있다. 물론 구현도 잘된다!

     

     

    그 다음은 BGM과 SoundEffect를 관리하는 BGMControl 스크립트를 최적화 해보겠다

     

     

    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    public class BGMControl : MonoBehaviour
    {
        public AudioSource[] SoundEffect;
        public bool BGMSwitch = true;
        public bool SoundEffectSwitch = true;
    
        private void Awake()
        {
            // 게임 시작 시 저장된 상태를 불러오기
            LoadAudioSettings();
            UpdateAudioSources(); // 초기 상태 반영
        }
    
        // 소스의 활성화 상태를 업데이트합니다.
        public void UpdateAudioSources()
        {
            ToggleAudioSources(SoundEffect, SoundEffectSwitch);
        }
    
        // 소스의 활성화 상태를 토글합니다.
        void ToggleAudioSources(AudioSource[] sources, bool isEnabled)
        {
            foreach (var source in sources)
            {
                if (isEnabled && !source.enabled)
                {
                    source.enabled = true; // 활성화 상태로 변경
                }
                else if (!isEnabled && source.isPlaying)
                {
                    source.Stop(); // 비활성화 상태에서 재생 중이면 정지
                    source.enabled = false; // 비활성화 상태로 변경
                }
            }
        }
    
        // 게임 시작 시 저장된 상태를 불러오기
        private void LoadAudioSettings()
        {
            BGMSwitch = PlayerPrefs.GetInt("BGMSwitch", 1) == 1; // 기본값은 true
            SoundEffectSwitch = PlayerPrefs.GetInt("SoundEffectSwitch", 1) == 1; // 기본값은 true
        }
    
        // 상태가 변경될 때 저장하기
        public void SaveAudioSettings()
        {
            PlayerPrefs.SetInt("BGMSwitch", BGMSwitch ? 1 : 0);
            PlayerPrefs.SetInt("SoundEffectSwitch", SoundEffectSwitch ? 1 : 0);
            PlayerPrefs.Save();
        }
    
        // 새로운 메서드 추가
        public void SoundEffectPlay(int index)
        {
            if (index >= 0 && index < SoundEffect.Length)
            {
                SoundEffect[index].Play();
            }
            else
            {
                Debug.LogWarning("SoundEffectPlay: Index out of range.");
            }
        }
    
        // BGMSwitch 또는 SoundEffectSwitch가 변경되었을 때 호출하세요.
        public void OnAudioSettingChanged()
        {
            UpdateAudioSources(); // 음향 설정 업데이트
        }
    }

     

    • Update 메서드 제거: 음향 설정이 변경될 때만 UpdateAudioSources를 호출하도록 하여 불필요한 매 프레임 호출을 피했다
    • ToggleAudioSources 개선: 소스가 비활성화될 때만 Stop()과 enabled = false를 호출하도록 하여 성능 오버헤드를 줄였다.
    • OnAudioSettingChanged 메서드 추가: BGMSwitch 또는 SoundEffectSwitch가 변경될 때 이 메서드를 호출하여 음향 소스를 업데이트할 수 있다.

     

    그 다음은 좀 뜬금없긴 한데 동적으로 좌우로 움직이는 고정물체에 관한 코드이다

    using System.Collections;
    using UnityEngine;
    
    public class GojungMoving : MonoBehaviour
    {
        public float speed = 1.5f; 
        public float distance = 1.7f; 
        private bool movingLeft = true;
    
        void Start()
        {
            StartCoroutine(Move());
        }
    
        IEnumerator Move()
        {
            Vector3 startPosition = transform.position;
            Vector3 leftPosition = startPosition + Vector3.left * distance;
            Vector3 rightPosition = startPosition + Vector3.right * distance;
    
            while (true)
            {
                if (movingLeft)
                {
                    yield return StartCoroutine(MoveToPosition(leftPosition));
                    movingLeft = false;
                }
                else
                {
                    yield return StartCoroutine(MoveToPosition(rightPosition));
                    movingLeft = true;
                }
            }
        }
    
        IEnumerator MoveToPosition(Vector3 targetPosition)
        {
            while (Vector3.Distance(transform.position, targetPosition) > 0.01f)
            {
                transform.position = Vector3.MoveTowards(transform.position, targetPosition, speed * Time.deltaTime);
                yield return null;
            }
        }
    }

     

    사실 움직이니 고정물체라는 말이 안맞긴하지만...

    이것도 한참 예전에 짠 코드라 확실히 가독성도 떨어지고 잘 짠 코드처럼 보이지 않는다

     

     

    using UnityEngine;
    
    public class GojungMoving : MonoBehaviour
    {
        [SerializeField]
        private float speed = 1.5f; 
        [SerializeField]
        private float distance = 1.7f; 
        private Vector3 targetPosition;
        private bool movingLeft = true;
    
        void Start()
        {
            // 초기 목표 위치 설정
            targetPosition = transform.position + Vector3.right * distance;
        }
    
        void Update()
        {
            // 목표 위치로 이동
            transform.position = Vector3.MoveTowards(transform.position, targetPosition, speed * Time.deltaTime);
    
            // 목표 위치에 도달했는지 확인
            if (Vector3.Distance(transform.position, targetPosition) < 0.01f)
            {
                // 이동 방향 전환
                if (movingLeft)
                {
                    targetPosition = transform.position + Vector3.left * distance;
                }
                else
                {
                    targetPosition = transform.position + Vector3.right * distance;
                }
                movingLeft = !movingLeft; // 방향 전환
            }
        }
    }

     

     

    기존에 사용한 코루틴 두개를 지워버리고 update문 안에 넣는걸로 대체했다.

    이러면 불필요한 부하를 줄여 더 부드러운 움직임이 가능하다

     

    이것으로 코루틴도 과도하게 쓸바엔 즉각적인 update가 더 효과적이라는것을 알 수 있다

     

    그 다음은 연습모드(구 챌린지모드)를 손봐주겠다

     

    using UnityEngine;
    using System.Collections.Generic;
    
    public class BulletSpawn : MonoBehaviour
    {
        public GameObject bulletPrefab;
        public float spawnInterval = 10f;
        public float minimumSpawnInterval = 1f;
        public float minForce = 1.7f;
        public float maxForce = 7f;
    
        private BoxCollider2D backgroundCollider;
        private float timer;
    
        private Queue<GameObject> bulletPool = new Queue<GameObject>();
    
        void Start()
        {
            GameObject background = GameObject.Find("BackGround");
            if (background != null)
            {
                backgroundCollider = background.GetComponent<BoxCollider2D>();
            }
    
            // 오브젝트 풀 초기화
            for (int i = 0; i < 50; i++)
            {
                GameObject bullet = Instantiate(bulletPrefab);
                bullet.SetActive(false);
                bulletPool.Enqueue(bullet);
            }
    
            timer = spawnInterval;
        }
    
        void Update()
        {
            timer -= Time.deltaTime;
    
            if (timer <= 0f)
            {
                SpawnBullet();
                spawnInterval = Mathf.Max(minimumSpawnInterval, spawnInterval - 0.2f);
                timer = spawnInterval;
            }
        }
    
        void SpawnBullet()
        {
            if (bulletPool.Count == 0) return;
    
            GameObject bullet = bulletPool.Dequeue();
            bullet.SetActive(true);
    
            float x = Random.Range(backgroundCollider.bounds.min.x, backgroundCollider.bounds.max.x);
            float y = Random.Range(backgroundCollider.bounds.min.y, backgroundCollider.bounds.max.y);
            bullet.transform.position = new Vector2(x, y);
    
            Rigidbody2D rb = bullet.GetComponent<Rigidbody2D>();
            if (rb != null)
            {
                Vector2 forceDirection = Random.insideUnitCircle.normalized;
                float forceMagnitude = Random.Range(minForce, maxForce);
                rb.velocity = Vector2.zero; // 이전 힘 초기화
                rb.AddForce(forceDirection * forceMagnitude, ForceMode2D.Impulse);
            }
    
            // 풀에 총알 반환하기 위한 메서드 구현 필요
        }
    
        public void ReturnBulletToPool(GameObject bullet)
        {
            bullet.SetActive(false);
            bulletPool.Enqueue(bullet);
        }
    }

     

    연습모드에서 일정한 주기로 총알을 생성하는 코드. 미리 50개의 총알 오브젝트를 생성해놓고

    사용하도록 하였다. 이러면 오히려 성능에 더더욱 도움이 될 수 있다

     

     

Designed by Tistory.