2023. 11. 15. 18:24ㆍSKKU DT
https://assetstore.unity.com/packages/3d/characters/humanoids/zombie-30232
무료 좀비 에셋 다운로드
좀비 프리팹을 Enemy 오브젝트의 Children으로 가져다 놓는다.
좀비의 애니메이션을 적용 (Idle에서 Move로)
Animator_Controller 생성
New State를 하나 만들어서 "Idle"로 이름을 바꾸고 [Motion] 자리에 좀비의 애니메이션 "Z_Idle"을 드래그앤 드랍한다.
제자리에서 움직이는 Z_Run_InPlace를 새로 만든 Move에 넣는다. 트랜지션 화살표 잇기
트리거 파라미터 생성(IdleToMove)
Idle -> Move 화살표 클릭하면 Inspector 창에서 [Conditions]에 조건을 넣을 수 있다.
호출했을 때 애니메이션이 바로 나오려면 [Has Exit Time]을 체크 해제하면 된다.
EnemyFSM 스크립트에서 자식의 컴포넌트를 가져오게 한다. Zombie1이 Enemy 안에 있다.
애니메이터 변수 생성
Animator Anim;
Start 함수에서 자식 오브젝트 컴포넌트 가져오기
anim = transform.GetComponentInChildren<Animator>();
EnemyFSM 스크립트의 Idle 함수에서 변수의 트리거 접근해서 바꿔주기
void Idle()
{
//만약 플레이어와 적의 거리가 발견 범위 이내라면 Move 상태로 전환
if(Vector3.Distance(transform.position, player.position) < findDistance)
{
m_State = EnemyState.Move;
Debug.Log("발견! Idle -> Move");
//이동 애니메이션을 전환
anim.SetTrigger("IdleToMove");
}
}
좀비가 나를 바라보며 다가오게 하기
EnemyFSM 스크립트에서 Move 함수 수정 -플레이어를 향해서 방향을 전환하게 수정(플레이어 - 적)
void Move()
{
//만약 현재 위치와 초기 위치 거리가 이동 가능 범위보다 크면 복귀
if(Vector3.Distance(transform.position, originPos) > moveDistance)
{
m_State = EnemyState.Return;
Debug.Log("상태 전환");
}
//만약 플레이어와 적의 거리가 공격 범위보다 크다면 플레이어를 향해 이동
else if (Vector3.Distance(transform.position, player.position) > attackDistance)
{
//이동 방향
Vector3 dir = (player.position - transform.position).normalized;
//이동
cc.Move(dir * moveSpeed * Time.deltaTime);
//플레이어를 향해 방향 전환
transform.forward = dir;
}
else
{
m_State = EnemyState.Attack;
Debug.Log("공격!");
//공격이 바로 안되므로 공격 딜레이 시간 만큼 미리 진행을 시켜놓아야 한다.
currentTime = attackDelay;
}
}
좀비의 애니메이션을 적용 (Move에서 Idle로) (Return)
MoveToIdle 트리거 생성, Move -> Idle 트랜지션 생성, Condition 부여, Has Exit Time 체크 해제
EnemyFSM 스크립트에서 Return 함수 수정, 초기 위치 방향으로 이동하게 수정
void Return()
{
//오리지널 방향으로 돌아가기 특정 좌표값보다 범위로 나타내는게 에러가 덜 날 가능성이 높다.
if(Vector3.Distance(transform.position, originPos) > 0.1f)
{
//방향 구하기 (목적지 - 내 위치)
Vector3 dir = (originPos - transform.position).normalized;
//이동하기
cc.Move(dir * moveSpeed * Time.deltaTime);
//복귀 지점 방향으로 전환
transform.forward = dir;
}
else //그렇지 않다면 적 위치를 초기 위치로 조정하고, 대기 상태로 전환한다.
{
transform.position = originPos;
m_State = EnemyState.Idle;
Debug.Log("상태 전환: Return -> Idle");
//대기 애니메이션으로 전환
anim.SetTrigger("MoveToIdle");
}
}
돌아가면 기울어져서 멈춘다.
EnemyFSM에 회전 변수 추가
Quaternion originRot;
Start 함수에 초기 위치에 회전 추가 originRot
void Start()
{
//플레이어 Transform Component 할당
player = GameObject.Find("Player").transform;
//최초의 적 상태를 대기로 설정
m_State = EnemyState.Idle;
//적 캐릭터 컨트롤러 컴포넌트 할당
cc = GetComponent<CharacterController>();
//적의 초기 위치 저장
originPos = transform.position;
originRot = transform.rotation;
//나의 자식 오브젝트 컴포넌트를 가져와라 컴포넌트 할당
anim = transform.GetComponentInChildren<Animator>();
}
Return함수 수정
void Return()
{
//오리지널 방향으로 돌아가기 특정 좌표값보다 범위로 나타내는게 에러가 덜 날 가능성이 높다.
if(Vector3.Distance(transform.position, originPos) > 0.1f)
{
//방향 구하기 (목적지 - 내 위치)
Vector3 dir = (originPos - transform.position).normalized;
//이동하기
cc.Move(dir * moveSpeed * Time.deltaTime);
//복귀 지점 방향으로 전환
transform.forward = dir;
}
else //그렇지 않다면 적 위치를 초기 위치로 조정하고, 대기 상태로 전환한다.
{
transform.position = originPos;
transform.rotation = originRot;
m_State = EnemyState.Idle;
Debug.Log("상태 전환: Return -> Idle");
//대기 애니메이션으로 전환
anim.SetTrigger("MoveToIdle");
}
}
좀비의 애니메이션을 적용 (Attack, Attack 대기)
Attack, Attack_Delay 스테이트 생성, 각각 Z_Attack, Z_Idle 애니메이션 넣기
Move -> Attack_Delay 트랜지션 연결, MoveToAttackDelay 조건 추가
EnemyFsm 스크립트의 Move 함수 수정
void Move()
{
//만약 현재 위치와 초기 위치 거리가 이동 가능 범위보다 크면 복귀
if(Vector3.Distance(transform.position, originPos) > moveDistance)
{
m_State = EnemyState.Return;
Debug.Log("상태 전환");
}
//만약 플레이어와 적의 거리가 공격 범위보다 크다면 플레이어를 향해 이동
else if (Vector3.Distance(transform.position, player.position) > attackDistance)
{
//이동 방향
Vector3 dir = (player.position - transform.position).normalized;
//이동
cc.Move(dir * moveSpeed * Time.deltaTime);
//플레이어를 향해 방향 전환
transform.forward = dir;
}
else
{
m_State = EnemyState.Attack;
Debug.Log("공격!");
//공격이 바로 안되므로 공격 딜레이 시간 만큼 미리 진행을 시켜놓아야 한다.
currentTime = attackDelay;
//공격 대기 애니메이션 실행
anim.SetTrigger("MoveToAttackDelay");
}
}
AttackDelay - > Attack 트랜지션에 StartAttack 트리거 추가, 조건 추가, Has Exit Time 체크 해제
EnemyFsm 스크립트의 Attack 함수 수정(anim.SetTrigger("StartAttack");)
void Attack()
{
//플레이어와 적의 거리가 공격 범위 이내라면 공격
if(Vector3.Distance(transform.position, player.position) < attackDistance)
{
//일정 시간마다 플레이어 공격
currentTime += Time.deltaTime;
if(currentTime > attackDelay)
{
player.GetComponent<PlayerMove>().DamageAction(attackPower);
print("공격!");
currentTime = 0;
//공격 애니메이션 실행
anim.SetTrigger("StartAttack");
}
}
//그렇지 않다면, Move();
else
{
m_State = EnemyState.Move;
Debug.Log("이동");
currentTime = 0;
}
}
**Attack -> AttackDelay는 별다는 트랜지션, 조건, 코드 수정 없이 돌아오기 때문에 건드릴 필요 없다.
Attack - [Loop Time] 끄기
Attack -> Move, Attack_Delay -> Move로 돌아오기
AttackToMove 파라미터 생성, AttackToMove 조건 추가, Has Exit Time 체크 해제
Attack_Delay -> Move도 마찬가지
EnemyFsm의 Attack 함수 수정(anim.SetTrigger("AttackToMove");)
void Attack()
{
//플레이어와 적의 거리가 공격 범위 이내라면 공격
if(Vector3.Distance(transform.position, player.position) < attackDistance)
{
//일정 시간마다 플레이어 공격
currentTime += Time.deltaTime;
if(currentTime > attackDelay)
{
player.GetComponent<PlayerMove>().DamageAction(attackPower);
print("공격!");
currentTime = 0;
//공격 애니메이션 실행
anim.SetTrigger("StartAttack");
}
}
//그렇지 않다면, Move();
else
{
m_State = EnemyState.Move;
Debug.Log("이동");
currentTime = 0;
//이동 애니메이션 실행
anim.SetTrigger("AttackToMove");
}
}
버그 수정(EnemyFSM 스크립트에서 DamageProcess() 코루틴 수정)
IEnumerator DamageProcess()
{
//피격 모션만큼 기다림 애니메이션 고려
yield return new WaitForSeconds(0.5f);
//이동 상태 전환
m_State = EnemyState.Move;
//이동 애니메이션
anim.SetTrigger("IdleToMove");
}
좀비의 애니메이션을 적용 (Die)
AnyState의 트랜지션을 Die와 연결, Die 파라미터 추가, Die 조건 추가
HitEnemy 함수 변경(anim.SetTrigger("Die");)
public void HitEnemy(int hitPower)
{
//적이 피격 상태이면 플레이어를 때릴 수 없게
if(m_State == EnemyState.Damaged)
{
return;
}
//플레이어의 공격력만큼 적 체력 감소
hp -= hitPower;
//적 체력이 0보다 크면 피격
if(hp > 0)
{
m_State = EnemyState.Damaged;
Damaged(); //코루틴 호출
Debug.Log("적 체력 표시: " + hp);
}
//0보다 작으면 죽음
else
{
m_State = EnemyState.Die;
Debug.Log("죽음");
//죽음 애니메이션
anim.SetTrigger("Die");
Die();
}
IEnumerator DieProcess()
{
//적 캐릭터 컨트롤러 컴포넌트 비활성화
cc.enabled = false;
//2초 대기
yield return new WaitForSeconds(3f);
//적 제거
Destroy(gameObject);
}
void Die()
{
//실행 중인 이전 Coroutine 중지
StopAllCoroutines();
//죽음 Coroutine 실행
StartCoroutine(DieProcess());
}
}
Scene 전환하기
로비 씬 만들기
이미지와 버튼 추가
SceneChange 스크립트 생성 -namespace 사용
using UnityEngine.SceneManagement;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneChange : MonoBehaviour
{
}
public void LoadBattleScene()
{
//파일 이름으로 씬 불러오기
SceneManager.LoadScene("MainScene");
}
}
EventSystem에 스크립트를 넣는다.
Button에 매핑한다.
[File] - [Build Settings]에서 씬을 추가해야 한다.
**맨 먼저 실행할 씬 부터 배치해야 한다.
ESC 버튼 만들기
UI 이미지 만들기
GameManager 스크립트에서 UI 패널 가져올 코드 만들기
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
//게임 매니저 가져와서 gm 변수 설정
public static GameManager gm;
private void Awake()
{
if(gm == null)
{
gm = this; //없으면 싱글톤 할당을 해서 적용한다.
}
}
//게임 상태 상수. Ready라는 상태를 모든 스크립트가 알아야함
public enum GameState
{
Ready,
Go,
Pause,
GameOver
}
//GameState 변수 선언. gState에 따라서 효과 적용
public GameState gState;
//UI 오브젝트 변수 생성
public GameObject gameLabel;
//게임 상태 텍스트 가져오기. 컴포넌트 변수는 Start에서 할당을 해주어야 함.
Text gameText;
//UI 패널 가져오기
public GameObject gameOption;
void Start()
{
//초기 상태를 준비로 설정
gState = GameState.Ready;
gameText = gameLabel.GetComponent<Text>();
gameText.text = "Ready";
StartCoroutine(ReadyToStart());
}
void Update()
{
}
IEnumerator ReadyToStart()
{
//2초 대기
yield return new WaitForSeconds(2f);
//Go
gameText.text = "Go!";
//대기
yield return new WaitForSeconds(0.5f);
//텍스트 비활성화
gameLabel.SetActive(false);
//상태 변경
gState = GameState.Go;
}
public void OpenOption()
{
gameOption.SetActive(true);
Time.timeScale = 0;
gState = GameState.Pause;
}
public void CloseOption()
{
gameOption.SetActive(false);
Time.timeScale = 1.0f;
gState = GameState.Go;
}
}
UI 이미지 만든 후 ButtonManager 스크립트 작성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ButtonManager : MonoBehaviour
{
public void RestartGame()
{
Time.timeScale = 1.0f;
//현재 열려있는 씬을 로드해라
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
public void QuitGame()
{
Application.Quit();
}
}
버튼 달아준 후 GameManager와 ButtonManager로 함수 만들기
GameManager에 OpenOption, CloseOption 함수 추가
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
//게임 매니저 가져와서 gm 변수 설정
public static GameManager gm;
private void Awake()
{
if(gm == null)
{
gm = this; //없으면 싱글톤 할당을 해서 적용한다.
}
}
//게임 상태 상수. Ready라는 상태를 모든 스크립트가 알아야함
public enum GameState
{
Ready,
Go,
Pause,
GameOver
}
//GameState 변수 선언. gState에 따라서 효과 적용
public GameState gState;
//UI 오브젝트 변수 생성
public GameObject gameLabel;
//게임 상태 텍스트 가져오기. 컴포넌트 변수는 Start에서 할당을 해주어야 함.
Text gameText;
//UI 패널 가져오기
public GameObject gameOption;
void Start()
{
//초기 상태를 준비로 설정
gState = GameState.Ready;
gameText = gameLabel.GetComponent<Text>();
gameText.text = "Ready";
StartCoroutine(ReadyToStart());
}
IEnumerator ReadyToStart()
{
//2초 대기
yield return new WaitForSeconds(2f);
//Go
gameText.text = "Go!";
//대기
yield return new WaitForSeconds(0.5f);
//텍스트 비활성화
gameLabel.SetActive(false);
//상태 변경
gState = GameState.Go;
}
public void OpenOption()
{
gameOption.SetActive(true);
Time.timeScale = 0;
gState = GameState.Pause;
}
public void CloseOption()
{
gameOption.SetActive(false);
Time.timeScale = 1.0f;
gState = GameState.Go;
}
}
ButtonManager 스크립트에 RestartGame, QuitGame 함수 추가
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ButtonManager : MonoBehaviour
{
public void RestartGame()
{
Time.timeScale = 1.0f;
//현재 열려있는 씬을 로드해라
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
public void QuitGame()
{
Application.Quit();
}
}
'SKKU DT' 카테고리의 다른 글
[SKKU DT] 15일차 -블렌더 컵 모델링, BSDF 설명 (0) | 2023.11.17 |
---|---|
[SKKU DT] 14일차 -블렌더 기본, 팁(Collection, Bevel, Modifier 정리) (0) | 2023.11.16 |
[SKKU DT] 13일차 -FPS 게임 만들기(5) -UI 데미지 입을 때, Ready,Go 텍스트 (0) | 2023.11.15 |
[SKKU DT] 12일차 -FPS 게임 만들기(4) -체력 바 UI 구현 (0) | 2023.11.14 |
[SKKU DT] 12일차 -FPS 게임 만들기(3) -적 만들기, 적 구현 (0) | 2023.11.14 |