godot-genre-sandbox について
編集可能なボクセル世界やセル・オートマトン型サンドボックスを作るGodot開発者向けのスキルです。チャンク描画、マテリアル属性による相互作用、配置検証、圧縮保存を扱います。
主な機能
- 01チャンク型ボクセル世界の構成
- 02可視面メッシュの生成
- 03マテリアル属性による相互作用
- 04セル・オートマトン液体シミュレーション
- 05RLEとバイナリ形式のワールド保存
- 06非同期読み込みと浮動原点のパターン
活用シーン
- 01編集可能な3Dボクセル地形の構築
- 02落下砂型の要素相互作用の実装
- 03大規模なチャンク世界の保存
- 04マルチプレイでの配置操作の検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ thedivergentai/ game-dev-agentic-skills --skill godot-genre-sandboxインストール後、Claude Code で/godot-genre-sandboxを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/elite-technical-patterns.mdmigration-notes.mdscripts/blueprint_manager.gdcellular_automata_liquid.gddynamic_placement_validator.gdsandbox_patterns.gdsandbox_world_serializer.gdvoxel_chunk_manager.gdvoxel_chunk_mesher.gdvoxel_world.gd詳細
絶対に避けるべきこと(エキスパート向けアンチパターン)
パフォーマンスとスケーラビリティ
- ブロックごとに個別の
RigidBodyノードを絶対に使用しないでください。ワールドには必ず Static Colliders を使用し、物理演算は動的なプロップに限定してください。 - ワールド全体を毎フレーム絶対にシミュレーションしないでください。変更があった 「Dirty」チャンク のみを処理してください。休止中のチャンクは CPU をまったく消費しないようにしてください。
MultiMeshバッファを毎フレーム絶対に更新しないでください。必ず変更をまとめて処理し、変更が完了したとき(例:プレイヤーがペイントを止めたとき)にのみバッファを再構築してください。- グリッドセルごとに標準の Godot
Nodesを絶対に使用しないでください。RAM のオーバーヘッドを最小限に抑えるため、必ず PackedInt32Arrays または型付き Dictionary を使用してください。 - 配置のためにボクセルごとに絶対にレイキャストしないでください。直接 O(1) でセルを計算するため、必ずグリッド量子化(
floor(pos/size))を使用してください。 - チャンク内のすべてのブロック面を絶対に描画しないでください。GPU に外側から見える面のみを渡す
ArrayMeshを必ず生成してください(カリング/グリーディメッシュ化)。
データと永続化
- すべてのブロックのトランスフォームを含む生配列を絶対に保存しないでください。均一な空間を圧縮するため、必ずランレングス符号化(RLE)(例:「Air x 50,000」)を使用してください。
- 大きな地形チャンクを同期的に絶対に読み込まないでください。フレームのカクつきを防ぐため、必ず
ResourceLoader.load_threaded_request()を使用してください。 - ボクセルデータセットに標準のテキスト
.tscnファイルを絶対に使用しないでください。解析を 10 倍高速化するため、必ずバイナリ.resファイルを使用してください。 - 浮動小数点精度の限界(32,768 ユニット)を絶対に無視しないでください。大規模なワールドでは、必ず浮動原点シフトを実装してください。
システムとアーキテクチャ
- 要素の相互作用を絶対にハードコードしないでください(
if water and fire)。相互作用が素材の属性(可燃性、密度)から生じるプロパティシステムを必ず使用してください。 - マルチプレイヤーでクライアント側の配置を絶対に信用しないでください。境界とリソースをサーバーが検証するよう必ず求めてください。
- バックグラウンド生成スレッドから SceneTree を絶対に操作しないでください。安全のため、必ず
call_deferred()または Mutex ロックを使用してください。 - 孤立したチャンクをメモリに絶対に残さないでください。読み込み済みの領域を必ず追跡し、破棄されたブランチには
queue_free()を呼び出してください。
🛠 エキスパート向けコンポーネント(scripts/)
必須/パスを指定して読み込まないでください
- 2D 落下砂/CA のみ:
cellular_automata_liquid.gdと、以下のプロパティ/ツールのパターンを読み込みます。voxel_chunk_*.gd、voxel_world.gd、グリーディメッシュ関連のパスは読み込まないでください。- 3D ボクセル/チャンクワールド:
voxel_world.gd→voxel_chunk_manager.gd→ 外側から見える面のメッシュ生成に必須のvoxel_chunk_mesher.gdを読み込みます。2D 要素レイヤーも実行する場合を除き、2D CA の液体処理は読み込まないでください。- 配置/マルチプレイヤー検証:クライアントの掘削/配置を信用する前に、
dynamic_placement_validator.gdを読み込みます。- 永続化:RLE/バイナリのチャンク IO には
sandbox_world_serializer.gdを読み込みます。非同期読み込みと浮動原点にはsandbox_patterns.gdを使用してください。
チャンク/ボクセル(3D)
- voxel_world.gd — グリッド状態、ツールによる編集、チャンクのライフサイクルを管理する最上位のワールドコントローラーです。
- voxel_chunk_manager.gd — 中規模ワールド向けのチャンクライフサイクルと
MultiMeshInstance3Dの一括更新です。 - voxel_chunk_mesher.gd — 大規模ワールドでは必須です。WorkerThreadPool による可視面の
ArrayMesh構築と、遅延実行されるset_meshを行います。
要素/ツール(2D CA)
- cellular_automata_liquid.gd — プロパティに基づく密度チェックによって液体/粉体を処理します。
配置/保存/ユーティリティ
- dynamic_placement_validator.gd — 配置時の境界/リソース/サーバー側チェックです(クライアントを信用しないでください)。
- sandbox_world_serializer.gd — RLE/バイナリによるチャンク永続化のパターンです。
- sandbox_patterns.gd — 非同期チャンク読み込み、マルチスレッド用ヘルパー、浮動原点シフトです。
アーキテクチャパターン
1. 要素システム(プロパティベースの創発)
振る舞いではなく、素材のプロパティをモデル化します。プロパティが重なり合うことで、相互作用が創発します。
# element_data.gd
class_name ElementData extends Resource
enum Type { SOLID, LIQUID, GAS, POWDER }
@export var id: String = "air"
@export var type: Type = Type.GAS
@export var density: float = 0.0 # For liquid flow direction
@export var flammable: float = 0.0 # 0-1: Chance to ignite
@export var ignition_temp: float = 400.0
@export var conductivity: float = 0.0 # For electricity/heat
@export var hardness: float = 1.0 # Mining time multiplier
# EDGE CASE: What if two elements have same density but different types?
# SOLUTION: Use secondary sort (type enum priority: SOLID > LIQUID > POWDER > GAS)
func should_swap_with(other: ElementData) -> bool:
if density == other.density:
return type > other.type # Enum comparison: SOLID(0) > GAS(3)
return density > other.density
2. セルオートマトンのグリッド(落ちもの砂シミュレーション)
更新順序が重要です。上から下への更新により、「テレポート」する Godot パーティクルを防げます。
# world_grid.gd
var grid: Dictionary = {} # Vector2i -> ElementData
var dirty_cells: Array[Vector2i] = []
func _physics_process(_delta: float) -> void:
# CRITICAL: Sort top-to-bottom to prevent double-moves
dirty_cells.sort_custom(func(a, b): return a.y < b.y)
for pos in dirty_cells:
simulate_cell(pos)
dirty_cells.clear()
func simulate_cell(pos: Vector2i) -> void:
var cell = grid.get(pos)
if not cell: return
match cell.type:
ElementData.Type.LIQUID, ElementData.Type.POWDER:
# Try down, then down-left, then down-right
var targets = [pos + Vector2i.DOWN,
pos + Vector2i(- 1, 1),
pos + Vector2i(1, 1)]
for target in targets:
var neighbor = grid.get(target)
if neighbor and cell.should_swap_with(neighbor):
swap_cells(pos, target)
mark_dirty(target)
return
ElementData.Type.GAS:
# Gases rise (inverse of liquids)
var targets = [pos + Vector2i.UP,
pos + Vector2i(-1, -1),
pos + Vector2i(1, -1)]
# Same swap logic...
# EDGE CASE: What if multiple godot-particles want to move into same cell?
# SOLUTION: Only mark target dirty, don't double-swap. Next frame resolves conflicts.
3. ツールシステム(ストラテジーパターン)
入力とワールドの変更を分離します。
# tool_base.gd
class_name Tool extends Resource
func use(world_pos: Vector2, world: WorldGrid) -> void: pass
# tool_brush.gd
extends Tool
@export var element: ElementData
@export var radius: int = 1
func use(world_pos: Vector2, world: WorldGrid) -> void:
var grid_pos = Vector2i(floor(world_pos.x), floor(world_pos.y))
# Circle brush pattern
for x in range(-radius, radius + 1):
for y in range(-radius, radius + 1):
if x*x + y*y <= radius*radius: # Circle boundary
var target = grid_pos + Vector2i(x, y)
world.set_cell(target, element)
# FALLBACK: If element placement fails (e.g., occupied by indestructible block)?
# Check world.can_place(target) before set_cell(), show visual feedback.
4. チャンクベースのレンダリング(3D ボクセル)— MultiMesh と ArrayMesh の比較
必須:外側の面/グリーディー方式のチャンクメッシュでは、voxel_chunk_mesher.gd(WorkerThreadPool + SurfaceTool + call_deferred("set_mesh"))を読み、応用してください。プロジェクトコードに不完全なメッシャーのスタブを直接記述しないでください。
| ワールド規模 | レンダリング経路 | 読み込み |
|---|---|---|
| 小規模(<100k ブロック) | 単一の MeshInstance3D + SurfaceTool | メッシャーのパターンのみ |
| 中規模(100k–1M) | チャンク化した MultiMeshInstance3D(単一メッシュに多数のインスタンスを配置。編集完了時にバッファーを一括処理) | 必須 voxel_chunk_manager.gd |
| 大規模(>1M)/編集可能な地形 | チャンク化した ArrayMesh(可視面/グリーディー方式の四角形)+ LOD。RenderingServer のインスタンス RID は任意 | 必須 voxel_chunk_mesher.gd + マネージャー |
ルール:すべてのインスタンスが同一のメッシュを共有し、インスタンスごとのトランスフォーム/カラーだけが必要な場合は、MultiMesh を優先してください。隣接するボクセルを固有の面(グリーディー面、UV アトラス、チャンクごとのコリジョン)に結合する必要がある場合は、ArrayMesh によるメッシングを優先してください。
サンドボックスワールドのセーブシステム
# chunk_save_data.gd
class_name ChunkSaveData extends Resource
@export var chunk_coord: Vector2i
@export var rle_data: PackedInt32Array # [type_id, count, type_id, count...]
# EXPERT TECHNIQUE: Run-Length Encoding
static func encode_chunk(grid: Dictionary, chunk_pos: Vector2i, chunk_size: int) -> ChunkSaveData:
var data = ChunkSaveData.new()
data.chunk_coord = chunk_pos
var run_type: int = -1
var run_count: int = 0
for y in range(chunk_size):
for x in range(chunk_size):
var world_pos = chunk_pos * chunk_size + Vector2i(x, y)
var cell = grid.get(world_pos)
var type_id = cell.id if cell else 0 # 0 = air
if type_id == run_type:
run_count += 1
else:
if run_count > 0:
data.rle_data.append(run_type)
data.rle_data.append(run_count)
run_type = type_id
run_count = 1
# Flush final run
if run_count > 0:
data.rle_data.append(run_type)
data.rle_data.append(run_count)
return data
# COMPRESSION RESULT: Empty chunk (16×16 = 256 blocks of air)
# Without RLE: 256 integers = 1024 bytes
# With RLE: [0, 256] = 8 bytes (128x compression!)
プレイヤー制作物の物理ジョイント
# joint_tool.gd
func create_hinge(body_a: RigidBody2D, body_b: RigidBody2D, anchor: Vector2) -> void:
var joint = PinJoint2D.new()
joint.global_position = anchor
joint.node_a = body_a.get_path()
joint.node_b = body_b.get_path()
joint.softness = 0.5 # Allows slight flex
add_child(joint)
# EDGE CASE: What if bodies are deleted while joint exists?
# Joint will auto-break in Godot 4.x, but orphaned Node leaks memory.
# SOLUTION:
body_a.tree_exiting.connect(func(): joint.queue_free())
body_b.tree_exiting.connect(func(): joint.queue_free())
# FALLBACK: Player attaches joint to static geometry?
# Check `body.freeze == false` before creating joint.
Godot 固有のエキスパート向け注意事項
MultiMeshInstance3D.multimesh.instance_count:バッファーを割り当てる前に必ず設定してください。動的に拡張できないため、再作成が必要です。RigidBody2D.sleeping:物体は動きがない状態が 2 秒続くと、自動的にスリープします。力を加えずに強制的に起こすには、apply_central_impulse(Vector2.ZERO)を使用してください。GridMapとMultiMesh:GridMap は MeshLibrary を使用し(多様性に優れています)、MultiMesh は単一のメッシュを使用します(速度に優れています)。組み合わせて、構造物には GridMap、地形には MultiMesh を使用してください。- 連続 CD:
continuous_cdには凸形状のコリジョンシェイプが必要です。飛翔体にはRectangleShape2Dではなく、CapsuleShape2Dを使用してください。
🚀 エリート向け技術実装(Batch 09)
1. グリーディー/可視面のメッシング
プレースホルダーのメッシャーを貼り付けないでください。スレッドを利用した可視面生成については、必ず voxel_chunk_mesher.gd を読んでください。完全なグリーディー方式の四角形結合には、そのパターンを拡張してください。MultiMesh と ArrayMesh の選択については、上記の §4 を参照してください。極限まで描画性能を高める場合は、メッシャーがサーフェスデータを所有した後で、コミット済み配列を RenderingServer 経由で送信できます(公式ドキュメント → サーバーの使用を参照)。
2. VoxelGI(優先度を下げ、ドキュメントとライティングスキルを使用)
サンドボックスのチャンクライティングは、不完全な RenderingServer.voxel_gi_allocate_data のスタブでは実装しません。プロシージャルなボリュームの動的 GI には、VoxelGI の使用に従い、実装の詳細は godot-3d-lighting を参照してください。実行時の VoxelGI が本当に必要な場合を除き、そのスキルに記載されたベイク/プローブ方式を優先してください。
3. ブループリントの共有(Base64/JSON シリアライズ)
シンプルな文字列を使って、プレイヤーが制作物を共有できるようにします。読みやすいシリアライズには JSON を、クリップボード連携には DisplayServer を使用してください。
class_name BlueprintManager extends Node
## Exports chunk data to the OS clipboard.
static func export_blueprint_to_clipboard(blueprint_data: Dictionary) -> void:
var json_string: String = JSON.stringify(blueprint_data)
DisplayServer.clipboard_set(json_string)
## Imports blueprint from clipboard.
static func import_blueprint_from_clipboard() -> Dictionary:
var json_string: String = DisplayServer.clipboard_get()
var parsed_data = JSON.parse_string(json_string)
return parsed_data if parsed_data is Dictionary else {}
深掘りレシピ(必要に応じて)
| トピック | リファレンス/スクリプト |
|---|---|
| 高度なメッシングとブループリントの共有 | elite-technical-patterns.md + voxel_chunk_mesher.gd |
| エレメント/CA グリッド | SKILL.md の Architecture Patterns §1–3 + cellular_automata_liquid.gd |
| チャンクの RLE 永続化 | SKILL.md の Save System § + sandbox_world_serializer.gd |
リファレンス
段階的な開示:特定の API を調査するときにのみ公式ドキュメントのリンクを開き、関連スキルは近接する領域へ案内するときに読み込んでください。ラティス全体を事前に読み込まないでください。
公式ドキュメント
- MultiMesh の使用 — チャンクごとにボクセル/プロップのインスタンスをバッチ処理し、フレームごとのバッファ再構築を回避します。
- GridMap の使用 — 構造物に単一の MultiMesh メッシュ以上の多様性が必要な場合に、MeshLibrary のセルを配置します。
- ArrayMesh — 貪欲メッシュ化した外面を、ブロックごとのメッシュではなく単一のサーフェスとして送信します。
- SurfaceTool — MeshInstance3D に確定する前に、法線を含むチャンクメッシュを構築してインデックスを付けます。
- バックグラウンド読み込み — ResourceLoader によるチャンクのスレッド読み込みで、探索中の引っかかりを防ぎます。
- ゲームの保存 — プレイヤーが構築したワールドを永続化します(グループ、JSON/
var_to_str、バイナリ Resources)。 - 複数スレッドの使用 — WorkerThreadPool でメッシュ化/生成を行い、SceneTree の変更は遅延実行します。
- サーバーの使用 — チャンク描画で SceneTree を迂回する場合の RenderingServer のメッシュ/インスタンス RID。
- VoxelGI の使用 — 大規模なプロシージャルサンドボックス領域における動的 GI 割り当て。
- 大規模ワールド座標 — 約 32k 単位を超える場合の精度限界と浮動原点の戦略。
- レイキャスト — ボクセルごとのレイキャストではなく、直接空間状態を使った照準/配置/破壊クエリ。
- PinJoint2D — プレイヤーが作成する物理ギミック用のヒンジ型ジョイント。
関連スキル
前提条件
- godot-project-foundations — チャンクシーンとバイナリ
.resワールドデータに取り組む前に、シーンツリー、Resources、インポートの基礎を確認します。 - godot-physics-3d — 地形用の StaticBody コライダー、RigidBody プロップ、配置検証に使う形状クエリについて学びます。
- godot-gdscript-mastery — メッシャーで使う型付き Dictionary/Packed 配列、WorkerThreadPool タスク、遅延 SceneTree 編集について学びます。
補完
- godot-3d-world-building — サンドボックスの建築ツールでよく再利用される GridMap/MeshLibrary とベイクのフローです。
- godot-3d-lighting — プロシージャルなボリューム向けの VoxelGI/プローブ/ベイク戦略です(このスキルで RenderingServer の GI スタブを独自に作成しないでください)。
- godot-procedural-generation — プレイヤーが編集する前にボクセルチャンクに初期値を設定するノイズ/ダンジョン生成器です。
- godot-performance-optimization — サンドボックス規模での MultiMesh の予算、ダーティチャンクのシミュレーション、ドローコールの上限です。
- godot-save-load-systems — 場当たり的な JSON を超える、RLE/バイナリ形式のチャンク永続化と移行可能なセーブスキーマです。
- godot-raycasting-queries — ブロックツール向けの PhysicsDirectSpaceState によるピッキングと形状の交差判定です。
- godot-2d-physics — 2D サンドボックスの装置や落下する砂の小道具に使う PinJoint2D/RigidBody2D のパターンです。
- godot-scene-management — 孤立したブランチを残さずに、スレッド化されたロードキューとチャンクノードのライフサイクルを管理します。
- godot-monte-carlo-balancer — サンドボックスシステムから生まれるクラフトコスト、元素の希少度、経済ループを調整します。
下流/利用側
- godot-genre-open-world — 編集可能なサンドボックスチャンクにストリーミング、フローティングオリジン、HLOD を重ねます。
- godot-genre-survival — 破壊可能なボクセル/元素の世界を利用する採集、ニーズ、クラフトのループです。
- godot-multiplayer-networking — 共有クリエイティブワールドにおける、サーバー権限による配置検証です。
マスター
- godot-master — ライブラリのルーターであり、スキル横断の検索に使うミラー化されたモジュールエントリです。