ゲームエンジンGodot4.0で3Dスマホゲームを作りたいと思いますが、その前にお勉強しています。
2023/3/1にstable版がリリースされました。
Godot_v4.0-stable_win64.exe.zipを使用しています。
目的
ゲームシステムをつくっていきます。
ステージのクリア/失敗を判定して、GameSystemが知るところまで実装します。
ベースプロジェクト
下記で作成したプロジェクトをベースに機能追加をします。
【Godot 4.0】スマホ3Dゲームを作るための勉強 その16 ゲームシステムを作る その1
https://qiita.com/FootInGlow/items/887c1765058ffde646fa
github(Godotのプロジェクトマネージャーからインポートして利用できます)
https://github.com/footinglow/Godot4/tree/main/02_study/S16_GameSystem_001
ステージクリアを判定する
ステージ内にある敵の砦をすべて破壊することをステージクリアの条件にします。
一番簡単そうなのはGodotのグループ機能を使う方法だと思います。
今回はStageに共通のGDScriptのAPIを持たせて、GameSystemのGDScriptでチェックするようにしましょう。
ステージクリア条件対象のEnemyシーンに「TargetEnemy」グループを設定する
res://Characters/enemy.tscnを開きます。
ルートノードのEnemyノードを選択して、インスペクタの横の「ノード/グループ」を選択します。
Bullet(DesignMySoldier)との衝突判定に使用するEnemyGroupグループがすでに追加されています。
「TargetEnemy」と入力して「追加」ボタンを押下します。
Enemyシーンに「TargetEnemy」グループが追加されました。
Stage001のスクリプトでEnemyノードの数を判定する
res://Stages/stage001.tscnを開きます。
ルートノードのStageを右クリックして、「スクリプトをアタッチ」を実行します。
res://Stages/stage_normal.gdとして、Stagesフォルダの下に保存します。
Stagesフォルダに追加しました。
現在アクティブなシーンの中から「TargetEnemy」グループを持つノードを検索するのは「get_tree().get_nodes_in_group("TargetEnemy")」を使用します。
get_tree()がSceneTreeという現在アクティブなシーンツリーを取得します。
GameSystemノード配下のすべてのシーンが対象になります。
(本スクリプトがアタッチされているStageノードは、/GameSystem/CurrectStage/Stageの位置にあります)
GODOT DOC Scripting Groupsを参考にしました。
https://docs.godotengine.org/en/latest/tutorials/scripting/groups.html
確認のためにprintしてみます。
extends Node3D
func _physics_process(delta):
print(get_tree().get_nodes_in_group("TargetEnemy"))
get_nodes_in_groupはArray[Node]で現在アクティブなノードのうち”TargetEnemy”グループを持つNodeリストを返します。
実行すると最初はEnemyとEnemy2の2つのEnemyを検出しています。
[Enemy:<CharacterBody3D#29494346964>, Enemy2:<CharacterBody3D#29729228002>]
Enemy2を倒すと、1個だけになります。
[Enemy:<CharacterBody3D#29494346964>]
2つもと倒すと空のArrayが返ってきます。
[]
Nodeリスト(Array[Node])のサイズが0個になれば、ステージクリアと判断できそうです。
ステージクリアの判定はStage内(のスクリプト)で実装します。
res://Stages/stage_normal.gdを開いて下記のように修正します。
extends Node3D
func is_stage_clear():
return get_tree().get_nodes_in_group("TargetEnemy").size() == 0
-
get_tree().get_nodes_in_group("TargetEnemy").size()
size()関数はNodeリスト(Array[Node])内の要素数を取得するので、Enemyがすべて残っている場合は2,すべて倒した場合は0になります。 -
get_tree().get_nodes_in_group("TargetEnemy").size() == 0
すべて倒した場合は0==0でtrueをreturnします。
敵砦が2つ残っている場合は、2なので、2==0はfalseとなります。
GameSystemで現在のステージのステージクリアを判定する
今ステージに追加したis_stage_clear()メソッドをGameSystemから参照しましょう。
res://game_system.tscnを開きます。
ルートノードのGameSystemを右クリックして「スクリプトをアタッチ」します。
追加されました。
以下のようにしましょう。
extends Node3D
func _physics_process(delta):
if $CurrentStage/Stage.is_stage_clear():
print("Stage Clear")
「 $CurrentStage/Stage」はシーン内の「Stage」をスクリプト内にドラッグアンドドロップすると挿入されます。
実行しましょう。
敵の砦(Enemy、Enemy2)を2つとも倒すと「Stage Clear」がprint出力されました。
ステージクリア失敗を判定する
EnemyBullet(DesignEnemySoldier)がPlayerの陣地までたどり着いた場合、Playerの負けとします。
Area3DをPlayerの陣地に設定して、EnemyBullet(DesignEnemySoldier)の侵入を判定しましょう。
EnemyBullet(EnemySoldier)の侵入検知用にArea3Dを追加する
res://Stages/stage001.tscnを開きます。
ルートノードのStageを右クリックして、子ノードを追加から「Area3D」を追加します。名称を「EnemyBulletSensor」に変更します。
EnemyBulletSensorを右クリックして、子ノードを追加から「CollisionShape3D」を追加します。
「CollisionShape3D」を選択した状態で、インスペクタのCollisionShape3D/Shapeに新規BoxShapeを設定します。
今設定した「BoxShape3D」をクリックし詳細設定を開いて、Sizeのxに5m、yは2mに設定します。
EnemyBullet(DesignEnemySoldier)の侵入を検知したsignalを受信する
先ほど追加したArea3DにEnemyBullet(DesignEnemySoldier)が侵入した場合、signalを受信するようにします。
Area3Dが検知する対象を設定します。
EnemyBulletSensorを選択した状態で、インスペクタのCollisionObject3D/Collisionを設定します。
Layerは検知されたい場合に設定するため、不要です。チェックをすべて外します。
Maskは検知対象を設定します。Enemyを設定します。
つぎに、インスペクタの横のノード/シグナルを選択します。
検知したい対象はCharacterBody3Dという「body」なので、「body_entered(body:Node3D)」を右クリックして「接続」を実行します。
ステージのクリア/失敗はStageのスクリプトで判定するので、「Stage」を選択します。
「func _on_enemy_bullet_sensor_body_entered(body):」メソッドが追加されました。
signalを受信したことを覚えておくためにメンバ変数を追加します。
またGameSystemのスクリプトからアクセスできるように、APIも追加します。
extends Node3D
var m_f_is_entered_enemy_bullet = false
func is_stage_clear():
return get_tree().get_nodes_in_group("TargetEnemy").size() == 0
func is_stage_failed():
return m_f_is_entered_enemy_bullet
func _on_enemy_bullet_sensor_body_entered(body):
m_f_is_entered_enemy_bullet = true
-
var m_f_is_entered_enemy_bullet = false
EnemyBulletの侵入を検知したことを記憶するためのフラグを追加しました。
一度侵入を検知するとtrueにするフラグです。 -
func is_stage_failed():
GameSystemがステージクリア失敗を知るためのAPIを追加します。
「return m_f_is_entered_enemy_bullet」でtrue/falseのフラグを返却するだけです。 -
func _on_enemy_bullet_sensor_body_entered(body):
EnemyBulletの侵入を検知した場合、m_f_is_entered_enemy_bulletにtrueを設定します。
GameSystemがステージクリア失敗を知る
GameSystemは_physics_process()関数の中で周期的にステージクリア/失敗をチェックするようにします。
とりあえずprintで表示するようにします。
res://game_system.gdを開きます。
extends Node3D
func _physics_process(delta):
if $CurrentStage/Stage.is_stage_clear():
print("Stage Clear")
if $CurrentStage/Stage.is_stage_failed():
print("Stage Failed")
- if $CurrentStage/Stage.is_stage_failed():
stageのスクリプトに先ほど追加したis_stage_failed()がtrueの場合、printします。
実行します。EnemyBullet(EnemySoldier)がPlayerの陣地に侵入すると「Stage Failed」のprint文が大量に表示されます。
以上です