본문 바로가기
디지털노마드

[챗GPT게임개발23] ChatGPT를 활용하여 적 공격 처리 추가하기(3)

by 인텔리노마드라이프 2024. 5. 12.
728x90

안녕하세요. 인텔리원스튜디오(IntelliOneStudio)입니다.

오늘은 ChatGPT를 활용하여 Unity 플랫포머 게임에서 적을 공격하는 기능을 추가해볼께요.

 

참고로 공격 처리는 총 3회에 걸쳐 게시될거에요. 오늘은 공격 처리 마지막 시간이예요.

제목 내용
[챗GPT게임개발21] ChatGPT를 활용하여 적 공격 처리 추가하기(1) OnCollisionEnter2D를 활용한 충동 감지 기능 개발
[챗GPT게임개발22] ChatGPT를 활용하여 적 공격 처리 추가하기(2) 애니메이션 이벤트로 콜라이더 위치 조정 
[챗GPT게임개발23] ChatGPT를 활용하여 적 공격 처리 추가하기(3) Physics2D.OverlapCircleAll을 이용한 충돌 감지 

 

[오늘의 목표] Physics2D.OverlapCircleAll을 이용한 충돌 감지
1. 충돌 감지 스크립트 개발
2. 충돌 감지를 위한 hittableLayers 생성

3. 실행 결과 살펴 보기 

 

 ChatGPT 답변 내용 중 제가 별도로 표시한 파란색 내용을 기준으로 읽으시면 좀 더 이해가 쉽게 될거예요.

 

충돌 감지를 위해 챗GPT가 알려준 개선 방법이 있었어요.  

아래 글에서 충돌 이벤트를 활용하는데 실패했고,

[챗GPT게임개발21] ChatGPT를 활용하여 적 공격 처리 추가하기(1)

 

챗GPT는 문제 해결을 위해 다음과 같은 해결책을 알려줬어요.

[질문] 안녕 ChatGPT, 공격은 애니메이션 클립으로 처리되고 있어. 왜 충돌이 발생하지 않는지 확인해줘 

[답변]

공격이 애니메이션으로 구현되어 있으나 실제로 적과의 충돌 이벤트가 발생하지 않는 문제에 대한 해결 방안을 제공해 드리겠습니다. 이는 애니메이션에 의한 시각적인 표현이 있음에도 불구하고 충돌 감지 로직이 제대로 구현되어 있지 않을 가능성이 있습니다. 충돌을 감지하도록 코드를 추가하거나 개선하는 몇 가지 방법을 아래에 설명합니다.

1. 애니메이션 이벤트 사용하기

Unity의 Animator 컴포넌트는 애니메이션 중 특정 시점에 코드를 실행할 수 있는 애니메이션 이벤트 기능을 제공합니다. 공격 애니메이션이 가장 강렬한 순간에 Animation Event를 추가하여 적과의 충돌을 체크하는 메소드를 호출할 수 있습니다.

애니메이션 이벤트 설정

  1. Unity 에디터에서 Animator 윈도우를 열고 공격 애니메이션 클립을 선택합니다.
  2. 애니메이션 타임라인에서 적절한 프레임에 마우스 오른쪽 버튼을 클릭하고 Add Event를 선택합니다.
  3. 추가된 이벤트에서 Function 필드에 호출할 함수 이름을 입력합니다(예: CheckHit)

PlayerController 스크립트에 충돌 검사 메소드 추가

void CheckHit()
{
    // 공격 범위 내에 적이 있는지 검사
    Collider2D[] hits = Physics2D.OverlapCircleAll(transform.position, attackRange, enemyLayer);
    foreach (var hit in hits)
    {
        if (hit.gameObject.CompareTag("Enemy"))
        {
            Debug.Log("Hit an enemy!");
            // 적에게 피해를 주는 로직
            var enemyHealth = hit.GetComponent<Health>();
            if (enemyHealth != null)
            {
                enemyHealth.Decrement();
            }
        }
    }
}

 

여기서 attackRange는 공격 범위를, enemyLayer는 적 캐릭터가 속한 레이어를 나타냅니다.

