Unity-공격 연습

새 씬을 만들어 공격 연습을 해보려한다.

플레이어를 중앙에 놓고, 새 메인 스크립트를 생성해 Test_PlayerControlAttackSceneMain 으로 명명하였다.

또한 namespace Test2로 묶어주었다.

빈 오브젝트 만들어서 스크립트를 넣어줌

 

스크립트 폴더를 정리함

 

몬스터 컨트롤러와 히어로 컨트롤러 수정. radius 는 1로 설정하였다.

기본 셋팅 완료

-어택 버튼을 만들어 공격 테스트를 해보려한다: 버튼 컴포넌트의 On Click() 속성을 사용.

+) OnClick은 속성(필드)이므로 변수(=속성:문법상 속성 = 프로퍼티)이다.

 

-HeroController에 Attack메서드를 작성하고 버튼에 추가해주었다. => 메서드를 작성하고 넣어주기

 : 게임오브젝트를 넣어주고, 메서드를 설정하는 것이다.

=> 변수에다 메서드를 넣었다? : 대리자이다. 즉 OnClick은 대리자인것이다.

https://docs.unity3d.com/2018.3/Documentation/ScriptReference/UI.Button.html

 

Unity - Scripting API: Button

You've told us this page needs code samples. If you'd like to help us further, you could provide a code sample, or tell us about what kind of code sample you'd like to see: You've told us there are code samples on this page which don't work. If you know ho

docs.unity3d.com

-버튼은 코드 베이스로 만들 수도 있다.

-AddListener는 UnityAction을 넣을 수 있다

-콜백은 무조건 대리자! => UnityAction은 콜백이니까 대리자~

 

Attack 구현을 위해 코드 수정

-heroController 수정
Main수정

-Impact하는 순간에 몬스터는 피해를 받는 것을 구현하려한다.

: 구현해야할 것 => 타격시 피해받는 몬스터의 애니메이션, 타격시 피해정보(영웅의 공격량만큼)를 몬스터가 저장

getHit의 루프타임을 꺼준다(edit에서 끄면된다)
monsterController에 HitDamage()작성, 애니메이션의 getHit State 2로 변경

 

impact 계산

x 는 attack 애니메이션의 전체 실행시간이다. 12프레임째 충돌발생.

Hero Controller 코드수정

heroController를 바탕으로 몬스터 컨트롤러도 수정해준다.

몬스터 컨트롤러

 

이펙트 추가 후 최종코드

 

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

namespace Test2
{
    public class Test_PlayerControlAttackSceneMain : MonoBehaviour
    {
        [SerializeField]
        private Button btnAttack;
        private HeroController heroController;
        private MonsterController monsterController;
        [SerializeField]
        private GameObject hitFxPrefab;
        private GameObject fxGo;
     
        
        // Start is called before the first frame update
        void Start()
        {
            heroController = GameObject.FindObjectOfType<HeroController>();
            monsterController = GameObject.FindObjectOfType<MonsterController>();
            
            Debug.LogFormat("btanAttack:{0}", btnAttack);//button 컴포넌트의 인스턴스

            //this.btnAttack.onClick.AddListener(Attack);//명명
            //this.btnAttack.onClick.RemoveListener(Attack);//이벤트를 제거할 수도 있다.
            //이벤트 등록
         //   this.monsterController.onHit = new System.Action(() => { });
            //대리자 인스턴스가 메서드를 참조하는것
            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();//파티클 실행
               
            };
            this.btnAttack.onClick.AddListener(() => {
                //사거리 체크

                //두점과의 거리, 두 오브젝트의 radius의 합
                Vector3 start = this.monsterController.transform.position;
                //몬스터 컨트롤러 컴포넌트가 붙어있는 게임 오브젝트의 transform컴포넌트의 position속성
                Vector3 end = this.heroController.transform.position;
                Vector3 c = start - end;//정규화되지 않은 방향벡터(방향과 크기),크기: 길이
                DrawArrow.ForDebug(end, c);//시작위치, 방향
                float distance = c.magnitude;
                Debug.LogFormat("distance: {0}", distance);
                Debug.DrawLine(start, end, Color.magenta, 2f); //영웅과 몬스터의 길이를 출력

                //두 컴포넌트의 radius의 합
                float radius = this.heroController.Radius + this.monsterController.Radius;
                Debug.LogFormat("radius:{0}", radius);
                Debug.LogFormat("<color=yellow>distance: {0},radius: {1}</color>", distance, radius);
                Debug.LogFormat("<color=green>IsWithinRange: {0}</color>", IsWithinRange(distance, radius));
                if (this.IsWithinRange(distance, radius))
                {
                    //HeroController에게 공격을 명령
                    this.heroController.Attack(this.monsterController);
                }

            });//익명(람다)
        }

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

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

       
    }
}
using System.Collections;
using System.Collections.Generic;
using System.Net.Http.Headers;
using Test;
using UnityEngine;

