3D 강의 코드를 2D 프로젝트로 옮기기 7
약 3,40분 정도 사용.
BaseAction코드를 (AI 관련 코드 빼고) 모두 옮김.MoveAction코드를 (AI 관련 코드 빼고) 모두 옮김.
취미로 하는 게임 개발 공부
약 3,40분 정도 사용.
BaseAction 코드를 (AI 관련 코드 빼고) 모두 옮김.MoveAction 코드를 (AI 관련 코드 빼고) 모두 옮김.한동안 공과 사 양쪽이 바빠서 정신 없었다. 다시 시작, 할 수 있을런지?
일단 기록하지 않았던 기간동안 파편적으로 진행한 것까지 합쳐서 오늘까지 Pathfinding 쪽과 Unit 쪽 코드를 모두 가져왔다.
Unit 을 가져오는 과정에서 HealthSystem 과 TurnSystem 도 같이 가져왔다.
2D 좌표에서도 LevelGrid 및 GridSystem 이 잘 적용되게끔 처리.
이제 Pathfinding 쪽을 마무리하고 Unit 쪽으로 넘어가면 된다.
grid debug object 스트립트 및 prefabs 들을 만들어서 게임 화면에 해당 객체들을 찍어보려고 시도 중이다.
2D Object 에는 TextMesh Pro 가 없어서 당황해서 삽질을 하다가, 나중에야 2D 프로젝트에서도 3D Object - TextMesh Pro 를 사용한다는 걸 알아내서 해당 객체로 prefab 을 만들었다.
스크립트까지 다 작성한 다음에 적용해보았는데 제대로 적용되지 않아서 살펴보니, gridPosition 에서 x, z 좌표를 사용했는데 2D 프로젝트에서는 x, y 좌표를 사용해야 해서 수정이 필요함을 확인했다.
Pathfinding 작성을 끝냈다.
grid debug object 스트립트 및 prefabs 들을 만들어서 게임 화면에 해당 객체들을 찍어보려고 시도 중이다.
오늘은 LevelGrid 작성을 완료하고 Pathfinding 작성을 시작했다. Pathfinding 작성에 앞서 PathNode 작성도 필요했기에 해당 클래스는 작성을 완료했다.
생각보다 고개를 갸우뚱 하는 코드들이 좀 있었다. 예를 들면 불필요한 코드 중복이라던가, LevelGrid 와 Pathfinding 의 순환참조라던가. 하지만 지금 목표는 리팩토링이 아니라 코드 리뷰 및 리마인드 이므로, 되도록 코드를 수정하지는 않고 있다.
강의: Unity Tutorial 2024 - Build a 2D RPG
강의 Part 09 내용은 그다지 중요해보이지 않아서 바로 다음 강의로 넘어갔다.
Part 10 내용은 배틀 시스템 구현이었는데, 초반 몇 분을 보니 도저히 강의 시간 내에 (이전 강의에서 몇 번 언급했던) 파이널판타지나 포켓몬 스타일의 배틀 시스템을 구현할 수 없을 것 같아서 뒷부분으로 돌려보니 역시나 밑작업만 완료하는 정도였다.
이어지는 강의가 있어야겠지만 이 강의가 올라온 것이 2023년이고, 후속 강의는 그 이후에 올라오지 않고 있다. (이 유튜브 채널에는 이 강의 영상 10개만 있고 이외에는 아무것도 없다.)
일단 2D 게임 객체 생성의 기초는 대략 본 것 같으니 2D 강의는 여기까지만 보도록 하겠다.
강의에 대한 총평을 하자면, 무료 강의인 것을 감안하면 나쁘지 않았다, 정도로 하겠다.
다시 습작 만들기를 시작하기 위해 Unity 2D 프로젝트를 새로 만들었다.
TrialAndError 라는 이름을 다시 쓰기에는 맥빠지므로 IdKwit 정도로 만들었다.
일단은 이전 3D 강의에서 구현했던 코드를 가져오는 게 좋겠다 싶어서 3D 강의 때 따라한 프로젝트를 열었는데, 생각보다 코드가 많아서 어떻게 옮길까 고민을 좀 했다.
생각해보니 그 때 강의가 끝나고 전체적인 코드 리뷰를 한다고 말만하고 코드 리뷰를 하지 않았었다. 그래서 코드리뷰도 할 겸 코드를 그냥 복사하지 않고 따라서 다시 쳐보면서 옮기기로 결정했다.
시작은 LevelGrid 부터다. LevelGrid 를 작성하다보니 GridSystem, GridObject, GridPosition 내용이 필요한 것 같아서 해당 내용들을 먼저 작성했고, 덩치가 커보이는 Unit, IInteractable, Pathfinding 클래스 생성만 해두고 본 내용은 아직 작성하지 않았다.
GridSystem, GridObject, GridPositionLevelGridUnit, IInteractable, Pathfinding강의: Unity Tutorial 2024 - Build a 2D RPG
DialogManager 클래스를 작성, 싱글톤으로 만든 후 ShowDialog 메서드 작성. ShowDialog 에서 대화 내용을 주고 받을 때 사용하기 위한 데이터 객체용 클래스 Dialog 도 생성. NPCController 의 Interact 메서드에서 DialogManager.ShowDialog() 를 실행하게끔 한다.
NPC 에게 말을 걸 경우 말풍선까지 잘 뜨지만, 다음 문장으로 넘어가지 않고 대화중인데도 움직일 수 있는 등 미비된 사항들이 있다. 이건 다음 영상에서 마저 구현한다.
DialogManager 에 OnShowDialog, OnHideDialog 이벤트를 추가하고, 해당 이벤트리스너를 통해 GameController 가 게임 상태를 바꾸게 했다.
GameController 가 대화 상태일 때는 DialogManager.HandleUpdate() 만 실행함으로써 말풍선 이외에는 업데이트가 되지 않게 제어한다.
DialogManager.HandleUpdate() 는 coroutine 을 활용해 라인 별로 타이핑하듯이 대화를 출력하는 기능을 구현한다.
coroutine.. 분명히 알던 개념인데 오랜만에 봐서 그런지 엄청 헷갈린다. 개념을 다시 찾아봐야겠다.
전투 시스템 설계에 대한 이론 수업이라고 해서 들어야 하나 고민 중. 아마 높은 확률로 듣지 않고 다음 강의로 넘어갈 듯.
강의: Unity Tutorial 2024 - Build a 2D RPG
Interactable 인터페이스를 구현해서 NPCController, ChallengerController 클래스를 만들고 각 객체에 붙여보았다.
게임 전반을 제어하기 위한 GameController 클래스를 추가. 대화 상자 용으로 사용될 UI 도 추가. 남은 부분에서 말을 걸면 대화상자가 열리는 것을 구현할 듯.
강의: Unity Tutorial 2024 - Build a 2D RPG
충돌 검사를 해서 현재 Player 가 바라보는 방향에 NPC 가 있으면 log 를 찍는 것까지 구현. 강의 처음에 보여준 말풍선은 다음 강의에서 구현한다고 한다.
강의: Unity Tutorial 2024 - Build a 2D RPG
Sorting Layer 는 일반 Layer 와 달리 z-index 역할.
2D 장애물을 만들기 위해서는 별도의 TileMap 을 만들고 그 위에 Tile Palette 로 그림을 그린 다음, Tilemap Collider 2D, Composite Collider 2D (+ Rigidbody 2D) 를 적용.
지금까지 강의에서 작성한 코드량이 얼마 되지도 않지만, [SerializeField] private 대신 public 을 사용하는 것을 보니 코드 퀄리티에서는 배울 게 없을 것 같다. 하긴 설명하는 수준을 보면 초심자용 영상인 것은 확실하다.
Project Settings 의 Graphics - Camera Settings 에서
으로 하면 y 좌표 기준으로 같은 Layer Sprite 객체가 정렬된다.
새로운 캐릭터를 추가하고 Interactable 이라는 Layer 를 추가한 뒤 해당 캐릭터에 지정해줬다. 이후에는 캐릭터에게 말을 걸면 말풍선이 나오는 것을 구현할 듯.
강의: Unity Tutorial 2024 - Build a 2D RPG
애니메이션을 생성하고 적용하는 방법이 이전 강의와는 다르다. 2D 라서, 라기보다는 애니메이션을 적용할 수 있는 방법이 많은 것이라고 이해하는 것이 좋을 것 같다.
강의: Unity Tutorial 2024 - Build a 2D RPG
움직임을 마저 구현. 입력을 받는 방법이 이전 강의와 다르다.
그리고 coroutine 을 사용하는데, 지금 시점에서는 굳이 coroutine 을 사용할 필요가 없어서 의아하긴 하다. 후속 영상에서 coroutine 을 사용한 개선이 있으려나?
강의: Unity Tutorial 2024 - Build a 2D RPG
강의 사이트가 아니라 유튜브 영상이다 보니 "강의 수강" 이라는 표현이 적절할지 헷갈린다.
캐릭터의 움직임을 구현하는 강의인데, 따라하던 중 갑자기 맥북의 드래그앤드랍이 먹통이 된다거나, 그래서 재부팅했더니 이번에는 vscode 의 dotnet 연동이 먹통이 된다거나 하는 이슈들이 있었다. 덕분에 예상치 못한 시간이 소모되었다.
강의는 내용이 너무 기초적이라서 어느정도 스킵할까 싶었지만 혹시라도 몰랐던 내용을 놓칠까봐 그러지 못하고 있다. 그 와중에 유튜버의 말이 너무 느려서 영상 배속도 조정해보고 있다.
여튼 이러저러한 이슈들 때문에 집중력이 좀 떨어져 많이 진행하지 못했다. 하지만 일단 언제나 그렇듯 꾸역꾸역, 느리더라도 어떻게든 진행하는 게 중요할 것이다. 열심히 해보자.
주말간 너무나 파편화된 시간밖에 쓰지 못해서 차마 글을 쓰지는 못했다. 여튼 습작 프로젝트는 클래스 별로 Prefab 을 몇 개 추가하고 스탯 기준 턴 시스템까지 구현한 상태였다. 하지만 어제 willow 형과의 논의에서 습작을 3D 가 아닌 2D 로 만들자는 결정이 나서 2D 에 대한 학습이 필요해졌다. 따라서 습작 프로젝트 진행은 (당연히) 멈추고, 관련 강의를 들어야 할지 아니면 무작정 2D 프로젝트를 만들면서 구글링 이나 생성형 AI 에 의지해야 할지 고민을 좀 했다.
결론은 강의를 듣긴 들어야 겠다는 것.
하지만 Udemy 에서 검색했을 때는 그다지 원하는 강의가 나오지 않았다. 그래서 유튜브에서 검색을 좀 해보았는데, 결국 찾은 것이 EPIC Game Dev 라는 채널의 Unity Tutorial 2024 - Build a 2D RPG 라는 재생 목록이다. 총 10개의 영상으로 구성되어 있고 다 합쳐서 3~4시간 정도 될 것 같다.
일단 첫 번째 영상을 보았는데, Sprite 타일 맵을 리소스로 어떻게 추가하고, 추가한 타일 맵으로 일종의 브러시를 만들어서 어떻게 맵을 그릴 수 있는지에 대한 내용이었다. 정제된 강의라기 보다는 날것의, 유튜브 다운 영상이었으나 어렵거나 산만하지 않아서 편하게 볼 수는 있을 것 같다.
그래서 일단 먼저 이 영상을 다 챙겨본 뒤에, 다시 습작 프로젝트로 돌아갈 예정이다.
시간을 많이 못 썼다. 일단 Prefab 을 추가 중이다. 각각의 클래스를 구분하기 위해 각각의 2d sprite 와 애니메이션도 같이 붙이고 있다.
클래스 별로 Prefab 을 만들어두려고 하는데, 다 같은 리소스면 재미가 없으므로 무료 2D Sprite 어셋을 좀 찾아봤다. https://craftpix.net/ 여기에서 대략 SD 캐릭터 느낌이 나는 무료 어셋들을 찾았다. 우리 클래스가 민병/경보병/중보병/궁수 이런 느낌인데 딱 맞는 무료 어셋은 없어서 아쉬움이 좀 있지만, 임시 어셋이니 적당히 하기로 했다.
오늘은 어셋을 적용하다가 끝났다. 내일 마저 적용하고 다음 스탭으로 넘어가야겠다.
처음에는 Unit 클래스를 상속받아서 뭔가를 만들어야 하나 싶었는데, 일단은 Prefab 별로 스탯 적용이 가능한 상태이므로 그럴 필요 없을 것 같다. 근거리/원거리 공격도 일단 강의 수강하면서 구현은 했으므로 문제 없을 듯.
내일 서너 클래스 정도는 만들어서 배치해보고, 그 다음에는 직접 컨트롤해볼 수 있게 할 예정이다.
지금도 직접 컨트롤은 되지만 적 캐릭터는 최소한의 로직으로 자동 컨트롤되는데, 이 부분을 임시로 빼 둘 생각이다.
오늘 시간을 많이 쓰지 못했고 진행도 많이 하지 못해서 기록을 건너뛸까 했는데, 그런 것도 기록하면서 하려고 만든 게 이 블로그라는 걸 깨달았다.
이 블로그는 다른 사람에게 보여주려고 하기보다는 공부 및 개발을 계속 해나가는 원동력으로 쓰기 위해 만든거니까 (그래서 댓글이나 GA 나 기타 등등은 우선순위가 낮아서 아무것도 달지 않았다), 아무리 조금만 했어도 웬만하면 기록하는 걸로 하자.
어제도 시간을 쓰긴 했다. 다만 사용한 시간 중 대부분을 클래스를 어떻게 구성할 것인가에 대한 고민에 투자했다. 스탯 기반으로 턴이 돌아가게 하려면 스탯을 관리해야 할텐데 어디서 관리할까? 가 주요 고민이었다.
어제 생각한 건 "단순히 스탯을 Unit 클래스에 넣는 걸로는 장기적으로 관리가 어려울 것 같은데 어떻게 하면 좋을까?" 였고, 오늘까지 고민한 끝에 아래처럼 구성하기로 결론을 내렸다.
UnitStatus: 캐릭터의 스탯에 관련된 것들을 종합적으로 관리, 아래 세 클래스의 인스턴스를 갖고 있다.
UnitAttributes: 힘, 민첩 등 캐릭터의 순수 스탯을 관리. 레벨 업 등으로 인해 영구적으로 변경되는 사항도 관리한다.UnitProgression: 캐릭터의 레벨과 경험치를 관리. 레벨업 할 때의 이벤트를 추가해서 UnitStatus 에서 그 이벤트를 구독하면서 UnitAttributes 에 영향을 줄 예정UnitEquipment: 캐릭터의 장비와 그에 따른 능력치 변경을 관리UnitEffect: 캐릭터의 버프 및 디버프와 그에 따른 능력치 변경을 관리결국 Unit 에서는 UnitStatus 에만 접근해 원하는 능력치를 얻어낼 예정이다. 예를 들어 이번에 UnitStatus 에 추가한 GetMovementPriority 는 하위 클래스 인스턴스에서 필요한 값을 뽑아와서 턴 우선순위 값을 반환하는 메서드다.
위에서 추가한 UnitStatus.GetMovementPriority 를 사용해 TurnSystem 내부에 있는 unitList 를 정렬하고, 정렬된 순서대로 턴이 진행되게끔 구현.
턴이 바뀔 때마다 캐릭터의 선택도 자동으로 되게끔 했다.
다음 스탭은 턴이 바뀔 때 해당 캐릭터로 카메라가 이동하도록 하는 것. 그걸로 턴제 시스템은 한 번 일단락 지은 뒤 클래스 구분 및 기본 스탯 적용으로 넘어갈 예정이다. 이미 UnitStatus 및 하위 클래스들의 틀을 잡아놓았으므로, 어려울 것 같지는 않다.
어제 2D Sprite 적용을 시도해보면서 만들었던 프로젝트의 이름은 TrialAndError. 강의 81강까지 들으며 따라했던 코드를 그대로 복사해서 새로 만든 프로젝트다. 당분간은 이 프로젝트에서 계속 시행착오를 반복하며 연습 겸 습작 구현은 진행할 예정이다.
어젯밤에 willow 형과 앞으로의 구현 방향을 간단히 논의했다. 형이 벤치마킹 하려는 게임은 파이어엠블렘 시리즈이고, 해당 시리즈를 기준으 하면서도 당장 내 역량으로 구현이 가능한 최소 수준의 게임을 구현해 볼 예정이다.
그래서 당장 이야기가 나온 태스크는
Unit 하나로 통일되어 있음)
정도이다.
강의에서 구현한 턴 시스템은 적군과 아군이 번갈아가면서 진행하는, 아주 단순한 구조다. 하지만 요구사항은 캐릭터마다 존재하는 스탯에 의한 우선순위 결정 후 캐릭터마다 턴을 진행하는 것이기 때문에 개선이 필요하다.
그래서 오늘은 TurnSystem 에서 unitList 를 관리하게 하고, 모든 Unit 은 초기화되는 순간 TurnSystem.Instance.AddUnit() 함수를 통해 자신을 등록하게 했다. 그래고 TurnSystem 내부에서는 "현재 액션이 가능한 유닛" 을 unitList 의 인덱스로 관리하게끔 했다. 이제 턴 종료 및 시작 시마다 캐릭터 단위로 액션 포인트가 적용된다.
아직 캐릭터 스탯에 의해 턴 순서가 정해지진 않는다. 그리고 턴이 시작된 캐릭터에게 카메라 포커싱을 주고 선택도 자동으로 되게끔 해야 할 것 같다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
이전 강의에서 예고한대로, 이번 강의에서 상호작용 가능한 객체 클래스를 추상화한다.
이전 강의에서 만든 Door 와, 이번 강의에서 테스트를 위해 추가한 InteractSphere 두 클래스에서 공통으로 쓰일 함수 Interact 를 IInteractable 이라는 인터페이스를 만들어서 추상화한 뒤에 사용하게끔 코드를 수정했다.
그리고 이전에 73,74강에서 의문을 가졌던, 파괴 가능한 객체를 왜 추상화하지 않는가에 대한 대답이 나왔는데, 강의 마지막에 당연히 해당 객체 클래스들도 추상화 가능하고 그걸 도전해보라고 말하고 있다.
기존에 Unity 에서 제공하는 Input 클래스를 필요한 곳에서 각각 사용하고 있던 것을, InputManager 라는 새로 만든 클래스에서 모두 관리하게끔 수정. 다른 클래스들은 이제 입력 관련 기능이 필요할 때는 InputManager 를 거치게 된다. 간단한 리팩토링이었고 다음 강의에서 입력 시스템을 개선한다고 한다.
Package Manager 에서 Input System 설치. 설치 직후 기존 Input Manager 를 비활성화하고 새로운 것을 활성화하겠냐는 대화 상자가 뜨는데 일단은 No.
이후 Edit - Project Settings - Player 에서 Active Input Handling 을 찾아 Both 를 선택.

