[ProjectT]/개발일지

[21.12.02] 몬스터 랜덤한 방향으로 이동 & 공격

치명적흑형 2021. 12. 2. 16:26

몬스터 AI구현

근접 몬스터와 원거리 몬스터 두종류

 

근접 몬스터

- 근접몬스터는 랜덤한 방향으로 이동하다

- 플레이어가 시야에 들어오면 플레이어를 쫒아온다 (플레이어가 사망할 때 까지)

- 플레이어가 공격범위에 들어오면 공격한다

 

원거리 몬스터

- 근접몬스터는 랜덤한 방향으로 이동하다

- 플레이어가 시야에 들어오면 플레이어를 공격 사정거리 까지만 쫒아온다 (플레이어가 사망할 때 까지)

- 플레이어가 공격범위에 들어오면 공격한다

- 공격이 쿨타임 중이고 이동 딜레이가 끝났다면 랜덤한 방향으로 이동한다(회피)

- 플레이어가 사정거리에서 멀어지면 다시 플레이어를 쫒는다

 

 

추가할 부분

- 몬스터 애니메이션

- 공격시 플레이어에게 데미지 전달

- 피격시 hp감소

- 프로토타입이 끝나면 길찾기 알고리즘도 추가

 

오브젝트 풀링 개선방향

- 원거리 몬스터와 플레이어의 탄환을 보관할 오브젝트 배치

- 코드도 


코드

 

근거리 몬스터

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestMeleeMonster : MonoBehaviour
{
    //Editor에서 사용하는 변수
    public float sight;
    public float attackRange;

    public float damage;
    public float speed;

    public float moveDelta;
    public float moveSpan;

    public float attackDelta;
    public float attackDelay;

    public GameObject[] targets;
    public GameObject target;

    private void Init()
    {
        this.sight = 5;
        this.attackRange = 0.5f;

        this.damage = 1;
        this.speed = 3;

        this.moveSpan = 1;
        this.attackDelay = 2;
    }
    void Start()
    {
        this.Init();
    }

    void Update()
    {
        //공격 쿨타임
        this.attackDelta += Time.deltaTime;

        //타겟 서칭
        if (this.targets.Length == 0)
        {
            this.SearchTarget();
        }

        if (this.targets.Length > 0 && this.target == null)
        {
            for (int i = 0; i < this.targets.Length; i++)
            {
                float distance = Vector3.Distance(this.targets[i].transform.position, this.transform.position);
                if (distance < this.sight)
                {
                    this.target = this.targets[i];
                }
            }

            //타겟을 찾았다면 ! 애니메이션(Idel)
            //if (this.target != null)
            //{
            //    //서치애니메이션 실행
            //}
        }

        //이동
        if(!this.moveRunning)   //moveRunning이 false라면
        {
            this.moveDelta += Time.deltaTime;
        }
        
        if (this.target == null)
        {
            if (this.moveDelta > this.moveSpan)
            {
                this.moveDelta = 0;

                if(this.moveRoutine != null)
                {
                    StopCoroutine(this.moveRoutine);
                }
                this.moveRoutine = StartCoroutine(this.Move());
            }
        }
        else
        {
            Vector2 dir = (this.target.transform.position - this.transform.position).normalized;
            this.transform.Translate(dir * this.speed * Time.deltaTime);
            
            float distance = Vector3.Distance(this.target.transform.position, this.transform.position);
            if (distance < this.attackRange)
            {
                if(this.attackDelta > this.attackDelay)
                {
                    //공격
                    this.attackDelta = 0;
                    Debug.Log("Attack");
                }
            }
        }
    }

    private void SearchTarget()
    {
        this.targets = GameObject.FindGameObjectsWithTag("Player");
    }

    private Coroutine moveRoutine;
    public bool moveRunning;

    private IEnumerator Move()
    {
        this.moveRunning = true;
        //Vector2 dir = Vector2.zero - new Vector2(Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f));
        Vector2 dir = new Vector2(Random.Range(-1, 1), Random.Range(-1, 1)).normalized;
        float delta = 0;
        while(2 > delta)
        {
            this.transform.Translate(dir * 0.5f * Time.deltaTime);
            delta += Time.deltaTime;
            yield return null;
        }
        this.moveRunning = false;
    }
}

 

