godot-game-one 是复古弹幕射击游戏,战斗节奏快,道具和升级系统决定了从开局到通关的成长曲线。这篇讲三件事:道具怎么掉落、升级怎么生效、不同敌人的掉落策略差异。

道具类型与效果

游戏有 6 种道具:

类型 效果 是否累积
spread 增加子弹数量,扩展射击角度 是,上限 10 级
speed 提高射速,缩短射击间隔 是,上限 15 级
power 提高子弹伤害 是,上限 50 级,溢出转回血
heal 恢复 1 点生命值 否,即时效果
bomb 清屏,逐个击杀所有敌人 否,即时效果
core 技能和激光冷却时间 ×0.7 否,即时效果

power 道具有一个特殊设计:当 bullet_power_level 达到上限 50 后,再拾取会自动转为回血。这避免了满级后道具浪费的问题。

核心逻辑在 scripts/game_state.gd:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func collect_powerup(type: String, peer_id: int = -1) -> void:
var s := _state(pid)
match type:
"spread":
if int(s.shoot_level) < 10:
s.shoot_level = min(int(s.shoot_level) + 1, 10)
"speed":
if int(s.shoot_speed_level) < 15:
s.shoot_speed_level = min(int(s.shoot_speed_level) + 1, 15)
"power":
if int(s.bullet_power_level) >= 15:
heal(1, pid) # 溢出转回血
else:
s.bullet_power_level = min(int(s.bullet_power_level) + 1, 50)
"heal": heal(1, pid)
"bomb": pass
"core":
s.skill_cooldown = max(float(s.skill_cooldown) * 0.7, 0.0)
s.laser_cooldown = max(float(s.laser_cooldown) * 0.7, 0.0)

升级对射击模式的质变

升级不是简单的数值累加,shoot_level 在不同阶段会改变射击模式。实现在 scripts/player/combat_controller.gd:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func _shoot() -> void:
var level: int = GameState.get_shoot_level(_player.peer_id)

if level >= 12: # 阶段 3:扇形散射 + 微扰动角度
var spread_angles: Array[float] = [-0.3, -0.15, 0.0, 0.15, 0.3]
for i in range(bullet_count):
var a = base_angle + spread_angles[i % 5] * (0.5 + (level - 12) * 0.1)
elif level >= 8: # 阶段 2:散射弹道
for i in bullet_count:
var a = base_angle + positions[i] * 0.008
elif level >= 4: # 阶段 1:多弹并行
for i in bullet_count:
var offset = perp * positions[i]
else:
# 初始阶段,小范围并行

三个阶段的视觉差异:

阶段 shoot_level 表现
初始 1-3 2-3 发子弹并行,同方向
散射 4-11 多发子弹,角度逐渐展开
扇形 12+ 5 路扇形弹幕,带微扰动

子弹数量的计算公式:

1
2
func get_bullet_count() -> int:
return min(2 + shoot_level / 3 + extra_bullet, 12)

射速的计算公式:

1
2
func get_shoot_cooldown() -> float:
return max(0.27 - shoot_speed_level * 0.018, 0.08)

满级射速(level 15)冷却仅 0.08 秒,配合 12 发扇形弹幕,输出密度极高。

追踪导弹:隐藏的升级链

shoot_level 还附带一个隐藏效果——追踪导弹。每 5 级解锁一个导弹舱,最多 5 个:

1
2
3
4
5
6
7
8
func _get_missile_tier() -> int:
return mini(level / 5, 5)

func _get_missile_damage() -> int:
return (bullet_damage + 2) * pow(1.5, tier - 1) # 指数增长

func _get_missile_interval() -> float:
return 1.5 / tier # tier 越高越快

导弹伤害是指数增长,tier 5 的导弹伤害是基础的 5 倍多。这个设计让 spread 道具的价值更高——它不仅增加子弹数量,还间接触发导弹升级。

护盾系统

护盾的 HP 与 bullet_power_level 联动:

1
2
3
4
5
func get_shield_max_hp(peer_id: int = -1) -> int:
var bp := get_bullet_power_level(peer_id)
var mhp := get_max_health(peer_id)
var bonus := get_shield_bonus(peer_id)
return maxi(1, int((10 + bp * 2 + int(mhp * 0.2)) * 0.5) + bonus)

护盾被击破后,8 秒自动充能恢复满层:

1
2
3
4
5
6
7
8
9
10
const SHIELD_RECHARGE_INTERVAL: float = 8.0

