Available in the Gaming / Industries Circle

VR Escape Room

Level Advanced
Fusion VR Escape Room サンプルは現在、有効な Photon Gaming Circle または Photon Industries Circle サブスクリプションを持つユーザーのみが利用できます。

Gaming Circleのメンバーシップは、記録的な速さで成功するマルチプレイヤー ゲームを作成してリリースするために必要なすべてのサンプル、SDK、およびサポートを提供します。 ゲーム以外の場合は、Industries Circle が完全なスイートと専用ライセンス オプションを提供します。

概要

Escape Room サンプルは、プレイヤーと環境との間の物理的な相互作用を開発する方法についてのアプローチを示しています。 コア は基本的なインタラクションとロコモーションを提供し、オプションモジュール は追加機能やインタラクションタイプを実装例として提供します。 すべてのモジュールにはフォルダが用意されており、必要なければ削除することができます。なお、本サンプルで提供するメインシーン(/Scenes/EscapeRoom Full.scene)は、すべてのモジュールとコンポーネントを使用しているため、モジュールを削除すると壊れてしまう可能性があります。 モジュールについては、本ドキュメントの最後に別途解説しています。

モジュール

  • Object Pull
  • Instant Camera
  • Whiteboard
  • Observer
  • Ui Module
  • Slots Module
  • Shooting Module
Fusion Escape Room Overview

トップに戻る
 

技術情報

  • このサンプルは、Server/Hosted Mode のトポロジーを使用しています。
  • このプロジェクトは、Unity 2020.3.37f1で開発されました。

トップに戻る
 

はじめる前に

サンプルを実行するには :

  • PhotonEngine Dashboard で Fusion AppId を作成し、Real Time Settings (Fusion メニュー) の App Id Fusion フィールドに貼り付けます。

  • PhotonEngine Dashboard で Voice AppId を作成し、Real Time Settings の App Id Voice 欄に貼り付けます。

  • 次に、"Start "シーンを読み込んで、"Play "ボタンを押します。その後、シーン全体を起動するか、特定のモジュールだけをテストすることができます。

Fusion Expo サンプルは現在、アクティブな Photon Gaming Circle または Photon Industries Circle サブスクリプション を持つユーザーのみが利用できます。 詳細とアクセスについては、developer@photonengine.com にメールでお問い合わせください。

トップに戻る
 

ダウンロード

バージョン リリース日 ダウンロード
1.1.3 Oct 20, 2022 Fusion VR Escape Room 1.1.3 Build 5

トップに戻る
 

入力の処理

メタクエスト

  • テレポート:AボタンまたはXボタンを押すとポインタが表示されます。ポインターを離すと、任意のターゲットにテレポートします。
  • 触る : 手や指をボタンの上に置くだけで、ボタンが切り替わります。
  • 掴む : 最初にオブジェクトに手をかざし、コントローラのグラブボタンで掴みます。
  • 使う: セレクトボタンを押すと、掴んだオブジェクトを使用することができます。

HTC Vive ControllerとValve Index Controllerに対応していることをご確認ください。

トップに戻る
 

デスクトップ

キーボード

  • 歩く : WASD
  • 上/下:スペース&左シフト

トップに戻る
 

マウス

  • 回転 : マウスの右ボタンを押したまま、マウスを動かすと視点が回転します。
  • UI : 左ボタンで UI ボタンを押す。
  • 掴んで使う (3D pens) : マウスをオブジェクトの上に置き、マウスの左ボタンで掴みます。その後、キーボードのスペースキーで使用することができます。

トップに戻る
 

コア

サンプルのコアは、物理ベースのインタラクションを RigidbodiesArticulation bodies を使って提供します。 リジッドボディは投げられるオブジェクトに、Articulation bodiesはレバーやダイヤル、ドアや引き出しのような機械的なシステムに使用されます。 カメラは物理的なものではなく、ユーザーの入力に基づくものです。テレポートロコモーションも含まれています (TeleportHandler.cs)

トップに戻る
 

