Unity VR – Bow and Arrow Part 1

I’ve enjoyed using a bow and arrow in VR ever since I booted up the VR Lab. There was something so satisfying about grabbing the bow in one hand and launching arrows by pulling the string with the other hand. This tutorial will cover how to make a quick and easy VR bow. I’ll be providing some free to use prefabs, but if you want additional prefabs and easily downloadable source code, you’ll have to check out my Patreon.

You’ll also want to use my free Unity VR Template that can be found here.

The free bow and arrow assets for this tutorial can be found here.

Updating The Project

Let’s start by updating the project. 

One must have update will be upgrading the XR-Interaction Toolkit. To do this go to Window -> Project Manager. Once that window is open, use the dropdown menu to select Packages: In Project. You should be able to select the XR Interaction Toolkit from there and upgrade it to 2.3.2.

I will also be updating the project to use URP (Universal Render Pipeline). This won’t be necessary, but if you wish to as well, instructions can be found here.

Setting Up The Bow

If we go into the Prefabs folder for this project (Bow And Arrow -> Prefab), we can drag the Bow prefab into the Scene. I’ve already put a few components onto the bow prefab, but to get it working, we will need to add more. Let’s review what we have so far.

We start with an Empty Object called “Bow” that has a few children attached to it. PlankBow is just the model for the bow. There is an attach point that’s already been adjusted so we can hold the bow correctly. We also have a start and end point. These will be used for when we pull the string of the bow back and will help limit the length that movement. We finally have a notch, which is where we will spawn the arrow.

To get things started, let’s begin by making our Bow grabbable.

  •  Select the “Bow” gameobject
    • Add Component “XR Grab Interactable”
    • For Rigidbody
      •  Is Kinematic = True
      • Set Interpolate = Interpolate 
    • For XR Grab Interactable
      •  Set Interaction Layer Mask to
        • Interactable, Interactable Ignore Ray (If you don’t have these, you can add them or use Default if you want to pick up the bow using that Interaction Layer)
      • Movement Type = Instantaneous
      • Attach Point = Attach Point (Drag attach point from the Prefab into the slot here)
 With that, we should be able to grab our bow! You may want to consider adjusting the Attach Point if you don’t feel like it feels right, but for now, we have a grabbable bow.
 
 

Making a String

In order to use this bow, we’re going to need a string to pull so we can launch our arrow. In order to get a string to work we’re going to use a Line Renderer. 

  • Select the Bow prefab
  • Add an empty object to it and call it “String”
  • On the new String object
    • Transform – Reset all Positions and Rotations to (0,0,0) 
    • Add Component – Line Renderer
    •  Under the Line Renderer component
      • Expand out Positions
        • Size = 3
        • Position 0 = (-0.85, 0, -0.2)
        • Position 1 = (0, 0, -0.2)
        • Position 2 = (0.85, 0, -0.2)
      • Width = .004
      • Use World Space = False
      • Material = String

We’ve created a string on our bow! Now we need to create a Pull Interaction that will pull back the string when we’re holding the bow. In order to accomplish this, we need to have a script that moves the the center Position of the String Renderer. If we set the Position 1 to (0, 0, -.5), we can see that this gives us the pulling effect that we’re after.

Let’s get the Pull Interaction working.

Pull Interaction

To get this to work, I’m going to inherit functionality from the Grab Interactable script. This will require that the string has a Collider on it that is set to IsTrigger = True.

  • Select the String
    • Add Component Box Collider
      • IsTrigger = True
Next we need to add a new script 
  • New Component – Script
    • Name it “Pull Interaction”
 
 
 

Above is the script I put together to handle the Pull Interaction that we’re after.

Starting off, we import the libraries for the XR Interaction Toolkit and we also use System so we can use Event Actions.

This class inherits its functionality from the XRBaseInteractable class. We’re just making ourselves a custom XR Grab Interactable.

I have Event Action at the beginning, which will pass in a float and signal to any other scripts when the Bow String is released.

I also have a few public variables that we’ll need later. The start and end transforms will help limit the length we can pull the bow, the notch will be used to move the arrow that spawns on it and the pullAmount will be used to store how much force will be used when releasing the String.

The private variables will store the Line Renderer and also the Select Interactor that grabs onto the String.

Awake – here we just call the Awake function for the base class just in case there is some implementation there that we don’t want to miss. We also grab and store the Line Renderer.

SetPullInteractor – This simply sets the current Pull Interactor. We’ll need to set this function up in the editor using the SelectEntered event.

Release – This function will be called when we connect it in the editor to the SelectExited event. It first signals that the PullActionRelease has occurred. It will then proceed to reset everything to their original positions and states. 

ProcessInteractable – This function is called and updated every frame that the Interactable is selected. To ensure that we are only using this function during these cases, we check to see the current UpdatePhase is equal to dynamic. We also double check to make sure the current Interactable is selected. We then find the current position of our Pull Interactor, call the CalculatePull function and call the UpdateString function.

CalculatePull – This function takes a Vector 3, which is our current pull position.

  • pullDirection is calculated by subtracting start from the pullPosition. This represents the direction from start to the pullPosition.
  • The targetDirection is calculated from subtracting the start from the end positions. This represents the direction from the start to the end points.
  • The maxLength is the length of the targetDirection, which is given by its magnitude. It represents the maximum length between the start and end position.
  • It then normalizes the targetDirection, which will be needed to calculate the Dot product in the next step.
  • The dot product of pullDirection and targetDirection is calculated using the Vector3.Dot function. The dot product gives a scalar value that represents the projection of pullDirection onto targetDirection. To get the final pull value, the result of the dot product is divided by maxLength. This normalizes the pull value by the maximum possible length
  • It finally clamps the value between 0 and 1 to ensure the pullValue won’t exceed or fall below 0 and 1.
In summary, this code calculated the pull value based on a given position (pullPosition) in relation to the ‘start’ and ‘end’ points. The pull value represents the extent to which the ‘pullPosition’ is aligned with the line segment between ‘start’ and ‘end’, with 0 indicating no alignment and 1 indication perfect alignment.
 
UpdateString – This function will set the position of our lineRenderer based on the Lerp between the start position z, end position z, and the pullAmount. We also zero out the rest of Vector positions using Vector3.forward. We also move the position of the Notch gameobject. We’ll be using this to spawn the arrow later and moving this will in turn, move the arrow back with us pulling the String. Finally, we set the position to the linePosition generated in the first calculation.

Connecting The String

With the script finished, we just need to connect some things together in the Unity editor.

  • On PullInteraction
    • Set Interaction Layer Mask = Interactable, Interactable Ignore Ray
    •  Colliders = Drag Box Collider from the String into the empty slot
    • Start = Drag Start object into empty slot
    • End = Drag End object into empty slot
    • Select Entered
      • Click Plus Sign
      • Drag String into empty slot
      •  Use Dropdown to go to PullInteraction -> (dynamic) SetPullInteractor
    • Select Exited
      • Click Plus Sign
      • Drag String into empty slot
      • Use Dropdown to go to PullInteraction -> Release

Testing The Bow

Booting up the project, we should now be able to grab the Bow and pull back the String!!!

In the next part, I’ll be covering creating an arrow, getting it to work properly and spawning it!