3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Godot 4.x】2D脱出ゲームを作ってみる⑨〜アイテム管理:シーン常駐のインベントリシステムを実装する〜

3
Posted at

はじめに

Claude Code + MCP + Godot 4.xで脱出ゲームを作る連載の第9回です。

前回までにダイヤル錠ギミック一式が完成しました。今回は脱出ゲームのもう一つの核となるインベントリシステムを実装します。画面右側に常時表示されるアイテムバー、タップでの選択、長押しでの詳細表示、そしてギミックへの組み込みまでを一気に作ります。

今回は試行錯誤の過程は省き、各指示に対する最終的な実装だけをまとめています。

この記事で作るもの

Resource継承によるアイテム定義(10〜50個規模を想定)を作り、シーンをまたいで常時表示されるインベントリバー(CanvasLayer常駐)に組み込みます。アイテムのタップ選択・長押し詳細ポップアップ、背景に重ねて表示するアイテム取得演出も実装し、状態管理はGlobalオートロードに統合して永続化します。

スクリーンショット 2026-07-13 14.51.20.png

前提条件

Claude Code + godot-mcpのセットアップ完了(第2回参照)、scripts/global.gd(オートロード)が作成済み(第8回参照)、scenes/components/popup.tscn が作成済み(第6回参照)という前提で進めます。以下の画像素材も assets/images/items/ に配置済みです。

ファイル 用途 画像
item_01_1.png アイテムのサムネイル(100×100px) item_01_1.png
item_01_1_large.png ポップアップ用の大きいアイテム画像 item_01_1_large.png
item_002.png アイテム取得演出用の全画面画像(1867×842px) item_002.png
item_popup.png アイテム詳細ポップアップの枠画像 item_popup.png

設計方針

まずはClaudeデスクトップで設計を相談しました。

プロンプト:
godot2Dでアイテム管理を行うベストな方法

これに対して質疑応答がありつつ、

Q: アイテムの規模感は?
A: アイテム数は中程度(10~50個)

Q: インベントリのUIはどのような形式を想定していますか?
A: 画面右側に常時表示するバー形式+アイテムをタップすると選択のトグル+アイテムを長押しでアイテムのポップアップを表示

Q: アイテムの機能はどこまで必要ですか?
A: 取得+使用+組み合わせ(複数アイテムで新しいものを作る)

これで設計をしてもらい、Claude Code + MCPに流し込みました。これだけのやり取りで設計してもらえると、ちょっと中毒になってしまいます。

これまでのプロジェクトは class_name を一切使わない素朴な構成(Global は単一の Node、各コンポーネントは extends Control + _ready() でシグナル接続)でした。今回はアイテムが10〜50個規模になりうるため、このプロジェクト初の class_name として ItemData(Resource継承)を導入します。状態管理は既存の Global オートロードに集約し、永続化も第8回の POST_OPEN と同じ user://save_data.json への書き込みパターンを流用します。


ファイル構成

resources/
  items/
    item_01_1.tres          ← 今回作成
scenes/
  post_open.tscn            ← 変更(アイテム取得演出を追加)
  components/
    item_slot.tscn          ← 今回作成
    item_popup.tscn         ← 今回作成
    inventory_bar.tscn       ← 今回作成
scripts/
  global.gd                 ← 変更(インベントリ管理を追加)
  post_open.gd               ← 変更
  resources/
    item_data.gd              ← 今回作成
  components/
    item_slot.gd                ← 今回作成
    item_popup.gd                ← 今回作成
    inventory_bar.gd              ← 今回作成

1. 既存アセットに合わせてインベントリの土台を作る

設計を流し込むと、まず質問が返ってきました。

  • この設計に沿って実際にファイル(ItemData, InventoryManager, InventoryBarなど)を作成し始める
  • 特定の部分(長押し判定、組み合わせロジックなど)をもう少し詰める
  • 既存のプロジェクト構成(frame_popup.pngやdial/base.pngなど)に合わせて調整する

これに対してこう答えました。

プロンプト:
既存のプロジェクト構成(frame_popup.pngやdial/base.pngなど)に合わせて調整する。assets/images/itemsに常時表示のサムネイルアイテム画像とポップアップ用のアイテム画像を配置済みです。

この質疑応答を経て、Claudeのコーディングが始まりました。

アイテム定義(ItemData)は次の形にしました。

