ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 최적화 - 14 (코드수정 #InvokeRepeating)
    Galaxy Ball/5. 최적화 2024. 11. 7. 20:25

    이번에 해볼것은 싱글플레이 패배창을 관리하는 SPFailSceneManager코드이다

     

    using System.Collections;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    public class SPFailSceneManager : MonoBehaviour
    {
        void Update()
        {
            StageGameManager stageGameManager = FindObjectOfType<StageGameManager>();
            int randomnumber = Random.Range(1, 6);
    
            if (Input.GetMouseButtonDown(0))
            {
                RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
    
                if (hit.collider != null && hit.collider.gameObject.name == "GoStage")
                {
                    if (stageGameManager.StageClearID <= 5)
                    {
                        SceneManager.LoadScene("Stage");
                    }
                    else if (stageGameManager.StageClearID >= 6)
                    {
                        SceneManager.LoadScene("Main Stage");
                    }
                }
                if (hit.collider != null && hit.collider.gameObject.name == "GoMenu")
                {
                    SceneManager.LoadScene("Start Scene");
                }
                if (hit.collider != null && hit.collider.gameObject.name == "Retry")
                {
                    SceneManager.LoadScene("Story-InGame");
                }
                if (hit.collider != null && hit.collider.gameObject.name == "Random")
                {
                    switch(randomnumber)
                    {
                        case 1:
                            SceneManager.LoadScene("Example Scene");
                            break;
                        case 2:
                            SceneManager.LoadScene("Design Scene");
                            break;
                        case 3:
                            SceneManager.LoadScene("1-1 Intro Scene");
                            break;
                        case 4:
                            SceneManager.LoadScene("Credit Scene");
                            break;
                        case 5:
                            SceneManager.LoadScene("ChallengeScene");
                            break;
                    }
                }
            }
        }
    }

     

    업데이트 안에 모든걸 때려넣은 엉망진창 코드...

     

    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    public class SPFailSceneManager : MonoBehaviour
    {
        private StageGameManager stageGameManager;
    
        void Start()
        {
            // Start에서 StageGameManager를 한 번만 찾도록 변경
            stageGameManager = FindObjectOfType<StageGameManager>();
        }
    
        void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                // 마우스 클릭 위치를 기준으로 RaycastHit2D 실행
                RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
    
                if (hit.collider == null) return;
    
                // 히트된 오브젝트의 이름에 따라 처리
                string hitName = hit.collider.gameObject.name;
    
                if (hitName == "GoStage")
                {
                    LoadStage();
                }
                else if (hitName == "GoMenu")
                {
                    SceneManager.LoadScene("Start Scene");
                }
                else if (hitName == "Retry")
                {
                    SceneManager.LoadScene("Story-InGame");
                }
                else if (hitName == "Random")
                {
                    LoadRandomScene();
                }
            }
        }
    
        // 스테이지를 로드하는 함수
        private void LoadStage()
        {
            if (stageGameManager != null)
            {
                if (stageGameManager.StageClearID <= 5)
                {
                    SceneManager.LoadScene("Stage");
                }
                else if (stageGameManager.StageClearID >= 6)
                {
                    SceneManager.LoadScene("Main Stage");
                }
            }
            else
            {
                Debug.LogWarning("StageGameManager를 찾을 수 없습니다.");
            }
        }
    
        // 랜덤한 씬을 로드하는 함수
        private void LoadRandomScene()
        {
            int randomnumber = Random.Range(1, 6);
    
            switch (randomnumber)
            {
                case 1:
                    SceneManager.LoadScene("Example Scene");
                    break;
                case 2:
                    SceneManager.LoadScene("Design Scene");
                    break;
                case 3:
                    SceneManager.LoadScene("1-1 Intro Scene");
                    break;
                case 4:
                    SceneManager.LoadScene("Credit Scene");
                    break;
                case 5:
                    SceneManager.LoadScene("ChallengeScene");
                    break;
            }
        }
    }

     

    보기 좋게, 그리고 update문안에 모든걸 쓸데없이 다 때려넣은 것들을 줄였다

     

     

    그 다음은 싱글플레이 안에서 아이템을 생성하는 스크립트이다

    using UnityEngine;
    
    public class SPRandomGenerate : MonoBehaviour
    {
        BGMControl bGMControl;
        public GameObject[] spherePrefabs;
        public GameObject background;
        public float minSpawnTime = 7f;
        public float maxSpawnTime = 12f;
    
        private float nextSpawnTime;
        private Collider2D backgroundCollider;
        private StageGameManager stageGameManager;
    
        void Start()
        {
            bGMControl = FindObjectOfType<BGMControl>();
            nextSpawnTime = Time.time + Random.Range(minSpawnTime, maxSpawnTime);
            backgroundCollider = background.GetComponent<Collider2D>();
    
            stageGameManager = FindObjectOfType<StageGameManager>();
            if (stageGameManager == null)
            {
                Debug.LogError("StageGameManager�� ã�� �� �����ϴ�.");
            }
        }
    
        void Update()
        {
            if (Time.time >= nextSpawnTime)
            {
                SpawnSphere();
                nextSpawnTime = Time.time + Random.Range(minSpawnTime, maxSpawnTime);
            }
        }
    
        void SpawnSphere()
        {
            Vector2 min = backgroundCollider.bounds.min;
            Vector2 max = backgroundCollider.bounds.max;
            Vector3 randomPosition = new Vector3(Random.Range(min.x, max.x), Random.Range(min.y, max.y), 0f);
    
            int maxIndex;
    
            if (stageGameManager.StageClearID <= 6)
            {
                return;
            }
            else if (stageGameManager.StageClearID <= 10)
            {
                maxIndex = 0;
            }
            else if (stageGameManager.StageClearID <= 17)
            {
                maxIndex = 1;
            }
            else if (stageGameManager.StageClearID <= 25)
            {
                maxIndex = 2;
            }
            else if (stageGameManager.StageClearID <= 32)
            {
                maxIndex = 3;
            }
            else if (stageGameManager.StageClearID <=44)
            {
                maxIndex = 4;
            }
            else if(stageGameManager.StageClearID == 45)
            {
                maxIndex = 3;
            }
            else if(stageGameManager.StageClearID == 65)
            {
                maxIndex = 3;
            }
            else
            {
                maxIndex = 4;
            }
    
            int prefabIndex = Random.Range(0, maxIndex);
            bGMControl.SoundEffectPlay(2);
            Instantiate(spherePrefabs[prefabIndex], randomPosition, Quaternion.identity);
        }
    }

     

    우선 update에서 매 프레임마다 SpawnSphere 메서드를 실행중이다. 이러면 성능에 지장이 간다

    기왕이면 update문 없이 코드를 짜고 싶은데 7~12초 사이에 매번 아이템을 랜덤으로 생성해야하기에

    이걸 어떻게하면 update없이 짤 수 있을까 싶지만 그럴때 사용하는 기능이 있다

     

    using UnityEngine;
    
    public class SPRandomGenerate : MonoBehaviour
    {
        private BGMControl bGMControl;
        public GameObject[] spherePrefabs;
        public GameObject background;
        public float minSpawnTime = 7f;
        public float maxSpawnTime = 12f;
    
        private Collider2D backgroundCollider;
        private StageGameManager stageGameManager;
        private int maxIndex = 0;
    
        void Start()
        {
            // 필요한 오브젝트 캐싱
            bGMControl = FindObjectOfType<BGMControl>();
            stageGameManager = FindObjectOfType<StageGameManager>();
            backgroundCollider = background.GetComponent<Collider2D>();
    
            if (stageGameManager == null)
            {
                Debug.LogError("StageGameManager를 찾을 수 없습니다.");
                return;
            }
    
            SetMaxIndex();
    
            // 지정된 간격으로 SpawnSphere를 호출
            float initialSpawnTime = Random.Range(minSpawnTime, maxSpawnTime);
            InvokeRepeating("SpawnSphere", initialSpawnTime, initialSpawnTime);
        }
    
        // StageClearID에 따른 maxIndex 설정
        private void SetMaxIndex()
        {
            float stageID = stageGameManager.StageClearID;
    
            if (stageID <= 6)
            {
                CancelInvoke("SpawnSphere"); // StageClearID가 6 이하일 때는 스폰을 중지
            }
            else if (stageID <= 10)
            {
                maxIndex = 0;
            }
            else if (stageID <= 17)
            {
                maxIndex = 1;
            }
            else if (stageID <= 21)
            {
                maxIndex = 2;
            }
            else if (stageID <= 34)
            {
                maxIndex = 3;
            }
            else if (stageID <= 44)
            {
                maxIndex = 4;
            }
            else if (stageID == 45 || stageID == 64 || stageID == 65)
            {
                maxIndex = 3;
            }
            else
            {
                maxIndex = 4;
            }
        }
    
        // 구체 생성
        private void SpawnSphere()
        {
            if (maxIndex < 0) return; // 스폰 조건을 충족하지 않으면 종료
    
            // 배경 내의 무작위 위치 계산
            Vector2 min = backgroundCollider.bounds.min;
            Vector2 max = backgroundCollider.bounds.max;
            Vector3 randomPosition = new Vector3(Random.Range(min.x, max.x), Random.Range(min.y, max.y), 0f);
    
            // 구체 프리팹 중 하나를 무작위로 선택하여 인스턴스화
            int prefabIndex = Random.Range(0, maxIndex + 1);
            if (bGMControl.SoundEffectSwitch)
            {
                bGMControl.SoundEffectPlay(2);
            }
            Instantiate(spherePrefabs[prefabIndex], randomPosition, Quaternion.identity);
    
            // 다음 스폰 간격을 랜덤 설정
            float nextSpawnTime = Random.Range(minSpawnTime, maxSpawnTime);
            CancelInvoke("SpawnSphere");
            InvokeRepeating("SpawnSphere", nextSpawnTime, nextSpawnTime);
            Debug.Log("MaxIndex : " + maxIndex);
        }
    }

     

     InvokeRepeating("SpawnSphere", initialSpawnTime, initialSpawnTime);

     

    바로 InvokeRepeating이라는 기능이다. 

    더보기
    더보기

    1. Update vs InvokeRepeating

    • Update: Update는 프레임마다 실행됩니다. 즉, 매 프레임마다 Time.time과 nextSpawnTime을 비교하게 되어, 스폰 조건을 확인하지 않아도 무조건 실행되므로 반복적이고 불필요한 계산이 발생할 수 있습니다.
    • InvokeRepeating: 특정 주기마다 메서드를 호출하므로, 필요한 시점에만 스폰 로직을 실행하게 됩니다. InvokeRepeating은 지정된 시간 간격마다 실행되기 때문에 불필요한 호출을 줄일 수 있고, 성능을 더 효율적으로 관리할 수 있습니다.

    2. 메모리 관리 및 CPU 부담

    • Update는 게임 오브젝트가 활성 상태인 한 지속적으로 실행되므로, CPU 연산 부담이 누적될 수 있습니다.
    • InvokeRepeating은 지정된 간격 동안 CPU를 활용하지 않고도 로직을 실행할 수 있어, 최적화에 유리합니다.

    3. 간단한 스폰 로직에 최적

    • 스폰처럼 반복 호출이 필요한 작업이라면, InvokeRepeating이 적합합니다. 특히 주기가 명확하게 정해진 경우, InvokeRepeating을 사용하면 단순 반복 연산이 줄어들어 더 효율적입니다.

    즉, 간격 기반의 호출이 필요하거나 주기적인 처리가 필요하다면, InvokeRepeating을 사용하는 것이 훨씬 효율적이다

    이럴때 사용하면 가장 좋은 기능이며 세트로 알아둬야할게

     

     CancelInvoke("SpawnSphere");

     

    조건이나 주기에 따라 캔슬도 가능하다. 

     

    추가로 게임의 밸런스를 위해 StageID가 45, 64, 65일땐 Invincible 아이템은 생성하지 않도록 해주었다

    참고로 저 3개의 스테이지는 중간보스 & 최종보스가 등장하는 스테이지이다.

     

    그 다음은 스테이지맵에서 네비게이션 역할을 하는 스크립트이다

     

     void FixedUpdate()
        {
            // Clearhere 오브젝트를 찾지 못했을 경우 한 번만 찾기 시도
            if (clearhereObject == null)
            {
                clearhereObject = GameObject.Find("Clearhere(Clone)");
                if (clearhereObject != null)
                {
                    lastClearherePosition = clearhereObject.transform.position;
                    UpdateActivation();
                }
            }

     

    간략하게 핵심만 보고 넘어가겠다

     

    1. 우선 update > fixedupdate로 바꾸어주었다. 근데 이렇게 바꿔도 기능이 복잡하질 않으니 큰 변화는 없다

    2. 원래는 매 프레임마다 찾았던 다음 목적지 Clearhere를 FixedUpdate문 안에서 한번만 찾도록 해주었다

     

     

    그 다음은 스토리에 관련된 코드들을 수정해보려고 한다

    누가 말해준건 아니지만 스토리와 관련된 코드들은 중요할 것 같다.

    우선 스테이지맵을 돌아다니는 내내 계속해서 활성화 되어있기 때문에 특히 update같이 매 프레임마다

    검사하는 방식은 굉장히 최적화에 치명적이다

     void Update()
        {
            if (showText != null && stageGameManager.StageClearID == 1)
            {
                if (showText.logTextIndex == 17)
                {
                    stageBallController.enabled = true;
                }
                if (showText.logTextIndex < 41)
                {
                    Stage.SetActive(false);
                }
                if (showText.logTextIndex >= 42)
                {
                    Stage.SetActive(true);
                    Clearhere.gameObject.SetActive(true);
                }
            }
    
            if (showText != null && stageGameManager.StageClearID == 5.5)
            {
                if (showText.logTextIndex == 4)
                {
                    StartCoroutine(IncreaseCameraSize(mainCamera, 112, 5.5f));
                }
                if (showText.logTextIndex == 8)
                {
                    ContinuousRandomMovement[] randomMovements = FindObjectsOfType<ContinuousRandomMovement>();
                    foreach (ContinuousRandomMovement randomMovement in randomMovements)
                    {
                        randomMovement.enabled = false;
                    }
                }
                if (showText.logTextIndex == 23)
                {
                    ContinuousRandomMovement[] randomMovements = FindObjectsOfType<ContinuousRandomMovement>();
                    foreach (ContinuousRandomMovement randomMovement in randomMovements)
                    {
                        randomMovement.enabled = true;
                    }
                    StartCoroutine(HandleCameraAndFadeIn(mainCamera, 15, 7f));
                }
            }
        }

    이게 Ch1story update메서드안에 들어간 코드들이다

    매 프레임마다 이렇게나 많은 조건문을 실행하고 있었으니 충분히 지장이 갈만하다

     

        switch (stageGameManager.StageClearID)
            {
                case 1:
                    stageBallController.enabled = false;
                    textManager.GiveMeTextId(1);
                    showText = FindAnyObjectByType<ShowText>();
    
                    StartCoroutine(HandleStage1());
                    break;
                case 2:
                    textManager.GiveMeTextId(2);
                    break;
                case 5.5f:
                    Destroy(Stage);
                    textManager.GiveMeTextId(3);
                    showText = FindAnyObjectByType<ShowText>();
    
                    StartCoroutine(HandleStage5_5());
                    break;
                case 6:
                    Destroy(Stage);
                    RemainTime.SetActive(true);
                    break;
            }

     

    그래서 update 메서드를 아예 없애주었다..!

    대신 switch문으로 조건에 만족할때만 코루틴을 돌리도록 해주었다.

    이렇게 해주면 매 프레임마다 수많은 조건문들을 검사하지 않아도 될것이다

     

    근데 문제는 다른 스토리 관련 스크립트에서도 다 이런 식이라는것...

      void Update()
        {
            showText = FindObjectOfType<ShowText>();
            if (showText != null && stageGameManager.StageClearID == 65)
            {
                if (showText.logTextIndex == 8 && !isZooming)
                {
                    StartCoroutine(SmoothZoom(5f, 1700f));
                }
                if (showText.logTextIndex == 12)
                {
                    StartCoroutine(ExecuteAfterDelay(4f));
                }
                if (showText.logTextIndex == 28)
                {
                    StartCoroutine(LoadSceneAfterDelay(4f, "Story-InGame"));
                }
            }
        }

    업데이트로 매 프레임마다 FindobjectofType을 실행중이었다. 

    이건 정말 모바일 환경에서 프레임에 지장을 줄 수 있는 위험한 코드로 전부 수정해주었다

     

     

Designed by Tistory.