Raycast-총알 발사 로직 수정/ 동적 장애물-NavMeshobstacle
2023. 8. 29.

DrawRay

-Ray를 씬 뷰에서 시각적으로 표현

 

-기존에 Instantiate로 총알을 생성했던 로직을 Raycast로 구현하려한다.

monsterController수정

 

-Barrel에 NavMeshObstacle추가

-Carve를 체크하면 장애물로 인식하고 우회한다.

계단 설치
OffMeshLink

-위와 같이 Off Mesh Link를 넣을 수 있다. Generate OffMeshLinks를 체크하고 Bake해주면 된다.

 

사용자 정의 Off Mesh Link

사용자 정의 off mesh link를 사용해보기 위해 off mesh link를 지우고 다시 bake한다.

 

 

-자연스러운 회전처리 연습

 

-시작하자마자 도는것을 막고, 목적지까지 남은 거리로 회전 여부를 판단+Slerp

 

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

public class TestMonster : MonoBehaviour
{
    [SerializeField] Transform target;
    // Start is called before the first frame update
    private NavMeshAgent agent;
    public float speed = 1f;
    private void Awake()
    {
        this.agent = this.GetComponent<NavMeshAgent>();
        this.agent.updateRotation = false;// NavMeshAgent의 자동 회전 기능 비활성화
        
    }
    void Start()
    {
        this.agent.SetDestination(this.target.position);
       
        
    }

    // Update is called once per frame
    void Update()
    {
        //var distance = Vector3.Distance(this.target.position,this.transform.position);
        //Debug.Log(distance);
        //if (distance < this.agent.stoppingDistance)
        //{
        //    this.agent.velocity = Vector3.zero;
        //}
        //else
        //{
        //    var dir = this.target.position - this.transform.position;
        //    this.agent.velocity = dir.normalized * this.speed * Time.deltaTime;

        //}
        if(this.agent.remainingDistance >= this.agent.stoppingDistance)
        {//목적지까지 남은 거리로 회전 여부 판단
            Vector3 direction = this.agent.desiredVelocity;//agent의 이동방향
           // direction = this.target.position - this.transform.position; //이동 방향
            Quaternion rot = Quaternion.LookRotation(direction.normalized);//회전 각도
            this.transform.rotation = Quaternion.Slerp(this.transform.rotation, rot, Time.deltaTime * 10.0f);

        }
    }
}

Area Mask로 가중치를 설정해보았다.

-Plane clear하고 다시 Bake(static 체크하고 bake해야한다.)

가중치가 가장 낮은쪽으로 이동

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=false;//몬스터의 사망 여부
    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 hashGotHit = Animator.StringToHash("GotHit");
    private readonly int hashDance = Animator.StringToHash("Dance");
    private readonly int hashSpeed = Animator.StringToHash("Speed");
    private readonly int hashDie = Animator.StringToHash("Die");
    private GameObject bloodEffectPrefab;//혈흔 효과 프리팹
    [SerializeField]private float traceDistance = 10.0f;//추적 사거리
    [SerializeField]private float attackDistance = 2f;//공격 사거리
    //--Data----------------------------------------------------
    private float initHp = 100f;
    private float currHp;
  
    void Awake()
    {
        this.currHp = this.initHp;
        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 속성을 사용해 이동할 좌푯값 지정

       
    }
   
    // Update is called once per frame
    void Update()
    {
        
    }
    private IEnumerator CheckState()
    {//상태 갱신 코루틴 함수: 일정 간격으로 몬스터의 행동상태 체크
        while (!isDie)
        {
            yield return new WaitForSeconds(0.3f);
            if (this.state == eState.DIE) yield break;
            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:
                    this.isDie = true;
                    this.agent.isStopped = true;
                    this.anim.SetTrigger(this.hashDie);
                    GetComponent<CapsuleCollider>().enabled = false;//몬스터의 collider 비활성화

                    yield return new WaitForSeconds(3.0f);
                    //몬스터 사망 시, 일정 시간 대기후 오브젝트 풀링으로 환원
                    this.currHp = 100;//사망 후 다시 사용을 위한 hp 초기화
                    this.isDie = false;
                    GetComponent<CapsuleCollider>().enabled = true;//몬스터 Collider 활성화
                    this.gameObject.SetActive(false);//몬스터 비활성화 - 오브젝트 풀에서 다시 추출가능한 상태로 만듦.
                    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.hashGotHit);

           // this.currHp -= 30.0f;
           //// Debug.Log(this.state);
           // if(this.currHp <= 0)
           // {//몬스터 사망
           //     this.state = eState.DIE;
           //     GameManager.instance.DisplayScore(50);//몬스터 사망 시 50점 추가
           // }
        }
    }
    private void ShowBloodEffect(Vector3 pos, Quaternion rotation)
    {
        GameObject go = Instantiate(this.bloodEffectPrefab, pos, rotation);
        go.transform.position = pos;
        go.transform.rotation = rotation;//법선 벡터로 회전
        Destroy(go, 0.5f);
    }
    public void OnDamage(Vector3 pos, Vector3 normal)
    {
        this.anim.SetTrigger(hashGotHit);

        Quaternion rot = Quaternion.LookRotation(normal);
        ShowBloodEffect(pos, rot);

        this.currHp -= 30.0f;
        Debug.Log(this.state);
        if (this.currHp <= 0)
        {//몬스터 사망
            this.state = eState.DIE;
            GameManager.instance.DisplayScore(50);//몬스터 사망 시 50점 추가
        }
    }
    private void OnEnable()
    {//스크립트가 활성화 될 때마다 호출되는 함수
        PlayerController.OnPlayerDie += this.OnPlayerDie;//이벤트 발생 시 수행할 함수 연결
        StartCoroutine(this.CheckState());//상태 갱신
        StartCoroutine(this.MonsterAction());//상태에따라 몬스터 행동 수행
    }
    private void OnDisable()
    {//기존에 연결된 함수 해제
        PlayerController.OnPlayerDie -= this.OnPlayerDie;
    }
    void OnPlayerDie()
    {
        StopAllCoroutines();//몬스터 상태체크 중지
        this.agent.isStopped = true;//추적을 정지하고 애니메이션 수행
        this.anim.SetFloat(this.hashSpeed, Random.Range(0.8f, 1.2f));
        this.anim.SetTrigger(this.hashDance);
    }
    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 System.Runtime.CompilerServices;
