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: