EasyCastleUNITY

간단 RPG Test2-2 본문

유니티 기초

간단 RPG Test2-2

EasyCastleT 2023. 8. 9. 15:47

영웅이 몬스터를 바라봄, 피격 애니메이션 및 이펙트 

영웅 컴포넌트

ParticleDestroyer: 이펙트 삭제

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

namespace Test2
{
    public class ParticleDestroyer : MonoBehaviour
    {
        private ParticleSystem ps;
        // Start is called before the first frame update
        void Start()
        {
            this.ps = this.GetComponent<ParticleSystem>();
            //코루틴 실행
            this.StartCoroutine(this.CoWaitForPlayAfterDestroy());
            
        }

        private IEnumerator CoWaitForPlayAfterDestroy()
        {
            //duration 만큼 기다렸다가 (프레임 경과)
            yield return new WaitForSeconds(this.ps.main.duration);
            Destroy(this.gameObject);
        }
        
    }
}

HeroController

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

namespace Test2
{
    public class HeroController : MonoBehaviour
    {
        public enum eState
        {
            Idle,Attack
        }
        [SerializeField]
        private float radius = 1.0f;
        [SerializeField]
        private MonsterController monster;

        private Animator anim;
        private eState state;
        private float impactTime = 0.399f;
        private MonsterController target;
        public float Radius
        {
            get
            {
                return this.radius;
            }
        }
        // Start is called before the first frame update
        void Start()
        {
            this.anim = GetComponent<Animator>();
            
        }

        // Update is called once per frame
        void Update()
        {
            this.transform.LookAt(monster.transform.position);
        }

        public void Attack(MonsterController target)
        {
            this.target = target;
            //공격애니메이션을 1회 실행
            this.PlayAnimation(eState.Attack);
            //Debug.LogError("Stop");
        }

        private void PlayAnimation(eState state)
        {
            //this.state : prev 현재, state: current: 미래
            //중복막기
            if(this.state != state)
            {
                Debug.LogFormat("State: {0} -> {1}", this.state, state);
                this.state = state;
                this.anim.SetInteger("State", (int)state);

                switch (state)
                {
                    case eState.Attack:

                        this.StartCoroutine(this.WaitForCompleteAttackAnimation());
                        break;
                }
            }
            else
            {
                Debug.LogFormat("{0}은 현재와 동일한 상태입니다.", state);
            }
            
        }
        //코루틴 함수
        //Update와 동일하게 동작 가능 
        private IEnumerator WaitForCompleteAttackAnimation()
        {
            yield return null; //1프레임 건너뜀

            Debug.Log("공격 애니메이션이 끝날때까지 기다림");
            AnimatorStateInfo animStateInfo = this.anim.GetCurrentAnimatorStateInfo(0);
            //실제 애니메이션 이름으로 찾아야 한다
            bool isAttackState = animStateInfo.IsName("Attack01");
            Debug.LogFormat("isAttackState: {0}", isAttackState);
            if (isAttackState)
            {
                Debug.LogFormat("Hero's animStateInfo.length: {0}", animStateInfo.length);
            }
            else
            {
                Debug.LogFormat("Attack State가 아닙니다");
            }

            yield return new WaitForSeconds(this.impactTime);
            Debug.Log("<color=red>Impact!!!!</color>");
            //대상에게 피해를 입힘
            this.target.HitDamage();

            yield return new WaitForSeconds(animStateInfo.length-this.impactTime);
            //Idle 애니메이션을 함
            //this.anim.SetInteger("State", 0);
            this.PlayAnimation(eState.Idle);
        }
        private void OnDrawGizmos() 
        {
            Gizmos.color = Color.yellow;
            GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, radius);
        }
    }
}

MonsterController

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

namespace Test2
{
    public class MonsterController : MonoBehaviour
    {
        public enum eMonsterState
        {
            Idle,GetHit
        }

        [SerializeField]
        private float radius = 1.0f;
        private Animator anim;
        private eMonsterState monsterState;
        private float hitTime = 0.01f;

        public Action onHit;
        public float Radius
        {
            get
            {
                return this.radius;
            }
        }
        // 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()
        {
            //피격애니메이션을 1회 실행
            //this.PlayAnimation(eMonsterState.GetHit);
            this.PlayAnimation(eMonsterState.GetHit);
            this.onHit();
        }

