blender-animation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Blender Animation

Blender 动画

Animate properties over time. Most animation is just keyframes — the trick is choosing the right interpolation and easing for the motion's character.
随时间为属性制作动画。大多数动画仅依赖关键帧——关键在于为运动特性选择合适的插值与缓动方式。

Decision tree

决策树

What kind of motion?
├── Object movement (translate / rotate / scale)
│   → Keyframe `location` / `rotation_euler` / `scale`
│   → Recipe 1, 2
├── Mechanical / constant speed (gears, conveyor belts, scrolling)
│   → Linear interpolation
│   → Recipe 3
├── Natural / organic (most things)
│   → Bezier interpolation with auto handles
│   → Recipe 1
├── Cartoon / stylized (overshoot, bounce, anticipation)
│   → Bounce / Elastic / Back easing
│   → Recipe 4
├── Facial / morph / blendshape
│   → Shape Keys, animate `value` property
│   → Recipe 5
├── Mechanical relations (one property = function of another)
│   → Drivers (Python expression)
│   → Recipe 6
└── Reusable / layered animations
    → NLA actions
    → Recipe 7
What kind of motion?
├── Object movement (translate / rotate / scale)
│   → Keyframe `location` / `rotation_euler` / `scale`
│   → Recipe 1, 2
├── Mechanical / constant speed (gears, conveyor belts, scrolling)
│   → Linear interpolation
│   → Recipe 3
├── Natural / organic (most things)
│   → Bezier interpolation with auto handles
│   → Recipe 1
├── Cartoon / stylized (overshoot, bounce, anticipation)
│   → Bounce / Elastic / Back easing
│   → Recipe 4
├── Facial / morph / blendshape
│   → Shape Keys, animate `value` property
│   → Recipe 5
├── Mechanical relations (one property = function of another)
│   → Drivers (Python expression)
│   → Recipe 6
└── Reusable / layered animations
    → NLA actions
    → Recipe 7

Recipes

操作示例

Recipe 1 — Animate object position (Bezier, natural)

示例1 — 为物体位置制作动画(Bezier缓动,自然运动)

python
import bpy

obj = bpy.data.objects['GEO-target']

scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = 60
scene.render.fps = 24
python
import bpy

obj = bpy.data.objects['GEO-target']

scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = 60
scene.render.fps = 24

Keyframe 1: at frame 1, at origin

关键帧1:第1帧,位于原点

scene.frame_set(1) obj.location = (0, 0, 0) obj.keyframe_insert('location', frame=1)
scene.frame_set(1) obj.location = (0, 0, 0) obj.keyframe_insert('location', frame=1)

Keyframe 2: at frame 60, moved to (5, 0, 0)

关键帧2:第60帧,移动至(5, 0, 0)

scene.frame_set(60) obj.location = (5, 0, 0) obj.keyframe_insert('location', frame=60)
print(f"animated:{obj.name} 1->60")

Default interpolation = Bezier (smooth in/out). To make it linear, see Recipe 3.
scene.frame_set(60) obj.location = (5, 0, 0) obj.keyframe_insert('location', frame=60)
print(f"animated:{obj.name} 1->60")

默认插值方式为Bezier(平滑入/出)。如需线性插值,请查看示例3。

Recipe 2 — Animate rotation (a 360° spin)

示例2 — 为旋转制作动画(360°旋转)

python
import bpy, math

obj = bpy.data.objects['GEO-target']
scene = bpy.context.scene
python
import bpy, math

obj = bpy.data.objects['GEO-target']
scene = bpy.context.scene

Use rotation_euler with a single axis.

使用单轴rotation_euler(欧拉角旋转)。

WARNING: animating past 180° on Euler can flip; use multiple keyframes or quaternions for full rotations.

注意:欧拉角旋转超过180°可能会出现翻转;完整旋转请使用多关键帧或四元数。

