캐릭터가 물속에 입수하면 일정 시간 후 데미지를 적용하도록 만들어보려 한다.

물에 BoxCollider 적용

우선 물에 BoxCollider를 적용한다.

image-20250307123936548

물에 1만큼 입수하면 Collider와 접촉 할 수 있도록 적용했다.

이후 캐릭터와 Collider가 만나면 트리거를 작동하는 방식으로 구현할 예정이다.

기존 CampFire 코드 활용

기존에 작성한 CampFire 스크립트가 있다.

public class CampFire : MonoBehaviour
{
    public int damage;
    public float damageRate;

    List<IDamagable> things = new List<IDamagable>();

    void Start()
    {
        InvokeRepeating("DealDamage", 0, damageRate);
    }

    void DealDamage()
    {
        for (int i = 0; i < things.Count; i++)
        {
            things[i].TakePhysicalDamage(damage);
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.TryGetComponent(out IDamagable damagable))
        {
            things.Add(damagable);
        }
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.TryGetComponent(out IDamagable damagable))
        {
            things.Remove(damagable);
        }
    }
}

이 코드는 트리거를 통해 즉시 데미지를 적용하는 방식으로 설계되었다.

하지만 물에서는 즉시 데미지를 적용하는 것이 아니라 일정 시간 후에 데미지를 적용해야 한다.

따라서 코드를 변형할 필요가 있다.

코드 변형을 위한 변경 사항 정리

  • 딜레이 타임 추가: 즉시 데미지를 입히는 것이 아니라 일정 시간이 지난 후에 데미지를 적용해야 한다.
  • 코루틴 적용: WaitForSeconds를 이용해 일정 시간이 지나야만 데미지를 받도록 변경한다.
  • 범위 이탈 처리: 대기 시간 중 캐릭터가 물을 벗어나면 데미지를 받지 않도록 설정한다.

  • 데이터 구조 변경: 중복 처리를 방지하기 위해 List 대신 HashSet을 사용한다.

변경된 CampFire 코드

public class CampFire : MonoBehaviour
{
    public int damage;
    public float damageRate;
    public float delayTime = 0;
    bool enter = false;

    HashSet<IDamagable> things = new HashSet<IDamagable>(); // 중복 방지

    void Start()
    {
        InvokeRepeating("DealDamage", 0, damageRate);
    }

    void DealDamage()
    {
        foreach (var thing in things)
        {
            thing.TakePhysicalDamage(damage);
        }
    }

    IEnumerator AddDelay(IDamagable damagable)
    {
        yield return new WaitForSeconds(delayTime);

        if (!things.Contains(damagable) && enter)
        {
            things.Add(damagable);
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        enter = true;
        if (other.TryGetComponent(out IDamagable damagable))
        {
            StartCoroutine(AddDelay(damagable));
        }
    }

    private void OnTriggerExit(Collider other)
    {
        enter = false;
        if (other.TryGetComponent(out IDamagable damagable))
        {
            things.Remove(damagable);
        }
    }
}

코드 변경 이유

기존의 CampFire 코드는 트리거에 진입하면 바로 데미지를 입도록 되어 있었다. 하지만 물에서는 일정 시간 후 데미지가 들어가야 하므로 AddDelay 코루틴을 추가했다.

  1. delayTime 변수를 추가하여 지연 시간을 조절할 수 있도록 했다.
  2. OnTriggerEnter에서 바로 데미지를 적용하는 대신, StartCoroutine(AddDelay(damagable))을 호출하여 일정 시간이 지나야 데미지를 입도록 변경했다.
  3. 데이터 구조 변경: List 대신 HashSet을 사용하여 중복 처리를 방지했다.
  4. OnTriggerExit에서 things.Remove(damagable)을 호출하여, 캐릭터가 물을 벗어나면 데미지가 중단되도록 했다.
  5. 코루틴 실행 중 범위를 벗어난 경우를 고려하여 bool enter 추가: AddDelay가 실행되는 동안 캐릭터가 물을 벗어날 경우에도 데미지가 추가되지 않도록 수정했다.

트러블 슈팅

개발 과정에서 몇 가지 문제를 해결해야 했다.

코루틴을 중지하는 기능이 없었음

  • 원래는 OnTriggerEnter에서 StartCoroutine으로 실행했지만, OnTriggerExit에서 해당 코루틴을 멈추는 코드가 없었다.
  • 해결 방법: Dictionary<IDamagable, Coroutine>을 사용해 실행 중인 코루틴을 추적하는 방식으로 수정하려 했지만, 최종적으로는 HashSet을 사용하여 AddDelay 내에서 중복을 방지하는 형태로 간결하게 해결했다.

코루틴이 실행된 후 범위를 벗어나도 데미지가 적용되는 문제

IEnumerator AddDelay(IDamagable damagable)
{
    yield return new WaitForSeconds(delayTime);

    if (!things.Contains(damagable))
    {
        things.Add(damagable);
    }
}

private void OnTriggerEnter(Collider other)
{
    if (other.TryGetComponent(out IDamagable damagable))
    {
        StartCoroutine(AddDelay(damagable));
    }
}

private void OnTriggerExit(Collider other)
{
    if (other.TryGetComponent(out IDamagable damagable))
    {
        things.Remove(damagable);
    }
}
  • 바로 위 기존 코드에서는 AddDelay가 실행된 후 things.Contains(damagable)만 확인하여 데미지를 추가했다.
  • 하지만 캐릭터가 물에 있다가 대기 시간 중 나가버리면, 여전히 데미지가 추가되는 문제가 발생했다.
  • 해결 방법: bool enter 변수를 도입하여 OnTriggerEnter에서 true, OnTriggerExit에서 false로 설정했다.
  • AddDelay에서 enter 값을 확인하여, 캐릭터가 여전히 물에 있는 경우에만 데미지를 적용하도록 수정했다.

중복 등록 문제

  • List를 사용하면 동일한 IDamagable 객체가 여러 번 추가될 수 있었다.
  • 해결 방법: HashSet<IDamagable>을 사용해 중복을 방지했다.

결론

이렇게 코드 변형을 통해 CampFire가 불이 아닌 물에서도 사용될 수 있도록 했다.

  • 기존 CampFire에서는 delayTime = 0으로 설정하여 기존 기능을 유지할 수 있다.
  • 물에서는 delayTime을 조정하여 일정 시간 후에 데미지를 적용하도록 설정했다.
  • HashSet을 사용하여 중복 처리를 방지하여 성능을 최적화했다.
  • 범위를 벗어나면 데미지가 적용되지 않도록 enter 변수로 추가 확인하여 버그를 수정했다.

이를 통해 같은 코드 구조를 유지하면서도 상황에 맞는 유연한 데미지 적용이 가능하도록 만들었다.

댓글남기기