본문 바로가기
C#

하드코딩으로 구현한 간단한 퀘스트 분기 처리

by Oz Driver 2025. 5. 2.

 

C# 에서 하드코딩으로 의사 결정 트리를 구성해보았습니다.

이 구조는 복잡한 if-else 대신, 조건과 결과를 분기 노드(DecisionNode) 형태로 깔끔하게 구성할 수 있어 가독성과 유지보수 측면에서 유리합니다.

 

퀘스트 트리

플레이어의 상태에 따라 다음과 같은 분기를 수행하는 퀘스트 결정 트리를 만듭니다

[레벨 ≥ 15?]
├─ 예 → [고대 열쇠 + 왕의 인장 있음?]
│       ├─ 예 → [튜토리얼 + 사이드미션1 완료?]
│       │       ├─ 예 → 🎁 특별 퀘스트 지급: 왕의 시험
│       │       └─ 아니오 → 📌 보조 퀘스트들을 먼저 완료하세요.
│       └─ 아니오 → 🔑 필요한 아이템이 없습니다.
└─ 아니오 → [VIP 토큰 있음?]
        ├─ 예 → 🎟️ 임시 퀘스트 개방
        └─ 아니오 → [숨겨진 스크롤 있음?]
                 ├─ 예 → 🌀 보너스 퀘스트 오픈
                 └─ 아니오 → ⛔ 레벨이 부족합니다.

 

코드 구성 요약

• DecisionNode : 조건 분기 및 결과 실행을 담는 노드

Player : 레벨, 인벤토리, 완료 퀘스트 목록 등 상태 정보 보유

QuestSystem : 트리 구성과 평가 수행

Main : 테스트용 예시 플레이어 상태 설정

 

예시 코드 

// QuestDecisionNode.cs
using System;
using System.Collections.Generic;

class DecisionNode
{
    public Func<bool> Condition = null;
    public Action Result = null;
    public DecisionNode YesNode = null;
    public DecisionNode NoNode = null;
}

class Player
{
    public int Level;
    public List<string> Inventory = new List<string>();
    public List<string> CompletedQuests = new List<string>();

    public bool HasItem(string item) => Inventory.Contains(item);

    public bool HasItems(params string[] items)
    {
        foreach (var item in items)
            if (!Inventory.Contains(item)) return false;
        return true;
    }

    public bool HasCompleted(string quest) => CompletedQuests.Contains(quest);
}

class QuestSystem
{
    private Player player;

    public QuestSystem(Player player)
    {
        this.player = player;
    }

    public DecisionNode BuildQuestDecisionTree()
    {
        return new DecisionNode
        {
            Condition = () => player.Level >= 15,
            YesNode = new DecisionNode
            {
                Condition = () => player.HasItems("Ancient Key", "Royal Seal"),
                YesNode = new DecisionNode
                {
                    Condition = () => player.HasCompleted("Tutorial") 
                                      && player.HasCompleted("SideMission1"),
                    YesNode = new DecisionNode
                    {
                        Condition = null,                    
                        Result = () => Console.WriteLine
                        ("🎁특별 퀘스트 지급: 왕의 시험")
                    },
                    NoNode = new DecisionNode
                    {
                        Condition = null,                        
                        Result = () => Console.WriteLine
                        ("📌보조 퀘스트들을 먼저 완료하세요.")
                    }
                },
                NoNode = new DecisionNode
                {
                    Condition = null,
                    Result = () => Console.WriteLine
                    ("🔑 필요한 아이템이 없습니다.")
                }
            },
            NoNode = new DecisionNode
            {
                Condition = () => player.HasItem("VIP Token"),
                YesNode = new DecisionNode
                {
                    Condition = null,                    
                    Result = () => Console.WriteLine
                    ("🎟️ VIP 토큰 보유자: 임시 퀘스트 개방")
                },
                NoNode = new DecisionNode
                {
                    Condition = () => player.HasItem("Hidden Scroll"),
                    YesNode = new DecisionNode
                    {
                        Condition = null,
                        Result = () => Console.WriteLine
                        ("🌀 숨겨진 스크롤로 보너스 퀘스트 오픈")
                    },
                    NoNode = new DecisionNode
                    {
                        Condition = null,
                        Result = () => Console.WriteLine
                        ("⛔ 레벨이 부족합니다.")
                    }
                }
            }
        };
    }

    public void EvaluateTree(DecisionNode node)
    {
        if (node == null) return;

        if (node.Condition != null)
        {
            if (node.Condition())
                EvaluateTree(node.YesNode);
            else
                EvaluateTree(node.NoNode);
        }
        else
        {
            node.Result?.Invoke();
        }
    }
}

class Program
{
    static void Main()
    {
        var player = new Player
        {
            Level = 12,
            Inventory = new List<string> { "Ancient Key", "Hidden Scroll" },
            CompletedQuests = new List<string> { "Tutorial" }
        };

        var questSystem = new QuestSystem(player);
        var root = questSystem.BuildQuestDecisionTree();
        questSystem.EvaluateTree(root);
    }
}

 

하드코딩이지만 구조화된 이점

if 조건문 없이, 조건 분기를 읽기 좋은 트리 구조로 표현 가능

각 조건에 대한 Condition = () => ... 람다식이 직관적

Condition = null 을 명시하여 조건이 없는 마지막 노드임을 강조 (사실 생략 가능하지만 가독성을 위해 남겨둠)

트리 흐름이 깔끔하게 보이고, 테스트하기도 쉬움

 

마무리

JSON이나 ScriptableObject로 확장하지 않고도, 하드코딩 상태에서도 의사결정 로직을 트리로 구성하는 방식은 기획 또는 로직이 일정 수준 복잡한 게임에도 충분히 실용적으로 사용할 수 있습니다.

언젠가 이 코드를 기반으로 JSON 자동 구성까지 확장해도 좋겠지만, 지금 단계에서는 이 방식만으로도 꽤 멋지고, 실제 구현 가능한 느낌이 납니다.