この記事はGPT先生と相談しながら書いています。
1.ファイル構成
Roguelike_Test_v0_1
+-- main.tscn/.gd:ゲームのメインシーンとそのスクリプト。
+-- assets:ゲームで使用される各種アセット。
| (地形チップ、マップ画像、プレイヤーチップなど)
+-- src
+-- common
| +-- gamesettings.gd:ゲーム全体の設定や定数を定義するスクリプト。
+-- dungeon
| +-- dungeon.tscn/.gd:ダンジョン生成のメインロジックとシーン。
| +-- dungeon_map.tscn/.gd:ダンジョンマップの表示を扱うシーンとスクリプト。
| +-- dungeon_rooms.tscn/.gd:ダンジョンの部屋配置を管理するシーンとスクリプト。
| +-- mappin.tscn/.gd:マップ上のピンやプレイヤー位置を示すシーンとスクリプト。
+-- player
+-- player.tscn/.gd:プレイヤーの表示と動作を定義するシーンとスクリプト。2.Playerシーン

衝突判定とかはしてないのでSprite2Dノードを作成し、キャラクター画像を設定しているくらいです。
インスペクターはCenteredのオンを解除しているくらいです。
Centeredがオンだと画像の中心が(0,0)の座標になります。
オフだと左上が(0,0)の座標になります。
extends Node2D
@onready var player = $Sprite2D
# プレイヤー配置時の調整分
const POS_ADD_WIDTH = 4
const POS_ADD_HEIGHT = 4
# プレイヤーの座標(グリッド)
var _player_grid_pos = Vector2(0, 0)
func _ready():
# 初期処理
_player_grid_pos = Gamesettings.PLAYER_START_POS
func proc() -> void:
# プレイヤーの表示
_dsp_player()
func _dsp_player():
# プレイヤーの表示処理(座標を変えるだけ)
# 最後の+5はMainノード上のDungeonノードの座標
position.x = _player_grid_pos.x * Gamesettings.TILE_SIZE + POS_ADD_WIDTH
position.y = _player_grid_pos.y * Gamesettings.TILE_SIZE + POS_ADD_HEIGHT
func _on_player_move(pos:Vector2):
# 移動先の座標を設定する
_player_grid_pos = pos
func get_player_grid_pos() -> Vector2:
# プレイヤーの座標を返す
return _player_grid_pos
GDScript_on_player_moveで座標(マス目上での位置)を設定し、_dsp_playerではマス目上の座標をpixelの座標に変換してノードを表示しています。
_on_player_moveはイベントで呼ばれるます。
3.Mainシーン

