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.gdStaticBody2Dにアタッチ。
typeを適切なインタラクションタイプ(LOOKABLE, PICKABLE, OPENABLE, LOCKED)に設定する。



3. オブジェクトのスクリプト (object.gd)

以下のスクリプトでは、オブジェクトの種類に応じたカスタムプロパティをInspectorに追加し、ゲーム中のインタラクションを制御します。
(1) スクリプトの概要
@tool を使用してInspector上でカスタムプロパティを動的に変更可能にする。
ObjectTypeenumで定義し、オブジェクトのタイプを管理する。
_get_property_list() でオブジェクトのタイプに応じたカスタムプロパティをInspectorに追加。
text_content, is_pickable, is_open, is_locked などのプロパティでオブジェクトの状態を管理。
(2) スクリプト本体
@tool
extends Node2D
class_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] = []
## ロック解除に必要なアイテムID
var 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上でオブジェクトごとにカスタムプロパティを設定可能。

最終更新日: 2025/02/13 02:41

コメント