오늘도 어제에 이어서 C# 강의를 수강하고 TRPG 프로젝트를 마저 제작하도록 하겠다. 오늘 강의의 범위는 C#에서 잘 드러나는 객체지향 프로그래밍적인 문법이라고 정리할 수 있겠다. 또, 이후 시간에는 TRPG 프로젝트의 필수 구현 기능들을 구현해보고, 가능하다면 추가 구현 기능까지 건드려 볼 생각이다.
객체를 생성하기 위한 템플릿, 속성과 동작을 가진다.
class Person
{
public string Name;
public int Age;
public void PrintInfo()
{
Console.WriteLine("Name: " + Name);
Console.WriteLine("Age: " + Age);
}
}
Person p = new Person();
p.Name = "John";
p.Age = 30;
p.PrintInfo(); // 출력: Name: John, Age: 30
클래스, 필드, 메서드 등의 접근 가능한 범위를 지정하는 키워드이다. 클래스의 캡슐화를 제어한다.
class Player
{
// 필드
private string name;
private int level;
// 메서드
public void Attack()
{
// 공격 동작 구현
}
}
Player player = new Player(); // Player 클래스의 인스턴스 생성
player.Attack(); // Attack 메서드 호출
즉, 클래스를 통해 정의한 객체는 생성 시 필드 의 속성을 가지고, 이후 메서드를 호출하여 통해 특정 작업을 수행한다.
new 키워드와 함께 호출Player player = new Player();get과 set 접근자를 사용하여 값을 읽고 설정하는 동작을 정의한다.get 접근자는 프로퍼티의 값을 반환하는 메서드로, 외부에서 읽을 때 호출된다.set 접근자는 프로퍼티의 값을 설정하는 메서드로, 외부에서 값을 대입할 때 호출된다. value라는 암묵적 매개변수를 사용하여 전달된 값을 처리한다.public class Player
{
// 내부 저장용 필드
private int score;
// 프로퍼티 정의: 외부에서 score 값을 읽거나 쓸 수 있도록 제공
public int Score
{
get { return score; }
set { score = value; }
}
}
// `score`라는 필드는 직접 접근하지 않고, `score`라는 프로퍼티를 통해서만 접근할 수 있다.
프로퍼티가 명확하게 이해가 되지 않아서, 조금 더 추가적인 내용을 찾아보았다.
프로퍼티의 사용방법
private제한자를 사용한 변수를 생성하지 않고 프로퍼티 자체를 사용한다.get,set 접근자 안에 내용이 없으면 프로퍼티 그 자체를 사용할 수 있다.public class Player
{
// 자동 구현 프로퍼티를 사용, 외부에서 값을 읽고 수정도 가능
public string Name { get; set; }
public int Level { get; set; }
// 읽기 전용 자동 구현 프로퍼티: 외부에서는 값을 읽기만 가능, 변경 불가능.
public int Health { get; private set; }
public Player(string name, int level, int health)
{
Name = name;
Level = level;
Health = health;
}
}
//예시 나누기 코드
using System;
public class Example
{
// out 사용 예: 나누기 연산 후 몫과 나머지를 반환하는 메서드
public static void Divide(int dividend, int divisor, out int quotient, out int remainder)
{
quotient = dividend / divisor;
remainder = dividend % divisor;
}
public static void Main()
{
// out 예시 사용
int dividend = 17;
int divisor = 5;
// out 변수는 초기화하지 않아도 됨
int quotient, remainder;
Divide(dividend, divisor, out quotient, out remainder); // 메서드 내부에서 값을 할당
Console.WriteLine($"{dividend} 나누기 {divisor} 의 몫은 {quotient}이고, 나머지는 {remainder}입니다");
}
}
Divide메서드는 나눗셈을 수행한 후, out 키워드를 사용하여 몫(quotient)과 나머지(remainder)를 반환한다.out의 특징: 호출할 때 해당 변수들은 초기화할 필요가 없으며, 메서드 내부에서 반드시 값을 할당해야한다.out 키워드를 사용하는 이유public class Example
{
// ref 사용 예: 두 변수의 값을 교환하는 메서드
public static void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
public static void Main()
{
// ref 예시 사용
int x = 5; // 반드시 초기화되어 있어야 함
int y = 10; // 반드시 초기화되어 있어야 함
Console.WriteLine("Before Swap: x = " + x + ", y = " + y);
Swap(ref x, ref y); // ref 키워드로 x와 y 전달
Console.WriteLine("After Swap: x = " + x + ", y = " + y);
}
}
Swap 메서드Swap 메서드는 두 변수 a와 b를 ref키워드를 사용하여 인자로 받음.ref의 특징ref를 사용하면 값 복사를 피할 수 있어 성능 면에서 이점이 있다.ref를 사용하면, 메서드가 반환값으로 새 값을 전달하는 대신, 전달받은 변수를 직접 수정하여 호출한 쪽의 변수 상태를 업데이트할 수 있다.두 키워드 모두 메서드 내부의 작업 결과를 외부 변수에 직접 효율적으로 반영하게 해준다. 즉, 메서드 내부에서의 변경이 외부에 그대로 적용된다.
필수 구현 기능 중 먼저, 보유중인 아이템을 전부 보여주고, 장착중인 아이템 앞에는 [E]표시를 붙여주는 기능을 만들어 줄 생각이다.
Item.cs를 만들어서 아이템을 관리해보겠다.public class Item
{
public string Name;
public int Atk;
public int Def;
public string Description;
public bool IsEquipped;
public Item(string name, int atk, int def, string description, bool isEquipped = false)
{
Name = name;
Atk = atk;
Def = def;
Description = description;
IsEquipped = isEquipped;
}
//예시 아이템
public static Item IronArmor = new Item("무쇠갑옷", 0, 5, "무쇠로 만들어져 튼튼한 갑옷입니다.");
public static Item SpartanSpear = new Item("스파르타의 창", 7, 0, "스파르타의 전사들이 사용했다는 전설의 창입니다.");
public static Item OldSword = new Item("낡은 검", 2, 0, "쉽게 볼 수 있는 낡은 검입니다.");
}
isEquipped 불리언 변수를 통해 장착 여부를 판별하겠다.Inventory.cspublic static class Inventory
{
public static List<Item> Items = new List<Item>();
public static void Show()
{
.
.
.
if (Items.Count == 0)
{
Console.WriteLine("아이템이 없습니다.\n");
}
else
{
foreach (var item in Items)
{
string Equip = item.IsEquipped ? "[E]" : " ";
Console.WriteLine($"- {Equip}{item.Name} | + {item.Atk} | + {item.Def} | {item.Description}");
}
foreach 반복문을 이용해 List Items를 Inventory 화면에 띄운다.isEquipped가 true면 [E], false면 공백을 표시하는 삼항 연산자 ?를 사용하여 장착 여부를 표현한다.이후 Inventory.cs 안에 장착 관리를 담당하는EquipManager() 메서드를 작성하였다.
private static void EquipManager()
{
while (true)
{
Console.Clear();
Console.WriteLine("인벤토리 - 장착 관리");
Console.WriteLine("보유 중인 아이템을 관리할 수 있습니다.\n");
Console.WriteLine("[아이템 목록]");
if (Items.Count == 0)
{
Console.WriteLine("아이템이 없습니다.\n");
}
else
{
for (int i = 0; i < Items.Count; i++)
{
var item = Items[i];
string Equip = item.IsEquipped ? "[E]" : " ";
Console.WriteLine($"{i + 1}. {Equip}{item.Name} | 공격력 + {item.Atk} | 방어력 + {item.Def} | {item.Description}");
}
}
Console.WriteLine("\n0. 나가기");
Console.WriteLine("\n장착할 아이템의 번호를 입력해주세요.");
Console.Write(">> ");
string input = Console.ReadLine();
if (input == "0")
{
break;
}
if (int.TryParse(input, out int selection))
{
int index = selection - 1;
if (index >= 0 && index < Items.Count)
{
var item = Items[index];
if (item.IsEquipped == true)
{
item.IsEquipped = false;
Console.WriteLine($"{item.Name}의 장착을 해제했습니다.");
}
else
{
item.IsEquipped = true;
Console.WriteLine($"{item.Name}을 장착했습니다.");
}
}
else
{
Console.WriteLine("잘못된 입력입니다. 다시 시도해주세요.");
}
}
else
{
Console.WriteLine("잘못된 입력입니다. 숫자를 입력해주세요.");
}
}
}
}
코드 설명
if (Items.Count == 0)
{
Console.WriteLine("아이템이 없습니다.\n");
}
else
{
for (int i = 0; i < Items.Count; i++)
{
var item = Items[i];
string Equip = item.IsEquipped ? "[E]" : " ";
Console.WriteLine($"{i + 1}. {Equip}{item.Name} | 공격력 + {item.Atk} | 방어력 + {item.Def} | {item.Description}");
}
}
if (int.TryParse(input, out int selection))
{
int index = selection - 1;
if (index >= 0 && index < Items.Count)
{
var item = Items[index];
if (item.IsEquipped == true)
{
item.IsEquipped = false;
Console.WriteLine($"{item.Name}의 장착을 해제했습니다.");
}
else
{
item.IsEquipped = true;
Console.WriteLine($"{item.Name}을 장착했습니다.");
}
}
input, out int selection int index = selection - 1 를 통해 input을 정수로 변환하고, 그 정수(-1)에 맞는 아이템 목록을 장착하거나 해제한다.IsEquipped가 true인 아이템을 가져온다.isEquipped가 true인 상태라면, 플레이어의 기본 공격력에 아이템의 공격력을 더해 totalAtk으로 정의한다.totalDef로 정의한다. public static int totalAtk()
{
int totalAtk = Atk;
foreach (var item in Inventory.Items)
{
if (item.IsEquipped)
totalAtk += item.Atk;
}
return totalAtk;
}
public static int totalDef()
{
int totalDef = Def;
foreach (var item in Inventory.Items)
{
if (item.IsEquipped)
totalDef += item.Def;
}
return totalDef;
}
}
Console.WriteLine($"공격력 : {Player.totalAtk}");
Console.WriteLine($"방어력 : {Player.totalDef}");


Item.cs에서 아이템의 가격 정보를 정의해주겠다. public class Item
{
public string Name;
public int Atk;
public int Def;
public string Description;
public int Price;
public bool IsEquipped;
public Item(string name, int atk, int def, string description, int price, bool isEquipped = false)
{
Name = name;
Atk = atk;
Def = def;
Description = description;
Price = price;
IsEquipped = isEquipped;
}
public static Item TraineeArmor = new Item("수련자 갑옷", 0, 5, "수련에 도움을 주는 갑옷입니다.", 1000);
public static Item IronArmor = new Item("무쇠갑옷", 0, 9, "무쇠로 만들어져 튼튼한 갑옷입니다.", 2000);
.
.
.
}
Shop.cs로 아이템 목록을 가져오겠다. static List<Item> StoreItems = new List<Item>()
{
Item.TraineeArmor,
Item.IronArmor,
Item.SpartanArmor,
Item.OldSword,
Item.BronzeAxe,
Item.SpartanSpear
};
public static void Show()
{
while (true)
{
Console.Clear();
Console.WriteLine("상점");
Console.WriteLine($"보유 골드: {Player.Gold} G\n");
Console.WriteLine("1. 아이템 구매");
Console.WriteLine("2. 아이템 판매");
Console.WriteLine("0. 나가기");
Console.WriteLine("\n원하시는 행동을 입력해주세요.");
Console.Write(">> ");
string input = Console.ReadLine();
if (input == "0") break;
else if (input == "1") Buy();
else if (input == "2") Sell();
else
{
Console.WriteLine("\n잘못된 입력입니다. 다시 시도해주세요.");
Console.ReadKey();
}
Console.Clear();
}
}
private static void Buy()
{
Console.Clear();
Console.WriteLine("상점 - 아이템 구매");
Console.WriteLine("구매할 아이템을 선택해주세요.\n");
Console.WriteLine("[보유골드]");
Console.WriteLine(Player.Gold);
Console.WriteLine("\n[아이템 목록]");
if (StoreItems.Count == 0)
{
Console.WriteLine("아이템이 없습니다.\n");
}
else
{
foreach (var item in StoreItems)
{
Console.WriteLine($"- {item.Name} | + {item.Atk} | + {item.Def} | {item.Description}");
}
Console.WriteLine();
}
}
private static void Sell()
{
Console.Clear();
Console.WriteLine("상점 - 아이템 판매");
Console.WriteLine("판매할 아이템을 선택해주세요.\n");
Console.WriteLine("[보유골드]");
Console.WriteLine(Player.Gold);
Console.WriteLine("\n[아이템 목록]");
if (Inventory.Items.Count == 0)
{
Console.WriteLine("아이템이 없습니다.\n");
}
else
{
foreach (var item in Inventory.Items)
{
Console.WriteLine($"- {item.Name} | + {item.Atk} | + {item.Def} | {item.Description}");
}
Console.WriteLine();
}
}
}
오늘은 여기까지 구현해보겠다. 내일은 남은 상점 기능들을 구현해보고 추가 기능을 구현해보겠다.
get, set 과 out, ref 등의 키워드를 조금 더 잘 활용할 수 있는 방법이 있을텐데 하는 생각이 들어 아쉬웠다..