ゲームエンジンGodot4.0で3Dスマホゲームを作りたいと思いますが、その前にお勉強しています。
2023/3/1にstable版がリリースされました。
Godot_v4.0-stable_win64.exe.zipを使用しています。
目的
ゲームシステムをつくっていきます。
ステージを追加して、クリアすると次のステージに切り替わるようにします。
ベースプロジェクト
下記で作成したプロジェクトをベースに機能追加をします。
【Godot 4.0】スマホ3Dゲームを作るための勉強 その17 ゲームシステムを作る その2
https://qiita.com/FootInGlow/items/c68af46ada40f09af453
github(Godotのプロジェクトマネージャーからインポートして利用できます)
https://github.com/footinglow/Godot4/tree/main/02_study/S17_GameSystem_002
StageItemsを追加する
敵の砦以外にもStageに配置する障害物を追加します。
StaticBody3Dをルートノードとして追加します。
シーンメニューから新規シーンを実行します。「その他のノード」ボタンを押下してStaticBody3Dを選択します。名称を「RockyMountain](岩山)にしましょう。
StageItemsフォルダに保存します。rocky_mountain.tscnという名前になると思います。
岩山の土台を追加します。
RockyMountainを右クリックして子ノードを追加からMeshInstance3Dを追加します。名称を「Base」に変更します。
「Base」を選択した状態で、インスペクタのMeshInstance3D/Meshに新規BoxMeshを設定します。
MeshInstance3D/Meshの右のアイコンをクリックして詳細設定を表示します。
Sizeをx=1.0、y=0.5m、z=1.0に変更します。
Node3D/Transform/position/yに0.25mを設定します。
Materialに新規StandardMaterial3Dを設定します。
色も設定しましょう。
Materialの右の球アイコンをクリックして詳細設定を表示して、albedoを開いて、colorを設定します。
濃いグレーにしました(R,G,B=60)
次に岩山の頂上を追加します。
Baseを右クリックして子ノードを追加からMeshInstance3Dを追加します。名称を「Top」に変更します。
「Top」を選択した状態で、インスペクタのMeshInstance3D/Meshに新規BoxMeshを設定します。
MeshInstance3D/Meshの右のアイコンをクリックして詳細設定を表示します。
Sizeをx=0.5、y=0.5m、z=0.5に変更します。
Node3D/Transform/position/yに0.25mを設定します。
次に衝突形状を設定します。Base(土台)とx,zは同じサイズにします。高さyは高くします。
RockyMountainを右クリックして子ノードを追加からCollisionShape3Dを追加します。
CollisionShape3Dを選択した状態で、インスペクタのCollisionShape3D/Shapeに新規BoxShape3Dを設定します。
Node3D/Transform/position/yに0.5mを設定します。
ステージへの配置はあとでやりましょう。
ステージを追加する
res://Stages/stage001.tscnを複製して、stage002.tscn、stage003.tscnを作成します。
「stage002.tscn」という名称で保存します。
各ステージを修正します。。
stage001.tscnを修正する
stage001.tscnを開きます。
Enemyを削除して、敵の砦を1個だけにします。
stage002.tscnを修正する
stage002.tscnを開きます。
Enemyを削除して、敵の砦を1個だけにします。
Enemy2を選択して、真ん中あたりに移動しましょう。
ファイルシステムのres://StageItems/rocky_mountain.tscnを、シーン内のStageにドラッグアンドドロップします。
Node3D/Transform/position/xに0.5m、zに-4mを設定します。
Node3D/Transform/rotation/yに45°を設定します。
stage003.tscnを修正する
stage003.tscnを開きます。
ファイルシステムのres://StageItems/rocky_mountain.tscnを、シーン内のStageにドラッグアンドドロップします。
Node3D/Transform/position/zに-5mを設定します。
Node3D/Transform/rotation/yに45°を設定します。
ステージクリアで次のステージに切り替わるようにする
GameSystemのスクリプトを修正して、ステージクリアでステージが切り替わるようにします。
GameSystemシーンからStageノードを削除する
Stageシーンはスクリプトから追加・削除するように変更するため、GameSystemシーンからStageを削除します。
削除しました。
GameSystemのスクリプトを修正する
res://game_system.gdを開いて、下記のように修正しましょう。
stage001から始まり、クリアするとstage002、stage003に進み、stage003をクリアするとstage001に戻ります。
ステージクリア失敗すると同じステージのやり直しです。
extends Node3D
# ステージシーンリスト(PackedScene型)
var m_nodearray_stages = [
preload("res://Stages/stage001.tscn"),
preload("res://Stages/stage002.tscn"),
preload("res://Stages/stage003.tscn"),
]
# 現在実行中のステージ番号
var m_i_current_stage_idx = 0
func set_new_stage(idx):
# CurrentStageにあるステージをすべて削除する
var nodes = $CurrentStage.get_children()
for node in nodes:
node.queue_free()
# idx番目のステージシーンのインスタンスを生成して、CurrentStageに追加する
var new_stage = m_nodearray_stages[idx].instantiate()
$CurrentStage.add_child(new_stage)
func _ready():
set_new_stage(m_i_current_stage_idx)
func _physics_process(delta):
var nodes = $CurrentStage.get_children()
if nodes:
# nodesは1個か0個のどちらかなので、0番目を固定的に使用する
if nodes[0].is_stage_clear():
print("Stage Clear")
# ステージは0,1,2,0,1,2,0...と繰り返す
m_i_current_stage_idx = ( m_i_current_stage_idx + 1 ) % m_nodearray_stages.size()
set_new_stage(m_i_current_stage_idx)
if nodes[0].is_stage_failed():
print("Stage Failed")
set_new_stage(m_i_current_stage_idx)
スクリプトの詳細を説明します。
ステージシーンの一覧を作成する
ステージシーンの一覧を定義しています。
var m_nodearray_stages = [
preload("res://Stages/stage001.tscn"),
preload("res://Stages/stage002.tscn"),
preload("res://Stages/stage003.tscn"),
]
preload関数を使用してステージシーンを読み込み、配列に定義しています。
シーンを読み込むと、PackedScene型になります。
配列なのでm_nodearray_stages[0]、m_nodearray_stages[1]というようにアクセスできます。
ステージシーンの入れ替えメソッドの実装
func set_new_stage(idx):
# CurrentStageにあるステージをすべて削除する
var nodes = $CurrentStage.get_children()
for node in nodes:
node.queue_free()
# idx番目のステージシーンのインスタンスを生成して、CurrentStageに追加する
var new_stage = m_nodearray_stages[idx].instantiate()
$CurrentStage.add_child(new_stage)
-
var nodes = $CurrentStage.get_children()
CurrentStageノードの子ノードをすべて取得します。
すべてといってもステージシーンは削除して1個追加の繰り返しなので、
起動時直後の子ノードがない場合は、
[]
ステージを追加した後で、次のステージに進む場合、もしくはステージをやり直す場合は
[Stage:]
のように0個か1個のノードの配列がnodesに設定されます。 -
for node in nodes:
nodesにあるNode型の配列の1個ずつ処理します。nodesが空の場合は何もせずにforブロックを抜けます。 -
node.queue_free()
Nodeを削除(依頼)します。 -
var new_stage = m_nodearray_stages[idx].instantiate()
m_nodearray_stages[idx]はPackedScene型のシーンデータが格納されているので、instantiate()でインスタンスを作成します。
BulletやEnemyBulletを追加した時と同じです。 -
$CurrentStage.add_child(new_stage)
CurrentStageノードの子ノードとしてステージシーンのインスタンスを追加します。
var m_i_current_stage_idx = 0
func _ready():
set_new_stage(m_i_current_stage_idx)
- set_new_stage(m_i_current_stage_idx)
起動直後の初期設定で、新しいシーンを設定しています。
func _physics_process(delta):
var nodes = $CurrentStage.get_children()
if nodes:
# nodesは1個か0個のどちらかなので、0番目を固定的に使用する
if nodes[0].is_stage_clear():
print("Stage Clear")
# ステージは0,1,2,0,1,2,0...と繰り返す
m_i_current_stage_idx = ( m_i_current_stage_idx + 1 ) % m_nodearray_stages.size()
set_new_stage(m_i_current_stage_idx)
if nodes[0].is_stage_failed():
print("Stage Failed")
set_new_stage(m_i_current_stage_idx)
ステージシーンは削除・追加を繰り返すため、ステージシーンが存在しない場合も考慮しています。
-
func _physics_process(delta):
_physics_process()の中で毎周期判定します。 -
var nodes = $CurrentStage.get_children()
CurrentStageの子ノードをすべて取得します。 -
if nodes:
ステージシーンが存在することを確認しています。
CurrentStageの子ノードが1個以上あるとtrueになります。 -
if nodes[0].is_stage_clear():
nodesには0個以上のNode型の要素(Stageシーン)が並びますが、今回は0個か1個のどちらかなので、nodes[0]固定で使用します。
現在プレイしているステージのres://Stages/stage_normal.gdスクリプトのis_stage_clear()メソッドを利用します。 -
m_i_current_stage_idx = ( m_i_current_stage_idx + 1 ) % m_nodearray_stages.size()
ステージクリアしたので、次のステージに進む処理です。
m_i_current_stage_idxをカウントアップしますが、m_nodearray_stages.size()の余りを代入するので、0,1,2,の次は0になります。m_nodearray_stagesにはステージシーン3つ設定しているので、size()は3を返します。 -
set_new_stage(m_i_current_stage_idx)
m_i_current_stage_idxで指定した番号のステージシーンに差し替えます -
if nodes[0].is_stage_failed():
nodesには0個以上のNode型の要素(Stageシーン)が並びますが、今回は0個か1個のどちらかなので、nodes[0]固定で使用します。
現在プレイしているステージのres://Stages/stage_normal.gdスクリプトのis_stage_failed()メソッドを利用します。
* set_new_stage(m_i_current_stage_idx)
ステージクリア失敗した場合、同じステージをやり直すので、m_i_current_stage_idxの値は変更せずに同じステージシーンに差し替えをします。
実行します。
敵の砦をすべて倒すと次のシーンにいきます。
EnemyBulletがPlayerの陣地に到達すると同じステージのやり直しになります。
以上です。