namespace Test2
{
    public class HeroController : MonoBehaviour
    {
        public enum eState
         {
            Idle,Run,Attack
        }
        [SerializeField]
        private float radius = 1f;
        private Animator anim;
        private eState state;
        private float impactTime = 0.399f;
        private MonsterController target;
        private Coroutine attackRoutine;
        public float Radius
        {
            get
            {
                return this.radius;
            }
        }
        // Start is called before the first frame update
        void Start()
        {
           this.anim = GetComponent<Animator>();
           this.target = GameObject.FindObjectOfType<MonsterController>();
        }
   
        // Update is called once per frame
        void Update()
        {

        }
        public void Attack(MonsterController target)
        {
            Debug.Log("Attack!!");
            //anim.SetInteger("State",2);
            PlayAnimation(eState.Attack);
        }
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.yellow;
            GizmosExtensions.DrawWireArc(this.transform.position,this.transform.forward,360,this.radius);
        }
        
        private 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.attackRoutine != null)
                        {
                            this.StopCoroutine(this.attackRoutine);
                        }
                        this.attackRoutine = this.StartCoroutine(this.WaitForCompleteAttackAnimation());
                        break;

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

        }
        //코루틴 함수
        //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.HitDamage();
            Debug.LogFormat("target.state: {0}",this.target.state);
           // this.target.state = MonsterController.eState.GetHit;
            this.target.PlayAnimation(MonsterController.eState.GetHit);
            Debug.LogFormat("after target.state: {0}", this.target.state);
            yield return new WaitForSeconds(animStateInfo.length - this.impactTime);    //0.72초만큼 기다림 
            this.target.PlayAnimation(MonsterController.eState.Idle);
            //Idle애니메이션을 함 
            //this.anim.SetInteger("State", 0);
            this.PlayAnimation(eState.Idle);

        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static Test2.HeroController;
using System;

namespace Test2
{
    public class MonsterController : MonoBehaviour
    {
        public enum eState
        {
            Idle, Run, GetHit
        }
        [SerializeField]
        private float radius = 1f;
        private Animator anim;
        public eState state;
        public Action onHit;
        public float Radius
        {
            get
            {
                return this.radius;
            }
        }
        // Start is called before the first frame update
        void Start()
        {
            anim = GetComponent<Animator>();
        }

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

        }
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.yellow;
            GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, this.radius);
        }
        public void HitDamage()
        {
          //
          //
          //Debug.LogError("피해 애니메이션을 실행합니다.");
            //  this.anim.SetInteger("State", 2);
            //this.state = eState.GetHit;
            this.onHit();//대리자 사용
        }
        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;

                }
            }

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

public class DestroyParticle : MonoBehaviour
{
    private ParticleSystem ps;
    // Start is called before the first frame update
    void Start()
    {
        this.ps = this.GetComponent<ParticleSystem>();
        //duration이 끝나면 제거 - 코루틴으로 구현 
        StartCoroutine(this.CoWaitForPlayAfterDestroy());
    }

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

    }

    private IEnumerator CoWaitForPlayAfterDestroy()
    {
        yield return new WaitForSeconds(this.ps.main.duration);
        Destroy(this.gameObject);

    }
}
myoskin