2. 트리거를 사용한 충돌 감지

플레이어 캐릭터의 공격 부위에 트리거(Collider2D 컴포넌트의 Is Trigger 옵션 활성화)를 설정하고, 공격 애니메이션 동안 이 트리거를 활성화하여 적과의 충돌을 감지할 수 있습니다.

트리거 활성화 및 비활성화

void StartAttack()
{
    attackTrigger.enabled = true;
}

void EndAttack()
{
    attackTrigger.enabled = false;
}


StartAttack과 EndAttack 메소드는 각각 공격 시작과 끝에서 호출되어야 합니다. 이를 애니메이션 이벤트나 UpdateAttackState 메소드에서 관리할 수 있습니다.

3. 물리 엔진 설정 점검

  • Rigidbody2D 설정: 플레이어나 적의 Rigidbody2D 설정이 적절한지 확인합니다. 예를 들어, Rigidbody의 Body Type이 Kinematic이면 충돌이 제대로 감지되지 않을 수 있습니다.
  • Layer 충돌 매트릭스: Project Settings -> Physics 2D에서 다양한 레이어 간의 충돌 가능성을 확인하고 필요에 따라 조정합니다.

이러한 접근 방법을 통해 애니메이션에 따른 공격 처리가 실제 게임 내에서도 정확히 반영될 수 있도록 조정할 수 있습니다.

 

오늘도 원하는 결과를 만들어내봐요.

시작해볼께요. 

애니메이션 이벤트 사용해서 해결하기

챗GPT가 알려준 방식 중 첫번째 개선책을 이용해요.

그리고 시작하기 전에 샘플로 알려준 소스 코드가 알고 싶었어요.

[질문] 안녕 ChatGPT, 아래 코드를 설명해줄래?
void CheckHit()
{
    // 공격 범위 내에 적이 있는지 검사
    Collider2D[] hits = Physics2D.OverlapCircleAll(transform.position, attackRange, enemyLayer);
    foreach (var hit in hits)
    {
        if (hit.gameObject.CompareTag("Enemy"))
        {
            Debug.Log("Hit an enemy!");
            // 적에게 피해를 주는 로직
            var enemyHealth = hit.GetComponent<Health>();
            if (enemyHealth != null)
            {
                enemyHealth.Decrement();
            }
        }
    }
}

[답변]

이 코드는 Unity 게임 개발에서 캐릭터가 공격할 때 사용되는 메소드로, 플레이어의 위치를 중심으로 한 원형 범위 내에 있는 적을 감지하고, 감지된 적에게 피해를 주는 로직을 수행합니다. 아래는 각 부분의 자세한 설명입니다.

코드 설명

void CheckHit()
{
    // 공격 범위 내에 적이 있는지 검사
    Collider2D[] hits = Physics2D.OverlapCircleAll(transform.position, attackRange, enemyLayer);

 

  • Physics2D.OverlapCircleAll: 이 함수는 특정 위치에서 주어진 반경(attackRange) 내에 있는 모든 Collider2D를 반환합니다. 이 함수는 플레이어의 위치(transform.position)를 중심으로 원을 그리고, 이 원 안에 들어온 객체들의 Collider2D를 배열로 반환합니다.
  • transform.position: 현재 객체(이 경우 플레이어)의 위치입니다.
  • attackRange: 플레이어의 공격이 도달할 수 있는 최대 거리입니다.
  • enemyLayer: 이 매개변수는 공격 대상이 될 수 있는 객체들이 속한 레이어를 지정합니다. 이렇게 지정함으로써, 레이어를 통해 공격 대상을 구분할 수 있으며, 불필요한 객체는 검사 대상에서 제외됩니다.
    foreach (var hit in hits)
    {
        if (hit.gameObject.CompareTag("Enemy"))
        {
            Debug.Log("Hit an enemy!");

 

  • 반복문(foreach)을 사용하여 hits 배열에 포함된 모든 콜라이더를 순회합니다.
  • CompareTag("Enemy"): 이 메소드는 각 콜라이더가 부착된 게임 오브젝트의 태그가 "Enemy"인지 확인합니다. 이는 플레이어의 공격이 적에게만 영향을 주도록 하기 위해 사용됩니다.
  • Debug.Log("Hit an enemy!"): 적과 충돌이 감지되면 콘솔에 메시지를 출력하여 개발자가 확인할 수 있도록 합니다.
            // 적에게 피해를 주는 로직
            var enemyHealth = hit.GetComponent<Health>();
            if (enemyHealth != null)
            {
                enemyHealth.Decrement();
            }
        }
    }
}

 

  • GetComponent<Health>(): 감지된 적의 게임 오브젝트에서 Health 컴포넌트를 가져옵니다. 이 컴포넌트는 적의 생명력을 관리합니다.
  • enemyHealth.Decrement(): 적의 Health 컴포넌트가 존재할 경우, 이 메소드를 호출하여 적의 생명력을 감소시킵니다. 이는 플레이어의 공격이 적에게 데미지를 입히는 효과를 나타냅니다.

