Specialization
Bow and arrow in VR
Content
Introduction
One of the last courses at The Game Assembly is the specialization. It’s a course that’s only 20 days half time where you get to choose one thing to dig further into. I chose to create a bow and arrow prototype in VR. One of the first VR titles I ever played was The Lab which has a bow and arrow minigame so I take some inspiration from that. The reason I choose to do a VR project as my specialization is because I am interested in VR development. I have never created anything in VR before but I enjoy playing VR games.
Goal
My goal was to make the bow and arrow as good as possible. I want the user to quickly become comfortable with the bow and shoot accurately.
The process
VR Setup
At the start of the project I did some research on VR development and decided to use Unity as the engine. I looked up some tutorials and found some videos from a guy called Valem on youtube. I used the XR interaction toolkit which is is a free XR framework by Unity. With the help of the tutorials I quickly got movement and grabbing working.
Bow model
The next step was to find a good model to use since I am not skilled enough to create my own. I found this one . The model is rigged and have animations so it seemed perfect for my use. It also came with an arrow model. I made them both grabbable but it didn’t feel right when you picked up the arrow. To solve this issue I used something called attachpoints. I tweaked the attachpoint a bit and that made it much better.
Then I learned about another XR interaction toolkit component that is called socket. The socket components does exactly what it says it sockets gameobjects to each other. I added a socket to the bow and changed some values and this was the result.
Pull mechanic
General
This is where the fun begins. I had basically just followed tutorials up to this point. So I started to learn more about the XR Interaction toolkit. I realised that they had events that you could subscribe to. I created a function to change the material of the arrow to red if the arrow got socketed to the bow.
Using the OnEnter event to make the arrow red
At this point in the development I struggled with the XR interaction toolkit. I felt constrained, there were many variables that I couldn’t reach. I could for example not know if I was currently grabbing an object or not. I studied the XR interaction toolkit documentation more and watched some youtube videos and I realized that I could derive from the XR interaction toolkit scripts. Most of the variables and functions were protected, not private! This was a big turning point during the project because I felt like I had more control of what happened and could use the framework more freely. I created a CustomInteractionManager that derived from the XR interaction toolkit’s own interaction manager. I created a dictionary in that stores every interactable that is currently being held and then I created a list that would act as a blacklist. Everytime an interactor picked up an interactable I first check if I am allowed to pick up that interactable. This became useful later on because I did not want my hand to grab the arrow when it was socketed because it should grab the string instead.
I also added functions that could force the interactor to drop the item it was currently holding(and the other way around). I used the forcedrop function for the bow. If an arrow was socketed but the player was not holding the bow, the arrow would get released.
Custom interaction managerI also made a CustomGrabInteractable script that derived from the XRGrabInteractable. I created this script to plan ahead and I thought that I could add some general functions for interactables here but it turned out I never really needed it expect for one thing. The script made it possible to know if the object was currently being grabbed or not. I also created two functions called AllowToBeGrabbed() and DisallowToBeGrabbed(). These functions called the blacklist functions in the CustomInteractionManager that I had written earlier. I could have called the blacklist functions directly instead. If I would have worked in C++ I would have made the blacklistfunctions in the CustomInteractionManager private and made the CustomGrabInteractable a friend to ensure that I can only call it from there. I couldn’t find a way to make friends in C# and I didn’t want to waste too much time to get it to work so I just kept the functions public.
String
Now I needed something to actually pull. I created a gameobject in unity that would act as the string and made it grabbable. Along with the string, I created two empty gameobjects. One of them was for the startposition of the string and one was for the end. While the string is being pulled I calculate how long the string has been pulled by comparing the current string position with the startposition gameobject. You could also solve this by having the startposition and endpoisition as variables in the code but I chose to have them as gameobjects because it is easier to tweak the values in the unity inspector. An if statement was added to the code to check if you are pulling the string backwards and if so I log the value to the console.
I had a lot of problems with the string. The pulling only worked in one direction in the beginning because I had used the global position of the objects instead of the local position. You could therefore only pull the string if you were pulling in negative z world direction. When I solved this issue I thought everything was fine until I tested it in VR. I had been a bit lazy with the development of this feature. I could drag the string object in unity’s editor so I didn’t have to actually put on my VR headset everytime and pull on the string. This made the development easier and more effiecent because I didn’t have to spend time to put on the headset and controllers. The laziness backfired because it turned out that grabbing an object in VR and selecting the same object in the unity inspector and dragging is not the same. When you are grabbing an object with the XR interaction toolkit, the object will deparent itself. Since I worked with localpositions I got different results if the gamobject was a child or not. It took me a long time to figure this out and even longer to find a solution. I solved it by converting the string position to the bow’s space, in this way it will act the same if the string is a child to the bow or not and therefore enable me to test it outside of VR. In the convert to bowspace function I had to take the scale of the string into consideration aswell because it will change depending on if the string is childed or not.
I saved the pull amount both in a raw form(the difference in position between the two points clamped) and in a normalized form. The reason for the raw form is because I can offset the socket with the pullamount and therefore move the arrow with how much I am pulling. The reason I also had a normalized version of the variable was because it is easier to read a value between 0-1 and understand how much I am pulling and because I could use it for the string animation. As I previously wrote, the bow model came with animations. I created a blend tree between the draw and idle animation for the bow and then sent the normalized pullamount value as the blend value. This is how the bow looked with the string pulling done.
You don’t really notice it if you don’t pay attention but the string animation didn’t look that good. I don’t know exactly why it happened but it created a loop on the string at one point. I guess that it has to do with the blending that unity does or that the bow model might be rigged wrong at some place. Sadly I did not have time to research this further and fix it.
The last thing I did for the pulling mechanic was making it possible to socket the arrow and pull the string in the same motion. Earlier you had to first grab the arrow and release it to get it to socket, then you had to pull. To make it all in one motion I created a trigger colllider near the string and checked if the hand was colliding. If the hand was colliding I forced the hand to drop the arrow and forced the hand to grab the string.
Arrow
When the pulling was completed. I created a script for the arrow and made a Fire function. This function was called when the string was released if there were an arrow on the string.
Firing the arrow
I noticed that the arrow didn’t feel realistic when it flew. You expect an arrow to mostly land with the tip down beause the tip is heavier so I tried to implement this feature. I started with creating a new rigidbody at the tip and making it heavier but that did not change a thing. I also experimented with hinge joints but that did not give me the correct result either. The final solution was pretty straightforward and I found it by googling around a bit. I could just rotate the arrow so it always faces the direction it is going. If the arrow is falling down, the arrow will be pointing down.
Another feature I added to the arrow was making it stick to surfaces. I used the unity function OnTriggerEnter first but it didn’t work exactly how I wanted. OnTriggerEnter always happened one frame too late so the arrow had rotated because of the physics and therefore didn’t stick with the same rotation it had when it touched the surface. I experimented with a bunch of different solutions and ended up with a box cast at the tip of the arrow. If a collider that wasn’t tagged with “Bow”, “Arrow” or “Player” overlapped with the cast it would call the stick function. If the object the arrow collided with had an rigidbody I also made that object the arrow’s parent so it would follow if the object moved.
Arrow stick to wall Arrow stick to moving object Arrow rotation towards the direction it is moving
Quiver
Creating the quiver was quite easy. I just made a trigger collider that was childed to the player camera. If your hand was empty and grabbing inside this collider, an arrow was spawned at the hand position and the hand was forced the to pick it up.
Polishing
When I felt like I had the base mechanics working I started to polish the project and give it more feedback. I added a sound that played when the arrow hit something. I made the arrow rest on the side of the bow instead of going trough it and I found a free environment on the asset store. I also created some cubes that you could shoot on that changed colors on hit. I would have liked to spend even more time on this section, I think I could have done more things with haptic feedback and it is a shame that I did not have time for it. Sound is also something that I think could improve the feeling a lot. Another thing that would be cool to change here is the force another object get’s when it’s been hit. I haven’t written any code for that so it is just Unity’s physics system. Here is a video of the final result.
Conclusion
Developing in VR has taught me a lot. It turns out that it is pretty tiresome to always put on your headset and therefore you take shortcuts. It gets harder if you have to test each feature in VR, especially if you want to read the console while you are doing a certain movement in VR. I am glad that I made it possible to pull the string both in the unity editor and in VR. Another thing I learned was framework I used (XR interaction toolkit). At the beginning of the project I felt frustrated and constrained because I didn’t understand how to use it corretly. I also learned some new things that you could do in Unity like using a blendtree. Another thing I learnt about Unity was that my scripts are executed in a specific order that you can change. There was a bug towards the end of the project that had with the script order to do that was extremely hard to find. I am relatively happy with the end result, the only thing I would want to add is more feedback as in vibrations and sound and make it more userfriendly for lefthanded use.
Sources
Assets
Tutorials
Valem (VR setup videos)
VR with Andrew (Socket tag check video)