E to interact events

I have a script in StarterPlayerScripts to handle E to interact events, which is meant to check if a part is within range, enable a BillboardGui, then when the E Key is pressed, TweenSize a grey label from YScaleSize 1 to 0, exposing a white label underneath, yes a little (read a lot) like Mad City.
There are 2 issues with this:

  1. I am trying to use the callback function to trigger giving cash to the player once the tween has completed. However, this triggers as soon as the players presses the E key.
  2. I have a table of parts which can be interacted with, ATM1, ATM2, ATM3. If only ATM1 is in the table, then I can approach it, press the E key and it does it’s thing (ignoring issue 1 above for now).
    If I then add ATM2 into the table, ATM1 stops responding to the E key, but ATM2 is OK. Add ATM3, then ATM1 & ATM2 fail to respond.

There is obviously something very wrong with how I am understanding the function callback within the tween and also with how I am handling the table of objects to interact with.
I started to read about collectionservice as a better way to handle the interactions as more are added, but I am not sure how to go about this.

-- SERVICES
local tweenservice = game:GetService("TweenService")

-- VARIABLES
local debounce = false
local player = game.Players.LocalPlayer
repeat wait() until player.Character ~= nil
local char = player.Character
local interactDistance = 10
local canInteract = false
local interactables = {
	workspace.ATM1.Screen,
	workspace.ATM2.Screen,
	workspace.ATM3.Screen
} 
local nearestpart = nil
local resetTimer = 10

local function giveCash(part)
	print("Give ", char.Name, " CASH from ", part.Parent.Name)
	if part.Name == "Screen" then
		wait(5)
		part.BillboardGui.Enabled = false
		part.BillboardGui.Frame.lbl_grey.Size = UDim2.new(1, 0, 1, 0)
		part.Color = Color3.new(1, 0, 0)
		nearestpart = nil
		wait(resetTimer)
		debounce = false
		part.BillboardGui.Enabled = true
		part.BillboardGui.Frame.lbl_grey.Size = UDim2.new(1, 0, 1, 0)
		part.Color = Color3.new(0.5, 0.73, 0.85)
	end
end

game:GetService("UserInputService").InputBegan:connect(function(key)
	if key.KeyCode == Enum.KeyCode.E and canInteract == true then
		if debounce == true then
			return
		else
			debounce = true
			print(player, " pressed E")
			local interactpart = nearestpart
			if nearestpart.Name == "Screen" then
				local Etween = interactpart.BillboardGui.Frame.lbl_grey:TweenSize(
					UDim2.new(1, 0, 0, 0),		-- endSize (required)
					Enum.EasingDirection.In,	-- easingDirection (default Out)
					Enum.EasingStyle.Sine,  	-- easingStyle (default Quad)
					5,							-- time (default: 1)
					true,						-- should this tween override ones in-progress? (default: false)
					giveCash(nearestpart)		-- a function to call when the tween completes (default: nil)
				)
			end
			wait(5)
			debounce = false
		end
	end
end)

-- Start E Bindable Events
while wait(0.5) do
	for i, part in pairs(interactables) do
		if (part.Position - char.HumanoidRootPart.Position).magnitude < interactDistance then
			canInteract = true
			nearestpart = part
		else
			canInteract = false
			nearestpart = nil
		end
	end
end

Any pointers would be greatly appreciated.
I spend a lot of time reading the Q&As on the DevForum and am truly astounded at how helpful the community is. I just paddle around the edges looking for hints as to how to implement my sons latest idea into his game.
The game itself is Downfall Avenue and the ATMs are outside of the bank, goading me, daring me to fix them…

2 Likes
  1. Cash giving
    When you give a function call as an argument for a function, you are actually giving the value of the function call, not the function itself. So, what happens is that when you call :TweenSize, the giveCash function runs first and it’s return value is given to :TweenSize. Givecash returns nothing, so that value is nil.

That’s why the cash is given instantly and nothing happens when the tween finishes. This can be fixed easily. Give the actual function as an argument and remove the part parameter of giveCash. Then you can either replace all the part words in the function with nearestPart or create the part variable in the beginning of the function like this

local part = nearestPart
  1. ATMs not responding
    Your while wait(0.5) do loop doesn’t check what is the nearest ATM. It just loops through each one of them and sets the last one as the atm to interact with (nearest part), if it’s close enough. The first two will never be chosen, because if they are close enough but the third isn’t it then when the loops checks the third one’s distance, it just sets nearestPart and canInteract as nil.
1 Like

