GURU_unity_4주차 FPS 게임 제작(7)

2023. 8. 12. 13:35SWU_프로젝트/GURU - unity

게임을 만드느라 기록하지못했던 마지막 차시 공부내용이다.

늦었지만 학습한걸 기록하려고 한다.

 

길찾기 (메시 링크)
스나이퍼 모드
무기 효과

 

 

길찾기 (메시 링크)

 

 

➡ 단절된 이동 영역을 건너가기

 

  • 내비게이션 이동 가능한 영역이 높이 차이 등의 이유로 인해 단절되어 있더라도 오프 메시 링크(Off Mesh Link) 기능을 이용해 임의로 연결 가능하다.
  • 오프 메시 링크 기능을 테스트하기 위해 임시로 장애물 오브젝트(큐브)를 생성한다.

 

 

  • 오르기 시작할 곳과 올라갈 곳, 내리기 시작할 곳과 내려올 곳에 각각의 빈 게임 오브젝트들을 배치한다.
  • 씬 뷰에서 위치를 편하게 볼 수 있도록 노란색 기즈모 아이콘도 설정한다.
  • 반드시 이동 가능 영역 안쪽에 빈 게임 오브젝트를 배치되도록 주의한다.

 

오르기 시작할 위치 오브젝트(UpStart)와 내려가기 시작할 위치 오브젝트(DownStart)에서 [Add Component – Navigation – Off Mesh Link]를 선택해 오프 메시 링크 컴포넌트를 추가

 

 

컴포넌트 항목 기능 설명
Start 이동이 시작되는 위치
End 이동이 끝나는 위치
Cost Overrid 거리 계산 비용 설정(음수일 때는 적용 안 함)
Bi Directional 양방향으로 이동 가능 여부를 체크
Activated 링크 기능 사용할지 여부를 체크
Auto Update Positions End 오브젝트의 위치가 변경되면 다시 내비게이션에 연결될지 여부를 체크
Navigation Area 내비게이션 설정의 Area 비용 설정

 

  • 오프 메시 링크 컴포넌트에 시작 오브젝트와 끝 오브젝트를 드래그해서 넣어준다.
  • 내비게이션 영역 항목을 Jump로 변경해 거리 계산 비용을 높게 변경해준다.

 

 

 

 

노드 비용 레이어 

  • 에이전트가 최단 거리를 계산할 때 이동하는 노드(Node)마다 비용이 존재하며 비용이 적을수록 더 가까운 거리로 계산한다. 
  • 비용 값은 내비게이션 뷰의 Area 탭에서 확인하거나 추가가 가능하다.

 

 

  • 오프 메시 링크를 포함하여 내비게이션 메시를 베이크하기 위해서 먼저 오프 메시 링크를 적용하려는 배경 오브젝트의 [Off Link Mesh Generation] 항목에 체크 표시한다.

 

  • 내비게이션 뷰의 [Bake] 탭에서 [베이크] 버튼을 클릭한다.
  • 유니티 에디터에서 플레이 해보면 장애물을 넘어오는 것이 최단 거리일 경우에는 에너미가 장애물을 넘어오는 것을 확인해준다.

 

 

스나이퍼 모드

목표 : 키보드 숫자 키 입력으로 무기 모드를 일반 모드와 스나이퍼 모드로 전환 하게 하고 싶다.

순서:
1. 무기 모드 변수 추가하기
2. 스나이퍼 모드 기능 추가하기
3. 키보드 입력에 따라 무기 모드를 변경하는 기능 추가하기

 

➡ 무기 모드 상태 추가하기

 

