이번 주 주간 과제로 드디어 게임을 제작하기 시작했다.
간단한 '런 게임'을 만드는 것이었지만, 목요일까지도 완벽하게는 만들지 못했다.
그래도 게임을 만들면서 구현하고 싶은 것들이 생길 때마다 새로 배웠던 것들을 가져다 쓰면서 저번에 배웠던 내용들이 왜 쓰는지에 대한 이해가 확실히 되었다.
이번 학습 일지에서는 월, 화, 수 열심히 '런 게임'을 만들면서 느꼈던 내용들과 목, 금 새로 배운 것 중에 와닿았던 내용들을 추려서 따로 정리해보려고 한다.
Starters 6주차 주간 과제 - '런 게임 만들기'
BoxCast와 콜라이더
이번에 게임을 만들면서 나뿐만 아니라 대부분의 사람들이 겪었던 문제가 하나 있었는데, 움직이는 물체의 벽면에 캐릭터가 부딪혔을 때에 캐릭터가 떨어지지 않고, 끼어버리는 문제였다.
처음에 이동시에 velocity를 주면서 y의 값을 0으로 주어서 뛰면서 이동할 때에 공중에서 걸어 다니는 등의 버그가 발생했는데, 위의 버그도 이것 때문에 일어난 버그 중 하나라고 생각했었고, velocity의 x만 더하는 것으로 수정했지만 위의 문제는 해결되지 않았다.
후에 알고보니 끼였다기보다는 마찰력이 작용해서 떨어지지 않는 현상이었다.
그렇다고 블럭의 마찰력을 0으로 하면 바닥을 밟고 있을 때에도 캐릭터가 무한정 미끄러져 버리는 현상이 일어났고, 머리가 지끈거리기 시작했다.
나는 결국에는 콜라이더 외에 BoxCast를 이용해서 바닥 부분에서 닿은 것인지 판단할 수 있는 장치를 하나 더 만들었다.
그리고, 캐릭터의 앞과 뒤로도 BoxCast를 쏘아서 해당 오브젝트가 발에 안 닿고 앞이나 뒤에 부딪혔다면, 벽에 맞은 것으로 처리했다.
void OnRayEnter()
{
int layerMask = 1 << LayerMask.NameToLayer("Ground") | LayerMask.NameToLayer("Floor");
// 캐릭터의 바닥 부분을 전체적으로 검사하는 hit, 앞부분과 뒤부분을 검사하는 hitF, hitB
RaycastHit2D hit = Physics2D.BoxCast(new Vector2(transform.position.x, transform.position.y - transform.lossyScale.y), new Vector2(transform.lossyScale.x - 0.2f, 0.2f), 0f, Vector2.down, detect, layerMask);
RaycastHit2D hitF = Physics2D.BoxCast(new Vector2(transform.position.x + transform.lossyScale.x * 0.5f, transform.position.y - transform.lossyScale.y * 0.5f), new Vector2(0.2f, transform.lossyScale.y - 0.2f), 0f, Vector2.right, detect, layerMask);
RaycastHit2D hitB = Physics2D.BoxCast(new Vector2(transform.position.x - transform.lossyScale.x * 0.3f, transform.position.y - transform.lossyScale.y * 0.5f), new Vector2(transform.lossyScale.x, transform.lossyScale.y), 0f, Vector2.left, detect, layerMask);
// 무언가에 맞았을 때에
if (!isDead && hit.collider != null)
{
Debug.Log("Hit");
// 바닥 부분이 땅바닥에 닿았다면
if (hit.collider.CompareTag("Ground"))
{
Debug.Log("isGround");
isJump = false;
states = States.run;
isRay = true;
}
// 8(땅 아래에 캐릭터에 떨어진 것을 검출하는 오브젝트)에 부딪히면
else if (hit.collider.gameObject.layer == 8)
{
Debug.Log("isFloor");
GetComponent<PolygonCollider2D>().enabled = true;
isRespawn = true;
}
// 적에게 맞았고, 이미 맞았던 상황이 아니라면
else if (hit.collider.gameObject.layer == 7 && !isHurt)
{
Debug.Log("isEnemy");
HeartManager.instance.HPDown();
states = States.hurt;
isHurt = true;
}
// 그 두개의 오브젝트가 아니면
else
{
isRay = false;
}
}
// 바닥은 부딪힌 것이 없고, 앞에서 부딪힌 경우
else if (!isHurt && hitF.collider != null)
{
// 부딪힌 것이 땅이라면, 벽에 맞은 것으로 처리하고, 콜라이더를 끔
if (hitF.collider.transform.CompareTag("Ground"))
{
Debug.Log("isWall");
GetComponent<PolygonCollider2D>().enabled = false;
}
}
// 바닥은 부딪힌 것이 없고, 뒤에서 부딪힌 경우
else if (hitB.collider != null)
{
// 부딪힌 것이 땅이라면, 벽에 맞은 것으로 처리하고, 콜라이더를 끔
if (hitB.collider.transform.CompareTag("Ground"))
{
Debug.Log("isWall");
GetComponent<PolygonCollider2D>().enabled = false;
}
}
// 아무것도 맞은 것이 없다면
else
{
isRay = false;
}
}
다행히(?) 벽에 대한 처리를 따로 받을 수 있었지만, 안타깝게도 가끔 땅이나, 적 오브젝트와 부딪혀도 (속도가 너무 빠르거나 해서) 벽으로 처리하고 콜라이더가 꺼진다거나 하는 또 다른 버그가 생겨버렸다..
그리고, 콜라이더가 해제된 상태에서는 아래 구덩이에 떨어진 것을 확인하기 위해 만들어 놓은 Floor 오브젝트의 충돌까지 무시해버려서 캐릭터가 한번 벽에 부딪히면, 영원히 지하 깊은 곳으로 떨어지게 되었다..
후에 강사님의 설명을 들어보니, 내가 접근한 방법이 틀리지는 않았지만, 더 간단하게 벽 충돌 처리를 하는 방법이 있다는 것을 알게 되었다.
일단 콜라이더나 Ray(Box)Cast에는 충돌 시에 contact를 생성하게 되는데 여기에 부딪힌 지점, 방향 등의 값이 들어있고 이를 활용하면 되었다.
먼저 contact[0].normal을 통해서 캐릭터가 처음으로 부딪힌 방향을 가져오고 Vector.up(위) 각도와의 차이로 위에서 접근한 것인지 벽에서 접근한 것인지, 아니면 머리로 부딪힌 것인지 알 수 있었다.
위의 경우라면, 검은색은 기준인 빨간 선과의 각도가 60도 이상(그림을 대충 그렸는데, 60도 이상이라고 하자)이므로 벽면에서 맞는 것으로 처리할 수 있다. (참고로 60도라는 기준은 때에 따라 조금씩 다르게 설정할 수 있다.)
반면, 주황색으로 충돌한 경우에는 차이가 180도이므로 아래서 위로 부딪힌 것이라는 것을 알 수 있게 된다.
다른 방법으로 Effector를 쓰는 방법도 설명해주셨다.
Effector를 사용하면 바닥 외에 옆에 대한 마찰력, 탄성을 다르게 설정할 수 있다.
위의 사진처럼 설정하면, 벽면에 대한 마찰과 탄성은 꺼버린다. 즉, 벽면에 충돌한 캐릭터는 미끄러지지만, 바닥에 착지한 캐릭터는 정상적으로 마찰력을 적용받게 된다.
간단해 보이지만, 단점도 있었다.
일단, 이번 과제에서 캐릭터의 콜라이더가 Polygon Collider여서 캐릭터의 side를 제대로 찾지 못했고, 벽에다 이 컴포넌트를 해도 모자 등에 걸리는 것은 잘 안 미끄러지거나 하기도 했다.
그리고, 3D로 만든 경우에는 아예 Effector 컴포넌트를 등록할 수 없다는 단점도 있었다.
그래도 2D 게임을 만드는데 Effector 컴포넌트는 위와 같이 벽에 대한 처리를 달리 하거나, 부력을 만들거나, 다른 여러 효과를 주는데 아주 유용하게 쓰이는 컴포넌트라는 것을 알게 되었다.
BoxCast를 대신해서 OverlapBox를 쓰자
우리가 위와 같이 캐릭터의 바닥 충돌 여부를 알기 위해 BoxCast를 사용하는 경우 사실 OverlapBox로 구현하는 것이 좋다는 것을 알게 되었다.
먼저 두 개의 차이는 다음과 같다.
위 사진에서 빨간색은 BoxCast의 Ray 범위를 보여주고 있고, 파란색은 OverlapBox의 Ray 범위를 보여준다. (선명한 부분은 오브젝트이고, 흐릿한 부분이 Ray를 쏘는 범위이다.)
잘 보면, BoxCast는 박스로 된 Ray가 방향대로 계속 움직이면서 충돌 여부를 파악한다.
반면에, OverlapBox의 경우에는 범위에 Box 형태의 Ray를 생성하면 그 부분만 충돌 여부를 파악하고, 방향을 설정해서 발사되거나 하지는 않는다.
즉, 캐릭터의 아래부분의 충돌만 확인하고 싶은 경우라면, 굳이 BoxCast를 써서 불필요한 메모리 낭비를 할 필요가 없다.
Starters 6주차 수업 - '유니티 툴 다루기 + UI'
6주 차 역시 저번 주의 연속으로 유니티 툴에 대하여 살펴보는 시간을 가졌다.
다만, 이번에는 UI 그리고, 게임, 메타버스에서 자주 쓰게 될 디자인 패턴에 대해서도 몇 가지 배웠다.
그중에서도 일단은 다음과 같은 내용들이 가장 나에게 와닿은 내용들이었다.
싱글톤 디자인 패턴
싱글톤은 게임에서 정말 많이 쓰는 디자인 패턴이라고 한다.
이전에 강의에서 우리는 Static Class 즉, 정적 클래스에 대해 배운 적이 있었다.
정적 클래스는 메모리 상에 단일로 존재하는 클래스로 주로 Helper나 계산을 주로 행하는 클래스에 쓰인다고 들었다.
이 싱글톤 클래스 역시 정적 클래스와 비슷한 역할을 수행하게 된다고 하셨다.
다만, 싱글톤은 Manager 클래스에서 사용하게 된다.
1. 게임 내의 설정 값(config)을 바꾸는 클래스
2. 게임 상에서 공통으로 사용되는 함수들을 관리하는 클래스
그렇다면, 왜 정적 클래스를 내버려두고, 싱글톤을 사용하는 것일까?
그건 두 클래스의 차이점을 보면 알 수 있다.
내부 변수, 메서드 | MonoBehaviour 상속 | |
Static Class | 모든 내부 필드들은 static(정적)으로 선언되어야 함 | 상속받을 수 없음 → 오브젝트에 해당 클래스를 붙일 수 없음 |
Singleton Class | 꼭 static일 필요 없음 | 상속받을 수 있음 |
일단 정적 클래스는 당연히 클래스 자체가 static으로 선언되어서, 그 안에 내용도 모두 static일 필요가 있다.
하지만, 싱글톤 클래스는 클래스 자체를 static으로 선언하지 않는다.
다만, instance라는 변수를 static으로 선언하고, 여기에 해당 함수를 담아서 사용할 수 있도록 한다.
그렇기 때문에 해당 함수나 코드를 필요시에 (그럴 일이 많을 것 같지는 않지만) 에디터나 다른 스크립트에서 수정할 수도 있게 만들 수 있고, 무엇보다 monoBehaviour를 상속받기 때문에 하이어라키의 오브젝트에 붙일 수 있게 된다.
public class Publisher : MonoBehaviour
{
private static Publisher _instance;
public static Publisher instance
{
get
{
if (_instance == null)
{
return null;
}
return _instance;
}
}
싱글톤을 실제로 만들 때에는 위와 같이 코드를 짜게 된다.
다만, 주의할 것은 싱글톤 클래스 역시 Static 클래스와 마찬가지로 단일로 존재할 필요가 있는데, Static 클래스와 다르게 다중 생성을 막을 코드를 짜지 않으면 다중 생성되어지는 경우가 생긴다. (이럴 경우, 무결성의 원칙에 어긋날 수 있다.)
private void Awake()
{
if (_instance == null)
{
GameObject obj = GameObject.Find("Publisher");
if (obj == null)
{
obj = new GameObject("Publisher");
_instance = obj.AddComponent<Publisher>();
}
DontDestroyOnLoad(obj);
}
}
그래서 이런 식으로 해당 클래스의 유무를 파악하고, null인 경우에만 해당 클래스를 생성하고, 그러지 않은 경우는 생성하지 않도록 해야 한다.
그리고, 보통 게임 전반적으로 계속 쓰는 변수나 함수를 담아놓기 때문에 해당 클래스는 씬이 전환되어도 사용되는 경우가 많을텐데, 이를 위해서 DonDestroyOnLoad();를 통해 해당 클래스를 담은 오브젝트가 씬이 바뀌어도 사라지지 않도록 해야 한다.
여하튼 싱글톤 클래스는 게임 매니저, Input manager... 등등의 온갖 매니저 클래스에 사용할 수 있기 때문에 나 역시 앞으로 활용할 여지가 많이 보여서 더 기억에 남았던 내용이었다.
(사실 미리 어디선가 듣고 어설프지만 먼저 사용하고 있었지만, 확실하게 알게 되어 좋았다..ㅎㅎ)
Rect Transform
금요일에는 본격적으로 UI에 대해 배웠다.
UI 그리고, 2D에서 가장 중요하다고 강조하신 것은 다름 아닌 Rect Tranform 컴포넌트였다.
기존의 Transform과 유사한 컴포넌트지만, 새롭게 보이는 것이 있었다.
바로 Pivot과 Anchor!
이는 position, rotation, scale의 기준이 되는 점을 (부모 객체의 위치를 기준으로) 다르게 옮길 수 있는 속성이었다.
Anchor(해석하면 닻이다.)는 부모 개체의 범위에서 해당 자식 오브젝트를 둘 일종의 범위를 정하는 속성이다.
해당 값은 X는 맨 왼편이 0, 맨 오른편이 1이고, Y는 맨 아래가 0, 맨 위가 1이며 min과 max의 값이 다르면 위의 사진처럼 Left, Right, Top, Bottom의 패딩 범위를 설정해서 오브젝트 위치를 맞추게 된다.
만약에 Anchor의 값이 같으면, 위 사진처럼 PosY와 Height로 속성 값이 바뀐다.
이 경우에는 일종의 좌표가 되어서 부모 오브젝트의 Y축(높이)의 가운데를 기점으로 얼마나 위, 아래에 배치할지(PosY), 해당 오브젝트의 높이는 얼마로 잡을지(Height)를 설정해야 한다.
Anchor가 부모 오브젝트에 대한 것이었다면, Pivot은 해당 오브젝트에 대한 기준을 정하는 속성이다.
해당 오브젝트의 position이 어떤 좌표(x, y)를 기점으로 설정될지 이를 통해서 잡히게 된다.
(당연히 rotation, scale도 이 지점을 기준으로 설정된다.)
그럼 왜 이런 것을 맞추어야 할까?
다음 그림들은 그냥 Position 값을 통해서 임의로 화면 좌측 상단에 UI를 배치한 경우이다.
잘 보면 알겠지만, 해상도가 달라짐에 따라서 원래 좌측 상단에 잘 붙어있던 UI가 상단에서 조금 아래에 배치된 것을 확인할 수 있다.
게임이나 앱을 쓰는 사용자들의 스마트폰의 해상도가 제각각인 현 상황에서 만약 이와 같이 UI를 배치하고 배포를 한다면, 분명 많은 유저들에게 욕을 먹을 수 있을 것이다.
그래서 우리는 모든 해상도, 모든 경우에도 화면의 특정 위치에 고정하고 싶은 UI 등의 오브젝트가 있을 때에 Rect Transform의 anchor, pivot을 설정해야 하는 것이다.
Starters 6주차 수업 후기
수업 후기를 올려보는 것은 상당히 오랜만인 것 같다.
요새 배운 내용의 100%를 블로그에 담으려고 노력했는데, 사실 처음 배운 입장에서 모든 내용들을 완벽하게 이해하지 못하고 정리하는 것이 마냥 좋지 않다는 것을 알았다.
학습 일지에서 무조건 학습한 내용만 중요한 것이 아니고, 배우면서 겪은 내 이야기와 느낀 점 역시 엄청 중요하다는 것을 다시 한번 알았기 때문이다.
이건 몇 주간 내가 작성한 글들을 보면서 많이 느꼈다.
특히나 정작 이 교육에서 내가 어떤 것이 부족했다거나 잘못 알았다거나 했는데, 교육을 통해 정정했다는 그런 내용을 남기는 것이 더 의미 있을 수 있는데 너무 부족했다는 것을 뼈저리게 느꼈다.
앞으로는 나를 위해 그리고, 이 글을 봐주시는 분들을 위해 느낀 점이 많았던 내용을 중점으로 추리고, 내 스토리와 녹여내서 글을 쓰도록 하겠다.
다음 주간 과제는 UI를 활용해서 포트리스와 같은 게임을 만들어보는 것이다.
슬라이더 UI와 이미지 등등을 사용해서 만들 예정인데, 일주일마다 새로운 게임을 만드는 것이 쉽지는 않지만, 오히려 이렇게 결과물들을 만들어내는 것에서 묘한 설렘을 느낄 수 있는 것 같아서 너무 좋은 것 같다.
유데미 코리아 바로가기 :
Udemy Korea - 실용적인 온라인 강의, 글로벌 전문가에게 배워보세요. | Udemy Korea
유데미코리아 AI, 파이썬, 리엑트, 자바, 노션, 디자인, UI, UIX, 기획 등 전문가의 온라인 강의를 제공하고 있습니다.
www.udemykorea.com
💡 본 포스팅은 유데미-웅진씽크빅 취업 부트캠프 유니티 1기 과정 후기로 작성되었습니다.
'Starters 부트캠프 > B - log' 카테고리의 다른 글
유데미 스타터스 유니티 개발자 취업 부트캠프 1기 - 8주차 학습 일지 (0) | 2022.08.14 |
---|---|
유데미 스타터스 유니티 개발자 취업 부트캠프 1기 - 7주차 학습 일지 (0) | 2022.08.07 |
유데미 스타터스 유니티 개발자 취업 부트캠프 1기 - 5주차 학습 일지 (2) | 2022.07.24 |
유데미 스타터스 유니티 개발자 취업 부트캠프 1기 - 4주차 학습 일지 / 유데미 [C#과 Unity로 3D 게임 개발하기] 강의 리뷰 (4) | 2022.07.16 |
유데미 스타터스 유니티 개발자 취업 부트캠프 1기 - 3주차 학습 일지 (2) | 2022.07.10 |