SimpleRPG-GameScene에서 플레이어 이동 및 Attack

기본 셋팅

게임씬을 만들고 캐릭터와 몬스터를 하나씩 만들어주었다.

-애니메이션 추가 및 코드수정(HeroController.cs)

 

-HeroController에 MoveToMonster구현하고, GameMain 수정

-tag에 collider로 접근하므로 몬스터랑 Ground(Plane)에 collider 추가해줘야한다.

최종화면

 

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

public class GameMain : MonoBehaviour
{
   
    private HeroController heroController;
    private MonsterController monsterController;
    private float maxDistance = 100f;
    [SerializeField]
    private GameObject hitFxPrefab;
    private GameObject fxGo;

    // Start is called before the first frame update
    void Start()
    {
        this.heroController = GameObject.FindObjectOfType<HeroController>();
        this.monsterController = GameObject.FindObjectOfType<MonsterController>();

        this.monsterController.onHit = () => {
            Debug.Log("이펙트 생성");
            Vector3 offset = new Vector3(0, 0.5f, 0);
            Vector3 tpos = this.monsterController.transform.position + offset;
            Debug.LogFormat("생성위치:{0}", tpos);
            //프리팹 인스턴스(복사본) 생성
            fxGo = Instantiate(this.hitFxPrefab);
            fxGo.transform.position = tpos;//위치 설정
            fxGo.GetComponent<ParticleSystem>().Play();//파티클 실행

        };
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))//마우스 좌클릭시 
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            Debug.DrawRay(ray.origin, ray.direction*100f, Color.red,3f);//ray 그리기
            RaycastHit hit;// Raycast에서 out 키워드 사용을 위한 변수 선언        
            if (Physics.Raycast(ray, out hit, maxDistance)) //Ray 충돌검사
            {
                if (hit.collider.tag == "Monster")//몬스터 클릭시
                {//태그를 통해 몬스터에 접근
                    Debug.LogFormat("Monster: {0}", hit.collider.tag);
                    //거리를 구한다.
                    Vector3 targetPosition = hit.collider.gameObject.transform.position;
                    Vector3 start = this.heroController.transform.position;
                    float distance = Vector3.Distance(start, targetPosition);
                    this.monsterController = hit.collider.gameObject.GetComponent<MonsterController>();
                    //각 반지름 더한거와 비교
                    float sumRadius = this.heroController.Radius + monsterController.Radius;
                    //Debug.Log(distance);
                    if (this.IsWithinRange(distance,sumRadius))//사거리 안에 들어옴 => attack
                    {
                        
                        Debug.Log("Attack");
                        //HeroController에게 공격을 명령
                        this.heroController.Attack(this.monsterController);
                    }
                    else//이동
                    {
                        this.heroController.MoveToMonster(monsterController);
                    }


                }
                else if (hit.collider.tag == "Ground")
                {
                    Debug.Log(hit.point);
                    this.heroController.Move(hit.point);
                }

            }
        }
    }

    private bool IsWithinRange(float distance, float radius)
    {
        return radius > distance;//true
    }

}
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class HeroController : MonoBehaviour
{
    public enum eState
    {
        Idle, Run, Attack
    }
    public float radius = 1;
    public float Radius
    {
        get
        {
            return this.radius;
        }
    }
    public eState state;
    private MonsterController monsterController;
    public GameObject target;//타겟 게임 오브젝트
    private Vector3 targetPosition;
    private float moveSpeed = 2f;
    private Animator anim;
    private Coroutine routine;
    private float impactTime = 0.399f;
    // Start is called before the first frame update
    void Start()
    {
        this.monsterController = GameObject.FindObjectOfType<MonsterController>();
        this.target = monsterController.gameObject;
       // this.transform.position = targetPosition;
        this.anim = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    public void Move(Vector3 targetPosition)
    {
        //타겟을 지움 
        this.monsterController = null;
        this.targetPosition = targetPosition;
        Debug.Log("Move");
        //이동시작
        if (this.routine != null)
        {
            //이미 코루틴이 실행중이다 -> 중지 
            this.StopCoroutine(this.routine);
        }
        StartCoroutine(this.CoMove());//코루틴 시작
    }
    public void MoveToMonster(MonsterController target)
    {
        this.monsterController = target;        
        this.targetPosition = this.monsterController.transform.position;//이동할 목표 지점을 저장

        Debug.Log("MoveToMonster");
        // Debug.LogFormat("targetPosition: {0},{1}",targetPosition,this.target.gameObject.transform.position);
        //이동시작      
        if (this.routine != null)
        {
            //이미 코루틴이 실행중이다 -> 중지 
            this.StopCoroutine(this.routine);
        }
        this.routine = StartCoroutine(this.CoMove());
       // this.anim.SetInteger("State", 1);//Run Animation
    }
    private IEnumerator CoMove()
    {
        while (true)
        {
            this.transform.LookAt(this.targetPosition);//방향을 바라봄
            this.transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);//정면으로 이동
            this.PlayAnimation(eState.Run);//Run 애니메이션 실행
            float distance = Vector3.Distance(this.transform.position, this.targetPosition);
            //  Debug.Log(distance);
            if (this.monsterController != null)  //타겟이 있을 경우
            {//this.targetPosition은 Vector3라 null과 비교하면안된다. 주의.
                if (distance <= (1f+1f))
                {
                    this.PlayAnimation(HeroController.eState.Idle);
                    break;//break없으면 무한루프돼서 유니티 멈춘다.
                }
            }
            else//타겟이 없으면
            {
                // Debug.Log(distance);
                if (distance <= 0.1f)//** 0이 될수 없다 가까워질때 도착한것으로 판단 하자 
                {//도착
                    this.PlayAnimation(eState.Idle);//Idle 애니메이션 실행
                    break;//isMoveStart 같은 변수를 안쓸 수 있다.
                }
            }
            yield return null;//다음 프레임 시작
        }
        Debug.Log("<color=yellow>도착!</color>");
    }

    public void PlayAnimation(eState state)
    {
        if (this.state != state)//중복막기
        {

            Debug.LogFormat("Hero: {0} => {1}", this.state, state);
            this.state = state;
            this.anim.SetInteger("State", (int)state);//정수 -> enum

            switch (state)
            {
                case eState.Attack:
                    //이미 코루틴이 실행중이라면 중지
                    if (this.routine != null)
                    {
                        this.StopCoroutine(this.routine);
                    }
                    this.routine = this.StartCoroutine(this.WaitForCompleteAttackAnimation());
                    break;

            }
        }
        else
        {
            //Debug.LogFormat("{0}는 현재 상태와 동일한 상태입니다.", state);
        }

    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.green;
        GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, this.radius);
    }

    public void Attack(MonsterController target)
    {
        Debug.Log("Attack!!");
        PlayAnimation(eState.Attack);

    }
    //코루틴 함수
    //IEnumerator 반환타입을 갖는 1개 이상의 yield return을 포함하는 함수
    private IEnumerator WaitForCompleteAttackAnimation()
    {//상태는 바로 변하지않는다. 다음 프레임에 변한다. 코루틴으로 확인해보자
        yield return null;//다음 프레임의 시작.yield return 위치에 주의.
        Debug.Log("공격 애니메이션이 끝날때까지 기다림");
        AnimatorStateInfo animStateInfo = this.anim.GetCurrentAnimatorStateInfo(0);
        bool isAttackState = animStateInfo.IsName("Attack01");//애니매이터의 애니메이션 이름
        Debug.LogFormat("isAttackState:{0}", isAttackState);
        if (isAttackState)
        {
            Debug.LogFormat("animatorStateInfo.Length:{0}", animStateInfo.length);
        }
        else
        {
            Debug.Log("attack state가 아닙니다.");
        }
        //yield return new WaitForSeconds(animStateInfo.length);
        yield return new WaitForSeconds(this.impactTime); //Impact 
        Debug.Log("<color=red>Impact</color>");
        //대상에게 피해를 입힘
        // this.target.state = MonsterController.eState.GetHit;
        this.monsterController.PlayAnimation(MonsterController.eState.GetHit);
        Debug.LogFormat("after target.state: {0}", this.monsterController.state);
        yield return new WaitForSeconds(animStateInfo.length - this.impactTime);    //0.72초만큼 기다림 
        this.monsterController.PlayAnimation(MonsterController.eState.Idle);
        //Idle애니메이션을 함 
        //this.anim.SetInteger("State", 0);
        this.PlayAnimation(eState.Idle);

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

public class MonsterController : MonoBehaviour
{
    public float radius =1f;
    public enum eState
    {
        Idle, Run, GetHit
    }
    public eState state;
    public float Radius
    {
        get
        {
            return this.radius;
        }
    }
    public Action onHit;//using System작성하고 써야함

    private Animator anim;
    // Start is called before the first frame update
    void Start()
    {
        this.anim = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    public void HitDamage()
    {
        //
        //
        //Debug.LogError("피해 애니메이션을 실행합니다.");
        //  this.anim.SetInteger("State", 2);
        //this.state = eState.GetHit;
        this.onHit();//대리자 사용
    }
    private void OnDrawGizmos()
    {
        Gizmos.color = Color.green;
        GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, this.radius);
    }
    public void PlayAnimation(eState state)
    {
        //state = eState.GetHit;
        if (this.state != state)
        {

            Debug.LogFormat("Monster: {0} => {1}", this.state, state);
            this.state = state;
            this.anim.SetInteger("State", (int)state);//정수 -> enum

            switch (state)
            {
                case eState.GetHit:
                    this.HitDamage();

                    break;

            }
        }

    }
}
myoskin