public class PlayerFire : MonoBehaviour
{
. . . (생략) . . .
// 무기 모드 변수
enum WeaponMode
{
Normal,
Sniper
}
WeaponMode wMode;
// 카메라 확대 확인용 변수
bool ZoomMode = false;
void Start()
{
. . . (생략) . . .
// 무기 기본 모드를 노멀 모드로 설정한다.
wMode = WeaponMode.Normal;
}
. . . (생략) . . .
}
  • PlayerFire.cs 스크립트에 무기 모드 변수를 전역 변수로 추가한다.
  • Start( ) 함수에서 최초의 무기 모드를 일반 모드로 지정한다.
  • 카메라 줌인 아웃 체크를 위한 변수를 선언(스나이퍼 모드)한다.
public class PlayerFire : MonoBehaviour
{
. . . (생략) . . .
void Update()
{
// 게임 상태가 ‘게임 중’ 상태일 때만 조작 가능하게 한다.
if (GameManager.gm.gState != GameManager.GameState.Run)
{
return;
}
// 노멀 모드: 마우스 오른쪽 버튼을 누르면 시선 방향으로 수류탄을 던지고 싶다.
// 스나이퍼 모드: 마우스 오른쪽 버튼을 누르면 화면을 확대하고 싶다.
. . . (생략) . . .
}
. . . (생략) . . .
}

플레이어 무기를 조작 가능한 상태에서 무기 모드에 따라 마우스 우측 버튼의 실행 내용이 변경되도록 설계한다.

 

