One_KWS

게임 개발 일지 #14 - 보스 공격 패턴 만들기 본문

게임 개발

게임 개발 일지 #14 - 보스 공격 패턴 만들기

One-Kim 2023. 4. 23. 17:56

보스 공격 패턴 구현

애니메이션 이벤트 추가

공격 애니메이션의 특정 타이밍에 패턴 공격을 실행시켜 주기 위해 각 애니메이션에 이벤트를 추가하였다.

애니메이션 중간에 Hit 이벤트를 추가하였다.

 

IAttackPattern

보스 몬스터의 공격 패턴은 아래와 같은 구조로 구현했다. 각 공격 패턴은 IAttackPattern이라는 Interface를 구현하고 있고 BossCombat 클래스에서 이 Interface를 통해 제어해 준다. (스트래티지 패턴을 이용했다.)

IAttackPattern 인터페이스에는 IsAvailable 프로퍼티와 Perform() 함수가 있다. IsAvailable은 해당 공격 패턴을 사용할 수 있는지를 판단하기 위한 프로퍼티이고 Perform() 함수는 해당 공격 패턴을 실행시키는 함수이다.

public interface IAttackPattern {
    public bool IsAvailable { get; }
    public void Perform();
}

 

BossCombat

각 공격 패턴은 Patterns 오브젝트의 자식으로 들어가 있다. Awake()에서 Patterns 자식의 IAttackPattern을 가져와 List에 담아놓게 구현했고 각 애니메이션의 Hit 이벤트가 실행될 때 해당 애니메이션의 인덱스에 해당하는 공격 패턴이 실행되도록 구현했다.

public class BossCombat : MonoBehaviour, IBehaviour {
    [SerializeField] private Transform patterns;
    private List<IAttackPattern> bossPatterns = new List<IAttackPattern>();

    private void Awake() {
        foreach (Transform pattern in patterns) {
            bossPatterns.Add(pattern.GetComponent<IAttackPattern>());
        }
    }
	
    ...

    private void Hit() {
        if (bossPatterns[currentAttackIndex] == null) {
            return;
        }

        bossPatterns[currentAttackIndex].Perform();
    }
}

 

CollisionTrigger

각 공격 패턴의 Hierachy 구조는 아래와 같다. Pattern 오브젝트 자식으로 공격 이펙트와 충돌 여부를 판단하기 위한 CollisionTrigger 오브젝트가 있다.

보스 공격 패턴의 Hierachy 구조

CollisionTrigger 오브젝트에는 충돌을 감지하여 이벤트를 실행시키는 컴포넌트가 붙어 있다. 보스의 공격에 다른 오브젝트가 충돌했을 경우 이벤트를 통해 해당 공격 패턴 클래스에서 처리한다. 

public class CollisionTrigger : MonoBehaviour {
    public UnityEvent<Collider> onTriggerEnter;
    public UnityEvent<Collider> onTriggerStay;
    public UnityEvent<Collider> onTriggerExit;

    private void OnTriggerEnter(Collider other) {
        onTriggerEnter?.Invoke(other);
    }

    private void OnTriggerStay(Collider other) {
        onTriggerStay?.Invoke(other);
    }

    private void OnTriggerExit(Collider other) {
        onTriggerExit?.Invoke(other);
    }
}

 

CleavePattern

첫번째 공격 패턴은 아래와 같이 구현했다. 보스 몬스터가 도끼를 내려칠 때 땅이 갈라지는 것 같은 이펙트를 넣었고 플레이어가 맞았을 경우 대미지를 입는다.  

위의 패턴 이펙트는 아래 두 이펙트를 써서 만들었다. 보스 몬스터가 도끼를 내려치는 부분부터 이펙트가 시작될 수 있도록 위치를 조정해 주었다.

첫번째 패턴에 사용한 이펙트

CleavePattern 스크립트를 생성하고 아래 코드 작성했다. Awake()에서 CollisionTrigger의 onTriggerEnter 이벤트에 TakeDamage() 함수를 연결해 주었고 충돌한 오브젝트가 플레이어인지 판단하여 대미지를 준다.

