Unity 입문 팀프로젝트 - 6 (End)

이준호·2023년 12월 7일
0
post-custom-banner

📌 2D 입문 팀프로젝트

➔ 수정된 부분

BounceBulletPrefabLogic.cs

  • Bullet들이 벽이나 공에 맞으면 공격을 다시 할수있게 초기화 메서드를 넣어두는데 Bounce의 경우 공이 튕기다 보니 예외 상황들이 생겼다.
  • 최초 벽에 부딪힌경우 리셋이 되면 공에 부딪히면 리셋이 안되게 막고, 벽에 부딪힌적 없이 공에 맞으면 리셋이 되도록 bool값을 이용하여 예외처리를 하였다.
	// 생략

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.collider.CompareTag("TopWall"))
        {
            if (firstWallCheck == true)
            {
                // byllet CoolTime Reset
                _itemManager.BulletCoolTimeReset();
                // Random Angle Setting
                int randomAngle = Random.Range(0, arrAngles.Length);
                Vector3 tmps = transform.eulerAngles;
                tmps.z = arrAngles[randomAngle];
                transform.eulerAngles = tmps;
                firstWallCheck = false;
            }
            else
            {
                // Bounce Angle Setting
                Vector3 tmp = transform.eulerAngles;
                tmp.z = _Radian - tmp.z;
                transform.eulerAngles = tmp;
                // Bounce After AddForce
            }
            //_rigid.velocity = Vector3.zero;
            _rigid.AddForce(transform.up * _itemManager._player.Force * 1.2f, ForceMode2D.Impulse);
        }
        else if (collision.collider.CompareTag("Wall"))
        {
            Vector3 tmp = transform.eulerAngles;
            tmp.z = (_Radian * 2) - tmp.z;
            transform.eulerAngles = tmp;
            //_rigid.velocity = Vector3.zero;
            _rigid.AddForce(transform.up * _itemManager._player.Force * 1.2f, ForceMode2D.Impulse);
        }
        bulletLifeCount++;

        if (collision.collider.CompareTag("Ball"))
        {
            if(firstWallCheck == true) _itemManager.BulletCoolTimeReset();
            firstWallCheck = false;
            gameObject.SetActive(false);
        }

        // bullet Destory Condition ( 오브젝트 풀링 필요 )
        if(bulletLifeCount >= 4)
        {
            _itemManager.BulletCoolTimeReset();
            firstWallCheck = false;
            gameObject.SetActive(false);
        }
    }











➔ 추가된 부분

Object Pooling

  • Instantiate, Destory는 계속 메모리를 차지해서 나중에 Destory될 때, 쓰레기 메모리가 남는다.
    ( 생성, 삭제 하면서 조각난 메모리가 쌓임 )
    그렇게 메모리들이 쌓이면 유니티 내에서 ( 가비지컬렉트(GC) : 쌓인 조각난 메모리를 비우는 기술 ) 이 실행이 되면 게임자체가 살짝 끊기게 된다.
  • 오브젝트 풀링 : 미리 생성해둔 풀에서 활성화 / 비활성화로 사용
  • Destory를 이용해 삭제하던 Bullet및 아이템을 SetActive(false)로 비활성화 방식으로 바꾸었다.
  • 새로 Instantiate로 찍어내는게 아닌 SetActive(false) -> true로 활성화 방식으로 바꾸다 보니 Bullet Logic 스크립트 부분들에 Awake, Start부분에 있던 것중 다시 생성이 될 때 셋팅되어야 하는 값들을 위해 OnEnable(), OnDisable() 를 사용하여 활성화, 비활성화 될 때 셋팅해주었다.
public class ObjectManager : MonoBehaviour
{  
	// 사용할 프리펩 원형 선언
    GameObject bulletNormalPrefab;
    GameObject bulletBouncePrefab;
    GameObject bulletPenetratePrefab;
    GameObject bulletGuidedPrefab;
    GameObject ballFreezingPrefab;
	
    // 프리펩을 생성하여 저장할 배열 변수 선언
    GameObject[] bulletBounce;
    GameObject[] bulletNormal;
    GameObject[] bulletPenetrate;
    GameObject[] bulletGuided;

    GameObject[] ballFreezing;

    GameObject[] targetPool;

