How to implement "Auto-Move"

I’m attempting to implement an “Auto-Move” feature similar to the Elder Scrolls. The player presses a button and the player’s character moves in the direction the camera is pointed until the player either presses the button again or presses one of the direction keys.

According to the documentation:

void Move ( Vector3 walkDirection , bool relativeToCamera )
Causes the player’s character to walk in the given direction until stopped, or interrupted by the player (by using their controls).

So in theory this should do exactly what I want but when I hit the C key the character just stands there. Here’s the code I’ve written, which is a combination of examples straight out of the documentation as well.


local function handleAction(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.Begin then
		game.Players.LocalPlayer:Move(Vector3.new(0, 0, -1), true)
	end
end

ContextActionService:BindAction("Auto-Move", handleAction, true, Enum.KeyCode.C, Enum.KeyCode.ButtonR1)

Have you tried printing to see what the exact problem is? Your function not might be running, maybe try UserInputService instead of ContextActionService for registering button presses.

I cobbled together a script that should do what you want:

local ContextActionService = game:GetService("ContextActionService")
local LocalPlayer = game:GetService("Players").LocalPlayer

local cancel = false
local function handleAction(actionName, inputState, inputObject)
	if not LocalPlayer.Character then return end
	local Character = LocalPlayer.Character
	local Humanoid = Character.Humanoid
	if inputState == Enum.UserInputState.Begin then
		cancel = not cancel
		while cancel do
			Humanoid:MoveTo(Character.PrimaryPart.CFrame.LookVector + Character.PrimaryPart.Position)
			Humanoid.MoveToFinished:Wait()
		end
		
	end
end
ContextActionService:BindAction("Auto-Move", handleAction, true, Enum.KeyCode.C, Enum.KeyCode.ButtonR1)

The only thing that you might want to change is that it moves the character in the direction that it is facing rather than moving in the direction the camera is facing.

This should work exactly as you described:

local ContextActionService = game:GetService("ContextActionService")
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")

local localPlayer = Players.LocalPlayer

local AUTO_MOVE_START_KEYS_LIST = { Enum.KeyCode.C, Enum.KeyCode.ButtonR1 }
local DIRECTION_MOVE_KEYS_LIST = { Enum.KeyCode.W, Enum.KeyCode.A, Enum.KeyCode.S, Enum.KeyCode.D }

local getLocalPlayerHumanoid = nil
do
	local cachedLocalPlayerHumanoid: Humanoid? = nil

	function getLocalPlayerHumanoid()
		if cachedLocalPlayerHumanoid then
			return cachedLocalPlayerHumanoid
		end

		local character = localPlayer.Character
		if not character then
			return
		end

		local humanoid: Humanoid? = character:FindFirstChildOfClass("Humanoid")
		if not humanoid then
			return
		end

		cachedLocalPlayerHumanoid = humanoid

		humanoid.AncestryChanged:Connect(function()
			if not humanoid:IsDescendantOf(game) then
				cachedLocalPlayerHumanoid = nil
			end
		end)

		return humanoid
	end
end

local ancestryChangedConnection: RBXScriptConnection? = nil
local diedConnection: RBXScriptConnection? = nil
local autoMoveThread: thread? = nil

local function closeAutoMoveThread()
	ancestryChangedConnection:Disconnect()
	diedConnection:Disconnect()
	coroutine.close(autoMoveThread)
	autoMoveThread = nil
end

local function handleAction(_, inputState: Enum.UserInputState, inputObject: InputObject)
	if inputState == Enum.UserInputState.Begin then
		if autoMoveThread then
			closeAutoMoveThread()

			local humanoid = getLocalPlayerHumanoid()
			if humanoid then
				local rootPart = humanoid.RootPart
				if rootPart then
					humanoid:MoveTo(rootPart.Position)
				end
			end
		elseif table.find(AUTO_MOVE_START_KEYS_LIST, inputObject.KeyCode) then
			for _, keyCode in ipairs(DIRECTION_MOVE_KEYS_LIST) do
				if UserInputService:IsKeyDown(keyCode) then
					return
				end
			end

			local humanoid = getLocalPlayerHumanoid()
			if humanoid then
				local rootPart = humanoid.RootPart
				if rootPart then
					autoMoveThread = coroutine.create(function()
						while true do
							local camera = workspace.CurrentCamera
							if camera then
								humanoid:MoveTo(camera.CFrame.LookVector * 10 + rootPart.Position)
							end

							task.wait()
						end
					end)

					coroutine.resume(autoMoveThread)

					ancestryChangedConnection = humanoid.AncestryChanged:Connect(function()
						if not humanoid:IsDescendantOf(game) then
							closeAutoMoveThread()
						end
					end)

					diedConnection = humanoid.Died:Connect(closeAutoMoveThread)
				end
			end
		end
	end
end

ContextActionService:BindActionAtPriority(
	"Auto-Move",
	handleAction,
	true,
	Enum.ContextActionPriority.Low.Value,
	unpack(AUTO_MOVE_START_KEYS_LIST),
	unpack(DIRECTION_MOVE_KEYS_LIST)
)

I haven’t used printing but I set break points and it executed the Move function as expected but it doesn’t seem to have any effect.

Perhaps Player:Move is broken or its purpose is different than what I thought. In any case, I’m trying out the two other solutions posted.

I stumbled across Core-Scripts/ControlScript.lua at master · Roblox/Core-Scripts · GitHub and it gave me an idea on how to implement Auto-Move.

local AutoMove = false

local function handleAction(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.Begin then
		AutoMove = not AutoMove
	end
end

local player = game.Players.LocalPlayer

local function onRenderStep(deltaTime)
	if AutoMove then
		player:Move(Vector3.new(0, 0, -1), true)
	end
end

local ContextActionService = game:GetService("ContextActionService")
ContextActionService:BindAction("Auto-Move", handleAction, true, Enum.KeyCode.C, Enum.KeyCode.ButtonR1)

local RunService = game:GetService("RunService")
RunService.RenderStepped:Connect(onRenderStep)

This is a lot simpler and cleaner. The only difference is that it only stops if you hit the Auto-Move key again.