2023. 8. 12. 13:35ㆍSWU_프로젝트/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밖에 학습하지 않아서 많이 낯설고 공부하는데 어려움이 많을까봐 걱정했었는데, 생각했던 것 보다는 수월하게 학습할 수 있었다.
다만, 오류가 너무 많이나서 게임만들면서 완성했던 내 게임들이 참 많이도 날아갔고 애를 먹었다.(*/ω\*)
그래도 유니티를 다뤄보고 게임제작을 학습한 경험은 잊지못할 추억이 될 것같다.
'SWU_프로젝트 > GURU - unity' 카테고리의 다른 글
guru_unity 해커톤 _ 게임 제작(마무리) (0) | 2023.08.03 |
---|---|
guru_unity 해커톤 _ 게임 제작(1) (0) | 2023.07.25 |
guru_unity 해커톤 _ 게임 기획서 제작(3) (0) | 2023.07.21 |
guru_unity 해커톤 _ 게임 기획서 제작(2) (0) | 2023.07.21 |
guru_unity 해커톤 _ 게임 기획서 제작(1) (0) | 2023.07.20 |