세피리아 참고 프로젝트 - 5-3) 힐러 몬스터

승민·어제

세피리아 모작

목록 보기
7/7

세피리아(Sephiria) 게임을 참고하여 진행한 작업내역을 기록하기 위한 글 입니다.

세피리아 모작(github)

구현된 몬스터

힐러 몬스터는 한종류만 구현해주었다.

주요 동작

힐러 몬스터는 힐 대상에게 가까이 다가가서 힐 이펙트를 뿌려주고, 그 이펙트에 닿은 대상은 힐을 받는 이펙트와 함께 실제 힐 처리가 진행된다.

이펙트 범위만큼 힐이 진행되기 때문에 힐은 한번에 여러 몬스터에게 적용될 수 있지만, 힐러 몬스터 끼리는 힐을 주고받지 못한다.

이 몬스터는 공격 기능이 없기 때문에 힐을 주고나서 쿨타임 동안은 플레이어에게서 등을 돌려 도망치고, 쿨타임이 되면 다시 힐을 받을 대상을 찾아 이동한다.

힐 대상은 대상까지의 거리와, 대상이 잃은 체력 비율을 기반으로 가장 높은 우선순위를 가진 몬스터를 대상으로 정한다.
즉, 체력이 낮고 가까울수록 더 높은 점수를 받아 힐러 몬스터가 그쪽으로 이동하여 힐을 줄 확률이 높아진다.

핵심 코드

힐러 몬스터는 타겟을 기반으로 동작하기 때문에 가장 먼저 타겟을 찾고 지정하는 코드를 보려한다.

대상 확보

private Monster FindHealTarget()
{
    Collider2D[] hits = Physics2D.OverlapCircleAll(rb.position, healSearchRange, monsterLayer);

    foreach (Collider2D hit in hits)
    {
        Monster monster = hit.GetComponentInParent<Monster>();

        //힐 불가능한 대상 제외(생략)
        //최대체력이 아닌 몬스터가 존재하면, 최대체력인 몬스터는 타겟 후보에서 제외(생략)

        //대상까지 거리 확보 후, 가까울 수록 점수를 높게줌
        float distance = Vector2.Distance(rb.position, monster.transform.position);
        float distanceScore = (healSearchRange - distance) * distanceScoreWeight;

        //체력이 많이 닳아있을 수록 높은 점수 확보
        float healthScore = (1f - monster.HealthRatio) * 100f;

        //점수 합산
        float score = healthScore + distanceScore;

        //최고 점수를 넘기지 못했으면 패스, 최고점수를 넘었으면 점수 및 타겟 재지정
        if (score <= bestScore) continue;

        bestScore = score;
        bestTarget = monster;
    }

    return bestTarget;
}

힐러 몬스터는 거리와 현재 체력 비율을 기반으로 가장 높은 점수를 얻은 몬스터를 타겟으로 지정하고 동작한다.

거리가 1 정도 떨어지고 체력이 10% 정도 닳아있는 A 몬스터와, 10 정도 떨어지고 체력이 70% 닳아있는 B 몬스터가 있다고 가정해보자.

healSearchRange는 현재 넓게 15 정도를 부여해준 상태인데, 만약 현재 체크중인 몬스터와의 거리가 1 이라고 한다면 14 * distanceScoreWeight 정도의 거리점수를 받을 수 있다.

distanceScoreWeight은 현재 2로 설정되어 있기 때문에, A 몬스터의 거리 점수는 약 28점이라고 할 수 있다.
B 몬스터의 경우 10점 정도의 거리 점수를 얻을 수 있다.

다음은 체력 비율인데, 이 비율값은 간단하게 "현재 체력 / 최대 체력"의 값을 전달한다.
즉 0 ~ 1의 값을 전달하기 때문에, 1에서 이 비율값을 빼주고 거기에 100을 곱하면 체력 점수를 확보할 수 있다.

A 몬스터는 체력 비율이 0.9 정도 되기 때문에 약 10점 정도를 받게된다.
B 몬스터는 체력 비율이 0.3 정도 되기 때문에 약 70점 정도를 받게된다.

두 몬스터의 점수를 합산해보면 각각 38점과 80점 정도로 이 상황에서 힐러 몬스터는 B 몬스터를 대상으로 지정하고 행동하게 된다.

 
힐은 최대체력인 몬스터에게도 시도하기 때문에 주변에 최대체력인 몬스터들 밖에 없다면 그중에서 거리점수라도 가장 높은 제일 가까운 몬스터를 대상으로 행동하게 된다.

다만 이렇게 되면 체력이 아주 조금 닳아있지만 거리가 먼 경우 회복할 체력은 있는데 거리 문제로 힐을 받지 못하게 될 수 있다.

