Blenderの3DアセットをPythonコードで管理する
3D将棋とSlime Mercenariesで、.blendを正本にせずBlender Pythonから盤・キャラクター・GLB/FBXを再生成する設計。
3Dモデルをコード生成する一番の利点は、「速く作れる」ことではありません。
形状・寸法・命名・出力条件をレビュー可能なSource of Truthとして残せることです。
3D将棋とSlime Mercenariesでは、主要なゲーム用3DアセットをBlenderのGUI操作だけで管理せず、Pythonから生成しています。

この記事では、Blender Pythonを採用した理由と、実際に運用して分かった「コード化する範囲」「生成物の扱い」「headless buildの設計」を整理します。
.blendを正本にすると何が困るか
.blendを手で編集して保存する運用自体は普通です。
問題になるのは、ゲームロジックと3Dアセットの間に数値契約がある場合です。
3D将棋では、盤面ロジックが次の座標系を前提にしています。
9 x 9 cells
1 cell = 1.0 unit
Cell_5_5 = world origin
file +1 = +X
rank +1 = -Z
盤の上面も Z = 0 に固定しています。
この状態でGUI上のScaleや位置を手で修正すると、見た目はほぼ同じでもゲーム側との契約だけがずれることがあります。
実際、初期の兵士生成スクリプトには旧仕様の「1マス = 2 units」相当の盤情報が残っており、後から確定した1マス=1unitの仕様と衝突しました。
この経験から、盤と兵士のGenerator自体も分けています。
generate_shogi_board.py
-> board geometry / grid / material / coordinate contract
generate_assets.py
-> miniature soldier geometry / glyph / material
「同じBlender sceneで使うから」という理由だけで、ライフサイクルの違うassetを一つのgeneratorへ入れないようにしています。
寸法をdataclassへ集約する
盤ではGeometryの定数を BoardSpec に集約しています。
@dataclass(frozen=True)
class BoardSpec:
cells: int = 9
cell_size: float = 1.0
outer_margin: float = 0.25
board_thickness: float = 0.55
board_bevel: float = 0.04
board_bevel_segments: int = 2
grid_width: float = 0.025
grid_height: float = 0.008
@property
def play_size(self) -> float:
return self.cells * self.cell_size
@property
def board_size(self) -> float:
return self.play_size + self.outer_margin * 2.0
盤サイズは結果として、
play size = 9.0
board size = 9.5
になります。
数値をGeometry生成処理へ散らさないことで、仕様変更時に「どこか一つだけ古いscaleが残る」可能性を減らせます。
生成開始時にsceneを空にする
headless buildでは「現在開いているscene」を前提にしません。
毎回、生成開始時にsceneを初期化します。
def clear_scene() -> None:
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)
3D将棋の兵士generatorでは、さらに未使用datablockも削除しています。
for collection in (
bpy.data.meshes,
bpy.data.curves,
bpy.data.materials,
bpy.data.cameras,
bpy.data.lights,
):
for datablock in list(collection):
if datablock.users == 0:
collection.remove(datablock)
Blenderでは同じscriptを何度も試していると、Material.001 や古いMesh datablockが残ることがあります。
再生成可能なassetにするなら、同じinputから同じsceneを作る方へ寄せた方が扱いやすくなります。
Preview用objectとexport対象を分ける
生成scriptでは、モデルだけでなくpreview cameraやlightも作れます。
ただし、それらをそのままruntime assetへ混ぜません。
3D将棋の盤では役割ごとにCollectionを分けています。
ShogiBoard_Asset
ShogiBoard_Markers
ShogiBoard_Preview
出力時にはasset collectionだけを対象にします。
Slime MercenariesのGLB exportでも明示的にcamera/lightを除外しています。
bpy.ops.export_scene.gltf(
filepath=str(output_path),
export_format="GLB",
export_yup=True,
export_apply=False,
export_cameras=False,
export_lights=False,
)
「sceneを作るために必要なもの」と「ゲームへ渡すもの」は同じではありません。
Procedural MaterialもSourceに含める
3D将棋の盤では外部wood textureを使わず、Blender nodeだけで木目を作っています。
理由は見た目の豪華さではなく依存管理です。
Texture Coordinate
-> Mapping
-> Noise Texture
-> Color Ramp
-> Principled BSDF
Noise
-> Bump
-> Principled Normal
この構成なら、画像textureのパス切れ、UV差分、ライセンス確認といった別の管理対象が増えません。
初期Playableでは、PBR assetを増やすより「盤と兵の寸法をいつでも再生成できる」ことを優先しました。
もちろん、すべてのゲームでexternal textureを避けるべきという意味ではありません。
コード生成の利点よりtexture authoringの価値が高くなった時点で、依存を追加すればよいというだけです。
生成物を正本にしない
3D将棋では次を生成物として扱います。
.blend
.glb
preview .png
Unity FBX
Source of TruthはPythonです。
Python source
↓
.blend
↓
GLB / FBX / Preview
↓
Game runtime
ただしUnityへ実際にimportするFBXとその .meta は、Playableを再現するためGit管理しています。
ここは少し区別が必要です。
- 形状変更の正本: Blender Python
- runtime repositoryで必要なderivative: FBX + meta
- ローカルで再生成できる中間物: outputのblend / preview
全部をGit管理しない、全部を無視する、の二択ではありません。
Headlessで生成できる状態をAcceptanceにする
3D将棋の盤は次のように生成します。
/Applications/Blender.app/Contents/MacOS/Blender \
--background \
--python blender/scripts/generate_shogi_board.py
スライムも同じです。
/Applications/Blender.app/Contents/MacOS/Blender \
--background \
--python tools/blender/slimes/build.py -- \
--slug sword \
--output public/assets/slimes/sword.glb
GUIでしか再現できない操作を残さないことで、model生成をbuild stepとして扱えます。
これは自動化だけでなく、別の人や別のマシンでも同じassetを作れるかという設計チェックになります。
ただし、見た目までコードだけで評価しない
Python生成を正本にしても、visual qualityはscriptの正しさだけでは決まりません。
特にゲーム用assetでは、
- 実際のcamera distance
- mobile画面でのpixel size
- animation中のsilhouette
- weaponとbodyの重なり
- faceの見え方
を人間が見る必要があります。
そのため実運用では、
Python source
-> headless generation
-> structural validation
-> turntable / gallery render
-> game engine
-> gameplay camera QA
までを一つの流れとして扱っています。
後の記事で、このvalidationとvisual QAも詳しく扱います。
Blender Pythonが向いていたケース
二つのプロジェクトを通じて、特に効果が大きかったのは次の種類のassetでした。
| Asset | コード化が効いた理由 |
|---|---|
| 9×9の盤 | 寸法とゲーム座標の契約が強い |
| 同系統キャラクター | Base形状やsocketを共有できる |
| 多数の敵バリエーション | Definitionの差分を小さくできる |
| Rig | bone名をUnity contractとして固定できる |
| Animation | keyframe区間やroot不変条件を検証できる |
| Export | axis / node / material条件を固定できる |
逆に、一点物の複雑なsculptをコードで無理に表現する価値は低い場合があります。
目的は「Blenderの全操作をPythonへ置き換える」ことではありません。
再生成性とゲーム側契約に価値がある部分だけ、コードを正本にする。
これが今のところ一番実用的でした。