일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- c#기본문법
- c# c#프로그래머스
- c#기초문법
- c#
- 유니티공부
- 유니티
- 티스토리챌린지
- unity공부
- unity3d게임만들기
- c#문제
- 유니티서바이벌게임만들기
- unity3d
- 스파르타코딩클럽
- c#프로그래머스기초문법
- Unity
- unity게임만들기
- unity3dservival
- Console.WriteLine
- c#코딩기초트레이닝
- 스파르타
- 유니티상호작용
- c# 백준
- 유니티게임만들기
- 시샵
- c#코테
- unity게임
- 시샵문법
- C#문법
- 오블완
- 유니티3dui
- Today
- Total
나 개발자 진짜 되냐?
[ Unity 3D 서바이벌 게임 만들기 10 ] 적 생성과 로직 본문
곰이 나를 위협한다..
곰을 잡아보자!!
곰의 경우
자연스레 움직이게 해야 하는데
그 부분을 우리는 ai로 구현할 예정이다.
패키지매니저에서
이 친구 설치!
설치하게 되면 AI가 생기는데
두 번째 Obsolete를 눌러주자
이것저것 많은데
여기서 일단
레이어 느낌이랑 비슷하게
선택할 수 있는 곳이 있다.
이렇게 되면
보통 오브젝트에서
만지작해주는데
못 걷는 곳
걷는 곳 이렇게 레이어를 설정해 줄 수 있다.
이런 레이어들을 설정해 주고
Bake로 가서 구워주어야
한다.
Bake 해주면
이렇게 경계선이 생기고
걷지 못하는 곳과 걷는 곳이 생기게 된다.
근데 잘 보면 문제가 있다.
나무나 돌의 경우
이렇게 있으면
돌이나 나무는 지나 가지면 안 되는데
전체적으로 다 갈 수 있게
경계선이 없는 것 같다.
Resource_Tree 인스펙터에 들어와서
컴포넌트를 하나 추가해 주자
Move Threshoid : 세팅된 값 이상의 포지션을 움직이게 되면
피해 가는 장애물의 영역을 개선
Time To stationary : 장애물이 정지되었다고 생각하게 되는 시간
Carve Only Stationary : 장애물이 정지되었을 때 이렇게 구멍을 뚫어서
못가는 구역으로 만들어 주는 친구이다.
이렇게..!
자, 이제 NPC를 만들어보자
빈 오브젝트를 하나 만들고 NPC라고 저장한 다음에
Bear을 검색해서 나오는 프리팹을( 색상이 있는 친구 ) 끌어다가 넣어준다.
npc와 bear 위치를 싹 초기화해 주고
위치를 w키로 조절해서 두고 싶은 곳에 둔다.
다음에
npc 오브젝트 안에
Animator 컴포넌트 다운로드 후
애니메이션에
곰 모션 추가해 준다.
npc파일 생성!
애니메이션 컨트롤러도 생성!
생성해 주었으면
미리
인스펙터창에 넣어주고
애니메이터 창에서
bool 값으로 Moving을 만들고
Tigger로 Attack를 만들어준다.
여기서
idle
와
attack
그리고
workforward
세 개를 끌어다가 온다.
이렇게 설정!
다음에 주황주황 블록을 눌러서
Make Transition
해서 선 그어주고
또 가만히 있다가 때리는 게 아니니까
WalkForward 에다가
Make Transition
해서 attack 할 수 있도록
공격 후 다시 제자리로 돌아와야 하니
idel로 Make Transition
역시 말보단 사진이 최고야..
현재 파란색 줄을 눌러보면
컨디션에
무빙을 해줘서 움직일 수 있게 해 주고
has exit time을 해제해서
바로 시작할 수 있게끔 해준다.
반대 화살표는
moving false 해준다
공격을 하게 되면
공격기능이 활성화되어야겠죠?!
밑에 attack 해주고
반대화살표는 해줄 거 없다!
WalkForward > attack 에도
attack로 해줘야 한다.
< 정리 >
Idle → Attack : HasExitTime ( ) , Attack
Idle → WalkForward : HasExitTime ( ) , Moving (true)
WalkForward → Idle : HasExitTime ( ) , Moving (false)
WalkForward → Attack : HasExitTime ( ) , Attack
Attack → Idle : HasExitTime ( V ) , Moving (false)
Attack → WalkForward : HasExitTime ( V ) , Moving (true)
자 이제
NPC 안에다가
컴포넌트를 3개 만들자
1. 스크립트
스크립트 폴더에 가서 NPC라는 친구 만들고
그 안에다가 넣기
2. box Collider 넣어서 충돌감지!
사이즈도 지정
3. Nav Mesh Agent
여기는 Agent를 넣어서 여기 값을
코드를 통해 수정할 계획!
4. 움직이게 하려면 Animator까지!
자 NPC 스크립트를 짜보자!
< NPC.cs >
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public enum AIState
{
Idle,
Wandering, // 임의로 목표 찍어서 움직이게 하기
Attacking, // 공격
Fleeing // 도망
}
public class NPC : MonoBehaviour, IDamagable
{
[Header("Stats")]
public int health; // NPC체력
public float walkSpeed; // 걷는 속도
public float runSpeed; // 뛰는 속도
public ItemData[] dropOnDeath; // 죽었을때 떨어뜨리는 데이터
[Header("AI")]
public float detectDistance; // 목표지점까지 거리
public float safeDistance;
private AIState aiState; //열거형 친구들
[Header("Wandering")] // 자동으로 목표를 찍고 이동할때
public float minWanderDistance; // 최솟값
public float maxWanderDistance; // 최댓값
public float minWanderWaitTime; // 새로운 목표지점을 찍을 때 기다리는 시간을
public float maxWanderWaitTime; // 최댓값 최솟값을 받아서 랜덤으로 시간 추출
[Header("Combat")] // NPC 공격
public int damage; // 데미지
public float attackRate; // 얼마나 공격간의 텀을 줄지
private float lastAttackTime; // 마지막 공격한 시각
public float attackDistance; // 공격 가능한 거리
private float playerDistance; // 플레이어와의 거리
public float fieldOfView = 120f; // 몬스터를 기준으로 시야각 지정
private NavMeshAgent agent; // NPC, 즉 곰
private Animator animator; // 애니메이터
private SkinnedMeshRenderer[] meshRenderers; // 몬스터들의 각종 매쉬들의 정보 리스트
private void Awake()
{
agent = GetComponent<NavMeshAgent>();
animator = GetComponentInChildren<Animator>();
//리스트기도하고 되게 다양할 수 있어서 Children 으로 !
meshRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
}
private void Start()
{ //시작할때 스테이트를 정해줘야한다.
SetState(AIState.Wandering);
}
private void Update()
{ // 플레이어의 거리에 따라 상태가 바뀔테니, 플레이어 위치를 잘 가져와야한다.
playerDistance = Vector3.Distance(transform.position, CharacterManager.Instance.Player.transform.position);
// 애니메이션도 상태를 계속 호출 할 것이다. 가만히 있는지 아닌지
animator.SetBool("Moving", aiState != AIState.Idle);
switch (aiState) // 열거형에 맞게 어떤게 나오면 호출할지 적어주어야한다.
{
case AIState.Idle:
PassiveUpdate();
break;
case AIState.Wandering:
PassiveUpdate();
break;
case AIState.Attacking:
AttackingUpdate();
break;
case AIState.Fleeing:
FleeingUpdate();
break;
}
}
private void SetState(AIState state)
{
aiState = state; // 열거형 친구들
switch (aiState)
{
case AIState.Idle: // 가만히 서있을 때
agent.speed = walkSpeed;
agent.isStopped = true; // 정지해있다.
break;
case AIState.Wandering: // 목표지점을 찍고 이동
agent.speed = walkSpeed; // 걷는다
agent.isStopped = false; // 안멈춰있다. = 움직인다.
break;
case AIState.Attacking: // 공격범위에 들어오면 뛰어올것
agent.speed = runSpeed; // 뛰어온다
agent.isStopped = false; // 안멈춘다.
break;
case AIState.Fleeing: // 때린다
agent.speed = runSpeed; //
agent.isStopped = false;
break;
}
// 이렇게 나누어주면 runspeed일때 비례해서 값이 늘어나게 된다.
animator.speed = agent.speed / walkSpeed;
}
void PassiveUpdate()
{ //상태가 원더링이거나, 목표지점을 찍고 남은거리가 0.1보다 작으면
if (aiState == AIState.Wandering && agent.remainingDistance < 0.1f)
{
SetState(AIState.Idle); // 잠시 멈춘다.
Invoke("WanderToNewLocation", Random.Range(minWanderWaitTime, maxWanderWaitTime));
// "" 안에 있는 함수 호출, 랜덤한 시간대에!
}
if (playerDistance < detectDistance) // 만약에 거리가 가까워지면
{
SetState(AIState.Attacking); //공격함수를 탄다.
}
}
void WanderToNewLocation() //반복적으로 다음 목표지점을 호출하는 함수
{
// 방어코드 작성
if (aiState != AIState.Idle)
{
return;
}
SetState(AIState.Wandering); //상태를 원더링으로 바꿈
agent.SetDestination(GetWanderLocation()); // 목표지점을 정하자.
}
Vector3 GetWanderLocation() // 목표지점
{
NavMeshHit hit; // 변수
// 현재위치 + 랜덤영역 + out hit + 최대거리 + laymask
NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * safeDistance), out hit, maxWanderDistance, NavMesh.AllAreas);
int i = 0;
// 목표지점이 너무 가까우면 곤란하니
while (GetDestinationAngle(hit.position) > 90 || playerDistance < safeDistance)
{
// 반복해서 수행한다.
NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * safeDistance), out hit, maxWanderDistance, NavMesh.AllAreas);
i++; // 그래도 거리가 멀수도 있으니 한번 더해봐라! 하는 의미로 i값을 추가
if (i == 30) // 한 30번 해보자!
break;
}
return hit.position; //포지션 가져오기
}
void AttackingUpdate()
{ //플레이어 위치가 공격할 수 있는거리까지 오지 않거나, 또는 시야각에 내가 안보이면 진입!
if (playerDistance > attackDistance || !IsPlayerInFieldOfView())
{
agent.isStopped = false; // 안멈춘다. 쫒아온다.
NavMeshPath path = new NavMeshPath(); // 경로를 가져온다.
if (agent.CalculatePath(CharacterManager.Instance.Player.transform.position, path)) // 경로를 계산해주는 친구 t/f로 나오는 값
{
agent.SetDestination(CharacterManager.Instance.Player.transform.position); // 목표지점으로 간다.
}
else // 강으로 들어갔다면 다시 임의의 지점으로 이동해야한다.
{
SetState(AIState.Fleeing);
}
}
else
{
agent.isStopped = true; // 잠시 멈추고
if (Time.time - lastAttackTime > attackRate) // 마지막 공격한 시간을 현재시간에서 빼주고
{ // 그값이 공격텀시간보다 커지면 즉, 재공격이 가능해지면
lastAttackTime = Time.time; // 현재시간 넣고 ( 시간 초기화 )
//IDamagble 컴포넌트를 가져와서 값을 넣어주며, 플레이어의 체력이 깎일것이다.
CharacterManager.Instance.Player.controller.GetComponent<IDamagable>().TakePhysicalDamage(damage);
animator.speed = 1; // 멈춰있으니 속도는 0
animator.SetTrigger("Attack"); // 그리고 attack로 해서 움직이게!
}
}
}
bool IsPlayerInFieldOfView() // 시야각
{
Vector3 directionToPlayer = CharacterManager.Instance.Player.transform.position - transform.position;
float angle = Vector3.Angle(transform.forward, directionToPlayer); // 내가 정면으로 바라보는 위치의 각도
return angle < fieldOfView * 0.5f; //120도를 반으로 나누어서 오른쪽으로 가는지 왼쪽으로 가는지 알게한다.
}
public void TakePhysicalDamage(int damageAmount)
{
health -= damageAmount;
if (health <= 0) // 체력이 다 닳았으니 죽는다.
Die();
StartCoroutine(DamageFlash()); // 데미지 효과가 나야한다.
}
void FleeingUpdate()
{
if (agent.remainingDistance < 0.1f) //거리가 가까워지면
{
agent.SetDestination(GetFleeLocation()); //받아온 값으로 도망친다.
}
else
{
SetState(AIState.Wandering);
}
}
Vector3 GetFleeLocation()
{
NavMeshHit hit;
NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * safeDistance), out hit, maxWanderDistance, NavMesh.AllAreas);
int i = 0;
while (GetDestinationAngle(hit.position) > 90 || playerDistance < safeDistance)
{
NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * safeDistance), out hit, maxWanderDistance, NavMesh.AllAreas);
i++;
if (i == 30)
break;
}
return hit.position;
}
float GetDestinationAngle(Vector3 targetPos)
{
return Vector3.Angle(transform.position - CharacterManager.Instance.Player.transform.position, transform.position + targetPos);
}
private void Die()
{
for (int x = 0; x < dropOnDeath.Length; x++) // 모든 데이터 아이템들을
{ // 생성한다. 위치는 내주변에서 2만큼 떨어진 곳에, 회전은 안되도록
Instantiate(dropOnDeath[x].dropPrefab, transform.position + Vector3.up * 2, Quaternion.identity);
}
// 몬스터는 사라진다.
Destroy(gameObject);
}
IEnumerator DamageFlash() // 코루틴
{
for (int x = 0; x < meshRenderers.Length; x++)
meshRenderers[x].material.color = new Color(1.0f, 0.6f, 0.6f); //색을 다 바꿔준다.
yield return new WaitForSeconds(0.1f); //코루틴 리턴, 0.1초 기다렸다가 밑에 식 실행
for (int x = 0; x < meshRenderers.Length; x++)
meshRenderers[x].material.color = Color.white; // 다시 색을 돌려놓는다.
}
}
일단 변수를 쭈욱 써보자
열거형도 하나 만든다.
그리고 내려가서
필요한 부분을 get해온다.
여기서 mesh를 리스트로 받아서 이렇게 get 한 이유는
공격받았을 때는 약간 색을 다르게 해 주려고 계획하고 있기 때문!
해주고 start를 통해서 우리는 이 친구가
어떤 동작을 할지 알려주어야 한다.
그러니 일단 그 동작을 하는 함수를 만들어주자
함수에는
각각 열거형의 기능들에 대해 적혀있다.
다음에 이 값을 start에 넣어주자.
자 다음에는
Update문으로 지속적으로 반복해야 하는 값을 넣어주어야 한다.
Passive Update
Attacking Update
상태에 따라
함수를 계속 돌려줄 예정이다.
animator.SetBool("Moving", aiState!= AIState.Idle);
여기 해석해 보면
idle로 잡고
만약에 idle이 아니면
뒷 식은 참이 되는 거니까
moving true로 계속 걸을 것이다.
근데 만약에 idle가 맞으면 뒤이 조건식이 틀려서 안 걷게 될 것이다.
시작을 우리는 Wandering로 했기 때문에
idle는 false라서 걷는 거부터 시작할 것이다.
passive부터 보자!
이렇게 해주고
WanderToNewLocation을 만들어주자
이렇게 해주면!
반복적으로 목표지점을 호출 가능하다
밑에 또 새로운 함수가 생겼다.
GetWanderLocation
목표지점에 대한 정보 메서드이다.
여기 코드 중간에
SamplePosition이 있는데
자세히 들어가 보면
여기 매개변수로
out NavMeshHit이라는 변수가 들어가게 된다
그래서 우리는 위에
NavMeshHit hit;를 넣어준 것!
sourcePosition에다가 우리가 지정할 영역을 설정해 주고
NavMeshHit으로 그 포지션 안에서 이동경로 한에서 최단 경로를 반환해 준다.
그리고 최고거리, 그리고 LayMask도 받아 필터링을 걸 수 있다.
중간쯤
Random.onUnitSphere가 있는데
이 부분은 반지름이 1인 구이다.
가상의 구를 만들어서 영역을 정하게 된다.
아!
while문 안에 식은
원래 위치에서 내 위치를 뺀 값이다.
자 이제 두 번째 Attacking
이제 거리가 가까워지면 공격을 하는데
공격을 하려면
곰의 시야에 내가 들어와야 한다.
그래서 시야각을 설정해주어야 한다.
시야각은 bool값으로 정한다.
만들어주고
공격을 작성해 주면 된다.
이제 곰이 공격했으니
이제 내 차례다!
공격은 우리
IDamageable이라고 인터페이스로 만들어주었었다.
그 친구를 가져오는 법은 간단하다.
1. 클래스에 가서
이렇게 해주고
빨간 줄에서
ctrl.
인터페이스이기 때문에
상속받은 이상
무조건 인터페이스가 정의한 메서드를 선언해주어야 한다.
그래서 빨간 것!
인터페이스 구현해 주면
맨 밑줄에 이 친구가 뜬다
작성
새로운 함수 두 개가 보인다.
Die
와
DamageFlash
DamageFlash의 경우 코루틴으로 만들어보자
마지막으로
Fleeing 도망
Get도 만들어보자!
이 식은 아까 GetWander과 똑같다.
그래서 주석 패스!
자 이제 유니티 와서 값 설정해 주자!
애니메이터에는
이렇게 넣어주면 되는데
컨트롤러에는 우리 아까 블록블록 넣어주고
밑에 아바타는
우리 곰 프리팹을 눌러보면
애니메이터가 잘 붙어있다.
저 아바타를 눌러주면 그게 어디 있는지 위치가 왼쪽에 나오는데
npc 컴포넌트로 올라가서
왼쪽 아바타를 고대로 데려오면 된다!
다음에
곰에 있던 애니메이터 삭제하고
프리팹 언팩해주면 끝!
곰이 엄청 무섭게 뛰어온다..
적 구현까지 완성!
진짜 마지막으로 오디오 넣는 방법으로
3D 게임은 여기서 마무리지으려 한다!
'유니티를 공부해봐요! > 중급이에요!' 카테고리의 다른 글
[ 11月 5日 ] 오늘 내가 배운 것 _ 36日次 (2) | 2024.11.05 |
---|---|
[ Unity 3D 서바이벌 게임 만들기 11 ] 사운드 및 뮤직 존 생성 (0) | 2024.11.03 |
[ Unity 3D 서바이벌 게임 만들기 9 ] 스태미나 구현 (0) | 2024.10.29 |
[ Unity 3D 서바이벌 게임 만들기 8 ] 자원 캐기 (0) | 2024.10.29 |
[ Unity 3D 서바이벌 게임 만들기 7 ] 아이템 장착 그리고 공격 (1) | 2024.10.29 |