몬스터 수정- 해시값 전달, 몬스터의 공격, 피격시 혈흔 효과, Player 수정
2023. 8. 24.

https://docs.unity3d.com/ScriptReference/Animator.StringToHash.html

 

Unity - Scripting API: Animator.StringToHash

Success! Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggested change from our users and will make updates where applicable. Close

docs.unity3d.com

-문자열을 해시값으로 변경해 저장

-애니메이션 컨트롤에서 정의한 파라미터는 해시테이블(HashTable)로 관리한다.

=> 파라미터 해시값을 미리 추출해 인자로 전달하는 방식이 속도 면에서 바람직한 방법이다.

 

몬스터 피격 리액션

-Player가 발사한 총알이 몬스터에 명중했을때 타격을 입는 리액션 구현

=>Collider 충돌감지: 몬스터에 콜라이더 추가

파라미터 해시값 추출

 

총알이 몬스터에 충돌했을 때 법선 벡터를 구해서 혈흔 효과를 주려한다.

 

Resources 폴더

-프리팹을 런 모드에서 스크립트를 통해 로드하려한다. 스크립트에서 프리팹 또는 에셋을 직접 로드하려면 해당 프리팹이나 에셋이 Resources 폴더 하위에 있어야 한다.(동적으로 에셋을 로드하고 싶을 때)

이 폴더는 예약 폴더로 대소문자를 정확히 지켜야한다. 

프리팹을 폴더에서 동적으로 가져오기

 

-충돌 지점에 법선 벡터를 그린다.

 

몬스터의 공격에 의해 Player가 피해를 입는 로직

몬스터가 공격상태가 되면 양손으로 타격하므로 물리적인 타격으로 주는 데미지를 구현한다.

playerController 수정

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UIElements;

public class MonsterController : MonoBehaviour
{
    
