Out of boredom, I’ve made a proximity prompt module (with the help of ai) since the default one can’t handle multiple keybinds.
Module
local CustomPrompt = {}
local UserInputService = game:GetService("UserInputService")
local ProximityPromptService = game:GetService("ProximityPromptService")
local TweenService = game:GetService("TweenService")
local TextService = game:GetService("TextService")
local Players = game:GetService("Players")
local LocalPlayer = Players.LocalPlayer
local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")
local GUISurfaceTemplate = script:WaitForChild("SurfaceGui")
local GUIBillboardTemplate = script:WaitForChild("BillboardGui")
local PROMPTTemplate = script:WaitForChild("PromptBase")
local KeyboardButtonImage = {
[Enum.KeyCode.Backspace] = "rbxasset://textures/ui/Controls/backspace.png",
[Enum.KeyCode.Return] = "rbxasset://textures/ui/Controls/return.png",
[Enum.KeyCode.LeftShift] = "rbxasset://textures/ui/Controls/shift.png",
[Enum.KeyCode.RightShift] = "rbxasset://textures/ui/Controls/shift.png",
[Enum.KeyCode.Tab] = "rbxasset://textures/ui/Controls/tab.png",
}
local KeyboardButtonIconMapping = {
["'"] = "rbxasset://textures/ui/Controls/apostrophe.png",
[","] = "rbxasset://textures/ui/Controls/comma.png",
["`"] = "rbxasset://textures/ui/Controls/graveaccent.png",
["."] = "rbxasset://textures/ui/Controls/period.png",
[" "] = "rbxasset://textures/ui/Controls/spacebar.png",
}
local KeyCodeToTextMapping = {
[Enum.KeyCode.LeftControl] = "Ctrl",
[Enum.KeyCode.RightControl] = "Ctrl",
[Enum.KeyCode.LeftAlt] = "Alt",
[Enum.KeyCode.RightAlt] = "Alt",
[Enum.KeyCode.F1] = "F1",
[Enum.KeyCode.F2] = "F2",
[Enum.KeyCode.F3] = "F3",
[Enum.KeyCode.F4] = "F4",
[Enum.KeyCode.F5] = "F5",
[Enum.KeyCode.F6] = "F6",
[Enum.KeyCode.F7] = "F7",
[Enum.KeyCode.F8] = "F8",
[Enum.KeyCode.F9] = "F9",
[Enum.KeyCode.F10] = "F10",
[Enum.KeyCode.F11] = "F11",
[Enum.KeyCode.F12] = "F12",
}
local function getScreenGui()
local screenGui = PlayerGui:FindFirstChild("ProximityPrompts")
if screenGui == nil then
screenGui = Instance.new("ScreenGui")
screenGui.Name = "ProximityPrompts"
screenGui.ResetOnSpawn = false
screenGui.Parent = PlayerGui
end
return screenGui
end
function CustomPrompt:CreateSurfacePrompt(Part, objectText, keybinds, onKeyPressed)
local totalKeybinds = keybinds
--// Tweens
local tweensForFadeOut = {}
local tweensForFadeIn = {}
--// Tween Infos
local tweenInfoFast = TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
local tweenInfoQuick = TweenInfo.new(0.06, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
--// Create Surface Gui
local promptUI = GUISurfaceTemplate:Clone()
promptUI.Name = "Prompt"
promptUI.Adornee = Part
promptUI.Face = Enum.NormalId.Front
promptUI.Parent = getScreenGui()
--// Surface Gui parts
local frame = promptUI:WaitForChild("Main")
local line = frame:WaitForChild("Line")
local objectTextLabel = frame:WaitForChild("ObjectText")
local promptListFrame = frame:WaitForChild("Prompts")
--// Object Text
objectTextLabel.Text = objectText
objectTextLabel.TextTransparency = 1
table.insert(tweensForFadeIn, TweenService:Create(objectTextLabel, tweenInfoFast, { TextTransparency = 0 }))
table.insert(tweensForFadeOut, TweenService:Create(objectTextLabel, tweenInfoFast, { TextTransparency = 1 }))
--// Line Separator
line.Size = UDim2.fromScale(0, 0.02)
table.insert(tweensForFadeIn, TweenService:Create(line, tweenInfoFast, { Size = UDim2.fromScale(1, 0.02) }))
table.insert(tweensForFadeOut, TweenService:Create(line, tweenInfoFast, { Size = UDim2.fromScale(0, 0.02) }))
--// Prompt List Frame
promptListFrame.GroupTransparency = 1
table.insert(tweensForFadeIn, TweenService:Create(promptListFrame, tweenInfoFast, { GroupTransparency = 0 }))
table.insert(tweensForFadeOut, TweenService:Create(promptListFrame, tweenInfoFast, { GroupTransparency = 1 }))
--// Connections and state
local holdBeganConnection
local holdEndedConnection
local triggeredConnection
local currentlyHeldKey = nil
local keybindMap = {} -- stores data per key
--// Create Prompts
for _, bind in ipairs(totalKeybinds) do
local key = bind.Key
local action = bind.Interaction
local duration = bind.Duration
--// Duplicate Prompt
local promptFrame = PROMPTTemplate:Clone()
promptFrame.Parent = promptListFrame
table.insert(tweensForFadeIn, TweenService:Create(promptFrame, tweenInfoFast, { GroupTransparency = 0 }))
table.insert(tweensForFadeOut, TweenService:Create(promptFrame, tweenInfoFast, { GroupTransparency = 1 }))
--// Prompt Parts
local keycodeFrame = promptFrame:WaitForChild("Keycode")
local actionTextLabel = promptFrame:WaitForChild("Action")
local fillFrame = keycodeFrame:WaitForChild("Fill")
local keycodeScale = keycodeFrame:WaitForChild("UIScale")
local keycodeKeybindText = keycodeFrame:WaitForChild("TextLabel")
local keycodeKeybindImage = keycodeFrame:WaitForChild("ImageLabel")
--// Tweens
local tweenHoldScaleBegin = TweenService:Create(keycodeScale, tweenInfoFast, { Scale = 1.1 })
local tweenHoldScaleEnd = TweenService:Create(keycodeScale, tweenInfoFast, { Scale = 1 })
local tweenFillBegin = TweenService:Create(fillFrame, TweenInfo.new(duration), { Size = UDim2.fromScale(1, 1) })
local tweenFillEnd = TweenService:Create(fillFrame, tweenInfoFast, { Size = UDim2.fromScale(1, 0) })
-- Set text
actionTextLabel.Text = action
local buttonTextString = UserInputService:GetStringForKeyCode(key)
local buttonTextImage = KeyboardButtonImage[key]
if buttonTextImage == nil then
buttonTextImage = KeyboardButtonIconMapping[buttonTextString]
end
if buttonTextImage == nil then
local keyCodeMappedText = KeyCodeToTextMapping[key]
if keyCodeMappedText then
buttonTextString = keyCodeMappedText
end
end
if buttonTextImage then
keycodeKeybindImage.Image = buttonTextImage
elseif buttonTextString ~= nil and buttonTextString ~= "" then
keycodeKeybindText.Text = buttonTextString
else
error(
"ProximityPrompt "
.. " has an unsupported keycode for rendering UI: "
.. tostring(key)
)
end
-- Store all needed info in the keybindMap
keybindMap[key] = {
ScaleUp = tweenHoldScaleBegin,
ScaleDown = tweenHoldScaleEnd,
FillUp = tweenFillBegin,
FillDown = tweenFillEnd,
Duration = duration,
Action = action
}
end
--// Input Handling
holdBeganConnection = UserInputService.InputBegan:Connect(function(input, gp)
--// Ignore if typing in chat
if UserInputService:GetFocusedTextBox() then return end
if input.UserInputType ~= Enum.UserInputType.Keyboard then return end
if currentlyHeldKey then return end
local data = keybindMap[input.KeyCode]
if data and data.Duration > 0 then
currentlyHeldKey = input.KeyCode
data.ScaleUp:Play()
data.FillUp:Play()
triggeredConnection = data.FillUp.Completed:Connect(function()
data.ScaleDown:Play()
data.FillDown:Play()
if onKeyPressed then
onKeyPressed(data.Action)
end
if triggeredConnection then
triggeredConnection:Disconnect()
triggeredConnection = nil
end
currentlyHeldKey = nil
end)
end
end)
holdEndedConnection = UserInputService.InputEnded:Connect(function(input, gp)
if input.UserInputType ~= Enum.UserInputType.Keyboard then return end
local data = keybindMap[input.KeyCode]
if data and currentlyHeldKey == input.KeyCode then
data.ScaleDown:Play()
data.FillDown:Play()
if triggeredConnection then
triggeredConnection:Disconnect()
triggeredConnection = nil
end
currentlyHeldKey = nil
end
end)
--// Fade In
for _, tween in ipairs(tweensForFadeIn) do
tween:Play()
end
--// Cleanup method
function CustomPrompt:Cleanup()
if holdBeganConnection then holdBeganConnection:Disconnect() end
if holdEndedConnection then holdEndedConnection:Disconnect() end
if triggeredConnection then triggeredConnection:Disconnect() end
for _, tween in ipairs(tweensForFadeOut) do
tween:Play()
end
task.wait(0.25)
promptUI:Destroy()
end
end
return CustomPrompt
(not done yet)
While testing, I realized that I want it to be called by the server just like the default prompt since I want each prompt to run a specific code, but since the module used LocalPlayer
, it only works when being called by the client. I tried to make an approach using Remote Events
but due low amount of braincells left, I dont now how I would do that.
Heres the code that works for Local Script
Local Script
local Players = game:GetService("Players")
local ProximityPromptService = game:GetService("ProximityPromptService")
local CustomPrompt = require(game:GetService("ReplicatedStorage").CustomPrompt)
ProximityPromptService.PromptShown:Connect(function(prompt, inputType)
if prompt.Style == Enum.ProximityPromptStyle.Default then return end
local keybinds = {
{ Key = Enum.KeyCode.Q, Interaction = "action 1", Duration = 0.1 },
{ Key = Enum.KeyCode.E, Interaction = "action 2", Duration = 0.5 },
{ Key = Enum.KeyCode.F, Interaction = "action 3", Duration = 1 }
}
local objectText = prompt.ObjectText
CustomPrompt:CreateSurfacePrompt(prompt.Parent, objectText , keybinds, function(key)
if key == "action 1" then
print("action 1")
elseif key =="action 2" then
print("action 2")
else
print("action 3")
end
end)
local hiddenConn
hiddenConn = prompt.PromptHidden:Connect(function()
CustomPrompt:Cleanup()
if hiddenConn then
hiddenConn:Disconnect()
hiddenConn = nil
end
end)
end)
What I’m thinking is when a prompt is shown, the remote event is fired, received by a local script, and that local script handles the module. Same for when the prompt is hidden. I also realized just now that I dont know how the server would handle the input began
note: I know its kind of messy. Thanks for the help