# scripts/resources/item_data.gd
class_name ItemData
extends Resource

@export var id: String = ""
@export var display_name: String = ""
@export var description: String = ""
@export var icon: Texture2D
@export var popup_texture: Texture2D
@export var combinable_with: Array[String] = []
@export var combine_result: String = ""

配置済みの item_01_1.png(サムネイル)・item_01_1_large.png(ポップアップ用)を紐付けたリソースを作成します。

# resources/items/item_01_1.tres
[gd_resource type="Resource" script_class="ItemData" load_steps=4 format=3]

[ext_resource type="Script" path="res://scripts/resources/item_data.gd" id="1_item_data"]
[ext_resource type="Texture2D" path="res://assets/images/items/item_01_1.png" id="2_icon"]
[ext_resource type="Texture2D" path="res://assets/images/items/item_01_1_large.png" id="3_popup"]

[resource]
script = ExtResource("1_item_data")
id = "item_01_1"
icon = ExtResource("2_icon")
popup_texture = ExtResource("3_popup")

シーン遷移(change_scene_to_file)をまたいでバーを常時表示させたいので、Global._ready()CanvasLayer を自前生成し、そこにインベントリバーのシーンを常駐させます。タイトル・スプラッシュ画面ではゲームUIを出したくないため、シーン名で表示を切り替えます。

# scripts/global.gd(抜粋)
const _ITEMS_DIR := "res://resources/items/"
const _HIDDEN_BAR_SCENES := ["Splash", "Title"]

signal item_added(item: ItemData)
signal item_removed(item_id: String)
signal item_selected(item: ItemData)
signal item_deselected

var _items: Array[ItemData] = []
var selected_item: ItemData = null
var _inventory_bar: Control

func _ready() -> void:
    _load()
    _setup_inventory_bar()

func _setup_inventory_bar() -> void:
    var canvas_layer := CanvasLayer.new()
    add_child(canvas_layer)
    _inventory_bar = preload("res://scenes/components/inventory_bar.tscn").instantiate()
    canvas_layer.add_child(_inventory_bar)
    get_tree().root.child_entered_tree.connect(_on_root_child_entered)
    _update_inventory_bar_visibility(get_tree().current_scene)

func _on_root_child_entered(node: Node) -> void:
    if node.get_parent() != get_tree().root:
        return
    _update_inventory_bar_visibility(node)

func _update_inventory_bar_visibility(scene: Node) -> void:
    if scene == null:
        return
    _inventory_bar.visible = scene.name not in _HIDDEN_BAR_SCENES

func add_item(item: ItemData) -> void:
    if has_item(item.id):
        return
    _items.append(item)
    item_added.emit(item)
    _save()

func has_item(item_id: String) -> bool:
    return _items.any(func(i): return i.id == item_id)

func get_items() -> Array[ItemData]:
    return _items

func select_item(item: ItemData) -> void:
    if selected_item == item:
        selected_item = null
        item_deselected.emit()
    else:
        selected_item = item
        item_selected.emit(item)

所持アイテムは POST_OPEN と同じ user://save_data.json への書き込みパターンを流用し、ITEMS キーにidの配列で永続化します。

アイテムスロット側は、既存の MailboxArea(第5回)と同じ gui_input を使った矩形の当たり判定パターンを踏襲しました。この時点では、押下時刻を記録して離した瞬間に経過時間を判定する方式にしています。

# scripts/components/item_slot.gd(この時点)
extends Control

signal tapped(item: ItemData)
signal long_pressed(item: ItemData)

const _LONG_PRESS_DURATION := 0.5

var item: ItemData
var _press_started_at: float = -1.0

func _ready() -> void:
    gui_input.connect(_on_gui_input)

func setup(new_item: ItemData) -> void:
    item = new_item
    $Icon.texture = item.icon

func _on_gui_input(event: InputEvent) -> void:
    if not (event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT):
        return
    if event.pressed:
        _press_started_at = Time.get_ticks_msec() / 1000.0
        return
    if _press_started_at < 0.0:
        return
    var duration := Time.get_ticks_msec() / 1000.0 - _press_started_at
    _press_started_at = -1.0
    if duration >= _LONG_PRESS_DURATION:
        long_pressed.emit(item)
    else:
        tapped.emit(item)