원거리 몬스터

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class TestRangedMonster : MonoBehaviour
{
    //Editor에서 사용하는 변수
    public float sight;
    public float attackRange;

    public float damage;
    public float speed;

    public float moveDelta;
    public float moveSpan;

    public float attackDelta;
    public float attackDelay;

    public GameObject[] targets;
    public GameObject target;


    public UnityAction<Vector3> attackAction;

    private void Init()
    {
        this.sight = 5;
        this.attackRange = 3.5f;

        this.damage = 1;
        this.speed = 2;

        this.moveSpan = 1;
        this.attackDelay = 2;
    }
    void Start()
    {
        this.Init();
    }

    void Update()
    {
        //공격 쿨타임
        this.attackDelta += Time.deltaTime;

        //타겟 서칭
        if (this.targets.Length == 0)
        {
            this.SearchTarget();
        }

        if (this.targets.Length > 0 && this.target == null)
        {
            for (int i = 0; i < this.targets.Length; i++)
            {
                float distance = Vector3.Distance(this.targets[i].transform.position, this.transform.position);
                if (distance < this.sight)
                {
                    this.target = this.targets[i];
                }
            }

            //타겟을 찾았다면 ! 애니메이션(Idel)
            //if (this.target != null)
            //{
            //    //서치애니메이션 실행
            //}
        }

        //이동
        if (!this.moveRunning)   //moveRunning이 false라면
        {
            this.moveDelta += Time.deltaTime;
        }

        if (this.target == null)
        {
            if (this.moveDelta > this.moveSpan)
            {
                this.moveDelta = 0;

                if (this.moveRoutine != null)
                {
                    StopCoroutine(this.moveRoutine);
                }
                this.moveRoutine = StartCoroutine(this.Move());
            }
        }
        else
        {
            float distance = Vector3.Distance(this.target.transform.position, this.transform.position);
            if (distance < this.attackRange)
            {
                if (this.attackDelta > this.attackDelay)
                {
                    //공격
                    this.attackDelta = 0;
                    this.attackAction(this.target.transform.position);
                    Debug.Log("Attack");
                }
                else
                {
                    //쿨타임 중이면 이동
                    if (this.moveDelta > this.moveSpan)
                    {
                        this.moveDelta = 0;

                        if (this.moveRoutine != null)
                        {
                            StopCoroutine(this.moveRoutine);
                        }
                        this.moveRoutine = StartCoroutine(this.Move());
                    }
                }
            }
            else
            {
                if (this.attackDelta > this.attackDelay)
                {
                    Vector2 dir = (this.target.transform.position - this.transform.position).normalized;
                    this.transform.Translate(dir * this.speed * Time.deltaTime);
                }
            }
        }
    }

    private void SearchTarget()
    {
        this.targets = GameObject.FindGameObjectsWithTag("Player");
    }

    private Coroutine moveRoutine;
    public bool moveRunning;

    private IEnumerator Move()
    {
        this.moveRunning = true;
        //Vector2 dir = Vector2.zero - new Vector2(Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f));
        Vector2 dir = new Vector2(Random.Range(-1, 1), Random.Range(-1, 1)).normalized;
        float delta = 0;
        while (2 > delta)
        {
            this.transform.Translate(dir * 1.0f * Time.deltaTime);
            delta += Time.deltaTime;
            yield return null;
        }
        this.moveRunning = false;
    }
}

 

적 불릿 제너레이터

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestEnemyBulletGenerator : MonoBehaviour
{
    [SerializeField]
    private GameObject bulletPrefab;
    [SerializeField]
    private TestRangedMonster testRangedMonster;

    //오브젝트 풀링
    //public GameObject bullet;
    public int bulletPoolCount = 5;
    public Queue<GameObject> bulletPool = new Queue<GameObject>();

    void Start()
    {
        this.testRangedMonster = this.GetComponent<TestRangedMonster>();

        this.testRangedMonster.attackAction = (targetPos) => {
            this.Attack(targetPos);
        };

        for (int i = 0; i < bulletPoolCount; i++)
        {
            CreateBulletPool();
        }
    }

    private void Attack(Vector3 targetPos)
    {
        GameObject go = this.DequeueBullet();
        go.transform.position = this.transform.position;
        float bulletAngle = GetAngle(this.transform.position, targetPos);
        go.transform.eulerAngles = new Vector3(0, 0, bulletAngle - 90); //총알 방향이 Vector2.up이라서 -90
    }
    private void CreateBulletPool()
    {
        GameObject temp = Instantiate(bulletPrefab);
        temp.GetComponent<TestEnemyBullet>().Init(this);
        temp.SetActive(false);
        bulletPool.Enqueue(temp);
    }
    public GameObject DequeueBullet()
    {
        if (this.bulletPool.Count <= 0)
        {
            CreateBulletPool();
        }

        GameObject dequeueObject = this.bulletPool.Dequeue();
        //dequeueObject.GetComponent<TestEnemyBullet>().CleanUp();
        dequeueObject.SetActive(true);
        return dequeueObject;
    }
    public void EnqueueBullet(GameObject enqueueObject)
    {
        enqueueObject.SetActive(false);
        this.bulletPool.Enqueue(enqueueObject);
    }

    public static float GetAngle(Vector2 vStart, Vector2 vEnd)
    {
        Vector2 v = vEnd - vStart;

        return Mathf.Atan2(v.y, v.x) * Mathf.Rad2Deg;
    }
}

 

적 불릿

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestEnemyBullet : MonoBehaviour
{
    [SerializeField]
    private float bulletSpeed;

    private TestEnemyBulletGenerator testEnemyBulletGenerator;

    public void Init(TestEnemyBulletGenerator testEnemyBulletGenerator)
    {
        this.testEnemyBulletGenerator = testEnemyBulletGenerator;
    }

    void Update()
    {
        this.transform.Translate(Vector2.up * this.bulletSpeed * Time.deltaTime);
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == "Wall")
        {
            //Destroy(this.gameObject);
            this.testEnemyBulletGenerator.EnqueueBullet(this.gameObject);
        }
        if (collision.tag == "Player")
        {
            //Destroy(this.gameObject);
            this.testEnemyBulletGenerator.EnqueueBullet(this.gameObject);
        }
    }
    //public void CleanUp()
    //{
    //}
}