이 몬스터의 본질은 동료 몬스터의 체력 회복에 있기 때문에, 코드 설명에서는 생략됐지만 탐색 범위 내에 최대체력이 아닌 몬스터가 확인되면, 최대체력인 몬스터는 전부 제외하고 연산을 진행하도록 되어있다.

따라서 최대 체력인 몬스터들이 주변에 있고, 매우 멀리 체력이 아주 조금 닳아있는 몬스터가 존재하는 경우엔 아주 조금이라도 회복시켜주기 위해 최대 체력 몬스터들을 전부 무시하고 탐색 과정을 진행한다.

힐 처리 과정

대상 탐색 이후, 해당 대상에게 충분히 이동한 다음 실제 힐 액션 과정이 진행된다.

//힐 준비 시작
public override void StartAttackPrepare()
{
    attackPrepareTimer = 0f;

    //현재 힐 대상 위치 및 방향 고정
    healPosition = healTarget.transform.position;
    healDirection = (healPosition - rb.position).normalized;

    //힐 이펙트 위치 및 방향 고정
    if (healEffect != null)
    {
        healEffect.transform.position = healPosition;
        healEffect.transform.up = healDirection;
    }
}

//힐 준비 진행(타이머 처리)
public override bool ExecuteAttackPrepare()
{
    attackPrepareTimer += Time.fixedDeltaTime;

    //약 0.5초 후 true 반환
    return attackPrepareTimer >= attackPrepareDuration;
}

메소드 명을 확인해보면, StartAttackPrepare, ExecuteAttackPrepare 즉 Attack 관련 메소드인 것을 확인할 수 있다.

이번 힐 몬스터는 별 다른 공격이 없고 힐 액션만 존재하기 때문에 기존의 몬스터 공격 동작 흐름을 이용해서 힐 액션의 처리를 진행해주었다.

가장 첫 단계인 준비 단계의 시작은 타이머 초기화와 함께 힐 처리 위치 및 방향을 미리 고정해둔다.

약 0.5초 후 다음 단계로 넘어가게 되면 이렇게 고정해둔 위치에 힐 액션을 진행하는데, 0.5초의 시간이 흘렀기 때문에 그 사이에 대상 몬스터가 움직였을 가능성이 존재하며, 그러한 경우 대상 몬스터가 힐을 받지 못하는 경우도 발생한다.

즉, 이렇게 타겟의 위치를 이용해서 힐 위치를 미리 저장해두는 방식은 몬스터에게 확정적으로 힐을 주지 못하도록 하기 위한 방식이라고 할 수 있다.

 

//힐 시작
public override void StartAttack()
{
    //힐 쿨타임 적용
    base.StartAttack();

    //힐 이펙트 출력
    if (healEffect != null)
        healEffect.SetActive(true);

    //고정해둔 위치에 광역 힐
    ApplyAreaHeal();
}

공격 상태로 전환되면서 실행되는 StartAttack 메소드는 간단한데, 단순하게 몬스터에게 힐(공격) 쿨타임을 적용하고, 이펙트 출력 및 광역 힐 메소드를 실행하도록 되어있다.

//광역 힐 적용
private void ApplyAreaHeal()
{
    //힐 방향에 맞는 각도 확보
    float angle = Vector2.SignedAngle(Vector2.up, healDirection);

    //고정된 힐 위치를 중심으로 범위 판정
    Collider2D[] hits = Physics2D.OverlapBoxAll(
        healPosition,
        healAreaSize,
        angle,
        monsterLayer);

    //힐 진행 대상 기록용 HashSet
    HashSet<Monster> healedTargets = new();

    foreach (Collider2D hit in hits)
    {
        Monster monster = hit.GetComponentInParent<Monster>();

        //힐이 불가능하거나 이미 힐을 진행한 대상 제외
        if (monster == null || monster is HealerMole || monster.IsDying || !healedTargets.Add(monster))
            continue;

        //힐 진행
        monster.Heal(healAmount);

        //힐 받은 위치에 이펙트 생성
        if (healReceiveEffectPrefab != null)
            Instantiate(healReceiveEffectPrefab, monster.ShadowPosition, Quaternion.identity);
    }
}

광역 힐 메소드는 OverlapBoxAll을 활용하여, 미리 지정해둔 위치, 범위(직접지정), 각도를 기준으로 특정 범위 내 몬스터의 콜라이더들을 모두 확보한다.

이후 이 대상들이 문제가 없는지 확인하며, 실제 힐 처리 및 이펙트 생성을 진행해주면 된다.

//체력 회복
public void Heal(float amount)
{
    if (isDying || amount <= 0f) return;

    //회복은 최대체력까지만 진행
    currentHealth = Mathf.Min(currentHealth + amount, maxHealth);
}

힐 자체는 단순하게 전달받은 수치를 최대체력 까지만 회복해주도록 구현되어있다.

0개의 댓글