Best way to Wait() for Character?

local Players = game:GetService("Players")
local player = Players.LocalPlayer or Players:GetPropertyChangedSignal("LocalPlayer"):wait()
player.CharacterAdded:Connect(function(character)
	local hum = character:WaitForChild("Humanoid")

	while wait(1.2) do
		hum.Jump = true
		print("Start jumping")
	end
end)

Thanks for pointing out, now I’ll be more aware to not rely on wait() functions alone by any means necessary :yum:

1 Like

LocalPlayer should also always be available as far as I know. I doubt it’s necessary to yield for it.

5 Likes

Don’t forget to call the function in CharacterAdded if the player.Character exists!
Also made a few edits that I think would improve your code.

So like this:

local Players = game:GetService("Players")
local player = Players.LocalPlayer -- no need to wait for LocalPlayer

local function onCharacterAdded(character)
    -- I prefer a little more verbose variables.
    local humanoid = character:WaitForChild("Humanoid")

    -- while wait() do isn't the best practice. Use the condition of while x do to check if character and
    -- humanoid exist

    while character and humanoid do
       wait(1.2)
       -- Possible bug since the character may not exist after 1.2 seconds, even though we did a check.
       -- Not a big deal since it simply errors, but if you'd like to remove some random errors.

       if not character then
          break
       end

       if not humanoid then
          break
       end

       humanoid.Jump = true
	   print("Start jumping")
	end
end

if player.Character then
   -- You should probably use FastSpawn instead of coroutines if you want good errors
   coroutine.wrap(onCharacterAdded)(player.Character)
end

player.CharacterAdded:Connect(onCharacterAdded)
6 Likes

This has some unnecessary code. Neither the loop condition nor loop body need to check character and humanoid. Since these are local variables, they only change when explicitly set by the user.

1 Like

Thanks, @Autterfly, for the information. The link above clarifies what you say; the only time LocalPlayer is necessary to yield would be if the local script were located in the ReplicatedFirst. The LocalScript can sometimes run before the LocalPlayer is available, so it needs to be yielded.

Therefore, wouldn’t that mean the while loop will run infinitely since they will never be set to nil?

So, when the character is added, there will be another while loop.

@saSlol2436 I’ve just tested this out with a print function, and I’m getting more print statements every second when I reset my character. Thanks for the advice; I wouldn’t have noticed . :+1:

No you shouldn’t lol. Assuming you are talking about the FastSpawn that uses bindables. Coroutines are better since you don’t have to create and destroy a bindable + a connection each time you want to create a thread which doesn’t seem very practical lol performance wise. It seems like an overcomplicated solution.

I am aware of the downsides of coroutines that they do silently error your code, but in most cases just use spawn. Spawn does have a slight delay of 0.03 cuz it uses wait() but taking that number seriously is just a case of premature-optimization.

wait() is bad only if you spam it because of flooding instructions into the task scheduler. But I doubt you would create many threads, as threads in general carry an expensive cost.

I honestly prefer to use this in a LocalScript

local character = Player.Character or Player.CharacterAdded:Wait()

It’s just one line.

9 Likes

remember to localize that character variable lol.

This “small” delay can be indefinite since spawn runs on a 30hz pipeline.

Take a look at this article, which talkes about why spawn, delay, and wait is all bad: https://eryn.io/gist/3db84579866c099cdd5bb2ff37947cec.

Either way, I use Promises a lot in my code and Promise.defer works really well for me (this is about eveara’s library). I included coroutines in the example since I didn’t want to drop some external library and make the code confusing.

check this out evarea is not a reliable source :wink:

Don’t get me wrong, evarea is a great coder. But no way I would use that function instead of a simple coroutine / spawn lol. Seems like a LOT of extra work from constructing an object. even the constructor ._new uses a coroutine…

-- not my code
local function makeErrorHandler(traceback)
	assert(traceback ~= nil)

	return function(err)
		-- If the error object is already a table, forward it directly.
		-- Should we extend the error here and add our own trace?

		if type(err) == "table" then
			return err
		end

		return Error.new({
			error = err,
			kind = Error.Kind.ExecutionError,
			trace = debug.traceback(tostring(err), 2),
			context = "Promise created at:\n\n" .. traceback,
		})
	end
end