    private void Awake()
    {
        bulletNormalPrefab = Resources.Load<GameObject>("Prefabs/Bullet");
        bulletBouncePrefab = Resources.Load<GameObject>("Prefabs/BounceBullet");
        bulletPenetratePrefab = Resources.Load<GameObject>("Prefabs/PenetrateItemBullet");
        bulletGuidedPrefab = Resources.Load<GameObject>("Prefabs/GuidedMissileBullet");
        ballFreezingPrefab = Resources.Load<GameObject>("Prefabs/BallFreezingLogic");
		
        // 한번에 등장할 개수를 고려하여 배열 길이 할당.
        bulletNormal = new GameObject[6];
        bulletBounce = new GameObject[10];
        bulletPenetrate = new GameObject[5];
        bulletGuided = new GameObject[5];
        ballFreezing = new GameObject[5];
    }

    private void Start()
    {
        Generate();
    }

    void Generate()
    {
    	// Instantiate()로 생성한 인스턴스를 배열에 저장
        // Player Bullets
        for(int index = 0; index < bulletNormal.Length; index++)
        {
            bulletNormal[index] = Instantiate(bulletNormalPrefab);
            // Instantiate()로 생성 후엔 바로 비활성화
            bulletNormal[index].SetActive(false);
        }

        for (int index = 0; index < bulletBounce.Length; index++)
        {
            bulletBounce[index] = Instantiate(bulletBouncePrefab);
            bulletBounce[index].SetActive(false);
        }

        for (int index = 0; index < bulletPenetrate.Length; index++)
        {
            bulletPenetrate[index] = Instantiate(bulletPenetratePrefab);
            bulletPenetrate[index].SetActive(false);
        }

        for (int index = 0; index < bulletGuided.Length; index++)
        {
            bulletGuided[index] = Instantiate(bulletGuidedPrefab);
            bulletGuided[index].SetActive(false);
        }

        // Use Item
        for (int index = 0; index < ballFreezing.Length; index++)
        {
            ballFreezing[index] = Instantiate(ballFreezingPrefab);
            ballFreezing[index].SetActive(false);
        }
    }
	
    // 오브젝트 풀에 접근할 수 있는 함수
    public GameObject MakeObject(string type)
    {
        switch(type) 
        {
            case "BulletNormal":
                targetPool = bulletNormal;
                break;
            case "BulletBounce":
                targetPool = bulletBounce;
                break;
            case "BulletPenetrate":
                targetPool = bulletPenetrate;
                break;
            case "BulletGuided":
                targetPool = bulletGuided;
                break;
            case "BallFreezing":
                targetPool = ballFreezing;
                break;
        }
        // 비활성화된 오브젝트에 접근하여 활성화 시키고, 반환
        for (int index = 0; index < targetPool.Length; index++)
        {
            if (!targetPool[index].activeSelf)
            {
                targetPool[index].SetActive(true);
                return targetPool[index];
            }
        }

        return null;
    }
}










Loding Scene

  • 오브젝트 풀링을 사용하여 처음에 오브젝트를 셋팅하는데 시간이 필요하여 비동기 Loding Scene기능을 추가했다.
    ( 로딩 시간 = 장면배치 + 오브젝트 풀 생성 )
public class LodingSceneController : MonoBehaviour
{
    static string nextScene;

    [SerializeField] Image progressBar;

    // static으로 선언된 함수의 내부에서는 static으로 선언되지 않은 일반 멤버변수나 함수는 바로 호출이 불가능
    // static : 정적, 하나의 메모리
    public static void LoadScene(string sceneName)
    {
        nextScene = sceneName;
        SceneManager.LoadScene("LodingScene");
    }

    void Start()
    {
        StartCoroutine(LoadSceneProgress());
    }