        private void PlayAnimation(eMonsterState monsterState)
        {
            //중복막기
            if(this.monsterState != monsterState)
            {
                Debug.LogFormat("Monster: {0} -> {1}", this.monsterState, monsterState);
                this.monsterState = monsterState;
                this.anim.SetInteger("MonsterState", (int)monsterState);

                switch(monsterState)
                {
                    case eMonsterState.GetHit:

                        this.StartCoroutine(this.WaitForCompleteGetHitAnimation());
                        break;
                }
            }
            else
            {
                Debug.LogFormat("{0}은 현재와 동일한 상태입니다.", monsterState);
            }
        }

        //코루틴
        private IEnumerator WaitForCompleteGetHitAnimation()
        {
            yield return null;

            Debug.Log("피격 애니메이션이 끝날때까지 기다림");
            AnimatorStateInfo animStateInfo = this.anim.GetCurrentAnimatorStateInfo(0);
            //실제 애니메이션 이름으로 찾아야 한다
            bool isGetHit = animStateInfo.IsName("GetHit");
            Debug.LogFormat("isGetHit: {0}", isGetHit);
            if(isGetHit )
            {
                Debug.LogFormat("Monster's animStateInfo.length: {0}", animStateInfo.length);
            }
            else
            {
                Debug.LogFormat("GetHit State가 아닙니다");
            }
            yield return new WaitForSeconds(this.hitTime);
            Debug.Log("<color=red>Hit!!!!!</color>");

            yield return new WaitForSeconds(animStateInfo.length-this.hitTime);

            this.PlayAnimation(eMonsterState.Idle);

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

Test2_PlayerAttackSceneMain

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

namespace Test2
{
    public class Test2_PlayerAttackSceneMain : MonoBehaviour
    {
        [SerializeField]
        private Button btnAttack;
        [SerializeField]
        private HeroController heroController;
        [SerializeField]
        private MonsterController monsterController;
        [SerializeField]
        private GameObject hitFxPrefab;
        // Start is called before the first frame update
        void Start()
        {
            //이벤트 등록
            this.monsterController.onHit = () =>{
                Debug.Log("이펙트 생성");
                Vector3 offset = new Vector3(0, 0.5f, 0);
                Vector3 tpos = this.monsterController.transform.position + offset;

                Debug.LogFormat("생성위치: {0}", tpos);
                //프리팹 인스턴스 생성
                GameObject fxGo = Instantiate(this.hitFxPrefab);
                //위치 설정
                fxGo.transform.position = tpos;
                //파티클 실행
                fxGo.GetComponent<ParticleSystem>().Play();

            };
            this.btnAttack.onClick.AddListener(() =>{
                //사거리 체크
                //두점과의 거리, 두 오브젝트의 radius의 합을 알아야 한다.
                //B-A : C (방향벡터) magnitude (벡터의 길이)
                Vector3 b = monsterController.transform.position;
                Vector3 a = heroController.transform.position;
                Vector3 c = b - a; //정규화되지 않은 방향벡터 (방향과 크기)

                float distance = c.magnitude;
                Debug.LogFormat("distance:{0}", distance);
                Debug.DrawLine(a, b); //영웅과 몬스터 사이의 길이를 출력
                //시작위치, 방향 , 출력되는 시간, 색깔, 타입
                //DrawArrow.ForDebug(a, c.normalized , 3f,Color.red,ArrowType.Solid); 
                //Vector3.Distance

                //두 컴포넌트의 radius(반지름) 합
                float sumRadius = this.heroController.Radius + this.monsterController.Radius;
                Debug.LogFormat("sumRadius: {0}", sumRadius);

                Debug.LogFormat("<color=yellow>distance: {0}, sumRadius: {1} </color>",
                    distance, sumRadius);
                Debug.LogFormat("isWithinRange: <color=lime>{0}</color>", 
                    this.isWithinRange(distance, sumRadius));

                if(this.isWithinRange(distance, sumRadius))
                {
                    //HeroController에게 공격을 명령
                    this.heroController.Attack(this.monsterController);
                }
            });
        }
        //사거리 계산
        private bool isWithinRange(float distance, float radius)
        {
            return radius >= distance; //true
        }
        // Update is called once per frame
        void Update()
        {
            
        }
        
        
    }

}

실행