news 2026/9/28 12:58:32

用 Godot 数学库实现角色移动、朝向判断与相机跟随:从 2D 到 3D 的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用 Godot 数学库实现角色移动、朝向判断与相机跟随:从 2D 到 3D 的完整指南

用 Godot 数学库实现角色移动、朝向判断与相机跟随:从 2D 到 3D 的完整指南

【免费下载链接】godot-docsGodot Engine official documentation项目地址: https://gitcode.com/GitHub_Trending/go/godot-docs

如果你刚接触 GDScript,第一次写"让角色动起来"的需求时,可能只需要一行position += velocity * delta,就能跑起来。但接下来每个需求都会撞上数学:斜向移动为什么比单方向快?角色什么时候算"面向"了敌人?相机怎么平滑地追着目标转?这篇 Godot 数学库完整指南从这些真实任务出发,带你认识 Godot 4.x 里最常用的一组数学工具:Vector2/Vector3 向量、Transform2D/Transform3D 变换矩阵、点积/叉积,以及 3D 里的四元数。不需要线性代数基础,每节都给你一段能直接跑的最小示例。

2D 任务一:让角色平滑移动,斜着走不加速

要解决的问题:读方向键或摇杆,让角色以恒定速度移动;但按下两个键时(比如"上+右"),角色不能变成 √2 倍速。

extends Node2D @export var speed := 200.0 func _physics_process(delta: float) -> void: # 从输入构建 0~1 范围的方向向量(Vector2 向量运算入门) var input_vec := Vector2( Input.get_action_strength("move_right") - Input.get_action_strength("move_left"), Input.get_action_strength("move_down") - Input.get_action_strength("move_up") ) if input_vec.length_squared() > 0.0: # normalized() 把向量压到长度 1,斜向和直走速度一致 var direction := input_vec.normalized() position += direction * speed * delta

用到的 Godot 数学库核心就三件事:

  • Vector2(x, y):用两个数表示方向+大小。注意 Godot 的 2D 屏幕坐标里Y 轴向下,Vector2(0, 1)是朝下,不是朝上。
  • .length()/.length_squared():向量的模长。只判断"远不远"时用length_squared(),可以省掉一次开方。
  • .normalized():得到单位向量,即"只保留方向、长度归一"。

官方参考:classes/class_vector2.rst 里列出了全部方法,dot()、angle_to()、lerp()都在同一页。

顺手解决"转身":平滑转向目标

角色移动了,贴图却还朝着旧方向。2D 里旋转就是一个角度,用lerp_angle做平滑转向:

# 在上一段代码后面加: if input_vec.length_squared() > 0.0: var direction := input_vec.normalized() position += direction * speed * delta # 目标朝向:Vector2 的 angle_to 给出到目标的角度 var target_angle := direction.angle() # lerp_angle 会自动走角度里短的那条路,不会出现原地打转 rotation = lerp_angle(rotation, target_angle, 12.0 * delta)

angle_to、lerp_angle这两个方法专门为"角色转向"这类需求准备,写法比手写角度差值简洁得多。

任务二:判断角色是否面向目标,点积就够了

要解决的问题:敌人 AI 想"看到"玩家才发起攻击。你手里有角色当前位置、朝向,和玩家位置。

直觉做法是算出"到玩家的向量"和"朝向"的夹角再比较,但那要先acos。Godot 数学库里有个更便宜的判断——点积(dot product):

func can_see_player(player_pos: Vector2) -> bool: var to_player := (player_pos - global_position).normalized() # 点积范围 -1 ~ 1:1 表示同向,0 垂直,-1 反向 return to_player.dot(direction) > cos(PI * 0.35) # 约 63° 的视锥内

a.dot(b)在两个单位向量之间等于夹角的余弦,所以"点积大于某个阈值"就等价于"夹角小于某个角度",全程不用acos。

需要法线或左右侧时,用叉积

3D 里更常见的是:已知碰撞的两个边,求表面的法线方向。这时用叉积(cross product):

# vertex1/2/3 是三角面三个顶点(Vector3) var edge_a := vertex2 - vertex1 var edge_b := vertex3 - vertex1 var normal := edge_a.cross(edge_b).normalized()

