VRスターターチュートリアルパート1¶
はじめに¶
このチュートリアルでは、Godotで初心者向けのVRゲームプロジェクトを作成する方法を説明します。
VRコンテンツを作成する際に最も重要なことの1つは、アセットの規模を正確にすることです ! これを正しく行うには多くの練習と反復が必要になりますが、簡単にするためにできることがいくつかあります:
VRでは、1単位は通常1メートルと見なされます。 その標準を中心に据えてアセットを設計すれば、頭痛の種を大幅に減らすことができます。
3Dモデリングプログラムで、現実世界の距離を測定して使用する方法があるかどうかを確認します。 Blenderでは、MeasureItアドオンを使用できます。 Mayaでは、測定ツールを使用できます。
You can make rough models using a tool like Google Blocks, and then refine in another 3D modelling program.
アセットは、VRとフラットスクリーンで劇的に異なって見える可能性があるので、頻繁にテストしてください!
このチュートリアルでは、次のことを説明します:
VRで実行するようにGodotに伝える方法。
VRコントローラを使用するテレポーテーション移動システムの作成方法。
VRコントローラを使用した人工的な運動移動システムの作り方。
VR コントローラを使用してRigidBodyノードをピックアップ、ドロップ、およびスローできる RigidBody ベースのシステムを作成する方法。
単純な破壊可能なターゲットを作成する方法。
ターゲットを破壊できる特別な RigidBody ベースのオブジェクトを作成する方法 。
ちなみに
While this tutorial can be completed by beginners, it is highly advised to complete 最初の2Dゲーム, if you are new to Godot and/or game development.
このチュートリアルシリーズを読む前に、3Dゲームを作成した経験が必要です。このチュートリアルでは、Godotエディタ、GDScript、および基本的な3Dゲーム開発の経験があることを前提としています。OpenVR対応ヘッドセットと2つのOpenVR対応コントローラーが必要です。
このチュートリアルは、Windows Mixed Realityヘッドセットとコントローラーを使用して作成およびテストされています。 このプロジェクトはHTC Viveでもテストされています。 Oculus Riftなどの他のVRヘッドセットでは、コードの調整が必要になる場合があります。
The Godot project for this tutorial is found on the OpenVR GitHub repository. The starter assets for this tutorial can be found in the releases section on the GitHub repository. The starter assets contain some 3D models, sounds, scripts, and scenes that are configured for this tutorial.
注釈
提供されたアセットのクレジット:
The sky panorama was created by CGTuts.
使用しているフォントはTitillium-Regular
このフォントは SIL Open Font License, Version 1.1 でライセンスされます
The audio used are from several different sources, all downloaded from the Sonniss #GameAudioGDC Bundle (License PDF)
オーディオファイルが保存されているフォルダの名前は、Sonniss audio bundle のフォルダと同じです。
The OpenVR addon was created by Bastiaan Olij and is released under the MIT license. It can be found both on the Godot Asset Library and on GitHub. 3rd party code and libraries used in the OpenVR addon may be under a different license.
The initial project, 3D models, and scripts were created by TwistedTwigleg and is released under the MIT license.
ちなみに
You can find the finished project on the OpenVR GitHub repository.
すべてを準備する¶
If you have not already, go to the OpenVR GitHub repository and download the "Starter Assets" file from the releases. Once you have the starter assets downloaded, open up the project in Godot.
注釈
このチュートリアルで提供されるスクリプトを使用するために、スターターアセットは必要ありません。 スターターアセットには、チュートリアル全体で使用されるいくつかの作成済みのシーンとスクリプトが含まれています。
プロジェクトが最初にロードされると、Game.tscnシーンが開きます。 これがチュートリアルで使用されるメインシーンになります。 シーン全体にすでに配置されているいくつかのノードとシーン、いくつかのバックグラウンドミュージック、およびいくつかのGUI関連の MeshInstance ノードが含まれています。
GUI関連の MeshInstance ノードには既にスクリプトがアタッチされています。 これらのスクリプトは、Viewport ノードのテクスチャを MeshInstance ノードのマテリアルのアルベドテクスチャに設定します。これは、VRプロジェクト内のテキストを表示するために使用されます。 必要に応じて、スクリプト GUI.gd
をご覧ください。このチュートリアルでは、MeshInstance ノードでUIを表示するために Viewport ノードを使用する方法については説明しません。
MeshInstance ノードでUIを表示するために Viewport ノードを使用する方法に興味がある場合は、ビューポートをテクスチャとして使用する チュートリアルを参照してください。Viewport をレンダリングテクスチャとして使用する方法と、そのテクスチャを MeshInstance ノードに適用する方法について説明します。
チュートリアルに進む前に、VRに使用されるノードがどのように機能するかについて少し話をしましょう。
ARVROrigin ノードは、VRトラッキングシステムの中心点です。ARVROrigin の位置は、VRシステムが床の「中心」点と見なす位置です。ARVROrigin には、VRシーン内のユーザーのサイズに影響を与える world scale プロパティがあります。 このチュートリアルでは、世界はもともと大きなものだったため、1.4 に設定されています。 前述したように、VRではスケールを比較的一定に保つことが重要です。
ARVRCamera は、プレイヤーのヘッドセットであり、シーンを表示します。ARVRCamera は、VRユーザーの高さによってY軸上でオフセットされます。これは、テレポート移動を追加するときに重要になります。 VRシステムが部屋のトラッキングをサポートしている場合、プレイヤーが移動すると ARVRCamera も移動します。これは、ARVRCamera が ARVROrigin ノードと同じ位置にあることが保証されていないことを意味します。
ARVRController ノードはVRコントローラーを表します。ARVRController は、ARVROrigin ノードに対するVRコントローラーの位置と回転に従います。 VRコントローラーへのすべての入力は、ARVRController ノードを介して行われます。1
の ID
を持つ ARVRController ノードは左のVRコントローラーを表し、2
の ID
を持つ ARVRController コントローラーは右のVRコントローラーを表します。
To summarize:
ARVROrigin ノードはVRトラッキングシステムの中心であり、床に配置されています。
ARVRCamera は、プレイヤーのVRヘッドセットであり、シーンを見ることができます。
ARVRCamera ノードは、ユーザの高さによってY軸上でオフセットされます。
VR システムがルームトラッキングをサポートしている場合、ARVRCamera ノードはプレイヤーの移動に合わせてX軸とZ軸でオフセットされる可能性があります。
ARVRController ノードは VR コントローラーを表し、VR コントローラーからの入力をすべて処理します。
VRの開始¶
VRノードを確認したので、プロジェクトの作業を始めましょう。Game.tscn
で Game
ノードを選択し、Game.gd
という新しいスクリプトを作成します。Game.gd
ファイルに次のコードを追加します:
extends Spatial
func _ready():
var VR = ARVRServer.find_interface("OpenVR")
if VR and VR.initialize():
get_viewport().arvr = true
OS.vsync_enabled = false
Engine.target_fps = 90
# Also, the physics FPS in the project settings is also 90 FPS. This makes the physics
# run at the same frame rate as the display, which makes things look smoother in VR!
using Godot;
using System;
public class Game : Spatial
{
public override void _Ready()
{
var vr = ARVRServer.FindInterface("OpenVR");
if (vr != null && vr.Initialize())
{
GetViewport().Arvr = true;
OS.VsyncEnabled = false;
Engine.TargetFps = 90;
// Also, the physics FPS in the project settings is also 90 FPS. This makes the physics
// run at the same frame rate as the display, which makes things look smoother in VR!
}
}
}
このコードの機能について説明します。
_ready
関数では、最初に ARVRServer の find_interface
関数を使用してOpenVR VRインターフェースを取得し、それを VR という変数に割り当てます。ARVRServer がOpenVRという名前のインターフェイスを見つけると、それを返します。それ以外の場合は null
を返します。
注釈
The OpenVR VR interface is not included with Godot by default. You will need to download the OpenVR asset from the Asset Library or GitHub.
次に、コードは2つの条件を組み合わせます。1つは VR 変数がnullではない (if VR
) かどうかをチェックし、もう1つはOpenVRインターフェイスが初期化できたかどうかに基づいてブール値を返す初期化関数を呼び出します 。 これらの条件の両方がtrueを返す場合、メインのGodot Viewport をARVRビューポートに変換できます。
If the VR interface initialized successfully, we then get the root Viewport and set the arvr property to true
. This will tell Godot to use the initialized
ARVR interface to drive the Viewport display.
最後に、VSyncを無効にして、1秒あたりのフレーム数(FPS)がコンピューターモニターによって制限されないようにします。 この後、ほとんどのVRヘッドセットの標準である 90
フレーム/秒でレンダリングするようにGodotに指示します。 VSyncを無効にしないと、通常のコンピューターモニターはVRヘッドセットのフレームレートをコンピューターモニターのフレームレートに制限する場合があります。
注釈
プロジェクト設定の Physics->Common
タブで、物理FPSが 90
に設定されています。 これにより、物理エンジンがVRディスプレイと同じフレームレートで実行され、VRでの物理反応がより滑らかに見えます。
Godotがプロジェクト内でOpenVRを起動するために必要なことはこれだけです! 必要に応じて試してみてください。 すべてが機能すると仮定すると、世界中を見ることができるようになります。 ルームトラッキングを備えたVRヘッドセットをお持ちの場合は、ルームトラッキングの制限内でシーン内を移動できます。
コントローラーの作成¶
Right now all that the VR user can do is stand around, which isn't really what we are going for unless we are working on a VR film. Lets write the code for the VR controllers. We are going to write all the code for the VR controllers in one go, so the code is rather long. That said, once we are finished you will be able to teleport around the scene, artificially move using the touchpad/joystick on the VR controller, and be able to pick up, drop, and throw RigidBody-based nodes.
最初に、VRコントローラーに使用するシーンを開く必要があります。Left_Controller.tscn
または Right_Controller.tscn
。ではシーンのセットアップ方法について簡単に説明します。
VRコントローラーシーンの設定方法¶
どちらのシーンでもルート ノードはARVRControllerノードです。唯一の違いは、Left_Controller
シーンの Controller Id
プロパティが 1
に設定されているのに対し、Right_Controller
プロパティには 2
が設定されている点です。
注釈
ARVRServer は、左右のVRコントローラーにこれら2つのIDを使用しようとします。3つ以上のコントローラー/追跡オブジェクトをサポートするVRシステムの場合、これらのIDの調整が必要になる場合があります。
次は Hand
MeshInstance ノードです。このノードは、VRコントローラーが :ref: RigidBody <class_RigidBody>` ノードを保持していないときに使用されるハンドメッシュを表示するために使用されます。Left_Controller
シーンの手は左手で、Right_Controller
シーンの手は右手です。
Raycast
という名前のノードは Raycast ノードで、VRコントローラーがテレポートするときにテレポートする場所を狙うために使用されます。Raycast の長さはY軸で -16
に設定され、手のポインターの指の外側を指すように回転します。Raycast
ノードには、単一の子ノード Mesh
があります。これは MeshInstance です。これは、テレポート Raycast が狙っている場所を視覚的に示すために使用されます。
Area
という名前のノードは、Area ノードで、VRコントローラーのグラブモードが AREA
に設定されている場合、RigidBody ベースのノードを取得するために使用されます。Area
ノードには、球体 CollisionShape を定義する単一の子ノード CollisionShape
があります。VRコントローラーがオブジェクトを保持していない状態でグラブボタンを押すと、Area
ノード内の最初の RigidBody ベースのノードが選択されます。
次は Grab_Pos
と呼ばれる Position3D ノードです。これは、RigidBody ノードを取得し、VRコントローラーによって保持される位置を定義するために使用されます。
Sleep_Area
という名前の大きな Area ノードは、CollisionShape 内のRigidBodyノードのスリープを無効にするために使用されます。これは、RigidBody ノードがスリープ状態になると、VRコントローラーがそれを取得できなくなるためです。Sleep_Area
を使用することで、ノード内のすべての RigidBody ノードがスリープできないようにし、VRコントローラーがそれを取得できるようにするコードを記述できます。
AudioStreamPlayer3D
と呼ばれる AudioStreamPlayer3D ノードには、オブジェクトがVRコントローラーによってピックアップ、ドロップ、またはスローされたときに使用するサウンドがロードされています。これはVRコントローラーの機能には必要ありませんが、オブジェクトをつかんだり落としたりすることがより自然に感じられます。
最後に、最後のノードは Grab_Cast
ノードと唯一の子ノードである Mesh
です。Grab_Cast
ノードは、VRコントローラーのグラブモードが RAYCAST
に設定されている場合、RigidBody ベースのノードを取得するために使用されます。これにより、VRコントローラーは、レイキャストを使用してわずかに手の届かないオブジェクトを取得できます。Mesh
ノードは、テレポート Raycast の照準を視覚的に示すために使用されます。
VR コントローラーシーンの設定方法と、ノードを使用してそれらの機能を提供する方法の概要を簡単に説明します。VR コントローラーのシーンを見てきたので、それらを駆動するコードを記述しましょう。
VRコントローラーのコード¶
シーンのルートノードである Right_Controller
または Left_Controller
を選択し、VR_Controller.gd
という新しいスクリプトを作成します。両方のシーンで同じスクリプトが使用されるため、どちらを最初に使用してもかまいません。VR_Controller.gd
を開いた状態で、次のコードを追加します:
ちなみに
このページからコードをコピーして、スクリプト エディタに直接貼り付けることができます。
If you do this, all the code copied will be using spaces instead of tabs.
スクリプトエディタでスペースをタブに変換するには、[編集]メニューをクリックし、[インデントをタブに変換]を選択します。これにより、すべてのスペースがタブに変換されます。タブをスペースに戻すには、[インデントをスペースに変換]を選択します。
extends ARVRController
var controller_velocity = Vector3(0,0,0)
var prior_controller_position = Vector3(0,0,0)
var prior_controller_velocities = []
var held_object = null
var held_object_data = {"mode":RigidBody.MODE_RIGID, "layer":1, "mask":1}
var grab_area
var grab_raycast
var grab_mode = "AREA"
var grab_pos_node
var hand_mesh
var hand_pickup_drop_sound
var teleport_pos = Vector3.ZERO
var teleport_mesh
var teleport_button_down
var teleport_raycast
# A constant to define the dead zone for both the trackpad and the joystick.
# See https://web.archive.org/web/20191208161810/http://www.third-helix.com/2013/04/12/doing-thumbstick-dead-zones-right.html
# for more information on what dead zones are, and how we are using them in this project.
const CONTROLLER_DEADZONE = 0.65
const MOVEMENT_SPEED = 1.5
const CONTROLLER_RUMBLE_FADE_SPEED = 2.0
var directional_movement = false
func _ready():
# Ignore the warnings the from the connect function calls.
# (We will not need the returned values for this tutorial)
# warning-ignore-all:return_value_discarded
teleport_raycast = get_node("RayCast")
teleport_mesh = get_tree().root.get_node("Game/Teleport_Mesh")
teleport_button_down = false
teleport_mesh.visible = false
teleport_raycast.visible = false
grab_area = get_node("Area")
grab_raycast = get_node("Grab_Cast")
grab_pos_node = get_node("Grab_Pos")
grab_mode = "AREA"
grab_raycast.visible = false
get_node("Sleep_Area").connect("body_entered", self, "sleep_area_entered")
get_node("Sleep_Area").connect("body_exited", self, "sleep_area_exited")
hand_mesh = get_node("Hand")
hand_pickup_drop_sound = get_node("AudioStreamPlayer3D")
connect("button_pressed", self, "button_pressed")
connect("button_release", self, "button_released")
func _physics_process(delta):
if rumble > 0:
rumble -= delta * CONTROLLER_RUMBLE_FADE_SPEED
if rumble < 0:
rumble = 0
if teleport_button_down == true:
teleport_raycast.force_raycast_update()
if teleport_raycast.is_colliding():
if teleport_raycast.get_collider() is StaticBody:
if teleport_raycast.get_collision_normal().y >= 0.85:
teleport_pos = teleport_raycast.get_collision_point()
teleport_mesh.global_transform.origin = teleport_pos
if get_is_active() == true:
_physics_process_update_controller_velocity(delta)
if held_object != null:
var held_scale = held_object.scale
held_object.global_transform = grab_pos_node.global_transform
held_object.scale = held_scale
_physics_process_directional_movement(delta);
func _physics_process_update_controller_velocity(delta):
controller_velocity = Vector3(0,0,0)
if prior_controller_velocities.size() > 0:
for vel in prior_controller_velocities:
controller_velocity += vel
controller_velocity = controller_velocity / prior_controller_velocities.size()
var relative_controller_position = (global_transform.origin - prior_controller_position)
controller_velocity += relative_controller_position
prior_controller_velocities.append(relative_controller_position)
prior_controller_position = global_transform.origin
controller_velocity /= delta;
if prior_controller_velocities.size() > 30:
prior_controller_velocities.remove(0)
func _physics_process_directional_movement(delta):
var trackpad_vector = Vector2(-get_joystick_axis(1), get_joystick_axis(0))
var joystick_vector = Vector2(-get_joystick_axis(5), get_joystick_axis(4))
if trackpad_vector.length() < CONTROLLER_DEADZONE:
trackpad_vector = Vector2(0,0)
else:
trackpad_vector = trackpad_vector.normalized() * ((trackpad_vector.length() - CONTROLLER_DEADZONE) / (1 - CONTROLLER_DEADZONE))
if joystick_vector.length() < CONTROLLER_DEADZONE:
joystick_vector = Vector2(0,0)
else:
joystick_vector = joystick_vector.normalized() * ((joystick_vector.length() - CONTROLLER_DEADZONE) / (1 - CONTROLLER_DEADZONE))
var forward_direction = get_parent().get_node("Player_Camera").global_transform.basis.z.normalized()
var right_direction = get_parent().get_node("Player_Camera").global_transform.basis.x.normalized()
# Because the trackpad and the joystick will both move the player, we can add them together and normalize
# the result, giving the combined movement direction
var movement_vector = (trackpad_vector + joystick_vector).normalized()
var movement_forward = forward_direction * movement_vector.x * delta * MOVEMENT_SPEED
var movement_right = right_direction * movement_vector.y * delta * MOVEMENT_SPEED
movement_forward.y = 0
movement_right.y = 0
if movement_right.length() > 0 or movement_forward.length() > 0:
get_parent().global_translate(movement_right + movement_forward)
directional_movement = true
else:
directional_movement = false
func button_pressed(button_index):
if button_index == 15:
_on_button_pressed_trigger()
if button_index == 2:
_on_button_pressed_grab()
if button_index == 1:
_on_button_pressed_menu()
func _on_button_pressed_trigger():
if held_object == null:
if teleport_mesh.visible == false:
teleport_button_down = true
teleport_mesh.visible = true
teleport_raycast.visible = true
else:
if held_object is VR_Interactable_Rigidbody:
held_object.interact()
func _on_button_pressed_grab():
if teleport_button_down == true:
return
if held_object == null:
_pickup_rigidbody()
else:
_throw_rigidbody()
hand_pickup_drop_sound.play()
func _pickup_rigidbody():
var rigid_body = null
if grab_mode == "AREA":
var bodies = grab_area.get_overlapping_bodies()
if len(bodies) > 0:
for body in bodies:
if body is RigidBody:
if !("NO_PICKUP" in body):
rigid_body = body
break
elif grab_mode == "RAYCAST":
grab_raycast.force_raycast_update()
if grab_raycast.is_colliding():
var body = grab_raycast.get_collider()
if body is RigidBody:
if !("NO_PICKUP" in body):
rigid_body = body
if rigid_body != null:
held_object = rigid_body
held_object_data["mode"] = held_object.mode
held_object_data["layer"] = held_object.collision_layer
held_object_data["mask"] = held_object.collision_mask
held_object.mode = RigidBody.MODE_STATIC
held_object.collision_layer = 0
held_object.collision_mask = 0
hand_mesh.visible = false
grab_raycast.visible = false
if held_object is VR_Interactable_Rigidbody:
held_object.controller = self
held_object.picked_up()
func _throw_rigidbody():
if held_object == null:
return
held_object.mode = held_object_data["mode"]
held_object.collision_layer = held_object_data["layer"]
held_object.collision_mask = held_object_data["mask"]
held_object.apply_impulse(Vector3(0, 0, 0), controller_velocity)
if held_object is VR_Interactable_Rigidbody:
held_object.dropped()
held_object.controller = null
held_object = null
hand_mesh.visible = true
if grab_mode == "RAYCAST":
grab_raycast.visible = true
func _on_button_pressed_menu():
if grab_mode == "AREA":
grab_mode = "RAYCAST"
if held_object == null:
grab_raycast.visible = true
elif grab_mode == "RAYCAST":
grab_mode = "AREA"
grab_raycast.visible = false
func button_released(button_index):
if button_index == 15:
_on_button_released_trigger()
func _on_button_released_trigger():
if teleport_button_down == true:
if teleport_pos != null and teleport_mesh.visible == true:
var camera_offset = get_parent().get_node("Player_Camera").global_transform.origin - get_parent().global_transform.origin
camera_offset.y = 0
get_parent().global_transform.origin = teleport_pos - camera_offset
teleport_button_down = false
teleport_mesh.visible = false
teleport_raycast.visible = false
teleport_pos = null
func sleep_area_entered(body):
if "can_sleep" in body:
body.can_sleep = false
body.sleeping = false
func sleep_area_exited(body):
if "can_sleep" in body:
# Allow the CollisionBody to sleep by setting the "can_sleep" variable to true
body.can_sleep = true
これはかなりの量のコードです。コードがステップごとに行うことを見ていきましょう。
VRコントローラーコードの説明¶
First, let's go through all the class variables in the script:
controller_velocity
: VRコントローラーの速度の大まかな近似値を保持する変数。prior_controller_position
: 3D空間でVRコントローラーの最後の位置を保持する変数。prior_controller_velocities
: 最後に計算された30個のVRコントローラー速度を保持する配列。これは、時間の経過とともに速度計算を滑らかにするために使用されます。held_object
: VRコントローラーが保持しているオブジェクトへの参照を保持する変数。VRコントローラーがオブジェクトを保持していない場合、この変数はnull
になります。held_object_data
: VRコントローラーによって保持されている RigidBody ノードのデータを保持するdictionary。これは、RigidBody のデータが保持されなくなったときにリセットするために使用されます。grab_area
: VRコントローラーでオブジェクトを取得するために使用される Area ノードを保持する変数。grab_raycast
: VRコントローラーでオブジェクトを取得するために使用される Raycast ノードを保持する変数。grab_mode
: VRコントローラーが使用しているグラブモードを定義する変数。このチュートリアルでオブジェクトを取得するモードは、AREA
とRAYCAST
の2つだけです。grab_pos_node
: 保持されたオブジェクトの位置と回転を更新するために使用されるノードを保持する変数。hand_mesh
: VRコントローラーのハンドメッシュを含む MeshInstance ノードを保持する変数。このメッシュは、VRコントローラーが何も保持していないときに表示されます。hand_pickup_drop_sound
: ピックアップ/ドロップサウンドを含む AudioStreamPlayer3D ノードを保持する変数。teleport_pos
: VRコントローラーがプレイヤーをテレポートするときにプレイヤーがテレポートされる位置を保持する変数。teleport_mesh
: プレイヤーのテレポート先を示すために使用される MeshInstance ノードを保持する変数。teleport_button_down
: コントローラーのテレポートボタンが押されているかどうかを追跡するために使用される変数。これは、このVRコントローラーがプレイヤーをテレポートしようとしているかどうかを検出するために使用されます。teleport_raycast
: テレポート位置の計算に使用される Raycast ノードを保持する変数。このノードには、照準の「レーザーサイト」 として機能する MeshInstance もあります。CONTROLLER_DEADZONE
: トラックパッドとVRコントローラーのジョイスティックの両方のデッドゾーンを定義する定数。詳細については、以下の注を参照してください。MOVEMENT_SPEED
: トラックパッド/ジョイスティックを使用して人為的に移動するときにプレイヤーが移動する速度を定義する定数。CONTROLLER_RUMBLE_FADE_SPEED
: VRコントローラーのランブルフェードの速度を定義する定数。directional_movement
: このVRコントローラーがタッチパッド/ジョイスティックを使用してプレイヤーを動かしているかどうかを保持する変数。
注釈
ジョイパッド / コントローラーのデッドゾーンの処理方法について、すべてを説明する素晴らしい記事がここにあります (英語)。
VRコントローラーのジョイスティック/タッチパッド用にその記事で提供されているスケーリングされた放射状デッドゾーンコードの翻訳バージョンを使用しています。この記事は素晴らしい読み物です。ぜひご覧ください!
これはかなりの数のクラス変数です。それらのほとんどは、コード全体で必要なノードへの参照を保持するために使用されます。次に _ready
関数から始めて、関数を見てみましょう。
_ready
関数のステップごとの説明¶
まず、 connect
関数によって返された値を使用しないことに関する警告を黙らせるようにGodotに指示します。このチュートリアルでは戻り値は必要ありません。
次に、テレポートの位置を決定するために使用する Raycast ノードを取得し、それを teleport_raycast
変数に割り当てます。次に、プレイヤーがテレポートする場所を示すために使用する MeshInstance ノードを取得します。テレポートに使用しているノードは、Game
シーンの子です。これにより、テレポートメッシュノードがVRコントローラーの変更の影響を受けなくなり、テレポートメッシュを両方のVRコントローラーで使用できるようになります。
次に、teleport_button_down
変数がfalseに設定され、teleport_mesh.visible
と teleport_raycast.visible
が共に false
に設定されます。これは、プレイヤーをテレポートするのではなく、初期状態にプレイヤーをテレポートするための変数を設定します。
次に、コードは grab_area
ノード、grab_raycast
ノード、および grab_pos_node
ノードを取得し、それらをすべて後で使用するためにそれぞれの変数に割り当てます。
次に grab_mode
が AREA
に設定されているため、VRコントローラーのグラブ/グリップボタンが押されると、VRコントローラーは grab_area
で定義された Area ノードを使用してオブジェクトをつかもうとします。また、grab_raycast
ノードの visible
プロパティを false
に設定して、grab_raycast
の laser sight
子ノードが表示されないようにします。
その後、VRコントローラーの Sleep_Area
ノードからの body_entered
および body_exited
シグナルを sleep_area_entered
および sleep_area_exited
関数に接続します。sleep_area_entered
および sleep_area_exited
関数は、VRコントローラーの近くでスリープすることができない RigidBody ノードを作成するために使用されます。
次に、hand_mesh
および hand_pickup_drop_sound
ノードが取得され、後で使用するためにそれぞれの変数に割り当てられます。
最後に、VRコントローラーが拡張する ARVRController ノードの button_pressed
および button_release
シグナルは 、それぞれ button_pressed
および button_released
関数に接続されます。つまり、VRコントローラーのボタンが押されたり離されたりすると、このスクリプトで定義されている button_pressed
または button_released
関数が呼び出されます。
_physics_process
関数のステップごとの説明¶
まず、rumble
変数がゼロ以上かどうかを確認します。ARVRController ノードのプロパティである rumble
変数がゼロより大きい場合、VRコントローラーが鳴ります。
rumble
変数がゼロより大きい場合、 CONTROLLER_RUMBLE_FADE_SPEED
にデルタを掛けることにより、毎秒 CONTROLLER_RUMBLE_FADE_SPEED
によってrumbleを減らします。次に、rumble
がゼロ未満かどうかをチェックする if
条件式があり、その値がゼロ未満の場合に rumble
をゼロに設定します。
この小さなコードのセクションは、VRコントローラーの振動を減らすために必要なすべてです。 ``rumble``に値を設定すると、このコードは時間とともに自動的にフェードします。
コードの最初のセクションでは、teleport_button_down
変数が true
に等しいかどうかを確認します。これは、このVRコントローラーがテレポートしようとしていることを意味します。
teleport_button_down
が true
に等しい場合、force_raycast_update
関数を使用して teleport_raycast
Raycast ノードを強制的に更新します。force_raycast_update
関数は、Raycast ノード内のプロパティを物理世界の最新バージョンで更新します。
次に、コードは teleport_raycast
の is_colliding
関数をチェックすることにより、teleport_raycast
が何かと衝突したかどうかを確認します。Raycast が何かと衝突した場合、レイキャストが衝突した PhysicsBody が StaticBody かどうかを確認します。次に、レイキャストによって返されたコリジョン法線ベクトルがY軸の `` 0.85`` 以上であるかどうかを確認します。
注釈
これは、ユーザーがRigidBodyノードにテレポートできないようにし、床のような表面にプレイヤーがテレポートできるようにするためです。
これらすべての条件が満たされている場合、teleport_raycast
の get_collision_point
関数に teleport_pos
変数を割り当てます。これは、レイスペースがワールド空間で衝突した位置に teleport_pos
を割り当てます。それから teleport_mesh
を teleport_pos
に保存されているワールド位置に移動します。
コードのこのセクションは、プレイヤーがテレポートレイキャストで狙っている位置を取得し、テレポートメッシュを更新し、テレポートボタンを離したときにユーザーがテレポートする場所を視覚的に更新します。
コードの次のセクションでは、最初に、ARVRController で定義される get_is_active
関数を介してVRコントローラーがアクティブかどうかを確認します。VRコントローラーがアクティブな場合、_ physics_process_update_controller_velocity
関数を呼び出します。
_physics_process_update_controller_velocity
関数は、位置の変化を通してVRコントローラーの速度を計算します。それは完全ではありませんが、このプロセスはVRコントローラーの速度の大まかな概念を取得します。これは、このチュートリアルの目的には適しています。
コードの次のセクションでは、held_object
変数が null
と等しくないかどうかを確認して、VRコントローラーがオブジェクトを保持しているかどうかを確認します。
VRコントローラーがオブジェクトを保持している場合、最初に held_scale
と呼ばれる一時変数にそのスケールを保存します。次に、保持されたオブジェクトの global_transform
を held_object
ノードの global_transform
に設定します。これにより、保持されたオブジェクトは、ワールド空間の grab_pos_node
ノードと同じ位置、回転、スケールになります。
ただし、保持されたオブジェクトが取得されたときにスケールが変更されないようにするには、held_object
ノードの scale
プロパティを held_scale
に戻す必要があります。
コードのこのセクションは、保持されたオブジェクトをVRコントローラーと同じ位置と回転に保ち、VRコントローラーとの同期を維持します。
Finally, the last section of code simply calls the _physics_process_directional_movement
function. This function contains all the code for moving the player when the
touchpad/joystick on the VR controller moves.
_physics_process_update_controller_velocity
関数のステップごとの説明¶
最初に、この関数は controller_velocity
変数をゼロにリセットします Vector3。
次に、prior_controller_velocities
配列に保存/キャッシュされたVRコントローラーの速度が保存されているかどうかを確認します。size()
関数が 0
より大きい値を返すかどうかを確認することでこれを行います。prior_controller_velocities
内にキャッシュされた速度がある場合、for
ループを使用して、保存されている各速度を反復処理します。
キャッシュされた速度のそれぞれについて、その値を controller_velocity
に加算するだけです。コードが prior_controller_velocities
のキャッシュされたすべての速度を通過したら、controller_velocity
を prior_controller_velocities
配列のサイズで除算します。これにより、合成された速度値が得られます。これにより、以前の速度が考慮され、コントローラーの速度の方向がより正確になります。
次に、最後の _physics_process
関数呼び出し以降にVRコントローラーが取った位置の変化を計算します。これを行うには、VRコントローラーのグローバル位置 global_transform.origin
から prior_controller_position
を減算します。これにより、prior_controller_position
の位置からVRコントローラーの現在の位置を指す Vector3 が得られ、これを relative_controller_position
という変数に保存します。
次に、位置の変更を controller_velocity
に加算して、位置の最新の変更が速度計算で考慮されるようにします。次に、relative_controller_position
を prior_controller_velocities
に加算して、次回のVRコントローラーの速度計算で考慮できるようにします。
次に、prior_controller_position
がVRコントローラーのグローバル位置 global_transform.origin
で更新されます。次に、controller_velocity
を delta
で除算し、速度を上げて、期待通りの結果をもたらしますが、経過時間との相対的な関係を維持します。これは完全な解決策ではありませんが、ほとんどの場合、結果は適切に見えます。このチュートリアルの目的には十分です。
最後に、関数は、size()
関数が 30
より大きい値を返すかどうかをチェックすることにより、prior_controller_velocities
が 30
を超える速度をキャッシュしているかどうかを確認します。prior_controller_velocities
に 30
を超えるキャッシュ速度が保存されている場合、remove
関数を呼び出してインデックス位置 0
を渡すことで、最も古いキャッシュ速度を削除します。
この関数が最終的に行うことは、最後の30回の _physics_process
呼び出しでのVRコントローラーの相対的な位置の変化を計算することにより、VRコントローラーの速度の大まかなアイデアを得るということです。これは完全ではありませんが、VRコントローラーが3D空間でどれだけ速く動いているかについての適切なアイデアを提供します。
_physics_process_directional_movement
関数のステップごとの説明¶
最初に、この関数はトラックパッドとジョイスティックの軸を取得し、それらをそれぞれ trackpad_vector
および joystick_vector
と呼ばれる Vector2 変数に割り当てます。
注釈
VRヘッドセットとコントローラーによっては、ジョイスティックやタッチパッドのインデックス値の再マッピングが必要になる場合があります。このチュートリアルの入力は、Windows Mixed Realityヘッドセットのインデックス値です。
次に、trackpad_vector
と joystick_vector
のデッドゾーンを考慮します。このコードは以下の記事で詳しく説明されていますが、コードがC#からGDScriptに変換されたので若干の変更が加えられています。
trackpad_vector
変数と joystick_vector
変数のデッドゾーンが考慮されると、コードは ARVRCamera のグローバルトランスフォームに関連する順方向および右方向のベクトルを取得します。これが行うことは、ワールド空間でユーザーカメラ ARVRCamera の回転に対して前方および右側を指すベクトルを提供することです。これらのベクトルは、ローカル空間モード
ボタンを有効にしてGodotエディタでオブジェクトを選択すると、青と赤の矢印の同じ方向を指します。順方向ベクトルは forward_direction
と呼ばれる変数に保存され、右方向ベクトルは right_direction
と呼ばれる変数に保存されます。
次に、コードは trackpad_vector
変数と joystick_vector
変数を一緒に加算し、normalized
関数を使用して結果を正規化します。これにより、両方の入力デバイスの移動方向が結合されるため、ユーザーを移動するために単一の Vector2 を使用できます。合成された方向を movement_vector
と呼ばれる変数に割り当てます。
次に、forward_direction
に保存されている前方方向を基準にして、ユーザーが前方に移動する距離を計算します。これを計算するために、forward_direction
に movement_vector.x
、delta
、MOVEMENT_SPEED
を掛けます。これにより、トラックパッド/ジョイスティックを前方または後方に押したときにユーザーが前(後)に移動する距離がわかります。これを movement_forward
という変数に割り当てます。
right_direction
に保存されている右の方向を基準にして、ユーザーが右に移動する距離についても同様の計算を行います。ユーザーが右に移動する距離を計算するには、right_direction
に movement_vector.y
、delta
、MOVEMENT_SPEED
を乗算します。これにより、トラックパッド/ジョイスティックを右または左に押したときにユーザーが(左)右に移動する距離がわかります。これを movement_right
という変数に割り当てます。
次に、movement_forward
と movement_right
の Y
軸上の動きを、それらの Y
値を 0
に割り当てることで削除します。これは、ユーザーがトラックパッドまたはジョイスティックを動かすだけで飛行/落下できないようにするためです。これを行わないと、プレイヤーは向いている方向に飛ぶことができます。
最後に、movement_right
または movement_forward
の length
関数が 0
より大きいかどうかを確認します。そうである場合、ユーザーを移動する必要があります。ユーザーを移動するには、get_parent().global_translate
を使用して ARVROrigin <class_ARVROrigin> ノードへのグローバル変換を実行し、``movement_forward` 変数を加算した movement_right
変数を渡します。これにより、VRヘッドセットの回転に対して、トラックパッド/ジョイスティックが指している方向にプレイヤーが移動します。また、directional_movement
変数を true
に設定して、このVRコントローラーがプレイヤーを動かしていることをコードが認識できるようにします。
movement_right
または movement_forward
の length
関数が 0
以下の場合、directional_movement
変数を false
に設定するだけで、コードはこのVRコントローラーがプレイヤーを動かしていないことを認識します。
この関数が最終的に行うことは、VRコントローラーのトラックパッドとジョイスティックから入力を受け取り、プレイヤーが押している方向にプレイヤーを移動させることです。動きはVRヘッドセットの回転に関連するため、プレイヤーが前方に押して頭を左に回すと、左に動きます。
_pickup_rigidbody
関数のステップごとの説明¶
最初に、関数は rigid_body
と呼ばれる変数を作成します。これは、ピックアップするRigidBodyがあると仮定して、VRコントローラーがピックアップする RigidBody を格納するために使用します。
次に、関数は grab_mode
変数が AREA
と等しいかどうかを確認します。そうである場合、get_overlapping_bodies
関数を使用して、grab_area
内のすべての PhysicsBody ノードを取得します。この関数は PhysicsBody ノードの配列を返します。PhysicsBody の配列を bodies
と呼ばれる新しい変数に割り当てます。
次に、bodies
変数の長さが 0
を超えているかどうかを確認します。そうである場合、forループを使用して body
の PhysicsBody ノードのそれぞれを調べます。
各 PhysicsBody ノードにごとに、if body is RigidBody
を使用して RigidBody ノードもしくは、それを拡張したものかを確認し、PhysicsBody ノードが :ref` RigidBody <class_RigidBody> ` ノードもしくは、それを拡張したものの場合は true
を返します。オブジェクトが RigidBody の場合、ボディに NO_PICKUP
という変数/定数が定義されていないことを確認します。これは、ピックアップできない RigidBody ノードが必要な場合、NO_PICKUP
という定数/変数を定義するだけで、VRコントローラーがそれを拾えなくなります。RigidBody ノードに NO_PICKUP
という名前で定義された変数/定数がない場合、refid_body
変数を RigidBody ノードに割り当て、forループを中断します。
コードのこのセクションは、grab_area
内のすべてのphysicsボディを通過し、NO_PICKUP
という名前の変数/定数を持たない最初の RigidBody ノードを取得します。それを rigid_body
変数に割り当てるので、この関数の後半で追加の後処理を行うことができます。
grab_mode
変数が AREA
と等しくない場合、代わりに RAYCAST
と等しいかどうかを確認します。RAYCAST
と等しい場合、force_raycast_update
関数を使用して grab_raycast
ノードを強制的に更新します。force_raycast_update
関数は、物理世界の最新の変更内容で Raycast を更新します。次に、 is_colliding
関数を使用して grab_raycast
ノードが何かと衝突したかどうかを確認します。これは、Raycast が何かにヒットした場合にtrueを返します。
grab_raycast
が何かにヒットした場合、get_collider
関数を使用して PhysicsBody ノードにヒットします。次にコードは、ノードヒットが PhysicsBody の場合に if body is RigidBody
を使用して、PhysicsBody ノードが RigidBody ノード、もしくはそれを拡張したノードの場合に true
を返します。次に、コードは RigidBody ノードに NO_PICKUP
という名前の変数がないかどうかを確認し、ない場合は rigid_body
変数に RigidBody ノードを割り当てます。
コードのこのセクションが行っていることは、grab_raycast
Raycast ノードを送信し、NO_PICKUP
という名前の変数/定数を持っていない RigidBody ノードと衝突したかどうかをチェックすることです。`` NO_PICKUP`` なしのRigidBodyと衝突した場合、そのノードを rigid_body
変数に割り当てるため、この関数で後から追加の後処理を行うことができます。
コードの最後のセクションでは、最初に rigid_body
が null
と等しくないかどうかを確認します。rigid_body
が null
に等しくない場合、VRコントローラーは RigidBody ベースのノードを検出できます。
ピックアップするVRコントローラーがある場合は、rigid_body
に保存されている RigidBody ノードに held_object
を割り当てます。次に、mode
、layer
、および mask
をそれぞれの値のキーとして使用して、RigidBody <class_RigidBody>`` ノードの mode
、collision_layer
、および collision_mask
を held_object_data
に保存します。これは、後でオブジェクトがVRコントローラーによってドロップされたときにそれらを再適用できるようにするためです。
次に、RigidBody のmodeを MODE_STATIC
に設定します。これは collision_layer
をゼロに設定し、collision_mask
もゼロに設定します。これにより、保持されている RigidBody がVRコントローラーで保持されている場合、物理世界の他のオブジェクトと対話できなくなります。
次に、visible
プロパティを false
に設定することにより、hand_mesh
MeshInstance を非表示にします。これは、手がホールドされたオブジェクトの邪魔にならないようにするためです。同様に、grab_raycast``「レーザーサイト」は、\ ``visible
プロパティを false
に設定することで非表示になります。
次に、コードは、保持されているオブジェクトが VR_Interactable_Rigidbody
というクラスを拡張しているかどうかを確認します。その場合、held_object
の controller
という変数を self
に設定し、held_object
の picked_up
関数を呼び出します。まだ VR_Interactable_Rigidbody
を作成していませんが、これが行うことは、picked_up
関数を呼び出すことで、controller
変数に保存されているVRコントローラーによってコントローラーへの参照が 保持されていることを VR_Interactable_Rigidbody
クラスに伝えることです。
ちなみに
ご心配なく。VR_Interactable_Rigidbody
については、このセクションの後で取りあげます!
このチュートリアルシリーズのパート2を完了した後、コードはより意味のあるものになるはずです。そこでは実際に VR_Interactable_Rigidbody
を使用します。
コードのこのセクションは、もしも gurb Area または Raycast を使用して RigidBody を検出した場合、VRコントローラーでそれを運べるようにセットアップします。
_throw_rigidbody
関数のステップごとの説明¶
最初に、関数は held_object
変数が null
に等しいかどうかを確認することにより、VRコントローラーがオブジェクトを保持していないかどうかを確認します。そうである場合は、単に return
を呼び出すため、何も起こりません。必ず、オブジェクトが保持されている場合にのみ _throw_rigidbody
関数を呼び出す必要があります。このチェックは、奇妙なことが発生した場合に、この関数が期待どおりに反応することを保証します。
VRコントローラーがオブジェクトを保持しているかどうかを確認した後、それを想定し、保存された RigidBody データを保持されたオブジェクトに戻します。held_object_data
dictionaryに保存されている mode
、layer
および mask
データを取得し、それを held_object
のオブジェクトに再適用します。これにより、RigidBody がピックアップされる前の状態に戻ります。
次に、held_object
で apply_impulse
を呼び出して、RigidBody がVRコントローラーの速度 controller_velocity
の方向にスローされるようにします。
次に、保持されているオブジェクトが VR_Interactable_Rigidbody
というクラスを拡張しているかどうかを確認します。もしそうなら、 held_object
の dropped
と呼ばれる関数を呼び出し、held_object.controller
を null
に設定します。まだ VR_Interactable_Rigidbody
を作成していませんが、これは droppped
関数を呼び出すので、RigidBody はドロップ時に必要なことを何でも行うことができ、RigidBody が保持されていないことがわかるように、controller
変数を null
に設定します。
ちなみに
ご心配なく。VR_Interactable_Rigidbody
については、このセクションの後で取りあげます!
このチュートリアルシリーズのパート2を完了した後、コードはより意味のあるものになるはずです。そこでは実際に VR_Interactable_Rigidbody
を使用します。
held_object
が VR_Interactable_Rigidbody
を拡張しているかどうかに関係なく、held_object
を null
に設定して、VRコントローラーがもう何も保持していないことを認識します。 VRコントローラーはもう何も保持していないので、hand_mesh.visible
をtrueに設定することで hand_mesh
を可視化します。
最後に、grab_mode
変数が RAYCAST
に設定されている場合、grab_raycast.visible
を true
に設定するため、grab_raycast
の Raycast「レーザーサイト」が表示されます。
sleep_area_entered
関数のステップごとの説明¶
この関数のコードの唯一のセクションは、Sleep_Area
ノードに入った PhysicsBody ノードに can_sleep
という変数があるかどうかを確認します。存在する場合、can_sleep
変数を false
に設定し、sleeping
変数を false
に設定します。
これを行わないと、VRコントローラーが PhysicsBody ノードと同じ位置にある場合でも、PhysicsBody ノードをVRコントローラーで取得することはできません。これを回避するには、VRコントローラーの近くにある PhysicsBody ノードを単に 「ウェイクアップ」します。
sleep_area_exited
関数のステップごとの説明¶
この関数のコードの唯一のセクションは、Sleep_Area
ノードに入った PhysicsBody ノードに can_sleep
という変数があるかどうかを確認します。存在する場合、can_sleep
変数を true
に設定します。
これにより、Sleep_Area
から出る RigidBody ノードが再びスリープ状態になり、パフォーマンスが節約されます。
わかりました、ふう!これは大量のコードでした!同じスクリプト VR_Controller.gd
を他のVRコントローラーシーンに追加して、両方のVRコントローラーが同じスクリプトを持つようにします。
あとは、プロジェクトをテストする前に1つのことを行うだけです!現在、VR_Interactable_Rigidbody
というクラスを参照していますが、まだ定義していません。このチュートリアルでは VR_Interactable_Rigidbody
を使用しませんが、プロジェクトを実行できるようにすばやく作成してみましょう。
対話可能なVRオブジェクトの基本クラスを作成する¶
Script
タブを開いたまま、VR_Interactable_Rigidbody.gd
という新しいGDScriptを作成します。
ちなみに
[ファイル] -> [新規スクリプト...] を押すことで、Script
タブでGDScriptを作成できます。
VR_Interactable_Rigidbody.gd
を開いたら、次のコードを追加します:
class_name VR_Interactable_Rigidbody
extends RigidBody
# (Ignore the unused variable warning)
# warning-ignore:unused_class_variable
var controller = null
func _ready():
pass
func interact():
pass
func picked_up():
pass
func dropped():
pass
このスクリプトを簡単に見ていきましょう。
まず、class_name VR_Interactable_Rigidbody
でスクリプトを開始します。これは、このGDScriptが VR_Interactable_Rigidbody
と呼ばれる新しいクラスであることをGodotに伝えます。これにより、スクリプトを直接ロードしたり特別なことをしたりする必要もなしに、すべての組み込みGodotクラスと同様に、ノードを他のスクリプトファイルの VR_Interactable_Rigidbody
クラスと突き合せることができます。
次は controller
と呼ばれるクラス変数です。controller
は、現在オブジェクトを保持しているVRコントローラーへの参照を保持するために使用されます。VRコントローラーがオブジェクトを保持していない場合、controller
変数は null
になります。 VRコントローラーへの参照が必要な理由は、保持されたオブジェクトが controller_velocity
のようなVRコントローラー固有のデータにアクセスできるようにするためです。
最後に、4つの関数があります。_ready
関数はGodotによって定義され、オブジェクトが VR_Interactable_Rigidbody
のシーンに追加されたときに行うべきことは何もないので、単に pass
を持つだけです。
interact
関数は、オブジェクトが保持されているときにVRコントローラーの対話ボタン(この場合はトリガー)が押されたときに呼び出されるスタブ関数です。
ちなみに
スタブ関数は、定義されているがコードを持たない関数です。スタブ関数は通常、上書きまたは拡張されるように設計されています。このプロジェクトでは、スタブ関数を使用しているため、すべての対話可能な RigidBody オブジェクト全体で一貫したインターフェイスがあります。
picked_up
および dropped
関数は、オブジェクトがVRコントローラーによってピックアップおよびドロップされるときに呼び出されるスタブ関数です。
今のところ私たちが行うべきことはそれだけです!このチュートリアルシリーズの次のパートでは、相互作用可能な特別な RigidBody オブジェクトの作成を開始します。
基本クラスが定義されたので、VRコントローラーのコードが機能するはずです。先に進み、もう一度ゲームを試してください。タッチパッドを押すとテレポートでき、グラブ/グリップボタンを使用してオブジェクトをつかんで投げることができます。
さて、トラックパッドやジョイスティックを使用して移動してみたくなるかもしれませんが、乗り物酔いになる場合があります!
この乗り物酔いを感じる主な理由の1つは、体は動いていないのに、視力は動いていることを伝えて来るからです。この信号の衝突により、気分が悪くなることがあります。 VRでの移動中の乗り物酔いを軽減するために、ビネットシェーダーを追加しましょう!
乗り物酔いの軽減¶
注釈
There are plenty of ways to reduce motion sickness in VR, and there is no one perfect way to reduce motion sickness. See this page on the Oculus Developer Center for more information on how to implement locomotion and reducing motion sickness.
移動中の乗り物酔いを軽減するために、プレイヤーが移動している間のみ表示されるビネットエフェクトを追加します。
First, quickly switch back to Game.tscn
. Under the ARVROrigin node there is a child node called Movement_Vignette
. This node is going to apply a simple
vignette to the VR headset when the player is moving using the VR controllers. This should help reduce motion sickness.
Open up Movement_Vignette.tscn
, which you can find in the Scenes
folder. The scene is just a ColorRect node with a custom
shader. Feel free to look at the custom shader if you want, it is just a slightly modified version of the vignette shader you can find in the
Godot demo repository.
プレイヤーが動いているときにビネットシェーダーを表示するコードを記述しましょう。Movement_Vignette
ノードを選択し、Movement_Vignette.gd
という名前の新しいスクリプトを作成します。次のコードを追加します:
extends Control
var controller_one
var controller_two
func _ready():
yield(get_tree(), "idle_frame")
yield(get_tree(), "idle_frame")
yield(get_tree(), "idle_frame")
yield(get_tree(), "idle_frame")
var interface = ARVRServer.primary_interface
if interface == null:
set_process(false)
printerr("Movement_Vignette: no VR interface found!")
return
rect_size = interface.get_render_targetsize()
rect_position = Vector2(0,0)
controller_one = get_parent().get_node("Left_Controller")
controller_two = get_parent().get_node("Right_Controller")
visible = false
func _process(_delta):
if controller_one == null or controller_two == null:
return
if controller_one.directional_movement == true or controller_two.directional_movement == true:
visible = true
else:
visible = false
このスクリプトはかなり短いため、スクリプトの機能について簡単に説明します。
ビネットのコードの説明¶
controller_one
と controller_two
の2つのクラス変数があります。これらの変数は、左右のVRコントローラーへの参照を保持します。
_ready
関数では、最初に yield
を使用して4つのフレームを待ちます。 4つのフレームを待機している理由は、VRインターフェイスの準備ができており、アクセスできるようにするためです。
待機後、ARVRServer.primary_interface
を使用してプライマリVRインターフェースが取得され、interface
という変数に割り当てられます。次に、コードは interface
が null
と等しいかどうかを確認します。interface
が null
に等しい場合、_process
は、値が false
の set_process
を使用して無効にされます。
interface
が null
でない場合、ビネットシェーダーの rect_size
をVRビューポートのレンダリングサイズに設定して、画面全体を占めるようにします。VRヘッドセットごとに解像度とアスペクト比が異なるため、これを行う必要があります。したがって、それに応じてノードのサイズを変更する必要があります。 また、ビネットシェーダーの rect_position
をゼロに設定して、画面に対して正しい位置に配置します。
次に、左右のVRコントローラーが取得され、それぞれ controller_one
と controller_two
に割り当てられます。 最後に、ビネットシェーダーは、デフォルトで visible
プロパティを false
に設定することで非表示になります。
_process
では、コードはまず controller_one
または controller_two
が null
と等しいかどうかをチェックします。いずれかのノードが null
に等しい場合は、return
が実行されるため、何も起こりません。
次に、コードは、controller_one
または controller_two
で directional_movement
が true
に等しいかどうかを確認することにより、VRコントローラーのいずれかがタッチパッド/ジョイスティックを使用してプレイヤーを動かしているかどうかを確認します。どちらかのVRコントローラーがプレイヤーを動かしている場合、ビネットシェーダーは visible
プロパティを true
に設定することで自身を可視にします。 どちらのVRコントローラーもプレイヤーを動かしていないので、両方のVRコントローラーで directional_movement
が false
である場合は、ビネットシェーダーは、その visible
プロパティを false
に設定することで自身を非表示にします。
これがスクリプトの全てです! コードを作成したので、トラックパッドやジョイスティックで動いてみましょう。 以前よりも乗り物酔いが少ないことに気付くはずです!
注釈
As previously mentioned, there are plenty of ways to reduce motion sickness in VR. Check out this page on the Oculus Developer Center for more information on how to implement locomotion and reducing motion sickness.
最終ノート¶
これで、環境内を移動して RigidBody ベースのオブジェクトとやり取りできる完全に機能するVRコントローラーが完成しました。このチュートリアルシリーズの次のパートでは、プレイヤーが使用する特別な RigidBody ベースのオブジェクトを作成します!
警告
このチュートリアルシリーズの完成したプロジェクトは、リリースタブの下のGodot OpenVR GitHubリポジトリからダウンロードできます!