2Dトップダウン脱出ゲームチュートリアル Part 3: ダイアログとオブジェクトの作成と インタラクション
Godot Engine バージョン4.3
Godot 4.3を使って、2Dトップダウン脱出ゲームのオブジェクトを作成します。会話用のダイアログの実装、またStaticBody2Dを活用し、見る・拾う・開ける・ロック解除などのインタラクションを可能にするスクリプトを実装します。
このパートでは、プレイヤーがゲーム内でインタラクト可能なオブジェクト(アイテム、扉、本棚など)を作成し、それらと「見る」「拾う」「開ける」「ロックを解除する」といった動作を実装します。
またダイアログの実装は別のチュートリアルで実装可能です。
こちらから実装してください。
1. オブジェクトのノード構成
作成するオブジェクトは
StaticBody2D
を基盤にし、以下の構成で作成します:StaticBody2D
: 衝突判定を持つ静的なオブジェクト。
CollisionShape2D
: 衝突判定用の形状を設定。
Sprite2D
: 見た目を表示するためのスプライト。
また、インタラクションの仕組みを追加するために、カスタムスクリプト
object.gd
を適用します。
例えばミラーの構成は下記になります。
このようにオブジェクトを作成してどんどんマップに配置していきます。
2. オブジェクトの作成
(1) Godotエディタでのオブジェクト作成手順
新しいシーンを作成し、ルートノードとして
StaticBody2D
を選択。StaticBody2D
の子ノードとして以下を追加:
CollisionShape2D
(矩形または円形の形状を設定)
Sprite2D
(オブジェクトの画像を設定)
object.gd
をStaticBody2D
にアタッチ。type
を適切なインタラクションタイプ(LOOKABLE, PICKABLE, OPENABLE, LOCKED)に設定する。
3. オブジェクトのスクリプト (object.gd
)
以下のスクリプトでは、オブジェクトの種類に応じたカスタムプロパティを
Inspector
に追加し、ゲーム中のインタラクションを制御します。(1) スクリプトの概要
@tool
を使用してInspector上でカスタムプロパティを動的に変更可能にする。ObjectType
をenum
で定義し、オブジェクトのタイプを管理する。_get_property_list()
でオブジェクトのタイプに応じたカスタムプロパティをInspectorに追加。text_content
, is_pickable
, is_open
, is_locked
などのプロパティでオブジェクトの状態を管理。(2) スクリプト本体
@toolextends Node2Dclass_name Interactable
# オブジェクトタイプの列挙型を定義var ObjectTypeKeys = ObjectType.keys()var ObjectTypeList = ",".join(ObjectTypeKeys)enum ObjectType { ## 見るオブジェクト用 LOOKABLE, ## 開けたり閉めたりするオブジェクト用 OPENABLE, ## アイテムを取得できるオブジェクト用 PICKABLE, ## ロックされているオブジェクト用 LOCKED }
# プロパティのエクスポート@export var type:ObjectType: set(value): type = value notify_property_list_changed() # プロパティ変更時に通知を送る##一度だけ対話可能かどうか@export var only_once:bool## テキストコンテンツを格納var text_content:Array[String] = []## アイテムが取得した時に削除するかどうかvar is_pickable:bool## オブジェクトが開いているかどうかvar is_open:bool## インベントリアイテム情報var item:InventoryItem## アイテムアイコンの表示位置var show_item_icon_pos:int## 開いた後のテキストコンテンツvar text_opened_content:Array[String] = []## ロック解除に必要なアイテムIDvar unlock_item_id:int## オブジェクトがロックされているかどうかvar is_locked:bool
var is_talked_done = false # すでに対話済みかどうか
# カスタムプロパティリストの取得func _get_property_list() -> Array: var properties: Array = [] if type == ObjectType.LOOKABLE: properties.append({ "name": "LOOKABLE DETAIL", "type": TYPE_NIL, "usage": PROPERTY_USAGE_CATEGORY # LOOKABLEタイプのカテゴリを追加 }) properties.append({ "name": "text_content", "type": TYPE_ARRAY, "usage": PROPERTY_USAGE_DEFAULT, "hint": PROPERTY_HINT_MULTILINE_TEXT, "hint_string": "%s/%s:String" % [TYPE_STRING, PROPERTY_HINT_MULTILINE_TEXT] }) if type == ObjectType.PICKABLE: properties.append({ "name": "PICKABLE DETAIL", "type": TYPE_NIL, "usage": PROPERTY_USAGE_CATEGORY # PICKABLEタイプのカテゴリを追加 }) properties.append({ "name": "is_pickable", "type": TYPE_BOOL, "usage": PROPERTY_USAGE_DEFAULT }) properties.append({ "name": "item", "type": TYPE_OBJECT, "usage": PROPERTY_USAGE_DEFAULT, "hint": PROPERTY_HINT_RESOURCE_TYPE, "hint_string": "InventoryItem", }) properties.append({ "name": "text_content", "type": TYPE_ARRAY, "usage": PROPERTY_USAGE_DEFAULT, "hint": PROPERTY_HINT_MULTILINE_TEXT, "hint_string": "%s/%s:String" % [TYPE_STRING, PROPERTY_HINT_MULTILINE_TEXT] }) properties.append({ "name": "show_item_icon_pos", "type": TYPE_INT, "usage": PROPERTY_USAGE_DEFAULT }) if type == ObjectType.OPENABLE: properties.append({ "name": "OPENABLE DETAIL", "type": TYPE_NIL, "usage": PROPERTY_USAGE_CATEGORY # OPENABLEタイプのカテゴリを追加 }) properties.append({ "name": "text_content", "type": TYPE_ARRAY, "usage": PROPERTY_USAGE_DEFAULT, "hint": PROPERTY_HINT_MULTILINE_TEXT, "hint_string": "%s/%s:String" % [TYPE_STRING, PROPERTY_HINT_MULTILINE_TEXT] }) properties.append({ "name": "is_open", "type": TYPE_BOOL, "usage": PROPERTY_USAGE_DEFAULT }) if type == ObjectType.LOCKED: properties.append({ "name": "LOCKED DETAIL", "type": TYPE_NIL, "usage": PROPERTY_USAGE_CATEGORY # LOCKEDタイプのカテゴリを追加 }) properties.append({ "name": "is_locked", "type": TYPE_BOOL, "usage": PROPERTY_USAGE_DEFAULT }) properties.append({ "name": "unlock_item_id", "type": TYPE_INT, "usage": PROPERTY_USAGE_DEFAULT }) properties.append({ "name": "text_content", "type": TYPE_ARRAY, "usage": PROPERTY_USAGE_DEFAULT, "hint": PROPERTY_HINT_MULTILINE_TEXT, "hint_string": "%s/%s:String" % [TYPE_STRING, PROPERTY_HINT_MULTILINE_TEXT] }) properties.append({ "name": "text_opened_content", "type": TYPE_ARRAY, "usage": PROPERTY_USAGE_DEFAULT, "hint": PROPERTY_HINT_MULTILINE_TEXT, "hint_string": "%s/%s:String" % [TYPE_STRING, PROPERTY_HINT_MULTILINE_TEXT] }) properties.append({ "name": "show_item_icon_pos", "type": TYPE_INT, "usage": PROPERTY_USAGE_DEFAULT }) properties.append({ "name": "item", "type": TYPE_OBJECT, "usage": PROPERTY_USAGE_DEFAULT, "hint": PROPERTY_HINT_RESOURCE_TYPE, "hint_string": "InventoryItem", }) return properties
このようにカスタムプロパティを設定することで、
type
によってInspector
を更新することが可能です。無駄なパラメータを表示しないで済むので綺麗に整理できます。
詳しくは下記のチュートリアルで確認できます。
プレイヤーのスクリプトにインタラクション用のアクションを追加します。
# 「アクション」ボタンが押され、インタラクト可能なオブジェクトがある場合の処理 if Input.is_action_just_pressed("action") && object_interactable && !dialog.is_dialog_mode: if object_interactable.only_once && object_interactable.is_talked_done: return # 一度だけ会話可能なオブジェクトは、会話済みなら処理しない else: object_interactable.is_talked_done = true # 会話済みに設定
# オブジェクトの種類ごとの処理 if object_interactable.type == Interactable.ObjectType.OPENABLE: object_interactable.is_open = !object_interactable.is_open # 開閉状態を切り替え if object_interactable.is_open: object_interactable.get_children()[0].hide() object_interactable.get_children()[1].show() dialog.text_to_display = object_interactable.text_content dialog.start_dialog() else: object_interactable.get_children()[0].show() object_interactable.get_children()[1].hide() elif object_interactable.type == Interactable.ObjectType.PICKABLE: # 取得可能なオブジェクトの処理 dialog.object_icon = object_interactable.item.item_icon dialog.show_object_icon_pos = object_interactable.show_item_icon_pos dialog.text_to_display = object_interactable.text_content dialog.cb_func = pickup dialog.start_dialog() elif object_interactable.type == Interactable.ObjectType.LOOKABLE: # 見ることができるオブジェクトの処理 if object_interactable.text_content.size() > 0: dialog.text_to_display = object_interactable.text_content dialog.start_dialog() elif object_interactable.type == Interactable.ObjectType.LOCKED: # ロックされたオブジェクトの処理 if inventory.has_item(object_interactable.unlock_item_id): object_interactable.is_locked = false if object_interactable.is_locked: dialog.text_to_display = object_interactable.text_content dialog.start_dialog() else: dialog.object_icon = object_interactable.item.item_icon dialog.show_object_icon_pos = object_interactable.show_item_icon_pos dialog.text_to_display = object_interactable.text_opened_content dialog.cb_func = pickup dialog.start_dialog() object_interactable.only_once = true object_interactable.is_talked_done = true
4. オブジェクトの種類の作成
見るオブジェクト(typeがLOOKABLEの場合)
このオブジェクトは、プレイヤーがインタラクションすると会話のダイアログを表示するだけのものになります。
例えばミラーのオブジェクトにインタラクションすると「自分が映っている」というダイアログが出るようにしたい場合は下記のように設定します。
開け閉めオブジェクト(typeがOPENABLEの場合)
このオブジェクトは、プレイヤーがインタラクションするとオブジェクトを開けたり閉めたりするオブジェクト用になります。
例えば冷蔵庫のオブジェクトにインタラクションすると冷蔵庫を開けて「冷蔵庫には何もない..」というダイアログが出るようにしたい場合は下記のように設定します。また再度冷蔵庫のオブジェクトにインタラクションすると冷蔵庫を閉めます。
StaticBody2D
の子ノードして閉めている画像のSprite2D
と開けている時の画像のSprite2D
を設定しておきます。この場合はデフォルトで閉めてあるので開けている時の画像のSprite2D
は非表示にしておきます。
PICKABLE, LOCKEDは次のチュートリアルのインベントリーシステムと一緒に説明します。
5. まとめ
object.gd
を適用したStaticBody2D
を作成することで、見る・拾う・開ける・ロック解除の機能を実装できる。@tool
と_get_property_list()
を活用することで、Inspector上でオブジェクトごとにカスタムプロパティを設定可能。
1
100%