Wonderful system, makes what used to be an annoying task into a quick and simple one. I do feel like there is one small property that is missing, however. Regardless of camera distance, the prompt will show up. If someone has their camera zoomed far out and encounters a proximity prompt, it covers up anything within its screen space. Most of the problem comes from the fact that the prompts use pixel sizes instead of stud / scale sizes, which means they are massive when you zoon out. I feel that there is a need to control the maximum distance you camera can be before showing up.
is there a way to have the proximity prompt seen through players but not through parts because Iâm having trouble
While I appreciate Proximity Prompts (which has more cross-platform functionality than my own âinteraction systemâ) then there is one thing annoying me particularly. The GUI attempts to reach its âstart phaseâ before even sending the Triggered signal. Which gives awful looking results like this and this, the last example being from Robloxâs own showcase. What youâll see is the GUI attempting to ârecoverâ before the ProximityPrompt can be set to Enabled = false. Ideally there would be a wait between the triggered signal being fired and before the GUI attempts to ârecoverâ, in my first example it has recovered 25% which equals to 0.25 second before being able to be disabled, little time indeed but enough time to look off and weird.
What I would do is have a player object/table of player objects whose proximity prompts will be disabled and when the NPC proximity prompt event is triggered by a player, fire to their client (or table of players)'s clients where a localscript/ module disabled the prompt on their client.
To prevent additional server NPC triggers when not needed simply use a server variable to indicate that a player is interacting for instance so that a client triggering a normally hidden prompt would simply be rejected on the server.
Yeah but imo it should be built in. Thanks anyways!
Small âBugâ? @crypto_mancer
If the hold duration is 0 and you hop in the game and edit it to something like 1, you donât get the radial progress effect until you walk away from the prompt and then re-enter its activation distance again
Also, the radial progress effect is a little strange to me. It seems to progress at the same rate always which can be confusing.
At HoldDuration of 0.5 here, it looks like it is cancelling because 0.5 seconds have elapsed but the progress indicates that itâs half way.
It should probably progress based on timeElapsed/HoldDuration to avoid this confusion
I may have repeated this before, but we truly need customization for UI size, and for more requires line of sight options.
This is one of my favorite features so far, so it will be great if it continues to be updated.
When smooth terrain was forced, you could not walk through holes you were once able to, and things sank into the ground.
These will be very useful. Thank you!
Yeah I did have this issue when i was using it, maybe you should add like 1 second of delay before reenabling the second Prompt.
Yes I know that, but the game was still playable, ofcourse it no longer worked like it should have but was playable, what I mean by playable is that you can load into the game without errors
Is it possible to have a customize feature? So we can customize the ui? (Different colors, etc)
It is because youâre changing it on the client side and not the server side.
Is anyone noticing some odd behavior from Custom ProximityPrompts, specially on mobile devices? The output isnât really giving me a ton of meaningful information as to what is going wrong here. Mobile devices are having issues where this table being returned is actually returning nil.
local datastuff = {
Cleanup = function()
PromptUI.Parent = nil
end;
Toggle = function(Visible)
PromptUI.Enabled = Visible
end;
}
warn(datastuff)
return datastuff
This is really odd because my code works perfectly fine on PC, and the warn statement above will print a memory address for the table. The only outside library I am using here is Knit, which just functions as a framework for my codebase. You can view the full client-side code responsible for handling interactions here:
-- InteractionController
-- Konstantine Papa
-- January 27, 2021
local ProximityPromptService = game:GetService("ProximityPromptService")
local UserInputService = game:GetService("UserInputService")
local TextService = game:GetService("TextService")
local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")
local Knit = require(game:GetService("ReplicatedStorage").Modules.Knit)
local InteractionService = Knit.GetService("InteractionService")
local InteractionController = Knit.CreateController { Name = "InteractionController" }
local Player = Players.LocalPlayer
local PlayerGui = Player:WaitForChild("PlayerGui")
local GamepadButtonImage = {
[Enum.KeyCode.ButtonX] = "rbxasset://textures/ui/Controls/xboxX.png";
[Enum.KeyCode.ButtonY] = "rbxasset://textures/ui/Controls/xboxY.png";
[Enum.KeyCode.ButtonA] = "rbxasset://textures/ui/Controls/xboxA.png";
[Enum.KeyCode.ButtonB] = "rbxasset://textures/ui/Controls/xboxB.png";
[Enum.KeyCode.DPadLeft] = "rbxasset://textures/ui/Controls/dpadLeft.png";
[Enum.KeyCode.DPadRight] = "rbxasset://textures/ui/Controls/dpadRight.png";
[Enum.KeyCode.DPadUp] = "rbxasset://textures/ui/Controls/dpadUp.png";
[Enum.KeyCode.DPadDown] = "rbxasset://textures/ui/Controls/dpadDown.png";
[Enum.KeyCode.ButtonSelect] = "rbxasset://textures/ui/Controls/xboxmenu.png";
[Enum.KeyCode.ButtonL1] = "rbxasset://textures/ui/Controls/xboxLS.png";
[Enum.KeyCode.ButtonR1] = "rbxasset://textures/ui/Controls/xboxRS.png";
}
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";
}
function InteractionController:KnitStart()
local function GetScreenGui()
local ScreenGui = PlayerGui:FindFirstChild("Interactions")
if (ScreenGui == nil) then
ScreenGui = Instance.new("ScreenGui")
ScreenGui.Name = "Interactions"
ScreenGui.ResetOnSpawn = false
ScreenGui.Parent = PlayerGui
end
return ScreenGui
end
local function CreatePrompt(Prompt, InputType, Gui)
local PromptUI = Instance.new("BillboardGui")
PromptUI.Name = "Prompt"
PromptUI.LightInfluence = 0
PromptUI.AlwaysOnTop = true
PromptUI.Size = UDim2.new(0, 200, 0, 80)
local Frame = Instance.new("Frame")
Frame.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
Frame.AnchorPoint = Vector2.new(0.5, 0.5)
Frame.Position = UDim2.new(0.5, 0, 0.5, 0)
Frame.Size = UDim2.new(1, 0, 1, 0)
Frame.Parent = PromptUI
local UIAspectRatioConstraint = Instance.new("UIAspectRatioConstraint")
UIAspectRatioConstraint.Parent = Frame
local UICorner = Instance.new("UICorner")
UICorner.CornerRadius = UDim.new(0.15, 0)
UICorner.Parent = Frame
local KeyLabel = Instance.new("TextLabel")
KeyLabel.BackgroundTransparency = 1
KeyLabel.Name = "KeyLabel"
KeyLabel.Position = UDim2.new(0, 0, 0.25, 0)
KeyLabel.Size = UDim2.new(1, 0, 0.7, 0)
KeyLabel.Font = Enum.Font.SourceSansBold
KeyLabel.TextColor3 = Color3.fromRGB(121, 201, 173)
KeyLabel.TextScaled = true
KeyLabel.Parent = Frame
local Header = Instance.new("Frame")
Header.BackgroundColor3 = Color3.fromRGB(121, 201, 173)
Header.Name = "Header"
Header.Size = UDim2.new(1, 0, 0.3, 0)
Header.Parent = Frame
local HeaderUICorner = Instance.new("UICorner")
HeaderUICorner.CornerRadius = UDim.new(0.45, 0)
HeaderUICorner.Parent = Header
local HeaderFrame = Instance.new("Frame")
HeaderFrame.AnchorPoint = Vector2.new(0, 1)
HeaderFrame.BorderSizePixel = 0
HeaderFrame.BackgroundColor3 = Color3.fromRGB(121, 201, 173)
HeaderFrame.Position = UDim2.new(0, 0, 1, 0)
HeaderFrame.Size = UDim2.new(1, 0, 0.5, 0)
HeaderFrame.Parent = Header
local TitleLabel = Instance.new("TextLabel")
TitleLabel.BackgroundTransparency = 1
TitleLabel.Name = "Title"
TitleLabel.Size = UDim2.new(1, 0, 1, 0)
TitleLabel.Font = Enum.Font.SourceSansBold
TitleLabel.Text = Prompt.ActionText
TitleLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
TitleLabel.TextScaled = true
TitleLabel.Parent = Header
if (InputType == Enum.ProximityPromptInputType.Gamepad) then
if GamepadButtonImage[Prompt.GamepadKeyCode] then
KeyLabel:Destroy()
KeyLabel = Instance.new("ImageLabel")
KeyLabel.BackgroundTransparency = 1
KeyLabel.Name = "KeyLabel"
KeyLabel.Position = UDim2.new(0, 0, 0.25, 0)
KeyLabel.Size = UDim2.new(1, 0, 0.7, 0)
KeyLabel.ImageTransparency = 1
KeyLabel.Image = GamepadButtonImage[Prompt.GamepadKeyCode]
KeyLabel.Parent = Frame
end
elseif (InputType == Enum.ProximityPromptInputType.Touch) then
KeyLabel:Destroy()
KeyLabel = Instance.new("ImageLabel")
KeyLabel.BackgroundTransparency = 1
KeyLabel.Name = "KeyLabel"
KeyLabel.Position = UDim2.new(0, 0, 0.25, 0)
KeyLabel.Size = UDim2.new(1, 0, 0.7, 0)
KeyLabel.ImageTransparency = 1
KeyLabel.Image = "rbxasset://textures/ui/Controls/TouchTapIcon.png"
KeyLabel.Parent = Frame
else
local ButtonTextString = UserInputService:GetStringForKeyCode(Prompt.KeyboardKeyCode)
local ButtonTextImage = KeyboardButtonImage[Prompt.KeyboardKeyCode]
if (ButtonTextImage == nil) then
ButtonTextImage = KeyboardButtonIconMapping[ButtonTextString]
end
if (ButtonTextImage == nil) then
local KeyCodeMappedText = KeyCodeToTextMapping[Prompt.KeyboardKeyCode]
if (KeyCodeMappedText) then
ButtonTextString = KeyCodeMappedText
end
end
if (ButtonTextImage) then
KeyLabel:Destroy()
KeyLabel = Instance.new("ImageLabel")
KeyLabel.BackgroundTransparency = 1
KeyLabel.Name = "KeyLabel"
KeyLabel.Position = UDim2.new(0, 0, 0.25, 0)
KeyLabel.Size = UDim2.new(1, 0, 0.7, 0)
KeyLabel.ImageTransparency = 1
KeyLabel.Image = ButtonTextImage
KeyLabel.Parent = Frame
elseif (ButtonTextString ~= nil and ButtonTextString ~= " ") then
KeyLabel.Text = ButtonTextString
else
error("ProximityPrompt " .. Prompt.Name .. " has an unsupported keycode for rendering UI; " .. tostring(Prompt.KeyboardKeyCode))
end
if (InputType == Enum.ProximityPromptInputType.Touch or Prompt.ClickablePrompt) then
local Button = Instance.new("TextButton")
Button.BackgroundTransparency = 1
Button.TextTransparency = 1
Button.Size = UDim2.fromScale(1, 1)
Button.Parent = PromptUI
local ButtonDown = false
Button.InputBegan:Connect(function(Input)
if ((Input.UserInputType == Enum.UserInputType.Touch) or Input.UserInputType == Enum.UserInputType.MouseButton1 and Input.UserInputState ~= Enum.UserInputState.Change) then
Prompt:InputHoldBegin()
ButtonDown = true
end
end)
Button.InputEnded:Connect(function(Input)
if (Input.UserInputType == Enum.UserInputType.Touch or Input.UserInputType == Enum.UserInputType.MouseButton1) then
if (ButtonDown) then
ButtonDown = false
Prompt:InputHoldEnd()
end
end
end)
PromptUI.Active = true
end
PromptUI.Adornee = Prompt.Parent
PromptUI.Parent = Gui
local datastuff = {
Cleanup = function()
PromptUI.Parent = nil
end;
Toggle = function(Visible)
PromptUI.Enabled = Visible
end;
}
warn(datastuff)
return datastuff
end
end
do
local function OnTriggered(Prompt, Cache)
if (Prompt.ActionText == "Sit") then
Cache.Toggle(false)
local Character = Player.Character or Player.CharacterAdded:Wait()
local Humanoid = Character:WaitForChild("Humanoid")
Humanoid:GetPropertyChangedSignal("Sit"):Connect(function()
if (not Humanoid.Sit) then
Cache.Toggle(true)
end
end)
end
end
ProximityPromptService.PromptShown:Connect(function(Prompt, InputType)
if Prompt.Style == Enum.ProximityPromptStyle.Default then
return
end
local Cache = CreatePrompt(Prompt, InputType, GetScreenGui())
Prompt.Triggered:Connect(function()
OnTriggered(Prompt, Cache)
end)
Prompt.PromptHidden:Wait()
Cache.Cleanup()
end)
end
end
function InteractionController:KnitInit()
end
return InteractionController
Iâve gone as far as to place print statements everywhere and it has all come down to that datastuff
table returning nil on mobile. The fact this problem is only occuring on mobile makes me think itâs an issue with ProximityPrompt.
If I canât find a fix for this issue I think my best bet would be to continue with coding my own interaction system without the use of ProximityPrompt. Itâs already proved to be pretty problematic for me and itâs ridiculous how much work needs to go into it to provide some basic functionality.
Iâd have to say, Proximity Prompts are one of the best things made on the platform. Such a simple tool can be taken so far with the style feature.
Users might think âI can only change the design of itâ Well, thats where your wrong.
Iâm currently working on a system that allowed me to make Interactions that look like a entire custom system. when its literally just proximity prompts.
Its still in testing/development and I plan on adding a few more things before its finished. I will not be open sourcing it or giving it out for sale but I want to let you guys see what you can really do if you put your mind to it with these prompts.
Youâre absolutely correct! I use an invisible custom ProximityPrompt to trigger open and close events on doors in my game:
I do find that if the ProximityPrompt is out of view of the camera, it fires the PromptHidden event, which isnât ideal for doors, as with certain doors they will close while youâre still walking through them if the door is large enough.
I would appreciate if Roblox could add a property to disable this behaviour or a separate event for approached/departed (which would be great for this sort of use-case, where no interaction, apart from pure proximity, is required).
If I understand the code correctly, it looks like youâre only returning datastuff
inside the else
block of this conditional:
elseif (InputType == Enum.ProximityPromptInputType.Touch) then
This might make it not function when on mobile. Possibly try moving the datastuff
code down one indention level.
I like this prompt, I will use it forever!
ProximityPromptService.PromptTriggered
ProximityPromptService.PromptButtonHoldBegan
ProximityPromptService.PromptButtonHoldEnded
Should all have Player as the first argument.
This in order to stay consistent with RemoteEvents, RemoteFunctions and ProximityPrompts: Triggered, PromptButtonHoldBegan and PromptButtonHoldEnded events.
I disagree, I think in the context of those events being Service-sided, the absolute most important argument passed there is the prompt instance. Without the prompt instance, those events are basically useless, meaning you will be using that first argument 100% of the time. By contrast, thereâs plenty of uses for those events that might not require knowing which player fired the prompt.