유니티는 게임 엔진이다. 그래픽, 물리, 오디오 등을 개발자가 하나하나 구현할 필요 없이 엔진이 처리해준다. 다양한 툴을 이용하여 게임 개발을 쉽게 할 수 있게 해준다!
화면 하단의 Project
를 보면 Assets
와 그 하위 Scenses, Settings
만 있음.
나머지 폴더를 설명하자면
Prefabs
: 아직 안써봐서 모르겠음!Scripts
: C#으로 작성된 로직 파일들을 여따 둔다Sprites
: 게임에서 사용되는 이미지 파일들여기서 Sprites
라는 단어를 어디서 본 건가 했더니, CSS의 이미지 최적화 기법중 하나였다.
실제로 이번 게임 제작에 쓰는 이미지들도 아래와 같다.
다량의 이미지를 한번에 넣고, 잘라서 쓰는 방식이다. http통신 횟수를 줄이기 위해 최적화 했었는데, 게임에서는 GPU의 텍스쳐 스위칭 작업을 최적화 하기 위해 사용한다고 함.
=> 각 이미지가 다른 파일일 경우, 교체할때 GPU를 소모하게됨
일맥상통하구먼?
현재 게임 내 배경화면은 이미지 하나를 복사하여 세로로 이어붙였다.
만약 플레이어 캐릭터가 위로 이동할 경우, 배경화면을 아래로 보내는 작업이 필요하다.
위와같은 상황은 스크립트를 활용하여 해결할 수 있다.
일단 유니티의 C# 클래스명등은 아예 모른다고 가정하고, 의사코드를 작성해보자.
1. 캐릭터가 수직으로 움직이는 것이 아닌, 배경이 수직으로 움직인다.
2. 각 배경화면의 y값이 줄어든다.
2-1. 만약 배경화면의 y값이 특정 값(화면 하단 끝쯤)보다 작아질 경우, 배경화면의 y값을 화면 상단y값으로 맞춘다.
3. 위 함수를 각 프레임(업데이트)시 마다 적용한다.
한 번 스크립트를 살펴보자.
using UnityEngine;
public class Background : MonoBehaviour
{
void Start(){
}
// Update is called once per frame
void Update()
{
}
}
Start
함수는 게임이 시작하자마자 한 번 발동하는 함수다. Update
는 각 프레임마다 적용되는 함수다.
이 두 메서드는 유니티 스크립트를 켜면 기본으로 존재한다.(약속된 네이밍임)
그러면 Start
메서드는 삭제하고, Update
메서드로 처리를 해보자.
public class Background : MonoBehaviour
{
private float moveSpeed = 3f;
void Update()
{
transform.position += Vector3.down * moveSpeed * Time.deltaTime;
if (transform.position.y < -10)
{
transform.position += new Vector3(0, 20f, 0);
}
}
}
먼저 화면이 얼만큼 속도로 내려갈지 상수moveSpeed
를 정해둔다. 이 값은 외부에서 사용할 일이 없어보이므로 은닉화하였다.
그 다음, 현재 화면의 좌표를 바꿔준다. 이때 사용되는 값이 transform.position
이다.
유니티에서 우측 Inspector
에 표기되는 값과 일치한다.
Vector3.down
은 공식문서를 찾아보면 Vector3(0, -1, 0)
를 축약한 값이라고 나온다.
따라서 프레임당 y축으로 -1 * 3(moveSpeed) 만큼 이동하게된다.
하지만 강의와 공식문서에서는 Time.deltaTime
이라는 값을 또 곱해준다.
이 값은 프레임간 렌더링시간을 의미한다. 어째서 이 값을 곱해줄까?
예를들어 1Frame당 1cm를 이동하는 로직이 존재한다고 가정해보자.
일반적인 가정용 모니터에서는 초당60frame이 움직이므로, 초당 60cm가 움직이게된다.
하지만 게이밍 모니터는 초당 144frame을 가볍게 넘는다. 따라서 초당 144cm이상 움직이게 된다.
이 둘을 똑같은 값으로 보정하기 위하여 각 프레임간 렌더링 시간을 곱하게된다.
프레임당 1cm 이동로직.
60프레임*1cm = 60cm/s
144프레임*1cm = 144cm/s
위 두 값에 프레임 간 렌더링 시간을 곱해준다.
60cm/s * 1/60 = 1cm/s
144cm/s * 1/144 = 1cm/s
비로소 같은 값이 되었다.
그런데, 좌표를 활용하기 위해 우리는 두가지 값을 사용했다.
transform.position
과 Vector3(...)
.
전자는 현재 좌표값인 걸 알겠고, Vector3
는 Vscode에서 초록색으로 표시되니 클래스따위의 무언가임을 짐작할 수 있다.
실제 로직과 유니티 공식문서
클래스는 아니고 struct(구조체)였다.
이런, 구조체는 분명 C언어에서만 사용하는 줄 알았는데 왜 C#에도 있을까?🤔
구조체와 클래스의 차이를 보면 알 수 있다.
일단 Vector3
가 구조체이며 메모리관리에 용이하다는 것을 알게되었다.
써본적이 없으니 구조체를 사용할 일이 있으면 적극적으로 사용해보자!
유니티에서는 애니메이션도 쉽게 지원해준다. 애니메이션은 연속된 스냅샷의 집합이다.
따라서 여러 sprite를 이어붙이면 애니메이션이 된다.
위 네개 이미지를 이어붙여 애니메이션을 만들었다. 헌데, 깜빡임 문제가 발생한다.
여러 사진은 문제없이 애니메이션화 되었다. 레이어 간 순서 문제였다.
Inspector내부 Sprite Renderer에서 order layer값을 높게 주어 해결하였다.
키보드로 캐릭터를 움직이고싶다면, 먼저 키보드의 입력값을 받아와야한다.
어떻게 받을까? 아마 js에서 하던대로 어떠한 Key의 Code를 감지할 것 같다.
받아온 키 값을 토대로 방향을 판별한 뒤, 속력을 넣어 속도(방향+속력)를 만들면 될 것 같다.
현재 강의에서는 딱히 가속은 없다. 그렇다면 일정한 속도를 가지게 하는 상수를 넣어 만들면 될것 같다.
public class Player : MonoBehaviour
{
[SerializeField]
private float moveSpeed;
void MoveFromKeyboard()
{
Vector3 moveTo = new Vector3(moveSpeed * Time.deltaTime, 0, 0);
if (Input.GetKey(KeyCode.LeftArrow))
{
transform.position -= moveTo;
}
else if (Input.GetKey(KeyCode.RightArrow))
{
transform.position += moveTo;
}
}
void Update()
{
MoveFromKeyboard();
}
}
[SerializeField]
라는 값을 제외하면, 문제없이 이해 가능한 코드다.
위 코드는 어떤 의미로 사용되는걸까?
공식문서에 따르면 다음과 같다.
private
으로 비공개로 만든 필드를 직렬화해준다. 직렬화는 데이터를 네트워크나 저장소를 통한 전송에 적합한 형식으로 변환하는 프로세스다.
이렇게 말하면 감이 와닿지 않는다.
현재 직렬화한 필드는 moveSpeed
다. 직렬화된 필드는 유니티의 Inpsector
에서 실시간으로 조정할수 있게 만들어준다
원래 존재하지 않던 Move Speed라는 값이 생겼다. 이 값을 조정하면, 플레이어의 속력이 바뀐다.
즉 유니티에서 직렬화란, 좁게말하여 Inspector에서 실시간으로 조정 가능하게 만든다는 의미이다.
더 큰 뜻을 가지고 있겠지만, 일단 그렇게 이해하고 넘어가겠음!
현재 키보드로 캐릭터 이동을 할수있다. 다만 맵을 넘어간다.
이를 막으려면, 어떻게 해야할까? 먼저 의사코드로 적어보자.
1. 현재 캐릭터의 가장자리 좌표를 얻어낸다.
2. 배경화면의 가장자리 좌표를 얻어낸다.
3. 만약, 현재 캐릭터 가장자리 X좌표의 최솟값이 배경화면의 가장자리 X좌표의 최솟값보다 작다면,
현재 캐릭터 가장자리 X좌표 최솟값을 배경화면 가장자리 X좌표 최솟값으로 맞춘다.
4. 만약, 현재 캐릭터 가장자리 X좌표의 최댓값이 배경화면 가장자리 X좌표의 최댓값보다 크다면,
현재 캐릭터 가장자리 X좌표 최댓값을 배경화면 가장자리 X좌표 최댓값으로 맞춘다.
말이 조금 길지만, 구현해볼 수 있다.
다만 여기서는 보다 쉽게 컴포넌트를 이용하여 구현하겠다!
컴포넌트란 재사용 가능한 독립된 모듈이다. 웹 개발할때 지겹게 들었던 그 컴포넌트다.
게임을 개발할때도 컴포넌트를 이용하면 불필요한 코드와 시간낭비를 획기적으로 줄일 수 있다.
유니티또한 컴포넌트 기능을 지원한다.
우측 Inspector
메뉴내 컴포넌트를 추가하는 버튼이 존재한다.
당장 필요한 컴포넌트는 하나(충돌감지)지만, 게임답게 찰진 움직임을 구현해보자.
세세한 2D물리엔진을 구현해주는 컴포넌트다.
기본적으로 중력Gravity Scale
이 1로 적용되어있으므로, 0으로 없애버렸다.
나머지 옵션들은 나중에 본격적으로 살펴보고 Body Type
을 Dynamic
으로 설정해주면 된다.
이는 모든 물리현상, 동역학을 받겠다는 의미다. (중력, 충돌, 힘 등 물리적 현상)
오브젝트간 물리적 충돌을 위한 모양을 정의해준다. 현재 캐릭터의 머리부분에만 적용시킬거라, 원형 콜라이더를 선택했다.
위처럼 머리 부분에 씌워주면 충돌감지가 적용된다.
그다음 화면 양쪽 벽에 충돌감지를 위한 오브젝트를 설정해준다. 사각 콜라이더를 선택했다.
양 옆을 넘어가지 않게 되었다.
게임 제작 아이디어 및 코드 출처 : https://www.youtube.com/watch?v=rJE6bhVUNhk&t=2125s