scene.frame_set(1) obj.rotation_euler = (0, 0, 0) obj.keyframe_insert('rotation_euler', frame=1)
scene.frame_set(120) obj.rotation_euler = (0, 0, math.radians(180)) # half-turn obj.keyframe_insert('rotation_euler', frame=120)
scene.frame_set(240) obj.rotation_euler = (0, 0, math.radians(360)) # full turn obj.keyframe_insert('rotation_euler', frame=240)
print(f"rotated:{obj.name} 360 over 240 frames")

For perfectly constant spin, set keyframes to Linear interpolation (Recipe 3).
scene.frame_set(1) obj.rotation_euler = (0, 0, 0) obj.keyframe_insert('rotation_euler', frame=1)
scene.frame_set(120) obj.rotation_euler = (0, 0, math.radians(180)) # 半圈 obj.keyframe_insert('rotation_euler', frame=120)
scene.frame_set(240) obj.rotation_euler = (0, 0, math.radians(360)) # 整圈 obj.keyframe_insert('rotation_euler', frame=240)
print(f"rotated:{obj.name} 360 over 240 frames")

如需完全匀速旋转,请将关键帧设置为线性插值(见示例3)。

Recipe 3 — Set keyframes to Linear interpolation

示例3 — 将关键帧设置为线性插值

Blender 5.x changed the Action API. Legacy
action.fcurves
was removed in favour of layered Actions:
action.layers[].strips[].channelbags[].fcurves
. Use this compat helper.
python
import bpy

def get_fcurves_compat(action):
    """Return all fcurves on an Action — works on both legacy (≤4.x) and layered (5.x+) actions."""
    if hasattr(action, 'fcurves'):
        return list(action.fcurves)
    fcurves = []
    for layer in action.layers:
        for strip in layer.strips:
            if hasattr(strip, 'channelbags'):
                for cb in strip.channelbags:
                    fcurves.extend(cb.fcurves)
    return fcurves

obj = bpy.data.objects['GEO-target']
if obj.animation_data and obj.animation_data.action:
    for fc in get_fcurves_compat(obj.animation_data.action):
        for kp in fc.keyframe_points:
            kp.interpolation = 'LINEAR'
print(f"interp:linear {obj.name}")
Other options:
'BEZIER'
(default),
'CONSTANT'
(step),
'SINE'
,
'QUAD'
,
'CUBIC'
,
'QUART'
,
'QUINT'
,
'BOUNCE'
,
'ELASTIC'
,
'BACK'
.
Blender 5.x 改动了Action API。旧版的
action.fcurves
已被移除,取而代之的是分层Actions:
action.layers[].strips[].channelbags[].fcurves
。请使用以下兼容工具函数。
python
import bpy

def get_fcurves_compat(action):
    """返回Action中的所有fcurves——兼容旧版(≤4.x)和分层(5.x+)Actions。"""
    if hasattr(action, 'fcurves'):
        return list(action.fcurves)
    fcurves = []
    for layer in action.layers:
        for strip in layer.strips:
            if hasattr(strip, 'channelbags'):
                for cb in strip.channelbags:
                    fcurves.extend(cb.fcurves)
    return fcurves

obj = bpy.data.objects['GEO-target']
if obj.animation_data and obj.animation_data.action:
    for fc in get_fcurves_compat(obj.animation_data.action):
        for kp in fc.keyframe_points:
            kp.interpolation = 'LINEAR'
print(f"interp:linear {obj.name}")
其他选项:
'BEZIER'
(默认)、
'CONSTANT'
(步进)、
'SINE'
'QUAD'
'CUBIC'
'QUART'
'QUINT'
'BOUNCE'
'ELASTIC'
'BACK'

Recipe 4 — Bouncy / cartoon easing on a specific keyframe

示例4 — 为特定关键帧添加弹跳/卡通风格缓动

python
import bpy
python
import bpy

(Re-use get_fcurves_compat from Recipe 3.)

(复用示例3中的get_fcurves_compat函数。)