Project 탭에서 Input Actions 를 PlayerInputActions 를 만들고, 더블 클릭해서 수정창을 열어서 입력 값들을 설정해준다.

그리고 PlayerInputActions 의 c# 클래스를 자동 생성하게끔 설정하고

InputManager 클래스에서 해당 클래스를 사용하면 된다.
playerInputActions = new PlayerInputActions();
playerInputActions.Player.Enable();
}
public Vector2 GetMouseScreenPosition()
{
#if USE_NEW_INPUT_SYSTEM
return Mouse.current.position.ReadValue();
#else
return Input.mousePosition;
#endif
}
키보드로 카메라 이동/회전/확대축소 도 설정하는 코드가 있는데 궁금하면 강의를 다시 보거나 코드를 다시 보자.
강사가 preprocessor 까지 써가면서 Input 을 사용하는 기존 코드를 남겨놓는 이유는 Input 이 테스트 등의 용도에서는 작성 및 사용이 훨씬 간단해서라고 한다. 즉 간단한 프로토타이핑이나 테스트에는 기존 Input 을 사용하고, 본격적인 구현에서 Input System 을 사용하라는 의미다.
이건 나중에 상황 봐서 걷어내도 될 것 같고, 유용하다면 계속 써도 될 것 같다. 너무 신경쓰진 말자.
맵을 좀 더 수정하고 상호 작용 가능한 객체 종류를 추가하고 문이 열리기 전에는 닫혀 있는 곳을 검은 안개?로 보이지 않게 처리하는 등 실제로 한 스테이지를 플레이해볼 수 있는 처리들을 진행했다.
하지만 지금까지의 강의들과는 다르게 작업하는 모습을 빨리 감기로 하이라이트만 보여주는 모습이었는데, 마지막으로 과제를 준 느낌이다. (날먹은 아닌 것이 강의 GitLab 에 들어가면 해당 강의의 코드 내역이 모두 존재한다.)
마무리 인사.
보너스 섹션이 두 섹션 남아있지만 본 강의가 끝난 시점에서 가벼운 리뷰를 한 번 해보자.
일단 단순히 동작하는 코드 혹은 결과를 만드는 것이 아니라 확장 및 유지보수에 용이한 코드 및 결과 구현에 집중한 것이 매우 마음에 드는 강의였다.
하지만 몇몇 기초적인 부분이 누락된 것이 아쉬운데 예를 들면 씬 전환과 같은 기본적인 게임 구성에 필요한 것들이다. 이건 쉽게 찾아볼 수 있는 정보라 그냥 강의에서 뺀 것일까 (아니면 UI 컴포넌트로 충분히 구성 가능하다고 생각한 것일까). 이것만 살짝 아쉽다.
전체적으로 보면 매우 만족스러운 강의였고 (리뷰도 만점 줬다.) 보너스 섹션인 Hex Grid System 과 Multi-Floors 도 (당장은 아니더라도) 모두 볼 생각이다.
일단 (같이 게임을 만들기로 의기투합한) willow 형이 원하는 게임은 캐릭터들이 2D 이므로, 3D 맵에서 2D 스프라이트가 움직이게 하던, 혹은 아예 2D 로 통째로 프로젝트를 만들던, 게임 만들기에 앞서 추가적인 지식이 필요한 상황이다.
이 부분을 1. 그냥 시행착오로 부딪혀 볼 것인지, 2. 혹은 다른 강의를 들을 것인지, 3. 혹은 ChatGPT 같은 생성형 AI 의 도움을 받을 것인지 고민 중에 있다. 당장은 1,3번을 조합하되 어려움을 느끼면 2번을 고려할 생각이다.
3D 로 게임을 만드는 것에도 여전히 흥미는 있기 때문에, 이 흥미는 지금이든 나중이든 어떤 식으로 발현해나갈 것인지도 고민이다.
내친 김에 2D Sprite 이미지를 적용하는 걸 좀 진행해봤다. 움직일 때 캐릭터는 회전하지 않게 하고, 카메라 회전 시 항상 2D 이미지가 잘 보이도록 회전하게 했다. 애니메이션은 Idle, Walking, Slashing 세 개 정도만 적용했고, 걷거나 공격할 때 캐릭터가 해당 방향을 바라보도록 (flipX) 적용했다. 생각보다 쉽게 잘 된다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
Door Prefab 을 만들고 InteractAction 에서 문과 상호작용하는 액션을 만들었다.
이름이 InteractAction 인데 너무 Door 와 결합이 강한데, 이건 다음 강의에서 개선할 예정.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
SwordAction 을 추가. 전에 했던 GrenadeAction 추가와 방식은 거의 같았다. 다만 좀 다른 것은 검을 휘두르는 액션을 추가하고 무기 prefab 을 추가한 것 정도.
뭔가 무기를 더 잘 관리할 수 있는 방법에 대해 말로 소개해줬는데, 이번에는 그 방법을 사용하지 않고 가장 간단한 방법을 사용. UnitAnimator 에 총과 칼 prefab 각각 등록해서 사용한다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
Package Manager 에서 ProBuilder 찾아서 설치.
Crate prefab 을 복사해 CrateDestroyed prefab 추가. 내부의 실제 상자 오브젝트를 선택한 뒤, Tools - ProBuilder - ProBuilderize 를 선택.
그 후 ProBuilder 의 Experimental 을 enabled 한 뒤에 Experimental 의 Boolean Tool 을 사용해야 한다. 자세한 사용법이 생각나지 않으면 강의를 다시 보자.
Boolean Tool 을 사용해 Crate 를 조각낸 뒤, 조각낸 객체들에 MeshCollider 와 RigidBody 컴포넌트를 부착하자. 그 다음 폭발 후 날아가는 애니메이션은 UnitRagdoll 에서 했던 것처럼 각 RigidBody 에 AddExplosionForce 를 해주면 된다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
GrenateAction 추가.
BaseAction 기반의 새로운 액션은 어떻게 추가하는지 복습하는 동시에, 발사체가 있고 범위 데미지가 있는 액션은 어떻게 구현하는지에 대한 튜토리얼이라 할 수 있겠다.
다만 아쉬운 건 수류탄이 직선으로 linear 타이밍으로 날아간다는 것과 폭발 이펙트가 없다는 것인데, 폭발 이펙트는 다음 강의에서 구현한다. 날아가는 애니메이션은 수정할런지.
폭발 이펙트는 이전의 ShootAction 에서 사용했던 BulletHitVFX 를 복사해서 수정.
Trail 도 ShootAction 에서 썼던 것을 복사해서 사용.
포물선을 그리며 날아가는 것에는 AnimationCurve 라는 unity 클래스를 활용.
Vector3 moveDir = (targetPosition - positionXZ).normalized;
float moveSpeed = 15f;
positionXZ += moveSpeed * Time.deltaTime * moveDir;
float distance = Vector3.Distance(positionXZ, targetPosition);
float distanceNormalized = 1 - distance / totalDistance;
float maxHeight = totalDistance / 4f;
float positionY = arcYAnimationCurve.Evaluate(distanceNormalized) * maxHeight;
transform.position = new Vector3(positionXZ.x, positionY, positionXZ.z);
Create (=상자) 라는 별도의 개임 객체를 만들고, 해당 객체의 layer 는 Obstacles 로 지정.
GrenateAction 에서 데미지를 주는 로직에서 범위 내에 있는 Crate 에도 데미지를 주도록 수정. Crate 는 HP 개념 없이 한 대만 맞으면 부서지도록 (삭제하도록) 구현.
단 이 과정에서 Crate 가 부서질 경우 Pathfinding 도 업데이트 해줘야 하는데, Pathfinding 과 Crate 간 상호 의존성을 만들지 않기 위해 PathfindingUpdater 라는 클래스를 따로 만들고, 해당 클래스에서 DestructibleCrate.OnAnyDestroyed 리스너를 등록하게끔 해서 구현.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
남은 시간에도 열심히 맵을 만들었다. Level Design 이라는 게 말 그대로 맵을 만드는 것을 뜻하는 듯.
Unit 의 움직임에 Pathfinding 을 적용한다.
이동 전 이동 가능한 지점을 찾는 데 obstacle 도 고려하기 위해 Pathfinding 과 Raycast 를 추가로 적용한다.
총을 쏠 때도 총을 쏠 수 있는 대상인지 판단할 때 Raycast 를 추가로 적용한다.
새로운 섹션 Polish 에 대한 소개. 공격할 때 화면이 흔들리는 효과, 수류탄/검 공격, 파괴/인터렉션 가능한 오브젝트 등등 게임의 디테일을 구현할 것이라고 한다. 흥미진진.
이 섹션 이후에도 두 개의 보너스 섹션 (Hex Grid System, Multi-Floors) 이 존재하는데, 보너스 섹션을 제외하면 공식적인 마지막 섹션이다. 이 섹션이 끝난 뒤에는 전체적으로 코드 리뷰도 한 번 하고, 본격적인 게임 구현 시작을 준비해야겠다.
CinemachineVirtualCamera, ActionVirtualCamera 에 Add Extension 메뉴를 통해 Impulse Listener 를 추가.
새로운 게임 객체 ScreenShake 추가. 해당 객체에 Cinemachine Impluse Source 컴포넌트를 추가.
ScreenShake 스크립트에는 Shake 메서드를 구현해 카메라를 흔들 수 있는 기능을 제공하게 하고, ShootAction 에는 OnAnyShoot 이벤트를 추가한 뒤, ScreenShakeAction 에서 ShootAction.OnAnyShoot 이벤트를 리스닝 한 뒤에, 리스너에서 ScreenShake.Shake 를 실행하게끔 구현.
정말 이 강의는 스크립트를 추가하고 구현을 나누고 결합을 느슨하게 하는 이런 일련의 행위들에 거리낌이 없다. 예전에 들었던 강의라면 이런 건 최소화하고 말 그대로 동작하게 하는 것에만 집중했을 것 같은데.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
장애물을 위한 Layer 를 새로 만들고, Wall prefab 을 만들어 해당 layer 를 적용한 뒤에, 맵 위에 wall 을 새워놓으면 raycast 로 wall 이 서 있는 칸을 확인해 해당 칸은 pathfinding 에서 사용하지 않도록 수정.
이를 위해 PathNode 는 isWalable 이라는 필드가 추가되었다.
그런데 다 잘 따라한 것 같은데 장애물을 인식을 못해서 한참 찾다가 raycast 랑 충돌할 layer mask 를 제대로 지정 안한 걸 겨우 찾아냈다.
맵을 만든다. 7분까지도 조용히 몇 마디 말 없이 맵만 만든다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
이 수업의 단일 강의 중 제일 길었던 것 같은데, pathfinding 알고리즘을 구현해서 0,0 으로부터 마우스가 있는 지점까지 길을 찾은 다음 디버그 라인을 그리는 것까지 구현하는 내용이었다. 아직 장애물이 아직 없기 때문에 결과가 특별하진 않다.
다음 수업에서는 장애물을 고려해서 개선한다고 한다.
예전에 잠깐 공부했던 알고리즘 PS 가 이렇게 도움이 될 줄이야. A* 알고리즘이 다익스트라랑 매우 비슷하기도 해서, 이해가 쉬웠다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
Pathfinding 을 본격적으로 구현하기에 앞서 해당 구현을 가시적으로 볼 수 있는 디버그용 객체를 생성.
Pathfinding 클래스 및 개임객체 추가Pathfinding 에서 사용할 그리드 객체인 PathNode 추가GridDebugObject 를 활용해 PathfindingGridDebugObject 를 추가LevelGrid 에서 기존에 그리던 GridDebugObject 를 그리지 않도록 임시 처리하고Pathfinding 에서 PathfindingGridDebugObject 를 그리게 함본격적인 Pathfinding 구현은 다음 강의부터 할듯.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
그냥 generic 개념을 가르쳐준다. 특별할 건 없다.
generic 을 GridSystem 에 적용했는데, 본격적으로 적용했다기 보다는 다음 강의에 앞서 사전 작업을 해두었다는 느낌.
경로 찾기 알고리즘에 대해 설명하는데, A Star Algorithm 이라고 한다. 찾아보니 다익스트라를 확장한 알고리즘이라고 한다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
UnitManager 스크립트를 추가해서 해당 스크립트에서 모든 Unit 을 갖고 있도록 구현.
UnitManager 는 모든 Unit 의 생성보다 빠르게 초기화되어 Unit 의 생성 이벤트를 들을 수 있어야 하므로, Edit - Project Settings - Script Execution Order 에서 순서 조정이 필요하다.
이후 예전에 만들어 두었던, 턴이 돌아오면 일정 시간 이후 NextTurn 만 하게 작성해두었던 EnemyAI 스크립트를 수정한다.
UnitManager 로부터 모든 적 Unit 목록을 가져온다.Unit 목록을 순회하면서 액션이 가능한 Unit 들의 액션을 실행한다.이번 강의에서는 적 Unit 이 SpinAction 만 하게 구현했는데, 다음 강의에서 개선할 것으로 보인다.
각 액션에 점수를 계산할 수 있는 로직을 넣고, 적 Unit 액션 실행 전 실행 가능한 액션들의 점수를 비교해 점수가 가장 높은 액션을 실행하게끔 구현.
점수를 관리하는 클래스 (지금 시점에서는 struct 로 작성해도 문제 없어보이는 클래스) EnemyAIAction 클래스를 만들어서 사용한다. 해당 클래스에는 gridPosition 과 점수 필드가 존재하는데, 이는 같은 액션이라 할지라도 어느 지점으로의 액션인지에 따라 점수가 다를 수 있기 때문.
액션 관련한 비효율적인 코드가 몇몇 눈에 띄는데, 후속 강의에서 개선할 것으로 보인다.
Pathfinding 섹션에 대한 소개.
맵에 지나다닐 수 없는 지형지물 (예, 벽) 을 만들고, 그런 맵 위에서 적에게 접근할 수 있는 최적의 경로를 찾는 알고리즘을 적용한다고 한다.
맵을 구현하기 위해 GridSystem 에 대한 리팩토링이 필요하고, 이를 위한 c# generic 에 대해서도 간단히 알려준다고 한다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
Unit Prefab 내부에 UI Canvas 생성. Render Mode 는 World Space 로.
UI 만 만들어 붙이면 캐릭터가 회전할 때 같이 회전해서 보기 불편하므로 항상 카메라를 바라보도록 하는 스크립트 LookAtCamera 도 작성해서 붙여준다.
LateUpdate 라이프 사이클도 사용. 이건 업데이트가 끝난 직후에 실행된다.
UI Image 객체를 사용할 때, 단순 색상 적용이 아닌 이미지 리소스를 적용해야만 보이는 옵션이 있다. 이 경우 Image - Source Image 에서 동그라미를 클릭해 Unity 에서 기본으로 제공하는 리소스들을 적용해 사용하면 간편하다. 예를 들어 white1px 를 적용하면 색상 적용도 잘 되고, 이미지 리소스 적용 시에만 적용할 수 있는 옵션도 적용할 수 있다. 이 옵션 적용으로 이미지를 체력바처럼 적용할 수 있다.

