I’m making a round based game, and I’ve run into a bit of a problem…
Here is a basic round system (not my actual code):
while true do
if not enoughPlayers() then repeat Players.PlayerAdded:Wait() until enoughPlayers() end
intermission()
wait(10)
-- How would I reset the loop if players leave
-- and there is not enoughPlayers() while it's yielding?
-- I know I could just check if there is enoughPlayers()
-- after the yielding, but I would prefer just breaking
-- out of intermission.
loadMap()
teleportPlayers()
wait(10)
-- what is the best way to stop a round when a winner
-- is found?
end
The questions I have are in the comments of the code.
Originally, I made a state machine and on the entering and exiting of the states I would do stuff like load the map, teleport players, etc. and when a winner was found, it would transition to the next state, and the callback for states leaving would handle the rest.
However, I don’t like this approach because I needed a new state for every code I did between yields, and then they aren’t really states or whatever…
Then I used Promises (current implementation) but I don’t know if I like that either…
Edit: I would also like to avoid conditional polling.
I think this is mostly a game design question, as it’s not the scripting I need help with, but I will move it there if people think it is a scripting help post.
Something like this would go into the #help-and-feedback:scripting-support :: However it’s fine! Anyways, what I basically did was used a repeat loop until a winner was chosen aka not equal to nil. An example is below;
-- Services
local Players = game:GetService("Players");
local ReplicatedStorage = game:GetService("ReplicatedStorage");
-- Variables
local NumPlayersNeeded = 2;
-- Objects
local Winner = Instance.new("ObjectValue");
Winner.Name = "Winner";
Winner.Parent = ReplicatedStorage;
-- Functions -------------------------------------------------
function checkPlayers()
return (NumPlayersNeeded >= #Players:GetPlayers());
end
function startGame()
-- Starting the minigame
repeat
wait();
-- Game code here
-- Countdown code / etc....
until Winner.Value ~= nil
print("Winner is: " .. tostring(Winner.Value));
end
--------------------------------------------------------------
-- Game Loop
while wait() do -- >> You can just use while true do <<
if (checkPlayers()) then
-- Run game code here
else
-- Display a message saying there's not enough players
end
end
However, and I’m sorry for not clarifying this in the original post, I would also like to avoid conditional polling like that. This DevForum post explains why.
The post does say how to get around conditional polling, but callbacks and events are also not ideal, as it would lead to callback hell, if I wanted to, say, add different “stages” to rounds (which is a very real possibility) and would look something like this:
while true do
enoughPlayers(function()
intermission(function()
if isEnoughPlayers() then
doRoundStage(1, function()
if not winner() then
doRoundStage(2)
end
end)
end
end)
end)
end
and that is a big no for me. This leads to barely readable and really hard to maintain code.
Also I guess I will move this post to scripting help. Thanks again!
Thanks! I totally forgot about polling, so now I need to go redo some code of mine.
Ah okay… I read through the post and using :Wait(); is perfectly fine. Here would be my way of doing this which should work like it is or with a little tweaking…
-- In that forum post :Wait() is perfectly fine, but wait() or :wait() isn't...
-- Remember how I made an ObjectValue called, "Winner"? >> I will be using that in this code <<
local WINNER_Event, WINNER_EVENT_CALLED = Instance.new("BindableEvent"), false; -- WINNER_EVENT_CALLED is a boolean
function GetWinner()
if (Winner.Value) then
WINNER_EVENT_CALLED = true;
WINNER_Event:Fire()
end
end
-- What I'm about to do is Get the property changed signal and fire this event...
Winner:GetPropertyChangedSignal("Value"):Connect(function()
if (not Winner.Value) then return end
GetWinner();
end)
-- Round Code
function startGame()
WINNER_EVENT_CALLED = false; -- Reset this to false
-- Game Mode functions
if (not WINNER_EVENT_CALLED) then
WINNER_Event:Wait();
-- Winner display etc....
print("Winner is: " .. tostring(Winner.Value));
end
end
while true do
game:GetService("RunService").Stepped:Wait() -- Again, this is perfectly fine as it's a yielding function.. Also this will prevent crashing
-- Your intermission, etc. code here;
-- Run the function
startGame();
end
Again, thanks for the reply, but I would still rather like to avoid callbacks and friends.
I wrote some theoretical code for using events and stuff and it still makes me want to commit toaster-bath:
-- theoretical Timer object defined here... :P
local timer = Timer.new()
local canContinue, connection
while true do
canContinue = true
if not enoughPlayers() then repeat Players.PlayerAdded:Wait() until enoughPlayers() end
timer:Start(30)
connection = Players.PlayerRemoving:Connect(function()
if not enoughPlayers then
connection:Disconnect()
timer:Cancel()
canContinue = false
end
end)
timer.Finished:Wait()
if canContinue then
timer:Start(120)
connection = ThingToHelpDetermineWinner.TeamWon:Connect(function()
connection:Disconnect()
timer:Cancel()
canContinue = false
end)
timer.Finished:Wait()
if canContinue then
timer:Start(360)
timer.Finished:Connect(function()
if canContinue then -- also means no winners were found
-- say it was a tie
end
end)
end
end
end
I really don’t like having to use that canContinue variable to determine if it was the timer that ran out or the game determined a winner or the game determined it cannot go on. But again, I really appreciate your time. <3
I also might be really dumb and am completely overthinking this.
Still basically the same thing; actually it could be worse because then I would need a new variable to see if the game can continue after intermission if players left and there were not enough to start a round.
I think the answer lies in something much more complicated than checking to see if there is a winner and if the game may continue on.
I am currently working on a round based game and the main round based script is now almost a 1000 lines of code so how did I make it readable because you don’t want to add all the code inside the loop to read.
So this post is pretty old but I would like to update it on how I solved it.
I ended up using promises, here is a post on why they are useful.
I’m not going to release the script because I feel like it’s pretty trivial to implement it yourself if you know what you are doing, and if you don’t, then you’re better off doing something you understand.
Since I’m not an experienced programmer, I can’t explain how I made promises work without making it sound really confusing, but thanks to everyone who replied!
Edit: I was asked to release a code snippet, so here it is. Please do note that this is pretty much untested pseudocode, because I am lazy
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local Teams = game:GetService("Teams")
local lib = ReplicatedStorage.lib
local Promise = require(lib.Promise)
local Team1 = Teams:WaitForChild("Team1")
local Team2 = Teams:WaitForChild("Team2")
-- there is a third team that players go to when they die :P
local function enoughPlayers()
return #Players:GetPlayers() >= 2
end
-- promise that resolves when the passed in team has no players
local function teamWipedOutPromise(team)
return Promise.fromEvent(team.PlayerRemoving, function()
return #team:GetPlayers() <= 0
end)
end
-- promise that resolves when enough players have joined the game
local function notEnoughPlayersPromise()
return Promise.fromEvent(Players.PlayerRemoving, function()
return #Players:GetPlayers() - 1 < 2
end)
end
-- promise that resolves when there are no longer enough players
local function enoughPlayersPromise()
return Promise.fromEvent(Players.PlayerAdded, function()
return enoughPlayers()
end)
end
function startGame()
return Promise.race({
teamWipedOutPromise(Team1) -- team2 win condition 1
:andThenReturn(Team2), -- return team2, because they won :D
teamWipedOutPromise(Team2) -- team1 win condition 1
:andThenReturn(Team1), -- return team1, same reason :P
Promise.resolve() -- team1 win condition 2
:andThen(function()
-- teleport + freeze all team1
-- do a lil' countdown
end)
:andThenCall(Promise.delay, 5)
:andThen(function()
-- unfreeze team1
end)
:andThenCall(Promise.delay, 30)
:andThen(function()
-- teleport team2
end)
:andThenCall(Promise.delay, 300)
:andThenReturn(Team1)
})
:andThen(function(winners)
-- handle winners
end)
:andThenCall(Promise.delay, 5)
:andThen(function()
-- teleport players back to lobby
end)
:andThen(function() -- can't use andThenReturn here because of conditional
return enoughPlayers() and intermit or waitForPlayers
-- return the next block of code we should do!
end)
end
function intermit()
return Promise.race({
notEnoughPlayersPromise()
:andThenReturn(waitForPlayers),
-- not enough players, so we go back to waiting for players :'(
Promise.delay(30)
:andThen(function()
-- load the map
-- assign teams
end)
:andThenReturn(startGame)
-- we successfully finished intermission with enough players,
-- so we should start the game >:D
})
end
function waitForPlayers()
return enoughPlayersPromise()
:andThenReturn(intermit)
-- enough players, so we can start the intermission! :D
end
local nextBlock = waitForPlayers
while true do
nextBlock = nextBlock():expect()
end