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
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)
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.
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.
@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 .
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.
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.
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])
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”.
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