Unity 개발 일지/게임 기획

[팀 프로젝트] Text RPG 만들기 3 - Skill & Event

ohty20012 2025. 4. 29. 21:26

전투 시스템 다음으로 내가 담당하게 된 부분은 스킬 시스템과 이벤트 시스템이다.

 

스킬 시스템과 이벤트 시스템의 흐름은 다음과 같다.

 

 

skill 과 event 각각 클래스를 만들고,

 

ContentLoader를 만든 후, ContentLoader에 skill과 event를 등록한다.

 

GameManager에서 Load()를 통해 스킬과 이벤트를 호출하고

 

Battle에서는 GameManager를 통해 skill목록과 event 목록에 접근한다.

 

(정확히는 event 목록에 접근 하는 것은 Battle이 아닌 Dungeon이다.)

 

 

 

1. Skill 과 Event 클래스의 구조

더보기
public delegate int SkillAction(Player player, Monster[] list, int turn);

public class Skill(string name, string description, int cost, int durationTurn, int targetAmount, Dictionary<string, SkillAction> actions)
{
  // 스킬 이름
  public string name = name;
  
  // 스킬 설명
  public string description = description;
  
  // 스킬 요구 마나
  public int cost = cost;
  
  // 스킬의 타겟 수
  // 0일시 자신에게 사용하는 스킬
  // 1 ~ {적 수}보다 적을 시 타겟을 지정하여 사용하는 스킬
  // {적 수} 이상일시 전체 혹은 무작위로 사용하는 스킬
  public int targetAmount = targetAmount;
  
  // 스킬의 지속시간
  // 0 이면 즉발스킬
  // 1 이상이면 버프 스킬
  public int durationTurn = durationTurn;
  
  public Dictionary<string, SkillAction> actions = actions;
  
  // 스킬을 호출할 때, 전달하는 eventName이 "Use"일 시 설명을 보여주고 발동.
  // "End"일 시 바로 발동
  public int Action(string eventName, Player player, Monster[] list, int turn)
  {
    switch (eventName)
    {
      case "Use":
        {
          AnsiConsole.MarkupLine(description);
          MenuUtil.OpenMenu("다음");
          if (actions.TryGetValue(eventName, out var action))
          {
            return action(player, list, turn);
          }
          return 0;
        }

      case "End":
        {
          if (actions.TryGetValue(eventName, out var action)) return action(player, list, turn);
          else return 0;
        }

      default: return 0;
    }
  }
}
더보기
public delegate void EventAction(string option, Action action);

public class Event(string name, string description, Dictionary<string, Action<Player, List<Monster>>> actions)
{
  // 이벤트 이름
  public string name = name;
  
  // 이벤트 설명
  public string description = description;
  
  public Dictionary<string, Action<Player, List<Monster>>> actions = actions;

  // Action으로 이벤트 발생. Player와 Monsterlist와 이벤트 후처리 함수 onFinish를 매개변수로 받음.
  public void Action(Player player, List<Monster> list, Action onFinish)
  {
    AnsiConsole.MarkupLine($"[[{name}]]\n{description}\n");

    var selected = AnsiConsole.Prompt(
      new SelectionPrompt<string>()
      .Title("선택지를 골라주세요:")
      .AddChoices(actions.Keys)
    );
    AnsiConsole.MarkupLine($"{selected} 을(를) 선택했습니다.");
    if (actions.TryGetValue(selected, out var onSelect))
    {
      onSelect?.Invoke(player, list);
    }
    onFinish?.Invoke();
  }
}
Player player = player;
List<Monster> monsters = [];

// 스킬 호출하기

Skill skill = GameManager.skills["알파 스트라이크"];
skill.Action("Use", player, monsters, 0);


// 이벤트 호출하기

Event event = GameManager.events["이상한 부족의 환영"];
event.Action(player, monsters, ()=> Console.WriteLine("조우 끝"));
// 이벤트 종료 후 "조우 끝"이 출력된다.

 

 

 

2. ContentLoader의 구조

더보기
using Spectre.Console;
using Starfall.Contents;
using Starfall.PlayerService;
namespace Starfall.Core;
public static class ContentLoader
{
  private static readonly Random random = new();
  public static void Load()
  {
    RegisterSkill(
      "단단해지기",
      "마나 10을 소모하여 1턴동안 방어력이 3 증가합니다.",
      10,
      1,
      0,
      new()
      {
        ["Use"] = (player, _) => {
          player.def += 3;
          AnsiConsole.MarkupLine("방어력이 3 증가했습니다!");
        },
        ["Off"] = (player, _) => {
        player.def -= 3;
        AnsiConsole.MarkupLine("방어력 버프의 지속시간이 끝났습니다.");
        }
     }
   );

    RegisterSkill(
        "더블 스트라이크",
        "마나 15을 소모하여 무작위 2명의 적에게 공격력의 150%만큼 피해를 줍니다.",
        15,
        0,
        100,
        new()
    	{
        ["Use"] = (player, list) =>
        {
            var target = (from mob in list
            for (int i = 0; i < 2; i++)
            {
                Battle.PlayerAttackMonster(player, list[random.Next(list.Count)], 150);
            }
        },
      }
    );

    RegisterEvent(
        "이벤트1",
        "이상한 부족의 환영",
        "플레이어는 원주민을 만났습니다. 원주민 중 한 명을 골라주세요.",
        new()
        {
            ["트랄랄레로 트랄랄라"] = (player, list) =>
            {
                AnsiConsole.MarkupLine("트랄랄레로 트랄랄라가 플레이어의 체력을회복");
                player.hp += 100;
            },
            	["봄바르딜로 코르코딜로"] = (player, list) =>
            {
            	AnsiConsole.MarkupLine("봄바르딜로 코르코딜로가 몬스터들을 공격");
            	foreach (var m in list)
            	{
               	 m.hp -= 100;
            	}
            },
            	["퉁x9 사후르"] = (player, list) =>
            	{
            		AnsiConsole.MarkupLine("퉁x9 사후르가 승리의 함성을 외침");
            		player.atk += 10;
            	}
        }
        );
    );
}
    private static void RegisterSkill(string name, string desc, int cost, int durationTurn, int targetAmount, Dictionary<string, SkillAction> actions)
    	=> GameManager.skills.Add(name, new(name, desc, cost, durationTurn, targetAmount, actions));
    private static void RegisterEvent(string name, string desc, Dictionary<string, Action<Player, List<Monster>>> actions)
    	=> GameManager.events.Add(name, new(name, desc, actions));
}

 

 

3. GameManager

  public static readonly Dictionary<string, Skill> skills = [];
  public static readonly Dictionary<string, Event> events = [];
  
  
  ContentsLoader.Load();

 

 

 

 

 

<참고>

delegate에 대해

https://ohty20012.tistory.com/24

 

델리게이트(delegate)

C#을 배우다 보면 처음에는 함수 호출 방식이 직관적이고 명확해 보이지만, 어느 순간에는 코드를 더 유연하고 모듈화된 방식으로 설계할 필요를 느끼게 된다.이때 사용하기 좋은 도구가 델리게

ohty20012.tistory.com