    public enum eState
    {//몬스터 상태 정의
        IDLE, TRACE, ATTACK, DIE
    }
    public eState state = eState.IDLE;//몬스터의 현재 상태
    private bool isDie;//몬스터의 사망 여부
    private NavMeshAgent agent;
    private Transform playerTrans;
    private Animator anim;
    private readonly int hashTrace = Animator.StringToHash("IsTrace");//Animator 파라미터의 해시값 추출
    private readonly int hashAttack = Animator.StringToHash("IsAttack");
    private readonly int hasGotHit = Animator.StringToHash("GotHit");
    private GameObject bloodEffectPrefab;//혈흔 효과 프리팹
    [SerializeField]private float traceDistance = 10.0f;//추적 사거리
    [SerializeField]private float attackDistance = 2.0f;//공격 사거리
    // Start is called before the first frame update
    void Start()
    {
        this.bloodEffectPrefab = Resources.Load<GameObject>("BloodSprayEffect");
        this.anim = GetComponent<Animator>();
        this.agent = this.GetComponent<NavMeshAgent>();//agent 컴포넌트 가져옴
        GameObject playerGo = GameObject.FindGameObjectWithTag("Player");
        this.playerTrans = playerGo.GetComponent<Transform>();
       // Vector3 playerPosition = playerTrans.position;
       // this.agent.destination = playerPosition;//destination 속성을 사용해 이동할 좌푯값 지정

        StartCoroutine(this.CheckState());//상태 갱신
        StartCoroutine(this.MonsterAction());//상태에따라 몬스터 행동 수행
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    private IEnumerator CheckState()
    {//상태 갱신 코루틴 함수: 일정 간격으로 몬스터의 행동상태 체크
        while (!isDie)
        {
            yield return new WaitForSeconds(0.3f);
            float distance = Vector3.Distance(this.transform.position, this.playerTrans.position);
            // Debug.Log(distance);
            //Debug.Log(this.state);

          
            if (distance <= this.traceDistance)//추적 사거리에 들어왔다면
            {
                this.state = eState.TRACE;//추적상태로 변경
                if (distance <= this.attackDistance) //공격 사거리에 들어왔다면
                {
                    this.state = eState.IDLE;
                    yield return null;
                    this.state = eState.ATTACK;//공격상태로 변경
                }

            }           
            else//멈춤
            {
                this.state = eState.IDLE;
            }
        }
        
    }

    private IEnumerator MonsterAction()
    {
        while (!isDie)
        {
            switch (this.state)
            {
                case eState.IDLE:
                    this.agent.isStopped = true;//추적 중지
                    //this.anim.SetBool("IsTrace", false);
                    this.anim.SetBool(this.hashTrace, false);
                    this.anim.SetBool(this.hashAttack,false);
                    break;

                case eState.TRACE:
                    this.agent.SetDestination(this.playerTrans.position);
                    this.agent.isStopped = false;                 
                    this.anim.SetBool(this.hashTrace, true);                
                    this.anim.SetBool(this.hashAttack, false);
                    break;

                case eState.ATTACK:    
                    this.anim.SetBool(this.hashAttack, true);
                    this.anim.SetBool(this.hashTrace, false);
                    break;

                case eState.DIE:
                    break;
            }
            yield return new WaitForSeconds(0.3f);
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.collider.CompareTag("Bullet"))
        {
            ContactPoint contactPoint = collision.GetContact(0);//첫 번째 충돌 지점
            //DrawArrow.ForDebug(contactPoint.point, -contactPoint.normal, 5f, Color.white);
            //point:위치, normal:법선, 법선 반대로 그려보기
            Quaternion rotation = Quaternion.LookRotation(-contactPoint.normal);//바라볼 방향
            //충돌 지점의 법선 벡터를 쿼터니언 타입으로 변환
            GameObject go = Instantiate(this.bloodEffectPrefab,contactPoint.point,rotation,this.transform);
            //go.transform.position = contactPoint.point;
            //go.transform.rotation = rotation;//법선 벡터로 회전         
        //    Debug.LogErrorFormat("{0}",contactPoint.point);
            Destroy(collision.gameObject);//remove bullet
            Destroy(go, 0.5f);//remove blood Effect
            this.anim.SetTrigger(this.hasGotHit);           
        }
    }
    private void OnDrawGizmos()
    {
        if(this.state == eState.TRACE)
        {
            Gizmos.color = Color.yellow;
            Gizmos.DrawWireSphere(this.transform.position, traceDistance);
        }
        else if(this.state == eState.ATTACK)
        {
            Gizmos.color= Color.green;
            Gizmos.DrawWireSphere(this.transform.position,attackDistance);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class PlayerController : MonoBehaviour
{
    public Vector3 cameraPosition;

    private Vector3 moveDir;
    private Vector3 aPosition;
    private Animation anim;
  
    [SerializeField]private float moveSpeed = 1f;
    [SerializeField] private float turnSpeed =80f;
    //data----------------------------------------------
    private readonly float initHp = 100.0f;//초기 생명값
    public float currHp;//현재 체력
    IEnumerator Start()
    {
        this.currHp = this.initHp;//체력 초기화
        this.anim = GetComponent<Animation>();
        anim.Play("Idle");//시작 시 Idle

        this.turnSpeed = 0.0f;
        yield return new WaitForSeconds(0.3f);
        this.turnSpeed = 80.0f;
    }
    private void Update()
    {
      
        //키보드 이동
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        float r = Input.GetAxis("Mouse X");//마우스를 왼쪽으로 움직이면 음수, 오른쪽 양수 반환
        moveDir = (Vector3.forward * v) + Vector3.right * h;
        this.transform.Translate(moveDir.normalized * this.moveSpeed * Time.deltaTime);//이동
        //   Debug.LogFormat("{0} , {1}", moveDir, moveDir.normalized);
        //  DrawArrow.ForDebug(this.transform.position, moveDir, 5f, Color.red);
        Vector3 angle = Vector3.up * turnSpeed * Time.deltaTime * r;
       // Vector3 angle2 = Vector3.up * turnSpeed * r;//Time.deltaTime을 빼보았다.
      //  Debug.LogFormat("angle2:{0},r:{1}",angle2,r );
        this.transform.Rotate(angle);//Vector3.up을 축으로 회전
        PlayAnim(h,v);

        
    }
    
    private void OnDrawGizmos()
    {
        DrawArrow.ForGizmo(this.transform.position, this.transform.forward , Color.red);
        Gizmos.color = Color.white;
        GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, 1);
        DrawArrow.ForGizmo(this.transform.position, -this.transform.forward * 3, Color.yellow);
        this.aPosition = this.transform.position + (-this.transform.forward * 3);//a지점의 위치
        DrawArrow.ForGizmo(this.aPosition, Vector3.up * 2, Color.green);//카메라의 위치를 의미한다.
        this.cameraPosition = this.aPosition+ (Vector3.up*2);
    }
    private void PlayAnim(float h,float v)//키보드 입력값을 기준으로 애니메이션 play
    {
        if (v >= 0.1f)//x축 : 전,후
        {
            anim.CrossFade("RunF", 0.25f);//전진
        }
        else if (v <= -0.1f)
        {
            anim.CrossFade("RunB", 0.25f);//후진
        }
        else if (h >= 0.1f)//z축 : 좌, 우
        {
            anim.CrossFade("RunR", 0.25f);
        }
        else if (h <= -0.1f)
        {
            anim.CrossFade("RunL", 0.25f);
        }
        else
        {
            anim.CrossFade("Idle", 0.25f);//정지 시 Idle
        }
    }

    private void OnTriggerEnter(Collider coll)
    {
        if(this.currHp >= 0.0f && coll.CompareTag("Punch"))
        {
               // Debug.Log(coll.tag);
                this.currHp -= 10.0f;
            
            if(this.currHp < 0.0f)
            {
                PlayerDie();
            }
        }

    }
    private void PlayerDie()
    {
        Debug.Log("Player Die!!");
    }
}
myoskin