|
@@ -15,7 +15,7 @@ Keep in mind, **one of the most important things when making VR content is getti
|
|
|
It can take lots of practice and iterations to get this right, but there are a few things you can do to make it easier:
|
|
|
|
|
|
- In VR, 1 unit is often considered 1 meter. If you design your assets around that standard, you can save yourself a lot of headache.
|
|
|
-- In your 3D model program, see if there is a way to measure and use real world distances. In Blender you can use the MeasureIt add-on, in Maya you can use the Measure Tool.
|
|
|
+- In your 3D model program, see if there is a way to measure and use real world distances. In Blender, you can use the MeasureIt add-on; in Maya, you can use the Measure Tool.
|
|
|
- You can make rough models using a tool like `Google Blocks <https://vr.google.com/blocks/>`_, and then refine in another 3D modelling program.
|
|
|
- Test often, as the assets can look dramatically different in VR than on a flat screen!
|
|
|
|
|
@@ -24,19 +24,19 @@ Throughout the course of this tutorial, we will cover:
|
|
|
- How to tell Godot to run in VR.
|
|
|
- How to make a teleportation system for moving the player.
|
|
|
- How to make a directional movement system (locomotion) for moving the player.
|
|
|
-- How to make a :ref:`RigidBody <class_RigidBody>` based pick up and drop system.
|
|
|
-- How to make various items that can be used in VR
|
|
|
+- How to make a :ref:`RigidBody <class_RigidBody>`-based pick up and drop system.
|
|
|
+- How to make various items that can be used in VR.
|
|
|
|
|
|
.. note:: While this tutorial can be completed by beginners, it is highly
|
|
|
advised to complete :ref:`doc_your_first_game`,
|
|
|
if you are new to Godot and/or game development and have some experience with making 3D games
|
|
|
**before** going through this tutorial series.
|
|
|
|
|
|
- This tutorial assumes you know have experience working with the Godot editor,
|
|
|
+ This tutorial assumes you know how to and have experience working with the Godot editor,
|
|
|
have basic programming experience in GDScript, and have basic 3D game development experience.
|
|
|
|
|
|
- Also, it is assumed you have both a OpenVR-ready headset and two OpenVR-ready controllers! This tutorial was written using a Windows Mixed Reality headset on Windows 10,
|
|
|
- so the tutorial is written to work on that headset. You may need to adjust the code to work with other VR headsets such as the Oculus Rift or HTC Vive.
|
|
|
+ Also, it is assumed you have both an OpenVR-ready headset and two OpenVR-ready controllers! This tutorial was written using a Windows Mixed Reality headset on Windows 10,
|
|
|
+ so the tutorial is written to work on that headset. You may need to adjust the code to work with other VR headsets, such as the Oculus Rift or HTC Vive.
|
|
|
|
|
|
You can find the start assets for this tutorial here: :download:`VR_Starter_Tutorial_Start.zip <files/VR_Starter_Tutorial_Start.zip>`
|
|
|
|
|
@@ -63,7 +63,7 @@ Getting everything ready
|
|
|
Launch Godot and open up the project included in the starter assets.
|
|
|
|
|
|
.. note:: While these assets are not necessarily required to use the scripts provided in this tutorial,
|
|
|
- they will make the tutorial much easier to follow as there are several premade scenes we
|
|
|
+ they will make the tutorial much easier to follow, as there are several premade scenes we
|
|
|
will be using throughout the tutorial series.
|
|
|
|
|
|
First, you may notice there is already quite a bit set up. This includes a pre-built level, several instanced scenes placed around,
|
|
@@ -73,7 +73,7 @@ You may notice that the GUI-related meshes already have a script attached to the
|
|
|
on the mesh. Feel free to take a look if you want, but this tutorial will not be going over how to use the :ref:`Viewport <class_Viewport>` nodes for making 3D GUI
|
|
|
:ref:`MeshInstance <class_MeshInstance>` nodes.
|
|
|
|
|
|
-The other thing to notice before we jump into writing the code is how the :ref:`ARVROrigin <class_ARVROrigin>` node works. How it works is kind of hard to explain,
|
|
|
+The other thing to notice, before we jump into writing the code, is how the :ref:`ARVROrigin <class_ARVROrigin>` node works. How it works is kind of hard to explain,
|
|
|
especially if you have never used VR before, but here is the gist of it:
|
|
|
The :ref:`ARVROrigin <class_ARVROrigin>` node is the center point of the room. If there is no room-scale tracking, then the :ref:`ARVROrigin <class_ARVROrigin>` will
|
|
|
be directly below the player, but if there is room-scale tracking, then the :ref:`ARVROrigin <class_ARVROrigin>` will be the center of the tracked room.
|
|
@@ -85,7 +85,7 @@ be directly below the player, but if there is room-scale tracking, then the :ref
|
|
|
If you select the :ref:`ARVROrigin <class_ARVROrigin>` node, you may notice that the world scale is set to ``1.4``. This is because I originally made the world too big,
|
|
|
and so I needed to scale the VR player slightly so they better fit the world. As mentioned earlier, keeping the scale relatively constant is very important!
|
|
|
|
|
|
-Another thing to notice here is how we have everything set up under the :ref:`ARVROrigin <class_ARVROrigin>` node. The player camera is a :ref:`ARVRCamera <class_ARVRCamera>`
|
|
|
+Another thing to notice here is how we have everything set up under the :ref:`ARVROrigin <class_ARVROrigin>` node. The player camera is an :ref:`ARVRCamera <class_ARVRCamera>`
|
|
|
that represents the player's head in the game. The :ref:`ARVRCamera <class_ARVRCamera>` will be offset by the player's height, and if there is room tracking, then the camera
|
|
|
can move around 3D space as well, relative to the :ref:`ARVROrigin <class_ARVROrigin>`. This is important to note, especially for later when we add teleporting.
|
|
|
|
|
@@ -94,12 +94,12 @@ We are going to use the vignette shader to help reduce motion sickness while mov
|
|
|
The reason it is a child of :ref:`ARVROrigin <class_ARVROrigin>` is because we want it to easily access the VR controllers.
|
|
|
|
|
|
The final thing to note is that there are two :ref:`ARVRController <class_ARVRController>` nodes, and these will represent the left and right controllers in 3D space.
|
|
|
-A :ref:`ARVRController <class_ARVRController>` with an ID of 1 is the left hand, while a :ref:`ARVRController <class_ARVRController>` with an ID of 2 is the right hand.
|
|
|
+An :ref:`ARVRController <class_ARVRController>` with an ID of 1 is the left hand, while an :ref:`ARVRController <class_ARVRController>` with an ID of 2 is the right hand.
|
|
|
|
|
|
Starting VR
|
|
|
-----------
|
|
|
|
|
|
-First, let's get the VR up and going! While ``Game.tscn`` is open, select the ``Game`` node and make a new script called ``Game.gd``. Add the following code:
|
|
|
+Firstly, let's get the VR up and going! While ``Game.tscn`` is open, select the ``Game`` node and make a new script called ``Game.gd``. Add the following code:
|
|
|
|
|
|
::
|
|
|
|
|
@@ -122,14 +122,14 @@ version.
|
|
|
|
|
|
With that done, let's quickly go over what this script does.
|
|
|
|
|
|
-First, we find a VR interface from the ARVR server. We do this because by default Godot does not include any VR interfaces, but rather exposes a API so anyone can make
|
|
|
-AR/VR interfaces with GDNative/C++. Next, we check to see if a OpenVR interface was found, and then we initialize it.
|
|
|
+Firstly, we find a VR interface from the ARVR server. We do this because by default Godot does not include any VR interfaces, but rather exposes an API so anyone can make
|
|
|
+AR/VR interfaces with GDNative/C++. Next, we check to see if an OpenVR interface was found, and then we initialize it.
|
|
|
|
|
|
-Assuming nothing went wrong with initializing, we then turn the main :ref:`Viewport <class_Viewport>` into a AR/VR viewport, by setting ``arvr`` to ``true``.
|
|
|
+Assuming nothing went wrong with initializing, we then turn the main :ref:`Viewport <class_Viewport>` into an AR/VR viewport, by setting ``arvr`` to ``true``.
|
|
|
We also set HDR to ``false``, since you cannot use HDR in OpenVR.
|
|
|
|
|
|
-Then we disable V-Sync and set the target FPS to 90 frames per second. Most VR headsets run at 90 Hz, and since the game will both display
|
|
|
-on the VR headset and on the computer's monitor, we want to disable V-Sync and set the target FPS manually so the computer's monitor does not drag the VR display down to 60 FPS.
|
|
|
+Then, we disable V-Sync and set the target FPS to 90 frames per second. Most VR headsets run at 90 Hz, and since the game will display
|
|
|
+on both the VR headset and the computer's monitor, we want to disable V-Sync and set the target FPS manually, so the computer's monitor does not drag the VR display down to 60 FPS.
|
|
|
|
|
|
.. note:: One thing to notice as well is that the physics FPS is also set to 90! This makes the physics run at the same frame rate as the display, which makes
|
|
|
things look smoother in VR.
|
|
@@ -142,15 +142,15 @@ you will be able to move around as far as the room tracking allows.
|
|
|
Coding the controllers
|
|
|
----------------------
|
|
|
|
|
|
-While perhaps interesting if we were making a VR film, we really want to do more than stand around and look. Currently we cannot move outside of the room tracking boundaries
|
|
|
+While perhaps interesting if we were making a VR film, we really want to do more than stand around and look. Currently, we cannot move outside of the room tracking boundaries
|
|
|
(assuming your VR headset has room tracking) and we cannot interact with anything! Let's change that!
|
|
|
|
|
|
You may have noticed that you have a pair of green and black hands following the controllers. Let's write the code for those controllers, which will allow the player to teleport
|
|
|
around the world and allow the player to grab and release :ref:`RigidBody <class_RigidBody>` nodes.
|
|
|
|
|
|
-Open either ``Left_Controller.tscn`` or ``Right_Controller.tscn``. Feel free to look at how the scene is set up; there is only a couple things of note to point out.
|
|
|
+Open either ``Left_Controller.tscn`` or ``Right_Controller.tscn``. Feel free to look at how the scene is set up; there are only a couple things of note to point out.
|
|
|
|
|
|
-First, notice how there is a couple :ref:`Raycast <class_Raycast>` nodes. We will be using one :ref:`Raycast <class_Raycast>` to teleport around the game world (``Raycast``) and
|
|
|
+Firstly, notice how there are a couple :ref:`Raycast <class_Raycast>` nodes. We will be using one :ref:`Raycast <class_Raycast>` to teleport around the game world (``Raycast``) and
|
|
|
we will use the other for picking up objects (``GrabCast``) if the player is using :ref:`Raycast <class_Raycast>` nodes to pick up objects.
|
|
|
|
|
|
The other thing to note is how there is an :ref:`Area <class_Area>` simply called ``Area``, that is a small sphere in the palm of the hand. This will be used to detect
|
|
@@ -165,8 +165,8 @@ Add the following to ``VR_Controller.gd``:
|
|
|
|
|
|
extends ARVRController
|
|
|
|
|
|
- var controller_velocity = Vector3(0,0,0)
|
|
|
- var prior_controller_position = Vector3(0,0,0)
|
|
|
+ var controller_velocity = Vector3(0, 0, 0)
|
|
|
+ var prior_controller_position = Vector3(0, 0, 0)
|
|
|
var prior_controller_velocities = []
|
|
|
|
|
|
var held_object = null
|
|
@@ -224,7 +224,7 @@ Add the following to ``VR_Controller.gd``:
|
|
|
# --------------------
|
|
|
if get_is_active() == true:
|
|
|
|
|
|
- controller_velocity = Vector3(0,0,0)
|
|
|
+ controller_velocity = Vector3(0, 0, 0)
|
|
|
|
|
|
if prior_controller_velocities.size() > 0:
|
|
|
for vel in prior_controller_velocities:
|
|
@@ -257,12 +257,12 @@ Add the following to ``VR_Controller.gd``:
|
|
|
var joystick_vector = Vector2(-get_joystick_axis(5), get_joystick_axis(4))
|
|
|
|
|
|
if trackpad_vector.length() < CONTROLLER_DEADZONE:
|
|
|
- trackpad_vector = Vector2(0,0)
|
|
|
+ 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)
|
|
|
+ joystick_vector = Vector2(0, 0)
|
|
|
else:
|
|
|
joystick_vector = joystick_vector.normalized() * ((joystick_vector.length() - CONTROLLER_DEADZONE) / (1 - CONTROLLER_DEADZONE))
|
|
|
|
|
@@ -423,8 +423,8 @@ variables outside of any/all functions.
|
|
|
- ``grab_area``: The :ref:`Area <class_Area>` node used to grab objects.
|
|
|
- ``grab_pos_node``: The position where held objects stay.
|
|
|
- ``hand_mesh``: The hand mesh, used to represent the player's hand when they are not holding anything.
|
|
|
-- ``teleport_pos``: The position the teleport :ref:`Raycast <class_Raycast>` is aimed at.
|
|
|
-- ``teleport_mesh``: The meshed used to represent the teleport position.
|
|
|
+- ``teleport_pos``: The position at which the teleport :ref:`Raycast <class_Raycast>` is aimed.
|
|
|
+- ``teleport_mesh``: The mesh used to represent the teleport position.
|
|
|
- ``teleport_button_down``: A variable for tracking whether the teleport button is being held down or not.
|
|
|
- ``teleport_raycast``: The teleport :ref:`Raycast <class_Raycast>` node, used for calculating the teleportation position.
|
|
|
- ``CONTROLLER_DEADZONE``: The dead zone for both the trackpad and the joystick.
|
|
@@ -435,7 +435,7 @@ _________
|
|
|
|
|
|
Next, let's go through ``_ready``.
|
|
|
|
|
|
-First we get the teleport :ref:`Raycast <class_Raycast>` node and assign it to ``teleport_raycast``.
|
|
|
+Firstly, we get the teleport :ref:`Raycast <class_Raycast>` node and assign it to ``teleport_raycast``.
|
|
|
|
|
|
Next, we get the teleport mesh; notice how we are getting it from ``Game/Teleport_Mesh`` using ``get_tree().root``. This is because we need the teleport mesh
|
|
|
to be separate from the controller, so moving and rotating the controller does not affect the position and rotation of the teleportation mesh.
|
|
@@ -451,10 +451,10 @@ _________
|
|
|
|
|
|
Now let's go through ``_physics_process``.
|
|
|
|
|
|
-First, we check to see if the teleportation button is down or not. If the teleportation button is down, we then force the teleportation :ref:`Raycast <class_Raycast>`
|
|
|
+Firstly, we check to see if the teleportation button is down or not. If the teleportation button is down, we then force the teleportation :ref:`Raycast <class_Raycast>`
|
|
|
to update, which will give us frame perfect collision detection. We then check to see if the :ref:`Raycast <class_Raycast>` is colliding with anything.
|
|
|
|
|
|
-Next, we check to see if the collision body the :ref:`Raycast <class_Raycast>` is colliding with is a :ref:`StaticBody <class_StaticBody>`. We do this to ensures the player
|
|
|
+Next, we check to see if the collision body the :ref:`Raycast <class_Raycast>` is colliding with is a :ref:`StaticBody <class_StaticBody>`. We do this to ensure the player
|
|
|
can only teleport on :ref:`StaticBody <class_StaticBody>` nodes. We then check to see if the ``Y`` value returned by the :ref:`Raycast <class_Raycast>`'s
|
|
|
``get_collision_normal`` function is more than ``0.85``, which is mostly pointing straight up. This allows the player only to teleport on fairly flat faces pointing upwards.
|
|
|
|
|
@@ -462,38 +462,38 @@ If all those checks for the teleport :ref:`Raycast <class_Raycast>` return true,
|
|
|
mesh to ``teleport_pos``.
|
|
|
|
|
|
The next thing we check is to see if the :ref:`ARVRController <class_ARVRController>` is active or not. If the :ref:`ARVRController <class_ARVRController>` is active, then
|
|
|
-that means there is a controller and it is being tracked. If the controller is active, we then reset ``controller_velocity`` to a empty :ref:`Vector3 <class_Vector3>`.
|
|
|
+that means there is a controller and it is being tracked. If the controller is active, we then reset ``controller_velocity`` to an empty :ref:`Vector3 <class_Vector3>`.
|
|
|
|
|
|
We then add all of the prior velocity calculations in ``prior_controller_velocities`` to ``controller_velocity``. By using the prior calculations, we get a smoother
|
|
|
throwing/catching experience, although it is not perfect. We want to get the average of these velocities, as otherwise we'd get crazy high velocity numbers that are not realistic.
|
|
|
|
|
|
-Next we calculate the velocity from the position the controller currently is, from the position the controller was at. We can use this difference in position to help track
|
|
|
+Next, we calculate the velocity from the position where the controller is currently, from the position the controller was at. We can use this difference in position to help track
|
|
|
the controller's velocity.
|
|
|
|
|
|
We then add the velocity from the controller this physics frame and the last physics frame to ``controller_velocity``. We then update ``prior_controller_position`` to the
|
|
|
current position, so we can use it in the calculations in the velocity next physics frame.
|
|
|
|
|
|
.. note:: The way we are calculating velocity is not perfect by any means, since it relies on a consistent amount of frames per second.
|
|
|
- Ideally we would be able to find the velocity directly from the VR controller but currently in OpenVR there is not way to access the controller's velocity.
|
|
|
+ Ideally, we would be able to find the velocity directly from the VR controller, but currently in OpenVR, there is no way to access the controller's velocity.
|
|
|
We can get pretty close to the real velocity by comparing positions between frames though, and this will work just fine for this project.
|
|
|
|
|
|
-Then we check to see if we have more than 30 stored velocities (more than a third of a second). If there are more than 30, we remove the oldest velocity
|
|
|
+Then, we check to see if we have more than 30 stored velocities (more than a third of a second). If there are more than 30, we remove the oldest velocity
|
|
|
from ``prior_controller_velocities``.
|
|
|
|
|
|
|
|
|
-Next we check to see if there is a held object. If there is, we update the position and rotation of the held object to the
|
|
|
-position and rotation of ``grab_pos_node``. Because of how scale works, we need to temporarily store the scale and then reset the scale once we have updated the transform,
|
|
|
-otherwise the scale will always be the same as the controller, which will break the immersion if the player grabs a scaled object.
|
|
|
+Next, we check to see whether there is a held object. If there is, we update the position and rotation of the held object to the
|
|
|
+position and rotation of ``grab_pos_node``. Because of how scale works, we need to temporarily store the scale and then reset the scale once we have updated the transform;
|
|
|
+otherwise, the scale would always be the same as the controller, which would ruin immersion if the player grabbed a scaled object.
|
|
|
|
|
|
|
|
|
The last thing we are going to do in ``_physics_process`` is move the player if they are moving the trackpad/joystick on the VR controller.
|
|
|
|
|
|
-First, we convert the axis values into :ref:`Vector2 <class_Vector2>` variables so we can process them. We invert the X axis so moving the trackpad/joystick left
|
|
|
+Firstly, we convert the axis values into :ref:`Vector2 <class_Vector2>` variables, so we can process them. We invert the X axis, so moving the trackpad/joystick left
|
|
|
will move the player left.
|
|
|
|
|
|
.. note:: Depending on your VR controller and OS, you may need to change the code so it gets the proper axis values!
|
|
|
|
|
|
-Next we account for dead zones on both the trackpad and the joystick. The code for doing this is adapted from the link below, and I would highly recommend looking at it.
|
|
|
+Next, we account for dead zones on both the trackpad and the joystick. The code for doing this is adapted from the link below, and I would highly recommend looking at it.
|
|
|
|
|
|
.. tip:: You can find a great article explaining joystick dead zones `on Third Helix <http://www.third-helix.com/2013/04/12/doing-thumbstick-dead-zones-right.html>`_.
|
|
|
|
|
@@ -503,7 +503,7 @@ finger on the center of the touchpad/joystick, which can make players experience
|
|
|
Next, we get the forward and right directional vectors from the VR camera. We need these so we can move the player forward/backwards and right/left based on where
|
|
|
they are currently looking.
|
|
|
|
|
|
-Then we calculate how much the player will be moving by adding both the trackpad and the joystick vectors together and normalizing them.
|
|
|
+Then, we calculate how much the player will be moving by adding both the trackpad and the joystick vectors together and normalizing them.
|
|
|
|
|
|
Next, we calculate how far the player will go forwards/backwards and right/left by multiplying the VR camera's directional vectors by the combined trackpad/joystick vector.
|
|
|
|
|
@@ -516,41 +516,41 @@ _________
|
|
|
Now, let's look at ``button_pressed``.
|
|
|
|
|
|
If the button pressed is button 15, which for the Windows Mixed Reality controllers is the trigger button, we will interact with the held object assuming the
|
|
|
-controller is holding one, and if the player is not holding a object, we will try to start teleporting.
|
|
|
+controller is holding one, and if the player is not holding an object, we will try to start teleporting.
|
|
|
|
|
|
-If the controller is holding a object, and the held object has a method/function called ``interact``, we call the ``interact`` function
|
|
|
+If the controller is holding an object, and the held object has a method/function called ``interact``, we call the ``interact`` function
|
|
|
on the held object.
|
|
|
|
|
|
-If the controller is not holding a object, we then check to make sure the teleportation mesh is not visible. This check ensure the player cannot teleport cannot teleport with
|
|
|
+If the controller is not holding an object, we then check to make sure the teleportation mesh is not visible. This check ensures the player cannot teleport with
|
|
|
both hands/controllers at the same time. If the teleportation mesh is not visible, we set ``teleport_button_down`` to ``true``, make ``teleport_mesh`` visible,
|
|
|
and make the teleportation raycast visible. This makes it where the teleportation mesh will follow the :ref:`Raycast <class_Raycast>` coming from the pointer
|
|
|
finger of the hand.
|
|
|
|
|
|
-If the button pressed is button 2, which for the Windows Mixed Reality controllers is the grab/grip button, we will grab/throw a object.
|
|
|
+If the button pressed is button 2, which, for the Windows Mixed Reality controllers, is the grab/grip button, we will grab/throw an object.
|
|
|
|
|
|
-First, we make sure the player is not trying to teleport, as we do not want the player to be able to grab something while in the middle of trying to teleport.
|
|
|
+Firstly, we make sure the player is not trying to teleport, as we do not want the player to be able to grab something while in the middle of trying to teleport.
|
|
|
|
|
|
-Then we check to see if the controller is already holding a object or not.
|
|
|
+Then, we check to see whether the controller is already holding a object or not.
|
|
|
|
|
|
-If the controller is not holding a object, we check to see which grab mode the player is using.
|
|
|
+If the controller is not holding an object, we check to see which grab mode the player is using.
|
|
|
|
|
|
-If the player is using the ``AREA`` grab mode, we then get all of the bodies overlapping the grab :ref:`Area <class_Area>`. We go through all of the bodies in the
|
|
|
+If the player is using the ``AREA`` grab mode, we then get all the bodies overlapping the grab :ref:`Area <class_Area>`. We go through all the bodies in the
|
|
|
grab :ref:`Area <class_Area>` and see if there is a :ref:`RigidBody <class_RigidBody>`. We also check to make sure any :ref:`RigidBody <class_RigidBody>` nodes in
|
|
|
the :ref:`Area <class_Area>` do not have a variable called ``NO_PICKUP``, since we do not want to be able to pick up nodes with that variable.
|
|
|
|
|
|
Assuming there is a :ref:`RigidBody <class_RigidBody>` node inside the grab :ref:`Area <class_Area>` that does not have a variable called ``NO_PICKUP``,
|
|
|
we assign it to ``rigid_body`` for additional processing.
|
|
|
|
|
|
-If the player is using the ``RAYCAST`` grab mode, we first force the :ref:`Raycast <class_Raycast>` to update. We then check to see if the :ref:`Raycast <class_Raycast>`
|
|
|
+If the player is using the ``RAYCAST`` grab mode, we first force the :ref:`Raycast <class_Raycast>` to update. We then check to see whether the :ref:`Raycast <class_Raycast>`
|
|
|
is colliding with something.
|
|
|
|
|
|
-If the :ref:`Raycast <class_Raycast>` is colliding with something, we then check to see if what is colliding with is a :ref:`RigidBody <class_RigidBody>`, and that it does not have
|
|
|
+If the :ref:`Raycast <class_Raycast>` is colliding with something, we then check to see if what it is colliding with is a :ref:`RigidBody <class_RigidBody>`, and that it does not have
|
|
|
a variable called ``NO_PICKUP``. If the :ref:`Raycast <class_Raycast>` is colliding with a :ref:`RigidBody <class_RigidBody>`, and it does not have a
|
|
|
variable called ``NO_PICKUP``, we assign it to ``rigid_body`` for additional processing.
|
|
|
|
|
|
If ``rigid_body`` is not ``null``, meaning we found a :ref:`RigidBody <class_RigidBody>` in the grab :ref:`Area <class_Area>`, we assign ``held_object`` to it.
|
|
|
Then we store the now held :ref:`RigidBody <class_RigidBody>`'s information in ``held_object_data``. We are storing the :ref:`RigidBody <class_RigidBody>` mode, layer,
|
|
|
-and mask so later when we drop it, we can reset all of those variables back to what they were before we picked up the :ref:`RigidBody <class_RigidBody>`.
|
|
|
+and mask so later, when we drop it, we can reset all those variables back to what they were before we picked up the :ref:`RigidBody <class_RigidBody>`.
|
|
|
|
|
|
We then set the held object's :ref:`RigidBody <class_RigidBody>` mode to ``MODE_STATIC`` and set the collision layer and mask to 0 so it cannot collide with any
|
|
|
other physic bodies.
|
|
@@ -561,21 +561,21 @@ grab :ref:`Raycast <class_Raycast>` invisible so the mesh used for showing the :
|
|
|
If the :ref:`RigidBody <class_RigidBody>` we picked up has the ``picked_up`` method/function, we call it. If the :ref:`RigidBody <class_RigidBody>` we picked up has a
|
|
|
variable called ``controller``, we set it to this controller.
|
|
|
|
|
|
-If the controller is not holding a object, and the button pressed is 2, we want to drop/throw the held object.
|
|
|
+If the controller is not holding an object, and the button pressed is 2, we want to drop/throw the held object.
|
|
|
|
|
|
-First, we set the held :ref:`RigidBody <class_RigidBody>`'s mode, layer, and mask back to what they were when we picked the object up.
|
|
|
-We then apply a impulse to the held object, using the controller's velocity as the force.
|
|
|
+Firstly, we set the held :ref:`RigidBody <class_RigidBody>`'s mode, layer, and mask back to what they were when we picked the object up.
|
|
|
+We then apply an impulse to the held object, using the controller's velocity as the force.
|
|
|
|
|
|
If the previously held :ref:`RigidBody <class_RigidBody>` has a function called ``dropped``, we call it. If the :ref:`RigidBody <class_RigidBody>` has a variable
|
|
|
-called ``controller`` we set it to ``null``.
|
|
|
+called ``controller``, we set it to ``null``.
|
|
|
|
|
|
-Then we set ``held_object`` to ``null``, since we are no longer holding any objects, and we make the hand mesh visible again.
|
|
|
+Then, we set ``held_object`` to ``null``, since we are no longer holding any objects, and we make the hand mesh visible again.
|
|
|
|
|
|
If we are using the ``RAYCAST`` grab mode, we make the :ref:`Raycast <class_Raycast>` visible so we can see the mesh used for showing the grab :ref:`Raycast <class_Raycast>`.
|
|
|
|
|
|
-Finally, regardless of whether we are grabbing a object or releasing it, we play the sound loaded into ``AudioStreamPlayer3D``, which is a pick-up/drop noise.
|
|
|
+Finally, regardless of whether we are grabbing an object or releasing it, we play the sound loaded into ``AudioStreamPlayer3D``, which is a pick-up/drop noise.
|
|
|
|
|
|
-The last thing we are doing in ``button_pressed`` is checking to see if the button pressed is 1, which for the Windows Mixed Reality controllers is the menu button.
|
|
|
+The last thing we are doing in ``button_pressed`` is checking to see if the button pressed is 1, which, for the Windows Mixed Reality controllers, is the menu button.
|
|
|
|
|
|
If the menu button is pressed, we change grab modes, and set the visibility of the grab :ref:`Raycast <class_Raycast>` so it is only visible when using ``RAYCAST`` as the grab mode.
|
|
|
|
|
@@ -585,33 +585,33 @@ Let's look at ``button_released`` next.
|
|
|
|
|
|
If the button released is button 15, the trigger, then we potentially want to teleport.
|
|
|
|
|
|
-First, we check to see if ``teleport_button_down`` is ``true``. If it is, that means the player is intending to teleport, while if it is ``false``, the player
|
|
|
-has simply released the trigger while holding a object.
|
|
|
+Firstly, we check to see whether ``teleport_button_down`` is ``true``. If it is, that means the player is intending to teleport, while if it is ``false``, the player
|
|
|
+has simply released the trigger while holding an object.
|
|
|
|
|
|
We then check to see if this controller has a teleport position, and we check to make sure the teleport mesh is visible.
|
|
|
|
|
|
If both of those conditions are ``true``, we then calculate the offset the :ref:`ARVRCamera <class_ARVRCamera>` has from the :ref:`ARVROrigin <class_ARVROrigin>`. We do this
|
|
|
because of how :ref:`ARVRCamera <class_ARVRCamera>` and :ref:`ARVROrigin <class_ARVROrigin>` work with room-scale tracking.
|
|
|
|
|
|
-Because we want to teleport the player in their current position to the teleport position, and remember because of room-scale tracking their current position can be offset from
|
|
|
-the origin, we have to figure out that offset so when we teleport we can remove it so that player's current position is teleported to the teleport position.
|
|
|
+Because we want to teleport the player in their current position to the teleport position, and remember, because of room-scale tracking, their current position can be offset from
|
|
|
+the origin, we have to figure out that offset so when we teleport, we can remove it so that player's current position is teleported to the teleport position.
|
|
|
|
|
|
We set the Y value of the camera_offset to zero because we do not want to account for offsets in the player's height.
|
|
|
|
|
|
-Then we teleport the :ref:`ARVROrigin <class_ARVROrigin>` to the teleport position, applying the camera offset.
|
|
|
+Then, we teleport the :ref:`ARVROrigin <class_ARVROrigin>` to the teleport position, applying the camera offset.
|
|
|
|
|
|
-Regardless of whether we teleported or not, we reset all of the teleport-related variables so the controller has to get new ones before teleporting again.
|
|
|
+Regardless of whether we teleported or not, we reset all the teleport-related variables so the controller has to get new ones before teleporting again.
|
|
|
|
|
|
_________
|
|
|
|
|
|
Finally, let's look at ``sleep_area_entered`` and ``sleep_area_exited``.
|
|
|
|
|
|
-When a body enters or exists the sleep area, we check if it has a variable called ``can_sleep``. If it does, we then set it to ``false`` and wake the body if it has entered
|
|
|
-the sleep area, while if it has exited we set it to ``true`` so the :ref:`RigidBody <class_RigidBody>` nodes can sleep (which can decrease CPU usage).
|
|
|
+When a body enters or exists the sleep area, we check whether it has a variable called ``can_sleep``. If it does, we then set it to ``false`` and wake the body if it has entered
|
|
|
+the sleep area, while if it has exited, we set it to ``true`` so the :ref:`RigidBody <class_RigidBody>` nodes can sleep (which can decrease CPU usage).
|
|
|
|
|
|
_________
|
|
|
|
|
|
-Okay, phew! That was a lot of code! Add the same script, ``VR_Controller.gd`` to the other controller so both controllers have the same script.
|
|
|
+Okay, whew! That was a lot of code! Add the same script, ``VR_Controller.gd`` to the other controller so both controllers have the same script.
|
|
|
|
|
|
Now go ahead and try the game again, and you should find you can teleport around by pressing the touch pad, and can grab and throw objects
|
|
|
using the grab/grip buttons.
|
|
@@ -651,7 +651,7 @@ With ``Movement_Vignette`` selected, make a new script called ``Movement_Vignett
|
|
|
var interface = ARVRServer.get_primary_interface()
|
|
|
|
|
|
rect_size = interface.get_render_targetsize()
|
|
|
- rect_position = Vector2(0,0)
|
|
|
+ rect_position = Vector2(0, 0)
|
|
|
|
|
|
controller_one = get_parent().get_node("Left_Controller")
|
|
|
controller_two = get_parent().get_node("Right_Controller")
|
|
@@ -675,7 +675,7 @@ In ``_ready``, we wait for four frames. We do this to ensure the VR interface is
|
|
|
|
|
|
Next, we get the current VR interface, and resize the :ref:`ColorRect <class_ColorRect>` node's size and position so that it covers the entire view in VR.
|
|
|
|
|
|
-Then we get the left and right controllers, assigning them to ``controller_one`` and ``controller_two``.
|
|
|
+Then, we get the left and right controllers, assigning them to ``controller_one`` and ``controller_two``.
|
|
|
|
|
|
We then make the vignette invisible by default.
|
|
|
|
|
@@ -691,7 +691,7 @@ Let's add some special :ref:`RigidBody <class_RigidBody>` nodes we can interact
|
|
|
Adding destroyable targets
|
|
|
--------------------------
|
|
|
|
|
|
-First, let's start by making some targets we will destroy in various different ways with various different special :ref:`RigidBody <class_RigidBody>` nodes.
|
|
|
+Firstly, let's start by making some targets we will destroy in various ways with various special :ref:`RigidBody <class_RigidBody>` nodes.
|
|
|
|
|
|
Open up ``Sphere_Target.tscn``, which you can find in the ``Scenes`` folder. ``Sphere.tscn`` is just a :ref:`StaticBody <class_StaticBody>`
|
|
|
with a :ref:`CollisionShape <class_CollisionShape>`, a mesh, and a audio player.
|
|
@@ -745,7 +745,7 @@ to ``Sphere_Target.gd``:
|
|
|
|
|
|
Let's go over how this script works, starting with the class variables.
|
|
|
|
|
|
-- ``destroyed``: A variable to track if this target is destroyed or not.
|
|
|
+- ``destroyed``: A variable to track whether this target is destroyed or not.
|
|
|
- ``destroyed_timer``: A variable to track how long the target has been destroyed.
|
|
|
- ``DESTROY_WAIT_TIME``: A constant to tell the sphere target how long to wait before destroying/deleting itself.
|
|
|
- ``health``: The amount of health the target has.
|
|
@@ -762,30 +762,30 @@ ________
|
|
|
|
|
|
Next, let's go over ``_physics_process``.
|
|
|
|
|
|
-First, we add time to ``destroyed_timer``. Then we check to see if enough time has passed and we can destroy the target. If enough time has
|
|
|
+Firstly, we add time to ``destroyed_timer``. Then we check to see whether enough time has passed and we can destroy the target. If enough time has
|
|
|
passed, we free/destroy the target using ``queue_free``.
|
|
|
|
|
|
________
|
|
|
|
|
|
Finally, let's go over ``damage``.
|
|
|
|
|
|
-First, we check to make sure the target has not already been destroyed.
|
|
|
+Firstly, we check to make sure the target has not already been destroyed.
|
|
|
|
|
|
Then, we remove however much damage the target has taken from the target's health.
|
|
|
|
|
|
If the target has zero or less health, then it has taken enough damage to break.
|
|
|
|
|
|
-First, we disable the collision shape and make the whole target mesh invisible.
|
|
|
+Firstly, we disable the collision shape and make the whole target mesh invisible.
|
|
|
Next, we spawn/instance the :ref:`RigidBody <class_RigidBody>` version of the target, and instance it at this target's position.
|
|
|
|
|
|
-Then we set ``destroyed`` to ``true`` and start processing ``_physics_process``.
|
|
|
+Then, we set ``destroyed`` to ``true`` and start processing ``_physics_process``.
|
|
|
Finally, we play a sound, and remove a sphere from ``Game.gd`` by calling ``remove_sphere``.
|
|
|
|
|
|
________
|
|
|
|
|
|
Now, you may have noticed we are calling a function in ``Game.gd`` we have not made yet, so let's fix that!
|
|
|
|
|
|
-First, open up ``Game.gd`` and add the following additional class variables:
|
|
|
+Firstly, open up ``Game.gd`` and add the following additional class variables:
|
|
|
|
|
|
::
|
|
|
|
|
@@ -805,9 +805,9 @@ Next, we need to add the ``remove_sphere`` function. Add the following to ``Game
|
|
|
if sphere_ui != null:
|
|
|
sphere_ui.update_ui(spheres_left)
|
|
|
|
|
|
-What this function does is it removes one from ``spheres_left``.
|
|
|
+What this function does is it subtracts one from ``spheres_left``.
|
|
|
|
|
|
-Then it checks to see if ``sphere_ui`` is not null, and if it is not, then it calls its ``update_ui`` function, passing in the amount of sphere's left.
|
|
|
+Then, it checks to see whether ``sphere_ui`` is not null, and if it is not, then it calls its ``update_ui`` function, passing in the amount of spheres left.
|
|
|
We'll add the UI code later in this part.
|
|
|
|
|
|
Now that we have destroyable targets, we need a way to destroy them!
|
|
@@ -817,8 +817,8 @@ Adding a pistol
|
|
|
|
|
|
Okay, let's add a simple pistol. Open up ``Pistol.tscn``, which you will find in the ``Scenes`` folder.
|
|
|
|
|
|
-There is a few things to note here. The first thing to note is how everything is rotated. This is to make the pistol rotate correctly when the player grabs it. The other thing to notice is
|
|
|
-how there is a laser sight mesh, and a flash mesh, both of these do what you'd expect: act as a laser pointer and act as a muzzle flash respectively.
|
|
|
+There are a few things to note here. The first thing to note is how everything is rotated. This is to make the pistol rotate correctly when the player grabs it. The other thing to notice is
|
|
|
+how there is a laser sight mesh, and a flash mesh; both of these do what you'd expect: act as a laser pointer and muzzle flash, respectively.
|
|
|
|
|
|
The other thing to notice is how there is a :ref:`Raycast <class_Raycast>` node at the end of the pistol. This is what we will be using to calculate where the bullets impact.
|
|
|
|
|
@@ -909,26 +909,26 @@ ________
|
|
|
|
|
|
Next, let's look at ``_physics_process``.
|
|
|
|
|
|
-First, we check to see if the flash is visible. We do this by checking to see if ``flash_timer`` is more than zero. This is because ``flash_timer`` will be a inverted timer,
|
|
|
+Firstly, we check to see if the flash is visible. We do this by checking to see if ``flash_timer`` is more than zero. This is because ``flash_timer`` will be an inverted timer,
|
|
|
a timer that counts down instead of counting up.
|
|
|
|
|
|
-If ``flash_timer`` is more than zero, we remove ``delta`` from it and check to see if it is equal to zero or less.
|
|
|
+If ``flash_timer`` is more than zero, we subtract ``delta`` from it and check to see whether it is equal to zero or less.
|
|
|
If it is, we make the flash mesh invisible.
|
|
|
|
|
|
-This makes it where the flash mesh becomes invisible after ``FLASH_TIME`` many seconds has gone by.
|
|
|
+This makes it where the flash mesh becomes invisible after ``FLASH_TIME`` many seconds have gone by.
|
|
|
|
|
|
________
|
|
|
|
|
|
-Now let's look at ``interact``, which is called when the trigger button on the VR controller is pressed and the pistol is being held.
|
|
|
+Now, let's look at ``interact``, which is called when the trigger button on the VR controller is pressed and the pistol is being held.
|
|
|
|
|
|
-First, we check to see if the flash timer is less than or equal to zero. This check makes it where we cannot fire when the flash is visible, limiting how often
|
|
|
+Firstly, we check to see if the flash timer is less than or equal to zero. This check makes it where we cannot fire when the flash is visible, limiting how often
|
|
|
the pistol can fire.
|
|
|
|
|
|
If we can fire, we reset ``flash_timer`` by setting it to ``FLASH_TIME``, and we make the flash mesh visible.
|
|
|
|
|
|
We then update the :ref:`Raycast <class_Raycast>` and check to see if it is colliding with anything.
|
|
|
|
|
|
-If the :ref:`Raycast <class_Raycast>` is colliding with something, we get the collider. We check to see if the collider has the ``damage`` function, and if it does we call it.
|
|
|
+If the :ref:`Raycast <class_Raycast>` is colliding with something, we get the collider. We check to see if the collider has the ``damage`` function, and if it does, we call it.
|
|
|
If it does not, we then check to see if the collider has the ``apply_impulse`` function, and if it does, we call it after calculating the direction from the
|
|
|
:ref:`Raycast <class_Raycast>` to the collider.
|
|
|
|
|
@@ -936,7 +936,7 @@ Finally, regardless of whether the pistol hit something or not, we play the pist
|
|
|
|
|
|
________
|
|
|
|
|
|
-Finally, let's look at ``picked_up`` and ``dropped``, which are called when the pistol is picked up and dropped respectively.
|
|
|
+Finally, let's look at ``picked_up`` and ``dropped``, which are called when the pistol is picked up and dropped, respectively.
|
|
|
|
|
|
All we are doing in these functions is making the laser pointer visible when the pistol is picked up, and making it invisible when the pistol is dropped.
|
|
|
|
|
@@ -999,7 +999,7 @@ Alright, select the ``Shotgun`` root node, the :ref:`RigidBody <class_RigidBody>
|
|
|
|
|
|
var body = raycast.get_collider()
|
|
|
|
|
|
- # If the body has the damage method, then use that, otherwise use apply_impulse.
|
|
|
+ # If the body has the damage method, then use that; otherwise, use apply_impulse.
|
|
|
if body.has_method("damage"):
|
|
|
body.damage(raycast.global_transform, BULLET_DAMAGE)
|
|
|
elif body.has_method("apply_impulse"):
|
|
@@ -1022,9 +1022,9 @@ You may have noticed this is almost exactly the same as the pistol, and indeed i
|
|
|
|
|
|
In ``_ready``, we get the ``Raycasts`` node, instead of just a single :ref:`Raycast <class_Raycast>`.
|
|
|
|
|
|
-The only other change, besides there being nothing in ``picked_up`` and ``dropped`` is in ``interact``.
|
|
|
+The only other change, besides there being nothing in ``picked_up`` and ``dropped``, is in ``interact``.
|
|
|
|
|
|
-Now we go through each :ref:`Raycast <class_Raycast>` in ``raycasts``. We then rotate it on the X and Z axis, making within a 10 to ``-10`` cone.
|
|
|
+Now we go through each :ref:`Raycast <class_Raycast>` in ``raycasts``. We then rotate it on the X and Z axes, making within a 10 to ``-10`` cone.
|
|
|
From there, we process each :ref:`Raycast <class_Raycast>` like we did the single :ref:`Raycast <class_Raycast>` in the pistol, nothing changed at all,
|
|
|
we are just doing it five times, once for each :ref:`Raycast <class_Raycast>` in ``raycasts``.
|
|
|
|
|
@@ -1045,7 +1045,7 @@ effected by the explosion when the bomb explodes.
|
|
|
The other thing to note is how there are two sets of :ref:`Particles <class_Particles>`: one for smoke coming out of the fuse, and another for the explosion itself.
|
|
|
Feel free to take a look at the :ref:`Particles <class_Particles>` nodes if you want!
|
|
|
|
|
|
-The only thing to notice his how long the explosion :ref:`Particles <class_Particles>` node will last, their lifetime, which is 0.75 seconds. We need to know this so we can time
|
|
|
+The only thing to notice is how long the explosion :ref:`Particles <class_Particles>` node will last, their lifetime, which is 0.75 seconds. We need to know this so we can time
|
|
|
the removal of the bomb with the end of the explosion :ref:`Particles <class_Particles>`.
|
|
|
|
|
|
Alright, now let's write the code for the bomb. Select the ``Bomb`` :ref:`RigidBody <class_RigidBody>` node and make a new script called ``Bomb.gd``. Add the following code to
|
|
@@ -1102,7 +1102,7 @@ Alright, now let's write the code for the bomb. Select the ``Bomb`` :ref:`RigidB
|
|
|
pass
|
|
|
else:
|
|
|
if body.has_method("damage"):
|
|
|
- body.damage(global_transform.looking_at(body.global_transform.origin, Vector3(0,1,0)), EXPLOSION_DAMAGE)
|
|
|
+ body.damage(global_transform.looking_at(body.global_transform.origin, Vector3(0, 1, 0)), EXPLOSION_DAMAGE)
|
|
|
elif body.has_method("apply_impulse"):
|
|
|
var direction_vector = body.global_transform.origin - global_transform.origin
|
|
|
body.apply_impulse(direction_vector.normalized(), direction_vector.normalized() * 1.8)
|
|
@@ -1142,7 +1142,7 @@ Alright, now let's write the code for the bomb. Select the ``Bomb`` :ref:`RigidB
|
|
|
Let's go through what this script does, starting with the class variables:
|
|
|
|
|
|
- ``bomb_mesh``: The :ref:`MeshInstance <class_MeshInstance>` used for the bomb mesh.
|
|
|
-- ``FUSE_TIME``: The length of time the fuse burns for.
|
|
|
+- ``FUSE_TIME``: The length of time for which the fuse burns.
|
|
|
- ``fuse_timer``: A variable for tracking how long the fuse has been burning.
|
|
|
- ``explosion_area``: The :ref:`Area <class_Area>` node used for detecting what nodes are inside the explosion.
|
|
|
- ``EXPLOSION_DAMAGE``: The amount of damage the explosion does.
|
|
@@ -1157,7 +1157,7 @@ ________
|
|
|
|
|
|
Let's go through ``_ready``.
|
|
|
|
|
|
-First, we get all of the nodes and assign them to the proper variables for later use.
|
|
|
+Firstly, we get all the nodes and assign them to the proper variables for later use.
|
|
|
|
|
|
Then, we make sure ``_physics_process`` is not going to be called. We do this since we will be using ``_physics_process`` only for the fuse and
|
|
|
for destroying the bomb, so we do not want to trigger that early, we only want the fuse to start when the player interacts while holding a bomb.
|
|
@@ -1166,9 +1166,9 @@ ________
|
|
|
|
|
|
Now, let's look at ``_physics_process``.
|
|
|
|
|
|
-First we check to see if ``fuse_timer`` is less than ``FUSE_TIME``. If ``fuse_timer`` is less than ``FUSE_TIME``, then the bomb must be burning down the fuse.
|
|
|
+Firstly we check to see whether ``fuse_timer`` is less than ``FUSE_TIME``. If ``fuse_timer`` is less than ``FUSE_TIME``, then the bomb must be burning down the fuse.
|
|
|
|
|
|
-We then add time to ``fuse_timer``, and check to see if the bomb has waited long enough and has burned through the entire fuse.
|
|
|
+We then add time to ``fuse_timer``, and check to see whether the bomb has waited long enough and has burned through the entire fuse.
|
|
|
|
|
|
If the bomb has waited long enough, then we need to explode the bomb. We do this first by stopping the smoke :ref:`Particles <class_Particles>` from emitting, and
|
|
|
making the explosion :ref:`Particles <class_Particles>` emit. We also hide the bomb mesh so it is no longer visible.
|
|
@@ -1176,11 +1176,11 @@ making the explosion :ref:`Particles <class_Particles>` emit. We also hide the b
|
|
|
Next, we make the set the collision layer and mask to zero, and set the :ref:`RigidBody <class_RigidBody>` mode to static. This makes it where the now exploded bomb cannot
|
|
|
interact with the physics world, and so it will stay in place.
|
|
|
|
|
|
-Then we go through everything inside the explosion :ref:`Area <class_Area>`. We make sure the bodies inside the explosion :ref:`Area <class_Area>` are not the bomb itself, since we
|
|
|
-do not want to explode the bomb with itself. We then check to see if the bodies have the ``damage`` method/function, and if it does we call that, while if it does not we check to
|
|
|
+Then, we go through everything inside the explosion :ref:`Area <class_Area>`. We make sure the bodies inside the explosion :ref:`Area <class_Area>` are not the bomb itself, since we
|
|
|
+do not want to explode the bomb with itself. We then check to see whether the bodies have the ``damage`` method/function, and if it does, we call that, while if it does not, we check to
|
|
|
see if it has the ``apply_impulse`` method/function, and call that instead.
|
|
|
|
|
|
-Then we set ``explode`` to true since the bomb has exploded, and we play a sound.
|
|
|
+Then, we set ``explode`` to true since the bomb has exploded, and we play a sound.
|
|
|
|
|
|
Next, we check to see if the bomb has exploded, as we need to wait until the explosion :ref:`Particles <class_Particles>` are done.
|
|
|
|
|
@@ -1198,7 +1198,7 @@ We also make the fuse :ref:`Particles <class_Particles>` start emitting, so smok
|
|
|
________
|
|
|
|
|
|
With that done, the bombs are ready to go! You can find them in the orange building. Because of how we are calculating velocity, it is easiest to throw bombs in a trusting-like
|
|
|
-motion as opposed to a more natural throwing like motion. The smooth curve of a throwing-like motion is harder to track, and the because of how we are tracking velocity it does
|
|
|
+motion as opposed to a more natural throwing like motion. The smooth curve of a throwing-like motion is harder to track, and the because of how we are tracking velocity, it does
|
|
|
not always work.
|
|
|
|
|
|
Adding a sword
|
|
@@ -1211,7 +1211,7 @@ Open up ``Sword.tscn``, which you will find in ``Scenes``.
|
|
|
There is not a whole lot to note here, but there is just one thing, and that is how the length of the blade of the sword is broken into several small :ref:`Area <class_Area>` nodes.
|
|
|
This is because we need to roughly know where on the blade the sword collided, and this is the easiest (and only) way I could figure out how to do this.
|
|
|
|
|
|
-.. tip:: If you know how to find the point where a :ref:`Area <class_Area>` and a :ref:`CollisionObject <class_CollisionObject>` meet, please let me know and/or make a PR on the
|
|
|
+.. tip:: If you know how to find the point where an :ref:`Area <class_Area>` and a :ref:`CollisionObject <class_CollisionObject>` meet, please let me know and/or make a PR on the
|
|
|
Godot documentation! This method of using several small :ref:`Area <class_Area>` nodes works okay, but it is not ideal.
|
|
|
|
|
|
Other than that, there really is not much of note, so let's write the code. Select the ``Sword`` root node, the :ref:`RigidBody <class_RigidBody>` and make a new script called
|
|
@@ -1281,27 +1281,27 @@ Other than that, there really is not much of note, so let's write the code. Sele
|
|
|
Let's go over what this script does, starting with the two class variables:
|
|
|
|
|
|
- ``SWORD_DAMAGE``: The amount of damage a single sword slice does.
|
|
|
-- ``controller``: The controller that is holding the sword, if there is one. This is set by the controller, so we do not need to set it here in ``Sword.gd``.
|
|
|
+- ``controller``: The controller that is holding the sword, if there is one. This is set by the controller, so we do not need to set it here, in ``Sword.gd``.
|
|
|
|
|
|
________
|
|
|
|
|
|
Let's go over ``_ready`` next.
|
|
|
|
|
|
-All we are doing here is connecting each of the :ref:`Area <class_Area>` nodes ``body_entered`` signal to the ``body_entered_sword`` function, passing in a additional argument
|
|
|
+All we are doing here is connecting each of the :ref:`Area <class_Area>` nodes ``body_entered`` signal to the ``body_entered_sword`` function, passing in an additional argument,
|
|
|
which will be the number of the damage :ref:`Area <class_Area>`, so we can figure out where on the sword the body collided.
|
|
|
|
|
|
________
|
|
|
|
|
|
Now let's go over ``body_entered_sword``.
|
|
|
|
|
|
-First, we make sure the body the sword has collided with is not itself.
|
|
|
+Firstly, we make sure the body the sword has collided with is not itself.
|
|
|
|
|
|
Then we figure out which part of the sword the body collided with, using the passed-in number.
|
|
|
|
|
|
-Next, we check to see if the body the sword collided with has the ``damage`` function, and if it does, we call it and play a sound.
|
|
|
+Next, we check to see whether the body the sword collided with has the ``damage`` function, and if it does, we call it and play a sound.
|
|
|
|
|
|
-If it does not have the damage function, we then check to see if it has the ``apply_impulse`` function. If it does, we then calculation the direction from the sword part the
|
|
|
-body collided with to the body. We then check to see if the sword is being held or not.
|
|
|
+If it does not have the damage function, we then check to see whether it has the ``apply_impulse`` function. If it does, we then calculate the direction from the sword part the
|
|
|
+body collided with to the body. We then check to see whether the sword is being held or not.
|
|
|
|
|
|
If the sword is not being held, we use the :ref:`RigidBody <class_RigidBody>`'s velocity as the force in ``apply_impulse``, while if the sword is being held, we use the
|
|
|
controller's velocity as the force in the impulse.
|
|
@@ -1320,7 +1320,7 @@ Updating the target UI
|
|
|
Okay, let's update the UI as the sphere targets are destroyed.
|
|
|
|
|
|
Open up ``Game.tscn`` and then expand the ``GUI`` :ref:`MeshInstance <class_MeshInstance>`. From there, expand the ``GUI`` :ref:`Viewport <class_Viewport>` node
|
|
|
-and then select the ``base_control`` node. Add a new script called ``Base_Control``, and add the following:
|
|
|
+and then select the ``Base_Control`` node. Add a new script called ``Base_Control``, and add the following:
|
|
|
|
|
|
::
|
|
|
|
|
@@ -1341,10 +1341,10 @@ and then select the ``base_control`` node. Add a new script called ``Base_Contro
|
|
|
|
|
|
Let's go over what this script does quickly, as it is fairly simple.
|
|
|
|
|
|
-First, in ``_ready``, we get the :ref:`Label <class_Label>` that shows how many spheres are left and assign it to the ``sphere_count_label`` class variable.
|
|
|
+Firstly, in ``_ready``, we get the :ref:`Label <class_Label>` that shows how many spheres are left and assign it to the ``sphere_count_label`` class variable.
|
|
|
Next, we get ``Game.gd`` by using ``get_tree().root`` and assign ``sphere_ui`` to this script.
|
|
|
|
|
|
-In ``update_ui``, we change the sphere :ref:`Label <class_Label>`'s text. If there is at least one sphere remaining, we change the text to show how many sphere's are still
|
|
|
+In ``update_ui``, we change the sphere :ref:`Label <class_Label>`'s text. If there is at least one sphere remaining, we change the text to show how many spheres are still
|
|
|
left in the world. If there are no more spheres remaining, we change the text and congratulate the player.
|
|
|
|
|
|
Adding the final special RigidBody
|
|
@@ -1393,7 +1393,7 @@ Add the following code to ``Reset_Box.gd``:
|
|
|
|
|
|
Let's go over what this does quickly, as it is also fairly simple.
|
|
|
|
|
|
-First, we get the starting global :ref:`Transform <class_Transform>` in ``_ready``, and assign it to ``start_transform``. We will use this to reset the position of the reset box ever so often.
|
|
|
+Firstly, we get the starting global :ref:`Transform <class_Transform>` in ``_ready``, and assign it to ``start_transform``. We will use this to reset the position of the reset box every so often.
|
|
|
|
|
|
In ``_physics_process``, we check to see if enough time has passed to reset. If it has, we reset the box's :ref:`Transform <class_Transform>` and then reset the timer.
|
|
|
|
|
@@ -1404,18 +1404,18 @@ When the reset box is dropped, we reset the :ref:`Transform <class_Transform>` a
|
|
|
|
|
|
________
|
|
|
|
|
|
-With that done, when you grab and interact with the reset box, the entire scene will reset/restart and you can destroy all of the targets again!
|
|
|
+With that done, when you grab and interact with the reset box, the entire scene will reset/restart and you can destroy all the targets again!
|
|
|
|
|
|
Final notes
|
|
|
-----------
|
|
|
|
|
|
.. image:: img/starter_vr_tutorial_sword.png
|
|
|
|
|
|
-Phew! That was a lot of work. Now you have a fairly simple VR project!
|
|
|
+Whew! That was a lot of work. Now you have a fairly simple VR project!
|
|
|
|
|
|
.. warning:: If you ever get lost, be sure to read over the code again!
|
|
|
|
|
|
You can download the finished project for this part here: :download:`VR_Starter_Tutorial_Complete.zip <files/VR_Starter_Tutorial_Complete.zip>`
|
|
|
|
|
|
-This hopefully will serve as a introduction into making fully featured VR games in Godot! The code written here can be expanded to make puzzle games, action games,
|
|
|
+This will hopefully serve as an introduction to making fully-featured VR games in Godot! The code written here can be expanded to make puzzle games, action games,
|
|
|
story-based games, and more!
|