Also, the StarterCharaterScript is a good solution, I’m just trying to see what’s possible with StarterPlayerScripts.
local Players = game:GetService("Players")
local player = Players.LocalPlayer or Players:GetPropertyChangedSignal("LocalPlayer"):wait()
player.CharacterAdded:Wait()
local character = player.Character
local hum = character:WaitForChild("Humanoid")
while wait(1.2) do
hum.Jump = true
print("Start jumping")
end
Found a solution without using the wait() function at the beginning of the script
just to point out, you can also update the character and humanoid variable through player.CharacterAdded
event and it should work again after the character Instance is destroyed.
edit: same goes for localscripts located in StarterPack
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
LocalPlayer
should also always be available as far as I know. I doubt it’s necessary to yield for it.
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.
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 .
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.
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
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”.
in localplayerScript
local player = Game:GetServices(“Players”)
Localplayer = player.LocalPlayer
character = Localplayer .Character or Localplayer.CharacterAdded:Wait()
Finally someone who went more in-depth into the problem of spawn.
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?