Mainシーンでは、イベントやゲームの流れを処理する予定です。
DungeonシーンとPlayerシーンを子ノードとして持っています。
extends Node2D
@onready var player = $Player
@onready var dungeon = $Dungeon
signal check_action_possibility(pos:Vector2) # プレイヤーのとるべき行動を確認する
var _current_state # 現在のゲームの状態
# Called when the node enters the scene tree for the first time.
func _ready():
# イベントの接続
# プレイヤーのアクションを確認
self.connect("check_action_possibility", Callable(dungeon, "_on_check_action_possibility"))
# プレイヤーを移動させるとき
dungeon.connect("player_move", Callable(player, "_on_player_move"))
# ゲームの状態の設定。(とりあえずプレイ状態)
_current_state = Gamesettings.GameState.PLAYING
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
# 入力の受付
_handle_input()
# ダンジョンの表示
dungeon.proc()
# プレイヤー移動処理
player.proc()
func _handle_input():
# イベントなどを受け取り、各種操作を行う
# ゲームの状態毎の入力受付
if _current_state == Gamesettings.GameState.PLAYING:
# ゲームプレイ中
# 方向キー:プレイヤーの移動または、アクションを行う
var playerpos = player.get_player_grid_pos() # プレイヤーの座標を取得
if Input.is_action_just_pressed("ui_left"):
playerpos = playerpos + Vector2(-1, 0)
emit_signal("check_action_possibility", playerpos)
elif Input.is_action_just_pressed("ui_up"):
playerpos = playerpos + Vector2(0, -1)
emit_signal("check_action_possibility", playerpos)
elif Input.is_action_just_pressed("ui_right"):
playerpos = playerpos + Vector2(1, 0)
emit_signal("check_action_possibility", playerpos)
elif Input.is_action_just_pressed("ui_down"):
playerpos = playerpos + Vector2(0, 1)
emit_signal("check_action_possibility", playerpos)
GDScript- _ready()
- シグナルの接続とゲーム状態の初期設定を行っている。
- _process(delta)
- フレーム毎に実行されます。DungeonシーンとPlayerシーンの毎時処理を実行しています。
各フレームの_process(delta)を使うと同期とかとるのが面倒そうなので、すべてMainシーンからの実行にしています。
- フレーム毎に実行されます。DungeonシーンとPlayerシーンの毎時処理を実行しています。
- _handle_input()
- キー入力などを受け取り処理を実行する。
今は矢印キーを押すとプレイヤーが動くという処理しか行っていません。
- キー入力などを受け取り処理を実行する。
4.プレイヤーの移動
矢印キーが押されるとプレイヤーが移動します。
- 処理としては。以下の流れになっています。
- Mainシーンでキー入力の受付。
- MainシーンからDungeonシーンに移動先の処理を行うシグナルの発行
- Dungeonシーンで移動先の処理を行いPlayerシーンに移動指示のシグナルを発行
- Playerシーンで座標を移動先に変更
シグナルの宣言は、シグナルを発行する所で行います。
signal check_action_possibility(pos:Vector2) # プレイヤーのとるべき行動を確認するGDScriptsignal player_move(pos:Vector2) # プレイヤーを移動させるGDScript接続はMainシーンで行いました。
宣言もすべて同じ場所で行いたかったのですがダメでした。
func _ready():
# イベントの接続
# プレイヤーのアクションを確認
self.connect("check_action_possibility", Callable(dungeon, "_on_check_action_possibility"))
# プレイヤーを移動させるとき
dungeon.connect("player_move", Callable(player, "_on_player_move"))GDScript_handle_input()でキー入力を受付、シグナルを発行し移動先の座標を渡します。
func _handle_input():
# イベントなどを受け取り、各種操作を行う
# ゲームの状態毎の入力受付
if _current_state == Gamesettings.GameState.PLAYING:
# ゲームプレイ中
# 方向キー:プレイヤーの移動または、アクションを行う
var playerpos = player.get_player_grid_pos() # プレイヤーの座標を取得
if Input.is_action_just_pressed("ui_left"):
playerpos = playerpos + Vector2(-1, 0)
emit_signal("check_action_possibility", playerpos)
elif Input.is_action_just_pressed("ui_up"):
playerpos = playerpos + Vector2(0, -1)
emit_signal("check_action_possibility", playerpos)
elif Input.is_action_just_pressed("ui_right"):
playerpos = playerpos + Vector2(1, 0)
emit_signal("check_action_possibility", playerpos)
elif Input.is_action_just_pressed("ui_down"):
playerpos = playerpos + Vector2(0, 1)
emit_signal("check_action_possibility", playerpos)
GDScript_on_check_action_possibility(pos)では移動先の座標を確認し、アクションを実行します。
現状は、何もなければプレイヤーの移動、部屋の出口だった場合は現在の部屋を移動先の部屋に変更しつながっている出口の横にプレイヤーを移動します。
プレイヤーの移動はシグナルを発行し移動先の座標を渡します。
func _on_check_action_possibility(pos):
# プレイヤーノードから発行されたシグナルを受ける
# プレイヤーが移動しようとしたときに発行される
# 移動可能ならプレイヤーノードへシグナルを発行しプレイヤーを移動させる
var tmppos : Vector2
# 座標が有効か確認
if pos.x < 0 or pos.x >= Gamesettings.ROOM_WIDTH \
or pos.y < 0 or pos.y >= Gamesettings.ROOM_HEIGHT:
return
# 移動先の確認
var next_tile = dungeonrooms.get_object_from_room(pos)
if next_tile == Gamesettings.RoomObj.NON:
# 何もなければ移動する
# プレイヤーノードにシグナルを発行、移動先に移動
emit_signal("player_move", pos)
elif next_tile >= Gamesettings.RoomObj.EXITL and next_tile <= Gamesettings.RoomObj.EXITD:
# 出口だったら部屋を移動する
dungeonrooms.change_room(next_tile - 2)
# プレイヤーノードにシグナルを発行、隣の部屋での座標に移動させる
if next_tile - 2 == Gamesettings.Direction.LEFT:
tmppos = Vector2(9, 4)
elif next_tile - 2 == Gamesettings.Direction.UP:
tmppos = Vector2(5, 7)
elif next_tile - 2 == Gamesettings.Direction.RIGHT:
tmppos = Vector2(1, 4)
elif next_tile - 2 == Gamesettings.Direction.DOWN:
tmppos = Vector2(5, 1)
emit_signal("player_move", tmppos)
GDScript_on_player_move(pos:Vector2)では渡された座標を設定するだけです。
func _on_player_move(pos:Vector2):
# 移動先の座標を設定する
_player_grid_pos = pos
GDScriptイベント関係は結構ハマりました。
宣言の場所とか接続のし方とかがうまくいかず悩みました。
出来てみると、どこでそんなに悩んのだのか、あまり複雑な感じはしないような・・・
5.終わりに
とりあえず、今回作成したプロジェクトはここまでになります。
次はモンスターの追加あたりですかね?モンスターを追加したら戦闘も行いたいのでステータスとかも考えないダメですね。
ちなみにこのプロジェクトは今年の正月休みから始めました。
プログラムを作成し、記事を書くまでに2か月近くかかっています・・・
完成までたどり着けるのか不安ですね。
プロジェクトはGithubにアップロードしています。
- ライセンスについて
- 本プロジェクトは [MITライセンス](LICENSE) の下で公開されています。
- 使用しているフリー素材
- このプロジェクトにはKenney (https://kenney.nl/) から提供されたアセットが含まれています。これらのアセットはCreative Commons Zero (CC0) ライセンスの下で提供されています。
これらの素晴らしいリソースを提供してくださったクリエイターの皆様に感謝します。