기존 카메라인 CinemachineVirtualCamera 를 복사해서 ActionVirtualCamera 를 만들고, ActionVirtualCamera 의 Priority 를 올리고 필요할 때마다 활성화/비활성화 해서 사용.
Priority 높은 카메라가 활성화되는 순간 자동으로 해당 카메라로 transition 발생. 이 transition 의 메서드 및 속도는 MainCamera 의 CinemachineBrain 에서 설정.
ActionVirtualCamera 카메라를 총 쏘는 Unit 의 어깨 오른쪽 위에 두고 타겟을 바라보게끔 하는 계산
switch(sender)
{
case ShootAction shootAction:
Unit shooterUnit = shootAction.GetUnit();
Unit targetUnit = shootAction.GetTargetUnit();
Vector3 cameraCharacterHeight = Vector3.up * 1.7f; // 실제 `Unit` 의 (지면으로부터의) 어깨 부근 좌표
Vector3 shootDir = (
targetUnit.GetWorldPosition() - shooterUnit.GetWorldPosition()
).normalized;
float shoulderOffsetAmount = 0.5f; // 어깨에서 오른쪽? 으로 얼마나 띄울 것인가
Vector3 shoulderOffset =
Quaternion.Euler(0, 90, 0) * // Y 축 기준 회전
shootDir *
shoulderOffsetAmount;
Vector3 actionCameraPosition =
shooterUnit.GetWorldPosition() +
cameraCharacterHeight +
shoulderOffset +
shootDir * -1;
actionCameraGameObject.transform.position = actionCameraPosition;
actionCameraGameObject.transform.LookAt(
targetUnit.GetWorldPosition() + cameraCharacterHeight
);
ShowActionCamera();
break;
}
액션에 따라 사정거리를 표시하는 GridSystemVisual 의 색상을 변경.
enum 을 만들어서 enum 별로 Material 을 지정하고 사용할 수 있게 한다.
[Serializable]
public struct GridVisualTypeMaterial
{
public GridVisualType gridVisualType;
public Material material;
}
public enum GridVisualType
{
White,
Blue,
Red,
Yellow,
}
이외에도 실제 액션이 가능한 GridPosition 뿐만 아니라 사정거리를 표시하는 것도 구현했고, 비효율적인 렌더링 로직도 개선했다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
HealthSystem 스크립트를 만들어 Unit Prefab 에 부착. 해당 스크립트에서는 체력 수치를 관리하고 public Damage() 메서드를 갖고 있고, 체력이 0이 되었을 때 실행되는 OnDead 이벤트를 제공한다.
Unit Prefab 을 복사해서 UnitRagdoll Prefab 생성. 부착되어있던 모든 컴포넌트 삭제. 내부에 있는 Animator 컴포넌트도 삭제하고, 총기 객체는 비활성화해서 안 보이게 처리.
상단 메뉴 Game Object - 3D Object - Ragdoll 선택

