Selection box system being so weird

Hello,
I have been trying to make a selection box system for a RTS type game. However, lots of errors have been happening. I have been trying to search a table from a replicated storage folder using :getchildren() and its just been so confusing. What i am trying to do is:

  • get the names of the children of the replicated storage folder
  • compare it with the names of the selected parts parents, parents parents, and so on
  • return the instance that is succesfully compared
  • then put the parents parents whatever in a table

Im so sorry if this is hard to understand - im confused myself. Please feel free to ask any questions!!!
The places where i am having most trouble on is lines 24 - 31.

local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")

local Player = Players.LocalPlayer
local Mouse = Player:GetMouse()
local SelectionBox = script.Parent --  "SelectionBox is not a valid member of ScreenGui"

local ClickedOn = nil
local Camera = workspace.CurrentCamera
local SelectionLocation = workspace.World.Soldiers -- the location of where the selections thingy be

local function GetSelectedParts()
	local SelectedParts = {}
	for i, Part in SelectionLocation:GetDescendants() do
		if not Part:IsA("BasePart") then
			continue
		end
		local PosOnScreen = Camera:WorldToScreenPoint(Part.Position)
		-- convert the position to the screen thingy 
		if 
			PosOnScreen.X < (SelectionBox.AbsolutePosition.X + SelectionBox.AbsoluteSize.X) and PosOnScreen.X > (SelectionBox.AbsolutePosition.X) and
			PosOnScreen.Y < (SelectionBox.AbsolutePosition.Y + SelectionBox.AbsoluteSize.Y) and PosOnScreen.Y > (SelectionBox.AbsolutePosition.Y)
			-- check if the pos on screen is inside the selection frame
		then
			local jim = game.ReplicatedStorage.Units:GetChildren().Name
			local jims = jim[Part.Parent.Name] or jim[Part.Parent.Parent.Name] or jim[Part.Parent.Parent.Parent.Name] or jim[Part.Parent.Parent.Parent.Parent.Name] 
			if jims then
				table.insert(SelectedParts, nil)  -- how do i get the successful one? im so confused on what i am doing lol
				print(nil)
			end
		end
	end
	return SelectedParts
end

UserInputService.InputBegan:Connect(function(Input, GameProcessed)
	if GameProcessed then
		return
	end

	if Input.UserInputType == Enum.UserInputType.MouseButton1 then
		ClickedOn = Vector2.new(Mouse.X, Mouse.Y)

		SelectionBox.Visible = true
		SelectionBox.Position = UDim2.fromOffset(ClickedOn.X, ClickedOn.Y)
	end
end)

UserInputService.InputEnded:Connect(function(Input)
	if Input.UserInputType == Enum.UserInputType.MouseButton1 then
		ClickedOn = nil
		SelectionBox.Visible = false
		GetSelectedParts()
	end
end)

UserInputService.InputChanged:Connect(function()
	if ClickedOn then
		local X = Mouse.X - ClickedOn.X
		local Y = Mouse.Y - ClickedOn.Y

		SelectionBox.Size = UDim2.fromOffset(X, Y)
	end
end)

are you trying to get all of the soldiers that are in the selection area? and why do you need to get all of the individual parts

you can make a recursive function that goes up the hierarchy until it finds a model, and then compare that with the units in replicated storage

edit: you should also optimize by removing all descendants of the character from the parts table, so it doesnt search and get the same character again

local function findCharacter(instance : Instance)
  if (instance:IsA("Model")) then return instance end
  if (instance == workspace) then return end -- or instance == nil if its not guaranteed to be in workspace
  return findCharacter(instance.Parent)
end

--example code inside the loop
local Part
local units = game.ReplicatedStorage.Units:GetChildren()

local character = findCharacter(Part)
for _, unit in ipairs(units) do
  if unit.Name == character.Name then
    --found matching unit
    break
  end
end

Small question since im not very experienced in coding - what is

local Part

supposed to be? It gives an error in my script - “Part is never defined or initalized” - what should i set Part to?

Nevermind - its just Part in the for loop.

Just a question - how would i implement your optimization edit?

you can iterate through the selected parts and remove if its a descendant of the character

--in the loop
for i = 1, #SelectedParts  do
  if not SelectedParts[i]:IsDescendantOf(character) then continue end -- if valid then skip
  table.remove(SelectedParts, i)
  i -= 1 -- shift index because of the .remove()
