Proximity Prompt Release

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.

2 Likes

is there a way to have the proximity prompt seen through players but not through parts because I’m having trouble

1 Like

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.

2 Likes

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.

1 Like

Yeah but imo it should be built in. Thanks anyways!

1 Like

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.

6f6d89a10f55272806e5d30938b4c997

It should probably progress based on timeElapsed/HoldDuration to avoid this confusion

4 Likes

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.

1 Like

When smooth terrain was forced, you could not walk through holes you were once able to, and things sank into the ground.

2 Likes

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.

1 Like

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

2 Likes

Is it possible to have a customize feature? So we can customize the ui? (Different colors, etc)

1 Like

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.

1 Like

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.

https://gyazo.com/229b49596ede825148c29c907329266b

2 Likes

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).

3 Likes

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.

2 Likes

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.

1 Like