Structuring a Decision Tree in C# Without If Statements
This article demonstrates how to structure a decision tree using pure C# code without relying on deeply nested if statements.
By using DecisionNode objects to represent each branching condition and outcome, we can make our logic cleaner, more readable, and easier to maintain.
Quest Tree Flow
[Level ≥ 15?]
├─ Yes → [Has "Ancient Key" + "Royal Seal"?]
│ ├─ Yes → [Completed "Tutorial" + "SideMission1"?]
│ │ ├─ Yes → 🎁 Grant special quest: Trial of the King
│ │ └─ No → 📌 Complete side quests first
│ └─ No → 🔑 Required items missing
└─ No → [Has "VIP Token"?]
├─ Yes → 🎟️ Temporary quest unlocked for VIPs
└─ No → [Has "Hidden Scroll"?]
├─ Yes → 🌀 Bonus quest unlocked
└─ No → ⛔ Level too low to proceed
Code Components
- DecisionNode : Holds a condition and branching result actions
- Player : Stores the level, inventory, and completed quests
- QuestSystem : Builds and evaluates the decision tree
- Main : Sample use with mock player data
Full Source Code
// 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
("🎁 Grant special quest: Trial of the King")
},
NoNode = new DecisionNode
{
Condition = null,
Result = () => Console.WriteLine
("📌 Complete side quests first")
}
},
NoNode = new DecisionNode
{
Condition = null,
Result = () => Console.WriteLine
("🔑 Required items missing")
}
},
NoNode = new DecisionNode
{
Condition = () => player.HasItem("VIP Token"),
YesNode = new DecisionNode
{
Condition = null,
Result = () => Console.WriteLine
("🎟️ Temporary quest unlocked for VIPs")
},
NoNode = new DecisionNode
{
Condition = () => player.HasItem("Hidden Scroll"),
YesNode = new DecisionNode
{
Condition = null,
Result = () => Console.WriteLine
("🌀 Bonus quest unlocked")
},
NoNode = new DecisionNode
{
Condition = null,
Result = () => Console.WriteLine
("⛔ Level too low to proceed")
}
}
}
};
}
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);
}
}
Benefits of This Hardcoded Tree
- Clear decision flow without if-statements
- Readable, declarative structure using lambdas
Condition = nullused explicitly to mark terminal nodes- Clean tree structure, easy to test and maintain
Final Thoughts
You don’t need JSON or ScriptableObjects to build decision logic.
This hardcoded tree structure is powerful enough to handle moderate game logic scenarios without becoming unreadable.
Later, you could replace this with external data (e.g., JSON) to enable designers to build the logic themselves. But for now? This approach is elegant, clean, and totally usable. 😄
