5. 道具系统
2026/4/14大约 6 分钟
5. 超级玛丽奥——道具系统
简介
道具系统就是让玛丽奥"升级"的系统。吃了蘑菇变大,吃了火焰花可以发射火球,吃了星星短暂无敌,吃了金币加分。
道具就像游戏里的"奖励"——你做了好事(顶砖块、踩敌人),游戏就给你奖励让你变得更强。
道具类型
| 道具 | 外观 | 获取方式 | 效果 |
|---|---|---|---|
| 蘑菇 | 红色蘑菇 | 顶问号砖块 | 小玛丽奥变大 |
| 火焰花 | 橙色花 | 顶问号砖块 | 大玛丽奥变火焰玛丽奥 |
| 星星 | 黄色星星 | 顶隐藏砖块 | 无敌10秒 |
| 1UP蘑菇 | 绿色蘑菇 | 顶隐藏砖块 | 额外1条命 |
| 金币 | 黄色圆圈 | 顶砖块/空中收集 | 加100分 |
道具生成逻辑
问号砖块被顶开后,弹出的道具取决于玛丽奥的当前状态:
小玛丽奥顶问号砖块 → 弹出蘑菇
大玛丽奥顶问号砖块 → 弹出火焰花这个逻辑就像自动售货机——你投不同的硬币,出来不同的饮料。玛丽奥的"状态"就是硬币。
道具基类
// Item.cs - 道具基类
using Godot;
public partial class Item : RigidBody2D
{
// ========== 属性 ==========
[Export] public float Gravity { get; set; } = 980.0f;
[Export] public float BounceForce { get; set; } = -200.0f;
// 道具类型
public string ItemType { get; protected set; } = "unknown";
// 是否已经激活(被弹出或被释放)
protected bool _isActivated = false;
// ========== 生命周期 ==========
public override void _Ready()
{
// 禁用物理,等激活后再启用
Sleeping = true;
// 连接碰撞信号
BodyEntered += OnBodyEntered;
}
// 激活道具(从砖块弹出时调用)
public void Activate()
{
if (_isActivated) return;
_isActivated = true;
Sleeping = false;
ApplyCentralImpulse(new Vector2(0, BounceForce));
// 启用碰撞检测
SetCollisionMaskValue(2, true); // 与玩家碰撞
}
public override void _PhysicsProcess(double delta)
{
if (!_isActivated) return;
// 道具也有重力
// RigidBody2D 自动处理重力
}
// 与玩家碰撞
protected virtual void OnBodyEntered(Node2D body)
{
if (body.IsInGroup("player"))
{
Collect(body as Player);
}
}
// 收集道具(子类重写具体效果)
protected virtual void Collect(Player player)
{
GD.Print($"玩家获得了 {ItemType}");
QueueFree();
}
}# item.gd - 道具基类
extends RigidBody2D
# ========== 属性 ==========
@export var gravity: float = 980.0
@export var bounce_force: float = -200.0
# 道具类型
var item_type: String = "unknown"
# 是否已经激活
var is_activated: bool = false
# ========== 生命周期 ==========
func _ready() -> void:
# 禁用物理,等激活后再启用
sleeping = true
# 连接碰撞信号
body_entered.connect(on_body_entered)
## 激活道具(从砖块弹出时调用)
func activate() -> void:
if is_activated:
return
is_activated = true
sleeping = false
apply_central_impulse(Vector2(0, bounce_force))
# 启用碰撞检测
set_collision_mask_value(2, true)
func _physics_process(delta: float) -> void:
if not is_activated:
return
## 与玩家碰撞
func on_body_entered(body: Node2D) -> void:
if body.is_in_group("player"):
collect(body)
## 收集道具(子类重写具体效果)
func collect(player: Node) -> void:
print("玩家获得了 %s" % item_type)
queue_free()蘑菇(变大)
// MushroomItem.cs - 蘑菇道具
public partial class MushroomItem : Item
{
public MushroomItem()
{
ItemType = "mushroom";
}
protected override void Collect(Player player)
{
if (player.IsSmall())
{
// 小玛丽奥 → 大玛丽奥
player.GrowUp();
GameManager.Instance.AddScore(1000);
}
else
{
// 已经是大的,加1000分
GameManager.Instance.AddScore(1000);
}
// 播放音效
AudioManager.Instance.PlaySfx("powerup");
base.Collect(player);
}
}# mushroom_item.gd - 蘑菇道具
extends "res://scripts/items/item.gd"
func _init() -> void:
item_type = "mushroom"
func collect(player: Node) -> void:
if player.is_small():
# 小玛丽奥 → 大玛丽奥
player.grow_up()
GameManager.add_score(1000)
else:
# 已经是大的,加1000分
GameManager.add_score(1000)
# 播放音效
AudioManager.play_sfx("powerup")
super.collect(player)火焰花(发射火球)
// FireFlowerItem.cs - 火焰花道具
public partial class FireFlowerItem : Item
{
public FireFlowerItem()
{
ItemType = "fire_flower";
}
public override void Activate()
{
base.Activate();
// 火焰花不会移动,停在被弹出的位置
Sleeping = true;
Freeze = true;
}
protected override void Collect(Player player)
{
if (player.IsBig())
{
// 大玛丽奥 → 火焰玛丽奥
player.BecomeFireMario();
GameManager.Instance.AddScore(1000);
}
else if (player.IsSmall())
{
// 小玛丽奥先变大
player.GrowUp();
GameManager.Instance.AddScore(1000);
}
AudioManager.Instance.PlaySfx("powerup");
base.Collect(player);
}
}# fire_flower_item.gd - 火焰花道具
extends "res://scripts/items/item.gd"
func _init() -> void:
item_type = "fire_flower"
func activate() -> void:
super.activate()
# 火焰花不会移动
sleeping = true
freeze = true
func collect(player: Node) -> void:
if player.is_big():
# 大玛丽奥 → 火焰玛丽奥
player.become_fire_mario()
GameManager.add_score(1000)
elif player.is_small():
# 小玛丽奥先变大
player.grow_up()
GameManager.add_score(1000)
AudioManager.play_sfx("powerup")
super.collect(player)星星(无敌)
// StarItem.cs - 星星道具
public partial class StarItem : Item
{
public StarItem()
{
ItemType = "star";
}
protected override void Collect(Player player)
{
// 让玩家进入无敌状态
player.ActivateStarPower(GameManager.STAR_DURATION);
GameManager.Instance.AddScore(1000);
AudioManager.Instance.PlaySfx("star_powerup");
base.Collect(player);
}
}# star_item.gd - 星星道具
extends "res://scripts/items/item.gd"
func _init() -> void:
item_type = "star"
func collect(player: Node) -> void:
# 让玩家进入无敌状态
player.activate_star_power(GameManager.STAR_DURATION)
GameManager.add_score(1000)
AudioManager.play_sfx("star_powerup")
super.collect(player)金币
// Coin.cs - 金币
public partial class Coin : Area2D
{
// 金币类型:收集型(空中/地面)和弹出型(从砖块弹出)
[Export] public bool IsPopCoin { get; set; } = false;
private AnimatedSprite2D _sprite;
public override void _Ready()
{
_sprite = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
_sprite.Play("spin");
BodyEntered += OnBodyEntered;
}
private void OnBodyEntered(Node2D body)
{
if (body.IsInGroup("player"))
{
Collect();
}
}
private void Collect()
{
// 加金币
GameManager.Instance.AddCoin();
// 播放音效
AudioManager.Instance.PlaySfx("coin");
if (IsPopCoin)
{
// 弹出型金币:先向上飞再消失
var tween = CreateTween();
tween.TweenProperty(this, "position:y", Position.Y - 48, 0.3f);
tween.Parallel().TweenProperty(_sprite, "modulate:a", 0.0f, 0.3f);
tween.TweenCallback(Callable.From(QueueFree));
}
else
{
// 普通金币:直接消失
QueueFree();
}
}
}# coin.gd - 金币
extends Area2D
## 金币类型
@export var is_pop_coin: bool = false
@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D
func _ready() -> void:
sprite.play("spin")
body_entered.connect(on_body_entered)
func on_body_entered(body: Node2D) -> void:
if body.is_in_group("player"):
collect()
func collect() -> void:
# 加金币
GameManager.add_coin()
# 播放音效
AudioManager.play_sfx("coin")
if is_pop_coin:
# 弹出型金币:先向上飞再消失
var tween = create_tween()
tween.tween_property(self, "position:y", position.y - 48, 0.3)
tween.parallel().tween_property(sprite, "modulate:a", 0.0, 0.3)
tween.tween_callback(queue_free)
else:
# 普通金币:直接消失
queue_free()玩家状态管理
在玩家脚本中管理状态变化:
// 在 Player.cs 中添加的状态管理代码
public enum PlayerState
{
Small, // 小玛丽奥
Big, // 大玛丽奥
Fire // 火焰玛丽奥
}
// 当前状态
private PlayerState _playerState = PlayerState.Small;
// 无敌状态
private bool _isInvincible = false;
private bool _isStarPower = false;
// 状态查询
public bool IsSmall() => _playerState == PlayerState.Small;
public bool IsBig() => _playerState == PlayerState.Big || _playerState == PlayerState.Fire;
// 变大(小→大)
public void GrowUp()
{
_playerState = PlayerState.Big;
UpdateAppearance();
UpdateCollisionSize();
PlayGrowAnimation();
}
// 变成火焰玛丽奥(大→火焰)
public void BecomeFireMario()
{
_playerState = PlayerState.Fire;
UpdateAppearance();
PlayPowerUpAnimation();
}
// 缩小(大/火焰→小)
public void ShrinkToSmall()
{
_playerState = PlayerState.Small;
UpdateAppearance();
UpdateCollisionSize();
StartInvincibility(1.5f); // 缩小后短暂无敌
PlayShrinkAnimation();
}
// 激活星星无敌
public void ActivateStarPower(float duration)
{
_isStarPower = true;
StartInvincibility(duration);
var timer = GetTree().CreateTimer(duration);
timer.Timeout += () => { _isStarPower = false; };
}
// 更新外观
private void UpdateAppearance()
{
switch (_playerState)
{
case PlayerState.Small:
_animatedSprite.SpriteFrames = GD.Load<SpriteFrames>("res://assets/sprites/player/small.tres");
break;
case PlayerState.Big:
_animatedSprite.SpriteFrames = GD.Load<SpriteFrames>("res://assets/sprites/player/big.tres");
break;
case PlayerState.Fire:
_animatedSprite.SpriteFrames = GD.Load<SpriteFrames>("res://assets/sprites/player/fire.tres");
break;
}
}
// 更新碰撞大小
private void UpdateCollisionSize()
{
var rect = _collisionShape.Shape as RectangleShape2D;
if (rect == null) return;
if (_playerState == PlayerState.Small)
{
rect.Size = new Vector2(16, 16);
}
else
{
rect.Size = new Vector2(16, 32);
}
}
// 短暂无敌
private void StartInvincibility(float duration)
{
_isInvincible = true;
// 闪烁效果
var tween = CreateTween();
tween.SetLoops();
tween.TweenProperty(_animatedSprite, "modulate:a", 0.3f, 0.1f);
tween.TweenProperty(_animatedSprite, "modulate:a", 1.0f, 0.1f);
var timer = GetTree().CreateTimer(duration);
timer.Timeout += () =>
{
_isInvincible = false;
_animatedSprite.Modulate = new Color(1, 1, 1, 1.0f);
tween.Kill();
};
}
// 发射火球(火焰玛丽奥专属)
public void ShootFireball()
{
if (_playerState != PlayerState.Fire) return;
var scene = GD.Load<PackedScene>("res://scenes/items/fireball.tscn");
var fireball = scene.Instantiate<RigidBody2D>();
fireball.Position = Position + new Vector2(_animatedSprite.FlipH ? -10 : 10, 0);
fireball.ApplyCentralImpulse(new Vector2(_animatedSprite.FlipH ? -300 : 300, -100));
var container = GetNode("/root/Level/Items");
container.AddChild(fireball);
AudioManager.Instance.PlaySfx("fireball");
}# 在 player.gd 中添加的状态管理代码
enum PlayerState:
SMALL # 小玛丽奥
BIG # 大玛丽奥
FIRE # 火焰玛丽奥
# 当前状态
var player_state: int = PlayerState.SMALL
# 无敌状态
var is_invincible: bool = false
var is_star_power: bool = false
# 状态查询
func is_small() -> bool:
return player_state == PlayerState.SMALL
func is_big() -> bool:
return player_state == PlayerState.BIG or player_state == PlayerState.FIRE
## 变大(小→大)
func grow_up() -> void:
player_state = PlayerState.BIG
update_appearance()
update_collision_size()
play_grow_animation()
## 变成火焰玛丽奥(大→火焰)
func become_fire_mario() -> void:
player_state = PlayerState.FIRE
update_appearance()
play_power_up_animation()
## 缩小(大/火焰→小)
func shrink_to_small() -> void:
player_state = PlayerState.SMALL
update_appearance()
update_collision_size()
start_invincibility(1.5)
play_shrink_animation()
## 激活星星无敌
func activate_star_power(duration: float) -> void:
is_star_power = true
start_invincibility(duration)
var timer = get_tree().create_timer(duration)
timer.timeout.connect(func(): is_star_power = false)
## 更新外观
func update_appearance() -> void:
match player_state:
PlayerState.SMALL:
animated_sprite.sprite_frames = load("res://assets/sprites/player/small.tres")
PlayerState.BIG:
animated_sprite.sprite_frames = load("res://assets/sprites/player/big.tres")
PlayerState.FIRE:
animated_sprite.sprite_frames = load("res://assets/sprites/player/fire.tres")
## 更新碰撞大小
func update_collision_size() -> void:
var rect = collision_shape.shape as RectangleShape2D
if rect == null:
return
if player_state == PlayerState.SMALL:
rect.size = Vector2(16, 16)
else:
rect.size = Vector2(16, 32)
## 短暂无敌
func start_invincibility(duration: float) -> void:
is_invincible = true
# 闪烁效果
var tween = create_tween()
tween.set_loops()
tween.tween_property(animated_sprite, "modulate:a", 0.3, 0.1)
tween.tween_property(animated_sprite, "modulate:a", 1.0, 0.1)
var timer = get_tree().create_timer(duration)
timer.timeout.connect(func():
is_invincible = false
animated_sprite.modulate = Color(1, 1, 1, 1.0)
tween.kill()
)
## 发射火球
func shoot_fireball() -> void:
if player_state != PlayerState.FIRE:
return
var scene = load("res://scenes/items/fireball.tscn")
var fireball = scene.instantiate()
fireball.position = position + Vector2(-10 if animated_sprite.flip_h else 10, 0)
fireball.apply_central_impulse(Vector2(-300 if animated_sprite.flip_h else 300, -100))
var container = get_node("/root/Level/Items")
container.add_child(fireball)
AudioManager.play_sfx("fireball")下一章预告
道具系统完成了!蘑菇、火焰花、星星、金币都有了。下一章我们将实现关卡设计——用 TileMap 搭建关卡,创建问号砖块、管道和旗杆终点。
