본문 바로가기
KDT/유니티 기초

23/08/08 캐릭터 이동 및 몬스터 공격하기

by 잰쟁 2023. 8. 8.
728x90

Main

using System.Collections;
using System.Collections.Generic;
using test;
using UnityEngine;
using UnityEngine.UI;

public class Test_PlayerControlSceneMain : MonoBehaviour
{
    [SerializeField]
    private HeroController heroController;
    
    void Start()
    {
        this.heroController.onMoveComplete = (target) =>
        {
            Debug.LogFormat("<color=cyan>이동을 완료했습니다.: {0}</color>",target);
            //타겟이 있다면 공격 애니메이션 실행
            if(target != null)
            {
                this.heroController.Attack(target);
            }
        };
    }

    // Update is called once per frame
    void Update()
    {
        //화면을 클릭하면 위치로 Hero가 이동
        if (Input.GetMouseButtonDown(0))
        {
            Debug.Log("Down");
            //Ray를 만든다
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            float maxDistance = 100f;
            //화면에 출력
            Debug.DrawRay(ray.origin, ray.direction*maxDistance, Color.red, 3f);
            //Ray 충돌검사
            RaycastHit hit;
            if(Physics.Raycast(ray, out hit, maxDistance))
            {
                //클릭한 오브젝트가 몬스터라면
                if(hit.collider.tag == "Monster")
                {
                    //거리를 구한다
                    float distance = Vector3.Distance(this.heroController.transform.position, hit.collider.transform.position);

                    MonsterController monsterController = hit.collider.gameObject.GetComponent<MonsterController>();

                    //각 반지름 더한것과 비교
                    float sumRadius = this.heroController.radius + monsterController.radius;

                    Debug.LogFormat("{0}, {1}", distance, sumRadius);

                    //사거리 안에 들어옴
                    if(distance <= sumRadius)
                    {
                        //공격
                        this.heroController.Attack(monsterController);
                    }
                    else
                    {
                        //이동
                        this.heroController.Move(monsterController);
                        //this.heroController.Move(hit.point);  둘이 같은 말
                    }
                }
                //클릭한 오브젝트가 몬스터가 아니면(땅이면)
                else if(hit.collider.tag == "Ground")
                {
                    this.heroController.Move(hit.point);
                }
            }
            
        }
       
    }
}

HeroController

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

namespace test {

    public class HeroController : MonoBehaviour
    {
        private Vector3 targetPosition;
        private Coroutine moveRoutine;
        public Animator anim;
        public System.Action<MonsterController> onMoveComplete;
        public float radius = 1f;
        private MonsterController target;
       
        
        void Start()
        {
            this.anim = this.GetComponent<Animator>();           
        }

        //Method Overload : 똑같은 이름의 함수일지라도 받아오는 매개변수에 따라 다르게 동작하는 함수
        public void Move(MonsterController target)
        {
            this.target = target;
            this.targetPosition = this.target.gameObject.transform.position;
            this.anim.SetInteger("State", 1);
            if(this.moveRoutine != null)
            {
                //이미 코루틴이 실행중이다 -> 중지
                this.StopCoroutine(this.moveRoutine);
            }
            this.moveRoutine = this.StartCoroutine(this.CoMove());
        }

        public void Move(Vector3 targetPosition)
        {
            //타겟을 지움
            this.target = null;

            //이동할 목표지점을 저장
            this.targetPosition = targetPosition;
            Debug.Log("Move");

            //애니메이션 적용하여 달려가기
            this.anim.SetInteger("State", 1);

            if (this.moveRoutine != null)
            {
                //이미 코루틴이 실행중이다 -> 이미 실행중인 코루틴 중지
                this.StopCoroutine(this.moveRoutine);
            }
            this.moveRoutine = this.StartCoroutine(this.CoMove());
        }

