오늘은 스파르타 내일배움캠프 Chapter 2의 개인 과제인 <스파르타 던전> 개발에 집중했다.
본격적으로 클래스를 다루고 추상 클래스와 추상 클래스의 상속, reference type과 깊은 복사, 클래스 설계, 화면 설계 등등.. 생각할게 많았다.
구현해야하는 주요 클래스를 정리해봤다.
Character Class
- 필드
- 이름, 직업, 레벨 스탯, 돈
- 장착한 장비, 인벤토리
- 메서드
- 장비 바꾸기
- 스탯 업데이트/동기화
- 인벤토리 조작
Item Class
abstact class
- 필드
- 아이템ID, 이름, 가격, 설명
- 메서드
- 구매했을 때, 판매했을 때, 장착했을 때
- 깊은 복사
Weapon Class : Item Class
- Item class를 상속받음
- 필드
- 공격력
- 메서드
- 깊은 복사 생성자 오버로딩
Armor Class : Item Class
- Item class를 상속받음
- 필드
- 방어력
- 메서드
- 깊은 복사 생성자 오버로딩
Inventory Class
- 필드
- 아이템 리스트 (
List<Item>
)- 메서드
Character
에게 아이템 객체 반환- 리스트에 아이템 추가
- 리스트에서 아이템 삭제
- 아이템 리스트 보여주기
- 갖고 있는 아이템인지 검사
Character
클래스가 Inventory
클래스를 필드로 가지고 있는 has-a
관계로.
Weapon
과 Armor
클래스는 추상 클래스인 Item
을 상속받는 is-a
관계로 설계해봤다.
public enum Scene
{
TitleScene,
LobbyScene,
MyInfo,
Inventory,
Shop,
Dungeon,
}
현재 화면에 그려질 씬을 switch
문으로 구분하기 위해 Scene
열거형을 작성했다.
게임루프 while
문 안에서 switch
문으로 현재 씬의 화면을 그려낸다. 대강 다음과 같이 동작한다.
while (true)
{
switch (currentScene)
{
case Scene.TitleScene
//TitleScene Draw, KeyInput, Logic
break;
case Scene.LobbyScene
//LobbyScene Draw, KeyInput, Logic
break;
... more scene ...
}
}
인벤토리 화면과 상점 화면은 화면은 동작과 로직이 추가로 필요한 메뉴가 존재한다.
위와 같은 처리는 각 씬의 화면을 그리는 DrawInventory()
와 DrawShop()
에 step
이라는 정수형 매개변수를 추가해서 씬 안의 씬을 구성해봤다.
static void DrawInventory(int step)
{
Console.Clear();
Console.WriteLine("인벤토리");
Console.WriteLine();
Console.WriteLine(" 번호 이름 정보 설명");
player.inventory.ShowItems();
Console.WriteLine();
switch (step)
{
case 0:
Console.WriteLine("1. 아이템 장착");
Console.WriteLine("2. 아이템 정렬");
Console.WriteLine("0. 나가기");
Console.WriteLine();
Console.WriteLine("원하시는 행동을 입력해주세요.");
break;
case 1:
Console.WriteLine("아이템의 번호로 장착할 아이템을 지정합니다.");
Console.WriteLine("0. 나가기");
Console.WriteLine();
Console.WriteLine("장착할 아이템의 번호를 입력해주세요.");
var input = Console.ReadLine();
// key 입력 처리
break;
case 2:
Console.WriteLine();
Console.WriteLine("원하시는 행동을 입력해주세요.");
var input2 = InputKey();
// key 입력 처리
break;
}
}
Item
을 상속받는 Weapon
과 Armor
는 Inventory
의 리스트에 Item
형으로 관리하고 있음Dictionary
로 가지고 있음Inventory
에 추가해줘야함.Inventory
에 추가하는 것이므로 내 인벤토리의 아이템을 조작하면 상점에 전시된 아이템까지 변조될 수 있음.구매하기로 정한 buyItem
은 상점의 Dictionary
에서 받아온 객체로 컴퓨터는 이 객체가 Weapon
인지, Armor
인지 모른다. 인벤토리에 추가할 새 아이템으로 깊은 복사를 해야하는데, Item
클래스는 추상 클래스라서 생성자를 public으로 선언할 수 없댄다...
if (buyItem as Weapon != null)
이런식으로 하나하나 노가다해서 깊은 복사를 진행 할 수도 있겠지만, 이러면 확장성, 안정성 모두 최악인 코드가 될 것이다...
돌머리로 여기저기 예외처리도 해보고 구글링도 해보다가 방법을 찾았다.
해결은 생각보다 간단했다. 클래스의 상속과 함수 오버라이딩의 기본만 알면 간단하게 해결 할 수 있는데, 빙 빙 돌고 있었다.
Item
클래스에 DeepCopy()
라는 추상 메서드를 선언한다. 이 메서드는 Item
을 반환한다.Weapon
과 Armor
에서 DeepCopy()
를 다음과 같이 구현한다.public class Armor : Item
{
...
public Armor(Armor reference)
{
ID = reference.ID;
Name = reference.Name;
Price = reference.Price;
Def = reference.Def;
Descript = reference.Descript;
}
public override Item DeepCopy()
{
return new Armor(this);
}
...
}
깊은 복사를 해주는 생성자를 만들고, DeepCopy()
에서 나(this
)를 복사해서 리턴한다!
이렇게 구현하면 Array<Item>
에서 꺼낸 객체가 Weapon
인지, Armor
인지, 아니면 또 다른 Item
의 파생클래스인지 따로 생각할 필요 없이 Item.DeepCopy()
로 깊은 복사를 진행한 새로운 객체를 반환 받을 수 있다!
추상 메서드나 가상 메서드는 부모 클래스의 자료형에 담긴 객체로 호출했더라도 자식 클래스에서 오버라이딩 된 메서드가 있다면 그 메서드를 실행한다...