アイテム詳細ポップアップは、既存の popup.tscn(第6回のダイヤル錠ポップアップ)と同じ frame_popup.png + 開閉Tweenのパターンを流用して新設しました。

# scripts/components/item_popup.gd(この時点)
extends Control

signal closed

func _ready() -> void:
    $Content/BtnBack.pressed.connect(close)
    hide()

func show_item(item: ItemData) -> void:
    $Content/Frame/ItemImage.texture = item.popup_texture
    $Content/Frame/DescriptionLabel.text = item.description
    show()
    modulate.a = 0.0
    var tween := create_tween()
    tween.tween_property(self, "modulate:a", 1.0, 0.2)

func close() -> void:
    var tween := create_tween()
    tween.tween_property(self, "modulate:a", 0.0, 0.2)
    tween.tween_callback(func(): hide(); closed.emit())

インベントリバー本体は Slots(VBoxContainer)にスロットを並べ、シグナル経由で Global の状態変化に追従する形にしています。

# scripts/components/inventory_bar.gd(この時点)
extends Control

const ITEM_SLOT := preload("res://scenes/components/item_slot.tscn")
const ITEM_POPUP := preload("res://scenes/components/item_popup.tscn")

var _popup: Control

func _ready() -> void:
    _popup = ITEM_POPUP.instantiate()
    add_child(_popup)
    Global.item_added.connect(_on_item_added)
    Global.item_removed.connect(_on_item_removed)
    Global.item_selected.connect(_on_item_selected)
    Global.item_deselected.connect(_on_item_deselected)
    for item in Global.get_items():
        _add_slot(item)

func _add_slot(item: ItemData) -> void:
    var slot := ITEM_SLOT.instantiate()
    slot.setup(item)
    slot.tapped.connect(_on_slot_tapped)
    slot.long_pressed.connect(_on_slot_long_pressed)
    $Slots.add_child(slot)

func _on_slot_tapped(item: ItemData) -> void:
    Global.select_item(item)

func _on_slot_long_pressed(item: ItemData) -> void:
    _popup.show_item(item)
# scenes/components/inventory_bar.tscn(この時点、抜粋)
[node name="InventoryBar" type="Control"]
anchors_preset = 15
mouse_filter = 2

[node name="Slots" type="VBoxContainer" parent="."]
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -140.0
offset_top = -300.0
offset_right = -20.0
offset_bottom = 300.0
grow_horizontal = 0
theme_override_constants/separation = 16

ItemData だけに class_name を使ったのは、既存コードが素朴な extends Control ベースだった一方、アイテムは10〜50個規模でエディタ上から個別に .tres ファイルとして作成・編集したかったためです。Resource を継承した専用クラスにすることで、エディタの「新規リソース作成」ダイアログにも ItemData が候補として表示されるようになります。

CanvasLayer でシーン遷移をまたいだ常駐UIも実現できました。change_scene_to_file() は現在のシーンツリーを丸ごと入れ替えますが、Global(オートロード)配下のノードは影響を受けません。Global._ready()CanvasLayer を生成し、その子としてインベントリバーを配置することで、どのシーンに遷移してもバーが表示され続けます。


2. バーを常に最前面に、背景を半透明黒に

ある程度仕上げてもらったので、ここから少し調整していきます。

プロンプト:
画面右のアイテム表示バーは常にz軸前面で、黒の透過50%で背景色を表示。

CanvasLayer はデフォルトでは layer = 0 扱いで、通常シーンのノードと同じレイヤーになります。ノードツリー上の追加順によっては通常シーンのコンテンツがバーより手前に来てしまうため、layer を明示的に高くして常に最前面を保証します。

  var canvas_layer := CanvasLayer.new()
+ canvas_layer.layer = 10
  add_child(canvas_layer)

あわせて Slots の背後に半透明の黒背景を追加します。

# scenes/components/inventory_bar.tscn(抜粋)
[node name="Background" type="ColorRect" parent="."]
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -160.0
offset_top = -320.0
offset_right = 0.0
offset_bottom = 320.0
grow_horizontal = 0
mouse_filter = 2
color = Color(0, 0, 0, 0.5)

layer の値が大きいほど手前に描画されます。通常シーンのノードは layer = 0 のため、layer = 10 を指定するだけで常に最前面になります。Background の範囲は Slots より20px広く取ることで、余白のある見た目にしています。mouse_filter = 2(Ignore)でタップは素通りさせています。