叉积的结果垂直于两个输入向量所在平面,是"算表面法线"的标准套路。2D 版本a.cross(b)返回的是标量,正负值可以直接告诉你 b 在 a 的顺时针还是逆时针一侧,常用于判定"目标在我左手边还是右手边"。

更多细节见 classes/class_vector3.rst。

任务三:搞懂 GDScript 变换矩阵,子节点跟着父节点走

要解决的问题:把"旋转 30° + 放大 1.5 倍 + 移到 (100, 50)"这类描述组合起来,并让子节点的transform自动跟着父节点传递。

Godot 4.x 中,2D 节点用Transform2D,3D 节点用Transform3D,两者都由两部分组成:

# 组合一个 2D 变换:先旋转,再缩放,最后给位置 var t := Transform2D() t = t.rotated(PI / 6) # 旋转 30° t = t.scaled(Vector2.ONE * 1.5) # 整体放大 1.5 倍 t.origin = Vector2(100, 50) # 放到指定位置 $Sprite2D.transform = t

3D 里Transform3D的basis就是三根轴向量(X/Y/Z),单位矩阵下它们正好对应世界三轴:

局部变换如何变成全局变换

节点树里每个节点只存局部变换(相对于父节点)。引擎沿节点树把各级transform连乘起来,才得到全局变换。你自己组合时同理,用*号即可:

# 把局部变换换算到世界空间:父级变换 * 局部变换 var local := Transform2D() local.origin = Vector2(50, 0) # 局部:向右 50 var world := get_parent().transform * local # 乘上父节点变换 # 反方向:把世界坐标换算回某节点局部坐标 var world_pos := Vector3(10, 0, -5) var local_pos := get_node("Enemy").transform.affine_inverse() * world_pos

3D 的写法完全一致,只是换成Transform3D。affine_inverse()给你一个"反向变换",是坐标换算最常用的技巧。这两个类型的完整方法表在 classes/class_transform2d.rst 和 classes/class_transform3d.rst。

任务四:进入 3D,用四元数做旋转和相机跟随

要解决的问题:相机要始终看着玩家、旋转平滑;如果直接用欧拉角(三个独立角度)连续转,会撞上"万向节锁",出现相机抽搐。

Godot 4.x 内部就是用四元数(Quaternion)存旋转的,API 也把它暴露给你:

# 从欧拉角创建四元数:绕 Y 轴转 90° var q := Quaternion.from_euler(Vector3(0, PI / 2, 0)) # slerp:两个旋转之间的平滑插值(球面线性插值) var from := Quaternion.IDENTITY var to := q var mid := from.slerp(to, 0.5) # 走到一半的旋转

四元数和Basis可以互转,接进Transform3D就能用:

# 四元数 → 3D 变换 var transform := Transform3D(Basis(Quaternion.from_euler(Vector3(0, deg_to_rad(45), 0))), Vector3.ZERO) # 变换 → 四元数(注意:basis 需正交归一) var q_back := transform.basis.get_rotation_quaternion()

最小可用的 3D 相机跟随

extends Camera3D @export var target: Node3D @export var offset := Vector3(0, 2, -5) @export var follow_speed := 5.0 func _process(delta: float) -> void: # lerp 让相机平滑地趋近期望位置(向量插值) global_position = global_position.lerp( target.global_position + offset, follow_speed * delta) # look_at 内部用四元数算朝向,避免欧拉角抖动 look_at(target.global_position, Vector3.UP)

lerp负责位置过渡,look_at负责朝向,两者配合就是最常见的第三人称跟随相机。Quaternion的完整方法(包括spherical_cubic_interpolate之类)见 classes/class_quaternion.rst。

收尾:性能小贴士、实战路线和速查表

写热点代码时的三个习惯

  1. 比较距离用平方:只判断"是否进入范围"时,用position.distance_squared_to(target) < range * range,省掉开方。
  2. 循环里别反复求逆:transform.affine_inverse()结果不变的话,先算一次存变量。
  3. 批量转换用 Basis:一组同向旋转下的向量,用同一个basis连乘比逐个transform * v更直观也更省。

