分割画面マルチプレイヤー

ℹ️ 留意事項

この記事は Godot 3から Godot 4 へ内容の書き換え中です。 Godot4では存在しない変数、関数が含まれている場合があります。もしその場合はリポジトリのIssuesまでご報告ください。

はじめに

このデモでは、見下ろし型の迷路ゲームを例に、ローカルマルチプレイヤーゲームを考えます。このゲームでは2人のプレイヤーが参加し、一方は矢印キー、もう一方はWASDキーで操作します。これは問題ありませんが、もしゲーム世界全体が1画面に収められる程度の大きさであれば、特に問題はありません。しかし、マップが非常に広い場合、両プレイヤーを個別に表示する「分割画面」ビューが必要になるでしょう。

alt alt

また、ミニマップ表示をすばやく設定する方法についても解説します。

ゲーム設定

ゲーム世界のセットアップに多くの時間をかけるつもりはありません。登場する2人のキャラクターは シンプルな8方向移動を実装したCharacterBody2D オブジェクトです。

メモ

このパーツのセットアップでお困りの場合は、公式Godotドキュメントの以下のセクションをご覧ください: 2D移動の概要

各操作は、プロジェクト設定の[インプットマップ]セクションで個別に設定されています。「right_1」は右矢印キー、「right_2」はDキーなどです。このように命名することで、コード内で以下の構文を使用でき、開発効率を大幅に向上させられます。

@export var id = 0

func get_input():
    velocity = Vector2()
    if Input.is_action_pressed('right_%s' % id):
        velocity.x += 1
    # etc.

この方法であれば、キャラクターが同じ移動スクリプトを使用できます。各プレイヤーに適切な値を id として割り当てるだけです。

以下の手順に従って、2つのプレイヤーを TileMap を含む「World」シーンに追加してください。

alt alt

ご希望であれば、ワールドが既に設定済みの開始プロジェクトをこちらからダウンロードできます。

splitscreen_start.zip

このマップはゲーム画面よりもはるかに大きいことに注意してください。ただ、それ以外の点ではすべて正常に動作します。このようにゲームの「World」を個別に設定することで、ビューポートの設定が格段に容易になり、より柔軟に扱えるようになります。

ビューポート、カメラ、およびワールドについて

まず、2つのビューポートを含む新しいシーンを作成します。 ルートノードとして使用するノードを作成します。通常、Nodeを使用します。このノードには独自のプロパティが何もないため(単にシーンの他の要素を保持するためのものです)、使い勝手が良いからです。

各ビューポートノード(Viewport)には位置情報が含まれません(Node3DNode2Dを継承していません)。ここでは、各ビューポートを管理するためにViewportContainerを使用します。このコンテナはControlノードの一種です。それらを横並びに配置するためには、HBoxContainerを使います。

HBoxContainerの配置を「中央」に設定し、2つのビューポート間に小さな隙間を設けるには、_カスタム定数/間隔_に 5 を設定してください。「Layout(レイアウト)」メニューでは「Full Rect」を選択します。

次に、2つのViewportContainerを子要素として追加し、それぞれに21という名前を付けます(これらは表示するプレイヤーに対応するためです)。両方のコンテナについて Size Flags を「Fill, Expand」に設定してください。これにより、各コンテナが画面の半分を埋めるように拡大されます。さらに、 Stretch プロパティもチェックすることで、Viewportが自動的にコンテナのサイズに合わせて調整されるようになります。

各コンテナ内に、Viewport 要素を追加してください。なお、ビューポートの Size プロパティを設定した場合、その値はコンテナによってリセットされますのでご注意ください。

Viewportにコンテンツを表示するには、Camera2Dが必要です。このカメラはViewport上にレンダリングを行います。各ビューポートに1つずつ追加してください。また、カメラを有効にするには 現在のプロパティを必ずチェックしてください 。さらに、各カメラのズーム値を(0.75, 0.75)に設定することで、プレイヤー周辺のエリアをより詳細に表示できるようになります。

ノードを以下のようにします。

 ┖╴Main (Node)
    ┖╴Viewports (HBoxContainer)
       ┠╴ViewportContainer2
       ┃  ┖╴Viewport2
       ┃    ┖╴Camera2D
       ┖╴ViewportContainer1
          ┖╴Viewport1
            ┖╴Camera2D
メモ

注意点:ViewportContainer1HBoxContainer内で2番目に配置しました。この設定により、プレイヤー1が矢印キーを使用するため、コンテナは右側に表示されます。

