내일배움캠프 Unity 7일차 TIL - 개인 과제 (Sparta Dungeon)

Wooooo·2023년 11월 7일
0

내일배움캠프Unity

목록 보기
9/94

오늘의 키워드

오늘은 스파르타 내일배움캠프 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 관계로.
WeaponArmor클래스는 추상 클래스인 Item을 상속받는 is-a관계로 설계해봤다.

화면 설계

switch + enum

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을 상속받는 WeaponArmorInventory의 리스트에 Item형으로 관리하고 있음
  • 상점에는 아이템을 진열하기 위해 모든 아이템의 인스턴스를 Dictionary로 가지고 있음
  • 상점에서 아이템을 샀다면, 상점이 가진 아이템의 인스턴스가 아니라 새로운 아이템을 Inventory에 추가해줘야함.
    • 이 과정에서 깊은 복사가 필요.
    • 깊은 복사를 하지 않는다면, 상점에 전시된 아이템을 Inventory에 추가하는 것이므로 내 인벤토리의 아이템을 조작하면 상점에 전시된 아이템까지 변조될 수 있음.

구매하기로 정한 buyItem은 상점의 Dictionary에서 받아온 객체로 컴퓨터는 이 객체가 Weapon인지, Armor인지 모른다. 인벤토리에 추가할 새 아이템으로 깊은 복사를 해야하는데, Item 클래스는 추상 클래스라서 생성자를 public으로 선언할 수 없댄다...
if (buyItem as Weapon != null) 이런식으로 하나하나 노가다해서 깊은 복사를 진행 할 수도 있겠지만, 이러면 확장성, 안정성 모두 최악인 코드가 될 것이다...
돌머리로 여기저기 예외처리도 해보고 구글링도 해보다가 방법을 찾았다.

문제 해결

해결은 생각보다 간단했다. 클래스의 상속과 함수 오버라이딩의 기본만 알면 간단하게 해결 할 수 있는데, 빙 빙 돌고 있었다.

  • Item 클래스에 DeepCopy()라는 추상 메서드를 선언한다. 이 메서드는 Item을 반환한다.
  • 자식 클래스인 WeaponArmor에서 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()로 깊은 복사를 진행한 새로운 객체를 반환 받을 수 있다!

까먹지 말자...

추상 메서드나 가상 메서드는 부모 클래스의 자료형에 담긴 객체로 호출했더라도 자식 클래스에서 오버라이딩 된 메서드가 있다면 그 메서드를 실행한다...

profile
game developer

0개의 댓글