def get_fcurves_compat(action): if hasattr(action, 'fcurves'): return list(action.fcurves) fcurves = [] for layer in action.layers: for strip in layer.strips: if hasattr(strip, 'channelbags'): for cb in strip.channelbags: fcurves.extend(cb.fcurves) return fcurves
obj = bpy.data.objects['GEO-target'] target_fc = None for fc in get_fcurves_compat(obj.animation_data.action): if fc.data_path == 'location' and fc.array_index == 2: # Z axis target_fc = fc break
if target_fc and len(target_fc.keyframe_points) >= 2: last_kp = target_fc.keyframe_points[-1] last_kp.interpolation = 'BOUNCE' last_kp.easing = 'EASE_OUT' # 'AUTO', 'EASE_IN', 'EASE_OUT', 'EASE_IN_OUT' print('animated:bouncy_landing')
undefined
def get_fcurves_compat(action): if hasattr(action, 'fcurves'): return list(action.fcurves) fcurves = [] for layer in action.layers: for strip in layer.strips: if hasattr(strip, 'channelbags'): for cb in strip.channelbags: fcurves.extend(cb.fcurves) return fcurves
obj = bpy.data.objects['GEO-target'] target_fc = None for fc in get_fcurves_compat(obj.animation_data.action): if fc.data_path == 'location' and fc.array_index == 2: # Z轴 target_fc = fc break
if target_fc and len(target_fc.keyframe_points) >= 2: last_kp = target_fc.keyframe_points[-1] last_kp.interpolation = 'BOUNCE' last_kp.easing = 'EASE_OUT' # 可选值:'AUTO'、'EASE_IN'、'EASE_OUT'、'EASE_IN_OUT' print('animated:bouncy_landing')
undefined

Recipe 5 — Shape keys (morph / blendshape / viseme)

示例5 — Shape keys(形状键,又称变形/混合形状/口型键)

python
import bpy

mesh_obj = bpy.data.objects['GEO-character_face']
python
import bpy

mesh_obj = bpy.data.objects['GEO-character_face']

Add basis (the rest pose)

添加基础形状(默认姿态)

if mesh_obj.data.shape_keys is None: basis = mesh_obj.shape_key_add(name='Basis')
if mesh_obj.data.shape_keys is None: basis = mesh_obj.shape_key_add(name='Basis')

Add a morph target

添加变形目标

smile = mesh_obj.shape_key_add(name='Smile') smile.value = 0.0
smile = mesh_obj.shape_key_add(name='Smile') smile.value = 0.0

⚠ At this point, switch to Edit Mode interactively and modify the mesh while 'Smile' is selected.

⚠ 此时需切换至编辑模式,选中'Smile'形状后交互式修改网格。

Or set vertex coordinates programmatically (advanced).

或通过编程方式设置顶点坐标(进阶操作)。

Animate

制作动画

scene = bpy.context.scene scene.frame_set(1) smile.value = 0.0 smile.keyframe_insert('value', frame=1)
scene.frame_set(24) smile.value = 1.0 smile.keyframe_insert('value', frame=24)
print('animated:shape_key_smile')

**For lip sync**: standard 15-viseme set (Oculus / ARKit) — name shape keys `viseme_aa`, `viseme_E`, `viseme_O`, etc. Animate each viseme's value across the audio timeline.
scene = bpy.context.scene scene.frame_set(1) smile.value = 0.0 smile.keyframe_insert('value', frame=1)
scene.frame_set(24) smile.value = 1.0 smile.keyframe_insert('value', frame=24)
print('animated:shape_key_smile')

**唇同步场景**:使用标准15种口型键集合(Oculus/ARKit)——将形状键命名为`viseme_aa`、`viseme_E`、`viseme_O`等,并在音频时间轴上为每个口型键的value属性制作动画。

Recipe 6 — Driver (one property as expression of another)

