[UNITY],[C#]/TIL : UNITY

[Unity] 똥피하기게임에서 자동으로 똥을 피하는 NPC

네,가능합니다 2024. 10. 16. 18:30

아아.. 제목을 정하기가 너무 어려워서 대충 적었습니다.

 

단순한 게임을 여러명에서 작업하려다 보니 너무 빠르게 끝나버린감이 없지않아 있어서 타이틀 씬 만들기를 훔쳐와서 제가 만들기로 했습니다..

 

그냥 타이틀만 만들거면 훔칠 이유가 없죠 !

 

제가 구상한건 타이틀 이미지가 깜박이고 그 뒤에선 NPC좀비가 왔다갔다하며 똥을 피하고 있는걸 보여주고 싶었는데

 

똥을 피하는 방법은 NPC가 알아서 해야겠죠 ?

 

물론 피하다가 맞으면 똑같은 상황에서 그 값은 적용하지 않는다 등 이런 머신러닝급으로 만든다는건 아니고..

 

아래의 와이어프레임처럼 구상했습니다.

 

그리고 사실 단일책임원칙에 어긋나는 스크립트이니 적용시 고쳐 사용하길 바랍니다.

(이동로직은 별도로 빼주는게 맞다고 생각합니다...만 협업중에 이미 MovementController를 만들고있는 분이 계시기때문에 그냥 한 스크립트에 작성하기로 했습니다.)

 

 

그리고 불이라고했다가 똥이라고 했다가 하는데 불 = 똥, 똥 = 불 로 이해부탁드립니다..

 

 

너무 간단하지 않은가 ! 이제 코드를 작성해보자 설명은 주석을 달아주겠다

 

    // NPC속도
    [SerializeField] private float moveSpeed = 7f;
    // 불 감지 범위 (캐릭터 기반)
    [SerializeField] private float detectionRange = 5f;
    // 감지하고 도망갈 범위
    [SerializeField] private float avoidDistance = 2f;
    // X축의 한계값 (벽에 비빔 방지)
    [SerializeField] private float boundaryX = 8f;
    
    // 이동방향
    private Vector2 movementDirection;
    // Fire 태그의 해시 값 (성능 최적화를 위해 해시로 저장)
    private int fireTagHash;
    // Sprite(방향에 따라 스프라이트를 플립)
    private SpriteRenderer spriteRenderer;

 

변수 선언은 끝났고 이제 Awake 함수에 필요한것들을 작성해봅시다.

 

    private void Awake()
    {
        // Fire태그의 해시값을 받아놓고
        fireTagHash = "Fire".GetHashCode();

        // NPC가 멈춰있는건 마음에 안들어서 첫 시작은 우측으로 이동하게 했음
        movementDirection = Vector2.right;

        // SpriteRendere 가져오기 (자식오브젝트에 MainSprite를 만들어놔서 이렇게했음)
        spriteRenderer = GetComponentInChildren<SpriteRenderer>();
    }
    private void Update()
    {
        // 가장 가까운 불을 찾아 반환해주는 함수를 호출하여 closestFire 에 담아줌
        GameObject closestFire = FindClosestFire();
        
        // 해당값이 null이 아니라면 회피 하는 함수 호출
        if (closestFire != null)
        {
            AvoidFire(closestFire.transform.position);
        }
        // null이라면 계속 움직임(함수 내부에 벽에 부딫히면 반대로 움직이게 해뒀음)
        else
        {
            MoveContinuously();
        }

        // NPC실제로 움직임이 일어나는 함수(스프라이트 회전 로직도 여기에있음) 위에선 방향을 받아옴
        MoveNPC();
    }

 

여기까지는 사실 준비작업 아래는 실제 기능을 구현한 로직들이 대부분

 

    GameObject FindClosestFire()
    {
        // 모든 Fire 태그를 가진 오브젝트를 배열로 저장 (불을 생성할때 리스트로 저장해서 받아올까 했지만 )
        // 타이틀에 이것밖에없는데 그렇게까지 최적화 할 필요는 없다고 판단
        GameObject[] fires = GameObject.FindGameObjectsWithTag("Fire");
        GameObject closestFire = null; //초기화
        float closestDistance = detectionRange; // 설정 된 감지범위 확인

        // foreach로 모든 불을 확인함
        foreach (GameObject fire in fires)
        {
            // 오브젝트에 fire 태그 해시값으로 확인하고
            if (fire.tag.GetHashCode() == fireTagHash)
            {
                // NPC와 불 사이 거리 계산
                float distanceToFire = Vector2.Distance(transform.position, fire.transform.position);

                // 가장 가까운 불을 기록하기
                if (distanceToFire < closestDistance)
                {
                    closestFire = fire;
                    closestDistance = distanceToFire;
                }
            }
        }
        // 가장 가까운 불 반환 (없으면 null)
        return closestFire;
    }
    void AvoidFire(Vector2 firePosition)
    {
        // 불에서 멀어지는 방향을 위치기반으로 계산 단순하게 왼쪽오른쪽만 판단하여 움직일것임
        Vector2 directionAwayFromFire = (transform.position - (Vector3)firePosition).normalized;
        movementDirection = new Vector2(directionAwayFromFire.x, 0) * avoidDistance; // Y축 고정
    }
    void MoveContinuously()
    {
        // 설정해둔 X좌표 이하나 이상으로 움직이면 반대로 보내기
        if (transform.position.x < -boundaryX)
        {
            movementDirection = Vector2.right;
        }
        else if (transform.position.x > boundaryX)
        {
            movementDirection = Vector2.left;
        }
    }
    void MoveNPC()
    {
        // 실제로 움직임이 발생 
        transform.Translate(movementDirection * moveSpeed * Time.deltaTime);

        // 기본이 우측을 보고있는 Sprite라서 왼쪽 방향으로 이동이라면 플립
        if (movementDirection.x < 0)
        {
            spriteRenderer.flipX = true;
        }
        else if (movementDirection.x > 0)
        {
            spriteRenderer.flipX = false;
        }
    }

 

이렇게 작성하고 NPC에게 갖다주면 끝이다 ! 가끔 댕청하게 한대씩 맞지만 꽤나 그럴듯한 연출을 보여준다.

 

매번 TIL을 작성할때 강의하는 느낌으로 작성하지만 내가 다 알고있으니 알려주겠다가 아니다.

나는 나중에 까먹은 나를 위해 작성한다.

코딩은 그런 것 같다. 매번 찾아보고 작성하거나 정말 자주 사용하는 코드라면 기억하겠지만

작성해본적 있는 로직이라고 바로 기억이 나는게 아니다. (내 기준일수도..)

 

그걸 이해를 하고 수정하거나 업데이트 할 수 있는 역량이 있냐가 중요한 것 같다.

 

그래서 TIL을 작성하며 다시 되짚어보고 나에게 한번 더 강제로 코드리뷰를 시키며 반성할점이 있는지 보는것이다.

 

작동영상을 보며 끝내자.

 

캐릭터 및 맵의 출처

📒 골드메탈 스튜디오

브랜드 | 유튜브 | 블로그

 

골드메탈 스튜디오 : 네이버 블로그

멀티크리에이터

blog.naver.com

 

아 불은 아직 미완성이라 흰색이다..