요약

이 메소드는 플레이어의 공격 액션 중에 호출되어, 플레이어 주변의 적들을 감지하고 공격 범위 내의 적에게 피해를 주는 기능을 수행합니다. 이러한 방식으로 게임 내에서의 상호작용과 전투 메커니즘을 구현합니다.

 

요약의 내용을 보면 필요한 기능을 찾은 것 같아요. 

이 기능을 적용하면 원하는 공격 기능을 추가할 수 있을 것 같아요.

필요한 스크립트를 완성 시켜요.

샘플 코드를 바탕으로 스크립트 개발을 완료해달라고 해요.

[질문] 안녕 ChatGPT, CheckHit 메소드를 PlayerController에 통합 후 PlayerController의 결과 코드를 제공해줘

[답변]

아래는 PlayerController 스크립트에 CheckHit 메소드를 통합한 결과 코드입니다. 이 코드는 공격 애니메이션 중에 적과의 충돌을 감지하고, 적에게 피해를 줄 수 있도록 구성되어 있습니다. 또한, 공격 애니메이션이 시작될 때 CheckHit 메소드를 호출하여 적과의 충돌을 처리합니다.

통합된 PlayerController 코드

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Platformer.Gameplay;
using static Platformer.Core.Simulation;
using Platformer.Model;
using Platformer.Core;

namespace Platformer.Mechanics
{
    public class PlayerController : KinematicObject
    {
        public AudioClip jumpAudio;
        public AudioClip respawnAudio;
        public AudioClip ouchAudio;

        public float maxSpeed = 7;
        public float jumpTakeOffSpeed = 7;
        public float attackRange = 1.5f;  // 공격 범위
        public LayerMask enemyLayer;  // 적 레이어

        private bool isTouchingWall;
        private bool wallSliding;
        private bool wallJumping;

        public JumpState jumpState = JumpState.Grounded;
        public AttackState attackState = AttackState.Idle;

        private bool stopJump;
        public Collider2D collider2d;
        public AudioSource audioSource;
        public Health health;
        public bool controlEnabled = true;

        bool jump;
        Vector2 move;
        SpriteRenderer spriteRenderer;
        internal Animator animator;
        readonly PlatformerModel model = Simulation.GetModel<PlatformerModel>();

        public Bounds Bounds => collider2d.bounds;

        void Awake()
        {
            health = GetComponent<Health>();
            audioSource = GetComponent<AudioSource>();
            collider2d = GetComponent<Collider2D>();
            spriteRenderer = GetComponent<SpriteRenderer>();
            animator = GetComponent<Animator>();
        }

        protected override void Update()
        {
            if (controlEnabled)
            {
                move.x = Input.GetAxis("Horizontal");
                
                if ((jumpState == JumpState.Grounded || wallSliding) && Input.GetButtonDown("Jump"))
                {
                    jumpState = JumpState.PrepareToJump;
                }
                else if (Input.GetButtonUp("Jump"))
                {
                    stopJump = true;
                    Schedule<PlayerStopJump>().player = this;
                }
                
                if (Input.GetButtonDown("Fire1") && attackState == AttackState.Idle)
                {
                    attackState = AttackState.PreparingAttack;
                }
            }
            else
            {
                move.x = 0;
            }

            UpdateWallSlide();
            UpdateJumpState();
            UpdateAttackState();
            base.Update();
        }