알맞는 객체를 연결한 뒤에 총기 객체는 다시 활성화.
Ragdoll Prefab 으로 객체를 만들어서 씬에 두면 바로 애니메이션이 실행된다.
충돌 애니메이션이 이상하면 Collider 를 수정해보자. 강의에서는 이상하게 작게 설정되어있던 Collider 하나를 크게 만들어줘서 해결했다.
Ragdoll 설정이 끝났으면, Unit 이 죽을 때 Ragdoll 을 생성해서 보여주자.
public class UnitRagdollSpawner : MonoBehaviour
{
// ...
private void Awake()
{
// ...
healthSystem.OnDead += HealthSystem_OnDead;
}
private void HealthSystem_OnDead(object sender, EventArgs e)
{
Transform ragdollTransform = Instantiate(ragdollPrefab, transform.position, transform.rotation);
UnitRagdoll unitRagdoll = ragdollTransform.GetComponent<UnitRagdoll>();
unitRagdoll.Setup(originalRootBorn);
}
}
그냥 생성만 해버리면 기존 Unit 이 자세와 달리 T 포즈로 나와서 쓰러질 것이므로, 기존 Unit 의 자세와 일치시키자.
public class UnitRagdoll : MonoBehaviour
{
[SerializeField] private Transform ragdollRootBorn;
public void Setup(Transform originalRootBorn)
{
MatchAllChildTransform(originalRootBorn, ragdollRootBorn);
}
private void MatchAllChildTransform(Transform root, Transform clone)
{
foreach(Transform child in root)
{
Transform cloneChild = clone.Find(child.name);
if(cloneChild != null)
{
cloneChild.position = child.position;
cloneChild.rotation = child.rotation;
MatchAllChildTransform(child, cloneChild);
}
}
}
}
오오 래그돌 재밌다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
Effect - Trail 객체를 생성해서 총 쏜 효과를 만든다.
만든 Trail 객체를 Prefab 으로 만든다.
Unit Prefab 내부의 총 객체 안에 ShootPoint 객체를 만들어서 총구에 위치시킨다.
이벤트 리스너에서 원하는 데이터 형식 받기
public event EventHandler<OnShootEventArgs> OnShoot;
public class OnShootEventArgs : EventArgs
{
public Unit targetUnit;
public Unit shootingUnit;
}
private void Shoot()
{
OnShoot?.Invoke(this, new OnShootEventArgs {
targetUnit = targetUnit,
shootingUnit = unit,
});
targetUnit.Damage();
}
ShootAction 에서 총을 쏘면 이벤트를 듣고 있던 UnitAnimator 에서 총알 발사 애니메이션 실행.
BulletProjectile) 으로 객체를 생성해서 움직인다.private void ShootAction_OnShoot(object sender, ShootAction.OnShootEventArgs e)
{
animator.SetTrigger("Shoot");
Transform bulletProjectileTransform =
Instantiate(bulletProjectilePrefab, shootPointTransform.position, Quaternion.identity);
BulletProjectile bulletProjectile = bulletProjectileTransform.GetComponent<BulletProjectile>();
Vector3 targetUnitShootAtPosition = e.targetUnit.GetWorldPosition();
targetUnitShootAtPosition.y = shootPointTransform.position.y;
bulletProjectile.Setup(targetUnitShootAtPosition);
}
public class BulletProjectile : MonoBehaviour
{
private void Update()
{
Vector3 moveDir = (targetPosition - transform.position).normalized;
float deistanceBeforeMoving = Vector3.Distance(transform.position, targetPosition);
float moveSpeed = 200f;
transform.position += moveSpeed * Time.deltaTime * moveDir;
float distanceAfterMoving = Vector3.Distance(transform.position, targetPosition);
if(deistanceBeforeMoving < distanceAfterMoving)
{
trailRenderer.transform.parent = null;
Destroy(gameObject);
}
}
}
총 쏜 이펙트가 좀 더 자연스럽게 사라지게 하기 위해 BulletProjectile 객체를 없애기 전에 trailRenderer 의 부모를 null 로 하고, autodestruct 옵션을 켜면 trail 애니메이션이 끝날 때 자연스럽게 사라진다.
총 맞은 이펙트를 만들기 위해 Effect - Particle 객체를 만드는 데 옵션을 꽤 많이 설정한다. 일일이 기록하진 않겠다. 그냥 기억 안나면 강의를 다시 보자.
Trail, Particle 과 Material 을 직접 만들어서 적용했는데 퀄리티가 괜찮다. 다음 강의가 Health System 이니 점점 게임 같아지는 것 같아서 재밌다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
ShootAction 을 추가하고 기본 기능을 구현.
여기서 기본 기능이라 함은 GetValidActionGridPositionList, TakeAction, Update 메서드를 구현해서 다른 액션 클래스들과 같이 동작하게 하는 것인데, ShootAction 에서는 state 개념까지 추가했다. 아마 다음 강의에서 이 state 를 사용해 총을 쏘는 애니메이션을 결합하지 않을까 싶다.
애니메이션을 전담할 UnitAnimator 클래스 생성
걷기 애니메이션 때는 Animaotr 에서 파라미터로 IsWalking Bool 을 추가했지만, 이번에는 Shoot Trigger 를 추가. Trigger 는 transition 을 한 번 실행하고 종료시키는 일회성 값. (Bool 보다 더 간단한 값이라고 이해하면 될듯)
"Firing Rifle" 로 가는 transition 은 hasExitTime 을 껐지만 "Firing Rifle" 에서 "Idle" 로 나오는 transition 은 hasExitTime 을 켜둔다. 총은 쏘면 자동으로 애니메이션이 끝나는 것이 자연스러우니까.
총알이 발사되지는 않고 총을 쏘는 듯한 액션만 취하는데, 총알은 다음 강의에 추가할 듯.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
TurnSystem, TurnSystemUI 를 추가해서 턴 종료 및 초기화 기능 구현.
이 과정에서 이벤트 리스너 실행 순서 이슈가 생길 수 있는 코드가 있어서, 해당 이슈 해결도 포함.
첫 번째 해결 방법은 Edit - Project Settings - Script Execution Order 메뉴를 사용해 스크립트의 초기화 순서를 강제하고, 그에 따른 이벤트 리스너 부착 순서를 강제해서 실행 순서가 정해지게 하는 방법.
당연하게도 이 방법은 사이드 이펙트를 일으킬 가능성도 높고 여러 리스너가 복잡하게 얽혀 있을 경우에는 근본적인 해결책이 될 수 업다.
다른 해결 방법은 이벤트 리스너를 더 추가해서 해결하는 방법인데, 턴이 종료되었을 때 뿐만 아니라 유닛의 액션 포인트가 바뀌었을 때에 대해서도 이벤트 리스너를 추가했다.
TurnSystem.Instance.OnTurnChanged += TurnSystem_OnTurnChanged;
Unit.OnAnyActionPointsChanged += Unit_OnAnyActionPointsChanged;
Enemies & Combat 섹션 소개.
기존 Unit Prefab 으로 Create - Prefab Variant 메뉴를 선택해 새로운 Prefab UnitEnemy 생성.
Unit 과 UnitEnemy 가 공통으로 사용하는 클래스 Unit 에 isEnemy 플래그 추가.TurnSystem 에는 isPlayerTurn 플래그 추가.End Turn 버튼 누르면 enemy 에게 턴이 넘어가고, 적 턴에는 특정 시간이 지난 후 턴을 넘기도록 구현. 적 AI 는 이후 강의에서 자세히 구현할 듯.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
예상대로 액션 메서드와 유효성 검사 메서드를 일반화
public abstract class BaseAction : MonoBehaviour
{
public abstract void TakeAction(GridPosition gridPosition, Action onActionComplete);
public virtual bool IsValidActionGridPosition(GridPosition gridPosition)
{
List<GridPosition> validGridPositionList = GetValidActionGridPositionList();
return validGridPositionList.Contains(gridPosition);
}
public abstract List<GridPosition> GetValidActionGridPositionList();
}
액션 실행 코드도 매우 간결해짐
if(selectedAction.IsValidActionGridPosition(mouseGridPosition))
{
SetBusy();
selectedAction.TakeAction(mouseGridPosition, ClearBusy);
}
좀 더 개선할 수 있을 것 같긴 한데, 강의에서도 추후 계선할 것이라고 하니 기다려보기로.
현재 선택된 액션을 UnitActionSystem 에 저장해두고, 액션이 바뀔 때마다 UnitActionSystemUI 의 모든 버튼을 순회하면서 저장해둔 액션과 동일한 액션을 들고 있는 버튼에 활성화 표시를 해준다.
액션 중에는 UnitActionSystemUI 를 사용하지 못하도록, 새로운 UI 인 ActionBusyUI 를 만들어서 해당 UI 로 UnitActionSystemUI 를 가려버리는 기능을 구현. 이 강의에서 드디어 파라미터를 전달하는 이벤트 핸들러 코드가 등장.
액션마다 포인트를 부여하고 액션을 사용하면 포인트를 소모하는 로직 구현
각 액션 클래스에서는 자신이 몇 포인트가 필요한 액션인지 알려줄 메서드 작성
public virtual int GetActionPointCost()
{
return 1;
}
Unit 은 자신의 액션 포인트를 소유하고 있고, 특정 액션에 대해 해당 액션을 실행할 수 있는 액션 포인트가 있는지 확인하고 소모하는 메서드들을 작성
public bool TrySpendActionPointsToTakeAction(BaseAction baseAction)
{
if(CanSpendActionPointsToTakeAction(baseAction))
{
SpendActionPoints(baseAction.GetActionPointCost());
return true;
} else
{
return false;
}
}
public bool CanSpendActionPointsToTakeAction(BaseAction baseAction)
{
return actionPoints >= baseAction.GetActionPointCost();
}
private void SpendActionPoints(int amount)
{
actionPoints -= amount;
}
UnitActionSystem 에서는 액션을 실행하기 전 액션 포인트를 확인하고 소모하는 로직을 추가. 그리고 UI 를 업데이트하는 로직도 추가
if(!selectedUnit.TrySpendActionPointsToTakeAction(selectedAction))
{
return;
}
SetBusy();
selectedAction.TakeAction(mouseGridPosition, ClearBusy);
OnActionStarted?.Invoke(this, EventArgs.Empty);
코드 마지막줄에 나온 OnActionStarted 콜백을 통해 UnitActionSystemUI 가 업데이트 됨
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
마지막에 누른 버튼 기준으로 어떤 액션을 할 지 결정.
switch (selectedAction)
{
case MoveAction moveAction:
moveAction.Move();
break;
case SpinAction spinAction:
spinAction.Spin()
break;
}
다음 강의에서 위 코드를 개선한다고 하는데 아마 Move(), Spin() 메서드를 일반화하지 않을까 싶다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
씬에 UI - Canvas 생성
Canvas 안에 UI - Image 생성
Image 는 테스트로 생성해봤던 것인지 다음 강의에서는 삭제되어있다.
Canvas 안에 UnitActionSystemUI 게임 객체 생성
이후 UnitActionSystemUI 에서 각 유닛이 선택될 때마다 가능한 액션 목록을 가져와 버튼을 동적으로 만드는 코드를 작성함.
눈여겨볼만한 코드는 공통 부모 클래스인 BaseAction 클래스를 사용해 액션 클래스들을 한 번에 다 가져와 사용하는 것과
baseActionArray = GetComponents<BaseAction>();
UnitActionSystemUI 에서 UnitActionSystem 의 OnSelectedUnitChanged 를 활용하는 것과
private void Start()
{
UnitActionSystem.Instance.OnSelectedUnitChanged += UnitActionSystem_OnSelectedUnitChanged;
CreateUnitActionButtons();
}
private void UnitActionSystem_OnSelectedUnitChanged(object sender, EventArgs e)
{
CreateUnitActionButtons();
}
BaseAction 에 적절한 abstract 함수를 만들어 사용하는 것
public abstract string GetActionName();
특히 마지막 코드는 앞으로도 클래스 상속을 제대로 활용할 것 같아서 기대가 크다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
테스트를 위해 SpinAction 을 추가, 유닛이 회전할 수 있는 함수 추가. MoveAction 도 움직이는 방향을 향해 유닛을 회전시키므로 두 액션이 충돌. 따라서 우리는 (어차피 턴베이스 게임이므로) 한번에 한 액션만 할 수 있게 해서 이 충돌을 막는다.
충돌을 막기 전에 코드를 정리한다. BaseAction 이라는 추상 부모 클래스를 만들어서 이 클래스에서 공통 로직을 관리하고 SpinAction 과 MoveAction 이 이 로직을 상속받게 한다.
아직 BaseAction 에 별 코드가 없기 한데, 이후 강의에서 점점 더 많아질 듯.
동시에 두 액션이 실행되는 걸 막기 위해 UnitActionSystem 에 플래그를 세우고, 각 액션에는 액션 성공 delegate (= 콜백) 를 전달해서 액션 시작/종료 시에 플래그 값을 변경하도록 한다.
delegate 를 직접 선언할 수도 있지만
public delegate void SpinCompleteDelegate();
private SpinCompleteDelegate onSpinComplete;
c# 에서 제공하는 delegate 클래스를 가져다 쓸 수도 있다.
using System;
private Action onSpinComplete;
onSpinComplete 도 onActionComplete 라고 이름 바꿔서 BaseAction 클래스에 일반화.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
어제 해결 못한 에러를 간신히 해결. GridDebugObject 가 prefab 이외에도 객체로 씬에 존재하고 있어서 생긴 문제였는데, 정확한 이유는 모르겠다. 여튼 해당 객체를 삭제해서 에러 해결.
이동과 관련된 구현을 별도의 스크립트로 나누고, 해당 스크립트에서 이동 가능한 GridPosition 을 값 검증 없이 디버깅 로그로 찍어보는 것까지 진행.
값 검증을 해서 유효한 GridPosition 을 뽑아서 실제로 GridPosition 으로 이동시키는 건 다음 강의에서 할 듯.
유효한 GridPosition 을 찾아내는 코드를 작성. 유효한 GridPosition 이라 함은
GridSystem 안에 위치하는지Unit 이 머무르는 위치는 아닌 것인지Unit 이 머무르는 위치는 아닌 것인지.를 체크하는 것.
LevelGrid, GridSystem, GridObject, GridPosition, Unit, UnitActionSystem, MoveAction 각 클래스의 역할과, 클래스를 분리하고 메서드를 만드는 컨벤션에 익숙해질 필요가 있어보인다.
Unit 의 이동 가능한 GridPosition 을 강조하는 UI 구현. 필요한 GridPosition 마다 동적으로 생성하는 게 가장 효율적이지만, 해당 구현은 다소 복잡하기 때문에 (성능 개선은 나중에 하기로 하고) 당장은 쉬운 방법으로 구현.
GridSystemVisualSingle 이라는 빈 객체 생성.
Quad 객체 생성
Quad 의 Mesh Renderer - Materials - Element 0 에 지정Quad 객체에서 관리. 위치는 GridSystemVisualSingle 에서 관리GridSystemVisual 이 현재 보여줘야할 grid UI 를 선택하고, 각 GridSystemVisualSingle 을 Show/Hide 한다.
이번 강의에서는 단순히 UnitActionSystem 의 selectedUnit 기준으로 움직일 수 있는 칸을 표시하게끔 했지만 이후 강의에서는 좀 더 많은 조건에 대응 가능하도록 한다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
"Grid System & Camera" 섹션이 끝나고 "Actions and UI" 섹션으로 진입. 이 섹션에서는 유닛들의 액션과 UI 를 구현하는 것에 대해 배운다.
이동 관련 코드를 별도의 스크립트 MoveAction 으로 분리한 뒤 실행했는데, 갑자기 런타임 에러가 발생. 다행히 이전 강의까지 듣고 커밋을 해놓은 상태여서 stash 해보았는데도 여전히 런타임 에러 발생. 로그를 찍으며 원인을 찾아보고 있는데 쉽지 않다..
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
카메라 컨트롤을 위해 Cinemachine 패키지를 프로젝트에 설치
Cinemachine 의 Virtual Camera 게임 오브젝트를 프로젝트에 추가하면 Main Camera 객체와 연결된다. 이제 카메라 설정 및 조작은 Cinemachine 의 Virtual Camera 를 통해 한다.
Cinemachine Virtual Camera 설정
private void Update()
{
Vector3 inputMoveDir = new Vector3(0, 0, 0);
if(Input.GetKey(KeyCode.W))
inputMoveDir.z = +1f;
if(Input.GetKey(KeyCode.S))
inputMoveDir.z = -1f;
if(Input.GetKey(KeyCode.A))
inputMoveDir.x = -1f;
if(Input.GetKey(KeyCode.D))
inputMoveDir.x = +1f;
float moveSpeed = 10f;
Vector3 moveVector = transform.forward * inputMoveDir.z
+ transform.right * inputMoveDir.x;
transform.position += moveSpeed * Time.deltaTime * moveVector;
Vector3 rotationVector = new Vector3(0, 0, 0);
if(Input.GetKey(KeyCode.Q))
rotationVector.y = +1f;
if(Input.GetKey(KeyCode.E))
rotationVector.y = -1f;
float rotationSpeed = 100f;
transform.eulerAngles += rotationSpeed * Time.deltaTime * rotationVector;
}
강사는 줌 방식을 카메라의 followOfset 의 y 축을 조정하는 방식으로 구현한다. 이는 개인에 따라 원하는 줌 방식이 아닐 수 있으므로, 원하지 않을 경우 다른 구현 방법을 찾아보자.
Cinemachine 패키지가 followOffset 을 조정할 수 있는 친절한 인터페이스를 제공하지 않으므로, 일련의 과정을 통해 Cimemachine 내부에서 followOffset 을 관리하는 컴포넌트를 찾아낸 다음, 해당 컴포넌트를 통해 해당 값을 조정한다.
float zoomAmount = 1f;
if(Input.mouseScrollDelta.y > 0)
{
targetFollowOffest.y -= zoomAmount;
}
if(Input.mouseScrollDelta.y < 0)
{
targetFollowOffest.y += zoomAmount;
}
targetFollowOffest.y = Mathf.Clamp(targetFollowOffest.y, MIN_FOLLOW_Y_OFFSET, MAX_FOLLOW_Y_OFFSET);
float zoomSpeed = 5f;
cinemachineTransposer.m_FollowOffset = Vector3.Lerp(
cinemachineTransposer.m_FollowOffset,
targetFollowOffest,
Time.deltaTime * zoomSpeed
);
한 가지 주의할 것은, Lerp 함수의 설명은 "Linearly interpolates between two points." 이지만, 적용한 구현 상 실제로는 linear 하게 적용되지 않고 ease out 방식으로 적용될 것이다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
GridSystem 위에서 Unit 이 움직일 때 올바른 위치의 GridPostiion 에 위치시키는 코드를 작성.
그것을 위해 GridPosition struct 에 ==, !=, Equals 등의 operator 들을 overriding 했다.
LevelGrid (singletone)
GridSystem
GridObject (2d array)
GridPositionUnit (array)
GridPositionGridSystem 바깥으로 Unit 이 나갈 때 앱이 죽는 건 추후 처리할 예정.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
GridObject.ToString() 을 구현, GridObject 의 위치에 GridObject.ToString() 을 렌더링해줄 GridDebugObject 를 구현
LevelGrid 클래스를 추가. 해당 클래스는 gridSystem 객체를 갖고 있고, unit 을 gridSystem 의 특정 gridObject 에 할당할 수 있는 메서드를 제공함.
일단 오늘은 반절 정도만 들어서 딱 위 정리한 내용만큼 구현했고, 남은 내용은 unit 이 위치 이동을 할 때 알맞은 gridObject 에 자동으로 할당하는 것.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
그리드 시스템 관리를 위한 GridSystem 클래스를 새로 생성.
MonoBehaviour 를 상속받지 않음.MonoBehaviour 를 상속 받지 않기 때문에 생성자를 직접 구현해야 함.그리드 상의 위치를 표현하기 위해 GridPosition 이라는 struct 생성
Debug.DrawLine(): 디버깅을 위해 선을 그릴 수 있다.
GridSystem 의 각 그리드 칸에 쓰일 클래스 GridObject 생성.
모든 GameObject 는 Transform 을 가지고 있다.
모든 Transform 은 GameObject 를 가지고 있다.
따라서 둘은 웬만한 상황에서 interchangable 하다. 취향과 상황에 맞게 골라 사용하자.
텍스트 오브젝트 가운데 정렬 편하게 하는 법 = 가로 가운데 정렬 + 세로 가운데 정렬 + 너비 0 + 높이 0 + Wrapping Disabled
이번 강의에서는 클래스 만들어서 GridSystem 안에서 필요한 만큼 객체를 만들어서 멤버로 할당하는 것 까지만.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
선택된 유닛을 강조하기 위한 별도의 3D 오브젝트 SelectedVisual 를 만들어 Unit Prefab 에 부착.
해당 오브젝트에는 SelectedUnitVisual 이라는 이름의 스크립트 부착.
SelectedUnitVisual 스크립트는 현재 Unit 이 선택된 Unit 인지 아닌지에 따라 SelectedUnit 오브젝트를 보이게/안보이게 함.
Observer Pattern 으로 selectedUnit 이 바뀔 때마다 이벤트 발송.
public event EventHandler OnSelectedUnitChanged;
private void SetSelectedUnit(Unit unit)
{
seleectedUnit = unit;
OnSelectedUnitChanged?.Invoke(this, EventArgs.Empty);
}
public Unit GetSelectedUnit()
{
return seleectedUnit;
}
관련해서 설명을 많이 해줬는데 이 강의가 유난히 말이 빠르고 발음이 구분이 잘 안 되어서, 코드로 보이는 것 이상은 잘 이해 못한 것 같다. 그나마 다행인 건 관련해서 이후 강의에서 코드를 개선할 예정이라고 하니, 그 때 다시 설명을 듣고 자세히 이해하는 것으로 하자.
Unit Prefab 내부에 있는 SelectedUnitVisual 스크립트는 Prefab 외부에 있는 (= 씬에 있는) 객체의 레퍼런스를 알 수 없다. 따라서 이벤트를 수신할 수 없는 상황인데, 이건 다음 강의에서 Singletone Pattern 을 사용해서 해결한다.
강의에 나온 Singletone Pattern 코드는 익히 알려진 것과 크게 다르지 않았음.
Awake() 가 Start() 보다 항상 먼저 실행됨. 따라서 내부 초기화는 Awake() 에서, 외부와 연결된 초기화는 Start() 에서 하면 좋다.
private void Start()
{
UnitActionSystem.Instance.OnSelectedUnitChanged += UnitActionSystem_OnSelctedUnitChanged;
UpdateVisual();
}
private void UnitActionSystem_OnSelctedUnitChanged(object sender, EventArgs empty)
{
UpdateVisual();
}
private void UpdateVisual() {
Unit selcectedUnit = UnitActionSystem.Instance.GetSelectedUnit();
if(unit == selcectedUnit)
{
meshRenderer.enabled = true;
} else
{
meshRenderer.enabled = false;
}
}
Grid 시스템과 카메라 조작을 구현하는 섹션으로 넘어가봅시다.
Grid 시스템 설계 개요.
Unit 를 위한 .gitignore https://github.com/github/gitignore/blob/main/Unity.gitignore
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
Animation Paramters 는 네 종류가 있다. Float, Int, Bool, Trigger.
Trigger 는 boolean 비슷한 특수한 값이다. true 로 값을 바꾼 뒤 false 로 직접 바꿔줄 필요가 없다. 나중에 shoot 애니메이션 (총 쏘는 애니메이션?) 적용 시 사용해 볼 것이다.
Animator - Parameters 메뉴에 파라미터를 추가하고, Transition 의 Inspector - Conditions 섹션에서 해당 파라미터를 조건으로 지정해준다.

