Overview
A bow mechanic is a staple of a VR building framework. Not in that it serves as a common way for users to interact with their environment, but that it utilises many of the foundational concepts of complex VR mechanics.
- Two handed interactable objects
- Nested interactable objects
- Interaction states
- Held interaction
Learning to create a bow mechanic helped me to make sure I was confident in these conceptual tasks. For this particular bow mechanic, I have also implemented an 'auto-aim' system to assist the user in a feel good experience.
I have created this system to be comprised of three components; the BowModule, the Arrow and the BowTarget. Following my explanation of these, I will also put some screenshots of my object setup so you can see about mimicking it to some extent
- Two handed interactable objects
- Nested interactable objects
- Interaction states
- Held interaction
Learning to create a bow mechanic helped me to make sure I was confident in these conceptual tasks. For this particular bow mechanic, I have also implemented an 'auto-aim' system to assist the user in a feel good experience.
I have created this system to be comprised of three components; the BowModule, the Arrow and the BowTarget. Following my explanation of these, I will also put some screenshots of my object setup so you can see about mimicking it to some extent
The Bow Module
Within my internal systems, I have a Module system. Basic Modules are just packages of logic that can be hooked into specific events by default. A UI module has overrides for clicks, drags and holds, where as an interactable module has overrides for grabs, releases and button interaction.
Originally, BowModule derived from my InteractableModule, but in the latest version of the project I have opted to use UltimateXR, and thus my BowModule derives from UxrGrabbableObjectComponent (looks like Uxr had a very similar idea to me around modular additions to objects).
Unsurprisingly, the BowModule is the center of logic for this system. It controls;
- Notch position reset
- Arrow spawning, pointing and launching
- High-level aim assist settings
As a basic summation of how the BowModule logic;
When the player interacts with the notch grabbable object, if they are also grabbing the bow it will spawn an arrow. While holding the arrow, but before firing, the user can set the amount of aim assistance they want by holding the trigger.
As the user pulls back, the script continuously updates the arrow rotation to be facing from the notch to the rest point on the bow stave. At this point, the bow is also searching the available BowTargets for a 'Best Target', which is the target closest to the aim point of the arrow (arrow direction * distance).
If the user releases the notch more than a certain distance away from its relative starting point, the arrow will be 'launched' This forwards the instruction to the arrow to fire, and provides it with a target and a level (0-1) of aim assist it should employ.
Originally, BowModule derived from my InteractableModule, but in the latest version of the project I have opted to use UltimateXR, and thus my BowModule derives from UxrGrabbableObjectComponent (looks like Uxr had a very similar idea to me around modular additions to objects).
Unsurprisingly, the BowModule is the center of logic for this system. It controls;
- Notch position reset
- Arrow spawning, pointing and launching
- High-level aim assist settings
As a basic summation of how the BowModule logic;
When the player interacts with the notch grabbable object, if they are also grabbing the bow it will spawn an arrow. While holding the arrow, but before firing, the user can set the amount of aim assistance they want by holding the trigger.
As the user pulls back, the script continuously updates the arrow rotation to be facing from the notch to the rest point on the bow stave. At this point, the bow is also searching the available BowTargets for a 'Best Target', which is the target closest to the aim point of the arrow (arrow direction * distance).
If the user releases the notch more than a certain distance away from its relative starting point, the arrow will be 'launched' This forwards the instruction to the arrow to fire, and provides it with a target and a level (0-1) of aim assist it should employ.
The Arrow
The Arrow script controls logic for the arrow projectile once it has been released.
This includes
- Movement,
- Aim assist application,
- Hit detection, and
- Hit FX.
All arrows have a 'time to live', which will begin to count down as soon as they release. I usually set this fairly high, at around 10 seconds, which gives the arrow time to hit a a target, embed and hang out for a few seconds, and then disappear.
Once the arrow receives the command to "Launch" from the bow, it will perform an immediate adjustment to the rotation of the arrow towards the target (if it has one). This makes inflight adjustments less noticable, but too much of a correction here is very noticable so its a balance.
The bow supplies the amount of aim assistance to use in the launch command, and the arrow uses that to calculate a final value for assistance. The value from the bow is normalized (0-1), and so provides a good high level control of how much assistance the game should use. An arrows 'pre-calculation' value is so that different arrows can have different levels of assistance, corresponding to 'degrees per second to rotate towards the target'. This is further compiled by a 'velocity aim assist', which applies an offset to the target position depending on the target velocity.
After the initial launch, the arrow while continually move forward, and slightly down depending on its 'drop rate', simulating basic gravity. The rotation of the arrow is then set to equal its velocity, pointing it down if need be.
If the arrow has a target, it will continually rotate towards it using the assist, and then attempt to detect a hit. It does this by retrieving the position of the arrow the previous frame, and the current position, and detecting physics colliders in-between the two positions. If there is a hit, it will move the arrow 95% back the the last point and stop moving. That 5% gives a wonderful 'embedded' look to the arrow!
Two functions are then called, depending on whether the arrow hit a BowTarget, or just a regular collider. If a BowTarget is hit, a Hit function will be called with relevant information such as arrow tip position and velocity.
This includes
- Movement,
- Aim assist application,
- Hit detection, and
- Hit FX.
All arrows have a 'time to live', which will begin to count down as soon as they release. I usually set this fairly high, at around 10 seconds, which gives the arrow time to hit a a target, embed and hang out for a few seconds, and then disappear.
Once the arrow receives the command to "Launch" from the bow, it will perform an immediate adjustment to the rotation of the arrow towards the target (if it has one). This makes inflight adjustments less noticable, but too much of a correction here is very noticable so its a balance.
The bow supplies the amount of aim assistance to use in the launch command, and the arrow uses that to calculate a final value for assistance. The value from the bow is normalized (0-1), and so provides a good high level control of how much assistance the game should use. An arrows 'pre-calculation' value is so that different arrows can have different levels of assistance, corresponding to 'degrees per second to rotate towards the target'. This is further compiled by a 'velocity aim assist', which applies an offset to the target position depending on the target velocity.
After the initial launch, the arrow while continually move forward, and slightly down depending on its 'drop rate', simulating basic gravity. The rotation of the arrow is then set to equal its velocity, pointing it down if need be.
If the arrow has a target, it will continually rotate towards it using the assist, and then attempt to detect a hit. It does this by retrieving the position of the arrow the previous frame, and the current position, and detecting physics colliders in-between the two positions. If there is a hit, it will move the arrow 95% back the the last point and stop moving. That 5% gives a wonderful 'embedded' look to the arrow!
Two functions are then called, depending on whether the arrow hit a BowTarget, or just a regular collider. If a BowTarget is hit, a Hit function will be called with relevant information such as arrow tip position and velocity.
The Bow Target
The BowTarget script contains logic for targets of the arrows. This is a fairly simple system that can be leveraged and grafted onto by other systems.
The main components are:
- Calculations for best target
- Hit FX
- TTL
- Physics application
The most important concept here are the 'best target' calculations. There are two of these; dot product and closest object calcs, both of which are extremely simple.
The dot product calculation simply returns a dot product comparing the direction of the arrow compared with the direction from the arrow to the target. This is obviously very useful for finding the target with the smallest angle difference in trajectory from the arrows current target. The closest object calc is used for the ricochet mechanic, currently not implemented in this version, but has been implemented in the past.
From there, its all about handling the arrow hit. The script will spawn a prefab facing the direction the arrow came in for FX, and will apply force if specified. If a 'time to live' number is specified, it will also destroy the target after a certain time.
I have made various derivatives of the BowTarget, such as the humanoid target, which would ragdoll a humanoid on hit, and the chain-physics target, which would apply force up a chain of objects to mimic a flow on effect. The system is simple and easily extendable!
The main components are:
- Calculations for best target
- Hit FX
- TTL
- Physics application
The most important concept here are the 'best target' calculations. There are two of these; dot product and closest object calcs, both of which are extremely simple.
The dot product calculation simply returns a dot product comparing the direction of the arrow compared with the direction from the arrow to the target. This is obviously very useful for finding the target with the smallest angle difference in trajectory from the arrows current target. The closest object calc is used for the ricochet mechanic, currently not implemented in this version, but has been implemented in the past.
From there, its all about handling the arrow hit. The script will spawn a prefab facing the direction the arrow came in for FX, and will apply force if specified. If a 'time to live' number is specified, it will also destroy the target after a certain time.
I have made various derivatives of the BowTarget, such as the humanoid target, which would ragdoll a humanoid on hit, and the chain-physics target, which would apply force up a chain of objects to mimic a flow on effect. The system is simple and easily extendable!
Object Setup
Bow Grabbable Object
The root bow object is an empty gameObject which I add the UxrGrabbableObject component to. I don't specify anything special here, although I did add some nice Grab points for left and right hand using the GrabStandard grip pose.
If you choose to have a rigidbody and colliders on your bow, you will need to be careful to modify the hit detection on the arrows to ignore them (in Arrow.cs), as otherwise your arrows will stick into your bow. |
Bow Module
Also on the root bow object is a 'BowModule' component.
In particular, note the Notch reference. Notch refers to a second, nested GrabbableObject inside the bow GrabbableObject. You can see it in the hierarchy figure above. Regarding the Top, Bottom and Rest objects, these are all empty gameObjects. The top and bottom objects should be located where you want the string to begin and end, at the top and bottom of the stave on your 3D model. The rest object should be located at the point that the arrow will pass through when you are aiming. Aim for the very middle of the bow, and you will see what I mean. Easy enough to adjust! The Arrow prefab refers to a premade arrow made into a prefab (not from the scene!). I will show you mine lower down. |
Notch Grabbable Object
Here is my UxrGrabbableObject component on the Notch object (child of bow object)
Again, fairly standard. I am using the 'GrabPinch2Fingers' grip pose which makes a nice string pull. Very important here though: the constraints at the bottom. Both Translation and Rotation should be set to "Restrict Local Offset". Rotation should have (0,0,0) for both values, meaning that the nock will be free to point to the rest object. Translation however, should have 0's for X and Y, but different values for Z. The Z value is the difference in local Z that the hand can move to pull the arrow back. By restricting this, the player cant pull back so far as to completely remove the arrow from the bow. As you can see, I am using a range from -0.5 to 0.05. It is important to note that this is in addition to the current local offset, so you dont need to add that in here. By specifying -0.5, the player can move their hand back half a meter maximum from the notch rest position. The 0.05 maximum allows the player to 'relax' and push the arrow forward a little bit as well, but with such a small movement the launch will cancel if they release (no backward flying arrows!) |
Bow String
Lastly for the bow: the String object.
This should be an empty gameObject with a LineRenderer component on it. The line shape, width, material etc are up to you. If you aren't sure where to start, set the width to 0.01, and the material to 'Line'. Line is the basic line material unity has setup, it will make the line a solid colour. I am using a derivative of Line for my own material so I can add emission effects. Important: the line renderer 'Positions' array must have only 3 elements. By default, lines only have 2 elements, so you will need to add one more. I suggest setting all three positions to (0,0,0) by default so you cant see any weird lines before the bow sets up. You must also set 'Use World Space' to true. The line position update method in BowModule sets these in world space, not local. |
Bow Target
Coming now to the 'BowTarget', there are a variety of ways this script can be employed. For testing, I suggest you just create a basic cylinder object, squish it down to 0.1 on the Y axis, and change out the Capsule Collider to a Mesh Collider.
You can then easily add a rigidbody, and again, this is very changeable. For my testing, I use targets with FreezePosition applied to all axies in the Rigidbody Constraints section, allowing the target to rotate but not fall. However, a target with UseGravity set to false, and no constraints is also very enjoyable, as you can shoot it around an enclosed arena. For the BowTarget itself, much of it is very self-explanatory. Even if you were to leave all of these blank, the script should still work! |
Arrow Prefab
Moving onto the arrow, here is an example of my arrows hierarchy. Don't forget, once you make this prefab you need to assign it in your BowModule!
The SM_Arrow is my 3D model, but be aware that I have offset its local position so that the prefab pivot point lies at the base of the arrow. You will want to replicate this, as this is what makes you able to 'nock' the arrow. The badly named 'GameObject' at the bottom is simply a trail renderer I have added to visualise the arrows flight. Up to you whether you add your own! |
Arrow Script
The Arrow is similarly as simple as the BowTarget script. Note that the movement speed is quite high.
The tip reference is just a transform reference to the very front of the arrow. If your arrow model doesn't have one (most of the ones I have encountered do), just add an empty gameObject as a child and move it to the tip. Make sure the gameObject Z is positive in the direction of the arrow. Proper arrows travel at around 60-65m/s, I have opted for 70 for a nice zippy movement. Ttl is 10 seconds, nice and long for the far away targets When drop rate is zero, there will be no gravity applied to the arrow, resulting in a more arcade-ish feel A Path Correction Base of 360 is quite high, which is why the aim assist on the bow is only using 0.2-0.5 of this value. |
Conclusion
Well done on making it to the end!
Hopefully you have managed to parse everything I've put down, and you have managed to replicate it for yourself (if that was your goal).
This is a fairly base-level mechanic that I'm going to be having some fun with in the future, experimenting with different arrow types, different targets and even different shooting styles.
I hope you enjoyed your read, and feel free to get in contact with any questions.
Pete
Hopefully you have managed to parse everything I've put down, and you have managed to replicate it for yourself (if that was your goal).
This is a fairly base-level mechanic that I'm going to be having some fun with in the future, experimenting with different arrow types, different targets and even different shooting styles.
I hope you enjoyed your read, and feel free to get in contact with any questions.
Pete