        void UpdateAttackState()
        {
            switch (attackState)
            {
                case AttackState.PreparingAttack:
                    animator.SetTrigger("attack");
                    attackState = AttackState.Attacking;
                    break;
                case AttackState.Attacking:
                    CheckHit();  // 공격 실행 시 적을 감지하고 피해를 줍니다.
                    attackState = AttackState.Idle;
                    break;
                case AttackState.Idle:
                    // 공격 완료 후 처리 로직
                    break;
            }
        }

        void CheckHit()
        {
            Vector2 center = new Vector2(transform.position.x + (spriteRenderer.flipX ? -attackRange : attackRange), transform.position.y);
            Collider2D[] hits = Physics2D.OverlapCircleAll(center, attackRange, enemyLayer);
            foreach (var hit in hits)
            {
                if (hit.gameObject.CompareTag("Enemy"))
                {
                    Debug.Log("적을 맞췄습니다!");
                    var enemy = hit.GetComponent<EnemyController>();
                    if (enemy != null)
                    {
                        var enemyHealth = enemy.GetComponent<Health>();
                        if (enemyHealth != null)
                        {
                            enemyHealth.Decrement();
                            if (!enemyHealth.IsAlive)
                            {
                                Debug.Log("적이 사망했습니다.");
                                Schedule<EnemyDeath>().enemy = enemy;
                            }
                        }
                    }
                }
            }
        }

        // 기타 메소드들은 이전과 동일하다고 가정합니다.
    }
}

 

설명

  • 공격 로직 실행: 공격 상태(AttackState)가 Attacking으로 변경될 때 CheckHit 메소드를 호출하여 적을 감지하고, 감지된 적에 대해 피해를 입히거나 사망 처리를 합니다.
  • 사망 이벤트 스케줄링: 적의 체력이 0 이하가 되면 EnemyDeath 이벤트를 스케줄링하여 게임 시스템에 적의 사망을 알립니다.

이 코드는 플레이어의 공격 동작 중 적과의 상호작용을 정확하게 처리하며, 게임 내에서 플레이어의 공격이 적에게 어떤 영향을 미치는지를 구현합니다.

 

필요한 스크립트는 완료된 것 같아요.

 

그런데 코드를 보면 애니메이션에 이벤트를 추가하는 방식이 아니라 스크립트 내에서 처리 방식으로 변경되었어요.

*공격 상태일 때 CheckHit를 호출해서 주변의 적을 감지. 

void UpdateAttackState()
        {
            switch (attackState)
            {
                case AttackState.PreparingAttack:
                    animator.SetTrigger("attack");
                    attackState = AttackState.Attacking;
                    break;
                case AttackState.Attacking:
                    CheckHit();  // 공격 실행 시 적을 감지하고 피해를 줍니다.
                    attackState = AttackState.Idle;
                    break;
                case AttackState.Idle:
                    // 공격 완료 후 처리 로직
                    break;
            }
        }

"enemyLayer"와 "Enemy" 태그 만들기

아래 요약된 내용을 보면,

스크립트를 실행하기 위해 "enemyLayer"와 "Enemy" 태그가 필요해요.

- 공격 범위(attackRange)와 적 레이어(enemyLayer): 이 두 변수는 공격 시 적을 감지하는 데 사용됩니다.
- CheckHit 메소드: 공격 애니메이션 중에 호출되어 플레이어 주변에서 적을 감지하고 피해를 입힙니다. 공격 방향에 따라 적을 감지하는 범위의 중심이 결정됩니다(spriteRenderer.flipX를 사용하여 방향을 확인).
- 적 태그 확인: 감지된 콜라이더 중에서 태그가 "Enemy"인 게임 오브젝트만 처리합니다. 이는 CompareTag 함수로 확인합니다.
- 체력 감소 및 사망 처리: 감지된 적의 Health 컴포넌트를 참조하여 체력을 감소시킵니다. 체력이 0 이하가 되면 적은 사망 처리되며, 이때 EnemyDeath 이벤트를 스케줄링하여 게임 시스템에 적의 사망을 알립니다.

 

