7. 剧情与任务
2026/4/14大约 9 分钟
7. 伏魔记——剧情与任务
7.1 什么是任务系统?
想象你在玩一款探险游戏。村长对你说:"森林深处有一只大妖怪,请你去消灭它!"——这就是一个任务(Quest)。完成任务后,村长奖励你一把宝剑——这就是任务奖励。
任务系统是RPG的灵魂。它告诉玩家"你现在该做什么",给玩家一个明确的目标,而不是在一个大世界里漫无目的地瞎逛。
任务类型
| 类型 | 说明 | 举例 |
|---|---|---|
| 主线任务 | 推进剧情的核心任务,必须完成 | "消灭森林之王" |
| 支线任务 | 额外的可选任务,完成后有奖励 | "帮老奶奶找回丢失的猫" |
| 日常任务 | 每天可以重复做的任务 | "收集5株草药" |
任务状态
每个任务都有生命周期,经历以下状态变化:
┌──────────┐ 接受 ┌──────────┐
│ 未接取 │ ────────→ │ 进行中 │
└──────────┘ └────┬─────┘
│
完成条件
│
┌────┴────┐
↓ ↓
┌──────────┐ ┌──────────┐
│ 已完成 │ │ 已失败 │
└──────────┘ └──────────┘7.2 任务数据结构
C
using System.Collections.Generic;
/// <summary>
/// 任务数据——定义一个完整的任务
/// </summary>
public class QuestData
{
public string Id { get; set; } // 任务唯一ID
public string Title { get; set; } // 任务标题
public string Description { get; set; } // 任务描述
public bool IsMainQuest { get; set; } // 是否是主线任务
public int RequiredLevel { get; set; } // 接取等级要求
// 任务目标
public List<QuestObjective> Objectives { get; set; }
// 任务奖励
public QuestReward Reward { get; set; }
// 前置任务(必须先完成这个任务才能接取)
public string PrerequisiteQuest { get; set; }
}
/// <summary>
/// 任务目标——完成任务需要做的事
/// </summary>
public class QuestObjective
{
public enum ObjectiveType
{
KillEnemy, // 击杀指定敌人
CollectItem, // 收集指定物品
TalkToNpc, // 和指定NPC对话
ReachLocation // 到达指定地点
}
public ObjectiveType Type { get; set; } // 目标类型
public string TargetId { get; set; } // 目标ID(敌人/物品/NPC的ID)
public int RequiredAmount { get; set; } // 需要的数量
public int CurrentAmount { get; set; } // 当前进度
public string Description { get; set; } // 目标描述
public bool IsCompleted => CurrentAmount >= RequiredAmount;
}
/// <summary>
/// 任务奖励
/// </summary>
public class QuestReward
{
public int Exp { get; set; } // 经验奖励
public int Gold { get; set; } // 金币奖励
public List<string> Items { get; set; } // 物品奖励列表
}GDScript
## 任务数据——定义一个完整的任务
class_name QuestData
var id: String ## 任务唯一ID
var title: String ## 任务标题
var description: String ## 任务描述
var is_main_quest: bool ## 是否是主线任务
var required_level: int ## 接取等级要求
# 任务目标
var objectives: Array = [] ## QuestObjective数组
# 任务奖励
var reward: Dictionary = {} ## {exp, gold, items}
# 前置任务
var prerequisite_quest: String
## 任务目标
class_name QuestObjective
enum ObjectiveType { KILL_ENEMY, COLLECT_ITEM, TALK_TO_NPC, REACH_LOCATION }
var type: ObjectiveType ## 目标类型
var target_id: String ## 目标ID
var required_amount: int ## 需要的数量
var current_amount: int = 0 ## 当前进度
var description: String ## 目标描述
## 是否已完成
func is_completed() -> bool:
return current_amount >= required_amount7.3 任务管理器
QuestManager 负责管理所有任务的接取、进度更新和完成判定。
C
using Godot;
using System.Collections.Generic;
/// <summary>
/// 任务管理器——管理任务的完整生命周期
/// </summary>
public partial class QuestManager : Node
{
// 所有可接取的任务模板
private Dictionary<string, QuestData> _allQuests = new();
// 当前进行中的任务
private Dictionary<string, QuestData> _activeQuests = new();
// 已完成的任务ID
private HashSet<string> _completedQuests = new();
// 信号
[Signal] public delegate void QuestAcceptedEventHandler(string questId);
[Signal] public delegate void QuestProgressUpdatedEventHandler(
string questId, string objectiveDesc);
[Signal] public delegate void QuestCompletedEventHandler(
string questId, QuestReward reward);
public override void _Ready()
{
RegisterQuests();
GD.Print("任务管理器已初始化");
}
/// <summary>
/// 注册所有可用任务
/// </summary>
private void RegisterQuests()
{
_allQuests["clear_forest"] = new QuestData
{
Id = "clear_forest",
Title = "清除森林妖怪",
Description = "幽暗森林中出现了大量妖怪,村长请你前往清除它们。",
IsMainQuest = true,
RequiredLevel = 1,
Objectives = new List<QuestObjective>
{
new QuestObjective
{
Type = QuestObjective.ObjectiveType.KillEnemy,
TargetId = "forest_goblin",
RequiredAmount = 5,
CurrentAmount = 0,
Description = "击杀森林小鬼(0/5)"
}
},
Reward = new QuestReward
{
Exp = 100,
Gold = 50,
Items = new List<string> { "hp_potion_m" }
}
};
_allQuests["collect_herbs"] = new QuestData
{
Id = "collect_herbs",
Title = "采集草药",
Description = "药铺老板需要5株止血草来制作金创药。",
IsMainQuest = false,
RequiredLevel = 1,
Objectives = new List<QuestObjective>
{
new QuestObjective
{
Type = QuestObjective.ObjectiveType.CollectItem,
TargetId = "herb",
RequiredAmount = 5,
CurrentAmount = 0,
Description = "采集止血草(0/5)"
}
},
Reward = new QuestReward
{
Exp = 30,
Gold = 30,
Items = new List<string> { "hp_potion_s", "hp_potion_s" }
}
};
}
/// <summary>
/// 接取任务
/// </summary>
public bool AcceptQuest(string questId)
{
// 检查任务是否存在
if (!_allQuests.ContainsKey(questId)) return false;
var quest = _allQuests[questId];
// 检查等级要求
var playerStats = GameManager.Instance.Party[0];
if (playerStats.Level < quest.RequiredLevel)
{
GD.Print($"等级不足!需要 {quest.RequiredLevel} 级");
return false;
}
// 检查前置任务
if (!string.IsNullOrEmpty(quest.PrerequisiteQuest)
&& !_completedQuests.Contains(quest.PrerequisiteQuest))
{
GD.Print("前置任务未完成!");
return false;
}
// 检查是否已接取或已完成
if (_activeQuests.ContainsKey(questId)
|| _completedQuests.Contains(questId))
{
return false;
}
// 接取任务(创建副本)
var questCopy = DeepCopyQuest(quest);
_activeQuests[questId] = questCopy;
GD.Print($"接取任务: {quest.Title}");
EmitSignal(SignalName.QuestAccepted, questId);
return true;
}
/// <summary>
/// 更新任务进度——击杀敌人时调用
/// </summary>
public void OnEnemyKilled(string enemyId)
{
foreach (var quest in _activeQuests.Values)
{
foreach (var obj in quest.Objectives)
{
if (obj.Type == QuestObjective.ObjectiveType.KillEnemy
&& obj.TargetId == enemyId
&& !obj.IsCompleted)
{
obj.CurrentAmount++;
obj.Description = $"击杀{GetEnemyName(enemyId)}" +
$"({obj.CurrentAmount}/{obj.RequiredAmount})";
EmitSignal(SignalName.QuestProgressUpdated,
quest.Id, obj.Description);
}
}
}
CheckAllQuestCompletion();
}
/// <summary>
/// 更新任务进度——收集物品时调用
/// </summary>
public void OnItemCollected(string itemId)
{
foreach (var quest in _activeQuests.Values)
{
foreach (var obj in quest.Objectives)
{
if (obj.Type == QuestObjective.ObjectiveType.CollectItem
&& obj.TargetId == itemId
&& !obj.IsCompleted)
{
obj.CurrentAmount++;
obj.Description = $"采集{GetItemName(itemId)}" +
$"({obj.CurrentAmount}/{obj.RequiredAmount})";
EmitSignal(SignalName.QuestProgressUpdated,
quest.Id, obj.Description);
}
}
}
CheckAllQuestCompletion();
}
/// <summary>
/// 检查所有进行中任务是否完成
/// </summary>
private void CheckAllQuestCompletion()
{
var completedQuests = new List<string>();
foreach (var kvp in _activeQuests)
{
string questId = kvp.Key;
var quest = kvp.Value;
bool allObjectivesComplete = true;
foreach (var obj in quest.Objectives)
{
if (!obj.IsCompleted)
{
allObjectivesComplete = false;
break;
}
}
if (allObjectivesComplete)
{
completedQuests.Add(questId);
}
}
// 完成任务并发放奖励
foreach (var questId in completedQuests)
{
CompleteQuest(questId);
}
}
/// <summary>
/// 完成任务,发放奖励
/// </summary>
private void CompleteQuest(string questId)
{
var quest = _activeQuests[questId];
// 发放奖励
foreach (var member in GameManager.Instance.Party)
{
var (newStats, _) = LevelSystem.AddExperience(
member, quest.Reward.Exp);
// 更新角色属性...
}
GameManager.Instance.AddGold(quest.Reward.Gold);
foreach (var itemId in quest.Reward.Items)
{
// 添加物品到背包...
}
// 从进行中移除,加入已完成
_activeQuests.Remove(questId);
_completedQuests.Add(questId);
GD.Print($"完成任务: {quest.Title}");
GD.Print($" 奖励: {quest.Reward.Exp}经验, {quest.Reward.Gold}金币");
EmitSignal(SignalName.QuestCompleted, questId, quest.Reward);
}
/// <summary>
/// 检查NPC是否有可接取的任务
/// </summary>
public List<string> GetAvailableQuestsForNpc(string npcId)
{
var available = new List<string>();
foreach (var quest in _allQuests.Values)
{
if (CanAcceptQuest(quest))
{
available.Add(quest.Id);
}
}
return available;
}
/// <summary>
/// 检查任务是否可以接取
/// </summary>
private bool CanAcceptQuest(QuestData quest)
{
if (_activeQuests.ContainsKey(quest.Id)) return false;
if (_completedQuests.Contains(quest.Id)) return false;
if (!string.IsNullOrEmpty(quest.PrerequisiteQuest)
&& !_completedQuests.Contains(quest.PrerequisiteQuest))
return false;
return true;
}
private string GetEnemyName(string id) => id;
private string GetItemName(string id) => id;
private QuestData DeepCopyQuest(QuestData original)
{
// 简化的深拷贝(实际项目中需要完整实现)
return original; // 实际应该创建副本
}
}GDScript
extends Node
## 任务管理器——管理任务的完整生命周期
# 所有可接取的任务模板
var _all_quests: Dictionary = {}
# 当前进行中的任务
var _active_quests: Dictionary = {}
# 已完成的任务ID
var _completed_quests: Array[String] = []
# 信号
signal quest_accepted(quest_id: String)
signal quest_progress_updated(quest_id: String, desc: String)
signal quest_completed(quest_id: String, reward: Dictionary)
func _ready() -> void:
_register_quests()
print("任务管理器已初始化")
## 注册所有可用任务
func _register_quests() -> void:
_all_quests["clear_forest"] = {
"id": "clear_forest",
"title": "清除森林妖怪",
"description": "幽暗森林中出现了大量妖怪,村长请你前往清除。",
"is_main_quest": true,
"required_level": 1,
"objectives": [
{
"type": QuestObjective.ObjectiveType.KILL_ENEMY,
"target_id": "forest_goblin",
"required_amount": 5,
"current_amount": 0,
"description": "击杀森林小鬼(0/5)"
}
],
"reward": {
"exp": 100,
"gold": 50,
"items": ["hp_potion_m"]
}
}
_all_quests["collect_herbs"] = {
"id": "collect_herbs",
"title": "采集草药",
"description": "药铺老板需要5株止血草来制作金创药。",
"is_main_quest": false,
"required_level": 1,
"objectives": [
{
"type": QuestObjective.ObjectiveType.COLLECT_ITEM,
"target_id": "herb",
"required_amount": 5,
"current_amount": 0,
"description": "采集止血草(0/5)"
}
],
"reward": {
"exp": 30,
"gold": 30,
"items": ["hp_potion_s", "hp_potion_s"]
}
}
## 接取任务
func accept_quest(quest_id: String) -> bool:
if not _all_quests.has(quest_id):
return false
var quest: Dictionary = _all_quests[quest_id]
# 检查等级要求
var player_level: int = GameManager._party[0].level
if player_level < quest.required_level:
print("等级不足!")
return false
# 检查前置任务
if quest.prerequisite_quest != "":
if not quest.prerequisite_quest in _completed_quests:
print("前置任务未完成!")
return false
# 检查是否已接取或已完成
if _active_quests.has(quest_id) or quest_id in _completed_quests:
return false
# 接取任务(深拷贝)
_active_quests[quest_id] = quest.duplicate(true)
print("接取任务: ", quest.title)
quest_accepted.emit(quest_id)
return true
## 更新任务进度——击杀敌人时调用
func on_enemy_killed(enemy_id: String) -> void:
for quest_id in _active_quests:
var quest: Dictionary = _active_quests[quest_id]
for obj in quest.objectives:
if obj.type == QuestObjective.ObjectiveType.KILL_ENEMY \
and obj.target_id == enemy_id \
and not obj.is_completed():
obj.current_amount += 1
obj.description = "击杀%s(%d/%d)" % [
enemy_id, obj.current_amount, obj.required_amount
]
quest_progress_updated.emit(quest_id, obj.description)
_check_all_quest_completion()
## 更新任务进度——收集物品时调用
func on_item_collected(item_id: String) -> void:
for quest_id in _active_quests:
var quest: Dictionary = _active_quests[quest_id]
for obj in quest.objectives:
if obj.type == QuestObjective.ObjectiveType.COLLECT_ITEM \
and obj.target_id == item_id \
and not obj.is_completed():
obj.current_amount += 1
obj.description = "采集%s(%d/%d)" % [
item_id, obj.current_amount, obj.required_amount
]
quest_progress_updated.emit(quest_id, obj.description)
_check_all_quest_completion()
## 检查所有进行中任务是否完成
func _check_all_quest_completion() -> void:
var completed: Array[String] = []
for quest_id in _active_quests:
var quest: Dictionary = _active_quests[quest_id]
var all_done: bool = true
for obj in quest.objectives:
if not obj.is_completed():
all_done = false
break
if all_done:
completed.append(quest_id)
for quest_id in completed:
_complete_quest(quest_id)
## 完成任务,发放奖励
func _complete_quest(quest_id: String) -> void:
var quest: Dictionary = _active_quests[quest_id]
var reward: Dictionary = quest.reward
# 发放经验
for member in GameManager._party:
var result = LevelSystem.add_experience(member, reward.exp)
member = result[0]
# 发放金币
GameManager.add_gold(reward.gold)
# 发放物品
for item_id in reward.items:
# 添加到背包...
# 移动到已完成列表
_active_quests.erase(quest_id)
_completed_quests.append(quest_id)
print("完成任务: ", quest.title)
print(" 奖励: ", reward.exp, "经验, ", reward.gold, "金币")
quest_completed.emit(quest_id, reward)7.4 主线任务链设计
主线任务是一串首尾相连的任务——完成A才能接B,完成B才能接C。这样玩家就有了明确的游戏进度感。
| 顺序 | 任务ID | 任务标题 | 前置任务 | 目标 | 奖励 |
|---|---|---|---|---|---|
| 1 | elder_request | 村长的请求 | 无 | 和村长对话 | 50金币 |
| 2 | clear_forest | 清除森林妖怪 | elder_request | 击杀5只森林小鬼 | 100经验+铁剑 |
| 3 | cave_explore | 探索暗影洞穴 | clear_forest | 到达洞穴入口 | 200经验+中金创药 |
| 4 | defeat_boss | 消灭洞穴之主 | cave_explore | 击杀洞穴Boss | 500经验+烈焰之刃 |
| 5 | save_village | 拯救清风镇 | defeat_boss | 击败最终Boss | 游戏通关 |
任务链的代码实现
任务链通过前置任务(Prerequisite)字段来实现。当玩家想接取任务B时,系统检查任务A是否已完成——如果没完成,就不能接取B。
elder_request ──→ clear_forest ──→ cave_explore ──→ defeat_boss ──→ save_village
(无前置) (elder_request) (clear_forest) (cave_explore) (defeat_boss)7.5 任务日志UI
任务日志让玩家随时查看当前有哪些任务、进度如何:
┌─────────────────────────────────────┐
│ 任 务 日 志 │
│ │
│ ▶ 主线任务 │
│ [进行中] 清除森林妖怪 │
│ 击杀森林小鬼(3/5) │
│ [锁定] 探索暗影洞穴 │
│ │
│ ▶ 支线任务 │
│ [进行中] 采集草药 │
│ 采集止血草(2/5) │
│ │
│ [ 关 闭 ] │
└─────────────────────────────────────┘7.6 本章小结
| 知识点 | 说明 |
|---|---|
| 任务数据 | 包含目标、奖励、前置任务的完整定义 |
| 目标类型 | 击杀敌人、收集物品、对话NPC、到达地点 |
| 任务管理器 | 接取、进度更新、完成判定、奖励发放 |
| 主线任务链 | 通过前置任务字段实现任务的串联 |
| 任务日志 | UI显示当前任务列表和进度 |
下一章我们将搭建完整的游戏界面系统,把所有功能整合到一起。
