Issue with using magnitude to find best spawn

So I’m trying to create a better spawn system for my game since spawning directly on top of players is a HUGE issue in the game currently. But even with this new spawn system that finds the magnitude, it still occasionally spawns the player on top of another. Here’s the code:

	local spawns = game.Workspace:WaitForChild("Spawns", 20)

		local sspawn = {}
		local mindistance = 80
		local availiablespawns = 0

		local function findbestspawn()
			local testmodel = Instance.new("Model")
			testmodel.Parent = game.ReplicatedStorage
			for i, v in pairs(game.Workspace:GetChildren()) do
				if v:IsA("Model") then
					if v:FindFirstChild("HumanoidRootPart") then
						if v.Parent.Name ~= player.Name then
							local plr = v.HumanoidRootPart
							for i, g in pairs (spawns:GetChildren()) do
								if g:IsA("Part") then
									local distancefromplayer = (g.Position - plr.position).Magnitude
									if distancefromplayer >= mindistance then
										table.insert(sspawn, g.Name)
										local aspawns = g:Clone()
										aspawns.Parent = testmodel
										availiablespawns = #testmodel:GetChildren() / 3
									end
								end
							end
						end
					end
				end
			end
			local debris = game:GetService("Debris")
			debris:AddItem(testmodel, 5)
		end
		findbestspawn()
		local ssspawn = spawns:FindFirstChild(sspawn[math.random(1, availiablespawns)])
		local x = ssspawn.Position.X
		local y = ssspawn.Position.Y
		local z = ssspawn.Position.Z
character.LowerTorso.CFrame = CFrame.new(Vector3.new(x, y + 2, z))

It basically gets the spawns in the map, and creates a table based on if any character on the map is within 80 studs of said spawn (it works and prints the availiable spawns + their magnitude away from the player). The reason I have availiablespawns = #testmodel:GetChildren() / 3 is because for some reason the spawns are also replicating 3 times, but this part doesn’t matter since all it’s doing is finding the number of available spawns and the spawns are named differently.

probably will fix later since it may have something to do with something higher in the code

After it grabs the available spawns and creates the table of available spawns it randomly chooses one of the best possible spawns, grabs it’s position values, and teleports the character to it.

Simple enough, but it still fails and sometimes places a player right on top of another and prints a 0 magnitude.

Any ideas?

1 Like

Okay so I figured out a problem, for some reason the table insert isn’t working and the table for the available spawns = nil. I tested this with a

print(sspawn[1])

function.

Any idea why the names of the spawns aren’t being inserted into the table?

1 Like

Is this a situation where you aren’t able to use a SpawnLocation? The object has had some internal changes such that players spawning on a large SpawnLocation are automatically spread out and use best fit to avoid spawning on top of each other or on top of a room’s roof.

Your code is also pretty expensive and will end up tagging NPCs too. Instead of iterating over every child of the Workspace, iterate through the table returned by the GetPlayers method of the Players service. Check that each iterated player has a character, that the character is in the workspace and that it has an alive Humanoid.

Finally, while I haven’t provided every tip to how to improve your code, I’d like to note that you should be using the HumanoidRootPart to teleport characters instead of a specific limb like the LowerTorso.

2 Likes

The game is just a 2D sidescroller shooter, so NPCs aren’t an issue, yet I will change the code to find the players’ characters like that, along with changing the way the teleport works with HumanoidRootPart.

The issue is, when you click deploy, that script fires, finds the spawns on the map, and spawns them it.

Think of phantom forces.

After playing with the code for a bit, I noticed that the main problem is the fact that the sspawn table isn’t updating at all what so ever. It’s almost like table.insert doesn’t work.

Though, if I place the table inside of the function instead of outside of it, it does work oddly enough.

Edit
Just realized that since it’s a local function, nothing outside said local function can be updated. So I have to simply find a work around.

1 Like

So I completely redid the code and it’s slightly better at spawning players away from others, yet at the same time it still sometimes spawns players literally on top of each other.