--[[
	Calls a Promise executor with error handling.
]]
local function runExecutor(traceback, callback, ...)
	return packResult(xpcall(callback, makeErrorHandler(traceback), ...))
end

function Promise._new(traceback, callback, parent)
	if parent ~= nil and not Promise.is(parent) then
		error("Argument #2 to Promise.new must be a promise or nil", 2)
	end

	local self = {
		-- Used to locate where a promise was created
		_source = traceback,

		_status = Promise.Status.Started,

		-- A table containing a list of all results, whether success or failure.
		-- Only valid if _status is set to something besides Started
		_values = nil,

		-- Lua doesn't like sparse arrays very much, so we explicitly store the
		-- length of _values to handle middle nils.
		_valuesLength = -1,

		-- Tracks if this Promise has no error observers..
		_unhandledRejection = true,

		-- Queues representing functions we should invoke when we update!
		_queuedResolve = {},
		_queuedReject = {},
		_queuedFinally = {},

		-- The function to run when/if this promise is cancelled.
		_cancellationHook = nil,

		-- The "parent" of this promise in a promise chain. Required for
		-- cancellation propagation upstream.
		_parent = parent,

		-- Consumers are Promises that have chained onto this one.
		-- We track them for cancellation propagation downstream.
		_consumers = setmetatable({}, MODE_KEY_METATABLE),
	}

	if parent and parent._status == Promise.Status.Started then
		parent._consumers[self] = true
	end

	setmetatable(self, Promise)

	local function resolve(...)
		self:_resolve(...)
	end

	local function reject(...)
		self:_reject(...)
	end

	local function onCancel(cancellationHook)
		if cancellationHook then
			if self._status == Promise.Status.Cancelled then
				cancellationHook()
			else
				self._cancellationHook = cancellationHook
			end
		end

		return self._status == Promise.Status.Cancelled
	end

	coroutine.wrap(function()
		local ok, _, result = runExecutor(
			self._source,
			callback,
			resolve,
			reject,
			onCancel
		)

		if not ok then
			reject(result[1])
		end
	end)()

	return self
end

function Promise.defer(callback)
	local traceback = debug.traceback(nil, 2)
	local promise
	promise = Promise._new(traceback, function(resolve, reject, onCancel)
		local connection
		connection = Promise._timeEvent:Connect(function()
			connection:Disconnect()
			local ok, _, result = runExecutor(traceback, callback, resolve, reject, onCancel)

			if not ok then
				reject(result[1])
			end
		end)
	end)

	return promise
end

What are the cons?

  • Object creation time
  • Fairly expensive (xpcall, closures, double the threads [coroutine.wrap + Promise._timeEvent])
  • Too many nested function calls
  • Overcomplication

Coroutines > Spawn > BindableSpawn > HeartbeatSpawn >>> Promise.defer

I see a lot of top-devs try to make these “solutions” to problems. “custom wait”, “custom delay”, “custom spawn”. But if we actually study how the code works behind the scenes, it really is a lot worse and does more harm than the “problem”.

7 Likes

in localplayerScript

local player = Game:GetServices(“Players”)
Localplayer = player.LocalPlayer

character = Localplayer .Character or Localplayer.CharacterAdded:Wait()

4 Likes

Finally someone who went more in-depth into the problem of spawn.

2 Likes

can do it all server side with this

game.Players.PlayerAdded:Connect(function(Player)
	repeat wait(0.1) until game.Workspace:FindFirstChild(Player.Name)
end)

game.Players.PlayerAdded:Connect(function(Player)
Player.CharacterAdded:Connect(function(Char)
end)
end)

did you really care about getting a solution that much where you bumped a 9 month old topic?

Had to add the idea that StarterCharacterScripts is not reliable because most of the client changes to the character replicate to the server, meaning that they can destroy easily the script if they exploit, replicating to the server, much recommended to put in serverscriptservice

yea, didn’t mean to say the context of the script, but for some reason, it didn’t work for me with attributes, attributes apply to this as well? last time I checked they didn’t replicate client → server

Hi, uhh, sorry for being late but, this is the best method i’ve could find to wait for character:

local LocalPlayer = game:GetService("Players").LocalPlayer
local Character = LocalPlayer.Character or function() repeat wait() until LocalPlayer.Character; return LocalPlayer.Character end
1 Like