HOME / 第 7 部分 · 完整游戏项目实战 / 第 86 课
MODULE 7 · 完整游戏项目实战

第 86 课 · 弹幕射击游戏(一):飞机与子弹

⏱ 预计时长 50 分钟📊 难度 进阶📚 模块 第 7 部分 · 完整游戏项目实战

🎯 本课学习目标

经典飞机弹幕游戏:控制战机躲弹幕、打敌机。本课做飞机与子弹系统。

📖 详细教学步骤

游戏设计设计

  • 玩家:战机,上下左右移动 + 自动/空格射击
  • 敌人:会发射弹幕的敌机
  • 目标:躲避弹幕,击落敌机得分
  • 难度:弹幕越来越密

玩家战机搭建

  1. 新建 player_plane.tscn,根 Area2D(用 Area 便于检测子弹)
  2. Sprite2D 战机图 + CollisionShape2D
  3. 脚本:WASD/方向键移动,限制在屏幕内
GDSCRIPTextends Area2D

@export var speed: float = 300.0
@export var fire_rate: float = 0.2
var fire_timer: float = 0.0

func _physics_process(delta):
    var dir = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    global_position += dir * speed * delta
    # 限制在屏幕内
    var vp = get_viewport_rect().size
    global_position.x = clampf(global_position.x, 20, vp.x - 20)
    global_position.y = clampf(global_position.y, 20, vp.y - 20)
    # 自动射击
    fire_timer -= delta
    if fire_timer <= 0:
        fire_timer = fire_rate
        shoot()

func shoot():
    var bullet = player_bullet.instantiate()
    bullet.global_position = global_position + Vector2(0, -20)
    get_parent().add_child(bullet)

玩家子弹代码

子弹向上飞,出屏幕删除:

GDSCRIPTextends Area2D

@export var speed: float = 600.0

func _physics_process(delta):
    global_position.y -= speed * delta
    # 出屏幕删除
    if global_position.y < -20:
        queue_free()

敌机与弹幕搭建

敌机发射弹幕(扇形):

GDSCRIPTextends Area2D

@export var fire_interval: float = 1.5
var timer: float = 0.0

func _physics_process(delta):
    # 敌机缓慢下移
    global_position.y += 60 * delta
    timer -= delta
    if timer <= 0 and is_visible_in_tree():
        timer = fire_interval
        fire_barrage()

func fire_barrage():
    # 发射 5 发扇形弹幕
    for i in range(5):
        var b = enemy_bullet.instantiate()
        b.global_position = global_position
        # 角度:-40° 到 +40°
        var angle = deg_to_rad(-40 + i * 20)
        b.dir = Vector2(0, 1).rotated(angle)
        get_parent().add_child(b)

碰撞与爆炸代码

子弹命中敌机、敌弹命中玩家:

GDSCRIPT# player_plane.gd
func _ready():
    body_entered.connect(_on_body_entered)

func _on_body_entered(body):
    if body.is_in_group("enemy_bullet"):
        body.queue_free()
        explode()   # 玩家爆炸
        get_tree().reload_current_scene()

# enemy.gd
func _ready():
    body_entered.connect(_on_body_entered)

func _on_body_entered(body):
    if body.is_in_group("player_bullet"):
        body.queue_free()
        take_damage(1)

生成敌机代码

主场景定时生成敌机:

GDSCRIPTconst ENEMY = preload("res://enemy_plane.tscn")

func _ready():
    spawn_timer.timeout.connect(_on_spawn)
    spawn_timer.start()

func _on_spawn():
    var e = ENEMY.instantiate()
    e.position = Vector2(randf_range(30, 610), -30)
    add_child(e)
⚠️
子弹打不到敌机?

子弹和敌机都必须是 Area2D 且互相检测;用组(player_bullet/enemy_bullet)判断身份。

⚠️
飞机出屏幕?

用 clampf 限制在屏幕范围内(第 1 步代码里有)。

🧪 动手试一试

🏋️ 课后练习

做出「扇形弹幕」:敌机每 1.5 秒发射 5 发不同角度子弹,玩家子弹能击落敌机。

Vector2(0,1).rotated(angle) 生成方向 + 每帧按 dir 移动。

💡

弹幕方向用向量旋转(rotated)最灵活。

💡

用组区分敌我子弹,碰撞判断清晰。

🎓 本课小结

弹幕游戏基础:玩家移动+射击 + 敌机弹幕(rotated 方向)+ Area 碰撞检测 + 爆炸重开;组区分敌我。