Unity 사전캠프 13일차 - 오류 해결하기, 게임 요소 확장하기

한예준·2025년 4월 2일

데미지 텍스트 출력 오류

  • 어제 발견된 오류들을 정리해보았다.

    • 데미지 텍스트가 화면에 출력되지 않음
    • 체력이 가득 차 있지 않아도 Ally가 총알을 통과시킴
    • Enemy가 체력이 0이 되어도 사라지지 않음
  • 데미지 텍스트 문제부터 해결하기로 한다. (나머지 문제와 연결되어 있다고 판단)

  • ShowDamageText() 함수는 호출되었고, 프리팹도 생성되었다.

  • 하지만 화면에는 아무것도 보이지 않았다.

  • 문제 원인 의심

    • 텍스트 위치가 잘못됨
    • 텍스트 프리팹 구조 문제 가능성
  • Debug.Log를 이용하여 텍스트가 생성된 좌표를 확인해보았다.

  • 좌표를 찍어보니 (-960, -540)처럼 화면 밖에 생성되고 있었다.

  • transform.position + Vector3.up * 2f로 위치 조정해도 해결되지 않았다.

  • Canvas 설정을 확인한다.

    • Screen Space - Overlay로 되어 있었고 문제 없음
    • 카메라도 제대로 연결되어 있었음
  • 프리팹 문제라고 판단하여 프리팹의 세부 내용을 확인해보았다.

    • 프리팹 안의 Canvas (Environment)라는 부모 항목이 존재함을 알았다.
    • 나는 이 항목이 기존 Canvas와 다르기 때문에 발생하는 문제라고 판단하였다.
    • 그래서 프리팹을 지우고 새로 생성해보았다.
    • 하지만 프리팹 안의 Canvas (Environment)는 실제 게임에선 생성되지 않는 임시 UI였다. → 문제X
  • 결국 문제는 좌표 변환 코드

    • 생성이 제대로 되어도 Canvas에 특정 좌표에 생성된 텍스트가 게임 화면에 출력이 되기 위해서는 좌표 변환이 제대로 이루어져야 했다.
  • 하지만 원인을 정확히 파악할 수 없어서 조건 분기를 이용하기로 하였다.

  • 수정한 코드:

Vector3 worldPos = transform.position + Vector3.up * 2f;
Vector3 screenPos = Camera.main.WorldToScreenPoint(worldPos);

if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
    RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, screenPos, null, out anchoredPos);
}
else
{
    RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, screenPos, Camera.main, out anchoredPos);
}
  • 수정 후:

    • 텍스트가 정확히 유닛 머리 위에 생성된다.
    • 위로 떠올랐다가 사라지는 동작 정상 작동한다.
    • 총알과 오브젝트의 충돌이 제대로 처리되었다.
  • 결국 문제는 좌표 변환 로직 이었다.

  • 이걸 고치고 나니 다른 문제도 다 해결되었다.

  • 다음은 실행 결과이다.

  • 데미지 텍스트가 정상적으로 출력됨을 확인했다.

다양한 공격 투사체

  • 다음으로, 다른 방식의 투사체를 생성해보도록 하겠다. 먼저 이 투사체의 특징을 정해보겠다.

    • 기존 투사체보다 느림.
    • 바닥에 닿거나 적군에게 닿으면 폭발함.
    • 일정 범위 안에 있는 적들 모두에게 데미지를 입힘.
    • 폭발할 때의 이펙트도 기존과 다름.
  • 처음에는 기존 Bullet 스크립트와는 별개로 새로운 스크립트를 만들까 고민했다. 하지만 앞으로 투사체 종류가 점점 늘어날 것을 생각하면, 매번 새로운 스크립트를 만드는 방식은 관리가 어렵다고 판단했다.

  • 그래서 추상 클래스를 이용하기로 했다.

    • 공통 기능은 하나로 묶고, 개별 기능은 따로 확장하고 싶었음
    • 기존에는 투사체마다 거의 비슷한 코드가 반복됐고, 수정할 때도 일일이 손봐야 했음
    • 공통되는 동작(이동, 충돌, 수명 관리 등)은 한 곳에 두고, 투사체마다 달라지는 동작만 따로 구현하고 싶었음
  • 추상 클래스의 장점

    • abstract 키워드를 이용하면 해당 클래스는 설계용으로만 사용할 수 있음
    • OnHit()처럼 반드시 오버라이드해야 하는 메서드를 강제할 수 있음
    • 코드 중복을 줄이고, 유지보수를 훨씬 쉽게 만들 수 있음
  • 추상 클래스 BulletManager.cs

public abstract class BulletManager : MonoBehaviour
{
    public float speed = 5f;
    public int damage = 1;
    public string targetTag;
    public GameObject hitEffectPrefab;
    public float lifetime = 3f;
    protected virtual void Start()
    {
        Destroy(gameObject, lifetime);
    }
    protected virtual void Update()
    {
        transform.Translate(Vector2.right * speed * Time.deltaTime);
    }

    protected virtual void OnTriggerEnter2D(Collider2D collision)
    {
        if (!collision.CompareTag(targetTag)) return;

        if (hitEffectPrefab != null)
        {
            GameObject fx = Instantiate(hitEffectPrefab, transform.position, Quaternion.identity);
            Destroy(fx, 0.5f);
        }

        OnHit(collision);
    }

    protected abstract void OnHit(Collider2D col);
}
  • 각 코드 별 기능

    • 관통 여부 : OnHit() 내부에서 Destroy(gameObject) 호출 여부로 결정
    • 타겟 설정 : targetTag 변경으로 아군/적군 구분
    • 속도/데미지 설정 : Inspector 또는 코드에서 설정 가능
    • 이펙트 추가 : hitEffectPrefab에 프리팹 연결
  • 이에 따라 기존 Bullet.csBulletManager.cs에게서 상속받은 일반 투사체로 기능할 수 있도록 수정한다.

public class Bullet : BulletManager
{
    protected override void OnHit(Collider2D col)
    {
        UnitHealth unit = col.GetComponent<UnitHealth>();
        if (unit != null)
        {
            unit.TakeDamage(damage);
        }

        Destroy(gameObject);
    }
}
  • BulletManager를 상속받는다.
  • TargetTag를 통해 적군/아군 판별
  • OnHit()을 통해 적군과 충돌했을 때 데미지를 주고 투사체 제거

일단 여기까지 구현해보고 나머지는 내일 구현해보도록 하겠다. 글에는 잘 표현이 안 됐지만, 오류를 고치는 데에 시간이 너무 많이 들어간다. 원인을 찾는 데에 시간이 많이 걸리곤 하는데, 이 시간을 줄이기 위해서 다양한 기능들을 구현해보며 연습해보겠다.

0개의 댓글