Raycast-총알 발사 로직 수정/ 동적 장애물-NavMeshobstacle
DrawRay
-Ray를 씬 뷰에서 시각적으로 표현
-기존에 Instantiate로 총알을 생성했던 로직을 Raycast로 구현하려한다.
-Barrel에 NavMeshObstacle추가
-Carve를 체크하면 장애물로 인식하고 우회한다.
-위와 같이 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;//비활성화
}
}
'유니티 심화' 카테고리의 다른 글
SceneManager클래스- LoadSceneAsync, LoadSceneMode.Additve/Single (0) | 2023.08.30 |
---|---|
라이트 매핑(Light Mapping) (1) | 2023.08.30 |
(UI)생명 게이지 점수 구현 - PlayerPrefs로 저장 및 불러오기 (0) | 2023.08.29 |
Object Pooling 연습 - 총알 생성 (0) | 2023.08.28 |
Object Pooling으로 몬스터 생성 (0) | 2023.08.28 |