Creating simple VR Hands from Scratch

As we all know, Roblox has a Virtual Reality system that can be used in order to read Virtual Reality input, and make Virtual Reality games. There is sadly not a lot of information out there on how to make a VR game, so I decided to make a very simple tutorial on creating VR hands from scratch.

To start off, we need to remove our player character. We will replace it with 2 VR hands. Create a new Local Script in StarterPlayer → StarterCharacterScripts. Because we place it in StarterCharacterScripts, it will only start running once the character has loaded. This allows us to use Player.Character without having to wait for it first which is quite ideal.

At the top of the script we will declare the player and the character. We also declare the camera, so we can set the HeadSize and set the type to Scriptable. We will also declare the StarterGui service, so we can remove the controller models and replace them with our own little hand models.

local player = game:GetService("Players").LocalPlayer
local character = player.Character
local camera = game.Workspace.CurrentCamera
local starterGui = game:GetService("StarterGui")

camera.CameraType = "Scriptable"
camera.HeadScale = 1

starterGui:SetCore("VRLaserPointerMode", 0)
starterGui:SetCore("VREnableControllerModels", false)

Now to create the hands, we will create a very simple function that will create a Part in the shape of a hand. We will also return the hand, so we can store it and make changes to it later on. We also set it’s position to the position of our character. This really has no purpose other then to not get a stack of 2 hands at the root of the world if one of the hands doesn’t work. And we parent it to the character so that other players can see it, and we have access over it via the Local Script.

local function createHand(handType)
	local hand = Instance.new("Part")
	hand.Parent = character
	hand.CFrame = character.HumanoidRootPart.CFrame
	hand.Size = Vector3.new(0.4, 0.4, 1)
	hand.Transparency = 0
	hand.CanCollide = false
	hand.Anchored = true
	hand.Name = handType
	return hand
end

We now create two hands and store a reference to them.

local leftHand = createHand("RightHand")
local rightHand = createHand("LeftHand")

In order to move the hands, we should get the position of the controllers. We’re going to do this in the UserCFrameChanged function. This function runs before things are displayed to the screen which makes sure there is no delay between moving the hands and your hand models moving in game. It comes with a move reference which holds the movement of the hand.

First add the InputSurface reference at the top, and then add the function.

local inputService = game:GetService("UserInputService")
...
inputService.UserCFrameChanged:Connect(function(part, move)
	-- Code will go here
end)

The thing with move, is that it’s relative to the camera. So when you use move, you should always multiply with the camera’s CFrame in order to move it properly. Now let’s check if the part is the right of left hand, and then move the right and left hands based on the move made. We use Enum.UserCFrame.LeftHand in order to check if the moved part is either the left, or right hand of the player that’s being moved.

inputService.UserCFrameChanged:Connect(function(part, move)
	if part == Enum.UserCFrame.LeftHand then
		leftHand.CFrame = camera.CFrame * move
	elseif part == Enum.UserCFrame.RightHand then
		rightHand.CFrame = camera.CFrame * move
	end
end)

Let’s try our new hands in game!

Hooray! Our hands are visible in the game! But there are some issues.


The camera is weirdly angled, and our character is still visible. The hands also look slightly off…
Time to fix it!

First, to get rid of the character, simply Anchor the HumanoidRootPart. And to fix the camera angle, set the CFrame of the camera, to the position of the camera. This will reset the orientation and fix the weird angle! So we add there lines near the top of our code:

character.HumanoidRootPart.Anchored = true
workspace.CurrentCamera.CFrame = CFrame.new(workspace.CurrentCamera.CFrame.Position)

For the hands, just change their names to something that is not a capital letter. So RightHand should be rightHand. Then the texturing issue is gone. I also added a skin-tone color and a SmoothPlastic material. The new hands function looks like this:

local function createHand(handType)
	local hand = Instance.new("Part")
	hand.Parent = character
	hand.CFrame = character.HumanoidRootPart.CFrame
	hand.Size = Vector3.new(0.4, 0.4, 1)
	hand.Transparency = 0
	hand.Color = Color3.new(1, 0.72, 0.6)
	hand.Material = Enum.Material.SmoothPlastic
	hand.CanCollide = false
	hand.Anchored = true
	hand.Name = handType
	return hand
end

local leftHand = createHand("rightHand")
local rightHand = createHand("leftHand")

With our new changes applied, it looks much better!

And that rounds up this tutorial. I hope your learned something from this, and if you have feedback or questions, feel free to reply to this thread. In the next tutorial, we will look at teleportation movement with raycasting and controller input!

Ready for the next step? How about some movement? You can find the next tutorial here:

110 Likes

I am quite not sure where some of the scripts will go to because I don’t know that much about scripting and a bit confused so could you please help to where exactly to put the scripts

3 Likes

wow this is actualy awesome! also figured out from other research that the controllers have the same controls as the XBOX controllers, and yeah

1 Like

only really got 1 question, how exactly could picking up parts when the trigger is down work? i understand how i can check if its anchored and if trigger is down, but how would i make the part move correctly?

Hey there! You should place all the code in a LocalScript in StarterPlaceCharacter.

5 Likes

Hi there! I will make more tutorials for VR and grabbing items will be one of them so stay tuned for that!

7 Likes

Yeah I assumed that would be it. I did it yesterday and it worked great but I would like to know how to make the game available/compatible for pc and vr games but anyways thanks

1 Like

You can actually check if the user is in VR or not. Pretty sure you can find some information on that on the devforum’s aswell.

2 Likes

Does all the code go in the same localscript or each in a different one.

1 Like

Hey! All the code will go in one script.

3 Likes

Cool, that makes it easy, I will hopefully be able to use it in a game.

1 Like

Shouldn’t this be done on the server? Otherwise others cannot see your hands.

1 Like

You can’t use the VRService in a Server Script. You chould use a remote event for creating the hands, and then unanchor them and then everybody will see them as the client will be the network owner. I will also cover this in the next tutorial.

4 Likes

Awesome, just was wondering good luck with your developing

1 Like

You almost forgot. (Use this ONLY if you are making a single player game. Or, If you want to.)

Always create the Arm model by a ServerScript and use Network Ownership so Server Scripts or other players can see your movements. Even though its in a local script entirely.

So, if you’re making a localscript based VR script for lets say… Boneworks type game and there’s like buttons. You can press the button entirely in localscript but the button uses serverscript.

4 Likes

I know this and I’m working on a part 3 where this issue is fixed. Also you cannot use network ownerships for parts that are not in Workspace :wink:

3 Likes

Where are the parts? Most parts that are rendered are in workspace. Right?

1 Like

They are arms. Of course they are in the character which for some reason doesn’t count as being in Workspace. Really strange, but it just won’t work like that.

3 Likes

I don’t know why, but setting the CameraType to Scriptable doesn’t even try to work.
The cam is still following the character and it rotates accordingly to the character.

Are you coding in a server script? this needs a local script. make sure you use workspace.CurrentCamera, not workspace.Camera.