1. 게임 개발 공부를 시작한 이유
군대에서 국방 오픈소스 아카데미 덕분에 무료로 Python의 기초적인 알고리즘, 자료구조 강의를 수강해 완강하여 백준등 여러 코딩테스트 사이트에서 문제도 틈틈히 풀었었고,
또 수학과인 본인의 전공과 밀접한 분야를 생각하다가, 전역할 즘에 국방부와 카카오엔터프라이즈에서 같이 진행하는 파이썬 인공지능 초보자 코스도 신청하여 강의를 들었는데, 솔직히 말해 재미가 없었다.
물론 모든 공부가 재미있을 수는 없지만, 수학과는 뭔가 다른 외로움때문에 재미가 없었다.
1학년때는 사실 배우는 수학이 어렵지가 않았고, 2학년때는 코로나로 인해 비대면수업을 해서 사실 강의만 듣고 혼자서 공부하였다.
굳이 다같이 스터디를 할 필요가 없는 수준의 과목이었다는 점도 있지만, 혼자 공부하는 것이 더 효율적이였고 편했다. 그래서 혼자 집에만 있으면서 공부해 약간 외롭긴했지만, 재밌게 공부하긴 했었다.
그런데 코딩은 군대에서 공부할때, 정말 혼자서 벽보고 대화하는 기분이였다.
컴퓨터의 언어를 공부한다지만, 컴퓨터가 나랑 대화하는 기분도 안들고, 동기부여도 크게 되지 않았다.
그래서 실질적인 결과물을 눈으로 볼 수 있는 것을 공부해보자 마음먹었고, 전역후에 게임, 웹, 소프트웨어(윈도우) 셋 중에 가장 동기부여도 잘 될 것같고 내가 즐겨하는 게임개발을 공부를 시작하였다.
또 복수전공을 마음먹은 것이
첫째로는 아무리 개발과 관련해 여러 질 좋은 온라인 강의도 많고, 검색하면 다 나온다지만, 개인의 역량부족과 더불어 확실히 근본적인 지식을 공부한다는 기분은 안들었다.
두번째로는 개발자가 개발을 잘하면 좋지만, 다른 개발자, 클라이언트등 여러 사람들과 협업과 대화를 통해 만족스러운 서비스를 제공하고 지식을 공유하며 성장할 수 있어야하는데, 혼자 공부해서는 이러한 능력을 기를 수 없다고 생각이 들었다.
따라서 복수전공을 통해 양질의 지식을 배우고, 여러 사람들과 커뮤니케이션을 하고 싶었다. 꼭 붙기를 간절히 기도하는 중이다.
아무튼 개강하기전에, 간단한 게임을 만드는 것이 내 목표이다. 사실 10~11월동안 C#과 유니티 온라인 강의를 인프런에서 수강하였고, 갤럭시탭도 그 시기에 샀다.
원래 그림그리는 것을 좋아했어서 클립스튜디오 사용법도 익힐겸 어리석게 게임에서 쓸 리소스 들을 먼저 만들었다가, 또 기획이 바뀌면서 안쓰게되어서 어영부영 시간을 날렸다.
게다가 2D게임을 만들때는 초기에 PPU(pixel per unit)을 설정하는 것이 매우 중요한데 , 무슨 카드하나의 해상도를 QHD 해상도 급으로 만들어서, 실제 엔진에서 쓰려고 하니 지나친 scale 축소로 이미지가 다 깨져서 정말 슬펐던 적이 있다.
아무튼 아마 아래에 서술할 2번 목표로 간다면 다운스케일링을 통해 다시 쓸 수는 있을 것 같다.
아래는 갤럭시탭과 클립스튜디오를 통해 직접 그려낸 카드 리소스들이다.
원본은 1800x2700이나 되는 카드의 샘플이다. 디자인은 슬레이더 스파이어와 아컴호러 카드게임, 레전드 오브 룬테라에서 얻었다.
2. 장르선정의 이유
아무튼 지금은 목표 게임을 정하고 , 우선 머릿속으로 구현하기 어려운 것들이나, 코드 작성할 때 스텝by스텝을 적어놓으면서 하고 있다.
목표는 두가지 중 하나 인데 키워드로 설명하자면
1. 로그라이크 + Hex Tile + 턴제 전략 + 카드 + 덱빌딩
2. 로그라이크 + 테이블 탑 + 턴제 전략 + 카드 및 주사위, 동전 + 백빌딩이다.
모티브로는
1. Slay The Sprie, Alina of the Arena 등의 현재 유행중인 덱빌딩 로그라이크(이하 슬더스류) 장르들
2. 다이시 던전, 아컴호러 카드게임(보드게임), 워 체스트(보드게임), 다이스 쓰론(보드게임) 등 여러 테이블 탑 게임들
로부터 모티브를 얻었다.
사실 두개의 방향성이 크게 덱빌딩이냐 백빌딩이냐 인데, 아직은 정하지 못해서 기초적인 틀부터 구현하였고,
아마 예상했을 때 간단한건 2번이라 2번을 목표로 갈 것 같긴하다.
그러면 왜 두가지 장르를 골랐냐 하면, 군대에서 보드게임류를 정말 재밌게 했었는데, 그래선지 PC 게임도 턴제/전략/카드등의 게임을 즐겨해서, 접한 장르의 게임을 만들어보고 싶은 마음이 들었다.
물론 출시할 것도 아니고, 유니티 및 C# 초보자로서 공부하는 용이니, 구현하고 안쓰는 것들도 일종의 공부로 받아들이면서 한걸음 씩 걸어나갈 것이다.
아래는 오늘자 Game 화면이다. 지금까지는 c#, 유니티 엔진에 대해 배운것을 복습, 여러 내용들을 다시 공부해가면서
1. Scriptable Object를 통한 Player Card, Player Stat, Enemy Stat 관리
2. 싱글톤 패턴을 이용한 여러 매니저 클래스들 구현
-덱메니저 : 덱 생성, 카드 드로우, 사용, 드래그, 클릭, 등 카드와 덱에 관한 전반적인 시스템을 관리
-타일 메니저 : 타일 맵에서의 상호작용 : 타일 클릭시 이동 등을 관리
-턴 메니저 : 턴 시스템을 구현..적 턴에는 카드 사용 못하게 상태 패턴 제어 등등..
3. 기타 스크립트를 통해 기본적인 스킬 구현 : 이동, 방어도 증가 등
정말 기본적인 시스템들의 일부를 구축을 해 놓았다.
아직 이정도 밖에 못했는데, 사실 비전공자가 맨땅에 헤딩을 한 격이라, 참 애를 많이 먹었다. 처음 강의를 들었을 때는
뜬구름같이 개념이 잘 잡히지 않았는데 맨땅에 헤딩해보면서 여러 C# 문법들을 구현해보고, 오류도 직접 수정해보면서 하니 어설프게나마 C# 문법과 유니티를 알게된 것 같다.
앞으로는 맨땅에 헤딩까지는 아니고.. 한걸음 한걸음 행군을 해보고 싶다. 앞으로의 나 자신 파이팅!
ps. 개발일지인데 코드하나쯤은 올리고 싶어서, 아래는 아직 제대로 사용하지도 않고, 아마 앞으로 사용하지 않을 것 같긴 한데, 구글링을 하다가 Hex Tile Map에 대한 글을 보고서, 유용한 알고리즘도 많고, Hex Tile Map에 대한 전반적인 이해를 위해 TileManager로 일부를 작성해 구현해보았다.
근데 사용하지 않은 이유는 사실 이 글을 보기전에 이미 무식하게 Tile Map에 필요한 기능들은 대충 구현을 했긴했다..
노트app을 캡처한 첫번째 사진에 타일맵에 주구장창 숫자쓰고 한 저것이
마우스 좌표 <->타일 맵의 Cell 좌표 <-> Game에서 World 좌표 및, 이동할 때 이동범위를 표시하기 위해 생성한 파란색 타일이 타일맵 밖에서 생성안되게 만드려고 한 노력의 흔적이다.
아무튼 본인은 수학과인데 육각형과 타일맵, 좌표를 가지고 이런 생각을 한다는 것 자체가 참 신기했다..
https://www.redblobgames.com/grids/hexagons/
public class TileManager : MonoBehaviour
{
public static TileManager Inst { get; private set; }
void Awake() => Inst = this;
public Dictionary<Vector3Int, bool> objectOnTile = new Dictionary<Vector3Int, bool>();
public Dictionary<Vector3Int, int> objectCount = new Dictionary<Vector3Int, int>();
public Tilemap tilemap;
public Grid grid;
int[] player_dir = { 1, 2, 3, 4, 5, 6 };
int[] player_min = { 1, 1, 1, 1, 1, 1 };
int[] player_max = { 2, 2, 2, 2, 2, 2 };
void Start()
{
foreach (Vector3Int pos in tilemap.cellBounds.allPositionsWithin) //pos를 axial좌표로 변환까지
{
if( !tilemap.HasTile(pos) )
{
continue;
}
objectOnTile.Add(pos, false);
objectCount.Add(pos, 0);
}
}
//이동 -> 범위 설정 -> 장애물감지 -> 범위재설정 -> 클릭 -> 이동
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
}
}
public Vector2 Axial_To_Oddr(Vector2Int pos)
{
if (Mathf.Abs(pos.y) % 2 == 0)
{
int oddr_x = pos.x + (pos.y / 2);
int oddr_y = pos.y;
Vector2Int result = new Vector2Int(oddr_x, oddr_y);
return result;
}
else if (Mathf.Abs(pos.y) % 2 == 1)
{
int oddr_x = pos.x + ((pos.y - 1) / 2);
int oddr_y = pos.y;
Vector2Int result = new Vector2Int(oddr_x, oddr_y);
return result;
}
else
return pos;
} //축 좌표 <->오프셋 좌표
Vector2 Oddr_To_Axial(Vector2Int pos)
{
if (Mathf.Abs(pos.y) % 2 == 0)
{
int axial_q = pos.x - (pos.y / 2);
int axial_r = pos.y;
Vector2Int result = new Vector2Int(axial_q, axial_r);
return result;
}
else if (Mathf.Abs(pos.y) % 2 == 1)
{
int axial_q = pos.x - ((pos.y - 1) / 2);
int axial_r = pos.y;
Vector2Int result = new Vector2Int(axial_q, axial_r);
return result;
}
else
return pos;
}
Vector2 Cube_To_Axial(Vector3 pos)
{
float axial_q = pos.x;
float axial_r = pos.y;
Vector2 result = new Vector2(axial_q, axial_r);
return result;
} //축 좌표 <-> 큐브 좌표
Vector3 Axial_To_Cube(Vector2 pos)
{
float cube_q = pos.x;
float cube_r = pos.y;
float cube_s = -(pos.x + pos.y);
Vector3 result = new Vector3(cube_q, cube_r, cube_s);
return result;
}
Vector3 Round_for_Cube(Vector3 pos)
{
float cube_q = Mathf.Round(pos.x);
float cube_r = Mathf.Round(pos.y);
float cube_s = Mathf.Round(pos.z);
float q_diff = Mathf.Abs(cube_q - pos.x);
float r_diff = Mathf.Abs(cube_r - pos.y);
float s_diff = Mathf.Abs(cube_s - pos.z);
if (q_diff > r_diff && q_diff > s_diff)
{
cube_q = -(cube_r + cube_s);
}
else if (r_diff > s_diff)
{
cube_r = -(cube_q + cube_s);
}
else
{
cube_s = -(cube_q + cube_r);
}
Vector3 result = new Vector3(cube_q, cube_r, cube_s);
return result;
} //가장 가까운 Cube좌표로 반올림 : 우선 사용X
public Vector2 Pos_To_AxialHex(Vector2 pos)
{
Vector3Int v3int_cellPos = grid.WorldToCell(pos);
int x = v3int_cellPos.x;
int y = v3int_cellPos.y;
Vector2Int oddr = new Vector2Int(x, -y);
return Oddr_To_Axial(oddr);
} // 일반(마우스) 포지션 -> Axial 좌표로
}