    IEnumerator LoadSceneProgress()
    {
        // LoadScene은 동기방식 Scene불러오기 기능이라 Scene을 다 불러오기 전까지는 다른 작업을 할 수가 없다.
        // LoadSceneAsync는 비동기방식으로 Scene을 불러오는 도중 다른 작업이 가능하다.
        AsyncOperation op = SceneManager.LoadSceneAsync(nextScene);
        // Scene을 비동기로 불러들일 때, Scene의 로딩이 끝나면 자동으로 불러올 Scene으로 이동할 것인지 설정.
        // false로 하면 Scene을 자동으로 넘기지 않고 90% 까지만 로드한 상태로 다음 Scene으로 넘어가지 않은 상태로 기다리게 되며
        // 다시 allowSceneActivation을 true로 변경하면 그 때 남은 부분을 마저 로드하고 넘어가게 된다.
        op.allowSceneActivation = false;

        float timer = 0f;
        while (!op.isDone)  // !isDone : 아직 로딩이 끝나지 않은 상태라면.
        {
            // 반복문이 한번 반복돌 때 마다 유니티엔진의 제어권을 넘기도록 한다.
            // 이런식으로 반복문이 돌 때마다 제어권을 넘겨주지 않으면 반복문이 끝나기 전에는 화면이 갱신x , 그래서 진행바 차는게 보이지 않는다.
            yield return null;

            if (op.progress < 0.9f) // 실제 로딩
            {
                progressBar.fillAmount = op.progress;
            }
            else  // 페이크 로딩
            {
                // UnScaledDeltaTime : 크기가 조정되지 않은 델타 타임.
                // 유니티에서는 Time Scale이라는 요소가 있는데 이 time scale을 조절하면 1일 때는 현실과 같은 속도로, 1보다 작으면 현실보다 느리게, 1보다 크면 현실보다 빠르게 시간이 흐르게 된다.
                // 이 Time scale에 DeltaTime이 영향을 받아서 시간이 느리거나 빠르게 흘러가는 효과를 줄 수 있다.
                // 하지만 이 time scale에 영향을 받지 않고 움직여야 하는 요소 역시 게임에 존재할 수 있는데 이때  그 영향을 받지 않는 값이 Unscaled Delta Time 이다.
                timer += Time.unscaledDeltaTime;
                progressBar.fillAmount = Mathf.Lerp(0.9f, 1f, timer);   // 0.9 ~ 1까지 1초에 걸쳐 채우기
                if (progressBar.fillAmount >= 1f)
                {
                    // progressBar가 다 채워지면 true로 바꿔준다.
                    op.allowSceneActivation = true;
                    yield break;
                }
            }
            
        }
}






아래의 경우는 0 ~ 0.9 & 0.9 ~ 1 모두 시간에 비례 Math.Lerp 하여 로딩바가 올라간다.

IEnumerator LoadScene()
{
    yield return null;

    AsyncOperation op = SceneManager.LoadSceneAsync(nextScene);
    op.allowSceneActivation = false;

    float timer = 0.0f;
    while (!op.isDone)
    {
        yield return null;

        timer += Time.deltaTime; 
        if (op.progress < 0.9f) 
        {
            progressBar.fillAmount = Mathf.Lerp(progressBar.fillAmount, op.progress, timer);
            if (progressBar.fillAmount >= op.progress) 
            {
                timer = 0f;
            }
        } 
        else 
        {
            progressBar.fillAmount = Mathf.Lerp(progressBar.fillAmount, 1f, timer); 
            if (progressBar.fillAmount == 1.0f)
            {
                op.allowSceneActivation = true; 
                yield break; 
            } 
        }
    }
}











➔ 마무리

어려웠던 점

  • 프로젝트를 하면서 깃 사용법에 조금은 익숙해졌다고 생각을 했는데 착각이었다. 코드의 충돌 같은 경우는 괜찮았지만 Scene 충돌이 일어나면 어떻게 해야 할지 감도 안 잡히고 머리가 아팠지만 팀원들이 자신의 일처럼 잘 도와주고 알려줘서 큰일 없이 잘 해결될 수가 있었다.

  • 처음 공격과 아이템 스크립트 간의 관계 설계를 잘못 설정하는 실수를 범해서 코드를 여러 번 수정하게 되었다. 오류들을 수정하면서 점점 알게 되는 점들도 많아서 좋은 기회였지만, 처음 코드의 설계가 매우 중요하다는 것을 뼈저리게 느끼게 되었다.

프로젝트 후기

팀에게 민폐가 되지 않을까 걱정이 태산이었지만 너무나 착하고 좋은 팀원들 덕분에 잘 마무리할 수 있었다. 다른 사람의 오류나 문제를 항상 자기 일처럼 도와주고 알려준 팀원들에게 너무 고맙고 많이 배워갈 수 있는 기회였다.

깃 주소

https://github.com/gusrb0296/2DTeamProject.git

profile
No Easy Day
post-custom-banner

0개의 댓글