結論: 経験値ジェムがプレイヤーの一定距離内に入ったら、プレイヤーへ向かって移動させます。拾える距離まで近づいたあとは、前回作った body_entered が働くため、ジェムの生成と取得の仕組みを大きく変えずにマグネット効果を追加できます。
このシリーズでは、Brotatoのようなアリーナ型ゲームの仕組みを、Godotで一つずつ小さく再現しています。前回は、敵を倒した位置に経験値ジェムを落とし、プレイヤーが触れると経験値を受け取る処理を作りました。
ジェムを毎回ひとつずつ拾いに行くと、敵の数が増えた場面で移動が忙しくなります。今回は、ジェムが近くにあると自動で吸い寄せられる範囲を追加します。吸引範囲は140ピクセル、吸引速度は450ピクセル毎秒から始めます。
- 対象:前回記事の経験値ジェムまで追加したプロジェクト
- 使用バージョン:Godot 4.7 系(4.7.x)
- 完成状態:近くの経験値ジェムがプレイヤーへ移動し、触れると経験値が増える
1. 吸引処理はジェム側に置く
今回は、経験値ジェム自身がプレイヤーとの距離を確認します。
| 担当 | やること |
|---|---|
| ExperienceGem | プレイヤーが吸引範囲に入ったら自分を移動させる |
| Player | 移動と経験値の管理を続ける |
| Main | ジェムを生成し、ゲーム全体の流れを管理する |
プレイヤー側から画面上の全ジェムを探して移動させる方法もあります。今回は、ジェムの処理をジェム自身へ置くことで、生成されたジェムが自分で動く形にします。
この構成なら、回復アイテムや将来追加する別のドロップアイテムへ、同じ吸引処理を必ずしも適用しなくて済みます。経験値だけを吸い寄せたいという、現在のゲームデザインにも合います。
2. 吸引範囲と速度を決める
experience_gem.gd の定数に、吸引範囲と速度を追加します。
const EXPERIENCE_VALUE := 1
const MAGNET_RANGE := 140.0
const MAGNET_SPEED := 450.0
MAGNET_RANGE は、ジェムが吸引を開始する距離です。MAGNET_SPEED は、ジェムがプレイヤーへ近づく速さを表します。
プレイヤーの移動速度は現在300前後なので、吸引速度を450にすると、移動速度300のプレイヤーに追いつきやすくなります。ただし毎フレーム距離を確認するため、プレイヤーが範囲外へ出ると吸引は止まります。吸引速度を低くすると、敵を避けながら拾う場面が長くなります。
3. 経験値ジェムをプレイヤーへ移動させる
experience_gem.gd に _physics_process() を追加します。前回作った body_entered の取得処理は残してください。
func _physics_process(delta: float) -> void:
var player := get_tree().get_first_node_in_group("player") as Node2D
if player == null:
return
var distance := global_position.distance_to(player.global_position)
if distance > MAGNET_RANGE:
return
global_position = global_position.move_toward(
player.global_position,
MAGNET_SPEED * delta
)
distance_to() でジェムとプレイヤーの距離を調べ、吸引範囲の外なら移動しません。範囲内に入ったときだけ move_toward() を使ってプレイヤーへ近づけます。
移動量に delta を掛けている点が重要です。delta は物理更新1回分の時間(秒)なので、画面の更新速度が変わっても、1秒あたりの移動速度をおおよそ450ピクセルに保てます。
4. スクリプト全体で処理の順番を確認する
前回記事の experience_gem.gd に吸引処理を加えると、全体は次の形になります。
extends Area2D
const EXPERIENCE_VALUE := 1
const MAGNET_RANGE := 140.0
const MAGNET_SPEED := 450.0
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _physics_process(delta: float) -> void:
var player := get_tree().get_first_node_in_group("player") as Node2D
if player == null:
return
var distance := global_position.distance_to(player.global_position)
if distance > MAGNET_RANGE:
return
global_position = global_position.move_toward(
player.global_position,
MAGNET_SPEED * delta
)
func _on_body_entered(body: Node2D) -> void:
if not body.is_in_group("player"):
return
if not body.has_method("gain_experience"):
return
body.call("gain_experience", EXPERIENCE_VALUE)
queue_free()
吸引処理は _physics_process()、経験値を渡す処理は _on_body_entered() に分かれています。ジェムが近づくことと、経験値が増えることを別の処理として読めるため、動作確認の場所も分かりやすくなります。
5. 吸引開始後の動きを調整する
吸引範囲に入ったジェムは、プレイヤーとの距離が縮まるたびに、次のフレームでもプレイヤーの位置へ向かいます。プレイヤーが移動しても、範囲内にいる間は新しい位置へ追従します。
プレイヤーの近くに複数のジェムがある場合、すべてのジェムが同時に移動します。ジェム同士に衝突判定は設定していないため、互いに押し合って止まることはありません。
吸引が強すぎる場合は、次の順番で調整します。
MAGNET_RANGEを140から100へ下げるMAGNET_SPEEDを450から350へ下げる- プレイヤーの移動速度を変えずに、拾う距離だけを調整する
吸引が弱すぎる場合は、まず速度より範囲を少し広げます。範囲だけを広げても、敵を倒した直後から全ジェムが画面を横切るようなら、速度を下げて動きを確認してください。
吸引範囲と速度をそれぞれ調整し、遊んだときの動きを比べます。
6. Area2Dの衝突設定を確認する
ジェムがプレイヤーへ近づいても取得できない場合、移動処理ではなく衝突設定が原因かもしれません。experience_gem.tscn の設定を確認します。
| 設定 | 値 |
|---|---|
| Root | Area2D |
| Collision Layer | 0 |
| Collision Mask | 1 |
| 子ノード | CollisionShape2D |
| Shape | CircleShape2D |
プレイヤーのCollision Layerが1以外なら、ジェムのCollision Maskをプレイヤーの値へ合わせます。CollisionShape2D がシーンにあっても、Shapeが空なら接触を検知できません。
吸引中にジェムの画像がプレイヤーへ重なるのに経験値が増えない場合は、まず body_entered のシグナル接続を確認してください。エディタとコードの両方で接続すると、処理が二重になる可能性があります。今回の例では _ready() のコード接続だけを使います。
7. ゲームオーバーやレベルアップ中の挙動
前回記事では、ゲームオーバー時にプレイヤーと敵の移動を止めています。経験値ジェムの _physics_process() は別のノードで動くため、ゲームオーバー画面の裏でジェムが移動する可能性があります。
ゲームオーバー後にジェムの吸引も止めたい場合は、プレイヤーが死亡しているかをジェム側で確認します。player.gd に is_player_dead() を追加する方法が分かりやすいです。
次の例は、死亡時に真になる is_dead がPlayerに定義されている場合のものです。配布する統合版では生存状態を alive で管理しているため、戻り値は not alive にしてください。
func is_player_dead() -> bool:
return is_dead
ジェムの _physics_process() で、プレイヤー取得後に次の判定を追加します。
if player.has_method("is_player_dead") and player.call("is_player_dead"):
return
ゲームオーバー後もジェムを画面に残すなら、この判定は不要です。ゲームオーバー画面を出したあとに画面上の動きを完全に止めたい場合だけ追加してください。
レベルアップ中は get_tree().paused = true になっているため、通常はジェムの _physics_process() も停止します。選択画面を閉じると、ジェムの吸引とプレイヤーの移動が再開します。
8. 吸引範囲をアップグレードへつなげる
今回はジェム側に固定値を置きましたが、後から「拾う範囲アップ」の強化を追加できます。最初の拡張では、プレイヤーに範囲を持たせます。
const START_MAGNET_RANGE := 140.0
const MAGNET_RANGE_UPGRADE := 40.0
var magnet_range := START_MAGNET_RANGE
func get_magnet_range() -> float:
return magnet_range
ジェム側はプレイヤーの magnet_range を読む形へ変更します。
var magnet_range := MAGNET_RANGE
if player.has_method("get_magnet_range"):
magnet_range = player.call("get_magnet_range")
if distance > magnet_range:
return
最初からアップグレード用の構造を増やすと確認箇所が多くなります。まず固定値で吸引を動かし、そのあと強化候補へつなぐ順番がおすすめです。
9. 実行して確認する
今回の変更は、Godot 4.7で次の順番に確認します。
- 前回記事のプロジェクトを複製する
experience_gem.gdに吸引範囲と速度を追加する_physics_process()でプレイヤーとの距離を確認するbody_enteredの取得処理を残す- F5でメインシーンを実行する(F6は現在開いているシーンを実行)
- 敵を倒して経験値ジェムを出す
- ジェムから140ピクセル以内へ近づく
- ジェムがプレイヤーへ移動することを確認する
- ジェムが触れた瞬間に消え、経験値が1増えることを確認する
- 複数のジェムが同時に吸い寄せられることを確認する
- レベルアップ画面を閉じたあと、吸引処理が再開することを確認する
- ゲームオーバー時に必要なら、ジェムの動きも止まることを確認する
吸引範囲の境界を確認するときは、ジェムをプレイヤーから少しずつ離して生成します。140ピクセルの外では止まり、範囲内へ入ると動き始めることを確認してください。
10. よくあるエラーを直す
ジェムがプレイヤーへ向かわない
experience_gem.gd が ExperienceGem にアタッチされているか、スクリプトに _physics_process() があるか確認します。MAGNET_RANGE が0になっていないかも確認してください。
ジェムが近づくが、取得されない
Area2Dの CollisionShape2D を確認します。ジェムが高速すぎる場合は、MAGNET_SPEED を450から300へ下げて、接触が検知されるかを確認します。
ジェムを拾うと経験値が2増える
敵撃破時の直接加算が残っていないか、body_entered の接続をコードとエディタの両方で設定していないか確認します。敵を倒した瞬間ではなく、ジェムを拾った瞬間だけ経験値が増える状態が正しい結果です。
レベルアップ画面でジェムが動き続ける
プロジェクト側でシーンツリーのPause設定を変更していないか確認します。ジェムのProcess ModeがAlwaysなら、Pausableにするか、Inheritにして親ノードのProcess Modeが一時停止に従う設定か確認してください。Inheritは親から設定を引き継ぐため、親がAlwaysなら一時停止中も処理が続きます。Godot公式: Pausing games and process mode
11. まとめ
今回は、経験値ジェムをプレイヤーへ吸い寄せるマグネット効果を追加しました。
- ジェム側でプレイヤーとの距離を測る
- 吸引範囲内だけ
move_toward()で移動する deltaを使い、画面更新速度に左右されにくくする- 既存の
body_enteredで取得処理を続ける - 吸引範囲と速度を別々に調整できるようにする
これで、敵を倒したあとにジェムを探して歩く手間が減り、敵の群れを避けながら経験値を回収する流れが作れました。次は、ジェムの吸引範囲をレベルアップ候補に加えると、プレイ中の選択と成長をつなげられます。
参考:Godot 4とGDScriptの入門書
Godot 4とGDScriptの基本から2Dゲーム開発を学びたい場合は、今回のようなシーン構成やスクリプトの分割を体系的に確認できる書籍も参考になります。
※本記事にはアフィリエイトリンクが含まれています。
Godot 4 & GDScriptではじめる 2Dゲーム開発レシピ(Amazonで詳細を見る)
※本リンクはアフィリエイトリンクです。
統合版プロジェクトとの対応
この記事で扱う機能は、企画全体をまとめたauto_attack_final_verified.zipにも組み込んでいます。この記事のコードは段階的に追加するための説明なので、途中の記事と統合版全体のファイル構成は一致しない場合があります。
統合版では接触シグナルではなく距離20以下でジェムを取得し、衝突マスクは0です。吸引が動かない場合は距離、get_magnet_range、gain_experienceを確認します。回復アイテムにも別の吸引処理があります。統合版には後続記事の機能も含まれます。記事の機能を確認するときは該当する操作を先に試し、最後にGodot 4.7でF5起動、入力、セーブ・ロード、再起動後の復元を確認してください。
