Blenderで「箱が開くアニメーション」を作る方法
完成イメージ
- 赤いギフトボックスが閉じた状態からスタート
- 蓋が後ろに開く(フレーム30〜80)
- 4つの側面が花びらのように外側に倒れる(フレーム85〜130)
- フタの上に金色のリボン・蝶結び付き
必要なもの
- Blender 4.0 以上(このチュートリアルは 5.1.1 で動作確認)
- 追加アドオン・プラグインなし
- Python の知識なくてもOK(スクリプトをコピペするだけ)
仕組みの説明
Blenderには Pythonスクリプト をそのまま実行できる機能があります。
このスクリプトが以下をすべて自動でやってくれます:
| やること | 内容 |
|---|---|
| 箱を作る | 底・4側面・蓋を別々のオブジェクトとして生成 |
| ヒンジを設定 | 各パネルの根元に「回転の軸(Empty)」を配置 |
| 親子関係 | 各パネルをヒンジに従わせる |
| アニメーション | キーフレームで回転を記録 |
| ライト・カメラ | 自動で配置 |
手順
ステップ1:Blenderを開く
Blenderを起動し、デフォルトの立方体がある状態でOKです。
ステップ2:Scriptingタブを開く
画面上部のタブから 「Scripting」 をクリックします。
Layout | Modeling | Sculpting | ... | Scripting ← これをクリック
ステップ3:新しいスクリプトを作成
Scriptingワークスペース内の 「New」 ボタンをクリックして、空のテキストエリアを作ります。
ステップ4:スクリプトを貼り付ける
下のスクリプトを全部コピーして、テキストエリアに貼り付けます。
import bpy
import math
# ===== シーンのリセット =====
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
scene = bpy.context.scene
scene.render.fps = 24
scene.frame_start = 1
scene.frame_end = 160
# ===== サイズ定数 =====
S = 2.0 # 箱の一辺
T = 0.09 # 板の厚さ
H = S / 2 # 半辺
# ===== マテリアル作成 =====
def make_mat(name, color, metallic=0.0, roughness=0.5):
mat = bpy.data.materials.new(name=name)
mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
bsdf.inputs["Base Color"].default_value = (*color, 1.0)
bsdf.inputs["Metallic"].default_value = metallic
bsdf.inputs["Roughness"].default_value = roughness
return mat
mat_box = make_mat("BoxRed", (0.60, 0.04, 0.04), roughness=0.55)
mat_lid = make_mat("LidRed", (0.70, 0.07, 0.04), roughness=0.40)
mat_ribbon = make_mat("Ribbon", (1.00, 0.82, 0.00), metallic=0.2, roughness=0.35)
mat_bow = make_mat("Bow", (1.00, 0.75, 0.00), metallic=0.3, roughness=0.3)
mat_inside = make_mat("Inside", (0.92, 0.87, 0.72))
mat_floor = make_mat("Floor", (0.85, 0.85, 0.85), roughness=0.8)
# ===== ヘルパー関数 =====
def add_box(name, loc, size, mat):
bpy.ops.mesh.primitive_cube_add(size=1, location=loc)
obj = bpy.context.active_object
obj.name = name
obj.scale = size
bpy.ops.object.transform_apply(scale=True)
obj.data.materials.append(mat)
return obj
def add_empty(name, loc):
bpy.ops.object.empty_add(type='PLAIN_AXES', location=loc)
e = bpy.context.active_object
e.name = name
return e
def parent_keep_transform(child, parent):
child.parent = parent
child.matrix_parent_inverse = parent.matrix_world.inverted()
def keyframe_rot(obj, frame, rx=0.0, ry=0.0, rz=0.0):
obj.rotation_euler = (math.radians(rx), math.radians(ry), math.radians(rz))
obj.keyframe_insert(data_path="rotation_euler", frame=frame)
def smooth_all(obj):
if not (obj.animation_data and obj.animation_data.action):
return
action = obj.animation_data.action
fcurves = []
try:
fcurves = list(action.fcurves)
except AttributeError:
try:
slot = obj.animation_data.action_slot
for layer in action.layers:
for strip in layer.strips:
try:
cb = strip.channelbag(slot)
fcurves.extend(cb.fcurves)
except Exception:
pass
except Exception:
pass
for fc in fcurves:
for kp in fc.keyframe_points:
kp.interpolation = 'BEZIER'
kp.easing = 'EASE_IN_OUT'
# ===== 床 =====
floor = add_box("Floor", (0, 0, -T - 0.5), (12, 12, 1.0), mat_floor)
# ===== 箱の底板 =====
bottom = add_box("Bottom", (0, 0, -T / 2), (S, S, T), mat_box)
inside_btm = add_box("InsideBottom", (0, 0, T / 2 + 0.001), (S - T * 2, S - T * 2, 0.01), mat_inside)
# ===== 4つの側面 =====
front_hinge = add_empty("FrontHinge", (0, H, 0))
front = add_box("Front", (0, H + T / 2, H), (S, T, S), mat_box)
parent_keep_transform(front, front_hinge)
back_hinge = add_empty("BackHinge", (0, -H, 0))
back = add_box("Back", (0, -(H + T / 2), H), (S, T, S), mat_box)
parent_keep_transform(back, back_hinge)
left_hinge = add_empty("LeftHinge", (-H, 0, 0))
left = add_box("Left", (-(H + T / 2), 0, H), (T, S + T * 2, S), mat_box)
parent_keep_transform(left, left_hinge)
right_hinge = add_empty("RightHinge", ( H, 0, 0))
right = add_box("Right", ( H + T / 2, 0, H), (T, S + T * 2, S), mat_box)
parent_keep_transform(right, right_hinge)
# ===== 蓋 =====
lid_hinge = add_empty("LidHinge", (0, -H, S))
lid = add_box("Lid", (0, 0, S + T / 2), (S + T * 2, S + T * 2, T), mat_lid)
parent_keep_transform(lid, lid_hinge)
# ===== 蓋のリボン =====
rib_lid_v = add_box("RibbonLidV", (0, 0, S + T + 0.002), (T * 1.8, S + T * 2, T * 1.2), mat_ribbon)
rib_lid_h = add_box("RibbonLidH", (0, 0, S + T + 0.002), (S + T * 2, T * 1.8, T * 1.2), mat_ribbon)
parent_keep_transform(rib_lid_v, lid_hinge)
parent_keep_transform(rib_lid_h, lid_hinge)
# ===== 蝶結び =====
bow_l = add_box("BowLeft", (-T * 2.5, 0, S + T * 1.5), (T * 2.2, T * 0.8, T * 2.2), mat_bow)
bow_r = add_box("BowRight", ( T * 2.5, 0, S + T * 1.5), (T * 2.2, T * 0.8, T * 2.2), mat_bow)
bow_c = add_box("BowCenter", (0, 0, S + T * 1.5), (T * 1.2, T * 0.8, T * 1.2), mat_bow)
for b in [bow_l, bow_r, bow_c]:
parent_keep_transform(b, lid_hinge)
# ===== キーフレームアニメーション =====
side_hinges = [front_hinge, back_hinge, left_hinge, right_hinge]
all_hinges = side_hinges + [lid_hinge]
for hinge in all_hinges:
keyframe_rot(hinge, 1)
keyframe_rot(hinge, 30)
# 蓋が開く(フレーム30〜80)
keyframe_rot(lid_hinge, 30, rx=0)
keyframe_rot(lid_hinge, 80, rx=112)
# 4面が展開(フレーム85〜130)
for hinge in side_hinges:
keyframe_rot(hinge, 85)
keyframe_rot(front_hinge, 130, rx=-90)
keyframe_rot(back_hinge, 130, rx= 90)
keyframe_rot(left_hinge, 130, ry=-90)
keyframe_rot(right_hinge, 130, ry= 90)
for hinge in all_hinges:
keyframe_rot(hinge, 160, **{
'rx': -90 if hinge == front_hinge else
90 if hinge == back_hinge else
112 if hinge == lid_hinge else 0,
'ry': -90 if hinge == left_hinge else
90 if hinge == right_hinge else 0,
})
for hinge in all_hinges:
smooth_all(hinge)
# ===== ライティング =====
for obj in list(bpy.data.objects):
if obj.type == 'LIGHT':
bpy.data.objects.remove(obj, do_unlink=True)
bpy.ops.object.light_add(type='AREA', location=(4, -3, 6))
main_light = bpy.context.active_object
main_light.data.energy = 300
main_light.data.size = 4
main_light.rotation_euler = (math.radians(40), 0, math.radians(40))
bpy.ops.object.light_add(type='AREA', location=(-4, 3, 3))
fill_light = bpy.context.active_object
fill_light.data.energy = 100
fill_light.data.size = 5
fill_light.data.color = (0.8, 0.88, 1.0)
bpy.ops.object.light_add(type='SPOT', location=(0, 5, 5))
back_light = bpy.context.active_object
back_light.data.energy = 150
back_light.data.spot_size = math.radians(60)
back_light.rotation_euler = (math.radians(-45), 0, 0)
# ===== カメラ =====
for obj in list(bpy.data.objects):
if obj.type == 'CAMERA':
bpy.data.objects.remove(obj, do_unlink=True)
bpy.ops.object.camera_add(location=(5.5, -5.5, 4.5))
cam = bpy.context.active_object
cam.name = "MainCamera"
cam.rotation_euler = (math.radians(52), 0, math.radians(45))
cam.data.lens = 50
scene.camera = cam
scene.frame_set(1)
print("完成!スペースキーで再生できます。")
ステップ5:スクリプトを実行する
右上の ▶ ボタン(または Alt + P)をクリック。
数秒で箱・ライト・カメラが自動生成されます。
ステップ6:アニメーションを再生する
- 上の 「Layout」 タブをクリック
- 3Dビューポート内をクリック
- スペースキー で再生スタート!
カスタマイズガイド
スクリプトの上部にある定数を変えるだけで見た目が変わります。
S = 2.0 # 箱の大きさ(大きくするには 3.0 など)
T = 0.09 # 板の厚さ(薄くするには 0.05 など)
色を変える
mat_box = make_mat("BoxRed", (0.60, 0.04, 0.04))
# R G B
# 青い箱にしたい場合:(0.04, 0.10, 0.60)
# 白い箱にしたい場合:(0.95, 0.95, 0.95)
アニメーションのタイミングを変える
| 変数 | 意味 |
|---|---|
frame_end = 160 |
アニメーション全体の長さ |
keyframe_rot(lid_hinge, 30〜80) |
蓋が開くタイミング |
keyframe_rot(hinge, 85〜130) |
側面が開くタイミング |
仕組みの解説(もう少し詳しく)
なぜ「Empty(空オブジェクト)」を使うのか?
Blenderでオブジェクトを回転させると、オブジェクト自身の中心点を軸に回ります。
箱の側面を「底辺を軸に」倒すには、底辺の位置に回転の軸が必要です。
そこで Empty(見えない軸) を底辺に置き、そこにパネルを子として紐付けます。
Emptyを回転させると、パネルがその端を軸に倒れていく仕組みです。
[Empty(ヒンジ)] ← 底辺の位置に配置
└── [パネル] ← Emptyの子として紐付け
キーフレームの仕組み
keyframe_rot(lid_hinge, 30, rx=0) # フレーム30:閉じている(0度)
keyframe_rot(lid_hinge, 80, rx=112) # フレーム80:開いている(112度)
Blenderがフレーム30〜80の間を自動的に補間して、なめらかな動きを作ります。
よくある問題
| 問題 | 原因・対処 |
|---|---|
| エラーが出て止まる | Blenderのバージョンが古い可能性。4.0以上を使ってください |
| 色が表示されない | ビューポートが「ソリッドモード」になっています。「マテリアルプレビュー」に切り替えてください(ヘッダーの球アイコン) |
| アニメーションが動かない | 3Dビューポート内をクリックしてからスペースキーを押してください |
まとめ
| ステップ | 操作 |
|---|---|
| 1 | Blenderを開く |
| 2 | Scriptingタブを開く |
| 3 | Newでスクリプトを作成 |
| 4 | スクリプトをコピペ |
| 5 | ▶ボタンで実行 |
| 6 | Layoutタブ → スペースキーで再生 |
Pythonの知識がなくても、コピペするだけで本格的なアニメーションが作れます。
色や大きさを変えて、オリジナルのギフトボックスを作ってみてください!