Hey! So I’ve seen a lot of people ask about making things that take a key to open. So, I’ve decided to make an in-depth (yet understandable) tutorial on this.
Things to Know
Alright, so before we start, I’m going to need you to know some things. Here they are:
UserInputService
The UserInputService on Roblox is a service that registers user input. Literally the most self-explanatory resource on Roblox.
Anyways, UserInputService has 2 key functions that we’ll be using in this tutorial:
- InputBegan
- InputEnded
InputBegan fires when the user has done something. Even WASD, arrow keys, number pad, Xbox, and more can be registered!
InputEnded fires when the user releases a key. Say you’re pressing W to move forward, and you stop moving to go AFK. As soon as the key pops back up, InputEnded is fired.
Enum
Alrighty, Enum is a really cool feature too. The way I think of whether an item is an EnumItem or not is:
Anything that, in Properties, can be changed through a dropdown menu is an EnumItem. This may be wrong, but everything I’ve ever had to code follows this principle.
Enum is also accessible from anywhere. No need to define anything to get access, all you need to type is: “Enum”.
TweenService
TweenService is a service accessed by the game:GetService() function. TweenService moves parts based on three properties: the floatTime (how long it’ll take to get from a to b in seconds), EasingStyle(the way the tween will look), and EasingDirection(how the speed will be divided.) EasingStyle and EasingDirection are both EnumItems.
Alright, we should be good!
Getting Started
Okay, so for this tutorial, we’ll be using the example of a door. Doors are cool to have an interaction function with, because they can give us the ability to have interiors that the players can explore.
HOWEVER, you can also do this with quests. Maybe, if this gets enough attention, I’ll make one on quests sometime. You can also use this tutorial to get an understanding of essential resources.
Building the Door
So, first we need to build our door. Now, I suck at building and I don’t want to build a whole new door. I made this:
I’m going to duplicate this (CTRL + D / CMD + D) so that I can show you guys how to use one BillboardGui for all doors.
Done! By the way, this is my Explorer right now:
Okie dokie, so now that we have our doors, let’s make a BillboardGui.
Making the BillboardGui
Okay, let’s make this!
So, put a BillboardGui into the StarterGui in Explorer. This will make it local to every player, so each player has their own Gui. Call it “Door” and insert a frame. Now, you can’t see it, right? Well, you need to set the Adornee. Go into Properties and click to the right of where it says “Adornee.” Now, click on your door. Only one. It doesn’t matter which.
Now, while you’re in the Properties, set this three properties:
MaxDistance to 15 (or another value, depending on how far you want your players to access it.)
AlwaysOnTop to true.
Size to {1.5, 0, 1.5, 0}
Okay, now you can see it! So go into the frame, and change it’s size to {1, 0, 1, 0}. This makes it compatible with all screen sizes.
Alright, now let’s insert a TextLabel and a TextButton. Call the TextButton “Key” and the TextLabel “Function.” Set the size of the “Key” TextButton to {1, 0, 0.75, 0}. Set the Function TextLabel’s size to {1, 0, 0.25, 0}, and it’s position to {0, 0, .75, 0}.
Are you following? Great!
Now we can customize the TextLabels in Properties. I’ll be using Stelrex’s uiDesign plugin to make mine look great.
uiDesign Plugin
uiDesign has a free version and a paid version. The paid version is 100 R$.
Free: uiDesign Lite - Roblox
Paid: uiDesign Plus - Roblox
Done mine!
Now, let’s start coding!
Coding the E-to-Interact
Okay, coding is my thing. I do primarily coding, hence why my builds were garbage.
Go into the BillboardGui, and into the Frame, then insert a LocalScript.
Put this in there:
local UserInputService = game:GetService("UserInputService") -- Defining the UserInputService.
local Key = Enum.KeyCode[script.Parent.Key.Text] -- The key we'll be using. All you have to do is edit the "Key" TextLabel's text to change the Key.
local Extras = {
Mobile = {Text = "Tap", Button = nil}; -- Tap, but we'll be using the TextButton for that.
Xbox = {Text = "RB", Button = Enum.KeyCode.ButtonR1}; -- R1 is the Right Bumper.
}
UserInputService.InputBegan:Connect(function(input, processed) -- Bind our function to the InputBegan event.
if processed then return end -- If the game has already processed the input, don't do anything. InputBegan fires 2-3 times each input, for some reason.
if input.UserInputType == Enum.UserInputType.Keyboard then -- If the action was on a keyboard, continue.
if input.KeyCode == Key then -- if the key matches then continue
InputCorrect("Closest")
end
elseif input.UserInputType == Enum.UserInputType.Gamepad1 then -- If you don't have Console Support, stop here, and don't write anything else in this section. | if the action was on a gamepad (Xbox controller), continue.
if input.KeyCode == Extras.Xbox.Button then -- If the button pressed was the button assigned, continue.
InputCorrect("Closest")
end
end
end)
So now we are registering the input. Now, code this ABOVE the InputBegan function, but below the the variables.
local function InputCorrect(action)
if action == "Open Door" then -- Choose your action's name.
-- HERE YOU CODE WHATEVER YOU WANT TO. QUESTS, PICK UPS, DOORS, ETC.
game.ReplicatedStorage.Doors.OpenDoor:FireServer("Closest") -- I'm transfering responsibility to the Server, so that all players can see the door being opened.
--[[
"Closest" will make the Server figure out the door itself.
Defining a physical door object will make the Server use that.
]]--
end
end
You’re done coding the client for now. Your LocalScript should look like this:
local UserInputService = game:GetService("UserInputService") -- Defining the UserInputService.
local Key = Enum.KeyCode[script.Parent.Key.Text] -- The key we'll be using. All you have to do is edit the "Key" TextLabel's text to change the Key.
local Extras = {
Mobile = {Text = "Tap", Button = nil}; -- Tap, but we'll be using the TextButton for that.
Xbox = {Text = "RB", Button = Enum.KeyCode.ButtonR1}; -- R1 is the Right Bumper.
}
local function InputCorrect(action)
if action == "Open Door" then -- Choose your action's name.
-- HERE YOU CODE WHATEVER YOU WANT TO. QUESTS, PICK UPS, DOORS, ETC.
game.ReplicatedStorage.Doors.OpenDoor:FireServer("Closest") -- I'm transfering responsibility to the Server, so that all players can see the door being opened.
--[[
"Closest" will make the Server figure out the door itself.
Defining a physical door object will make the Server use that.
]]--
end
end
UserInputService.InputBegan:Connect(function(input, processed) -- Bind our function to the InputBegan event.
if processed then return end -- If the game has already processed the input, don't do anything. InputBegan fires 2-3 times each input, for some reason.
if input.UserInputType == Enum.UserInputType.Keyboard then -- If the action was on a keyboard, continue.
if input.KeyCode == Key then -- if the key matches then continue
InputCorrect("Open Door")
end
elseif input.UserInputType == Enum.UserInputType.Gamepad1 then -- If you don't have Console Support, stop here, and don't write anything else in this section. | if the action was on a gamepad (Xbox controller), continue.
if input.KeyCode == Extras.Xbox.Button then -- If the button pressed was the button assigned, continue.
InputCorrect("Open Door")
end
end
end)
Now, make a Folder in ReplicatedStorage called “Doors”. Then, add a RemoteEvent to the Folder called “OpenDoor”.
Now, insert a Script into ServerScriptService. Call it “Doors”, and put this in:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Doors = ReplicatedStorage:WaitForChild("Doors")
local TweenService = game:GetService("TweenService")
local increment = 3 -- Change this to the time (in seconds) that you'd like the game to wait before closing the door.
Doors.OpenDoor.OnServerEvent:Connect(function(player, door)
print("Got event")
if typeof(door) == "string" then
print("string")
if door == "Closest" then
local lowest = 1000000 -- A very high number
local lowestDoor = nil
for _, Door in pairs(workspace:GetDescendants()) do
if Door.Name == "DoorPart" then
if (player.Character.HumanoidRootPart.Position - Door.Position).magnitude < lowest then
lowestDoor = Door
lowest = (player.Character.HumanoidRootPart.Position - Door.Position).magnitude
end
end
end
local tween = TweenService:Create(lowestDoor, TweenInfo.new(2, Enum.EasingStyle.Quint, Enum.EasingDirection.InOut, 0, false, 0), {Position = lowestDoor.OpenedPos.Value, Orientation = lowestDoor.OpenedRot.Value})
tween:Play()
wait(increment)
tween = TweenService:Create(lowestDoor, TweenInfo.new(2, Enum.EasingStyle.Quint, Enum.EasingDirection.InOut, 0, false, 0), {Position = lowestDoor.ClosedPos.Value, Orientation = lowestDoor.ClosedRot.Value})
tween:Play()
else
warn("Unregistered type of string for variable 'door'. Please review.")
return
end
end
end)
If you test now, you’ll see an error along the lines of, “Attempt to index a nil value ‘OpenedPos’.” Let’s fix that. Go to your door with the BillboardGui, and create four Vector3 Values. Call one “OpenedPos,” another “OpenedRot”, one called “ClosedPos”, and the final one called “ClosedRot”.
Now, we must be VERY careful when doing this next step. It tells the game how we want our game to make our doors look.
Making the Door Placement Values
In EDIT mode, go to your door, and open it using the Rotation and Move Tools. Now, use a noting software, such as Microsoft Word, Google Docs, or Notepad.
Write the two names of the Opened___ values. Then, copy the door’s opened position. After you’ve copied, click beside OpenedPos and paste the 3 numbers into your noting software. Then, move on to the OpenedRot value. Copy the door’s opened orientation, and paste it beside “OpenedRot.”
Once you’ve done that, go back into studio, hold control, and press Z until your door is back closed. Now you can just copy and paste the orientation and position of the closed door into the Closed___ values accordingly.
Now, select all four values, and press CTRL/CMD + C. Now, select the two other doors, and press CTRL/CMD + V.
Do the values for each door. It’ll take a little while, but it’s totally worth it.
Coding the BillboardGui’s Adornee Changing
Now that we’re done that time-consuming, annoying step, let’s code our final aspect of the system: the BillboardGui.
“But Maximuss, we already coded the gui!” We did, but not completely. We need to make the BillboardGui’s Adornee the closest door. Let’s do this now.
Add a second LocalScript into the BillboardGui, called “AdorneeAdjustments”. Put this in that script:
local BillboardGui = script.Parent.Parent -- The BillboardGui
local player = game.Players.LocalPlayer -- The player
repeat wait(1) until game:IsLoaded() == true -- Wait until the game is fully loaded, so we don't get any errors
while true do
wait(1)
local lowest = 10000000 -- a very high number
local lowestDoor = nil
for _, door in pairs(game.Workspace:GetDescendants()) do
if door.Name == "DoorPart" then
if (player.Character.HumanoidRootPart.Position - door.Position).magnitude < lowest then
lowest = (player.Character.HumanoidRootPart.Position - door.Position).magnitude
lowestDoor = door
end
end
end
-- Notice the similarities in this code from the one in ServerScriptService? It all adds up!
BillboardGui.Adornee = lowestDoor
end
Alright, now test!
The Doors Work! (YouTube Video)
YES!
So, all you need to do now is make sure the door part of all your builds is called “DoorPart”, set these values inside all doors, and your UI should work!
Thanks for sticking with me, and I hope this helped!
P.S. Let me know if you have any issues with your broken script’s code, and I’ll try to diagnose your issue!