HOME / 第 5 部分 · 3D 游戏开发 / 第 55 课
MODULE 5 · 3D 游戏开发
第 55 课 · 3D 射击:射线与命中检测
🎯 本课学习目标
- 用射线实现命中检测
- 实现准星与开火逻辑
- 敌人受击反馈
FPS 射击本质是「从枪口发射一条射线,看它撞到谁」。本课实现完整射击。
📖 详细教学步骤
射击原理概念
大多数 FPS 的「子弹」不是真实物体,而是瞬间射线检测:
- 从相机中心(准星)向正前方发射一条射线
- 射线撞到第一个物体 = 命中
- 调用被命中物体的受击函数
好处:无需子弹物理,零延迟、永不脱靶(指哪打哪)。
开火检测代码
在玩家脚本里加射击:
GDSCRIPTextends CharacterBody3D
@onready var cam: Camera3D = $CameraRig/Camera3D
func _unhandled_input(event):
if event.is_action_pressed("fire"):
shoot()
func shoot():
var center = get_viewport().get_visible_rect().size / 2
var from = cam.project_ray_origin(center)
var to = from + cam.project_ray_normal(center) * 1000.0
var query = PhysicsRayQueryParameters3D.create(from, to)
var hit = get_world_3d().direct_space_state.intersect_ray(query)
if hit:
print("命中:", hit.collider.name, " 位置:", hit.position)
# 通知受击对象
var body = hit.collider
if body.has_method("take_damage"):
body.take_damage(25)
子弹延迟(弹道时间)进阶
想要「子弹飞一会儿」的打击感,用延时:
GDSCRIPTfunc shoot():
var from = cam.project_ray_origin(...)
var to = from + cam.project_ray_normal(...) * 1000.0
# 延迟 0.05 秒后检测(模拟飞行)
await get_tree().create_timer(0.05).timeout
var query = PhysicsRayQueryParameters3D.create(from, to)
var hit = get_world_3d().direct_space_state.intersect_ray(query)
...
准星实操
- 在 HUD(CanvasLayer)加一个 Label/TextureRect 做准星
- 居中放置(锚点:居中)
- 文字用一个「+」或圆点
- UI 永远在 3D 之上,天然当准星
敌人受击反馈代码
敌人脚本:
GDSCRIPTextends CharacterBody3D
@export var hp: int = 100
func take_damage(amount: int):
hp -= amount
# 受击闪白
$MeshInstance3D.material_override.albedo_color = Color(1, 0.5, 0.5)
await get_tree().create_timer(0.1).timeout
$MeshInstance3D.material_override.albedo_color = Color.WHITE
if hp <= 0:
die()
func die():
print("敌人被击杀")
queue_free()
命中特效(火花/弹孔)进阶
命中位置加粒子特效:
GDSCRIPTfunc spawn_hit_effect(pos: Vector3):
var fx = hit_fx_scene.instantiate() # 火花粒子场景
get_tree().current_scene.add_child(fx)
fx.global_position = pos
fx.emitting = true
⚠️
射线穿不过某些物体?
检查物体是否有 CollisionShape3D;查询可加 collision_mask 过滤层。
⚠️
命中自己?
射线 query 加 exclude 排除玩家自身 RID。
🧪 动手试一试
🏋️ 课后练习
做一个会掉血的靶子(HP 100),用射线射击:命中减 25,血空后消失并打印击杀。
玩家 shoot() 射线检测 → 靶子 take_damage()。
💡
射线命中反馈(火花/声音/受击闪白)是射击手感的灵魂。
💡
PhysicsRayQueryParameters3D.create + intersect_ray 是 3D 交互的万能钥匙。
🎓 本课小结
射击 = 从相机中心发射射线 → intersect_ray 命中检测 → 调用受击函数;配合准星、命中特效与受击反馈形成完整射击体验。