5. 制作与生存系统
2026/4/14大约 10 分钟
制作与生存系统
如果只有建造和战斗,游戏会缺少一种"紧迫感"。生存系统就是给玩家一个活下去的理由——你得吃东西不然会饿死,得在天黑前做好准备不然会被怪物围攻。这种压力让游戏更有趣。
本章你将学到
- 资源采集树(原材料怎么一步步变成成品)
- 合成配方系统(配方表和解锁条件)
- 饥饿和体力系统(饿肚子会影响战斗力)
- 昼夜生存压力(夜晚更危险)
资源采集树
资源采集就像一棵树——从原材料(树根)开始,经过加工(树干),最终得到成品(树枝和树叶)。
资源层级说明
| 层级 | 说明 | 举例 |
|---|---|---|
| 原材料 | 从自然界直接采集 | 木材、石材、铁矿、植物纤维 |
| 一级加工 | 在工作台上加工 | 木板、石砖、铁锭、绳索 |
| 二级加工 | 用一级材料合成 | 墙壁、武器、钢材 |
| 三级加工 | 高级合成 | 铁甲、帕鲁球、高级装备 |
合成配方系统
每种可合成的物品都有一个"配方"——就像做菜的菜谱,告诉你需要什么材料、在哪里做、做多长时间。
配方数据表
| 物品 | 材料 | 工作台 | 时间 | 解锁条件 |
|---|---|---|---|---|
| 木板 x4 | 木材 x2 | 工作台 | 5秒 | 初始 |
| 石砖 x4 | 石材 x2 | 工作台 | 5秒 | 初始 |
| 木剑 | 木材 x5, 绳索 x2 | 工作台 | 10秒 | 初始 |
| 铁剑 | 铁锭 x3, 木材 x2 | 锻造台 | 20秒 | 基地等级3 |
| 帕鲁球 x3 | 铁锭 x1, 水晶 x1 | 工作台 | 15秒 | 捕捉教程后 |
| 治疗药水 | 植物 x3, 水 x1 | 炼药台 | 10秒 | 基地等级2 |
| 食物料理 | 食材 x2 | 烹饪台 | 15秒 | 初始 |
| 铁甲 | 钢材 x5, 皮革 x3 | 锻造台 | 30秒 | 基地等级4 |
合成系统代码
C#
// CraftingSystem.cs
// 合成系统——管理配方和合成逻辑
using Godot;
using System.Collections.Generic;
// 配方数据
public class Recipe
{
public string ResultItem; // 产出物品ID
public int ResultCount = 1; // 产出数量
public Dictionary<string, int> Materials; // 需要的材料
public string RequiredStation; // 需要的工作台
public float CraftTime = 5.0f; // 合成时间(秒)
public string UnlockCondition; // 解锁条件
}
public partial class CraftingSystem : Node
{
// 所有配方
private Dictionary<string, Recipe> _recipes = new();
// 正在进行的合成
private List<CraftingJob> _activeJobs = new();
public override void _Ready()
{
RegisterRecipes();
}
public override void _Process(double delta)
{
// 更新所有正在进行的合成
for (int i = _activeJobs.Count - 1; i >= 0; i--)
{
_activeJobs[i].Timer -= (float)delta;
if (_activeJobs[i].Timer <= 0)
{
CompleteCraft(_activeJobs[i]);
_activeJobs.RemoveAt(i);
}
}
}
// 注册所有配方
private void RegisterRecipes()
{
AddRecipe("wood_plank", "wood", 2, "wood_plank", 4, "workbench", 5f);
AddRecipe("stone_brick", "stone", 2, "stone_brick", 4, "workbench", 5f);
AddRecipe("wood_sword", "wood", 5, "wood_sword", 1, "workbench", 10f);
AddRecipe("iron_sword", "iron_ingot", 3, "iron_sword", 1, "forge", 20f);
AddRecipe("pal_ball", "iron_ingot", 1, "pal_ball", 3, "workbench", 15f);
}
private void AddRecipe(string id, string matId, int matCount,
string resultId, int resultCount, string station, float time)
{
_recipes[id] = new Recipe
{
ResultItem = resultId,
ResultCount = resultCount,
Materials = new Dictionary<string, int> { { matId, matCount } },
RequiredStation = station,
CraftTime = time
};
}
// 检查能否合成
public bool CanCraft(string recipeId, Dictionary<string, int> inventory)
{
if (!_recipes.TryGetValue(recipeId, out var recipe)) return false;
foreach (var mat in recipe.Materials)
{
if (!inventory.TryGetValue(mat.Key, out int count) || count < mat.Value)
return false;
}
return true;
}
// 开始合成
public bool StartCraft(string recipeId, Dictionary<string, int> inventory)
{
if (!CanCraft(recipeId, inventory)) return false;
var recipe = _recipes[recipeId];
// 扣除材料
foreach (var mat in recipe.Materials)
{
inventory[mat.Key] -= mat.Value;
}
// 添加合成任务
_activeJobs.Add(new CraftingJob
{
RecipeId = recipeId,
Timer = recipe.CraftTime
});
GD.Print($"开始合成: {recipeId},需要 {recipe.CraftTime} 秒");
return true;
}
// 完成合成
private void CompleteCraft(CraftingJob job)
{
if (_recipes.TryGetValue(job.RecipeId, out var recipe))
{
GD.Print($"合成完成: {recipe.ResultItem} x{recipe.ResultCount}");
// TODO: 将产出到玩家背包
}
}
}
// 正在进行的合成任务
public class CraftingJob
{
public string RecipeId;
public float Timer;
}GDScript
# crafting_system.gd
# 合成系统——管理配方和合成逻辑
extends Node
# 配方数据
class Recipe:
var result_item: String # 产出物品ID
var result_count: int = 1 # 产出数量
var materials: Dictionary = {} # 需要的材料
var required_station: String # 需要的工作台
var craft_time: float = 5.0 # 合成时间(秒)
# 所有配方
var _recipes: Dictionary = {}
# 正在进行的合成
var _active_jobs: Array = []
func _ready() -> void:
_register_recipes()
func _process(delta: float) -> void:
# 更新所有正在进行的合成
var completed: Array = []
for i in range(_active_jobs.size() - 1, -1, -1):
var job: CraftingJob = _active_jobs[i]
job.timer -= delta
if job.timer <= 0:
_complete_craft(job)
_active_jobs.remove_at(i)
## 注册所有配方
func _register_recipes() -> void:
_add_recipe("wood_plank", {"wood": 2}, "wood_plank", 4, "workbench", 5.0)
_add_recipe("stone_brick", {"stone": 2}, "stone_brick", 4, "workbench", 5.0)
_add_recipe("wood_sword", {"wood": 5}, "wood_sword", 1, "workbench", 10.0)
_add_recipe("iron_sword", {"iron_ingot": 3}, "iron_sword", 1, "forge", 20.0)
_add_recipe("pal_ball", {"iron_ingot": 1, "crystal": 1}, "pal_ball", 3, "workbench", 15.0)
func _add_recipe(id: String, materials: Dictionary, result_id: String,
result_count: int, station: String, time: float) -> void:
var recipe := Recipe.new()
recipe.result_item = result_id
recipe.result_count = result_count
recipe.materials = materials
recipe.required_station = station
recipe.craft_time = time
_recipes[id] = recipe
## 检查能否合成
func can_craft(recipe_id: String, inventory: Dictionary) -> bool:
if not _recipes.has(recipe_id):
return false
var recipe: Recipe = _recipes[recipe_id]
for mat_id in recipe.materials:
var needed: int = recipe.materials[mat_id]
var have: int = inventory.get(mat_id, 0)
if have < needed:
return false
return true
## 开始合成
func start_craft(recipe_id: String, inventory: Dictionary) -> bool:
if not can_craft(recipe_id, inventory):
return false
var recipe: Recipe = _recipes[recipe_id]
# 扣除材料
for mat_id in recipe.materials:
inventory[mat_id] -= recipe.materials[mat_id]
# 添加合成任务
var job := CraftingJob.new()
job.recipe_id = recipe_id
job.timer = recipe.craft_time
_active_jobs.append(job)
print("开始合成: %s,需要 %.1f 秒" % [recipe_id, recipe.craft_time])
return true
## 完成合成
func _complete_craft(job) -> void:
if _recipes.has(job.recipe_id):
var recipe: Recipe = _recipes[job.recipe_id]
print("合成完成: %s x%d" % [recipe.result_item, recipe.result_count])
# TODO: 将产出到玩家背包
# 正在进行的合成任务
class CraftingJob:
var recipe_id: String
var timer: float饥饿与体力系统
什么是饥饿系统?
就像现实中人要吃饭一样,游戏角色也需要吃东西。不吃东西会越来越饿,饿了就跑不动,再不吃饭就会扣血。
生存属性数值
| 属性 | 范围 | 自然变化 | 影响 |
|---|---|---|---|
| 生命值(HP) | 0~100 | 极度饥饿时每秒 -1 | 归零 = 死亡 |
| 饥饿值 | 0~100 | 每分钟 -2 | 影响体力和移速 |
| 体力值 | 0~100 | 饱腹时每秒 +2 | 跑步、攻击消耗体力 |
生存系统代码
C#
// SurvivalSystem.cs
// 生存系统——管理饥饿、体力、生命值
using Godot;
public partial class SurvivalSystem : Node
{
[Export] public float MaxHunger = 100f;
[Export] public float MaxStamina = 100f;
[Export] public float MaxHp = 100f;
[Export] public float HungerDrainRate = 2f; // 每分钟饥饿下降
[Export] public float StaminaRegenRate = 2f; // 每秒体力恢复
public float CurrentHunger { get; private set; }
public float CurrentStamina { get; private set; }
public float CurrentHp { get; private set; }
private float _hungerTimer;
public override void _Ready()
{
CurrentHunger = MaxHunger;
CurrentStamina = MaxStamina;
CurrentHp = MaxHp;
}
public override void _Process(double delta)
{
float dt = (float)delta;
// 饥饿值持续下降
_hungerTimer += dt;
if (_hungerTimer >= 60f) // 每60秒扣一次
{
CurrentHunger -= HungerDrainRate;
CurrentHunger = Mathf.Max(0, CurrentHunger);
_hungerTimer = 0f;
}
// 根据饥饿状态处理效果
if (CurrentHunger < 20f)
{
// 极度饥饿:持续扣血
CurrentHp -= 1f * dt;
CurrentHp = Mathf.Max(0, CurrentHp);
}
// 体力恢复(只有不太饿时才恢复)
if (CurrentHunger > 50f)
{
CurrentStamina += StaminaRegenRate * dt;
}
else if (CurrentHunger > 20f)
{
CurrentStamina += StaminaRegenRate * 0.5f * dt; // 恢复减半
}
CurrentStamina = Mathf.Min(MaxStamina, CurrentStamina);
// 检查死亡
if (CurrentHp <= 0)
{
GD.Print("角色死亡!");
// TODO: 触发死亡处理
}
}
// 吃食物恢复饥饿值
public void Eat(float hungerRestore, float hpRestore = 0f)
{
CurrentHunger = Mathf.Min(MaxHunger, CurrentHunger + hungerRestore);
CurrentHp = Mathf.Min(MaxHp, CurrentHp + hpRestore);
GD.Print($"吃了食物:饥饿 +{hungerRestore},生命 +{hpRestore}");
}
// 消耗体力(跑步、攻击时调用)
public bool UseStamina(float amount)
{
if (CurrentStamina < amount) return false;
CurrentStamina -= amount;
return true;
}
// 获取饥饿状态对应的移动速度倍率
public float GetSpeedMultiplier()
{
if (CurrentHunger < 20f) return 0.5f;
if (CurrentHunger < 50f) return 0.7f;
return 1.0f;
}
}GDScript
# survival_system.gd
# 生存系统——管理饥饿、体力、生命值
extends Node
@export var max_hunger: float = 100.0
@export var max_stamina: float = 100.0
@export var max_hp: float = 100.0
@export var hunger_drain_rate: float = 2.0 # 每分钟饥饿下降
@export var stamina_regen_rate: float = 2.0 # 每秒体力恢复
var current_hunger: float = 100.0
var current_stamina: float = 100.0
var current_hp: float = 100.0
var _hunger_timer: float = 0.0
func _ready() -> void:
current_hunger = max_hunger
current_stamina = max_stamina
current_hp = max_hp
func _process(delta: float) -> void:
# 饥饿值持续下降
_hunger_timer += delta
if _hunger_timer >= 60.0: # 每60秒扣一次
current_hunger -= hunger_drain_rate
current_hunger = maxf(0, current_hunger)
_hunger_timer = 0.0
# 根据饥饿状态处理效果
if current_hunger < 20.0:
# 极度饥饿:持续扣血
current_hp -= 1.0 * delta
current_hp = maxf(0, current_hp)
# 体力恢复(只有不太饿时才恢复)
if current_hunger > 50.0:
current_stamina += stamina_regen_rate * delta
elif current_hunger > 20.0:
current_stamina += stamina_regen_rate * 0.5 * delta # 恢复减半
current_stamina = minf(max_stamina, current_stamina)
# 检查死亡
if current_hp <= 0:
print("角色死亡!")
# TODO: 触发死亡处理
## 吃食物恢复饥饿值
func eat(hunger_restore: float, hp_restore: float = 0.0) -> void:
current_hunger = minf(max_hunger, current_hunger + hunger_restore)
current_hp = minf(max_hp, current_hp + hp_restore)
print("吃了食物:饥饿 +%.0f,生命 +%.0f" % [hunger_restore, hp_restore])
## 消耗体力(跑步、攻击时调用)
func use_stamina(amount: float) -> bool:
if current_stamina < amount:
return false
current_stamina -= amount
return true
## 获取饥饿状态对应的移动速度倍率
func get_speed_multiplier() -> float:
if current_hunger < 20.0:
return 0.5
if current_hunger < 50.0:
return 0.7
return 1.0昼夜生存压力
为什么需要昼夜系统?
昼夜系统给游戏增加了"节奏感"——白天安心采集建造,夜晚要小心怪物。这就像给游戏加了一个"心跳",让玩家始终有紧迫感。
昼夜循环设计
| 时段 | 游戏时间 | 环境变化 | 生存压力 |
|---|---|---|---|
| 清晨 | 6:00-8:00 | 光线变亮 | 安全,适合外出 |
| 白天 | 8:00-16:00 | 明亮 | 最安全,采集建造 |
| 黄昏 | 16:00-18:00 | 光线变暗 | 提醒玩家准备回基地 |
| 夜晚 | 18:00-6:00 | 黑暗 | 危险!怪物更强更活跃 |
夜晚的特殊机制
- 怪物更强:夜晚出现的怪物攻击力 +50%,移动速度 +30%
- 视野受限:玩家周围只有火把/灯光照亮的小范围能看见
- 夜间 Boss:某些特殊 Boss 只在夜晚出现
- 温度下降:雪山和沼泽区域夜晚会额外扣血(需要保暖装备)
食物系统
| 食物 | 恢复饥饿 | 恢复生命 | 获取方式 |
|---|---|---|---|
| 野果 | +10 | 0 | 直接采集 |
| 烤肉 | +25 | +5 | 烹饪台制作 |
| 面包 | +20 | 0 | 烹饪台制作 |
| 炖菜 | +35 | +15 | 烹饪台制作 |
| 帕鲁料理 | +50 | +30 | 高级烹饪台 |
常见问题
Q:饥饿值下降会不会太快,让玩家没时间做其他事?
每分钟下降 2 点意味着 50 分钟游戏时间才会从满饿到 0。这个节奏给了玩家足够的时间去探索和建造,但也不会让你完全忽略吃饭这件事。如果觉得太紧,可以把 HungerDrainRate 调小。
Q:体力系统怎么影响战斗?
每次攻击消耗 10 点体力,跑步每秒消耗 5 点。体力不够时不能攻击也不能跑,只能慢慢走。所以战斗中需要合理安排攻击节奏,别一下把体力用光。
Q:夜晚能不能跳过?
MVP 阶段不建议跳过。正式版可以设计"床"这个建筑——在床上睡觉可以跳过夜晚,但睡觉时饥饿值也会下降,所以睡前要吃饱。
Q:死亡惩罚是什么?
建议设计成:死亡后掉落背包里一半的材料,回到基地复活。这样死亡有代价但不会太残忍,玩家会学着注意生存状态。
下一步
生存系统做好了,接下来实现 战斗与捕捉系统——让玩家可以和帕鲁打架并捕捉它们。