func _update_shield_recharge(delta: float) -> void:
if GameState.get_shield_layers(peer_id) > 0:
_shield_recharge_timer = SHIELD_RECHARGE_INTERVAL
_shield_recharge_timer -= delta
if _shield_recharge_timer <= 0.0:
_shield_recharge_timer = SHIELD_RECHARGE_INTERVAL
var max_shield := _compute_shield_max_hp()
GameState.set_shield_layers(peer_id, max_shield)

这个设计让 power 道具有双重价值:直接增加子弹伤害,同时提升护盾上限。

道具掉落策略

不同敌人的掉落策略差异明显。

普通敌人

20% 基础掉率,随敌人类型递增:

1
2
3
# scripts/main.gd
enemy.drop_chance = 0.20 + enemy_type * 0.12
# type 0: 20%, type 1: 32%, type 2: 44%...

掉落时从 5 种道具中等概率随机选择:

1
2
3
4
5
func _try_spawn_powerup() -> void:
if randf() < drop_chance:
var types: Array[String] = ["spread", "speed", "power", "heal", "bomb"]
var picked_type: String = types[randi() % types.size()]
pu.setup(picked_type)

精英怪

精英怪死亡时弧形喷射 12 个道具,沿扇形弧度排列:

1
2
3
4
5
6
7
func _spawn_rewards() -> void:
var types: Array[String] = ["heal", "bomb", "spread", "speed", "heal", "power",
"heal", "power", "speed", "spread", "heal", "bomb"]
var step: float = PI / max(float(types.size() - 1), 1.0)
var angle: float = -PI * 0.85 + step * float(i)
var offset := Vector2(cos(angle), sin(angle)) * randf_range(26.0, 58.0)
pu.global_position = global_position + offset

注意 heal 出现了 4 次——精英怪的掉落偏向恢复,因为精英战通常伴随大量伤害。

在线模式下,道具会标记 assigned_peer_id,轮流分配给存活玩家。

Boss

Boss 掉落分两层:普通掉落 + 核心掉落。普通掉落是 4 个道具均匀分布在半径 70 的圆环上:

1
2
3
4
func _drop_supply_fragments() -> void:
var types: Array[String] = ["heal", "speed", "power", "heal"]
for i in range(types.size()):
_spawn_powerup_fragment(types[i], float(i) * TAU / float(types.size()), 70.0)

核心掉落额外包含 core 道具,绑定给特定玩家:

1
2
func _spawn_core_fragments() -> void:
var types: Array[String] = ["core", "heal", "speed", "power", "core", "heal"]

道具拾取:磁铁机制

道具不是碰到就拾取,而是有一个磁铁吸引的过程。

玩家每 4 帧检测一次附近的道具:

1
2
3
4
5
6
7
8
func _try_pickup_nearby() -> void:
_pickup_frame_skip += 1
if _pickup_frame_skip % 4 != 0:
return
var powerups := get_tree().get_nodes_in_group("powerups")
for pu in powerups:
if pu.global_position.distance_to(_player.global_position) < _pickup_radius:
pu.start_magnet(self)

磁铁飞行有加速曲线:

1
2
3
4
5
6
7
8
9
func _physics_process(delta: float) -> void:
if _magnet_target and is_instance_valid(_magnet_target):
_magnet_elapsed += delta
var accel_t = clampf(_magnet_elapsed / 0.35, 0.0, 1.0)
_magnet_speed = lerpf(320.0, 720.0, accel_t) # 0.35s 内从 320 加速到 720
var dir = global_position.direction_to(_magnet_target.global_position)
global_position += dir * _magnet_speed * delta
if global_position.distance_to(_magnet_target.global_position) < 40.0:
collect()

磁铁机制的目的是给玩家一个视觉反馈——看到道具飞向自己,比瞬间消失更有满足感。

升级链条全景

1
2
3
4
5
6
7
8
9
击杀掉落 ─┬── "spread"  → shoot_level ↑  (上限10,触发射击模式质变)
├── "speed" → shoot_speed_level ↑ (上限15,冷却 0.27→0.08s)
├── "power" → bullet_power_level ↑ (上限50,溢出转回血)
├── "heal" → HP +1
├── "bomb" → 即时清屏
└── "core" → 技能/激光冷却 ×0.7

shoot_level 额外解锁:
每 5 级 → 追踪导弹舱 +1(伤害 ×1.5^n,间隔 ÷n)

每个道具都有明确的价值,但组合起来会有质变。spread + speed 让输出密度指数上升,power 同时提升伤害和护盾,core 缩短技能冷却让爆发更频繁。单个道具是加法,组合起来是乘法。