using Unity.Burst.CompilerServices;
using UnityEngine;

public class FireController : MonoBehaviour
{
    [SerializeField]private GameObject bullet;
    [SerializeField]private Transform firePos;
    [SerializeField] private AudioClip fireSfx;//총소리에 사용할 음원
    private new AudioSource audio;//new 한정자
    private MeshRenderer muzzleFlash;
    // Start is called before the first frame update
    void Start()
    {
         this.audio= GetComponent<AudioSource>();
         this.muzzleFlash = firePos.GetComponentInChildren<MeshRenderer>();
        //firPos의 자식에 있는 muzzleFlash의 컴포넌트를 가져옴
         this.muzzleFlash.enabled= false;//처음 시작할 때 비활성화
    }

    // Update is called once per frame
    void Update()
    {
        //  Debug.DrawRay(firePos.position, firePos.forward * 10.0f, Color.green);
       
        var layerMask = 1 << LayerMask.NameToLayer("Monster_Body");

       
        if(Input.GetMouseButtonDown(0))
        {
            this.Fire();
            Ray ray = new Ray(firePos.position, firePos.forward);
            Debug.DrawRay(ray.origin, ray.direction * 10f, Color.red, 2f);
            RaycastHit hit;

            if (Physics.Raycast(ray.origin, ray.direction, out hit, 10.0f, layerMask))
            {//광선의 원점, 발사 방향, 맞은 결과 데이터, 거리, 감지하는 범위의 레이어 마스크
              Debug.LogFormat("{0}",hit.transform.name);
                hit.transform.GetComponent<MonsterController>()?.OnDamage(hit.point, hit.normal);
            }
        }
    }

    private void Fire()
    {
        Instantiate(bullet,firePos.position,firePos.rotation);//총알 프리팹의 인스턴스 생성
        this.audio.PlayOneShot(fireSfx, 1.0f);
        StartCoroutine(this.ShowMuzzleFlash());//총구 화염 효과
    }
    IEnumerator ShowMuzzleFlash()
    {
        Vector2 offset = new Vector2(Random.Range(0,2),Random.Range(0,2)) * 0.5f;//오프셋 좌푯값 랜덤생성
        this.muzzleFlash.material.mainTextureOffset = offset;
        //회전 변경
        float angle = Random.Range(0, 360);
        this.muzzleFlash.transform.localRotation = Quaternion.Euler(0,0,angle);
        //크기조절
        float scale = Random.Range(1.0f, 2.0f);
        this.muzzleFlash.transform.localScale = Vector3.one * scale;

        this.muzzleFlash.enabled = true;//활성화
        yield return new WaitForSeconds(0.2f);
        this.muzzleFlash.enabled = false;//비활성화
    }
}
myoskin