インタラクションフローの概要

  • ポーリングと入力の送信 :
    • (XRInput.cs, LocalController.cs, PCInput.cs)
  • 入力に基づき、剛体と力を使って頭と手の位置を更新する :
    • (PlayerSystem.cs, XRObject.cs)
  • 手は入力を読み取り、HandTool と任意の IControllerInputReceiver に渡す。
    • (Hand.cs, IControllerInputReceiver)
  • HandTool と他のコンポーネントは入力に反応する :
    • (HandTool.cs, TeleportHandler.cs, InstantCameraInteractable.cs)
  • ハンドツールはホットスポットが範囲内にあるかどうかをチェックし、入力に基づいて取得/解放を行う。
    • (HotspotCollector.cs, Hotspot.cs, HighlightBase.cs)
  • ホットスポットは、インタラクションの開始/停止時に InteractableBase とすべての IInteractable に通知。
    • (InteractableBase.cs, GrabbableBase.cs)
  • GrabbableBase は力を使って手の位置を追跡する。
    • (GrabbableBase.cs, GrabbableRigidbody.cs, GrabbableArticulation.cs)

トップに戻る
 

入力

入力の送信

トップに戻る
 

### LocalInputBase.cs

Fusion からは独立した DontDestroyOnLoad オブジェクトとして存在し、セッション間で持続的に動作します。入力を収集して Runner に送信する役割を担います。

  • XRInput がその主な実装です。
  • PCInput はデバッグ用の入力メソッドで、より素早く反復処理を行うことができますが、機能は限定されています。

トップに戻る
 

入力を受け取る

PlayerSystem.cs

プレイヤーオブジェクト(頭と手)の基本的な位置決めを XRObject.cs を介して処理します。 XRObject は入力に追従して剛体力を更新します。

トップに戻る
 

Hand.cs

コントローラの入力を読み取り、IControllerInputReceiverを介して入力を受け取るために登録されたものに渡します。 基本的なインタラクションのために、明示的に HandTool.cs に入力を渡します。

  • 入力を受け取りたいシステムは IControllerInputReceiver を実装して、ハンドに登録することができます (例 : TeleportHandler.cs)
  • 掴んだオブジェクトは一時的に登録することができます ( 例 InstantCameraInteractable.cs) -> Hand.AddInputBehaviour() / Hand.RemoveInputBehaviour())

トップに戻る
 

インタラクション

HandTool:

  • Grab / Dropのための入力を読み取ります。
  • HotspotCollectorを使用して、ホットスポットを見つけ、ハイライトします。
  • Hotspotの取得と削除

トップに戻る
 

HotspotCollector:

  • レイヤーとタグを元に、半径内のホットスポットを検索する。
  • フィルタの順番: レイヤー, タグ, HighlightPriority, 距離
  • ホットスポットがホバーされるとハイライトされる (HotspotCollector -> Hotspot -> HighlightBase)

トップに戻る
 

Hotspot:

HandTools がオブジェクトと相互作用することができる相互作用点を表します。

  • オブジェクトをより多くのハンドでつかむ必要がある場合は、ホットスポットを追加します。必要であれば、これらのホットスポットは同一であっても構いません。
  • Start / Stop Interactionコールは同じGameObjectの IInteractableInteractableBase を親として渡します。

トップに戻る
 

InteractableBase:

InteractableBase: インタラクティブ・オブジェクトの基底クラス。

** Start / Stop Interaction 呼び出しを取得します

トップに戻る
 

グラブ可能なオブジェクトの階層構造。
  • Root: InteractableBase, Rigidbody, NetworkRigidbody, Highlight, NetworkObject, BodyProperties.
    • Visuals
      • Highlight Visuals
    • Collision
    • Hotspot: Hotspot, GrabbableRigidbody / AttachmentRigidbody, Collider (Trigger)

トップに戻る
 

BodyProperties

BodyPropertyCollection (スクリプト可能なオブジェクト)を割り当てて、掴んだときのオブジェクトの動作を制御します。

BodyPropertyCollection

