미션4를 해결했다.
간단하게 공격과 죽었을 때의 이벤트가 발생하게끔 구현했다.
문법의 개념과 사용법 자체는 어렵지 않은 것 같다.
어떻게 설계에 사용되는지 익히면 될 것 같다.
여기선 간단하게 문장출력으로 구현했다.
유니티에서는 여러가지 로직이 추가될 것 같다.
미션 4: 델리게이트와 이벤트
- 목표: 델리게이트와 이벤트를 활용하여 게임의 상태를 관리한다.
- 미션:
- 체력이 0 이하가 되면 "게임 오버" 이벤트를 발생시키는 델리게이트와 이벤트를 만들어라.
- 플레이어가 공격할 때 "공격 이벤트"를 발생시키고, 콘솔에 공격 메시지를 출력해라.
- 몬스터가 죽으면 "몬스터 처치 이벤트"를 발생시켜라.
처음엔 delegate를 직접 커스텀해서 event를 각 player와 monster에 만들었는데,
이걸 IGameInterface 즉, player와 monster가 공통적으로 상속하는 저 인터페이스에 구현하는게 낫겠다고 판단했다.
하지만 delegate는 클래스나 네임스페이스 내에서만 선언이 가능하고 인터페이스에서는 불가능하다는 것을 알았다.
그래서 c# 에서 내장돼있는 delegate인 Action을 사용하여 해결했다.
public interface IGameCharacter
{
public int Hp { get; set; }
public int AttackPower { get; set; }
public string Name { get; set; }
//delegate void OnDead();
event Action OnDeadEvent;
//delegate void OnAttack();
event Action OnAttackEvent;
}
이렇게 action 을 사용하여 구현하였다.
유니티에서 복잡한 입출력이 필요하다면 그때, 커스텀하거나 하면 될 것 같다.
public class Player : IGameCharacter
{
public int MaxHp { get; set; } = 100;
int _hp;
int _exp = 0;
//public delegate void OnDead();
public event Action OnDeadEvent;
//public delegate void OnAttack();
public event Action OnAttackEvent;
플레이어에 이렇게 구현했고
public int Hp { get { return _hp; }
set {
if (value <= 0) { _hp = 0; OnDeadEvent?.Invoke(); }
else if (value > MaxHp) { _hp = MaxHp; }
else { _hp = value; }
}
}
public Player() { _hp = MaxHp;OnDeadEvent += Dead; OnAttackEvent += PlayAttack; }
public void PlayAttack()
{
Console.WriteLine("플레이어가 공격모션을 실행합니다. in Player");
}
public void Attack(Monster monster)
{
OnAttackEvent?.Invoke();
monster.TakeDamage(AttackPower);
}
이렇게 생성자에서 이벤트에 내가 가지고 있는 함수를 등록했고, hp의 set프로퍼티에서 0일때 deadevent를 호출하게 했다.
attack도 마찬가지로 attack함수 내에서 invoke를 실행했다. '
몬스터도 마찬가지이다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TextRpgCS
{
public class Monster : IGameCharacter
{
static int CountId = 1;
int _hp = 30;
public int Id { get; } = CountId;
public string Name { get; set; }
public int Level { get; set; } = 1;
public int Exp { get; set; } = 10;
public int Hp { get { return _hp; } set { if (value <= 0) { _hp = 0; OnDeadEvent?.Invoke(); } else { _hp = value; } } }
//public delegate void OnDead();
public event Action OnDeadEvent;
//public delegate void OnAttack();
public event Action OnAttackEvent;
public int AttackPower { get; set; } = 10;
public Monster()
{
Level = 1*CountId;
Exp = 10*CountId;
Hp = 30*CountId;
AttackPower = 10*CountId/2;
this.Name = $"몬스터{CountId++}";
OnDeadEvent += Dead;
OnAttackEvent += PlayAttack;
}
public void PlayAttack()
{
Console.WriteLine($"{Name}몬스터가 공격을 시도합니다.");
}
public void Attack(Player player)
{
OnAttackEvent?.Invoke();
player.TakeDamage(AttackPower);
}
public void TakeDamage(int damage)
{
Hp -= damage;
Console.WriteLine($"{Name}에게 데미지{damage}를 입혔습니다. {Name}의 체력 : {Hp}");
}
void Dead()
{
Console.WriteLine($"{CountId}번 몬스터 사망!");
}
}
}
마지막은 GameManager에서도 구독하여 외부에서 구독한 것을 구현해봤다.
public void Init()
{
Player = new Player();
monsters = new List<Monster>();
for(int i = 0; i < MonsterCount; i++)
{
monsters.Add(new Monster());
monsters[i].OnDeadEvent += BroadcastMonsterDead;
}
_boss = new Boss();
_boss.OnDeadEvent += BroadcastMonsterDead;
// 이벤트 등록
Player.OnDeadEvent += GameOver;
Player.OnAttackEvent += BroadcastPlayerAttack;
}
void GameOver()
{
Console.WriteLine("플레이어가 사망하여 게임이 종료되었습니다. in GameManager");
}
void BroadcastPlayerAttack()
{
Console.WriteLine("GameManager : 플레이어가 공격을 시도합니다!");
}
void BroadcastMonsterDead()
{
Console.WriteLine("몬스터가 사망합니다!");
}
3개의 테스트 구독용 함수를 만들어서 구독을 구현했다.
추가로 gameManager에 있던 전투관련함수를 BattleManager를 만들어서 분리했다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TextRpgCS
{
internal class BattleManager
{
public static BattleManager Instance = new BattleManager();
public void Init()
{
}
public ResultBattle StartBattle(Player player, Monster monster)
{
Console.WriteLine($"{monster.Name}이(가) 당신을 공격합니다!\n");
int choice;
while (player.Hp > 0 && monster.Hp > 0)
{
Console.WriteLine(UtilTextManager.ChoiceMenuInBattle);
choice = int.Parse(Console.ReadLine());
if (choice == 3)
{
if (monster is Boss)
Console.WriteLine(UtilTextManager.RetreatBoss);
else
{
Console.WriteLine(UtilTextManager.ExitDungeon);
return ResultBattle.RetreatPlayer;
}
}
else if (choice == 2)
{
// 인벤토리 보여주기
ItemManager.Instance.PrintInventory();
}
else
{
Console.WriteLine($"용사{player.Name}가 {monster.Name}을 공격!");
player.Attack(monster);
if (monster.Hp <= 0) return ResultBattle.MonsterDie;
monster.Attack(player);
}
}
return ResultBattle.PlayerDie;
}
}
}
마지막으로 테스트다. 이것은 chatgpt가 작성한 유닛테스트 코드이다.
msTest프로젝트를 추가하여 미션4에 대한 테스트를 진행했다.
테스트의 장점은 내가 캐치못한 부분을 혼자 이렇게 돌아가겠지하고코드를 일일이 따라가는 것보다 훨씬 빠르게 문제를 캐치할 수 있다는 점이다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TextRpgCS
{
internal class BattleManager
{
public static BattleManager Instance = new BattleManager();
public void Init()
{
}
public ResultBattle StartBattle(Player player, Monster monster)
{
Console.WriteLine($"{monster.Name}이(가) 당신을 공격합니다!\n");
int choice;
while (player.Hp > 0 && monster.Hp > 0)
{
Console.WriteLine(UtilTextManager.ChoiceMenuInBattle);
choice = int.Parse(Console.ReadLine());
if (choice == 3)
{
if (monster is Boss)
Console.WriteLine(UtilTextManager.RetreatBoss);
else
{
Console.WriteLine(UtilTextManager.ExitDungeon);
return ResultBattle.RetreatPlayer;
}
}
else if (choice == 2)
{
// 인벤토리 보여주기
ItemManager.Instance.PrintInventory();
}
else
{
Console.WriteLine($"용사{player.Name}가 {monster.Name}을 공격!");
player.Attack(monster);
if (monster.Hp <= 0) return ResultBattle.MonsterDie;
monster.Attack(player);
}
}
return ResultBattle.PlayerDie;
}
}
}
테스트 성공했다.
'텍스트 Rpg - C#' 카테고리의 다른 글
C#으로 만드는 텍스트 Rpg (6) (0) | 2025.02.05 |
---|---|
C#으로 만드는 텍스트 Rpg (5) (0) | 2025.02.05 |
C#으로 만드는 텍스트 Rpg (3) (0) | 2025.02.01 |
C#으로 만드는 텍스트 RPG (2) (0) | 2025.01.31 |
C#으로 만드는 텍스트 RPG (1) (0) | 2025.01.25 |