그 다음 스크립트에서 코드 작성
[SerializeField] private Animator unitAnimator;
unitAnimator.SetBool("IsWalking", true);
unitAnimator.SetBool("IsWalking", false);
객체를 회전시키는 방법은 세 가지.
transform.rotation: Quaternion 사용transform.eulerAngles: 각도 사용transform.forward: Vector 사용여기서는 가장 간단한 transform.forward 사용
private void Update()
{
// ...
float rotateSpeed = 10f;
transform.forward = Vector3.Lerp( // 두 백터를 interpolate 해주는 함수
transform.forward, // 현재 방향 백터. 매 프레임마다 바뀔 수 있음.
moveDirection, // 목표 방향 백터.
Time.deltaTime * rotateSpeed // rotateSpeed 를 곱해주지 않으면 너무 느리다.
);
// ...
}
여러 유닛 중 원하는 유닛을 선택하는 기능 구현.
일단 씬에 있던 기존 Unit 객체를 Project 로 드래그앤드랍 해서 재사용 가능하게 만들고, 재사용 가능해진 객체 (강의에서는 Prefab 이라고 말한다.) 를 다시 드래그앤드랍 해서 씬에 Unit 객체를 추가.
특정 객체 선택을 위해서는 마우스 Raycast 기능을 써야 하는데, 이 경우 객체가 충돌 가능해야 한다. 하지만 현재 Unit 객체는 선택이 가능하지 않으므로 Collider 를 설정해줘야 한다.
Unit Prefab 에 Layer 도 "Units" 이라는 이름으로 새로 만들어줘서 지정.
private bool TryHandleUnitSelection()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(
ray,
out RaycastHit raycastHit,
float.MaxValue,
unitLayerMask
))
{
if(raycastHit.transform.TryGetComponent<Unit>(out Unit unit))
{
seleectedUnit = unit;
return true;
}
}
return false;
}
특정 스크립트의 초기화를 빨리 하고 싶을 때는 Edit - Settings - Script Excution Order 를 사용.