示例6 — Driver(驱动,一个属性作为另一个属性的表达式)

python
import bpy
python
import bpy

Example: child object's X = parent's X × 2

示例:子物体的X坐标 = 父物体的X坐标 × 2

target = bpy.data.objects['GEO-follower'] source = bpy.data.objects['GEO-leader']
fc = target.driver_add('location', 0) # X axis driver = fc.driver driver.type = 'SCRIPTED'
target = bpy.data.objects['GEO-follower'] source = bpy.data.objects['GEO-leader']
fc = target.driver_add('location', 0) # X轴 driver = fc.driver driver.type = 'SCRIPTED'

Add variable referencing source's X position

添加引用源物体X坐标的变量

var = driver.variables.new() var.name = 'src_x' var.type = 'TRANSFORMS' var.targets[0].id = source var.targets[0].transform_type = 'LOC_X' var.targets[0].transform_space = 'WORLD_SPACE'
driver.expression = 'src_x * 2' print(f"driver:{target.name}.x = {source.name}.x * 2")
undefined
var = driver.variables.new() var.name = 'src_x' var.type = 'TRANSFORMS' var.targets[0].id = source var.targets[0].transform_type = 'LOC_X' var.targets[0].transform_space = 'WORLD_SPACE'
driver.expression = 'src_x * 2' print(f"driver:{target.name}.x = {source.name}.x * 2")
undefined

Recipe 7 — Push current animation to NLA strip (for reuse)

示例7 — 将当前动画推送至NLA片段(用于复用)

python
import bpy

obj = bpy.data.objects['GEO-character']

if obj.animation_data and obj.animation_data.action:
    track = obj.animation_data.nla_tracks.new()
    track.name = 'NLA-Walk'
    strip = track.strips.new('Walk', start=1, action=obj.animation_data.action)
    obj.animation_data.action = None    # clear timeline; NLA owns the animation
    print(f"nla:pushed_walk_strip")
After this, the animation is reusable — duplicate the strip, scale time, blend with other tracks.
python
import bpy

obj = bpy.data.objects['GEO-character']

if obj.animation_data and obj.animation_data.action:
    track = obj.animation_data.nla_tracks.new()
    track.name = 'NLA-Walk'
    strip = track.strips.new('Walk', start=1, action=obj.animation_data.action)
    obj.animation_data.action = None    # 清空时间轴;动画由NLA管理
    print(f"nla:pushed_walk_strip")
完成此操作后,动画即可复用——可复制片段、缩放时间、与其他轨道混合。

Recipe 8 — Subtle idle animation (loopable rotation)

示例8 — 细微的待机动画(可循环旋转)

python
import bpy, math

obj = bpy.data.objects['GEO-target']
python
import bpy, math

obj = bpy.data.objects['GEO-target']

Tiny sway around Y, 4-second loop

绕Y轴轻微摆动,4秒循环

scene = bpy.context.scene scene.frame_start = 1 scene.frame_end = 96 # 4s at 24fps
scene.frame_set(1) obj.rotation_euler = (0, math.radians(-2), 0) obj.keyframe_insert('rotation_euler', frame=1)
scene.frame_set(48) obj.rotation_euler = (0, math.radians(2), 0) obj.keyframe_insert('rotation_euler', frame=48)
scene.frame_set(96) obj.rotation_euler = (0, math.radians(-2), 0) obj.keyframe_insert('rotation_euler', frame=96)
scene = bpy.context.scene scene.frame_start = 1 scene.frame_end = 96 # 24fps下为4秒
scene.frame_set(1) obj.rotation_euler = (0, math.radians(-2), 0) obj.keyframe_insert('rotation_euler', frame=1)
scene.frame_set(48) obj.rotation_euler = (0, math.radians(2), 0) obj.keyframe_insert('rotation_euler', frame=48)
scene.frame_set(96) obj.rotation_euler = (0, math.radians(-2), 0) obj.keyframe_insert('rotation_euler', frame=96)