BodyPropertyDataの配列が格納されており、何人の手がオブジェクトを掴んだかに応じて適用される(0:掴んでいない、1:一人の手で掴まれた、n:n人の手で掴まれた)。 この方法によって、オブジェクトは片手で扱うには扱いにくいか重すぎるが、2人以上なら簡単に扱うことができるようになる。

  • BodyPropertyData:

    • `Mass
    • Drag
    • AngularDrag
    • JointFrictionArticulation (Articulation bodies に対してのみ適用可能)
    • UseGravity
    • OverrideInertia (設定すると、慣性が InertiaWhenGrabbedScale * Vector3.one に設定されます。)
    • InertiaWhenGrabbedScale (オブジェクトが回転の変化に対してどの程度抵抗するかを制御します)
    • VelocityExtrapolation (オブジェクトに新しい力を加えるとき、現在の速度をどれだけ打ち消すか。力自体の倍率としても使用される)
    • TorqueExtrapolation (オブジェクトに新しい力を加えるとき、現在の角速度をどれだけ打ち消すか)

    GrabbableBase (Hotspot上)。

    掴まれたときにオブジェクトを手の位置まで追跡する処理を行います。

    ボディの種類によって適切な実装を選択する。

    • GrabbableRigidbody
    • GrabbableArticulation

    AttachmentRigidbody (on Hotspot GameObject):

    Rigidbodyオブジェクトを HandTool にアタッチします。これは非常にキビキビした動作が必要なライトオブジェクトに使用されます。(例:ホワイトボードマーカー、InstantCamera Pictures、Pistol) これが動作するためには、オブジェクトは特定の構造を必要とします。

    • Root
      • Visuals
      • Collision/Logic
        • Colliders
        • Hotspot: AttachmentRigidbody, Trigger Collider,

    ハンドを掴むと、CollisionVisuals GameObjectsHand に直接アタッチされ、あたかもハンドの一部であるかのように動作します。

    ハイライト

    インタラクト可能なオブジェクトがハンドの範囲内にあり、インタラクト可能であることを視覚的に確認することができます。

    Value Provider / Reader

    value providerは、あるシステムから別のシステムへ情報を転送し、特別なコンポーネントを使用せずに単純なロジックチェーンを作るための汎用的なコンポーネントです。

    例:

    1. articulation body (ArticulationBodyValueReader.cs) から値を読み取る。
    2. 閾値と比較する (ValueLogicIntCompare.cs)
    3. その値を使って、他のものを動かす (ArticulationBodyDriverSetLimits.cs)

トップに戻る
 

StreamTextureManager

モジュールに必要です。OnReliableDataコールバックを使ってネットワーク上にテクスチャーデータを送るシステム。Runnerと同じGameObject上にある必要があります。

トップに戻る
 

IgnoreCollision

Physics.IgnoreCollisionAPIを使って特定のオブジェクト間のコリジョンを無視する必要がある場合があります。

  • IgnoreCollision.cs: 静的な無視。ゲームプレイ中に変更されることはありません。スクリプトのあるGameObjectのコライダーはリストにあるすべてのコライダーを無視します。
  • NetworkColliderCollection.cs: 無視することができるコライダーのグループ。
  • NetworkIgnoreCollision.cs: AddIgnore()RemoveIgnore() を使用して、割り当てられたすべての NetworkColliderCollections に対してローカルコライダーを無視することができます。

トップに戻る
 

アーティキュレーションボディ

このサンプルでは、アーティキュレーションボディを多用しています。これは Unity で接続されたボディをシミュレートする安定した方法で、ここではレバー、ボタン、ダイヤル、ドロワーなどに使用されています。 このサンプルでは、NetworkArticulationBodyの実装は完全な機能ではありません。 参考文献としてhttps://docs.unity3d.com/2020.3/Documentation/ScriptReference/ArticulationBody.html

トップに戻る
 

コアコンポーネント

  • NetworkArticulationBodyRootRoot 上に配置されます。

    • NetworkArticulationBody は、すべての子オブジェクト Articulation bodies に配置されます。

      • NetworkedArticulationDrive Articulation body の Drive プロパティをネットワーク化します。
      • ArticulationBodyValueReaderSingleAxis Articulation body の値を読み込んで、Value Provider システムで使用することができます。
      • ArticulationBodySingleAxisSoftSnap 駆動プロパティを特定のポイントにスナップするように動的に設定します。例:ダイヤルのノッチ、引き出しのソフトクローズ、レバーの固定有効位置などです。

      ローカル補間

      NetworkArticulationBodyNetworkTransform を継承し、安定したローカル補間を提供します。この機能を実現するためには、補間対象を設定し、咬合体の階層を反映させる必要があります。

      構造

      • Root: NetworkObject, ArticulationBody, NetworkArticulationBodyRoot, InteractableBase.
        • Visuals: Articulation system と同じ階層に従います。
      • Child: ArticulationBody, NetworkArticulationBody, オプション: BodyProperties* ArticulationBodySoftSnap, ArticulationBodyValueReaderSingleAxis) * Child: (オプション) ArticulationBody, NetworkArticulationBody, ArticulationBodyValueReaderSingleAxis)
      • Child: (任意。上記と同じコンポーネント) * Hotspot: ホットスポット, トリガーコライダー, GrabbableArticulation.

      BodyProperties コンポーネントは Hotspot の下に少なくとも一度は必要です。各 Hotspot は親の中の最初のものを探します。

    アーティキュレーションボディをリジッドボディに接続する

    もし、Articulationrigidbody に接続する必要がある場合は、Joint (例: ConfigurableJoint) を使って行う必要があります。

    既知の問題

    • Compute Parent AnchorArticulation bodies に使用しないでください。初期状態でない場合、クライアントによって異なることがあります。
    • Articulation bodies のバグにより、articulation hierarchy のインデックスが異なります。正しく同期させるためには、クライアント間で同じインデックスが必要です。一時的な修正は NetworkArticulationBodyRoot.Spawned() に実装されています。これは正しい順番に並べ替えます。
    • この「ハックフィックス」は、rigidbodyarticulation bodies の間の Configurable Joints を切断してしまいます(例: Shooting モジュールの Pistol)。そのため、このオブジェクトでは順番を変更することはできません。

    モジュール

    オブジェクトの引き寄せ

    グラブボタンを押しながら、オブジェクトを指差し、手を上にフリックすることで、オブジェクトを自分の方に引き寄せることができます。

    要件

    • HandTool にある ObjectPullCollector というプレハブ。
    • Hotspot にある ObjectPullBody コンポーネント、また GrabbableBase コンポーネント (GrabbableRigidbody) が必要です。

トップに戻る
 

Instant Camera

インスタントカメラでは、より複雑なインタラクタブルオブジェクト(追加入力)と、ネットワーク経由で大きなデータを送信するための OnReliableData コールバック関数が紹介されています。

  • InstantCameraInteractable: カメラのレンダーテクスチャーをキャプチャして写真を撮ります。そのテクスチャのプリントアウトを生成します。
  • InstantCameraPrintout: シェーダーを使用してフェードインするまで、テクスチャーの到着を待ちます。さらに、写真が最初に撮影された後に参加したクライアントには再送信されます。
  • InstantCameraPicture.shader: 画像受信後にフェードインするためのシンプルなサーフェイスシェーダ

トップに戻る
 

ホワイトボード

描画可能なホワイトボードです。

  • WhiteboardSurface: 描画する表面。現在の状態を Rendertexture として保存します。
  • WhiteboardMarker: インタラクタブル。サーフェス(境界)を通して見て、近づいたらストロークを描くように指示します。

トップに戻る
 

シェーダー

  • WhiteboardFill: ホワイトボードの塗りつぶし。ホワイトボードを与えられたテクスチャで初期化します。
  • WhiteboardStroke: ホワイトボードを与えられたテクスチャで初期化します。レンダリングテクスチャに直線を描きます。
  • WhiteboardStrokeEraser: レンダーテクスチャに直線を描きます。レンダリングテクスチャを直線で消去します。

トップに戻る
 

Observer

VRプレイヤーを観察し、世界と対話するためのマウス/キーボードコントローラです。

要件は以下の通りです。

  • Observers は異なる Input rig と Player prefab を使用します (StartModulebutton.cs を参照してください)。
  • 操作したいオブジェクトに IObserverable コンポーネントを追加します。
    • リジッドボディをドラッグするための ObserverableGrabRigidbody コンポーネント。
    • ObservableArticulationBodyButton は一定の力を加えます (トグル可能)
    • ObservableArticulationBodyDrawer は一定時間力を加え、bool の状態を維持します (もう一度押すと力が反転します)。
    • ObservableArticulationBodyLever は保持している間、力を加えます。
    • ObservableValueProviderOverride は float の値を直接設定します (オーバーライドのトグルを使用します)。効果を得るにはロジックチェーンにインジェクトする必要があります。

フローティング UI ウィンドウは CreateObserverUi() を使用して、すべての IObserverable コンポーネントに自動的に作成されます。カスタムUIはここで実装することができます。デフォルトのコンポーネントは UI_ObserverItem プレハブで定義されています。

トップに戻る
 

Observer Input

Observer オブジェクトの操作は Input として直接送信されます。(ObserverInput / ObserverInputHandler.cs を参照してください) 入力は以下の要素で構成されます。

  • 修正されるコンポーネントを識別するための NetworkBehaviourId
  • Value (現在は float, bool, Vector3 のみ)

トップに戻る
 

UIモジュール

ネットワークに接続された基本的なCursorとUIインタラクションです。Unity の 'Tracked Device Raycaster' と 'InputSystem UI Input Module' を使い、Canvas インタラクションをローカルに登録し、Input として送ります。 PC Rig で使用する場合は 'Graphics Raycaster' も必要です。

  • LocalController.OnInputUi()

  • ポインタが指すキャンバスを InputDataController.CanvasBehaviour (NetworkBehaviourId) に保存します。

    • ポインタがCanvasに衝突した際のワールドの位置を InputDataController.CursorPosition に保存します。
    • ポインタが NetworkedUiButton コンポーネントを持つ GameObject に当たった場合は、 InputDataController.UiInteractionBehaviour (NetworkBehaviourId) に保存されます。
  • UiPointerHandler が入力を読み取り、ポインタを送信します。

    • 入力を読み取り、ポインタの位置とインタラクションを対応する NetworkedCanvasNetworkedUiButton に送ります。

トップに戻る
 

スロットモジュール

スロットシステムは、物体を所定の位置や方向にスムーズに誘導するために使用されます。例えば プラグ、キー、パズルのピース、マガジンなどです。 これを実現するために、GrabbableBase.csは、オブジェクトが掴まれたときに到達したい目標位置をオーバーライドするための様々な elegate とコールバックを持っています。

  • RotationDelegate GetRotationMethod
  • GrabAndTargetPositionDelegate GetGrabAndTargetPositionMethod
  • UnityAction PreTrackCallback
  • UnityAction PostTrackCallback

トップに戻る
 

Slottable

Slottableコンポーネントはこれらのコールバックをオーバーライドし、Slotに対する現在のターゲットに応じて、望ましい位置と回転を変更します。

  • PreTrack: スロットに十分近いかどうかを確認し、後で使用するために2つの要素を計算します。

    • _slotFactorHand: 手からスロットまでの距離。これは、オブジェクトが想定するターゲット位置と回転を決定するために使用されます。
    • _slotFactorObject: スロットからオブジェクトまでの距離。オブジェクトからスロットまでの距離。これはオブジェクトがまだ回転できる場合(>RotationLockThreshold)に、オブジェクトがスロットされ、解放されるべきかどうかを判断するために使用されます。
  • PostTrack: オブジェクトをドロップすべきかどうかをチェックします。

オブジェクトファクターはオブジェクトの実際の位置で、ハンドファクターは力が加わる前の手の入力位置から決定されます。

Slottable_slotFactorHand_slotFactorObject に応じて Slot から目的の位置/回転を取得します。

その他のコンポーネント

  • GrabbableRidgidbody
  • 有効なスロットを見つけるための HotspotCollector
  • ( 任意 ) NetworkColliderCollection: Slot に近づくと無視される colliders を指定します (設定されている場合は、スロットとホットスポットと相互作用する手に設定されているコライダーは無視されます)。

トップに戻る
 

Slot

Slotコンポーネントはオブジェクトが範囲内にあるときにどのように動作するかのデータを含んでいます。 Curvesはオブジェクトの位置から希望のスロットの位置まで、位置や回転がどれくらい急激に変化するかを決定します。 Slottables HotspotCollectorに拾われるためには正しいLayerでTriggerとしてマークされたコライダーが最低1つ必要です。

HotspotsToIgnoreHandCollisionWhenGrabbed は、ホットスポットを掴む手を無視するために使用することができます。 これは、スペースが限られていて、Slotを掴んでいる手を無視したい場合に便利です。 例: 片方の手がピストルを持っていて、マガジンとそのスロットが手のすぐ近くにあり、通常であればマガジンが手に衝突してしまいます。 ここにハンドルのホットスポットを追加して、手の衝突も無視するようにします。 UseLineForRange を設定すると、点ではなく線分をスロットのターゲットとして使用することができます。

DropType はスロットに成功したときにオブジェクトがどのようにハンドリングされるかを決定します。

  • Kinematic: オブジェクトを静的レベルのジオメトリにスロットするときに使用します。オブジェクトはキネマティックになり、再び掴まれない限り移動することができません。
  • Destroy: オブジェクトがデスポーンされます。
  • (TeleportOffsite: まだよくテストされていませんが、リジッドボディにスロットされたオブジェクトに使うことができ、再びつかんだときに瞬時に再配置できます。スロットされたオブジェクトを所定の位置にレンダリングするには、受信側に何らかの工夫が必要です)

スロットは、様々なパラメータをチェックすることで、Slottableがスロット可能かどうかを判断します。

  • SlotType はスロットにマッチします。
  • 他の Slottable が既に存在しない (または AllowMultipleObjects が設定されている)。
  • (Slot.CanSlot()) でさらに制限を加えることができます。

コンポーネント:

  • NetworkIgnoreCollision (オプション): 割り当てられたコライダをスロットされたオブジェクトに対して無視することができます。これは Slottable が壁を通り抜けられるようにする一方で、手自身やスロットに合わない他のオブジェクトは通り抜けられないようにするために使用できます。

トップに戻る
 

Shootingモジュール

銃のようなインタラクションを実現するためのサンプル実装です。2つの例が提供されています。

  • ショットガン

    • 一度に複数の弾丸を発射
    • GrabbableRigidbody を使った重たい物理ボディ
    • 単発の弾丸をリロードする
  • ピストル

    • アーティキュレーションボディによる手動コッキング
    • フルマガジンのリロード
    • AttachmentRigidbodyを用いたグラブ動作

トップに戻る
 

知っておくと便利なこと

シーンセットアップ

すべてのシーンには Connector.csLocalRigSpawner.cs が存在します。もしローカルリグが生成されていなかったり、シーンにあらかじめ定義されていない場合は、リグを生成し (xr または pc)、ランナーを生成して接続します。 Start.scene では、ランナーは SinglePlayer モードで起動します。この方法では、余分な作業をすることなく、ゲームコードに完全にアクセスすることができます。

トップに戻る
 

手とオブジェクトの視覚的補間 :

異なる状況下では、NetworkRigidobdyの通常の補間が Render() で上書きされます。

  • 手が何かを掴んだり衝突したりしていない場合は、ローカルコントローラの位置が設定されます。これにより、フォース(XRObject.cs, UpdateRender())を使用してハンドオブジェクトを移動する際に発生する遅延を回避することができます。
  • もし、手がオブジェクトをつかんでいる場合、そのオブジェクトが手の位置を決定するためのデファクトスタンダードとなります。手がオブジェクトを掴んでいる場合、オブジェクトが手の位置を決定するデファクトスタンダードとなります。
  • オブジェクトが離された場合、ビジュアルハンドは実際の位置にレンダリングされます (HandTool.cs Render())。

トップに戻る
 

よくある問題

  • つかめるオブジェクトがあちこち飛んでいる / 回転がおかしい : 次の設定をチェックして、力が蓄積したり振動したりしていないことを確認します。 プレイ中にエディターで調整できます。

  • BodyProperties : 各グラブルオブジェクトには BodyProperties コンポーネントがあり、物理プロパティが設定され、同期されます。プロパティ自体はスクリプト可能なオブジェクトで、似たようなオブジェクトで再利用することができます。オブジェクトに持たせたい重量や感触を得るために、値を微調整してください。オブジェクトが制御不能になったとき、位置や回転成分が振動している可能性があります。GrabbableBase コンポーネントの中で、位置や回転をトラッキングするかどうかをトグルして、問題の原因を突き止め、それに応じて微調整します。

    • 位置: Mass, Drag, UseGravityk, Velocity Extrapolation.
    • 回転: 質量、角度のあるドラッグ、インターリア、トルクの外挿
  • GrabbableRidgidbody, "Set Center Of Mass To Grab Point" : Grabbable Rigidbody コンポーネントでは、掴んだときに重心を掴み点に設定するかどうかをトグルします。これはオブジェクトの回転特性に大きな影響を与えるので、異なる BodyProperties が必要になるかもしれません。もし、同じ BodyProperties を持つ同じようなオブジェクトが異なる振る舞いをする場合、これが原因かもしれません。


ドキュメントのトップへ戻る