*프로젝트에 적용할 때는 enemyLayer -> hittableLayers 로 변경했어요.

[레이어와 태그 생성]

 

아래 enemyLayer 에 대한 추가적인 설명을 정리해달라고 했어요.

[질문] 안녕 ChatGPT, public LayerMask enemyLayer; // 적 레이어 --> 이건 어떤 오브젝트를 연결해야해?

[답변]

enemyLayer는 Unity에서 특정한 유형의 게임 오브젝트를 구분하고 관리할 때 사용하는 LayerMask입니다. enemyLayer는 플레이어의 공격 범위 내에서 감지해야 할 적 캐릭터들이 속한 레이어를 지정하기 위해 사용됩니다. 이를 설정함으로써 플레이어의 공격이 특정한 오브젝트, 즉 적 캐릭터에만 영향을 미치도록 할 수 있습니다.

enemyLayer에 연결해야 할 오브젝트:

  1. 적 캐릭터 설정:
    • 프로젝트 내에 존재하는 적 캐릭터들을 하나의 레이어에 속하게 설정합니다. 예를 들어, 모든 적 캐릭터에 "Enemy"라는 레이어를 지정할 수 있습니다.
  2. 레이어 생성 및 지정:
    • Unity 에디터의 상단 메뉴에서 Edit > Project Settings > Tags and Layers를 선택합니다.
    • 사용 가능한 레이어 중 하나를 선택하고 "Enemy"와 같은 적절한 이름을 입력하여 새 레이어를 생성합니다.
    • 적 캐릭터의 오브젝트를 선택하고, Inspector 창에서 Layer 드롭다운 메뉴를 통해 새로 생성한 "Enemy" 레이어를 지정합니다.
  3. enemyLayer 변수 연결:
    • PlayerController 스크립트가 포함된 게임 오브젝트의 Inspector 창에서 enemyLayer 필드를 찾습니다.
    • 이 필드 옆의 드롭다운 버튼을 클릭하여 "Enemy" 레이어를 선택합니다. 이렇게 설정하면 enemyLayer는 "Enemy" 레이어에 속한 오브젝트만 감지하도록 설정됩니다.

코드 내에서 enemyLayer 사용:

enemyLayer는 주로 충돌 감지 함수에서 사용되며, 특정 레이어에 속한 오브젝트만을 대상으로 충돌 검사를 수행하도록 합니다. 예를 들어, Physics2D.OverlapCircleAll 함수에서 enemyLayer를 매개변수로 사용하여 플레이어의 공격 범위 내에 있는 적 캐릭터만을 감지하도록 설정할 수 있습니다.

이렇게 설정하면 플레이어의 공격 로직이 다른 유형의 게임 오브젝트 (예: 플랫폼, 장애물 등)에 영향을 받지 않고 오직 적 캐릭터에만 적용되어, 게임의 명확성과 정확성을 높일 수 있습니다.

 

그리고 챗GPT가 알려준 순서대로 진행해요.

결과는 아래 그림과 같아요.

 

스크립트, 레이어 그리고 태그를 완료했어요. 이제 공격해봐요.

이제 변경 사항을 적용하고 실행 모드에서 결과를 확인해볼께요. 

 

원하는대로 공격이 잘되네요.

 

오늘은 ChatGPT를 활용하여 Unity 플랫포머 게임에서 적을 공격하는 기능을,

Physics2D.OverlapCircleAll 사용해서 구현해봤어요.

 

다음 시간에는 Health 컴포넌트를 적에게 적용해볼께요. 

 

[골프 연습 중인 인텔리원스튜디오 개발자. 인텔리노마드라이프와 함께 행복하세요 from IntelliOneStudio ]

 

지금까지,

언제나 성장하는 인텔리원스튜디오(IntelliOneStudio)입니다.

감사합니다.

728x90