6. 道具系统
2026/4/14大约 7 分钟
6. 雷霆战机——道具系统
6.1 道具是什么?
想象你在打怪的时候,怪物掉落了宝箱——打开可能是金币、药水、武器。游戏中的道具(Power-Up) 就是类似的奖励机制。
在雷霆战机中,敌人被消灭后有一定概率掉落道具。玩家只需要飞过去就能拾取。道具让游戏有了"成长感"——你越打越强,也越想继续打下去。
6.2 道具类型
| 道具 | 颜色 | 效果 | 掉落概率 |
|---|---|---|---|
| P(Power) | 红色 | 武器等级+1 | 10% |
| B(Bomb) | 蓝色 | 炸弹+1 | 5% |
| S(Shield) | 绿色 | 获得护盾(挡一次伤害) | 3% |
| 1UP | 金色 | 生命+1 | 2% |
| Score | 黄色 | 额外500分 | 10% |
| M(Missile) | 紫色 | 发射追踪导弹 | 5% |
6.3 道具基类
C
using Godot;
/// <summary>
/// 道具基类
/// </summary>
public partial class PowerUp : Area2D
{
// 道具类型枚举
public enum PowerUpType
{
Power, // 武器升级
Bomb, // 炸弹
Shield, // 护盾
Life, // 1UP
Score, // 分数
Missile // 追踪导弹
}
[Export] public PowerUpType Type { get; set; }
[Export] public Color ItemColor { get; set; } = Colors.Red;
private float _lifetime = 0f;
private bool _collected = false;
private Sprite2D _sprite;
private Label _label;
// 颜色映射
private static readonly Color[] TypeColors =
{
Colors.Red, // Power
Colors.Blue, // Bomb
Colors.Green, // Shield
Colors.Gold, // Life
Colors.Yellow, // Score
Colors.Purple // Missile
};
// 标签映射
private static readonly string[] TypeLabels =
{
"P", "B", "S", "1UP", "$", "M"
};
public override void _Ready()
{
_sprite = GetNode<Sprite2D>("Sprite");
_label = GetNode<Label>("Label");
ItemColor = TypeColors[(int)Type];
_label.Text = TypeLabels[(int)Type];
_label.AddThemeColorOverride(
"font_color", ItemColor);
// 碰撞检测区域
var shape = new CircleShape2D();
shape.Radius = 12f;
var collision = new CollisionShape2D();
collision.Shape = shape;
AddChild(collision);
}
public override void _Process(double delta)
{
if (_collected) return;
// 向下飘落
Position += new Vector2(0, GameConfig.PowerUpSpeed) * (float)delta;
// 轻微左右摆动
_lifetime += (float)delta;
Position += new Vector2(
Mathf.Sin(_lifetime * 3f) * 20f * (float)delta,
0
);
// 闪烁提示即将消失
if (_lifetime > GameConfig.PowerUpLifetime - 2f)
{
Visible = Mathf.Sin(_lifetime * 10f) > 0;
}
// 超时自动消失
if (_lifetime > GameConfig.PowerUpLifetime)
{
QueueFree();
}
// 超出屏幕底部
if (Position.Y > GameConfig.ScreenHeight + 20f)
{
QueueFree();
}
}
/// <summary>
/// 被玩家拾取
/// </summary>
public void Collect()
{
if (_collected) return;
_collected = true;
var manager = GetNode<RaidenGameManager>(
"/root/RaidenGameManager");
switch (Type)
{
case PowerUpType.Power:
manager.UpgradeWeapon();
break;
case PowerUpType.Bomb:
manager.CollectBomb();
break;
case PowerUpType.Shield:
// 给玩家添加护盾(由Player脚本处理)
var player = GetNodeOrNull<Node>(
"/root/Main/GameArea/Player");
player?.Call("AddShield");
break;
case PowerUpType.Life:
// 生命+1(通过GameManager处理)
manager.Call("AddLife");
break;
case PowerUpType.Score:
manager.AddScore(500);
break;
case PowerUpType.Missile:
player = GetNodeOrNull<Node>(
"/root/Main/GameArea/Player");
player?.Call("AddMissiles", 5);
break;
}
// 拾取特效
PlayCollectEffect();
QueueFree();
}
private void PlayCollectEffect()
{
var tween = CreateTween();
tween.TweenProperty(this, "scale",
new Vector2(1.5f, 1.5f), 0.1f);
tween.TweenProperty(this, "modulate:a", 0f, 0.2f);
tween.TweenCallback(Callable.From(QueueFree));
}
}GDScript
extends Area2D
## 道具基类
enum PowerUpType { POWER, BOMB, SHIELD, LIFE, SCORE, MISSILE }
@export var type: PowerUpType = PowerUpType.POWER
@export var item_color: Color = Color.RED
var _lifetime: float = 0.0
var _collected: bool = false
# 颜色映射
const TYPE_COLORS = [
Color.RED, # Power
Color.BLUE, # Bomb
Color.GREEN, # Shield
Color.GOLD, # Life
Color.YELLOW, # Score
Color.PURPLE # Missile
]
# 标签映射
const TYPE_LABELS = ["P", "B", "S", "1UP", "$", "M"]
func _ready() -> void:
item_color = TYPE_COLORS[type]
$Label.text = TYPE_LABELS[type]
$Label.add_theme_color_override("font_color", item_color)
var shape = CircleShape2D.new()
shape.radius = 12.0
var collision = CollisionShape2D.new()
collision.shape = shape
add_child(collision)
func _process(delta: float) -> void:
if _collected:
return
# 向下飘落
position += Vector2(0, GameConfig.POWERUP_SPEED) * delta
# 轻微左右摆动
_lifetime += delta
position += Vector2(sin(_lifetime * 3.0) * 20.0 * delta, 0)
# 闪烁提示即将消失
if _lifetime > GameConfig.POWERUP_LIFETIME - 2.0:
visible = sin(_lifetime * 10.0) > 0
# 超时自动消失
if _lifetime > GameConfig.POWERUP_LIFETIME:
queue_free()
# 超出屏幕底部
if position.y > GameConfig.SCREEN_HEIGHT + 20.0:
queue_free()
## 被玩家拾取
func collect() -> void:
if _collected:
return
_collected = true
var manager = get_node("/root/RaidenGameManager")
match type:
PowerUpType.POWER:
manager.upgrade_weapon()
PowerUpType.BOMB:
manager.collect_bomb()
PowerUpType.SHIELD:
var player = get_node_or_null("/root/Main/GameArea/Player")
if player:
player.add_shield()
PowerUpType.LIFE:
manager.add_life()
PowerUpType.SCORE:
manager.add_score(500)
PowerUpType.MISSILE:
var player = get_node_or_null("/root/Main/GameArea/Player")
if player:
player.add_missiles(5)
# 拾取特效
_play_collect_effect()
func _play_collect_effect() -> void:
var tween = create_tween()
tween.tween_property(self, "scale", Vector2(1.5, 1.5), 0.1)
tween.tween_property(self, "modulate:a", 0.0, 0.2)
tween.tween_callback(queue_free)6.4 道具掉落管理
敌人被消灭后,按概率掉落道具。
C
using Godot;
/// <summary>
/// 道具管理器——处理道具掉落和生成
/// </summary>
public partial class PowerUpManager : Node
{
private Node2D _container;
private PackedScene _powerUpScene;
private Random _random = new();
// 加权随机表
private (PowerUp.PowerUpType Type, float Weight)[] _dropTable =
{
(PowerUp.PowerUpType.Power, 30f),
(PowerUp.PowerUpType.Bomb, 15f),
(PowerUp.PowerUpType.Score, 25f),
(PowerUp.PowerUpType.Missile, 15f),
(PowerUp.PowerUpType.Shield, 8f),
(PowerUp.PowerUpType.Life, 7f),
};
private float _totalWeight;
public override void _Ready()
{
_container = GetNode<Node2D>("../GameArea/PowerUps");
_powerUpScene = GD.Load<PackedScene>(
"res://scenes/powerups/power_up.tscn");
foreach (var (_, weight) in _dropTable)
_totalWeight += weight;
}
/// <summary>
/// 尝试从指定位置掉落道具
/// </summary>
public void TryDrop(Vector2 position, float dropChance)
{
// 概率判定
float roll = _random.NextSingle();
if (roll > dropChance) return;
// 从加权表中随机选择道具类型
PowerUp.PowerUpType type = GetRandomType();
// 创建道具实例
var powerUp = _powerUpScene.Instantiate<PowerUp>();
powerUp.Type = type;
powerUp.GlobalPosition = position;
_container.AddChild(powerUp);
}
/// <summary>
/// 加权随机选择道具类型
/// </summary>
private PowerUp.PowerUpType GetRandomType()
{
float roll = _random.NextSingle() * _totalWeight;
float cumulative = 0f;
foreach (var (type, weight) in _dropTable)
{
cumulative += weight;
if (roll <= cumulative)
return type;
}
return PowerUp.PowerUpType.Power;
}
/// <summary>
/// 强制掉落指定类型的道具
/// </summary>
public void ForceDrop(Vector2 position, PowerUp.PowerUpType type)
{
var powerUp = _powerUpScene.Instantiate<PowerUp>();
powerUp.Type = type;
powerUp.GlobalPosition = position;
_container.AddChild(powerUp);
}
}GDScript
extends Node
## 道具管理器——处理道具掉落和生成
var _container: Node2D
var _power_up_scene: PackedScene
var _random = RandomNumberGenerator.new()
# 加权随机表
var _drop_table = [
{"type": 0, "weight": 30.0}, # Power
{"type": 1, "weight": 15.0}, # Bomb
{"type": 4, "weight": 25.0}, # Score
{"type": 5, "weight": 15.0}, # Missile
{"type": 2, "weight": 8.0}, # Shield
{"type": 3, "weight": 7.0}, # Life
]
var _total_weight: float = 0.0
func _ready() -> void:
_container = get_node("../GameArea/PowerUps")
_power_up_scene = load("res://scenes/powerups/power_up.tscn")
for entry in _drop_table:
_total_weight += entry["weight"]
## 尝试从指定位置掉落道具
func try_drop(pos: Vector2, drop_chance: float) -> void:
var roll = randf()
if roll > drop_chance:
return
var type = _get_random_type()
var power_up = _power_up_scene.instantiate()
power_up.type = type
power_up.global_position = pos
_container.add_child(power_up)
## 加权随机选择道具类型
func _get_random_type() -> int:
var roll = randf() * _total_weight
var cumulative = 0.0
for entry in _drop_table:
cumulative += entry["weight"]
if roll <= cumulative:
return entry["type"]
return 0 # 默认Power
## 强制掉落指定类型的道具
func force_drop(pos: Vector2, type: int) -> void:
var power_up = _power_up_scene.instantiate()
power_up.type = type
power_up.global_position = pos
_container.add_child(power_up)6.5 护盾系统
护盾是一种特殊道具效果:获得护盾后,玩家可以抵挡一次伤害。
C
/// <summary>
/// 玩家护盾(附加在Player脚本中)
/// </summary>
// 在Player类中添加以下属性和方法:
private bool _hasShield = false;
private Sprite2D _shieldVisual;
public void AddShield()
{
_hasShield = true;
if (_shieldVisual != null)
_shieldVisual.Visible = true;
// 播放获得护盾音效
}
// 修改OnHit方法,优先消耗护盾
public void OnHit()
{
if (_isInvincible) return;
if (_hasShield)
{
_hasShield = false;
if (_shieldVisual != null)
_shieldVisual.Visible = false;
// 播放护盾破碎特效
return;
}
_gameManager.LoseLife();
// ... 原有的受伤逻辑
}GDScript
## 在Player脚本中添加护盾逻辑
var _has_shield: bool = false
@onready var _shield_visual = $ShieldVisual
func add_shield() -> void:
_has_shield = true
if _shield_visual:
_shield_visual.visible = true
# 修改on_hit方法
func on_hit() -> void:
if _is_invincible:
return
if _has_shield:
_has_shield = false
if _shield_visual:
_shield_visual.visible = false
# 播放护盾破碎特效
return
_game_manager.lose_life()
# ... 原有逻辑6.6 炸弹效果
使用炸弹时,清除屏幕上所有敌人子弹,并对所有敌人造成伤害。
C
/// <summary>
/// 炸弹效果
/// </summary>
public partial class BombEffect : Node2D
{
private float _duration = 0.5f;
private float _timer = 0f;
public override void _Ready()
{
// 全屏白色闪光
var rect = new ColorRect();
rect.Color = new Color(1, 1, 1, 0.8f);
rect.Size = GetViewportRect().Size;
AddChild(rect);
ZIndex = 100;
}
public override void _Process(double delta)
{
_timer += (float)delta;
// 闪光淡出
var rect = GetChild<ColorRect>(0);
float alpha = Mathf.Lerp(0.8f, 0f, _timer / _duration);
rect.Color = new Color(1, 1, 1, alpha);
if (_timer >= _duration)
{
QueueFree();
}
}
/// <summary>
/// 执行炸弹效果
/// </summary>
public static void Execute(Node rootNode)
{
var manager = rootNode.GetNode<RaidenGameManager>(
"/root/RaidenGameManager");
manager.UseBomb();
// 清除所有敌人子弹
var enemyBullets = rootNode.GetNode<Node2D>(
"Main/GameArea/EnemyBullets");
foreach (var bullet in enemyBullets.GetChildren())
{
(bullet as Node)?.QueueFree();
}
// 对所有敌人造成伤害
var enemies = rootNode.GetNode<Node2D>(
"Main/GameArea/Enemies");
foreach (var enemy in enemies.GetChildren())
{
(enemy as Enemy)?.TakeDamage(5);
}
// 显示炸弹特效
var effect = new BombEffect();
effect.Position = new Vector2(
GameConfig.ScreenWidth / 2f,
GameConfig.ScreenHeight / 2f);
rootNode.GetNode("Main/GameArea/Effects").AddChild(effect);
// 屏幕震动
ScreenShake.Shake(GameConfig.ScreenShakeOnBomb, 0.5f);
}
}GDScript
extends Node2D
## 炸弹效果
var _duration: float = 0.5
var _timer: float = 0.0
func _ready() -> void:
# 全屏白色闪光
var rect = ColorRect.new()
rect.color = Color(1, 1, 1, 0.8)
rect.size = get_viewport_rect().size
add_child(rect)
z_index = 100
func _process(delta: float) -> void:
_timer += delta
var rect = get_child(0) as ColorRect
var alpha = lerpf(0.8, 0.0, _timer / _duration)
rect.color = Color(1, 1, 1, alpha)
if _timer >= _duration:
queue_free()
## 执行炸弹效果
static func execute(root_node: Node) -> void:
var manager = root_node.get_node("/root/RaidenGameManager")
manager.use_bomb()
# 清除所有敌人子弹
var enemy_bullets = root_node.get_node("Main/GameArea/EnemyBullets")
for bullet in enemy_bullets.get_children():
bullet.queue_free()
# 对所有敌人造成伤害
var enemies = root_node.get_node("Main/GameArea/Enemies")
for enemy in enemies.get_children():
if enemy.has_method("take_damage"):
enemy.take_damage(5)
# 显示炸弹特效
var effect = BombEffect.new()
effect.position = Vector2(
GameConfig.SCREEN_WIDTH / 2.0,
GameConfig.SCREEN_HEIGHT / 2.0)
root_node.get_node("Main/GameArea/Effects").add_child(effect)
# 屏幕震动
ScreenShake.shake(GameConfig.SCREEN_SHAKE_ON_BOMB, 0.5)6.7 本章小结
| 组件 | 说明 |
|---|---|
| 道具基类 | 6种道具类型,飘落+摆动+闪烁 |
| 掉落管理 | 加权随机,按概率掉落 |
| 护盾系统 | 抵挡一次伤害,优先消耗 |
| 炸弹效果 | 清屏+全屏闪光+屏幕震动 |
关键设计点:
- 道具使用加权随机,Power和Score最容易掉
- 道具会在消失前2秒闪烁提醒
- 护盾不叠加,有就只有一个
- 炸弹效果全屏闪光+清弹+伤害所有敌人
下一章我们将实现Boss战系统。