apply 누르는 순간 값이 자동으로 조금 바뀌는 데 중요한 건 아닌듯.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
Animator 컴포넌트를 사용해 애니메이션을 객체에 연결하는 방법을 설명한다.

강사 아저씨가 말이 빨리지거나 발음이 뭉게질 때 좀 힘들다. 자막도 자동 생성 자막이라 그럴 때는 큰 도움이 안 되고. 일단은 큰 맥락을 이해할 수 있다면 모든 문장과 단어를 완벽하게 해석하는 건 적당히 넘어가고 있다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
이전 강의들에서 했던 "객체를 움직이게 하는 것"과 "마우스 클릭 지점을 찾는 것"을 조합해서 마우스 클릭 지점으로 객체를 움직이는 내용.
public 필드: Unity 에디터와 모든 다른 스크립트에서 접근 가능private 필드: 자기 자신만 접근 가능. Unity 에디터도 다른 모든 스크립트도 접근 불가.[SerializeField] private 필드: 자기 자신과 Unity 에디터에서만 접근 가능. 다른 모든 스크립트는 접근 불가.애니메이션 파일을 다운 받고 해당 파일 초기화만 진행.. 객체에 연결하는 건 다음 강의인가..
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
Camera.main 코드가 항상 카메라를 찾아왔기 때문에 비용이 비쌌지만, 2020년 버전 이후로는 캐싱이 자동으로 되기 때문에 그런 문제가 사라졌다.out. 분명 전에도 본 적 있는데 볼 때마다 신기하다.
Physics.Raycast(ray, out RaycastHit raycastHit
강의에서 C# 스크립트를 수정하기 시작했는데 VS Code 에서 여전히 Unity 코드의 자동 완성 기능이 동작하지 않아서, 답답해서 강의를 멈추고 찾아봤다.
일단 구글에서 "vscode unity autocomplete" 라고 검색해서 돌아다니다보니 보인 영상 Set up Visual Studio Code with Unity and INTELLISENSE WORKING 2023을 따라해봤는데 잘 되지 않았다.
그런데 해당 영상에서 VS Code 의 extension 에러 메시지를 확인해보라는 말이 있어서 확인해보았는데, 거기에 아래와 같은 에러가 있었다.
"Microsoft.VisualStudio.ProjectSystem.Query.Remoting.QueryExecutionService (0.3)" 서비스를 활성화하지 못했습니다.
해당 에러로 검색을 해보니 [BUG] C# Dev Kit installed but not working #1732 글이 나왔고, 해당 글의 코멘트에서 아래와 같은 내용을 찾았다.
To do that, you need to uncheck this setting located under
Extensions->C#->C# Dev Kit:Dotnet: Use Legacy Dotnet Resolution
Please make sure you reload VS Code window after changing this setting.
바로 해당 설정을 한 뒤 Developer: Reload Window 를 해보니 위 에러는 없어지고 다른 에러가 나왔다. 지금 와서 찾으려니 정확한 에러 메시지를 찾을 수 없는데, "Apple CPU 인데 x64 용 .net SDK 가 깔려있는 것 같다" 는 내용의 에러였다.
그래서 .net 에서 공식으로 제공하는 .net 삭제 스크립트를 사용해 기존에 깔려있던 .net SDK 를 삭제한 뒤, Apple Sillicon CPU 용 .net 9.0 SDK 를 재설치했다.
재설치한 후에는 뭔가 알 수 없는 에러가 떠서 깔끔하게 재부팅을 했고, 재부팅 후에는 Unity 코드들의 자동 완성 및 타입 추론이 잘 되는 것을 확인할 수 있었다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
중급자용 코스라서 조금 걱정을 했는데, 이 정도 수준도 (짧지만) 별개의 강의로 만들어 줄 정도라면 앞으로도 따라가는 데 무리는 없을 것 같다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
이제야 "Introduction" 섹션을 끝내고 "Unit Movement & Selection" 섹션에 진입.
Add two nodes, a Vector 3 node (Under Input, Basic, Vector3) and add node (under Math, Basic).
In the Vector 3 node set X and Z to 1 and set Y to 0.
Connect the Vector3 out connector to one of the in connectors on the Add node.
Remove the connector from the position node on the triplanar
Attach the out connector from the Position node to the other connector on the Add node.
The connect the out of the add node to the position connector on the Triplanar node.
씬뷰 "Q" 상태에서 맥 트랙패드
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
렌더링되고 있는 씬에 렌더링 효과를 후처리하는 것. 이 강의에서는 몇몇 더미 오브젝트를 추가한 뒤에 여러 효과를 적용해보며 각 효과들이 어떤 변화를 주는지 알아보았다.
아래 옵션들을 추가 및 변경했다.
상세한 효과는 직접 조정해보면서 다시 확인해보자.
어차피 나중에 다시 보게 될 값들인 것 같아서 지금 자세히 들여다보지는 않았다.
아 그런데 15분도 안 되는 강의인데 실제로는 다 듣기까지 거의 1시간이 걸렸는데.. 영어라 더 걸리는 것 같다.
씬뷰에서 움직이기가 불편해서 단축키를 찾아봤다. https://docs.unity3d.com/kr/2018.4/Manual/UnityHotkeys.html
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
강의에서 사용된/작성된 코드에 접근하는 방법에 대해 안내. 주로 GitLab 으로 관리하는 듯 하다.
Unity Hub 에서 3D URP 프로젝트를 새로 생성한 후, 본격적인 개발 강의에 앞서 IDE 설정을 진행.
영어 강의는 확실히 자막이 있더라도 한국어 강의보다 집중력을 몇 배로 요구하는 것 같다. 자막도 자동 생성된 자막이라 백퍼센트 맞지 않아서 더더욱 그렇다. "더욱 집중하게 해주고 영어 듣기 연습을 시켜준다"고, 긍정적으로 생각할 수는 있겠다.
만든 블로그를 GitHub Pages 에 배포해보았다. 그런데 404 가 뜨길래 서브도메인 관련 설정을 잘못했나 하고 찾다가 나중에야 배포 과정에서의 빌드 문제인 것을 확인했다. 결국 빌드 에러까지 다 수정해서 제대로 배포되는 것을 확인했다.
블로그는 일단 글 목록 페이지는 문제 없이 잘 보이는데 글 상세 페이지의 스타일은 제대로 정리되지 않았고, 글 상세 페이지에서는 새로고침 시 글에 첨부한 이미지가 안 보이는 등의 문제가 있다. 하지만 글 목록 페이지에서 글의 모든 내용을 다 보이게 해놨기 때문에 큰 문제는 없다. 당장 블로그 구현에 시간을 많이 투자하고 싶지는 않기 때문에, 여차하면 글 상세 페이지 자체를 없애는 것도 염두에 두고 있다.
강의 수강: Unity Turn-Based Strategy Game: Intermediate C# Coding
"6. Reminder Of C# Fundamentals (15:02)" 강의를 들었다.
해당 강의는 C# 의 기초 문법과 Unity 의 라이프 사이클 함수에 대한 리마인드 를 했다.
들으며 느낀건데, 이 강의는 Unity 경험도 있는 사람을 대상으로 한 중급자 코스다.. 내게 Unity 경험이래봐야 3년 전에 들은 인프런 강의 뿐인데.. 앞으로 잘 들을 수 있을런지 걱정이 된다. 일단은 더 들어보다가 도저히 안 되겠으면 초급자 코스를 찾아가야겠다.
Unity Turn-Based Strategy Game: Intermediate C# Coding 의 "4. Set Up Unity & VS Code (10:20)" 영상을 보면서 개발 환경 구성을 시도했다.
해당 영상에서 2021.1 버전을 설치하라고 해서 설치하려고 했는데, Unity 홈페이지에서 받은 설치파일로 설치를 하려고 하면 에러 메시지가 계속 떴다.

맥의 "개인정보 보호 및 보안" 설정과 관련된 이슈 같은데, 문제는 회사 노트북이라 해당 설정 수정이 안 된다는 것. 학습용이라 회사에 이야기하면 임시로라도 풀어줄 가능성이 높지만 그 경우 주말을 날려야 한다.

"그래도 열기" 버튼을 눌러도 설치로 넘어가지 않는다.
어쩔 수 없이 방법을 선회, Unity Hub 를 설치 후 거기서 Unity 를 설치했다. (예전 학습 때는 Unity Hub 를 사용했던 것이 간신히 생각해냈다.) 그나마도 2021.1 버전은 없어서 2021.3.45f1 버전을 설치했다.
(어이없는 게 설치를 위해 멈춰뒀던 강의 영상을 다시 재생하니까 영상에서도 Unity Hub 를 사용하고 있다..)
설치하고 새 프로젝트 만들기 쭉쭉 따라서 진행. 잘 설치된 것까지 확인했다.
그런데 새 프로젝트에서 intellisense 가 작동하지 않아서 원인을 찾아봤는데 알 수 없었다. 다음 강의 "5. IntelliSense Issues? (01:44)" 가 intelliSense 에 관한 내용이었는데, 아쉽게도 해당 글의 내용으로는 이슈가 해결되지 않았다.
블로그 스타일도 대충 다듬었다.
공부 기록을 하면서 공부하면 더 재밌을 것 같아서 블로그를 간단히 만들기로 결정.
Gatsby 로 만든 블로그가 이미 있으나, 거기는 주전공에 대한 내용을 넣고 게임 개발은 별도로 기록하는 게 좋을 것 같아서 nextjs 로 새로 만드려고 한다.
https://cmdcolin.github.io/posts/2023-04-08-nextjs-appdir-blog 이 블로그 글을 따라해서 마크다운으로 정적 페이지 만들기가 가능한지 까지 확인.
이제 스타일만 간단히 넣으면 될 것 같다.
willow 형의 추천으로 Unity Turn-Based Strategy Game: Intermediate C# Coding 결제.
해당 강의의 아래 세 영상을 맛보기로 보았다.
실질적으로 개발에 관련된 내용은 아니고 프로젝트를 소개하는 내용이었는데, 영어 강의를 들을 수 있는지 확인하는 차원에서 들었다. 영어 자막도 있기 때문에, 어떻게든 들을 수 있을 것 같다.