7. 道具与升级系统
2026/4/13大约 8 分钟
道具与升级系统
道具系统概述
什么是道具系统?
道具系统就是"打敌人掉东西,捡了能变强"的机制。它是魂斗罗中最让人兴奋的部分之一——你正在苦战,突然一个飞鹰标志从天而降,捡到之后散弹枪在手,瞬间翻盘。这种"从弱变强"的体验是游戏成瘾性的关键。
道具的核心作用
| 作用 | 描述 | 玩家感受 |
|---|---|---|
| 奖励 | 打败强敌后的回报 | "值了!" |
| 成长 | 让角色变得更强 | "我变厉害了!" |
| 策略 | 不同道具适合不同情况 | "该选哪个?" |
| 惊喜 | 随机掉落带来意外之喜 | "哇,居然是这个!" |
道具类型一览
永久道具(一直有效直到死亡)
| 道具 | 外观 | 效果 | 稀有度 |
|---|---|---|---|
| 机枪 | 飞鹰 + M | 射速翻倍 | 普通 |
| 散弹枪 | 飞鹰 + S | 5方向散射 | 稀有 |
| 激光枪 | 飞鹰 + L | 穿透激光 | 稀有 |
| 火焰枪 | 飞鹰 + F | 近距离高伤害 | 普通 |
临时道具(一段时间后消失)
| 道具 | 外观 | 效果 | 持续时间 |
|---|---|---|---|
| 护盾 | 蓝色光球 | 免疫一次伤害 | 直到被击中 |
| 无敌 | 闪烁光圈 | 完全无敌 | 5秒 |
| 加速 | 闪电标志 | 移动速度翻倍 | 10秒 |
| 速射 | 双枪标志 | 射速再翻倍 | 8秒 |
资源道具
| 道具 | 外观 | 效果 |
|---|---|---|
| 额外生命 | 红色飞鹰 | 生命+1 |
| 分数奖励 | 金色星星 | 额外1000分 |
道具场景
道具场景结构
PowerUp (Area3D) # 道具根节点
├── MeshInstance3D # 道具外观(旋转的标志)
├── CollisionShape3D # 碰撞体
├── PointLight3D # 发光效果
├── AnimationPlayer # 旋转/闪烁动画
└── PowerUp.cs / PowerUp.gd # 道具脚本道具脚本
C#
using Godot;
/// <summary>
/// 道具基类 - 掉落物的基础行为
/// </summary>
public partial class PowerUp : Area3D
{
// ===== 道具类型 =====
public enum PowerUpType
{
MachineGun, // 机枪
SpreadGun, // 散弹枪
LaserGun, // 激光枪
FireGun, // 火焰枪
Shield, // 护盾
Invincible, // 无敌
ExtraLife, // 额外生命
ScoreBonus // 分数奖励
}
[Export] public PowerUpType Type;
[Export] public float RotationSpeed = 2.0f; // 旋转速度
[Export] public float FloatAmplitude = 0.3f; // 上下浮动幅度
[Export] public float FloatSpeed = 3.0f; // 浮动速度
[Export] public float Lifetime = 15.0f; // 存在时间(秒)
private Vector3 _startPosition;
private float _timer = 0.0f;
private bool _collected = false;
// ===== 信号 =====
[Signal] public delegate void PowerUpCollectedEventHandler(PowerUpType type);
public override void _Ready()
{
_startPosition = GlobalPosition;
BodyEntered += OnBodyEntered;
}
public override void _Process(double delta)
{
if (_collected) return;
float dt = (float)delta;
_timer += dt;
// 旋转道具
RotateY(RotationSpeed * dt);
// 上下浮动
float floatOffset = Mathf.Sin(_timer * FloatSpeed) * FloatAmplitude;
Position = new Vector3(
Position.X,
_startPosition.Y + floatOffset,
Position.Z
);
// 超时消失
if (_timer >= Lifetime)
{
FadeOut();
}
}
/// <summary>
/// 玩家碰到道具
/// </summary>
private void OnBodyEntered(Node3D body)
{
if (!(body is Player)) return;
if (_collected) return;
_collected = true;
GD.Print($"玩家捡到了道具: {Type}");
// 发出信号
EmitSignal(SignalName.PowerUpCollected, Type);
// 播放拾取动画
CollectAnimation();
}
/// <summary>
/// 拾取动画
/// </summary>
private void CollectAnimation()
{
var tween = CreateTween();
tween.TweenProperty(this, "scale", Vector3.One * 1.5f, 0.2);
tween.TweenProperty(this, "scale", Vector3.Zero, 0.3);
tween.TweenCallback(Callable.From(QueueFree));
}
/// <summary>
/// 超时消失动画
/// </summary>
private void FadeOut()
{
var tween = CreateTween();
// 闪烁5次后消失
for (int i = 0; i < 5; i++)
{
tween.TweenProperty(this, "visible", false, 0.1);
tween.TweenProperty(this, "visible", true, 0.1);
}
tween.TweenCallback(Callable.From(QueueFree));
}
}GDScript
extends Area3D
## 道具基类 - 掉落物的基础行为
# ===== 道具类型 =====
enum PowerUpType {
MACHINE_GUN, # 机枪
SPREAD_GUN, # 散弹枪
LASER_GUN, # 激光枪
FIRE_GUN, # 火焰枪
SHIELD, # 护盾
INVINCIBLE, # 无敌
EXTRA_LIFE, # 额外生命
SCORE_BONUS # 分数奖励
}
@export var type: PowerUpType
@export var rotation_speed: float = 2.0 # 旋转速度
@export var float_amplitude: float = 0.3 # 上下浮动幅度
@export var float_speed: float = 3.0 # 浮动速度
@export var lifetime: float = 15.0 # 存在时间(秒)
var _start_position: Vector3
var _timer: float = 0.0
var _collected: bool = false
# ===== 信号 =====
signal power_up_collected(type: int)
func _ready():
_start_position = global_position
body_entered.connect(_on_body_entered)
func _process(delta):
if _collected:
return
_timer += delta
# 旋转道具
rotate_y(rotation_speed * delta)
# 上下浮动
var float_offset = sin(_timer * float_speed) * float_amplitude
position = Vector3(
position.x,
_start_position.y + float_offset,
position.z
)
# 超时消失
if _timer >= lifetime:
_fade_out()
## 玩家碰到道具
func _on_body_entered(body: Node3D):
if not body is Player:
return
if _collected:
return
_collected = true
print("玩家捡到了道具: %d" % type)
# 发出信号
power_up_collected.emit(type)
# 播放拾取动画
_collect_animation()
## 拾取动画
func _collect_animation():
var tween = create_tween()
tween.tween_property(self, "scale", Vector3.ONE * 1.5, 0.2)
tween.tween_property(self, "scale", Vector3.ZERO, 0.3)
tween.tween_callback(queue_free)
## 超时消失动画
func _fade_out():
var tween = create_tween()
# 闪烁5次后消失
for i in range(5):
tween.tween_property(self, "visible", false, 0.1)
tween.tween_property(self, "visible", true, 0.1)
tween.tween_callback(queue_free)道具生成规则
掉落条件
| 敌人类型 | 基础掉落率 | 掉落类型 |
|---|---|---|
| 步兵 | 10% | 随机武器 |
| 炮台 | 30% | 随机武器(概率更好) |
| 飞行器 | 20% | 随机武器或临时道具 |
| 重装兵 | 50% | 高概率好武器 |
| Boss | 100% | 必定掉落好武器 + 额外生命 |
随机掉落逻辑
C#
using Godot;
/// <summary>
/// 道具管理器 - 管理道具的生成和效果
/// </summary>
public partial class PowerUpManager : Node3D
{
[Export] public PackedScene PowerUpScene;
[Export] public float DropHeight = 2.0f; // 道具掉落高度
// 武器掉落权重
private static readonly (PowerUp.PowerUpType Type, float Weight)[] WeaponWeights = {
(PowerUp.PowerUpType.MachineGun, 40), // 40% 概率
(PowerUp.PowerUpType.SpreadGun, 20), // 20% 概率
(PowerUp.PowerUpType.LaserGun, 15), // 15% 概率
(PowerUp.PowerUpType.FireGun, 15), // 15% 概率
(PowerUp.PowerUpType.ExtraLife, 5), // 5% 概率
(PowerUp.PowerUpType.Shield, 5), // 5% 概率
};
private Player _player;
public override void _Ready()
{
_player = GetTree().GetFirstNodeInGroup("player") as Player;
}
/// <summary>
/// 在指定位置生成道具
/// </summary>
public void SpawnPowerUp(Vector3 position, float dropChance)
{
// 概率判定
if (GD.Randf() > dropChance) return;
// 随机选择道具类型
var type = GetRandomPowerUpType();
// 创建道具实例
var powerUp = PowerUpScene.Instantiate<PowerUp>();
powerUp.Type = type;
powerUp.GlobalPosition = position + new Vector3(0, DropHeight, 0);
GetParent().AddChild(powerUp);
// 连接拾取信号
powerUp.PowerUpCollected += OnPowerUpCollected;
GD.Print($"生成道具: {type} 在位置 {position}");
}
/// <summary>
/// 根据权重随机选择道具类型
/// </summary>
private PowerUp.PowerUpType GetRandomPowerUpType()
{
float totalWeight = 0;
foreach (var (_, weight) in WeaponWeights)
totalWeight += weight;
float roll = GD.Randf() * totalWeight;
foreach (var (type, weight) in WeaponWeights)
{
roll -= weight;
if (roll <= 0)
return type;
}
return PowerUp.PowerUpType.MachineGun;
}
/// <summary>
/// 道具被拾取时
/// </summary>
private void OnPowerUpCollected(PowerUp.PowerUpType type)
{
if (_player == null) return;
switch (type)
{
case PowerUp.PowerUpType.MachineGun:
ApplyWeaponUpgrade(WeaponData.MachineGun);
break;
case PowerUp.PowerUpType.SpreadGun:
ApplyWeaponUpgrade(WeaponData.SpreadGun);
break;
case PowerUp.PowerUpType.LaserGun:
ApplyWeaponUpgrade(WeaponData.LaserGun);
break;
case PowerUp.PowerUpType.FireGun:
ApplyWeaponUpgrade(WeaponData.FireGun);
break;
case PowerUp.PowerUpType.ExtraLife:
_player.AddLife();
break;
case PowerUp.PowerUpType.Shield:
_player.ActivateShield();
break;
case PowerUp.PowerUpType.Invincible:
_player.ActivateInvincibility(5.0f);
break;
case PowerUp.PowerUpType.ScoreBonus:
AddScore(1000);
break;
}
}
private void ApplyWeaponUpgrade(WeaponData weapon)
{
// 通知射击管理器切换武器
var shootingManager = GetTree().GetFirstNodeInGroup("shooting_manager");
if (shootingManager != null && shootingManager.HasMethod("SwitchWeapon"))
{
shootingManager.Call("SwitchWeapon", weapon);
}
}
private void AddScore(int points)
{
var levelManager = GetTree().GetFirstNodeInGroup("level_manager");
if (levelManager != null && levelManager.HasMethod("AddScore"))
{
levelManager.Call("AddScore", points);
}
}
}GDScript
extends Node3D
## 道具管理器 - 管理道具的生成和效果
@export var power_up_scene: PackedScene
@export var drop_height: float = 2.0 # 道具掉落高度
# 武器掉落权重
var _weapon_weights: Array[Dictionary] = [
{ "type": PowerUp.PowerUpType.MACHINE_GUN, "weight": 40 }, # 40%
{ "type": PowerUp.PowerUpType.SPREAD_GUN, "weight": 20 }, # 20%
{ "type": PowerUp.PowerUpType.LASER_GUN, "weight": 15 }, # 15%
{ "type": PowerUp.PowerUpType.FIRE_GUN, "weight": 15 }, # 15%
{ "type": PowerUp.PowerUpType.EXTRA_LIFE, "weight": 5 }, # 5%
{ "type": PowerUp.PowerUpType.SHIELD, "weight": 5 }, # 5%
]
var _player: Node
func _ready():
_player = get_tree().get_first_node_in_group("player")
## 在指定位置生成道具
func spawn_power_up(pos: Vector3, drop_chance: float):
# 概率判定
if randf() > drop_chance:
return
# 随机选择道具类型
var type = _get_random_power_up_type()
# 创建道具实例
var power_up = power_up_scene.instantiate()
power_up.type = type
power_up.global_position = pos + Vector3(0, drop_height, 0)
get_parent().add_child(power_up)
# 连接拾取信号
power_up.power_up_collected.connect(_on_power_up_collected)
print("生成道具: %d 在位置 %s" % [type, str(pos)])
## 根据权重随机选择道具类型
func _get_random_power_up_type() -> int:
var total_weight: float = 0
for item in _weapon_weights:
total_weight += item["weight"]
var roll: float = randf() * total_weight
for item in _weapon_weights:
roll -= item["weight"]
if roll <= 0:
return item["type"]
return PowerUp.PowerUpType.MACHINE_GUN
## 道具被拾取时
func _on_power_up_collected(type: int):
if not _player:
return
match type:
PowerUp.PowerUpType.MACHINE_GUN:
_apply_weapon_upgrade(WeaponData.machine_gun())
PowerUp.PowerUpType.SPREAD_GUN:
_apply_weapon_upgrade(WeaponData.spread_gun())
PowerUp.PowerUpType.LASER_GUN:
_apply_weapon_upgrade(WeaponData.laser_gun())
PowerUp.PowerUpType.FIRE_GUN:
_apply_weapon_upgrade(WeaponData.fire_gun())
PowerUp.PowerUpType.EXTRA_LIFE:
_player.add_life()
PowerUp.PowerUpType.SHIELD:
_player.activate_shield()
PowerUp.PowerUpType.INVINCIBLE:
_player.activate_invincibility(5.0)
PowerUp.PowerUpType.SCORE_BONUS:
_add_score(1000)
func _apply_weapon_upgrade(weapon: WeaponData):
var shooting_manager = get_tree().get_first_node_in_group("shooting_manager")
if shooting_manager and shooting_manager.has_method("switch_weapon"):
shooting_manager.switch_weapon(weapon)
func _add_score(points: int):
var level_manager = get_tree().get_first_node_in_group("level_manager")
if level_manager and level_manager.has_method("add_score"):
level_manager.add_score(points)武器升级效果详解
升级规则
死亡降级
原版魂斗罗中,角色死亡后武器会降级——散弹枪变回普通枪。这个设计增加了紧张感,让玩家更小心。我们也可以实现这个机制。
| 当前武器 | 死亡后降级为 |
|---|---|
| 激光枪 | 散弹枪 |
| 散弹枪 | 机枪 |
| 机枪 | 普通枪 |
| 普通枪 | 普通枪(已经最低了) |
S标志升级机制
原版魂斗罗中,飞鹰标志上的"S"代表升级。每次捡到同类型的武器道具,武器会升级:
捡到第1个飞鹰 → 机枪(基础版)
捡到第2个飞鹰 → 机枪(射速+20%)
捡到第3个飞鹰 → 机枪(射速+40%)
...以此类推,最高5级护盾与无敌效果
护盾效果
护盾的工作方式
护盾就像一层"额外血量"。当玩家有护盾时,被敌人碰到不会死,而是消耗护盾。护盾消失了就恢复正常状态。护盾不会自动消失——它只在被击中时消耗。
无敌效果
无敌时间通常是5秒,期间:
- 角色闪烁(提示玩家"你无敌了")
- 碰到敌人不会受伤
- 碰到敌人子弹也不会受伤
- 可以冲进敌群横扫
道具设计原则
| 原则 | 说明 |
|---|---|
| 有价值 | 道具必须让玩家感到"赚到了" |
| 有选择 | 不同道具有不同用途,玩家需要思考 |
| 有风险 | 好道具放在危险位置,鼓励冒险 |
| 有反馈 | 捡到道具时有明显的视觉和音效反馈 |
| 不过分 | 道具让游戏变简单,但不能让游戏失去挑战 |
道具放置的黄金位置
- 隐藏角落——探索的奖励
- 危险区域中央——风险与回报并存
- 打败强敌后——成就感加倍
- 连续跳跃平台上方——考验操作
- 关卡中段——给玩家补充力量
本章小结
本章我们实现了完整的道具系统:
- 道具类型——武器升级、临时增益、资源道具
- 道具场景——旋转浮动动画、超时消失
- 掉落规则——按概率随机掉落,权重可调
- 道具管理器——统一管理道具生成和效果
- 升级机制——同类型道具叠加升级
- 护盾与无敌——临时增益效果
下一章预告
下一章将实现Boss战系统——多阶段Boss、状态机、弹幕设计和Boss死亡动画。Boss战是每个关卡的高潮,需要精心设计。