3. 背景を画面高さ100%に

プロンプト:
背景は高さ100%、コンテンツの高さはそのまま維持。

Background のアンカーを中央基準から画面右端フル高さ(anchors_preset = 11 / RightWide)に変更します。Slots 側はサイズ・位置とも変更しません。

  [node name="Background" type="ColorRect" parent="."]
- anchors_preset = 6
+ anchors_preset = 11
  anchor_left = 1.0
- anchor_top = 0.5
+ anchor_top = 0.0
  anchor_right = 1.0
- anchor_bottom = 0.5
+ anchor_bottom = 1.0
  offset_left = -160.0
- offset_top = -320.0
+ offset_top = 0.0
  offset_right = 0.0
- offset_bottom = 320.0
+ offset_bottom = 0.0
  grow_horizontal = 0
+ grow_vertical = 2
  mouse_filter = 2
  color = Color(0, 0, 0, 0.5)

4. アイテム取得演出を配置する

ここで実際にアイテムをシーンに設置してみます。

プロンプト:
assets/images/item_002.pngを配置

これだけの指示ではさすがにClaudeも迷ったので、追加で背景をこう伝えました。

· assets/images/item_002.png(1867×842、フルスクリーンサイズ)はどう扱いますか?

プロンプト:
郵便受けの扉が開いている状態で、
item_01_1のアイテムを持っていない場合、この画像をステージ背景画像上に被せて表示します

配置された item_002.png は1867×842pxとサムネイル(100×100)とは桁違いに大きく、既存の背景画像(bg_stage_002_2.png)とほぼ同サイズでした。用途を確認すると「郵便受けの扉が開いている状態で、item_01_1 を持っていない場合、背景に重ねて表示する」アイテム設置演出用の画像だったので、post_open.tscn(開封後シーン)に組み込みます。

背景 BgStage002_2 と同じ全画面サイズの TextureButton として重ね、タップでアイテムを取得できるようにしました。ノードは BgStage002_2 の直後・BtnBack の手前に配置し、z軸を「背景 < アイテム < 戻るボタン」の順にしています。

# scenes/post_open.tscn(抜粋)
[node name="ItemButton" type="TextureButton" parent="."]
offset_right = 1920.0
offset_bottom = 1083.0
ignore_texture_size = true
stretch_mode = 1
texture_normal = ExtResource("4_item")
# scripts/post_open.gd(抜粋)
const _ITEM := preload("res://resources/items/item_01_1.tres")

func _ready() -> void:
    ...
    $ItemButton.pressed.connect(_on_item_button_pressed)
    _update_item_button_visibility()

func _update_item_button_visibility() -> void:
    $ItemButton.visible = not Global.has_item(_ITEM.id)

func _on_item_button_pressed() -> void:
    Global.add_item(_ITEM)
    _update_item_button_visibility()

_ready() と取得直後の両方で _update_item_button_visibility() を呼ぶことで、初回訪問時は画像を表示、取得後・再訪時は非表示という状態を Global.has_item() だけで一元管理できます。個別のフラグを別途持つ必要がありません。

鍵をタップすると、こうなります。

スクリーンショット 2026-07-13 14.49.34.png

郵便受けから鍵が無くなり、画面右側のアイテムスロットに格納されました。

スクリーンショット 2026-07-13 14.51.02.png


5. ポップアップ枠画像とアイテム名ラベル

プロンプト:
アイテムのポップの枠画像は、assets/images/item_popup.pngを使ってください。またアイテム画像の下にラベルを配置して、アイテムのDisplay Nameの値を表示。

item_popup.pngframe_popup.png と同じ900×600pxだったため、枠の差し替えだけでレイアウトの座標系はそのまま流用できます。

- [ext_resource type="Texture2D" path="res://assets/images/frame_popup.png" id="1_frame"]
+ [ext_resource type="Texture2D" path="res://assets/images/item_popup.png" id="1_frame"]

アイテム画像を少し小さくして隙間を作り、その下に NameLabel を追加します。

  [node name="ItemImage" type="TextureRect" parent="Content/Frame"]
  anchor_left = 0.5
  anchor_right = 0.5