        private IEnumerator CoMove()
        {
            while (true)
            {               
                //방향을 바라봄
                this.transform.LookAt(this.targetPosition);
                //바라봤으니까 정면으로 이동
                this.transform.Translate(Vector3.forward * 2f * Time.deltaTime);
                   
                //목표지점과 나와의 거리를 잼
                float distance = Vector3.Distance(this.transform.position, this.targetPosition);
                Debug.Log(distance);
                //0이 될 수 없다. 가까워질때 도착한 것으로 하자

                //타겟이 있을경우
                if(this.target != null)
                {
                    if(distance <= (1f + 1f))  //hero와 monster의 반지름의 합
                    {
                        break;
                    }
                }
                else
                {
                    //타겟이 없을 경우
                    if (distance <= 0.1f)
                    {
                        //도착                    
                        break;
                    }
                }
                            
                yield return null; //다음 프레임 시작
            }
            Debug.Log("<color = yellow>도착!</color>");
            this.anim.SetInteger("State", 0);

            //대리자를 호출
            this.onMoveComplete(this.target);
        }

        //기즈모 생성 메서드
        private void OnDrawGizmos()
        {
            GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, 1, 40);
        }

        //공격 메서드
        public void Attack(MonsterController target)
        {
            this.anim.SetInteger("State", 2);
            //코루틴 함수 호출
            this.StartCoroutine(this.CoAttack());
        }

        //hero가 무기로 몬스터를 칠때까지(0.1초 이후) 이펙트 효과 생성
        private IEnumerator CoAttack()
        {
            //yield return null;  : 다음 프레임 시작
            yield return new WaitForSeconds(0.1f);  //0.1초 이후
            //float elapsedTime = 0; //흐른 시간 
            //while (true)
            //{
            //  elapsedTime += Time.deltaTime;
            //  if(elapsedTime > 0.1f)
            //  {
            //      break;
            //  }
            //  yield return null;
            //} :여기까지가 위에 한 줄이랑 결과 같음

            Debug.Log("<color=red>Impact!!!!!</color>");

            //0.83 - 0.1
            yield return new WaitForSeconds(0.73f);

            Debug.Log("Attack 애니메이션 종료");

            this.anim.SetInteger("State", 0);  //공격 후 기본동작으로 전환
        }
    }
}

MonsterController

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

public class MonsterController : MonoBehaviour
{
    public float radius = 1f;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    //기즈모 생성 메서드
    private void OnDrawGizmos()
    {
        GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, 1, 40);
    }
}

GizmosExtensions

using UnityEngine;

public class GizmosExtensions
{
    private GizmosExtensions() { }

    /// <summary>
    /// Draws a wire arc.
    /// </summary>
    /// <param name="position"></param>
    /// <param name="dir">The direction from which the anglesRange is taken into account</param>
    /// <param name="anglesRange">The angle range, in degrees.</param>
    /// <param name="radius"></param>
    /// <param name="maxSteps">How many steps to use to draw the arc.</param>
    public static void DrawWireArc(Vector3 position, Vector3 dir, float anglesRange, float radius, float maxSteps = 20)
    {
        var srcAngles = GetAnglesFromDir(position, dir);
        var initialPos = position;
        var posA = initialPos;
        var stepAngles = anglesRange / maxSteps;
        var angle = srcAngles - anglesRange / 2;
        for (var i = 0; i <= maxSteps; i++)
        {
            var rad = Mathf.Deg2Rad * angle;
            var posB = initialPos;
            posB += new Vector3(radius * Mathf.Cos(rad), 0, radius * Mathf.Sin(rad));

            Gizmos.DrawLine(posA, posB);

            angle += stepAngles;
            posA = posB;
        }
        Gizmos.DrawLine(posA, initialPos);
    }

    static float GetAnglesFromDir(Vector3 position, Vector3 dir)
    {
        var forwardLimitPos = position + dir;
        var srcAngles = Mathf.Rad2Deg * Mathf.Atan2(forwardLimitPos.z - position.z, forwardLimitPos.x - position.x);

        return srcAngles;
    }
}