Perform 함수에서는 CleaveCoroutine() 코루틴을 실행시키고 CleaveCoroutine() 함수에서는 Cleave 이펙트가 지나갈 때 플레이어와 충돌 여부를 확인할 수 있도록 CollisionTrigger를 이동시킨다. 

public class CleavePattern : MonoBehaviour, IAttackPattern {
    [SerializeField] private ParticleSystem particleSystem;
    [SerializeField] private CollisionTrigger collisionTrigger;
    [SerializeField] private float damage = 20f;
    
    private Rigidbody rigidbody;
    private bool isAvailable = true;
    public bool IsAvailable => isAvailable;

    private void Awake() {
        rigidbody = collisionTrigger.GetComponent<Rigidbody>();
        collisionTrigger.onTriggerEnter.AddListener(TakeDamage);
    }

    public void Perform() {
        isAvailable = false;

        particleSystem.Play();
        StartCoroutine(CleaveCoroutine());
    }

    //CollisionTrigger 오브젝트를 이동시킨다.
    private IEnumerator CleaveCoroutine() {
    	//CollisionTrigger 오브젝트의 초기 위치 설정
        collisionTrigger.transform.localPosition = Vector3.up * 0.5f;

        float time = 0;
        while (time < 1) {
            collisionTrigger.transform.localPosition = Vector3.Lerp(collisionTrigger.transform.localPosition,
                Vector3.forward * 20, Time.deltaTime * 3f);

            time += Time.deltaTime;
            yield return null;
        }

        isAvailable = true;
    }

    private void TakeDamage(Collider target) {
        if (!target.gameObject.CompareTag("Player")) {
            return;
        }

        target.GetComponent<Health>().TakeDamage(damage);
    }
}

보스 패턴 1 구현 결과

 

SlashSpinPattern

두 번째 패턴은 보스가 도끼를 휘두를 때 돌아가는 오브젝트가 발사되고 플레이어가 맞았을 경우 대미지를 입게 된다. 

첫 번째 패턴과 마찬가지로 CollisionTrigger 오브젝트를 만들고 자식 오브젝트로 아래와 같은 돌아가는 이펙트를 넣어주었다. 원래 Looping 옵션이 true로 되어 있던 이펙트인데 3초 동안만 재생되도록 수정했다. 같은 방식으로 총 3개를 만들어주고 방향을 하나는 정방향 나머지 두 개는 각각 45도, -45도 방향으로 돌려서 각각 다른 방향으로 움직일 수 있게 했다. 

두 번째 공격 패턴에 사용한 이펙트와 공격 패턴 Hierachy 구조

 

SlashSpinPattern 스크립트를 생성하여 BossPattern 오브젝트에 붙여주고 아래와 같이 코드를 작성했다. 3개의 오브젝트를 움직여야 하기 때문에 List를 사용했다. Perform() 함수에서 for문을 이용하여 3개의 CollisionTrigger에 대한 코루틴을 실행시킨다. 코루틴 안에서 각각의 CollisionTrigger는 forward 방향으로 1초간 이동하고 2초간 제자리에 머무른 다음 비활성화 된다.

public class SlashSpinPattern : MonoBehaviour, IAttackPattern {
    [SerializeField] private ParticleSystem particleSystem;
    [SerializeField] private List<CollisionTrigger> collisionTriggers;
    [SerializeField] private List<ParticleSystem> spins;
    
    private bool isOngoing = false;
    private bool isAvailable = true;
    public bool IsAvailable => isAvailable;
    
    private void Awake() {
        foreach (var collisionTrigger in collisionTriggers) {
            collisionTrigger.onTriggerEnter.AddListener(TakeDamage);
        }
    }

    public void Perform() {
        isAvailable = false;
        particleSystem.Play();

        for(int i=0;i<collisionTriggers.Count;i++){
            StartCoroutine(SlashSpinCoroutine(collisionTriggers[i].transform, spins[i]));
        }
    }

