1. 프로젝트 소개
- 개발 인원: 1명
- 개발 기간: 250708 ~ 250711(4일)
- C# 콘솔 환경에서 제작된 텍스트 기반 RPG 게임이다.
- 아이템 장착, 전투, 저장 기능 등 다양한 시스템이 구현되어 있으며,
간단한 키 입력만으로 진행되는 클래식한 텍스트 RPG의 매력을 담았다.
- 소스 코드: 깃허브 링크
2. 구현 기능
2-1. 키 입력 처리
- 숫자 입력이 아닌, 방향키와 z키, x키를 통해 메뉴를 탐색하고 기능을 수행한다.
2-2. 게임 시작 화면
- 게임 시작 시 간단한 소개 문구가 출력된다.
- 마을에서 수행할 수 있는 행동들을 안내한다.
2-3. 상태 보기
- 캐릭터의 6가지 속성 정보를 출력한다. 초기값은 고정되어 있으며, 아이템 장착에 따라 능력치가 변화한다.
2-4. 인벤토리
- 현재 보유 중인 아이템 전체를 출력한다. 장착 중인 아이템 앞에는 [E] 표시가 붙는다.
2-5. 장착 관리
- 인벤토리에서 원하는 아이템을 장착할 수 있다. 아이템 부위당 하나만 장착할 수 있다.
2-6. 상점
- 보유 중인 Gold와 상점 아이템 목록을 표시한다.
- 각 아이템 오른쪽에는 가격이 표시되며, 구매 시 구매완료로 표기된다.
- 원하는 아이템을 선택하여 구매할 수 있다.
2-7. 아이템 판매
- 상점에서 판매 메뉴 선택 시 보유 아이템 목록이 출력된다.
- 장착 중인 아이템은 판매할 수 없다.
2-8. 던전
- 던전은 3단계 난이도와 자동 / 수동 모드가 있다.
2-8-1. 자동 모드
- 방어력 기준으로 성공 여부를 판단한다.
권장 방어력 미만: 40% 확률로 실패 → 보상 없음, 체력 절반 감소
권장 방어력 이상: 성공 → 권장치 ±값에 따라 체력 소모량 결정
- 공격력은 클리어 시 보상량에 영향을 준다.
2-8-2. 수동 모드 (스네이크 게임)
- 난이도에 따라 맵 크기와 스네이크 속도가 달라진다.
- 클리어 / 실패에 따른 보상 구조는 자동 모드와 동일하다.
2-9. 스네이크 미니 게임
- 입력 처리와 화면 출력을 별도의 스레드로 구현하여 스네이크의 움직임이 멈추지 않도록 구현하였다.
2-10. 레벨 시스템
- 던전 클리어 시 경험치를 획득합니다. 경험치가 일정량 도달하면 자동으로 레벨업합니다.
- 다량의 경험치 획득 시 한 번에 여러 레벨 상승이 가능합니다.
- 레벨 1이 증가할 떄마다, 공격력과 방어력이 증가합니다.
2-11. 휴식하기
- 휴식을 선택하면 500G 으로 체력을 회복합니다.
2-12. 게임 저장
시작 화면에서 현재 플레이어의 모든 정보를 저장할 수 있습니다. 게임 재실행 시 자동으로 저장된 데이터를 불러옵니다.
3. Trouble Shooting
3-1. 아이템 참조 문제 (데이터 로드시 발생)
3-1-1. 문제 발생 배경
- 프로젝트에서는 플레이어 데이터를 JSON 형식으로 저장하고, 게임 재시작 시 해당 데이터를 역직렬화하여 불러오는 구조를 사용했다.
- 이때, 플레이어의 능력치뿐만 아니라 인벤토리와 장비창에 포함된 아이템 정보도 함께 저장하고 불러온다.
- 초기 구현에서는 다음과 같은 구조로 아이템을 처리했다:
1) 상점에서 아이템을 구매하면 해당 객체를 얕은 복사 방식으로 인벤토리에 추가
2) 인벤토리의 아이템은 장비창에도 동일한 방식으로 전달
3) 즉, 하나의 아이템 객체를 상점, 인벤토리, 장비창이 공유하는 형태
- 이 방식은 구현 당시에는 매우 효율적이었다. 예를 들어, 상점에서는 인벤토리를 별도로 확인하지 않아도 해당 아이템이 이미 존재하는지 알 수 있었고, 장비창에서도 인벤토리의 상태를 그대로 반영할 수 있었다.
3-1-2. 문제 원인
- 하지만 저장된 데이터를 불러올 때 문제가 발생했다. 역직렬화는 각 객체를 메모리에 새로 생성하기 때문에, 겉보기에는 동일한 검이라도, 실제로는 서로 다른 인스턴스가 되었다.
- 예를 들면, 원래는 상점 검 → 얕은 복사 → 인벤토리 검 → 얕은 복사 → 장비창 검 구조였으나, 로드 후에는 상점 검, 인벤토리 검, 장비창 검이 모두 다른 객체가 되었다.
- 이로 인해 다음과 같은 문제가 발생했다:
1) 장비창에서 인벤토리의 아이템을 장착하려 해도 같은 참조가 아니라서 상태 공유가 되지 않음
2) 상점에서 판매 중인 아이템과 인벤토리의 아이템이 동일한 것으로 간주되지 않음
3-1.3. 해결 방법
- 구조 자체를 변경하는 방식(예: 팩토리 패턴 적용, 객체 간 독립 처리 등)도 고려했지만, 장비창과 인벤토리가 동일한 씬에서 동시에 표시되어야 하는 제약이 있었기 때문에 기존 구조를 유지하면서 데이터 참조 방식만 조정하는 방향으로 결정했다.
- 해결 방식은 다음과 같다:
▶ 데이터를 로드하는 시점에서만, 인벤토리의 아이템 객체를 기준으로 상점과 장비창이 같은 인스턴스를 참조하도록 매핑하였다.
▶ 즉, 기존처럼 얕은 복사를 활용하되, 역직렬화 이후에는 참조 관계를 수동으로 재정렬했다.
- 이를 통해 로드 후에도 세 시스템(상점, 인벤토리, 장비창)이 동일한 아이템 인스턴스를 공유할 수 있게 되었으며, 초기 구조를 그대로 유지하면서도 데이터 불일치 문제를 해결할 수 있었다.
3-2. 키가 눌렸을 때만 입력 받기
3-2-1. 문제 발생 배경
- 스네이크 게임에서는 멀티스레드를 활용해 키 입력을 별도로 처리하고 있었다. 게임이 실행되는 동안에는 별다른 문제가 없었지만, 게임이 끝난 후 장면이 전환되었을 때 예상치 못한 입력 문제가 발생했다.
3-2-2. 문제 원인
- 문제의 원인은 다음과 같다:
▶ 멀티스레드에서 입력을 받는 방식이 Console.ReadKey()를 사용해 즉각적으로 키 입력을 대기하는 구조였다.
▶ 그런데 게임이 종료된 이후에도, 입력 스레드가 한 번 더 Console.ReadKey()를 호출할 수 있었고, 이 입력 대기가 다음 장면으로 전환된 후의 Console.ReadKey()와 겹쳐 버려, 두 번 입력을 해야 하는 상황이 발생했다.
3-2-3. 해결 방법
- 해결 방법은 간단했다:
Console.KeyAvailable을 사용하여 키 입력이 실제로 존재할 때만 Console.ReadKey()를 호출하도록 조건을 걸었다.
- 이를 통해 불필요한 입력 대기 없이, 사용자가 키를 눌렀을 때만 키 입력을 정상적으로 처리하도록 만들 수 있었다.
3-3. 선입력(입력 누적) 문제
3-2-1. 문제 발생 배경
- 초기에는 게임 플레이 중 키를 한 번씩만 눌러서 문제가 없었다.
- 그러나 나중에 테스트 과정에서, 여러 키를 빠르게 연속 입력할 경우, 입력이 버퍼에 누적되어 이후 씬 전환 시 의도치 않은 동작이 발생한다는 사실을 발견했다.
- 입력 버퍼에 남은 값들이 게임 씬이 바뀐 후에도 계속해서 처리되며, 새로운 장면에서 불필요한 키 입력이 자동으로 반영되는 문제가 발생했다.
3-2-2. 문제 원인
- 이 문제의 핵심은 Console.ReadKey()가 입력 버퍼에 남아 있는 키 입력을 순차적으로 처리한다는 점이다.
- 즉, 사용자가 이전 씬에서 여러 키를 입력하면, 그 값들이 모두 버퍼에 저장된 상태로 남아 있고, 이후 씬으로 넘어가더라도 Console.ReadKey()는 그 값을 하나씩 읽어서 처리해 버린다.
- 결과적으로 새로운 장면에서도 사용자가 아무 키도 누르지 않았음에도 불구하고, 전 씬에서 입력한 키 값들로 인해 캐릭터가 움직이거나 방향이 바뀌는 등 의도치 않은 동작이 발생했다.
- 특히 스네이크 게임에서는 방향키를 여러 번 누르면, 입력 버퍼에 남아 있던 방향키 값들이 모두 차례로 소비되어, 스네이크가 혼자 방향이 바뀌는 현상이 발생했다.
- 이러한 동작은 사용자가 화면을 보고 직접 반응해, 입력한 값만 처리되도록 설계한 의도와는 완전히 어긋나는 결과였다.
3-2-3. 해결 방법
- 문제 해결을 위해, 입력 버퍼를 비우는 기능을 구현했다.
class ControlManager
{
public void ClearInputBuffer() // 씬 넘어가기전 현재 입력된 모든 입력 값 없애기
{
while (Console.KeyAvailable)
{
Console.ReadKey(true);
}
}
}
- 이 메서드는 Console.KeyAvailable을 통해 입력 버퍼에 키가 존재하는 동안 Console.ReadKey(true)를 반복 호출하여 모든 입력을 소모하는 방식으로 작동한다.
- 이 방식을 적용한 결과, 씬이 전환되더라도 이전 입력이 다음 씬에 영향을 주지 않게 되었고, 실제 사용자가 입력한 키만 반영되는 올바른 흐름이 유지되었다.
- 스네이크 게임의 경우에도, 방향 전환 키를 여러 번 입력해도 한 번의 방향 입력만 반영되고 나머지는 버려지기 때문에, 자동 방향 변경 현상이 말끔히 해결되었다.
4. 프로젝트 후기
- 처음에는 튜터님께 스네이크 게임을 구현해보는 건 어떻겠냐는 제안을 받았고, 단순히 구현으로만 끝날 줄 알았다.
- 그런데 개발을 진행하면서, 던전 클리어 방식을 ‘자동 / 수동 클리어’로 나눌 수 있겠다는 아이디어가 떠올랐고, 그 덕분에 스네이크 게임을 자연스럽게 던전 시스템과 연결시킬 수 있었다.
- 특히 기존에는 Console.ReadKey()를 이용해 입력이 들어올 때까지 화면이 멈추는 구조였는데, 스네이크에서는 캐릭터가 실시간으로 움직이고 동시에 화면 출력도 계속 이어져야 했기 때문에, 이 부분을 구현하며 많은 만족감을 느꼈다.
- 스네이크 게임을 처음 구상할 때 가장 고민이 많았던 부분은 바로 ‘실시간 입력 처리’였다. 뱀이 멈추지 않고 계속 움직여야 하는 특성상, 일반적인 키 입력 대기 방식인, Console.ReadKey()로는 구현이 불가능했고, 결국 멀티스레드를 통해 입력을 따로 처리하는 방식을 택했다.
- 프로젝트 후반부에 키 입력 누적 문제를 해결하려다 Console.KeyAvailable을 알게 되면서 ‘굳이 멀티스레드가 아니어도 됐겠네?’라는 생각이 잠깐 스치긴 했지만, 지금 돌이켜보면 오히려 잘 된 일이라고 생각한다. 이 기회를 통해 멀티스레드 입력 처리와 동기화에 대해 직접 고민하고, 실제로 적용해보는 소중한 경험을 할 수 있었다고 생각한다.
'Programming > C#' 카테고리의 다른 글
| 🎯 C# 델리게이트와 이벤트 차이 - Action vs event Action (1) | 2025.08.14 |
|---|---|
| C# 콘솔에서 세이브/로드 시 참조가 깨지는 문제 (with Guid) (3) | 2025.07.18 |
| C# 콘솔에서 멀티 스레드로 키 입력 처리하기 (2) | 2025.07.10 |
| C# static 함수의 구조와 그 의미 (2) | 2025.07.09 |
| C# object: 참조 타입의 뿌리 (1) | 2025.07.07 |