空間(World)の追加

シーンを実行すると、ビューポートには何も表示されません。これはビューポートがレンダリングする「世界」を持っていないためです。3Dの場合はworldプロパティ、2Dの場合はworld_2dプロパティが、ビューポートの環境設定を表し、カメラによって何が表示されるかを決定します。ワールドはコード内で設定可能ですが、2Dの場合、追加した子ノードも自動的に表示される点に注意が必要です。

以下のように、「World」シーンをViewport1の子としてインスタンス化します。これでシーンを実行すると、左ビューポート内に世界が表示されます。

また、 Viewport2に世界を追加が必要ですが、同じ世界を使用させたい場合はどうすればよいでしょうか。 これはコードで処理できます。Main にスクリプトをアタッチし、以下を追加してください。

extends Node

@onready var viewport1 = $Viewports/ViewportContainer1/Viewport1
@onready var viewport2 = $Viewports/ViewportContainer2/Viewport2
@onready var camera1 = $Viewports/ViewportContainer1/Viewport1/Camera2D
@onready var camera2 = $Viewports/ViewportContainer2/Viewport2/Camera2D
@onready var world = $Viewports/ViewportContainer1/Viewport1/World

func _ready():
    viewport2.world_2d = viewport1.world_2d

onready ノード参照は利便性のためです。作業中に随時使用します。 $と入力すると、Godotは自動的にノードパスを提案してくれるので、手動でタイプする必要はありません。 また、シーンツリーからノードをスクリプトエディタへドラッグすれば、そのノードのパスを取得できます。

現在シーンを実行すると、両方のビューポートでレンダリングされた世界が表示されます。ただし、どちらのカメラも移動していないため、表示されるのは世界のごく一部に過ぎません。

カメラのセットアップ方法

以下のスクリプトを各カメラに適用してください。

extends Camera2D

var target = null

func _physics_process(delta):
    if target:
        position = target.position

現在、各カメラにターゲットを割り当て、そのノードの座標に従うように設定できます。 以下のMainスクリプトで実装します。

func _ready():
    viewport2.world_2d = viewport1.world_2d
    camera1.target = world.get_node("Player_1")
    camera2.target = world.get_node("Player_2")

現在シーンを実行すると、各プレイヤーは自分のビューポートの中央に配置され、分割画面の設定が正しく機能しています!

ヒント

カメラの Drag Margin プロパティを無効にすると、見た目が一番良くなります。

カメラの制限

次に、プレイヤーカメラがマップの表示範囲外にスクロールしないように制限を追加してください。この関数をメインスクリプトに追加し、_ready() で呼び出してください。

func set_camera_limits():
    var map_limits = world.get_used_rect()
    var map_cellsize = world.cell_size
    for cam in [camera1, camera2]:
        cam.limit_left = map_limits.position.x * map_cellsize.x
        cam.limit_right = map_limits.end.x * map_cellsize.x
        cam.limit_top = map_limits.position.y * map_cellsize.y
        cam.limit_bottom = map_limits.end.y * map_cellsize.y

ミニマップ

もう一つ便利な機能を追加してください。マップ全体を見渡せるミニマップです。プレイヤーが現在地を把握しやすくなります。

さらにもう 1 つ の ViewportContainer が必要です。今回は Main の子要素として配置します。この場合、 _Stretch_は使用しません*。代わりに Viewport を追加し、_Size_を (340, 200) に設定します その後、 の Camera2D 要素を追加してください。表示画面中央に配置するため、Camera2DPosition(512, 300) に設定しましょう。表示範囲を拡大するには、Zoom(9, 9) に設定してください。このカメラについても忘れずに Current を選択してください。

_ready()関数内では、ミニマップが他の2つのビューポートと同じワールドを使用するように設定します。

$Minimap/Viewport.world_2d = viewport1.world_2d

「レイアウト」メニューを使用してMinimapコンテナを「中央下部」に配置してください。実際に見てみてください。

alt alt

エッジ周りのグレーゾーンを解消が必要です。正確なズームレベルを特定して希望のミニマップサイズに合わせることもできますが、代わりにViewport設定の_Transparent Bg(透過背景)_をチェックします。これで非地図領域が見えなくなり、ミニマップがメインビューポートの上に直接浮かんで表示されるようになります。

alt alt

まとめ

ビューポートは非常に強力な機能ですが、同時に混乱を招く可能性もあります。効果的な管理方法として、ゲームロジックから完全に切り離し、表示専用として使用する方法が有効です。