- offset_left = -150.0
- offset_top = 60.0
- offset_right = 150.0
- offset_bottom = 360.0
+ offset_left = -130.0
+ offset_top = 40.0
+ offset_right = 130.0
+ offset_bottom = 300.0
  mouse_filter = 2
  expand_mode = 1
  stretch_mode = 5

+ [node name="NameLabel" type="Label" parent="Content/Frame"]
+ anchor_right = 1.0
+ offset_left = 60.0
+ offset_top = 310.0
+ offset_right = -60.0
+ offset_bottom = 360.0
+ mouse_filter = 2
+ theme_override_font_sizes/font_size = 28
+ horizontal_alignment = 1

  [node name="DescriptionLabel" type="Label" parent="Content/Frame"]
  anchor_right = 1.0
  offset_left = 60.0
- offset_top = 380.0
+ offset_top = 370.0
  offset_right = -60.0
  offset_bottom = 560.0
  func show_item(item: ItemData) -> void:
      $Content/Frame/ItemImage.texture = item.popup_texture
+     $Content/Frame/NameLabel.text = item.display_name
      $Content/Frame/DescriptionLabel.text = item.description

6. ポップアップ内要素を下に100pxシフト

想定よりポップアップ内の上の方にアイテム画像とラベルが配置されたので、調整しました。

プロンプト:
アイテムポップアップのアイテム画像とアイテムラベルの表示位置を下に100px移動

画像とラベルだけを動かすと、その下にある DescriptionLabel(y: 370〜560)と重なってしまうため、説明文も含めてブロックごと100pxずらす方針にしています。

  [node name="ItemImage" type="TextureRect" parent="Content/Frame"]
- offset_top = 40.0
- offset_bottom = 300.0
+ offset_top = 140.0
+ offset_bottom = 400.0

  [node name="NameLabel" type="Label" parent="Content/Frame"]
- offset_top = 310.0
- offset_bottom = 360.0
+ offset_top = 410.0
+ offset_bottom = 460.0

  [node name="DescriptionLabel" type="Label" parent="Content/Frame"]
- offset_top = 370.0
- offset_bottom = 560.0
+ offset_top = 470.0
+ offset_bottom = 660.0

DescriptionLabel の下端(660px)は枠画像の高さ(600px)を超えますが、要素同士は重ならないため問題ありません。


7. 長押し時間を1秒に

長押しでアイテムポップアップを表示させたいと思い、ほぼそのままプロンプトに流しました。

プロンプト:
画面右のアイテムを1s長押ししたらアイテムポップアップが表示されるように

item_slot.gd の長押し判定しきい値を0.5秒から1.0秒に変更しました。

- const _LONG_PRESS_DURATION := 0.5
+ const _LONG_PRESS_DURATION := 1.0

8. アイテムスロットを両ポップアップより前面に

プロンプト:
画面右のアイテムスロットはアイテムポップアップやギミックポップアップよりz軸の上に来るように

BackgroundSlotsitem_popup はいずれも Global が作った同じ CanvasLayerlayer = 10)に属しています。第7回のダイヤル錠の「ギミックポップアップ」(popup.tscn)は各シーン内の通常ノード(layer = 0)なので、この時点で既に Slots より奥にあります。一方 item_popupinventory_bar.gd_ready()add_child() した際に子リストの末尾(=最前面)に入るため、長押しで詳細ポップアップを開くと Slots がその下に隠れてしまう状態でした。

_ready() の最後で Slots を子リストの末尾に移動し、同じレイヤー内での描画順・入力優先度を Slots が最上位になるよう固定します。

      _popup = ITEM_POPUP.instantiate()
      add_child(_popup)
+     move_child($Slots, get_child_count() - 1)
      Global.item_added.connect(_on_item_added)

Godotのz軸管理は2段階になっていることがここで分かりました。CanvasLayer.layer で「シーンをまたいだレイヤー」を制御し、同一レイヤー内では「ノードツリーの子の並び順(move_child() で操作可能)」が描画順・入力優先度を決めます。今回のように、あるコンポーネントの中でさらに前後関係を調整したい場合は move_child() が有効です。


9. 長押し判定をリリース待ちからリアルタイム発火に変更

プロンプト:
画面右のアイテムを1s長押しした時点で、まだ長押ししていても、アイテムポップアップが表示されるように

これまでの実装は「指を離した瞬間に押していた時間を逆算する」方式だったため、1秒経過してもまだ押し続けている間はポップアップが出ませんでした。押下と同時にワンショットタイマーを仕込み、指を離さずに1秒経過した時点でその場で long_pressed を発火する方式に変更します。