end

it might also be faster to store the characters found in a dictionary (acting as a hashset) and skip if its a repeat character

--outside loop
local seenCharacters = {}

--in the loop
if (seenCharacters[character]) then continue end
seenCharacters[character] = true
1 Like

I did the second solution, but it isnt producing any errors and isnt working. Heres the part of my script which i put it in:

then
			local units = game.ReplicatedStorage.Units:GetChildren()
			local character = findCharacter(Part)
			local seenCharacters = {}
			for _, unit in ipairs(units) do
				if (seenCharacters[character]) then continue end
				seenCharacters[character] = true
				if unit.Name == character.Name then
					print(character.Name)
					
					table.insert(SelectedParts, character)
					break
				end
			end
		end

put it in the loop that iterates through the parts, because its supposed to skip the current part if its part of a repeated character

also units:GetChildren() at the start of the script will save a lot of processing power

local units = game.ReplicatedStorage.Units:GetChildren() -- put this outside at the start

if (seenCharacters[character]) then continue end
seenCharacters[character] = true

then
			
			local character = findCharacter(Part)
			local seenCharacters = {}
			for _, unit in ipairs(units) do
				
				if unit.Name == character.Name then
					print(character.Name)
					
					table.insert(SelectedParts, character)
					break
				end
			end
		end
1 Like

Its still isnt working - but the performance saving tip helped! Heres my full script so far:

local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local Player = Players.LocalPlayer
local Mouse = Player:GetMouse()
local SelectionBox = script.Parent --  "SelectionBox is not a valid member of ScreenGui"
local units = game.ReplicatedStorage.Units:GetChildren() -- put this outside at the start
local ClickedOn = nil
local Camera = workspace.CurrentCamera
local SelectionLocation = workspace.World.Soldiers -- the location of where the selections thingy be



local function findCharacter(instance : Instance)
	if (instance:IsA("Model")) then return instance end
	if (instance == workspace) then return end -- or instance == nil if its not guaranteed to be in workspace
	return findCharacter(instance.Parent)
end

local function GetSelectedParts()
	
	local SelectedParts = {}
	for i, Part in SelectionLocation:GetDescendants() do
		local character = findCharacter(Part)
		local seenCharacters = {}
		if (seenCharacters[character]) then continue end
		seenCharacters[character] = true
		if not Part:IsA("BasePart") then
			continue
		end
		local PosOnScreen = Camera:WorldToScreenPoint(Part.Position)
		-- convert the position to the screen thingy 

		if 
			PosOnScreen.X < (SelectionBox.AbsolutePosition.X + SelectionBox.AbsoluteSize.X) and PosOnScreen.X > (SelectionBox.AbsolutePosition.X) and
			PosOnScreen.Y < (SelectionBox.AbsolutePosition.Y + SelectionBox.AbsoluteSize.Y) and PosOnScreen.Y > (SelectionBox.AbsolutePosition.Y)
			-- check if the pos on screen is inside the selection frame
		
		

		then
	
			for _, unit in ipairs(units) do

				if unit.Name == character.Name then
					print(character.Name)

					table.insert(SelectedParts, character)
					break
				end
			end
		end
	end
	return SelectedParts
end

UserInputService.InputBegan:Connect(function(Input, GameProcessed)
	if GameProcessed then
		return
	end

	if Input.UserInputType == Enum.UserInputType.MouseButton1 then
		ClickedOn = Vector2.new(Mouse.X, Mouse.Y)

		SelectionBox.Visible = true
		SelectionBox.Position = UDim2.fromOffset(ClickedOn.X, ClickedOn.Y)
	end
end)

UserInputService.InputEnded:Connect(function(Input)
	if Input.UserInputType == Enum.UserInputType.MouseButton1 then
		ClickedOn = nil
		SelectionBox.Visible = false
		local john = GetSelectedParts()
		game.ReplicatedStorage.TableSend:FireServer(john)
		game.ReplicatedStorage.TableSendClient:Fire(john)
	end
end)

UserInputService.InputChanged:Connect(function()
	if ClickedOn then
		local X = Mouse.X - ClickedOn.X
		local Y = Mouse.Y - ClickedOn.Y

		SelectionBox.Size = UDim2.fromOffset(X, Y)
	end
end)