One_KWS

게임 개발 일지 #11 - 여러 종류 몬스터 만들기 본문

게임 개발

게임 개발 일지 #11 - 여러 종류 몬스터 만들기

One-Kim 2023. 3. 21. 22:51

여러 종류의 몬스터를 만들 때 매번 새로운 프리팹을 만들고 스크립트를 붙이고 하는 작업을 반복할 수 없기 때문에 중복 작업을 줄이기 위해 몬스터를 수정했다. (수정하면서 몬스터 종류도 추가했다.)

 

Prefab Variant

Enemy 프리팹에 공통으로 사용할 오브젝트만 남겨두고 삭제했다. Animator에는 기준이 될 AnimatorController를 넣었다.

Create/Prefab Variant로 Demon과 Skeleton을 만들어주었다.

만들어진 Prefab Variant에 사용할 캐릭터와 무기를 넣어주었다.  

Animator Override Controller

몬스터 마다 다른 무기를 가지고 있기 때문에 각각의 무기에 따라 다른 애니메이션을 적용시켜주기 위해 기준이 되는 Enemy Animator Controller를 우클릭하여 Animator Override Controller를 만들었다.

Animator Override Controller 마다 다르게 적용시켜줄 애니메이션을 넣어주었고 몬스터의 Prefab Variant의 Animator에 넣어주었다. 

 

Weapon과 Projectile

몬스터의 무기도 각각 다르게 넣었다. 애니메이션 중간에 멈춰놓고 무기 위치를 자연스럽게 조정했다. 

 

Skeleton의 경우 원거리 공격을 하기 때문에 발사체를 만들어야 했다. Projectile 오브젝트를 생성하고 Rigidbody와 Collider를 붙여주었다. 그리고 Projectile 스크립트를 생성하여 아래 코드처럼 작성했다.

public class Projectile : MonoBehaviour {
    public Action<Collider> OnHit;
    
    [SerializeField] private float speed;
    [SerializeField] private ParticleSystem hitEffect;
    [SerializeField] private GameObject projectileBody;
    
    private Rigidbody rigidbody;
    private Collider collider;

    private void Awake() {
        rigidbody = GetComponent<Rigidbody>();
        collider = GetComponent<Collider>();
    }

    public void Shoot(Vector3 force) {
        rigidbody.AddForce(force * speed);
    }

    public void OnTriggerEnter(Collider other) {
        if (!other.CompareTag("Player")) {
            return;
        }
        
        if (hitEffect != null) {
            hitEffect.Play();
        }
		
        // 맞았을 때의 이펙트가 실행중일 때는 발사체가 안보이게 하고 이펙트가 끝나고 Destroy한다.
        rigidbody.velocity = Vector3.zero;
        projectileBody.gameObject.SetActive(false);
        collider.enabled = false;
        OnHit?.Invoke(other);
        Destroy(gameObject, 0.3f);
    }
}

 

발사체 오브젝트를 Prefab으로 만든 후 Fireball Prefab Variant를 만들었다. 자식 오브젝트로 파이어 볼 모양의 에셋과 맞았을 때 터지는 이펙트도 넣어주었다. 

공격시 무기에서 발사체를 생성해 주기 위해서 Weapon 클래스에 아래 코드를 추가했다.

[SerializeField] private Projectile projectile;

public bool HasProjectile => projectile != null;

public void ShootProjectile(Vector3 direction, Action<Collider> onHit) {
    if (!HasProjectile) {
        return;
    }

    var projectileObject = Instantiate(projectile, transform.position + direction * 2f, Quaternion.identity);
    var targetProjectile = projectileObject.GetComponent<Projectile>();

    targetProjectile.OnHit = onHit;
    targetProjectile.Shoot(direction);
}

 

EnemyCombat 수정

몬스터의 공격 애니메이션 중간에 이벤트를 추가하고 EnemyCombat 클래스에서 해당 이벤트가 발생했을 때 동작시킬 로직을 구현했다.

private void OnHit() {
	// 원거리 공격을 하는 몬스터일 경우 해당 Weapon의 Projectile을 발사한다.
    if (weapon.HasProjectile) {
        weapon.ShootProjectile(transform.forward, OnProjectileHit);
        return;
    }

    TakeDamageToPlayer();
}

//Weapon의 ShootProjectile 함수의 매개변수로 넘겨주는 함수
private void OnProjectileHit(Collider collider) {
    var playerHealth = collider.GetComponent<Health>();
    playerHealth.TakeDamage(damage);
}

 

결과

실행 결과 몬스터들이 각각 다른 애니메이션으로 동작하고 원거리공격 몬스터도 잘 동작했다.

 

이제 새로운 몬스터를 만들때 코드를 건드릴 필요 없이 Prefab Variant로 생성하고 Animator override Controller와 Weapon, Projectile를 넣어주면 된다. 이제 몬스터마다 체력, 공격력 등을 따로 설정해주면 될 것 같다. (이건 Scriptable Object로 만들어야겠다.. 어쩌면 체력이나 공격력같은 것들 말고도 위에서 만들었던 무기나 애니메이션 같은 것들도 다 Scriptable Object로 설정해 줄 수 있게 만들 수 있을 것 같다.)

 

사용 에셋

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)