ゲームエンジンGodot4.0で3Dスマホゲームを作りたいと思いますが、その前にお勉強しています。
2023/3/1にstable版がリリースされました。
Godot_v4.0-stable_win64.exe.zipを使用しています。
目的
ステージアイテムを増やします。
通過すると増殖するエリアをつくります。
ベースプロジェクト
下記で作成したプロジェクトをベースに機能追加をします。
【Godot 4.0】スマホ3Dゲームを作るための勉強 その24 Androidアプリケーションとしてエクスポートする・調整する
https://qiita.com/FootInGlow/items/2f3fc2e60b8ddf9e7594
github(Godotのプロジェクトマネージャーからインポートして利用できます)
https://github.com/footinglow/Godot4/tree/main/02_study/S24_AndroidAppExport
方法
Bulletが通過すると増殖するエリアMultiplicationAreaを作成します。
Area3Dでエリアを作成します。倍率をLabel3Dで表示しましょう。
BulletにArea3D検出用のDetecterを追加することで、MultiplicationAreaを検知します。
MultiplicationAreaシーンを作成する
シーンメニューから新規シーンを選択して、「その他のノード」ボタンを押下します。
Area3Dを選択します。
名称を「MultiplicationArea」に変更します。
StageItemsの中に保存しましょう。
res://StageItems/multiplication_area.tscnが追加されます。
直方体を設定する
「MultiplicationArea」を右クリックして、子ノードを追加からMeshInstance3Dを追加します。
MeshInstance3Dを選択した状態で、インスペクタのMeshInstance3D/Meshの<空>をクリックして、「新規BoxMesh」を選択します。
設定された立方体アイコンをクリックして詳細設定を表示して、Sizeのzに0.3mを設定します。
またNode3D/Transform/Positionのyに0.5mを設定します。
透明の青にする
Meshの詳細設定を開いている状態で、Materialの右の<空>をクリックして新規StandardMaterial3Dを設定します。
Materialに設定された球をクリックしてStandardMaterial3Dの詳細設定を表示して、Albedoを開きます。
白い長方形をクリックしてカラーピッカーを表示して青い色(R=0,G=0,B=255)にします。
またA(Alpha)に透明度を設定しましょう。A=128にしました。0が透明、255が非透明なので真ん中の値にしました。
透明設定を有効にします。
albedoの設定の3つ上の「Transparency」を開いて、Transparencyを「Alpha]に設定すると、Alpha値が有効になり見た目が半透明になります。
倍率を表示する
「MultiplicationArea」を右クリックして、子ノードを追加からLabel3Dを追加します。
MeshInstance3Dを選択した状態で、インスペクタのLabel3D/textに「x2」と文字列を入力して、Font Sizeを50pxにします。
Node3D/Transpose/Positionのyを1mにします。
Node3D/Transpose/Rotationのxを-90°にします。
ここを通り過ぎたら2倍に増えそうです。
MeshInstance3Dと同じ大きさ・位置のCollisionShape3Dを追加する
「MultiplicationArea」を右クリックして、子ノードを追加からCollisionShape3Dを追加します。
CollisionShape3Dを選択した状態で、インスペクタのCollisionShape3D/Shapeの右の<空>をクリックして新規BoxShape3Dを設定します。
Shapeの右に設定された「BoxShape3D」をクリックして詳細設定を表示して、Sizeのzを0.3mにします。
Node3D/Transform/Positionのyを0.5mにします。
検出されるための設定をする
MultiplicationAreaが検出されるための設定をします。
MultiplicationArea専用のCollision Layerを追加します。
プロジェクトメニューからプロジェクト設定を開いて、「Layer Names/3D物理」のLayer 5に「MultiplicationArea」を設定します。
res://StageItems/multiplication_area.tscnを開きます。
ルートノードMultiplicationAreaを選択して、インスペクタのCollisionObject3D/Collisionを開きます。
検出されたい側なので、Layerの5:MultiplicationAreaを有効にします。
検出することはないので、Maskは未選択にします。
Bulletを分裂する
MultiplicationAreaを通過するとBulletを追加して、分裂したように見えるようにします。
MultiplicationArea検出用にArea3Dを追加する
res://Characters/bullet.tscnを開きます。
ルートノードのBulletを右クリックして、子ノードを追加からArea3Dを選択します。
名称を「MultiplicationAreaDetecter」にします。
Bullet本体と同じCollisionShape3DをArea3Dノードにも追加します。
ルートノードBulletの子ノードのCollisionShape3Dを右クリックしてコピーします。
MultiplicationAreaDetecterを右クリックして貼り付けをします。
大きさ、位置を確認します。
MultiplicationAreaDetecterを選択して、インスペクターの「CollisionObject3D/Collistion」を開きます。
検出される必要はなのでLayerは未選択です。
MultiplicationAreaを検出したいので、Maskの5:MultiplicationAreaにチェックを入れます。
MultiplicationAreaDetecterを選択して、インスペクターの横のノード/シグナルを選択します。
今回はArea3Dを検出したいので、area_entered(area:Area3D)を右クリックして「接続」を実行します。
Bulletを選択して、受信側メソッド名を確認して「接続」ボタンを押下します。
res://Characters/bullet.gdに「func _on_multiplication_area_detecter_area_entered(area):」が追加されました。
Bulletの分身を作る
bullet.gdに追加された_on_multiplication_area_detecter_area_entered(area)を下記のように実装します。
func _on_multiplication_area_detecter_area_entered(area):
var avater = duplicate()
avater.transform.origin += Vector3(0.1, 0, 0)
add_sibling(avater)
-
var avater = duplicate()
今まではPackedSceneからinstantiateして生成していましたが、今回は自分自身を複製するのでduplicateメソッドを使用します。
Node3D/Transform/Positionなどはコピーされていそうですが、GDScript内の変数は初期化されていそうです。
Node.duplicate()のヘルプを見ると、Object._init()を使用している場合うまく動かないようです
-
avater.transform.origin += Vector3(0.1, 0, 0.1)
まったく同じところにPhysicsBody3Dを追加するとどっかに飛んで行ってしまうため、左右と前後方向に少しずらして追加します。 -
add_sibling(avater)
増殖元のBulletと同じ階層に追加します。
ステージにMultiplicationAreaDetecterを追加する
res://Stages/stage001.tscnを開きます。
Stageを右クリックして、Instantiate Child Sceneを実行します。
「StageItems/multiplication_area.tscn」を開きます。
追加されました。
MultiplicationAreaDetecterを選択した状態で、インスペクターのNode3D/Transform/Positionのzを-3mに設定ます。
実行します。BulletがMultiplicationAreaを通過すると2倍以上にたくさん増えましたね。
これは増殖したBulletがMultiplicationAreaを検出して増殖するのを繰り返しているためです。
一度通過したMultiplicationAreaを記憶して、2回目以降は増えないようにします。
一度通過したMultiplicationAreaには反応しないようにする
ノードはそれぞれ、インスタンスIDというユニークなIDを持っているので、一度追加したMultiplicationAreaのインスタンスIDを記憶しておいて、通過済みを判定できるようにします。分裂したBulletにも反映します。
res://Characters/bullet.gdを開き、辞書を追加します。
# 通過したMultiplicationAreaを記憶するための辞書
var m_dict_multiarea_ids = {}
_on_multiplication_area_detecter_area_enteredメソッドを下記のように修正します。
func _on_multiplication_area_detecter_area_entered(area):
if m_dict_multiarea_ids.has(area.get_instance_id()):
pass
else:
m_dict_multiarea_ids[area.get_instance_id()] = true
var avater = duplicate()
avater.m_dict_multiarea_ids = m_dict_multiarea_ids.duplicate(true)
avater.transform.origin += Vector3(0.1, 0, 0)
add_sibling(avater)
-
if m_dict_multiarea_ids.has(area.get_instance_id()):
「area.get_instance_id()」でオブジェクト固有のインスタンスIDが取得できます。
hasメソッドでm_dict_multiarea_ids辞書に「area.get_instance_id()」がある場合は、すでに通過したMultiplicationAreaと判断します。 -
m_dict_multiarea_ids[area.get_instance_id()] = true
初めて通過するMultiplicationAreaをキーに辞書に登録します。キーに対応する値は何でもよいのですが、trueとしています。 -
avater.m_dict_multiarea_ids = m_dict_multiarea_ids.duplicate(true)
分身の辞書は初期値なので、オリジナルのm_dict_multiarea_idsをコピーします。
GDScript(によらず)辞書は参照コピーになるため、duplicate(true)で(深い)コピーを作成して渡します。
実行します。今度はひとりがふたりになります。
また実装オリジナルの座標は変更していませんが、分裂したBulletと衝突して押し合うのでオリジナルも左に移動します。
MultiplicationAreaの倍率をインスペクターで設定できるようにする
MultiplicationAreaの倍率をインスペクタで変更できるようにします。
res://StageItems/multiplication_area.tscnを開きます。
MultiplicationAreaを右クリックして、「スクリプトをアタッチ」を実行します。
StageItemsディレクトリ配下を確認して保存します。
「res://StageItems/multiplication_area.gd」が追加されました。
下記のように、倍率の変数を追加してexportします。
またシーン生成時に呼ばれる_ready()メソッドでLabel3Dに反映します。
extends Area3D
@export var m_i_magnification = 2.0 # 倍率
func _ready():
$Label3D.text = "x%d" % m_i_magnification
-
"x%d" % m_i_magnification
%dが整数です。m_i_magnificationの値に置換されます。
インスペクタに「M I MAgnification」として「2」が表示されました。
x3の倍率のMultiplicationAreaをステージに追加します。
res://Stages/stage001.tscnを開きます。
Stageを右クリックして、Instantiate Child Sceneを実行します。
「StageItems/multiplication_area.tscn」を開きます。
シーン内のMultiplicationAreaはMultiplicationArea_x2に名称変更します。
MultiplicationArea2はMultiplicationArea_x3に名称変更します。
MultiplicationArea_x2を選択した状態で、インスペクタのmultiplication_area.gd/M I Magnificationが「2」であることを確認します。
MultiplicationArea_x3を選択した状態で、インスペクタのmultiplication_area.gd/M I Magnificationを「3」に設定します。
位置を移動します。
MultiplicationArea_x3を選択した状態で、Node3D/Transform/PositionのXを0.5m、zを-5mにします。
実行します。
新たに追加したほうは「x3」と表示されていますね。
MultiplicationAreaの倍率に従って増殖するようにする
res://Characters/bullet.gdを開いて、_on_multiplication_area_detecter_area_enteredメソッドを修正します。
func _on_multiplication_area_detecter_area_entered(area):
if m_dict_multiarea_ids.has(area.get_instance_id()):
pass
else:
# 倍率2の場合1人、倍率2倍の場合ふたりと倍率-1人増える
var i_add_num = area.m_i_magnification - 1
var d_add_x = -0.15
var d_add_z = 0.1
for i in range(i_add_num):
m_dict_multiarea_ids[area.get_instance_id()] = true
var avater = duplicate()
avater.m_dict_multiarea_ids = m_dict_multiarea_ids.duplicate(true)
avater.transform.origin += Vector3(d_add_x, 0, d_add_z)
add_sibling(avater)
d_add_z = -d_add_z
d_add_x += 0.1
-
var i_add_num = area.m_i_magnification - 1
通過したMultiplicationAreaのm_i_magnificationを読みだしています。
増殖する人数は倍率ー1(2倍なら1名、5倍なら4名)をi_add_numに設定します。 -
var d_add_x = -0.15 ,var d_add_z = 0.1
増殖するときに少しずつずれた場所に追加するためのパラメータ初期値です。 -
for i in range(i_add_num):
i_add_num分繰り返します。iは未使用です。 -
avater.transform.origin += Vector3(d_add_x, 0, d_add_z)
d_add_x、d_add_zで増殖したBulletの位置をずらしています。 -
d_add_z = -d_add_z, d_add_x += 0.1
追加するたびに少しずつ位置をずらしています。
d_add_zは+0.1,-1.0を繰り返して前後方向をずらしています。
d_add_xは初期値-0.15から0.1ずつ右にずれていきます。
実行しましょう。
x2の通過すると一人が二人に、x3を通過すると一人が3人に増殖します。
res://Characters/bullet.gd全体は下記のようになりました。
extends CharacterBody3D
@export var m_v_dir = Vector3.FORWARD
@export var m_d_speed_mps = 2.0
@export var m_attack_power_ps = 1.0
@export var m_hp = 1
var m_v3_sensing_pos = null
var m_v3_enemy_pos = null
# 通過したMultiplicationAreaを記憶するための辞書
var m_dict_multiarea_ids = {}
func _physics_process(delta):
if m_v3_enemy_pos:
m_v_dir = m_v3_enemy_pos - transform.origin
else:
m_v_dir = Vector3.FORWARD
velocity = m_v_dir.normalized() * m_d_speed_mps
move_and_slide()
# バトル判定
var battle_pos = null
for index in range(get_slide_collision_count()):
var collision = get_slide_collision(index)
if (collision.get_collider() == null):
continue
if collision.get_collider().is_in_group("EnemyGroup"):
var enemy = collision.get_collider()
# バトル中に相手の位置を保持
battle_pos = enemy.transform.origin
# Bulletの攻撃
enemy.m_hp -= m_attack_power_ps * delta
# Enemyの攻撃
m_hp -= enemy.m_attack_power_ps * delta
# HPが0になったら消える
if m_hp < 0:
queue_free()
# アニメーションの設定
if battle_pos:
$DesignMySoldier.animation_play("Battle")
else:
$DesignMySoldier.animation_play("Walk")
# バトル中の相手、センシングした敵の順に目標位置を設定する
if battle_pos:
m_v3_enemy_pos = battle_pos
elif m_v3_sensing_pos:
if (m_v3_sensing_pos - transform.origin).length() < 0.1:
m_v3_sensing_pos = null
m_v3_enemy_pos = m_v3_sensing_pos
else:
m_v3_enemy_pos = null
func discover_enemy(enemy_pos):
m_v3_sensing_pos = enemy_pos
func _on_visible_on_screen_notifier_3d_screen_exited():
queue_free()
func _on_multiplication_area_detecter_area_entered(area):
if m_dict_multiarea_ids.has(area.get_instance_id()):
pass
else:
# 倍率2の場合1人、倍率2倍の場合ふたりと倍率-1人増える
var i_add_num = area.m_i_magnification - 1
var d_add_x = -0.15
var d_add_z = 0.1
for i in range(i_add_num):
m_dict_multiarea_ids[area.get_instance_id()] = true
var avater = duplicate()
avater.m_dict_multiarea_ids = m_dict_multiarea_ids.duplicate(true)
avater.transform.origin += Vector3(d_add_x, 0, d_add_z)
add_sibling(avater)
d_add_z = -d_add_z
d_add_x += 0.1
以上