New code:

		local sspawn = {}
		local mindistance = 80
		local availablespawns = 0
		local players = game.Players
		local children = {}
		for i, v in pairs(players:GetChildren()) do
			if v.Name ~= player.Name then
				table.insert(children, v.Name)
			end
		end
		for i, g in pairs (spawns:GetChildren()) do
			if g:IsA("Part") then
				if children[1] == nil then
					availablespawns = #spawns:GetChildren()
				else
					for i = 1, #children do
						local kiddo = game.Players:FindFirstChild(children[i]).Character.HumanoidRootPart
						local distancefromplayer = (g.Position - kiddo.Position).Magnitude
						if distancefromplayer >= mindistance then
							table.insert(sspawn, g.Name)
							availablespawns = math.ceil(#sspawn / #children)
						end
					end
				end
			end
		end
		local ssspawn = ""
		if children[1] == nil then
			local manualspawn = spawns:FindFirstChild(math.random(1, #spawns:GetChildren()))
			ssspawn = manualspawn
		else
			local autospawn = spawns:FindFirstChild(sspawn[math.random(1, availablespawns)])
			ssspawn = autospawn
		end
		for i = 1, #sspawn do
			table.remove(sspawn, i)
		end
		local x = ssspawn.Position.X
		local y = ssspawn.Position.Y
		local z = ssspawn.Position.Z

It now clears the table because I realized an issue was that I was stacking available spawns on each other. I also realized that the reason the number for available spawns multiplying is because it’s firing it for every character found.

The table is just the names of the spawns, so the duplication won’t affect it.
image
Though it finds the distance from spawn to player it still occasionally does this :
image
I can’t wrap my head around why even if the magnitude is 0 it spawns directly on the player when it’s only allowed to spawn if the magnitude is greater than 80

1 Like

I narrowed down the cause of spawning on top of other players to these lines here:

for i = 1,#children do
local kiddo = game.Players:FindFirstChild(children[i]).Character.HumanoidRootPart
					local distancefromplayer = (g.Position - kiddo.Position).Magnitude
						if distancefromplayer >= mindistance then
							table.insert(sspawn, g.Name)
							print(player.Name.." spawns", "Distance: "..distancefromplayer,"Spawn: "..g.Name)
										availiablespawns = math.ceil(#sspawn/#children)
									end
								end

It’s basically taking all of the children’s HumanoidRootPart positions and subtracting it from one spawn position. Meaning if one player is directly on the spawn but another player across the map, it’s gonna take the distance between those two then compare it to the part and send the magnitude. Yet, if both players are on that spawn, the spawn system won’t spawn a player there.

Is there any way to work around it taking the distance of all players added together compared to the spawn and finding the magnitude?

1 Like

Would you please let us know what you got from the print?

1 Like

Well in all honesty the print doesn’t matter much because it does properly print the player who spawns, the magnitudes of the available spawns, and the names of the spawns. The code works. The problem is, it’s taking all the player’s magnitude because of my for loop and comparing it to the part, rather than looking at each individual player’s distance from it.

1 Like

Hey there !

I have written a script that might be able to help you out, with slight tweaks here and there it could be adjusted to serve multiple purposes.

Right now this checks whether a player is close to a spawn and ensures no other players spawn at that point, (the first occurrence in the dictionary)

Here is the script I created, place in Server Script Service : [Partially-Tested]

local players = game:GetService("Players")

local player = players.LocalPlayer

OnePlayer = nil

 spawn_container =  {
			
	[1] = {}
	
	

	}
--[[ First of all since I don't know their positions i'll manually insert them through a loop 
	  I used a folder called Spawns instead of a Model.
																																																																	]]
  local Spawns = workspace:WaitForChild("Spawns")	
	 for positions = 1,#Spawns:GetChildren() do
	    spawn_container[positions] = Spawns[positions].Position
  	end

  for key, value in pairs(spawn_container) do
    	print(key,value)--> successfully prints locations for each spawn
   end
 
 print("Total Positions loaded "..tonumber(#spawn_container))
  

  for _,kiddo in ipairs(players:GetPlayers()) do
      kiddo.CharacterAdded:Connect(OnCharacterAdded)
	 function OnCharacterAdded(char)   
	  --local char = kiddo.Character or kiddo.CharacterAddedWait()
	       local RealLocation
		    local root = char.HumanoidRootPart or char:WaitForChild("HumanoidRootPart")
		        OnePlayer = kiddo
		        for PossibleLocations = 1,#spawn_container do 
              if (spawn_container[PossibleLocations]-root).magnitude > 20--make sure no one is at the spawn or on it 
           then RealLocation = spawn_container[PossibleLocations]  
	    OnePlayer.RespawnLocation = RealLocation
        
            end
		end
	end
end
2 Likes

Alright, now I’m stuck on another issue. I’ve tried editing your code to fit my needs and basically since the function is already inside of a OnServerEvent:Connect() function, I have to make it local. Meaning I can’t grab RealLocation. How would I go about that?

  for _,kiddo in ipairs(players:GetPlayers()) do
  kiddo.CharacterAdded:Connect(function(char)
	local RealLocation
	  --local char = kiddo.Character or kiddo.CharacterAddedWait()
		    local root = char.HumanoidRootPart or char:WaitForChild("HumanoidRootPart")
		        OnePlayer = kiddo
		        for PossibleLocations = 1,#spawn_container do 
              if (spawn_container[PossibleLocations]-root).magnitude > 20--make sure no one is at the spawn or on it 
           then RealLocation = spawn_container[PossibleLocations]  
	    OnePlayer.RespawnLocation = RealLocation
            end
		end
end)
end
local x = RealLocation.Position.X --These values can't be found since RealLocation is inside of the local function 
local y = RealLocation.Position.Y
local z = RealLocation.Position.Z
1 Like

Hey !

Following @PostApproval 's suggestion

Check that each iterated player has a character, that the character is in the workspace and that it has an alive Humanoid.

I edited my code (again) to make it work better, now whenever a character is added, the distances of all currently existent players’ root parts are compared with each other, to ensure that if a player’s root part is at least (Minimum Distance) away from the spawn , then only can that spawn be used for another player.

Side Note

Make sure that the spawns are still parented to the folder, and they are named numerically.
You won’t be needing an external event to handle this as it runs whenever a player or his character is loaded

My script in Server Script Service :

--// Variables
  MinimumDistance = 30--how far a player has to be from a spawn location , so that others can spawn there
  MaxDistance  = nil
  
  loc = nil

  points = {
	[1] = {}
	} 
  
  spawn_container =  {
			
	[1] = {}
 }

  local players = game:GetService("Players")


--// Adding Positions to dictionary
   SpawnFolder = workspace:WaitForChild("Spawns")
	  for positions = 1,#SpawnFolder:GetChildren() do
	  spawn_container[positions] = SpawnFolder[positions].Position--maybe alternately Spawns:FindFirstChild(positions).Position?
  	end

--// Once a player's character is added

    players.PlayerAdded:Connect(function(player)
	
	player.CharacterAdded:Connect(function(character)
		
--// Loop to check players' distances from spawn points      
 	
	for _,existant in ipairs(players:GetPlayers()) do
	   if existant.Character then--if that player is existant basically,
	      local distancestocomparewith  = {}
			  local Characterg = existant.Character
			      if Characterg and Characterg==Characterg then--if the character still exists, and the character is the same one (meaning the player didnt die)
				     local part = Characterg:FindFirstChild("HumanoidRootPart") 
				       table.insert(distancestocomparewith,part.Position)
                      end        
                     for i = 1, #distancestocomparewith do--for all possible distances
         	       points[i] = (distancestocomparewith[i] - spawn_container[i]).magnitude--add magnitudes to the points table
                 end
            return MaxDistance == (math.max(unpack(points))) -- get the largest distance from all distances previously added
              end	
	           print("Max dist  : "..MaxDistance)--can we do something with this??
     
	             local humanoid = character:FindFirstChild("Humanoid")		
                   if humanoid then
	                 humanoid.Died:Connect(function()
	    	          for i = 1,#points do
		            if (points[i]-spawn_container[i]).magnitude > MinimumDistance then--algorithm to prevent duplication, and to compare magnitude for all players
			      player.RespawnLocation = spawn_container[i]
		             end
		        end              
		
	
		           end)
              end
       	   end 
	   end)
  end)

That should work. Please correct me if it didn’t.

1 Like

So, I think I get what you’re doing and I realized that I should’ve been more descriptive with my issue. So, the game isn’t using real spawn locations, it uses parts that are called “spawns” and a deploy button to spawn said player. So setting player.RespawnLocation wouldn’t exactly work.

Well, even if I do use spawn points. How would I “deploy” the character? Would I have to use :LoadCharacter()?

1 Like

This is what I meant by deploy. My original code teleported them to a random part or “spawn” after they clicked deploy.

If it auto respawns the character into the map, then the UI would be pointless to have.

It feels like you are overcomplicating things. Simplify the issue;

Do you need player-to-player collission? No -> Then check this out: https://developer.roblox.com/en-us/articles/Player-Player-Collisions

Do the spawn sizes have to be a specific size? No -> Make them bigger, roblox has already solved this issue before.

Now, if you answered yes on all of those above, what I’d is put a boolean value inside each spawn, and a script that updates itself checking whether a player are still standing in it. And then each time a player requests to spawn, you just for loop through every spawn’s value and if the spawn’s value is false then you relocate them there.

Player to player collisions are off in my game, and yes spawns have to be a specific size since it’s a 2D side-sidescroller.

Even with the idea with the boolean value that detects if the spawn is being touched by a player, it wouldn’t be efficient for a shooter game. They’re not going to be standing on a spawn. The point is to spawn them away from nearby players so they don’t get instantly killed upon spawn. Which is why I was trying to find the HumanoidRootParts of all the player’s characters and compare them to the spawn locations. If they’re far away enough then the game would spawn them there rather than they not be standing on it, yet right beside the spawn, and get spawned there and killed instantly.

Also, that would be a great idea if the StarterGui and PlayerGui wasn’t tied to the character. Sadly players can’t view any UI elements until they’re loaded.

However, since you can still load characters for players before they spawn ,
you can loop through players and compare the magnitude of the root parts’ locations - the spawn point’s location through a for loop, and then returning the spawn location it was compared with, setting the player’s respawn location to that spawn point’s location, and then loading the character.
That should solve the problem associated with deploying the character before his UI has loaded.

edit :

@Ukendio using magnitude you can get the difference between the compared vectors, so you can use math.max to get the spawn that has the largest magnitude (here distance from) the player’s root part. (in my opinion it is more efficient, in terms of memory, to use magnitude rather than loop constantly to check whether a player is in a specific region)

1 Like

Instead of comparing magnitude, why not use region3?

1 Like