Set extrapolation mode to cycle (loop)

设置外插模式为循环(cycle)

def get_fcurves_compat(action): if hasattr(action, 'fcurves'): return list(action.fcurves) fcurves = [] for layer in action.layers: for strip in layer.strips: if hasattr(strip, 'channelbags'): for cb in strip.channelbags: fcurves.extend(cb.fcurves) return fcurves
if obj.animation_data and obj.animation_data.action: for fc in get_fcurves_compat(obj.animation_data.action): fc.modifiers.new('CYCLES') print('animated:idle_loop')
undefined
def get_fcurves_compat(action): if hasattr(action, 'fcurves'): return list(action.fcurves) fcurves = [] for layer in action.layers: for strip in layer.strips: if hasattr(strip, 'channelbags'): for cb in strip.channelbags: fcurves.extend(cb.fcurves) return fcurves
if obj.animation_data and obj.animation_data.action: for fc in get_fcurves_compat(obj.animation_data.action): fc.modifiers.new('CYCLES') print('animated:idle_loop')
undefined

Pro animation principles

专业动画原则

  • Slow in, slow out — Bezier handles do this automatically; matches real-world physics
  • Anticipation — small reverse motion before the main action (jump prep)
  • Follow-through — secondary parts continue after main motion stops (cape, hair)
  • Squash & stretch — exaggeration with shape keys or scale animation
  • Arcs — natural motion follows curves, not straight lines
  • 慢入慢出——Bezier手柄可自动实现此效果,符合现实物理规律
  • 预备动作——主动作前的小幅反向运动(如跳跃前的下蹲)
  • 跟随动作——主动作停止后,次要部件继续运动(如斗篷、头发)
  • 挤压与拉伸——通过形状键或缩放动画实现夸张效果
  • 弧线运动——自然运动遵循曲线而非直线

Common pitfalls

常见问题

SymptomFix
Robotic motionDefault Bezier is correct; Linear is wrong for organic things
Rotation flips at 180°Use multiple keyframes (90, 180, 270, 360) or quaternions
Shape key changes lostMust exit Edit Mode to commit shape key state
Animation only on one axis
keyframe_insert('location', index=0)
for X only; index=1 Y, =2 Z
Driver doesn't updateRefresh viewport; check Preferences → Editing → Allow Driver Python Expression
Render fps mismatchSet
scene.render.fps
BEFORE animating to avoid timing drift
症状解决方法
机械感运动默认Bezier插值是正确选择;线性插值不适用于有机物体
旋转在180°处翻转使用多关键帧(90°、180°、270°、360°)或四元数
形状键修改丢失必须退出编辑模式才能保存形状键状态
仅单轴有动画仅为X轴添加关键帧使用
keyframe_insert('location', index=0)
;index=1对应Y轴,index=2对应Z轴
驱动不更新刷新视口;检查偏好设置→编辑→允许驱动Python表达式
渲染帧率不匹配制作动画前先设置
scene.render.fps
,避免时间偏移

When to load
references/overview.md

何时加载
references/overview.md

Load when:
  • Animation curves need fine tuning (handle types, bezier shape control)
  • NLA layering / blending needed
  • Drivers with complex expressions (multi-variable, conditional)
  • ARKit 52-blendshape full face animation
  • Walk-cycle / run-cycle / attack patterns
The reference covers: full F-curve interpolation/easing matrix, NLA workflow, drivers cookbook, shape-key best practices, animation principles.
在以下场景加载:
  • 需要微调动画曲线(手柄类型、Bezier形状控制)
  • 需要NLA分层/混合
  • 使用带有复杂表达式的驱动(多变量、条件表达式)
  • ARKit 52种混合形状的全脸动画
  • 行走循环/奔跑循环/攻击动作模式
该参考文档涵盖:完整的F曲线插值/缓动矩阵、NLA工作流程、驱动使用指南、形状键最佳实践、动画原则。