0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Godot 4.0】スマホ3Dゲームを作るための勉強 その25 Bulletが増殖するエリアをつくる

Posted at

 ゲームエンジン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が追加されます。
スクリーンショット (223).png

直方体を設定する

 「MultiplicationArea」を右クリックして、子ノードを追加からMeshInstance3Dを追加します。
 MeshInstance3Dを選択した状態で、インスペクタのMeshInstance3D/Meshの<空>をクリックして、「新規BoxMesh」を選択します。
 設定された立方体アイコンをクリックして詳細設定を表示して、Sizeのzに0.3mを設定します。
 またNode3D/Transform/Positionのyに0.5mを設定します。
 スクリーンショット (224).png

透明の青にする

 Meshの詳細設定を開いている状態で、Materialの右の<空>をクリックして新規StandardMaterial3Dを設定します。
スクリーンショット (225).png
 Materialに設定された球をクリックしてStandardMaterial3Dの詳細設定を表示して、Albedoを開きます。
 白い長方形をクリックしてカラーピッカーを表示して青い色(R=0,G=0,B=255)にします。
 またA(Alpha)に透明度を設定しましょう。A=128にしました。0が透明、255が非透明なので真ん中の値にしました。
スクリーンショット (226).png
 透明設定を有効にします。
 albedoの設定の3つ上の「Transparency」を開いて、Transparencyを「Alpha]に設定すると、Alpha値が有効になり見た目が半透明になります。
 スクリーンショット (228).png

倍率を表示する

 「MultiplicationArea」を右クリックして、子ノードを追加からLabel3Dを追加します。
 MeshInstance3Dを選択した状態で、インスペクタのLabel3D/textに「x2」と文字列を入力して、Font Sizeを50pxにします。
 Node3D/Transpose/Positionのyを1mにします。
 Node3D/Transpose/Rotationのxを-90°にします。
スクリーンショット (230).png
スクリーンショット (233).png

 ここを通り過ぎたら2倍に増えそうです。

MeshInstance3Dと同じ大きさ・位置のCollisionShape3Dを追加する

 「MultiplicationArea」を右クリックして、子ノードを追加からCollisionShape3Dを追加します。
 CollisionShape3Dを選択した状態で、インスペクタのCollisionShape3D/Shapeの右の<空>をクリックして新規BoxShape3Dを設定します。
 Shapeの右に設定された「BoxShape3D」をクリックして詳細設定を表示して、Sizeのzを0.3mにします。
 Node3D/Transform/Positionのyを0.5mにします。
スクリーンショット (234).png

検出されるための設定をする

 MultiplicationAreaが検出されるための設定をします。
 MultiplicationArea専用のCollision Layerを追加します。

 プロジェクトメニューからプロジェクト設定を開いて、「Layer Names/3D物理」のLayer 5に「MultiplicationArea」を設定します。
スクリーンショット (237).png

 res://StageItems/multiplication_area.tscnを開きます。
 ルートノードMultiplicationAreaを選択して、インスペクタのCollisionObject3D/Collisionを開きます。
 検出されたい側なので、Layerの5:MultiplicationAreaを有効にします。
 検出することはないので、Maskは未選択にします。
スクリーンショット (238).png

Bulletを分裂する

 MultiplicationAreaを通過するとBulletを追加して、分裂したように見えるようにします。

MultiplicationArea検出用にArea3Dを追加する

 res://Characters/bullet.tscnを開きます。
 ルートノードのBulletを右クリックして、子ノードを追加からArea3Dを選択します。
 名称を「MultiplicationAreaDetecter」にします。

 Bullet本体と同じCollisionShape3DをArea3Dノードにも追加します。
 ルートノードBulletの子ノードのCollisionShape3Dを右クリックしてコピーします。
 MultiplicationAreaDetecterを右クリックして貼り付けをします。
 大きさ、位置を確認します。
 スクリーンショット (240).png

 MultiplicationAreaDetecterを選択して、インスペクターの「CollisionObject3D/Collistion」を開きます。
 検出される必要はなのでLayerは未選択です。
 MultiplicationAreaを検出したいので、Maskの5:MultiplicationAreaにチェックを入れます。
 スクリーンショット (243).png

 MultiplicationAreaDetecterを選択して、インスペクターの横のノード/シグナルを選択します。
 今回はArea3Dを検出したいので、area_entered(area:Area3D)を右クリックして「接続」を実行します。
スクリーンショット (245).png
 Bulletを選択して、受信側メソッド名を確認して「接続」ボタンを押下します。
スクリーンショット (246).png
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を実行します。
スクリーンショット (247).png
 「StageItems/multiplication_area.tscn」を開きます。
スクリーンショット (248).png
 追加されました。
スクリーンショット (251).png

 MultiplicationAreaDetecterを選択した状態で、インスペクターのNode3D/Transform/Positionのzを-3mに設定ます。
スクリーンショット (255).png

 実行します。BulletがMultiplicationAreaを通過すると2倍以上にたくさん増えましたね。
 これは増殖したBulletがMultiplicationAreaを検出して増殖するのを繰り返しているためです。
スクリーンショット (258).png
 一度通過した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と衝突して押し合うのでオリジナルも左に移動します。
スクリーンショット (263).png

MultiplicationAreaの倍率をインスペクターで設定できるようにする

 MultiplicationAreaの倍率をインスペクタで変更できるようにします。

 res://StageItems/multiplication_area.tscnを開きます。
 MultiplicationAreaを右クリックして、「スクリプトをアタッチ」を実行します。
 StageItemsディレクトリ配下を確認して保存します。
 スクリーンショット (264).png
「res://StageItems/multiplication_area.gd」が追加されました。
スクリーンショット (267).png

 下記のように、倍率の変数を追加して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」が表示されました。
スクリーンショット (269).png

 x3の倍率のMultiplicationAreaをステージに追加します。
 res://Stages/stage001.tscnを開きます。
 Stageを右クリックして、Instantiate Child Sceneを実行します。
スクリーンショット (270).png
 「StageItems/multiplication_area.tscn」を開きます。
スクリーンショット (248).png
 シーン内のMultiplicationAreaはMultiplicationArea_x2に名称変更します。
 MultiplicationArea2はMultiplicationArea_x3に名称変更します。
スクリーンショット (272).png

 MultiplicationArea_x2を選択した状態で、インスペクタのmultiplication_area.gd/M I Magnificationが「2」であることを確認します。
スクリーンショット (273).png
 MultiplicationArea_x3を選択した状態で、インスペクタのmultiplication_area.gd/M I Magnificationを「3」に設定します。
スクリーンショット (274).png
 位置を移動します。
 MultiplicationArea_x3を選択した状態で、Node3D/Transform/PositionのXを0.5m、zを-5mにします。
スクリーンショット (275).png
スクリーンショット (281).png

 実行します。
スクリーンショット (276).png
 新たに追加したほうは「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人に増殖します。

スクリーンショット (279).png

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

 以上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?