Passing self into UserInputService:InputBegan?

I’m trying to be a cool guy and use a modulescript to implement a thing I’m working on, but I’m brain farting on something I’m sure is super simple. The module itself will need to reassign a module variable anytime the mouse moves, so I used RunService and UserInputService:GetMouseLocation() to detect when the mouse moves and reassign the variable like that. The current implementation looks somewhat like this:

function module.new()
	local self = {
		activated = false,
		location = Vector3.new(0,0,0)
	}
	setmetatable(self, module)
	
	self:Activate() -- only here for testing
	
	return self
end

function module:Activate()
	RunService:BindToRenderStep("movement", Enum.RenderPriority.Input.Value, function ()
		self:UpdateMovement()
	end)
	self.activated = true
end

function module:UpdateMovement()
 	local newLocation = UserInputService:GetMouseLocation()
	if newLocation.Magnitude - self.location.Magnitude == 0 then -- not actually tested to work, but you get the gist
		print("no movement")
		return
	end
	self.location = newLocation
	
	-- more stuff...
end

function module:Deactivate()
	if self.activated then
		RunService:UnbindToRenderStep("movement")
	end
end

function module:Destroy()
	self:Deactivate()
end

The above works, but running something every frame is wasteful when I only want to run something when an input happens. So, I’ve been looking to migrate the above to UserInputService:InputBegan. However, I have no idea how to do that. Not because I don’t know how to use InputBegan but because I need to change self.location inside of the connect when it’s not in scope. Am I missing something stupid or should I just say screw it and march on with what I already have?

Use events instead.

The InputBegan event triggers any time any kind of input begins. This includes mouse movement.
Simply bind your function to this event, and verify that the input in question was mouse movement.

UserInputService.InputBegan:Connect(function(Input, GPE)
    if GPE then return end
    if Input.UserInputType ~= Enum.UserInputType.MouseMovement then return end

    -- Do stuff here
end)
1 Like

i may be wrong but i think
that when you try to reference self in the callback of UserInputService.InputBegan function it will use the one of the original method module:UpdateMovement()

if this isn’t true then you can create a local variable that references to self and use it inside of the event callback
since tables are passed by reference selfRef in this examble is just a link to self

function module:UpdateMovement()
	local newLocation = UserInputService:GetMouseLocation()
	if newLocation.Magnitude - self.location.Magnitude == 0 then -- not actually tested to work, but you get the gist
		print("no movement")
		return
	end
	self.location = newLocation
	
	local selfRef = self
	
	UserInputService.InputBegan:Connect(function()
		
		
		selfRef.location
	end)

	-- more stuff...
end

Yeah, I stumbled onto this while looking through the UserInputType documentation, which drove me to want to port my RunService code to UserInputService. My main problem is that when I connect InputBegan, I go out of scope for the current running instance of self. This screws me up because the code I need to run inside InputBegan needs to set specific variables to self. I feel like the solution to this is extremely simple, but I’m brain-farting on it

When I use self there, autocomplete forgets everything inside of self and the type checker gives me unbound variable errors

Won’t that make a new instance of self? I’m trying to use the same instance of self in UserInputService.InputBegan as I do in module.new() because the self created in module.new() is planned to have more variables than just self.location.

Before I passed out yesterday I tried doing something similar to your code and I’m still getting compile errors:

local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Navigation = {}
module.__index = module


function Navigation.new()
	local self = {
		Connections = {},
		Location = Vector3.new(0,0,0)
	}
	setmetatable(self, module)
	self:Activate() -- Just here for testing purposes
	return self
end

function module:Activate()
	
	table.insert(self.Connections, UserInputService.InputBegan:Connect(function(input: InputObject, inGui: boolean) 

		if inGui then return end

		if input.UserInputType == Enum.UserInputType.MouseMovement then
			self:UpdateMovement()
		end
	end))
	
end

function module:UpdateMovement()
 	local newLocation = UserInputService:GetMouseLocation()
	if newLocation.Magnitude - self.location.Magnitude == 0 then -- not actually tested to work, but you get the gist
		print("no movement")
		return
	end
	self.location = newLocation
	
	-- more stuff...	
end

function Navigation:Destroy()
	for connection:RBXScriptSignal in self.Connections do
		connection:Disconnect()
	end	
end

return module

When I run it, I get:

ReplicatedStorage.Gameplay.Test.Scripts.ModuleScript:49: invalid argument #1 to 'insert' (table expected, got nil)  -  Client - ModuleScript:49
  05:53:51.650  Stack Begin  -  Studio
  05:53:51.651  Script 'ReplicatedStorage.Gameplay.Test.Scripts.ModuleScript', Line 49 - function Activate  -  Studio - ModuleScript:49
  05:53:51.651  Script 'Players.normalperson921.PlayerScripts.LocalScript', Line 5  -  Studio - LocalScript:5
  05:53:51.651  Stack End  -  Studio

I’m definitely doing something wrong, but what?

Quick Edit for more information:
I put some BreakPoints in my code to stop near the beginning of self:Activate() and for some reason self only has its functions passed???
image
Here’s where I put the breakpoint specifically:

function module:Activate()
	
	table.insert(self.Connections, UserInputService.InputBegan:Connect(function(input: InputObject, inGui: boolean) --< Breakpoint Inserted Here

		if inGui then return end

		if input.UserInputType == Enum.UserInputType.MouseMovement then
			self:UpdateMovement()
		end
	end))
	
end

tables in luau are passed by reference so it would be just a linkto the original table that is also how module scripts work,
examples

local tableA = {}
tableA.Cash = 500


local tblAReference = tableA -- tblAReference is simply a link/pointer to tableA


print(tblAReference.Cash) -- 500

tblAReference.Cash = 100 

print(tableA.Cash) -- 100
local tableA = {}
tableA.Cash = 500


local function incrementAValueOfTable(tbl, index, value)
	tbl[index] = value
end


print(tableA.Cash) -- 500

incrementAValueOfTable(tableA , "Cash", 50)

print(tableA.Cash) -- 50

also, i may be missing something but i tried using self and it works

local RunService = game:GetService("RunService")

local t = {}
 
t.Value = "My Cool Value"


function t:Method()
	local connection = RunService.Heartbeat:Connect(function()
		-- am able to use self here
		print(self.Value) -- My Cool Value
	end)
end


t:Method()

ReplicatedStorage.Gameplay.Test.Scripts.ModuleScript:49: invalid argument #1 to 'insert' (table expected, got nil)  -  Client - ModuleScript:49

that is confusing since you passed self.Connections which is a table

local self = {
		Connections = {},
table.insert(self.Connections

I am confused tbh

not sure but you can pass self as an argument to the function (and the argument name doesn’t have to be self and it would still work )
an example (not really clear but i tried)

local RunService = game:GetService("RunService")

local t = {}
 
t.Value = "My Cool Value"
t.Location = Vector3.new(5,5,5)

local function heartBeat(self, dt)
	print(self.Value)
	print(self.Location)
	print(dt)
	-- changing self here will change the original self too and any change to the original self will reflect here
end


function t:Method()
	local connection = RunService.Heartbeat:Connect(function()
		heartBeat(self)
	end)
end

t:Method()