void Update()
{
. . . (생략) . . .
// 마우스 오른쪽 버튼 입력을 받는다.
if (Input.GetMouseButtonDown(1))
{
switch (wMode)
{
case WeaponMode.Normal:
// 수류탄 오브젝트를 생성하고, 수류탄의 생성 위치를 발사 위치로 한다.
GameObject bomb = Instantiate(bombFactory);
bomb.transform.position = firePosition.transform.position;
// 수류탄 오브젝트의 리지드보디 컴포넌트를 가져온다.
Rigidbody rb = bomb.GetComponent<Rigidbody>();
// 카메라의 정면 방향으로 수류탄에 물리적인 힘을 가한다.
rb.AddForce(Camera.main.transform.forward * throwPower, ForceMode.Impulse);
break;
case WeaponMode.Sniper:
break;
}
. . . (생략) . . .
}

일반 모드에서는 기존의 수류탄 투척 방식을 그대로 유지한다.

 

 

 

 

  • 게임 뷰의 해상도는 고정된 채로 카메라의 시야각을 조정하면 화면이 확대 또는 축소된다.
  • 카메라의 시야각은 카메라 컴포넌트의 Field of View(FOV) 항목에서 조절 가능하다.
void Update()
{
. . . (생략) . . .
switch (wMode)
{
. . . (생략) . . . 
case WeaponMode.Sniper:
// 만일, 줌 모드 상태가 아니라면 카메라를 확대하고 줌 모드 상태로 변경한다.
if (!ZoomMode)
{
Camera.main.fieldOfView = 15f;
ZoomMode = true;
}
// 그렇지 않으면 카메라를 원래 상태로 되돌리고 줌 모드 상태를 해제한다.
else
{
Camera.main.fieldOfView = 60f;
ZoomMode = false;
}
break;
}
. . . (생략) . . .
}
  • 스나이퍼 모드에서는 마우스 우측 버튼을 클릭할 때마다 카메라의 시야각을 변경한다.
void Update()
{
. . . (생략) . . .
// 만일 키보드의 숫자 1번 입력을 받으면, 무기 모드를 일반 모드로 변경한다.
if (Input.GetKeyDown(KeyCode.Alpha1))
{
wMode = WeaponMode.Normal;
// 카메라의 화면을 다시 원래대로 돌려준다.
Camera.main.fieldOfView = 60f;
}
// 만일 키보드의 숫자 2번 입력을 받으면, 무기 모드를 스나이퍼 모드로 변경한다.
else if (Input.GetKeyDown(KeyCode.Alpha2))
{
wMode = WeaponMode.Sniper;
}
}
  • Input 클래스의 GetKeyDown() 함수를 이용하여 사용자의 키보드 입력 여부를 확인 가능 
  • 숫자 1번과 2번 키를 이용하여 무기 모드를 변경 가능하다.
  • 스나이퍼 모드에서 일반 모드로 변경 시 줌인 상태를 해제

 

➡ 무기 모드 상태 텍스트 출력하기

 

 

  • 플레이어의 hp 슬라이더가 있는 캔버스에서 마우스 오른쪽 버튼을 클릭한 후 [Create – UI –Text]를 선택하여 새로운 텍스트 UI를 생성한다. 
  • 텍스트 UI의 이름을 ‘Text_WeaponMode’로 변경한다.
  • 텍스트 UI는 hp 슬라이더의 좌측 상단에 위치시킨다.

 

  • 폰트 크기는 20에 굵은(Bold) 폰트로 지정
  • 폰트의 위치는 중앙 정렬 
  • 폰트 색상은 검은색(R: 0, G: 0, B: 0, A: 255)으로 변경
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerFire : MonoBehaviour
{
. . . (생략) . . .
// 무기 모드 텍스트
public Text wModeText;
. . . (생략) . . .
}
  • UnityEngine.UI 네임 스페이스 추가
  • 무기 모드 글자를 출력할 Text 변수를 public 변수로 추가
void Update()
{
. . . (생략) . . .
// 만일 키보드의 숫자 1번 입력을 받으면, 무기 모드를 일반 모드로 변경한다.
if (Input.GetKeyDown(KeyCode.Alpha1))
{
wMode = WeaponMode.Normal;
// 카메라의 화면을 다시 원래대로 돌려준다.
Camera.main.fieldOfView = 60f;
// 일반 모드 텍스트 출력
wModeText.text = "Normal Mode";
}
// 만일 키보드의 숫자 2번 입력을 받으면, 무기 모드를 스나이퍼 모드로 변경한다.
else if (Input.GetKeyDown(KeyCode.Alpha2))
{
wMode = WeaponMode.Sniper;
// 스나이퍼 모드 텍스트 출력
wModeText.text = "Sniper Mode";
}
}

사용자의 키 입력을 받아 모드 변경을 하는 부분에 ‘Normal Mode’와 ‘Sniper Mode’ 문자열을 출력하는 코드를 추가

 

유니티 에디터에서 플레이하면서 무기 모드 전환 여부 확인한다.

 

 

무기 효과

목표 : 총을 쏠 때마다 총구에 불꽃이 튀는 효과를 주고 싶다.

순서:
1.. 사격 이펙트 리소스를 총구의 끝부분에 위치시키기
2. 총을 쏠 때마다 사격 이펙트를 활성화하기
3. 일정한 시간이 경과되면 사격 이펙트를 다시 비활성화하기

 

➡ 총기 효과 에셋 가져오기

 

 

  • 에셋 스토어의 검색 창에 ‘FPS’를 입력 
  • [Pricing] 탭의 [Free Assets] 항목을 체크 
  • 검색 결과 화면에서 첫 번째 줄에 있는 Easy FPS 에셋을 다운로드 및 임포트

 

 

  • [ MuzzelFlash 폴더 - MuzzlePrefabs 폴더]에 있는 5개의 프리팹을 하이어라키 뷰로 드래그해서 넣어준다.
  • 프리팹에서 스크립트 컴포넌트를 제거(Remove Component) 한다.

 

  • Muzzle Position’이라는 이름으로 빈 게임 오브젝트를 만들어 손가락 위치 오브젝트의 자식 오브젝트로 등록
  • 유니티를 플레이하고 있는 상태(run time)에서 Muzzle Position 오브젝트를 총구 끝에 배치
  • 총구 위치 값을 복사한 뒤 런 타임을 종료한 상태에서 다시 붙여넣기

  • 사격 이펙트 오브젝트들을 Muzzle Position 오브젝트의 자식 오브젝트로 등록 
  • 사격 이펙트 오브젝트의 위치 값과 회전 값을 부모 오브젝트와 동일한 위치가 되도록 0으로 초기화 
  • 이펙트 오브젝트의 스케일 값을 0.4로 축소

➡ 총기 효과 기능 구현

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerFire : MonoBehaviour
{
. . . (생략) . . .
// 총 발사 효과 오브젝트 배열
public GameObject[] eff_Flash;
. . . (생략) . . .
}

여러 개의 이펙트 오브젝트를 모아 담을 수 있도록 배열 변수를 선언한다.

 

void Update()
{
. . . (생략) . . .
// 마우스 왼쪽 버튼을 입력받는다.
if (Input.GetMouseButtonDown(0))
{
. . . (생략) . . .
// 총 이펙트를 실시한다.
StartCoroutine(ShootEffectOn(0.05f));
}
. . . (생략) . . .
}
// 총구 이펙트 코루틴 함수
IEnumerator ShootEffectOn(float duration)
{
// 랜덤하게 숫자를 뽑는다.
int num = Random.Range(0, eff_Flash.Length - 1);
// 이펙트 오브젝트 배열에서 뽑힌 숫자에 해당하는 이펙트 오브젝트를 활성화한다.
eff_Flash[num].SetActive(true);
// 지정한 시간만큼 기다린다.
yield return new WaitForSeconds(duration);
// 이펙트 오브젝트를 다시 비활성화한다.
eff_Flash[num].SetActive(false);
  • 사격 이펙트는 발사하는 순간에만 잠깐 보였다가 일정 시간이 흐른 후에 사라지도록 구현 
  • 시간 체크를 편하게 할 수 있도록 코루틴 함수로 구현

 

  • 배열 변수 항목에 이펙트 오브젝트 5개를 모두 추가 
  • 이펙트 오브젝트들은 총을 발사할 때에만 보이므로 전부 비활성화 상태로 변경한다.

유니티 에디터에서 플레이하면서 총구 이펙트(Muzzle flash) 효과를 확인한다.

 

 

  • 에셋 스토어의 검색 창에 ‘Grenade’를 입력하고 [Pricing] 탭의 Free Assets 항목을 체크한다.
  • 검색 결과 화면에서 첫 번째 줄에 있는 Grenade DM51A1 에셋을 다운로드 및 임포트한다.

  • 수류탄 프리팹 파일을 더블 클릭하여 프리팹 설정 화면으로 전환한다.
  • 에셋 스토어에서 임포트한 수류탄 모델링 프리팹 파일을 드래그해서 넣어준다.

  • 기존의 스피어 오브젝트 모습이 보이지 않도록 Mesh Renderer 컴포넌트를 비활성화한다.
  • 수류탄의 스케일을 2배로 확대한다.

  • 충돌 영역(Sphere Collider)의 Radius 항목을 ‘0.5’에서 ‘0.2’로 축소
  • 충돌 영역이 수류탄보다는 살짝 큰 편이지만 충돌 역역이 너무 작으면 폭발 이펙트가 배경에 묻힐 수 있으므로 수류탄 모델링보다 좀 더 크게 설정한다.

 

  • Bomb 프리팹을 선택한 후 인스펙터 뷰 우측 상단의 [Override] 항목을 선택하고 [Apply All] 버튼을 클릭하여 저장한다.
  • 하이어라키 뷰에 있는 수류탄 프리팹은 [Delete] 키를 눌러 삭제한다.

유니티 에디터에서 플레이하면서 수류탄 모델링과 폭발 효과를 확인한다.

 

 

➡ 수류탄 폭발 데미지 기능 구현

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BombAction : MonoBehaviour
{
// 폭발 이펙트 프리팹 변수
public GameObject bombEffect;
// 수류탄 데미지
public int attackPower = 10;
// 폭발 효과 반경
public float explosionRadius = 5f;
// 충돌했을 때의 처리
private void OnCollisionEnter(Collision collision)
{
. . . (생략) . . .
}
}

수류탄의 폭발 효과를 주기 위해 폭발 데미지와 폭발 범위를 지정할 수 있는 public 변수를 선언한다.

 

  • 폭발 범위 안에 있는 모든 대상에 대해 충돌 처리를 위해 Physics 클래스에 있는 OverlapSphere( ) 함수를 사용 
  • 오버랩 함수는 호출하는 그 프레임 1회에 특정 영역 안에 있는 모든 게임 오브젝트의 콜라이더 컴포넌트들을 모두 모아 배열로 반환하는 함수
  • 영역의 형태는 구 형태(Sphere), 큐브 형태(Box), 캡슐 형태(Capsule)의 세 가지 형태가 있으며, 수류탄의 폭발 형태에 맞게 구 형태의 오버랩 함수를 사용
. . . (생략) . . .
// 충돌했을 때의 처리
private void OnCollisionEnter(Collision collision)
{
// 폭발 효과 반경 내에서 레이어가 ‘Enemy’인 모든 게임 오브젝트들의 Collider 컴포넌트를 배열에 저장한다.
Collider[] cols = Physics.OverlapSphere(transform.position, explosionRadius, 1 << 10);
// 이펙트 프리팹을 생성한다.
GameObject eff = Instantiate(bombEffect);
// 이펙트 프리팹의 위치는 수류탄 오브젝트 자신의 위치와 동일하다.
eff.transform.position = transform.position;
// 자기 자신을 제거한다.
Destroy(gameObject);
}

 

  • 수류탄의 폭발 효과를 주기 위해 폭발 데미지와 폭발 범위를 지정할 수 있는 public 변수를 선언한다.
  • 첫 번째 비트에서부터 ‘Enemy’ 레이어 번호(10번 레이어)만큼을 좌측으로 이동했을 때 선택된 비트만 검출
. . . (생략) . . .
// 충돌했을 때의 처리
private void OnCollisionEnter(Collision collision)
{
// 폭발 효과 반경 내에서 레이어가 ‘Enemy’인 모든 게임 오브젝트들의 Collider 컴포넌트를 배열에 저장한다.
Collider[] cols = Physics.OverlapSphere(transform.position, explosionRadius, 1 << 10);
// 저장된 Collider 배열에 있는 모든 에너미에게 수류탄 데미지를 적용한다.
for (int i = 0; i < cols.Length; i++)
{
cols[i].GetComponent<EnemyFSM>().HitEnemy(attackPower);
}
. . . (생략) . . .
}

검출된 Collider 배열을 순회하면서 해당 에너미에게 수류탄 데미지를 부여한다.

 

 

  • 에너미 오브젝트를 프로젝트 뷰의 Prefabs 폴더로 드래그해 프리팹으로 저장한다.
  • 여러 개의 에너미 프리팹을 추가로 씬에 배치한다.
  • 에너미들이 수류탄의 폭발 범위에 모두 들어올 수 있게 배치하낟.
  • 게임 플레이에서 에너미가 모인 곳에 수류탄을 던져서 폭발 영역 내의 모든 에너미들에게 동시에 데미지가 적용되는 것을 확인한다.

 

 

이제 드디어 수업 끝! 

 

처음으로 C#과 유니티라는 것을 배우고 다뤄보면서 c랑 python밖에 학습하지 않아서 많이 낯설고 공부하는데 어려움이 많을까봐 걱정했었는데, 생각했던 것 보다는 수월하게 학습할 수 있었다.

다만, 오류가 너무 많이나서 게임만들면서 완성했던 내 게임들이 참 많이도 날아갔고 애를 먹었다.(*/ω\*)

 

그래도 유니티를 다뤄보고 게임제작을 학습한 경험은 잊지못할 추억이 될 것같다.