建议的动手路线

  1. 用"任务一"的脚本做一个摇杆/键盘控制的 2D 角色,加lerp_angle转身。
  2. 加一个敌人:用点积视锥检测玩家,用Vector2.angle_to平滑转向,触发攻击。
  3. 搭一个 3D 场景:给相机套上"任务四"的跟随脚本,再把offset改成可用键盘调整,体会四元数视角。

做完这三个小项目,上表里 80% 的方法你都实际敲过一遍了。

核心方法速查表

需求2D 方法3D 方法说明
单位化取方向v.normalized()v.normalized()移动方向、视锥检测
距离比较a.distance_squared_to(b)同左免开方的范围判断
角度判断(点积)a.dot(b)同左面向检测、投影
侧向/法线(叉积)a.cross(b)(标量)a.cross(b)(Vector3)转向方向、表面法线
位置插值a.lerp(b, t)同左相机跟随、缓动
旋转插值lerp_angle(a, b, t)q.slerp(r, t)平滑转向
组合/传递Transform2D * tTransform3D * t局部 ↔ 全局换算
反变换t.affine_inverse()同左世界坐标转局部坐标

参考文档

以上示例均基于 Godot 4.x。官方文档中对应章节:数学教程位于 tutorials/math/(含向量与矩阵专题),各类完整 API 在 classes/ 目录下。建议对照vector_math.rst、matrices_and_transforms.rst、vectors_advanced.rst三篇原文,配合本文的任务路线练习一遍。

【免费下载链接】godot-docsGodot Engine official documentation项目地址: https://gitcode.com/GitHub_Trending/go/godot-docs

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 12:57:35

Python ARIMA时间序列销量预测:从平稳性检验到滚动预测实战

简介&#xff1a;这份资源是面向Python数据分析初学者、毕业设计及课程设计学生的ARIMA时间序列销量预测完整方案&#xff0c;帮助解决从数据平稳化处理、模型定阶到预测检验的全流程建模问题。包内共16个文件&#xff0c;以py脚本、png图表、zbak备份、xls与xlsx数据表及md说明…

作者头像 李华
网站建设 2026/9/28 12:56:53

Oracle NULL防坑指南:从三值逻辑到NVL、聚合排序与数据同步

NULL这个家伙&#xff0c;我愿称之为Oracle里最防不胜防的坑。前两天一个朋友发来一条SQL&#xff0c;说月度报表统计人数莫名其妙少了一大截&#xff0c;我扫了一眼就发现问题了&#xff1a;WHERE条件里写了NOT IN&#xff0c;子查询结果里带了一个NULL&#xff0c;于是整张表…

作者头像 李华
网站建设 2026/9/28 12:54:41

UltraScale+ GTH DRP接口实战:时序、地址映射与Verilog控制器实现

1. 为什么GTH的DRP接口值得单独拎出来讲搞过UltraScale系列FPGA高速收发器的同行都清楚&#xff0c;GTH这玩意儿功能强归强&#xff0c;但配置项多到让人头皮发麻。平时我们用IP核向导&#xff08;Wizard&#xff09;点点鼠标就能生成一个能跑的收发器&#xff0c;大部分场景确…

作者头像 李华
网站建设 2026/9/28 12:53:50

Android 13多路录音实战:AudioRecord 6通道PCM采集与拆分

1. 多路录音到底难在哪&#xff1a;从AudioRecord的底层逻辑说起Android录音这件事&#xff0c;看起来简单——调个AudioRecord&#xff0c;传个AudioFormat&#xff0c;startRecording()就完事了。但一旦你需要的不是"一路混音后的立体声"&#xff0c;而是"同时…

作者头像 李华
网站建设 2026/9/28 12:53:11

卡口过车数据实时流量预测:LSTM融合模型落地与90%准确率实战

简介&#xff1a;这份资源面向智能交通、深度学习方向的学习者与开发者&#xff0c;围绕卡口实时过车数据展开交通流量预测实践&#xff0c;核心采用LSTM循环神经网络并引入融合预测思路&#xff0c;宣称准确率可达90%以上。内容覆盖时间序列预测的完整链路&#xff1a;卡口数据…

作者头像 李华