SkeletonIK3D
2026/4/14大约 7 分钟
最后同步日期:2026-04-15 | Godot 官方原文 — SkeletonIK3D
SkeletonIK3D
节点继承关系
定义
SkeletonIK3D 是 Godot 中的 3D 逆向运动学(Inverse Kinematics,简称 IK) 节点。它可以让一段骨骼链(比如从肩膀到手腕)自动计算出中间关节(手肘)应该弯曲多少度,才能让末端骨骼(手掌)到达目标位置。
想象你在伸手去拿桌子上的杯子:你的大脑不需要思考"肩膀转 30 度、手肘弯 45 度、手腕转 15 度",你只需要"想"着杯子在哪,手臂就会自动调整各关节角度去够到它。SkeletonIK3D 做的就是这件事——你告诉它"手掌应该到这个位置",它会自动计算整条手臂的关节角度。
一句话理解:SkeletonIK3D 让骨骼链自动计算中间关节的角度,使末端骨骼到达目标位置。最常见的用途是让角色的脚掌贴合地面、手抓住物体。
使用频率:★★ 偶尔使用——只有在需要 IK 效果(脚踩地面、手抓物体等)时才需要用到。
节点用途
- 让角色的脚掌始终贴合地面(即使地面高低不平)
- 让角色的手自动伸向目标位置(比如抓住扶手、扶着墙壁)
- 让角色的头部朝向某个方向(看向目标)
- 实现程序化的肢体定位(比如攀爬系统、布娃娃过渡到动画)
使用场景
典型场景
- 不平地形上角色行走时,脚掌自动贴合地面高度
- 角色伸手抓住 ledge(边缘攀爬系统)
- 角色推箱子时手部贴合箱子的表面
- 角色死亡后用 IK 让身体自然地"倒向"地面
不适用场景
- 普通的角色动画(走路、跑步等不需要 IK) -> 用 AnimationPlayer 直接播放
- 骨骼上挂载物品 -> 使用 BoneAttachment3D
- 2D 骨骼的 IK -> Godot 4.x 中 2D IK 使用 SkeletonModificationStack2D(Skeleton2D 的属性)
- 简单的朝向效果 -> 直接在代码中修改骨骼旋转即可
常用节点搭配
| 搭配节点 | 用途 | 必需? |
|---|---|---|
| Skeleton3D | 骨骼系统(Skeleton),SkeletonIK3D 必须放在 Skeleton3D 下面 | 必需 |
| Marker3D | IK 目标标记点,表示骨骼末端应该到达的位置 | 必需 |
| AnimationPlayer | 播放基础动画,IK 在动画基础上做调整 | 必需 |
典型节点树结构:
CharacterBody3D
└── Model
├── Skeleton3D
│ ├── (骨骼...)
│ ├── SkeletonIK3D ← 左脚 IK
│ │ (RootBone: "LeftUpLeg", TipBone: "LeftFoot")
│ ├── SkeletonIK3D ← 右脚 IK
│ │ (RootBone: "RightUpLeg", TipBone: "RightFoot")
│ └── SkeletonIK3D ← 左手 IK
│ (RootBone: "LeftArm", TipBone: "LeftHand")
├── Marker3D ← 左脚目标位置
├── Marker3D ← 右脚目标位置
└── Marker3D ← 左手目标位置生效必备素材/资源
| 资源 | 类型 | 说明 |
|---|---|---|
| Skeleton3D | 场景节点 | SkeletonIK3D 必须放在 Skeleton3D 下面 |
| 骨骼名称 | StringName | 需要知道起始骨骼(RootBone)和末端骨骼(TipBone)的名称 |
| IK 目标节点 | Marker3D / Node3D | 表示末端骨骼应该到达的目标位置 |
| 基础动画 | Animation 资源 | IK 在动画基础上做调整,所以仍然需要 AnimationPlayer 播放动画 |
节点属性与信号
IK 配置属性
| 属性 | 类型 | 默认值 | 继承自 | 说明 |
|---|---|---|---|---|
RootBone | StringName | "" | — | 骨骼链的起始骨骼名称(比如大腿骨骼)。IK 会从这个骨骼开始计算 |
TipBone | StringName | "" | — | 骨骼链的末端骨骼名称(比如脚掌骨骼)。这个骨骼会被拉向目标位置 |
TargetNode | NodePath | "" | — | IK 目标节点的路径。末端骨骼会努力朝这个节点的位置移动 |
TargetMinimumDistance | float | 0.01 | — | 末端骨骼到目标的最小距离。低于此距离时 IK 停止计算 |
TargetTransform | Transform3D | — | — | IK 目标变换(如果不使用目标节点) |
Interpolation | float | 1.0 | — | IK 效果的插值强度。0.0 表示完全不用 IK,1.0 表示完全应用 IK。可用于平滑过渡 |
OverrideTipRotation | bool | true | — | 是否让末端骨骼的旋转也朝向目标 |
UseMagnet | bool | false | — | 是否启用磁铁模式(吸引中间关节) |
MagnetPosition | Vector3 | (0,0,0) | — | 磁铁位置,用于吸引中间关节(比如让膝盖朝前弯而不是朝后弯) |
继承自 SkeletonModifier3D 的属性
| 属性 | 类型 | 默认值 | 继承自 | 说明 |
|---|---|---|---|---|
Enabled | bool | true | SkeletonModifier3D | 是否启用此修改器 |
ExecutionOrder | int | 0 | SkeletonModifier3D | 多个修改器之间的执行顺序 |
信号
| 信号 | 参数 | 说明 |
|---|---|---|
| (无特有信号) | — | SkeletonIK3D 没有自己特有的信号,继承自 SkeletonModifier3D 的信号照常可用 |
常用方法
| 方法 | 返回值 | 说明 |
|---|---|---|
Start() | void | 启动 IK 计算。调用后 IK 会每帧自动计算并调整骨骼 |
Stop() | void | 停止 IK 计算。骨骼会恢复到动画控制的状态 |
IsRunning() | bool | IK 是否正在运行 |
GetRootBone() | StringName | 获取起始骨骼名称 |
SetRootBone(boneName) | void | 设置起始骨骼名称 |
GetTipBone() | StringName | 获取末端骨骼名称 |
SetTipBone(boneName) | void | 设置末端骨骼名称 |
GetTargetNode() | NodePath | 获取目标节点路径 |
SetTargetNode(targetNodePath) | void | 设置目标节点路径 |
代码示例
设置骨骼链,启动 IK 让脚掌贴合地面
C
using Godot;
public partial class FootIK : Node3D
{
private SkeletonIK3D _leftFootIK;
private SkeletonIK3D _rightFootIK;
private Marker3D _leftFootTarget;
private Marker3D _rightFootTarget;
private Skeleton3D _skeleton;
public override void _Ready()
{
_skeleton = GetNode<Skeleton3D>("Model/Skeleton3D");
// 获取左右脚的 IK 节点
_leftFootIK = GetNode<SkeletonIK3D>("Model/Skeleton3D/LeftFootIK");
_rightFootIK = GetNode<SkeletonIK3D>("Model/Skeleton3D/RightFootIK");
// 获取目标标记点
_leftFootTarget = GetNode<Marker3D>("LeftFootTarget");
_rightFootTarget = GetNode<Marker3D>("RightFootTarget");
// 设置左脚 IK:从大腿到脚掌
_leftFootIK.RootBone = "LeftUpLeg";
_leftFootIK.TipBone = "LeftFoot";
_leftFootIK.TargetNode = _leftFootTarget.GetPath();
_leftFootIK.Interpolation = 0.5f; // 不要 100% 生效,保留一些动画的自然感
// 设置右脚 IK:从大腿到脚掌
_rightFootIK.RootBone = "RightUpLeg";
_rightFootIK.TipBone = "RightFoot";
_rightFootIK.TargetNode = _rightFootTarget.GetPath();
_rightFootIK.Interpolation = 0.5f;
// 启动 IK
_leftFootIK.Start();
_rightFootIK.Start();
GD.Print("左脚 IK 运行状态: " + _leftFootIK.IsRunning());
GD.Print("右脚 IK 运行状态: " + _rightFootIK.IsRunning());
}
public override void _PhysicsProcess(double delta)
{
// 使用射线检测地面的高度,更新脚掌目标位置
UpdateFootTarget(_leftFootTarget, "LeftFoot");
UpdateFootTarget(_rightFootTarget, "RightFoot");
}
/// <summary>
/// 使用射线检测更新脚掌目标位置
/// </summary>
private void UpdateFootTarget(Marker3D target, string boneName)
{
// 找到对应骨骼的当前位置
int boneIdx = _skeleton.FindBone(boneName);
if (boneIdx == -1) return;
Vector3 boneGlobalPos = _skeleton.GetBoneGlobalPose(boneIdx).Origin;
// 从骨骼位置向下发射射线,检测地面
var spaceState = GetWorld3D().DirectSpaceState;
var query = new PhysicsRayQueryParameters3D
{
From = boneGlobalPos + Vector3.Up * 0.5f,
To = boneGlobalPos + Vector3.Down * 1.0f
};
var result = spaceState.IntersectRay(query);
if (result.Count > 0)
{
// 如果检测到地面,把目标位置设为地面上方一点点
Vector3 groundPos = (Vector3)result["position"];
target.GlobalPosition = groundPos + Vector3.Up * 0.05f;
}
else
{
// 如果没有地面,目标位置跟随骨骼(IK 不生效)
target.GlobalPosition = boneGlobalPos;
}
}
}GDScript
extends Node3D
@onready var skeleton: Skeleton3D = $Model/Skeleton3D
@onready var left_foot_ik: SkeletonIK3D = $Model/Skeleton3D/LeftFootIK
@onready var right_foot_ik: SkeletonIK3D = $Model/Skeleton3D/RightFootIK
@onready var left_foot_target: Marker3D = $LeftFootTarget
@onready var right_foot_target: Marker3D = $RightFootTarget
func _ready():
# 设置左脚 IK:从大腿到脚掌
left_foot_ik.root_bone = "LeftUpLeg"
left_foot_ik.tip_bone = "LeftFoot"
left_foot_ik.target_node = left_foot_target.get_path()
left_foot_ik.interpolation = 0.5 # 不要 100% 生效,保留一些动画的自然感
# 设置右脚 IK:从大腿到脚掌
right_foot_ik.root_bone = "RightUpLeg"
right_foot_ik.tip_bone = "RightFoot"
right_foot_ik.target_node = right_foot_target.get_path()
right_foot_ik.interpolation = 0.5
# 启动 IK
left_foot_ik.start()
right_foot_ik.start()
print("左脚 IK 运行状态: ", left_foot_ik.is_running())
print("右脚 IK 运行状态: ", right_foot_ik.is_running())
func _physics_process(_delta):
# 使用射线检测地面的高度,更新脚掌目标位置
update_foot_target(left_foot_target, "LeftFoot")
update_foot_target(right_foot_target, "RightFoot")
## 使用射线检测更新脚掌目标位置
func update_foot_target(target: Marker3D, bone_name: String):
# 找到对应骨骼的当前位置
var bone_idx = skeleton.find_bone(bone_name)
if bone_idx == -1:
return
var bone_global_pos = skeleton.get_bone_global_pose(bone_idx).origin
# 从骨骼位置向下发射射线,检测地面
var space_state = get_world_3d().direct_space_state
var query = PhysicsRayQueryParameters3D.new()
query.from = bone_global_pos + Vector3.UP * 0.5
query.to = bone_global_pos + Vector3.DOWN * 1.0
var result = space_state.intersect_ray(query)
if result.size() > 0:
# 如果检测到地面,把目标位置设为地面上方一点点
var ground_pos = result.position
target.global_position = ground_pos + Vector3.UP * 0.05
else:
# 如果没有地面,目标位置跟随骨骼(IK 不生效)
target.global_position = bone_global_posIK 的性能开销
IK 计算有一定的性能开销。每个 SkeletonIK3D 每帧都要做一次骨骼链求解计算。如果场景中有大量角色同时使用 IK,可能会影响性能。建议只在玩家角色或重要的 NPC 上使用 IK,远处的小怪可以用简单的动画代替。
Interpolation 参数的妙用
把 Interpolation 设为 0.5~0.8 而不是 1.0,可以让 IK 效果和原始动画混合,看起来更自然。完全 100% 的 IK 有时候会让动作显得僵硬,因为完全覆盖了动画师精心调整的关节角度。