var item: ItemData
var _is_pressed: bool = false
var _long_press_triggered: bool = false

func _on_gui_input(event: InputEvent) -> void:
    if not (event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT):
        return
    if event.pressed:
        _is_pressed = true
        _long_press_triggered = false
        _wait_for_long_press()
    else:
        if not _long_press_triggered:
            tapped.emit(item)
        _is_pressed = false

func _wait_for_long_press() -> void:
    await get_tree().create_timer(_LONG_PRESS_DURATION).timeout
    if _is_pressed:
        _long_press_triggered = true
        long_pressed.emit(item)

ポイントは await get_tree().create_timer(...).timeout でノンブロッキングな待機処理を書いているところです。1秒待ってから _is_pressed を確認し、まだ押されたままなら長押し確定として long_pressed を発火し、_long_press_triggered フラグを立てます。指を離した時点(else節)ではこのフラグを見て、すでに長押しとして処理済みなら tapped(タップ選択)を重複発火しないようにしています。


動作確認

post_open.tscn を訪れると item_002.png の演出画像が表示され、タップするとアイテムを取得して画面右にサムネイルが表示されます。再訪時は演出画像が表示されず、アイテムは所持済みのままです。アイテムのサムネイルをタップすると選択状態がトグルし、1秒間長押しすると詳細ポップアップが表示されます(押しっぱなしの状態でも発火します)。インベントリバーはタイトル・スプラッシュ画面では表示されず、他のシーンでは常に最前面に表示されます。

鍵をタップすると、こうなります。

スクリーンショット 2026-07-13 14.49.34.png

郵便受けから鍵が無くなり、画面右側のアイテムスロットに格納されます。

スクリーンショット 2026-07-13 14.51.02.png

鍵をタップするとハイライトで選択状態になります。

スクリーンショット 2026-07-13 14.51.11.png

鍵を長押しするとアイテムのポップアップが表示されます。

スクリーンショット 2026-07-13 14.51.20.png

バーがどのシーンでも表示されない場合は Global._setup_inventory_bar()_ready() から呼ばれているか、タイトル画面でもバーが出てしまう場合は _HIDDEN_BAR_SCENES にシーン名が正しく含まれているか(大文字小文字も一致させる)を確認してください。バーが背景より奥に表示される場合は CanvasLayer.layer10 など高い値になっているか、長押ししてもポップアップが出ない場合は _LONG_PRESS_DURATION の値と _wait_for_long_press()pressed 時に呼ばれているかを見ます。ポップアップの下にスロットが隠れる場合は move_child($Slots, get_child_count() - 1)_ready() の最後で呼ばれているか、取得済みアイテムが再訪時にまた表示される場合は Global.has_item() の判定・_save() が正しく呼ばれているかを確認してください。


今回はシーンをまたいで常時表示するUIを Global が生成する CanvasLayer にインベントリバーを常駐させる形で実現し、CanvasLayer.layer を通常シーンより高い値にすることで常に最前面に描画されるようにしました。タイトル・スプラッシュでは get_tree().root.child_entered_tree でシーン切り替えを検知し、シーン名で visible を切り替えています。10〜50個規模のアイテム定義には class_name ItemData(Resource継承)と.tresファイルを使い、状態のシーン間共有・永続化は Global にシグナル付きAPIを持たせて user://save_data.json へ書き込む、第8回の POST_OPEN と同じパターンを踏襲しました。背景に馴染むアイテム取得演出は背景と同サイズの全画面 TextureButton を重ねて所持状態で表示を切り替え、同一CanvasLayer内でのさらなる前後関係は move_child() で子リストの並び順を明示的に制御し、押しっぱなし中に発火する長押し判定は await get_tree().create_timer(duration).timeout で待機してタイムアウト時点の押下状態を確認する形にしています。

今回の実装で、Godotにおけるz軸管理が「CanvasLayer.layer によるシーンをまたいだ前後関係」と「同一レイヤー内でのノードの並び順(move_child)」の2段階になっていることが分かりました。この考え方は今後さらに複雑なUIを組む際にも応用できます。

次回はインベントリのアイテムを使って、複数アイテムの組み合わせや謎解きギミックへの組み込みを実装していきます。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?