7. 道具与装备系统
2026/4/13大约 10 分钟
道具与装备系统
道具让游戏更有趣
如果说武器是角色的"拳头",那道具就是角色的"背包"。在割草游戏中,地面上的道具(宝箱、恢复物、磁铁)和装备系统(武器槽位、被动道具)共同构成了游戏的"收集"维度。人类天生喜欢收集东西——无论是邮票、贝壳还是游戏里的装备。
本章我们要实现两个核心系统:地面道具拾取和武器进化合成。
地面道具系统
道具类型
在地图上随机出现的道具分为以下几种:
| 道具类型 | 外观 | 效果 | 出现方式 |
|---|---|---|---|
| 宝箱 | 金色箱子 | 随机获得新武器或被动道具 | 击杀精英/Boss后掉落 |
| 恢复物 | 红色心形 | 恢复一定生命值 | 随机掉落 |
| 磁铁 | 蓝色磁铁 | 临时大幅增加拾取范围 | 随机掉落 |
| 炸弹 | 黑色圆球 | 消灭屏幕内所有普通敌人 | 随机掉落 |
道具掉落规则
掉落概率设计
道具掉落不能太频繁(否则游戏没有挑战),也不能太少(否则玩家没有惊喜感)。建议:
- 普通敌人:5% 概率掉落恢复物
- 精英敌人:100% 掉落宝箱
- Boss:100% 掉落宝箱 + 额外奖励
道具基类实现
C
// GroundItem.cs
// 地面道具基类
using Godot;
public partial class GroundItem : Area3D
{
[Export] public string ItemName = "道具";
[Export] public float PickupRange = 1.2f;
// 道具类型
public enum ItemType
{
Chest, // 宝箱
Heal, // 恢复物
Magnet, // 磁铁
Bomb // 炸弹
}
[Export] public ItemType Type;
private Node3D _player;
private bool _isCollected = false;
private float _floatTimer = 0f;
public override void _Ready()
{
_player = GetTree().CurrentScene
.GetNodeOrNull<Node3D>("Player");
// 创建碰撞检测区域
var shape = new SphereShape3D();
shape.Radius = PickupRange;
var collision = new CollisionShape3D();
collision.Shape = shape;
AddChild(collision);
}
public override void _PhysicsProcess(double delta)
{
if (_isCollected || _player == null) return;
// 悬浮动画(上下浮动)
_floatTimer += (float)delta;
Position = new Vector3(
Position.X,
Mathf.Sin(_floatTimer * 3f) * 0.15f + 0.3f,
Position.Z
);
// 旋转动画
Rotation = new Vector3(0, _floatTimer * 2f, 0);
// 检测玩家是否靠近
float distance = GlobalPosition.DistanceTo(
_player.GlobalPosition);
if (distance < PickupRange)
{
Collect();
}
}
/// <summary>
/// 拾取道具(子类可重写以实现不同效果)
/// </summary>
protected virtual void Collect()
{
if (_isCollected) return;
_isCollected = true;
GD.Print($"拾取了 {ItemName}");
switch (Type)
{
case ItemType.Chest:
OpenChest();
break;
case ItemType.Heal:
HealPlayer();
break;
case ItemType.Magnet:
ActivateMagnet();
break;
case ItemType.Bomb:
ExplodeBomb();
break;
}
// 拾取动画(缩小消失)
var tween = CreateTween();
tween.TweenProperty(this, "scale",
Vector3.Zero, 0.3f);
tween.TweenCallback(QueueFree);
}
private void OpenChest()
{
// 打开宝箱:随机获得一个新武器或被动道具
var skillManager = GetTree().CurrentScene
.GetNodeOrNull<Node>("SkillSelectionManager");
if (skillManager != null && skillManager.HasMethod("ShowChestReward"))
{
skillManager.Call("ShowChestReward");
}
}
private void HealPlayer()
{
// 恢复生命值
if (_player.HasMethod("Heal"))
{
_player.Call("Heal", 20f);
}
}
private void ActivateMagnet()
{
// 临时增加拾取范围
var player = _player as CharacterBody3D;
if (player != null && player.HasMethod("AddMagnetBuff"))
{
player.Call("AddMagnetBuff", 10f); // 持续10秒
}
}
private void ExplodeBomb()
{
// 消灭屏幕内所有普通敌人
var enemies = GetTree().CurrentScene
.GetNodeOrNull<Node3D>("Enemies");
if (enemies != null)
{
foreach (Node3D enemy in enemies.GetChildren())
{
if (enemy.HasMethod("TakeDamage"))
{
enemy.Call("TakeDamage", 9999f);
}
}
}
// 爆炸特效(后续实现)
GD.Print("轰!炸弹爆炸了!");
}
}GDScript
# ground_item.gd
# 地面道具基类
extends Area3D
@export var item_name: String = "道具"
@export var pickup_range: float = 1.2
# 道具类型
enum ItemType { CHEST, HEAL, MAGNET, BOMB }
@export var type: ItemType
var _player: Node3D
var _is_collected: bool = false
var _float_timer: float = 0.0
func _ready():
_player = get_tree().current_scene.get_node_or_null("Player")
# 创建碰撞检测区域
var shape = SphereShape3D.new()
shape.radius = pickup_range
var collision = CollisionShape3D.new()
collision.shape = shape
add_child(collision)
func _physics_process(delta):
if _is_collected or _player == null:
return
# 悬浮动画(上下浮动)
_float_timer += delta
position = Vector3(
position.x,
sin(_float_timer * 3.0) * 0.15 + 0.3,
position.z
)
# 旋转动画
rotation = Vector3(0, _float_timer * 2.0, 0)
# 检测玩家是否靠近
var distance = global_position.distance_to(
_player.global_position)
if distance < pickup_range:
collect()
## 拾取道具
func collect():
if _is_collected:
return
_is_collected = true
print("拾取了 %s" % item_name)
match type:
ItemType.CHEST:
open_chest()
ItemType.HEAL:
heal_player()
ItemType.MAGNET:
activate_magnet()
ItemType.BOMB:
explode_bomb()
# 拾取动画(缩小消失)
var tween = create_tween()
tween.tween_property(self, "scale", Vector3.ZERO, 0.3)
tween.tween_callback(queue_free)
func open_chest():
# 打开宝箱:随机获得一个新武器或被动道具
var skill_manager = get_tree().current_scene \
.get_node_or_null("SkillSelectionManager")
if skill_manager and skill_manager.has_method("show_chest_reward"):
skill_manager.show_chest_reward()
func heal_player():
# 恢复生命值
if _player.has_method("heal"):
_player.heal(20.0)
func activate_magnet():
# 临时增加拾取范围
if _player.has_method("add_magnet_buff"):
_player.add_magnet_buff(10.0) # 持续10秒
func explode_bomb():
# 消灭屏幕内所有普通敌人
var enemies = get_tree().current_scene \
.get_node_or_null("Enemies")
if enemies:
for enemy in enemies.get_children():
if enemy.has_method("take_damage"):
enemy.take_damage(9999.0)
print("轰!炸弹爆炸了!")武器槽位系统
槽位规则
| 规则 | 说明 |
|---|---|
| 最大武器数 | 6把(达到上限后只能升级已有武器) |
| 最大被动道具 | 6个 |
| 丢弃武器 | 不支持(一旦选择不可撤销) |
| 替换机制 | 达到上限时,升级选项只会出现已有武器的升级 |
为什么限制6个?
6个武器是经过反复测试的最佳值。太少(3个)会让玩家觉得没有成长空间,太多(10个)会让画面过于混乱,而且会降低策略性——反正什么都能选,不需要权衡。
武器管理器
C
// WeaponManager.cs
// 管理玩家装备的所有武器
using Godot;
using System.Collections.Generic;
public partial class WeaponManager : Node3D
{
// === 配置 ===
[Export] public int MaxWeapons = 6;
// === 运行时状态 ===
public List<WeaponBase> EquippedWeapons { get; private set; }
= new List<WeaponBase>();
// 被动增益累积值
public Dictionary<string, float> PassiveBonuses { get; private set; }
= new Dictionary<string, float>();
// 信号
[Signal] public delegate void WeaponAddedEventHandler(
string weaponName);
[Signal] public delegate void WeaponLeveledUpEventHandler(
string weaponName, int level);
/// <summary>
/// 添加新武器
/// </summary>
public bool AddWeapon(PackedScene weaponScene)
{
if (EquippedWeapons.Count >= MaxWeapons) return false;
var weapon = weaponScene.Instantiate<WeaponBase>();
AddChild(weapon);
EquippedWeapons.Add(weapon);
GD.Print($"装备了新武器:{weapon.WeaponName}");
EmitSignal(SignalName.WeaponAdded, weapon.WeaponName);
return true;
}
/// <summary>
/// 升级指定武器
/// </summary>
public bool UpgradeWeapon(int weaponIndex)
{
if (weaponIndex < 0
|| weaponIndex >= EquippedWeapons.Count)
return false;
var weapon = EquippedWeapons[weaponIndex];
bool success = weapon.LevelUp();
if (success)
{
GD.Print($"{weapon.WeaponName} 升级到 " +
$"Lv.{weapon.CurrentLevel}");
EmitSignal(SignalName.WeaponLeveledUp,
weapon.WeaponName, weapon.CurrentLevel);
}
return success;
}
/// <summary>
/// 获取所有可升级的武器
/// </summary>
public List<WeaponBase> GetUpgradeableWeapons()
{
var result = new List<WeaponBase>();
foreach (var weapon in EquippedWeapons)
{
if (weapon.CurrentLevel < weapon.MaxLevel)
{
result.Add(weapon);
}
}
return result;
}
/// <summary>
/// 添加被动增益
/// </summary>
public void AddPassiveBonus(string effectName, float value)
{
if (PassiveBonuses.ContainsKey(effectName))
{
PassiveBonuses[effectName] += value;
}
else
{
PassiveBonuses[effectName] = value;
}
GD.Print($"被动增益:{effectName} +{value * 100}%");
}
/// <summary>
/// 获取指定被动增益的总值
/// </summary>
public float GetPassiveBonus(string effectName)
{
return PassiveBonuses.ContainsKey(effectName)
? PassiveBonuses[effectName]
: 0f;
}
/// <summary>
/// 检查武器栏是否已满
/// </summary>
public bool IsWeaponSlotFull()
{
return EquippedWeapons.Count >= MaxWeapons;
}
}GDScript
# weapon_manager.gd
# 管理玩家装备的所有武器
extends Node3D
# === 配置 ===
@export var max_weapons: int = 6
# === 运行时状态 ===
var equipped_weapons = []
var passive_bonuses = {}
# 信号
signal weapon_added(weapon_name: String)
signal weapon_leveled_up(weapon_name: String, level: int)
## 添加新武器
func add_weapon(weapon_scene: PackedScene) -> bool:
if equipped_weapons.size() >= max_weapons:
return false
var weapon = weapon_scene.instantiate()
add_child(weapon)
equipped_weapons.append(weapon)
print("装备了新武器:%s" % weapon.weapon_name)
weapon_added.emit(weapon.weapon_name)
return true
## 升级指定武器
func upgrade_weapon(weapon_index: int) -> bool:
if weapon_index < 0 or weapon_index >= equipped_weapons.size():
return false
var weapon = equipped_weapons[weapon_index]
var success = weapon.level_up()
if success:
print("%s 升级到 Lv.%d" % [
weapon.weapon_name, weapon.current_level])
weapon_leveled_up.emit(
weapon.weapon_name, weapon.current_level)
return success
## 获取所有可升级的武器
func get_upgradeable_weapons() -> Array:
var result = []
for weapon in equipped_weapons:
if weapon.current_level < weapon.max_level:
result.append(weapon)
return result
## 添加被动增益
func add_passive_bonus(effect_name: String, value: float):
if effect_name in passive_bonuses:
passive_bonuses[effect_name] += value
else:
passive_bonuses[effect_name] = value
print("被动增益:%s +%.0f%%" % [effect_name, value * 100])
## 获取指定被动增益的总值
func get_passive_bonus(effect_name: String) -> float:
return passive_bonuses.get(effect_name, 0.0)
## 检查武器栏是否已满
func is_weapon_slot_full() -> bool:
return equipped_weapons.size() >= max_weapons武器进化系统
什么是进化?
进化是割草游戏中最让人兴奋的系统。当两把特定武器都升到满级(通常8级)后,它们可以合体成一把更强大的"进化武器"。就像宝可梦进化一样——两个小家伙合在一起变成一个大家伙。
进化配方表
| 武器A | 武器B | 进化结果 | 效果描述 |
|---|---|---|---|
| 旋转刀片 Lv.8 | 空心书 Lv.8 | 邪恶之轮 | 巨大的旋转锯轮,伤害和范围翻倍 |
| 弹幕 Lv.8 | 护甲 Lv.8 | 密集弹幕 | 弹幕数量x3,穿透敌人 |
| 火焰AOE Lv.8 | 冷冻光环 Lv.8 | 地狱火 | 持续燃烧+减速+更高伤害 |
| 闪电链 Lv.8 | 磁铁 Lv.8 | 雷暴 | 全屏随机闪电攻击 |
| 鞭子 Lv.8 | 速度靴 Lv.8 | 死神镰刀 | 超长范围,一击秒杀普通怪 |
进化的隐藏性
建议不要在游戏内直接告诉玩家进化配方(至少不要全部公开)。让玩家自己探索和发现,会大大增加游戏的可玩时间和讨论度。可以在成就系统中给出暗示。
进化系统实现
C
// EvolutionSystem.cs
// 武器进化合成系统
using Godot;
using System.Collections.Generic;
public partial class EvolutionSystem : Node
{
// === 进化配方 ===
private Dictionary<string, EvolutionRecipe> _recipes
= new Dictionary<string, EvolutionRecipe>();
// === 引用 ===
private WeaponManager _weaponManager;
public override void _Ready()
{
_weaponManager = GetParent()
.GetNode<WeaponManager>("WeaponManager");
RegisterRecipes();
}
/// <summary>
/// 注册所有进化配方
/// </summary>
private void RegisterRecipes()
{
// 旋转刀片 + 空心书 = 邪恶之轮
_recipes["evil_wheel"] = new EvolutionRecipe
{
WeaponA = "旋转刀片",
WeaponB = "空心书",
ResultName = "邪恶之轮",
ResultScene = GD.Load<PackedScene>(
"res://scenes/weapons/evil_wheel.tscn"),
RequiredLevel = 8
};
// 弹幕 + 护甲 = 密集弹幕
_recipes["dense_barrage"] = new EvolutionRecipe
{
WeaponA = "弹幕",
WeaponB = "护甲",
ResultName = "密集弹幕",
ResultScene = GD.Load<PackedScene>(
"res://scenes/weapons/dense_barrage.tscn"),
RequiredLevel = 8
};
// 火焰AOE + 冷冻光环 = 地狱火
_recipes["hellfire"] = new EvolutionRecipe
{
WeaponA = "火焰AOE",
WeaponB = "冷冻光环",
ResultName = "地狱火",
ResultScene = GD.Load<PackedScene>(
"res://scenes/weapons/hellfire.tscn"),
RequiredLevel = 8
};
}
/// <summary>
/// 检查是否有可用的进化
/// </summary>
public List<EvolutionRecipe> CheckAvailableEvolutions()
{
var available = new List<EvolutionRecipe>();
var weaponNames = new HashSet<string>();
// 收集当前武器名称和等级
var weaponLevels = new Dictionary<string, int>();
foreach (var weapon in _weaponManager.EquippedWeapons)
{
weaponLevels[weapon.WeaponName] = weapon.CurrentLevel;
}
// 检查每个配方
foreach (var recipe in _recipes.Values)
{
bool hasA = weaponLevels.ContainsKey(recipe.WeaponA)
&& weaponLevels[recipe.WeaponA] >= recipe.RequiredLevel;
bool hasB = weaponLevels.ContainsKey(recipe.WeaponB)
&& weaponLevels[recipe.WeaponB] >= recipe.RequiredLevel;
if (hasA && hasB)
{
available.Add(recipe);
}
}
return available;
}
/// <summary>
/// 执行进化合成
/// </summary>
public bool PerformEvolution(EvolutionRecipe recipe)
{
// 移除两把基础武器
RemoveWeapon(recipe.WeaponA);
RemoveWeapon(recipe.WeaponB);
// 添加进化武器
bool success = _weaponManager.AddWeapon(recipe.ResultScene);
if (success)
{
GD.Print($"进化成功!{recipe.WeaponA} + {recipe.WeaponB}" +
$" = {recipe.ResultName}");
}
return success;
}
private void RemoveWeapon(string weaponName)
{
for (int i = _weaponManager.EquippedWeapons.Count - 1;
i >= 0; i--)
{
if (_weaponManager.EquippedWeapons[i].WeaponName
== weaponName)
{
var weapon = _weaponManager.EquippedWeapons[i];
_weaponManager.EquippedWeapons.RemoveAt(i);
weapon.QueueFree();
break;
}
}
}
}
// 进化配方数据结构
public class EvolutionRecipe
{
public string WeaponA { get; set; }
public string WeaponB { get; set; }
public string ResultName { get; set; }
public PackedScene ResultScene { get; set; }
public int RequiredLevel { get; set; } = 8;
}GDScript
# evolution_system.gd
# 武器进化合成系统
extends Node
# === 进化配方 ===
var _recipes = {}
# === 引用 ===
var _weapon_manager: Node
func _ready():
_weapon_manager = get_parent().get_node("WeaponManager")
register_recipes()
## 注册所有进化配方
func register_recipes():
# 旋转刀片 + 空心书 = 邪恶之轮
_recipes["evil_wheel"] = {
"weapon_a": "旋转刀片",
"weapon_b": "空心书",
"result_name": "邪恶之轮",
"result_scene": load(
"res://scenes/weapons/evil_wheel.tscn"),
"required_level": 8
}
# 弹幕 + 护甲 = 密集弹幕
_recipes["dense_barrage"] = {
"weapon_a": "弹幕",
"weapon_b": "护甲",
"result_name": "密集弹幕",
"result_scene": load(
"res://scenes/weapons/dense_barrage.tscn"),
"required_level": 8
}
# 火焰AOE + 冷冻光环 = 地狱火
_recipes["hellfire"] = {
"weapon_a": "火焰AOE",
"weapon_b": "冷冻光环",
"result_name": "地狱火",
"result_scene": load(
"res://scenes/weapons/hellfire.tscn"),
"required_level": 8
}
## 检查是否有可用的进化
func check_available_evolutions() -> Array:
var available = []
# 收集当前武器名称和等级
var weapon_levels = {}
for weapon in _weapon_manager.equipped_weapons:
weapon_levels[weapon.weapon_name] = weapon.current_level
# 检查每个配方
for key in _recipes:
var recipe = _recipes[key]
var has_a = weapon_levels.get(recipe.weapon_a, 0) \
>= recipe.required_level
var has_b = weapon_levels.get(recipe.weapon_b, 0) \
>= recipe.required_level
if has_a and has_b:
available.append(recipe)
return available
## 执行进化合成
func perform_evolution(recipe: Dictionary) -> bool:
# 移除两把基础武器
remove_weapon(recipe.weapon_a)
remove_weapon(recipe.weapon_b)
# 添加进化武器
var success = _weapon_manager.add_weapon(recipe.result_scene)
if success:
print("进化成功!%s + %s = %s" % [
recipe.weapon_a, recipe.weapon_b,
recipe.result_name])
return success
func remove_weapon(weapon_name: String):
for i in range(_weapon_manager.equipped_weapons.size() - 1, -1, -1):
if _weapon_manager.equipped_weapons[i].weapon_name == weapon_name:
var weapon = _weapon_manager.equipped_weapons[i]
_weapon_manager.equipped_weapons.remove_at(i)
weapon.queue_free()
break被动道具升级
被动道具不像武器那样可以"进化",但它们可以叠加。每次选择同一个被动增益,效果会叠加:
| 被动道具 | 基础效果 | 每级增加 | 最大等级 |
|---|---|---|---|
| 速度之靴 | 移速+10% | +10% | 5级(+50%) |
| 磁铁 | 拾取范围+25% | +25% | 5级(+125%) |
| 生命水晶 | 生命+20% | +20% | 5级(+100%) |
| 力量手套 | 伤害+15% | +15% | 5级(+75%) |
| 铁甲 | 减伤+10% | +10% | 5级(+50%) |
被动道具的搭配策略
- "磁铁 + 速度之靴" = 快速收集经验的跑酷流
- "力量手套 + 铁甲" = 站桩输出的坦克流
- "生命水晶 + 铁甲" = 硬扛伤害的持久流
总结
本章我们实现了:
- 地面道具 — 宝箱、恢复物、磁铁、炸弹
- 武器槽位 — 最多装备6把武器
- 武器进化 — 两把满级武器合体成更强的武器
- 被动增益叠加 — 同一被动可以多次选择叠加效果
装备系统让游戏有了"构建"(build)的概念——每局选择不同的武器和被动组合,形成独特的战斗风格。下一章,我们要创建大地图,让玩家有一个更大的世界可以探索。
