導入:なぜインベントリ設計が重要か
ゲーム開発において、インベントリシステムはプレイヤーの体験を豊かにする上で欠かせない要素です。しかし、インベントリシステムは単にアイテムを格納する箱ではありません。 アイテムのデータ構造、 インベントリのロジック、そして プレイヤーに情報を伝えるUI という、 三つの異なる要素が密接に連携して初めて機能します。
本記事では、Godotの強力な機能である Resource と シグナル を活用し、初心者から中級者向けに、堅牢で拡張性の高いインベントリシステムの基礎設計を解説します。
セクション1: アイテムデータの定義 (Resourceの活用)
インベントリシステムを設計する上で、まず最初に行うべきは「アイテムとは何か」を定義することです。アイテムのデータは、Godotでは Resource として扱うのが最適です。
ItemResource.gd のコード例
# ItemResource.gd
class_name ItemResource
extends Resource
@export var item_id: String = ""
@export var item_name: String = "New Item"
@export_multiline var description: String = ""
@export var icon_path: String = ""
@export var stackable: bool = true
@export var max_stack_size: int = 99
このResourceを継承した .tres ファイルを作成することで、具体的なアイテムの定義をデータとして管理できます。
セクション2: アイテム管理ロジックの実装
次に、アイテムの追加や削除といったインベントリの「動作」を管理するロジックを実装します。このロジックは、 シングルトン(オートロード) として実装することで、ゲーム内のどこからでもアクセスできるようにします。
Inventory.gd のコード例(アイテム追加・削除)
# Inventory.gd
extends Node
signal inventory_changed
var items: Dictionary = {}
const MAX_SLOTS: int = 20
func add_item(item_resource: ItemResource, count: int = 1) -> bool:
if not item_resource:
return false
if item_resource.stackable and items.has(item_resource):
items[item_resource] += count
inventory_changed.emit()
return true
elif items.size() < MAX_SLOTS:
items[item_resource] = count
inventory_changed.emit()
return true
return false
func remove_item(item_resource: ItemResource, count: int = 1) -> bool:
if not items.has(item_resource):
return false
items[item_resource] -= count
if items[item_resource] <= 0:
items.erase(item_resource)
inventory_changed.emit()
return true
func get_inventory_data() -> Dictionary:
return items
ポイント: inventory_changed.emit() がアイテムの追加・削除のたびに呼び出されています。これがUI連携の鍵となります。
セクション3: UI連携の基礎
インベントリのロジックがアイテムデータを更新しても、UIがそれを知らなければ画面は変わりません。ここでGodotの シグナル が真価を発揮します。
InventoryUI.gd のコード例(インベントリ表示)
# InventoryUI.gd
extends Control
@export var inventory_logic: Inventory
const SLOT_SCENE = preload("res://scenes/inventory_slot.tscn")
func _ready():
if inventory_logic:
inventory_logic.inventory_changed.connect(_update_ui)
_update_ui()
func _update_ui():
for child in get_children():
child.queue_free()
var inventory_data = inventory_logic.get_inventory_data()
for item_resource in inventory_data:
var slot = SLOT_SCENE.instantiate()
slot.set_item_data(item_resource, inventory_data[item_resource])
add_child(slot)
var empty_slots_count = inventory_logic.MAX_SLOTS - inventory_data.size()
for i in range(empty_slots_count):
var slot = SLOT_SCENE.instantiate()
slot.set_empty()
add_child(slot)
この実装により、アイテムが追加・削除されるたびにInventory.gdがシグナルを発し、それを受け取ったInventoryUI.gdが自動的に最新のデータに基づいて画面を更新します。
実践的な使用例:アイテムの獲得
# Player.gd 内の例
func _on_item_collected(item_resource: ItemResource):
if Inventory.add_item(item_resource, 1):
item_node.queue_free()
このように、プレイヤーノードはUIの存在を知る必要がなく、UIノードはプレイヤーノードの存在を知る必要がありません。すべてがインベントリロジックという中央のハブを介して、シグナルによって疎結合に連携しています。
まとめ:堅牢なインベントリ設計の要点
本記事で解説したGodot Engineにおけるインベントリシステムの基礎設計は、以下の三つの柱に基づいています。
| 要素 | Godotの機能 | 役割 |
|---|---|---|
| アイテムデータ | Resource | アイテムの静的な情報を定義・保存 |
| インベントリロジック | シングルトン(Node) | アイテムの追加・削除などの動作を管理 |
| UI連携 | シグナル | ロジックの変更をUIに通知 |
この設計パターンを採用することで、ゲームの規模が拡大しても、アイテムの種類が増えても、UIのデザインが変わっても、システムの核となるロジックを安定して維持することができます。