    private IEnumerator SlashSpinCoroutine(Transform triggerTransform, ParticleSystem spin) {
        spin.Play();
        triggerTransform.gameObject.SetActive(true);
        triggerTransform.localPosition = Vector3.forward * 3f;

        float time = 0;
        while (time < 1) {
            triggerTransform.localPosition = Vector3.Lerp(triggerTransform.localPosition,
                triggerTransform.forward.normalized * 15, Time.deltaTime * 1.5f);

            time += Time.deltaTime;
            yield return null;
        }

        yield return new WaitForSeconds(2f);
        isAvailable = true;
        triggerTransform.gameObject.SetActive(false);
    }

    private void TakeDamage(Collider target) {
        if (!target.gameObject.CompareTag("Player")) {
            return;
        }

        target.GetComponent<Health>().TakeDamage(damage);
    }
}

보스 패턴 2 구현 결과

 

BlastPattern

세 번째 패턴은 보스의 앞 방향으로 폭발이 3번 나면서 플레이어에게 피해를 주는 패턴이다. 

 

사용한 이펙트는 아래 두가지 이펙트를 합쳐서 사용했다. 마법진(?) 이펙트가 먼저 플레이 되고 0.8초 뒤에 터지는 이벤트가 플레이 되도록 구현했다.

 

3번 폭발해야 하기 문에 같은 이펙트와 CollisionTrigger도 3개 만들었다.

보스 세 번째 패턴의 Hierachy 구조

BlastPattern 스크립트를 생성하여 코드를 작성했다. 세 번째 패턴도 마찬가지로 CollisionTrigger와 이펙트가 3개씩 있기 때문에 List를 이용했다. BlastCoroutine()에서 for문을 이용하여 시간차로 이펙트가 플레이되도록 구현했다. 폭발이 일어나는 시간 동안에만 충돌 감지를 할 수 있도록 마법진이 보이는 0.8초 동안에는 비활성화 되어 있다가 0.2초 동안만 CollisionTrigger를 활성화 했다.

public class BlastPattern : MonoBehaviour, IAttackPattern {
    [SerializeField] private List<ParticleSystem> blasts;
    [SerializeField] private List<CollisionTrigger> collisionTriggers;
    
    private bool isOngoing = false;
    private bool isAvailable = true;
    public bool IsAvailable => isAvailable;
    
    private void Awake() {
        foreach (var collisionTrigger in collisionTriggers) {
            collisionTrigger.onTriggerEnter.AddListener(TakeDamage);    
        }
    }

    public void Perform() {
        isAvailable = false;
        StartCoroutine(BlastCoroutine());
    }
    
    private IEnumerator BlastCoroutine() {
        for(int i =0;i<collisionTriggers.Count;i++) {
            blasts[i].Play();
    
            yield return new WaitForSeconds(0.8f);
            collisionTriggers[i].gameObject.SetActive(true);
            yield return new WaitForSeconds(0.2f);
            collisionTriggers[i].gameObject.SetActive(false);
        }
        
        isAvailable = true;
    }
    
    private void TakeDamage(Collider target) {
        if (!target.gameObject.CompareTag("Player")) {
            return;
        }

        target.GetComponent<Health>().TakeDamage(damage);
    }
}

보스 패턴 3 구현 결과

 


 

 

보스 몬스터의 공격 패턴을 좀 더 다양하게 하고 싶지만.. 아직 갈 길이 멀기 때문에 이정도로 하고 이후에 더 다듬어 봐야겠다. 다음 작업으로는 CollisionTrigger의 충돌 이벤트와 연결된 TakeDamage() 함수 부분을 다듬으면 될 것 같다. 

 

 

사용 에셋

Character

POLYGON Modular Fantasy Hero Characters (Synty Studios)

POLYGON Fantasy Rivals (Synty Studios)

 

Animation

Oriental Sword AnimSet (wemakethegame)

DOTween (Demigiant)

 

VFX

Magic Arsenal (Magic Arsenal)

 

UI

GUI PRO Kit - Fantasy RPG (Layer Lab)

 

ETC

UniTask (neuecc - Yoshifumi Kawai)