Godot 4のコンポーネント指向開発シリーズ、今回はレトロアーケードゲームやシューティングゲーム(STG)で定番の機能**「ScreenWrapper (画面端ループ)」**です。

『パックマン』や『アステロイド』のように、キャラクターが画面の右端から出ると、即座に左端から出現するあの挙動を、ノードを1つ追加するだけで実装します。


このコンポーネントは、親ノードの座標を監視し、画面(ビューポート)の境界線を越えた瞬間、反対側の境界線へワープさせます。

1. コンポーネントのコード (Full Code)

以下のコードをコピーして、ScreenWrapper.gd という名前で保存してください。

class_name ScreenWrapper
extends Node

## 親ノードが画面外に出たら、反対側から出現させる(ループ)コンポーネント
## 親ノードは Node2D (CharacterBody2D, Area2D, Sprite2D等) を想定しています。
## 注意: 固定カメラ(画面切り替えなし)のゲームで最も効果を発揮します。

# --- 設定パラメータ ---
@export_group("Wrap Settings")
@export var horizontal_wrap: bool = true  ## 横方向のループを有効にするか
@export var vertical_wrap: bool = true    ## 縦方向のループを有効にするか
@export var margin: float = 20.0          ## 画面端からの猶予(スプライトの半分くらいのサイズを指定)

# --- 内部変数 ---
var _parent: Node2D

func _ready() -> void:
	_parent = get_parent() as Node2D
	if not _parent:
		push_error("ScreenWrapper: 親が Node2D ではありません。")
		set_process(false)

func _process(_delta: float) -> void:
	# 現在の画面サイズ(矩形)を取得
	# カメラを使っている場合はカメラの位置考慮が必要ですが、
	# ここではシンプルな「ウィンドウサイズ」基準で実装しています。
	var screen_rect = get_viewport().get_visible_rect()
	var pos = _parent.global_position

	# --- 横方向 (X軸) のループ判定 ---
	if horizontal_wrap:
		# 右端を超えたら -> 左端へ
		if pos.x > screen_rect.end.x + margin:
			_parent.global_position.x = screen_rect.position.x - margin
		# 左端を超えたら -> 右端へ
		elif pos.x < screen_rect.position.x - margin:
			_parent.global_position.x = screen_rect.end.x + margin

	# --- 縦方向 (Y軸) のループ判定 ---
	if vertical_wrap:
		# 下端を超えたら -> 上端へ
		if pos.y > screen_rect.end.y + margin:
			_parent.global_position.y = screen_rect.position.y - margin
		# 上端を超えたら -> 下端へ
		elif pos.y < screen_rect.position.y - margin:
			_parent.global_position.y = screen_rect.end.y + margin

2. 使い方チュートリアル

このコンポーネントは、宇宙船や隕石など、「画面内に留まらせたいオブジェクト」なら何にでも使えます。

手順①:対象オブジェクトの準備

  1. プレイヤーや敵キャラ(CharacterBody2DArea2D)を用意します。
  2. 以前作成した KeyboardMover などを付けて、動ける状態にしておくとテストが楽です。

手順②:コンポーネントのアタッチ

  1. 対象オブジェクトの子ノードとして Node を追加し、名前を ScreenWrapper にします。
  2. スクリプト ScreenWrapper.gd をアタッチします。

シーン構成図:

Player (CharacterBody2D)
 ├── Sprite2D (画像: サイズ約64pxと仮定)
 ├── CollisionShape2D
 ├── KeyboardMover (移動用)
 └── ScreenWrapper (Node)  <-- 今回追加!

手順③:マージン(Margin)の調整

これが最も重要です。

Margin が 0 だと、画像の中心点が画面端に来た瞬間にワープするため、「体が半分残っているのに消えて、反対側から半分出てくる」 という不自然な見た目(ポップ現象)になります。

  • 推奨設定: スプライトの幅の半分、または少し大きめの値。
    • 例: 画像サイズが 64×64 なら、Margin3240 くらい。
    • こうすると、「完全に画面外に出てから、反対側の画面外へワープ」するため、自然に見えます。

手順④:実行

ゲームを実行し、画面の端に向かって移動し続けてください。

右から出たら左から、下から出たら上から、自然に戻ってくるはずです。


3. 応用テクニック

隕石や弾丸にも使える

プレイヤーだけでなく、「敵の弾」や「漂う隕石」にこれを付けると、画面内を永遠に飛び交う障害物が簡単に作れます。

(例:ProjectileMover で直進させ、ScreenWrapper でループさせる)

一方向だけループさせる

インスペクターの Horizontal WrapVertical Wrap のチェックを外せば、「横はループするけど、上下は壁で止まる(または落ちる)」といった挙動も作れます。

  • 横スクロールSTG: Vertical Wrap をOFFにして、上下移動を制限する。
  • プラットフォーマー: Horizontal Wrap をONにして、マリオブラザーズのような「左右が繋がったステージ」を作る。

カメラを使う場合(上級者向け注記)

このコードは get_viewport().get_visible_rect() を使用しているため、**「カメラが固定されているゲーム」**で正常に動作します。

もし広大なマップをカメラが移動するゲームで「特定の部屋の中だけループさせたい」場合は、screen_rect の取得部分を「カメラの現在の枠」や「指定したArea2Dの枠」に書き換える必要があります。