前回Godotのチュートリアルにある簡単なゲームを作成しました。
今回は、Godotのプロジェクト一覧からダウンロードできるテンプレートプロジェクト「2D Role Playing Game Demo」の中身を読んでみます。
参考ページ:
- GitHub: Role Playing Game
環境:
- Godot Engine v3.2.2
ゲームの機能。
- キーボード操作でマップを徘徊
- 対面するオブジェクト毎に違うアクションが発生する
- 会話Windowが表示され会話ができる
- 対戦モードがある
学びメモ。
Gameシーン
- AnimationPlayer: 画面切り替えアニメーション
- Transition(CanvasLayer):
- ColorRect: fadeにりようする黒塗りNode
- Combat: 対戦シーン
- Exploration: 探索シーン
- Camera2D: カメラ
extends Node
# Nodeのファイルパスを紐づけるために公開変数にする
export(NodePath) var combat_screen
export(NodePath) var exploration_screen
# ファイルパスの定義
const PLAYER_WIN = "res://dialogue/dialogue_data/player_won.json"
const PLAYER_LOSE = "res://dialogue/dialogue_data/player_lose.json"
func _ready():
# Nodeの取得
exploration_screen = get_node(exploration_screen)
combat_screen = get_node(combat_screen)
# シグナルの接続 .connect(<signal_name>, <target_node>, <target_function_name>)
combat_screen.connect("combat_finished", self, "_on_combat_finished")
# $Exploration は get_node( "Exploration") の短縮系
for n in $Exploration/Grid.get_children():
if not n.type == n.CellType.ACTOR:
continue
if not n.has_node("DialoguePlayer"):
continue
# モブのシグナルの接続
n.get_node("DialoguePlayer").connect("dialogue_finished", self,
"_on_opponent_dialogue_finished", [n])
# 戦闘シーン削除
remove_child(combat_screen)
# 戦闘開始
func start_combat(combat_actors):
# 探索シーン削除
remove_child($Exploration)
# fadeイン
$AnimationPlayer.play("fade")
# animation_finished のシグナル後再開
yield($AnimationPlayer, "animation_finished")
# 戦闘シーン追加
add_child(combat_screen)
# 戦闘シーン表示
combat_screen.show()
# 戦闘シーン初期化
combat_screen.initialize(combat_actors)
# fadeアウト
$AnimationPlayer.play_backwards("fade")
# 対戦相手の会話が終了
func _on_opponent_dialogue_finished(opponent):
# 対戦相手が負けていたらreturn
if opponent.lost:
return
# 対戦ペアを決めて戦闘開始
var player = $Exploration/Grid/Player
var combatents = [player.combat_actor, opponent.combat_actor]
start_combat(combatents)
# 対戦終了
func _on_combat_finished(winner, _loser):
# 対戦シーンを削除
remove_child(combat_screen)
# fadeアウト
$AnimationPlayer.play_backwards("fade")
# 探索シーン追加
add_child(exploration_screen)
# 対話シーン読み込み
var dialogue = load("res://dialogue/dialogue_player/DialoguePlayer.tscn").instance()
# 会話ファイル設定
if winner.name == "Player":
dialogue.dialogue_file = PLAYER_WIN
else:
dialogue.dialogue_file = PLAYER_LOSE
# アニメーション終了後再会
yield($AnimationPlayer, "animation_finished")
# ダイアログを表示
var player = $Exploration/Grid/Player
exploration_screen.get_node("DialogueUI").show_dialogue(player, dialogue)
# 対戦シーンクリア
combat_screen.clear_combat()
# ダイアログが終了後再会
yield(dialogue, "dialogue_finished")
# ダイアログを解放
dialogue.queue_free()
Combatシーン
- Combatants (Node2D): 戦闘員の配置View
- TurnQueue: ターンを管理
- UI (Control)
- Combatants (HboxContainer): 戦闘員のゲージ配置View
- Buttons (PanelContainer): コマンドView
- GridContainer: コマンドガイドコンテナ
- Attack (Button): Attackボタン
- Defend (Button): Defendボタン
- Flee (Button): Fleeボタン
- GridContainer: コマンドガイドコンテナ
extends Node
# 戦闘終了のシグナル
signal combat_finished(winner, loser)
# 戦闘員の型?
const Combatant = preload("res://turn_combat/combatants/Combatant.gd")
# 初期設定
func initialize(combat_combatants):
# 渡された戦闘メンバー
for combatant in combat_combatants:
combatant = combatant.instance()
if combatant is Combatant:
# CombatantsNodeに追加
$Combatants.add_combatant(combatant)
# ConbatantのHealtnNodeのdeadシグナルの接続
combatant.get_node("Health").connect("dead", self, "_on_combatant_death", [combatant])
else:
combatant.queue_free()
$UI.initialize()
$TurnQueue.initialize()
# 状態クリア
func clear_combat():
# CombatantsとUI/Combatantsの子Node解放
for n in $Combatants.get_children():
n.queue_free()
for n in $UI/Combatants.get_children():
n.queue_free()
# 戦闘を終了する
func finish_combat(winner, loser):
emit_signal("combat_finished", winner, loser)
# 戦闘で倒れた通知
func _on_combatant_death(combatant):
var winner
if not combatant.name == "Player":
winner = $Combatants/Player
else:
for n in $Combatants.get_children():
if not n.name == "Player":
winner = n
break
finish_combat(winner, combatant)
Explorationシーン
- Grass (TileMap): マス目
- Grid (TileMap): オブジェクトマップ
- Player (Node2D): プレイヤー
- Opponent (Node2D): 対戦相手
- DialoguePlayer: ダイアログ情報
- Timer: (?)
- Object (Node2D): アイテム
- DialoguePlayer: ダイアログ情報
- DialogueUI (Panel): ダイアログ
extends TileMap
# Enum定義
enum CellType { ACTOR, OBSTACLE, OBJECT }
# Nodeのファイルパス
export(NodePath) var dialogue_ui
func _ready():
# 子NodeをCellとして配置
for child in get_children():
set_cellv(world_to_map(child.position), child.type)
# Cellから対象のポーンを取得
func get_cell_pawn(cell, type = CellType.ACTOR):
for node in get_children():
if node.type != type:
continue
if world_to_map(node.position) == cell:
return(node)
# ポーンの移動要求
func request_move(pawn, direction):
var cell_start = world_to_map(pawn.position)
var cell_target = cell_start + direction
var cell_tile_id = get_cellv(cell_target)
match cell_tile_id:
-1: # 空きだったら
# 移動
set_cellv(cell_target, CellType.ACTOR)
set_cellv(cell_start, -1)
return map_to_world(cell_target) + cell_size / 2
CellType.OBJECT, CellType.ACTOR: # オブジェクトがあったら
# 移動先にあったポーン取得
var target_pawn = get_cell_pawn(cell_target, cell_tile_id)
print("Cell %s contains %s" % [cell_target, target_pawn.name])
if not target_pawn.has_node("DialoguePlayer"): # ダイアログ要素がなければ
return
# ダイアログ表示
get_node(dialogue_ui).show_dialogue(pawn, target_pawn.get_node("DialoguePlayer"))