How you’re checking for the closest interactable part is a bit sloppy; it can only give you the last ATM listed as an output. If only ATM2 is in range, on the 2nd loop it will pick it up just fine. When we move to ATM3, it will set canInteract=false and nearestpart=nil, cancelling all the work done in the 2nd loop.

Something like this would be better suited

-- Start E Bindable Events
while wait(0.5) do

    local closest = {
        part = false;
        distance = false;
    }

    for i, interactPart in pairs(interactables) do
        local thisDistance = (interactPart.Position - char.HumanoidRootPart.Position).magnitude;
        local isReachable = thisDistance < interactDistance;

		if (isReachable and not closest.distance) or (thisDistance < closest.distance) then --if we can reach this part and its closer than another part that we can also reach
            closest.part = interactPart;
            closest.distance = thisDistance;
		end
    end
    
    if closest.part then
        canInteract = true;
        nearestpart = closest.part;
    else
        canInteract = false;
        nearestpart = false;
    end

end

Thanks Integern I should have spotted the failure there, it was sloppy. That has certainly solved issue 2.

I must admit I didn’t fully follow your response RoBoPoJu. Reading the man page for GUIOBJECT:TweenSize it says:

callback Function nil A callback function to execute when the tween completes

I took that as a literal that the function would execute once the tween had ended.
I then thought I could just add .Completed:Wait() but I think how I am calling the tween doesn’t permit that.

In the end I mangled together the following that works:

-- SERVICES
local tweenservice = game:GetService("TweenService")

-- VARIABLES
local debounce = false
local player = game.Players.LocalPlayer
repeat wait() until player.Character ~= nil
local char = player.Character
local interactDistance = 8
local canInteract = false
local interactables = {
	workspace.ATM1.Screen,
	workspace.ATM2.Screen,
	workspace.ATM3.Screen
} 
local nearestpart = nil
local resetTimer = 10
local Etween

local function callback(didComplete)
	if didComplete then
		part = nearestpart
		spawn(function()
			print("Give ", char.Name, " CASH from ", part.Parent.Name)
			if part.Name == "Screen" then
				part.BillboardGui.Enabled = false
				part.BillboardGui.Frame.lbl_grey.Size = UDim2.new(1, 0, 1, 0)
				part.Color = Color3.new(1, 0, 0)
				nearestpart = nil
				wait(resetTimer)
				debounce = false
				part.BillboardGui.Enabled = true
				part.BillboardGui.Frame.lbl_grey.Size = UDim2.new(1, 0, 1, 0)
				part.Color = Color3.new(0.5, 0.73, 0.85)
			end
		end)
	else
		-- Tween interrupted
		return
	end
end

game:GetService("UserInputService").InputBegan:Connect(function(key)
	if key.KeyCode == Enum.KeyCode.E and canInteract == true then
		if debounce == true then
			return
		else
			debounce = true
			print(player, " pressed E")
			local interactpart = nearestpart
			if nearestpart.Name == "Screen" then
				Etween = interactpart.BillboardGui.Frame.lbl_grey:TweenSize(
					UDim2.new(1, 0, 0, 0),		-- endSize (required)
					Enum.EasingDirection.In,	-- easingDirection (default Out)
					Enum.EasingStyle.Sine,  	-- easingStyle (default Quad)
					5,							-- time (default: 1)
					true,						-- should this tween override ones in-progress? (default: false)
					callback					-- a function to call when the tween completes (default: nil)
				)
			end
			debounce = false
		end
	end
end)

-- Start E Bindable Events
while wait(0.5) do
	local closest = {
		part = false;
		distance = 100;
	}
	for i, interactPart in pairs(interactables) do
		local thisDistance = (interactPart.Position - char.HumanoidRootPart.Position).magnitude;
		local isReachable = thisDistance < interactDistance;
		if (isReachable and not closest.distance) or (thisDistance < closest.distance) then	
			closest.part = interactPart;
			closest.distance = thisDistance;
		end
	end
	
	if closest.part then
		canInteract = true;
		nearestpart = closest.part;
	else
		canInteract = false;
		nearestpart = false;
	end
end

I now just need to add an InputEnded event to try and cancel the tween, but I am suspecting I will need to rewrite the tween to separate the tweeninfo, tweencreate and tween:Play to be able to Cancel when the player releases the key.

I am stuck as to who to mark for the solution, as I face the dilemma that both of you assisted.

NOTE: I only posted the full code here as I find it useful to pull apart other peoples solutions to re-purpose them, so I hope that above might help other people.

1 Like