How could I achieve Capture Objective / King of The Hill?

I’m looking for how could I do ‘capture the objective’ or ‘king of the hill’ type script for my game

what im mainly looking for is there will be 2 teams, each team needs to fight for the main objective
when there are 2 players both different teams in the objective area, the objective progress will not switch, it will stay on unclaimed
there needs to be atleast 1 player in the objective area for the points to still be gained, the more people in the objective the faster (not much faster) the objective is earning points
when another team has entered the area, the points from the other team decrease (unless it’s 0 points, then it stops there)

i’m not too sure how i could achieve this, ive been thinking something about if player in box(area) then …

1 Like

I would make a loop that runs every 0.1s that checks if players are within capture distance (using magnitude for simplicity) and then add capture progress as needed.

The hill/objective should go to neutral when it’s being de-captured by enemies so that when it reaches zero, so you only need to keep track of which team is currently capturing/owns the flag.

1 Like

One way that I’ve done banners in domination games is to use the proximity prompt for a 10 sec capture time. The banner color changes to the color of the team that captured it.

As for King of the Hill type games, use a spatial queries to query above the sensor plate. If only one team is on it, then it sets the color to that team. If members from more than one team are detected, then it’s a neutral color.

In both cases, you will need another script that runs in the background that checks the colors periodically and increments the associated team score.

1 Like

Yeah I’m a little lost on how could I start coding this,
I didn’t want to really do the proxomity prompt as it doesnt fit the style of a WW2 game, or atleast not that I have a good idea for it.

Also could you explain to me what does spatial queries mean?

1 Like

i rather make this using :Touched() and :TouchEnded() and a few if/else statements

1 Like

I suggest starting with player detection first, for this I suggest the zone + module, pretty simple and easy to use.

Track teams in a capture point zone in a table


		local playersInsideZone = {}
		local BlueTeamContestants = {}
		local RedTeamContestants = {}
		local detectionLoop = nil --Heartbeat connection that is created to reward points if players are in zone every frame

		zone.playerEntered:Connect(function(player : Player)
			print(("%s entered the zone!"):format(player.Name))
			playersInsideZone[player] = true
			local team = player.Team
			if not team then
				warn("Player team missing", player, team)
				return
			end
			if team.Name == "Blue" then
				table.insert(BlueTeamContestants, player)
			elseif team.Name == "Red" then
				table.insert(RedTeamContestants, player)
			end

Once you get the total number of players and teams on a point then you can start writing the rest of the logic.

1 Like

Just use roblox’s :GetPartsBoundsInBox, people say its heavy on memory/performance but c’mon it’s not that bad. Don’t use .Touched as it’s pretty inconsistent for things like this, using distance is also kinda weird i’d say like a good example of it being bad is in piggy. Like it’s not baddddd, but I think the creator of piggy used distance to calculate whether a player dies or not. That’s definitely better than .Touched don’t get me wrong, but it looks super weird. Like alot of players will say “How did he get me he was so far away!” the same can be said in mm2, that’s because they’re both using magnitude. Raycasting basically has the same problem, so I’d say using a hitbox like :GetPartsInbox() is the best solution. Here’s an example:

local zone = workspace.Zone -- This is just a part from the workspace you can use to visualise the hitbox
local Runservice = game:GetService("RunService")
local Params = OverlapParams.new() -- This is to include/exclude detected parts in the region
Params.FilterType = Enum.RaycastFilterType.Exclude -- No idea why they didn't make a different name for it but this is how it works
Params.FilterDescendantsInstances = {workspace.Rig} -- Just to ignore the rig otherwise it'll kill itself
while true do
	local hitbox = workspace:GetPartBoundsInBox(workspace.Rig.PrimaryPart.CFrame, zone.Size, Params) -- look up the syntax on documentation
	for i, part in hitbox do -- :GetPartBoundsInBox returns an array of baseparts detected so we can loop through them
		if part.Parent:FindFirstChildOfClass("Humanoid") then
			print("Player found!")
			local character = part.Parent
			character:FindFirstChildOfClass("Humanoid").Health = 0
		end
	end
	Runservice.Heartbeat:Wait() -- Just so that we don't crash.
end

To make this even better you can do it on the client-side as the client will react faster compared to the server. But server-side also works well, I tested the detection and it’s very good. You can also use Zoneplus like @dthecoolest said but I don’t think its necessary. If you’re confused on anything here’s the link to the documentation: WorldRoot | Documentation - Roblox Creator Hub

1 Like

.Touched isn’t necessarily bad as I said earlier, but it’s pretty inconsistent especially on actual servers. It’s alright for what it says, like just touching a part. But when you’re in a region it’s best to use region-based functions. Especially in OP’s case where there will be two teams and they will be battling, ensuring that the region works well is crucial. There will definitely be alot of players who lag and the server will be lagging quite alot too.

1 Like

what does this line do?

what would zoneplus change or add? more functionality . . ?

Basically, in a region only baseparts are detected. Now your character is a model and it has children which are baseparts (meshparts). E,g, Right leg, right arm, torso, etc. These baseparts are detected by the region function (:GetPartsInBoujndsBox). So the statement is basically saying in english: If a part is detected and it’s parent has a humanoid (Humanoids are a special object that only characters like your player has, I am checking if the character has a humanoid because other parts such as the baseplate are also detected but we only want to see if it’s a player that has entered the region, however baseparts don’t have a humanoid so it would only be your character that is detected). And if a humanoid is found (the player’s humanoid basically). It’s health is 0. Humanoids determine how much health the player has.

1 Like

Zoneplus is just a module people use for optimization purposes. Because the default roblox region detector is not as optimized (since usually you’d have to put it in a while loop). But it’s not mandatory. And I don’t think the default roblox region detector is that impactful on memory/performance.

1 Like

Incase the roblox region detector will be impactful, would it be easy to switch or everything will be needed to be recoded?

I should do this on script (server) not localscript right?

1 Like

Yes use a server script and parent it in serverscriptservice, If the roblox region detector (:GetPartsBoundsInBox) becomes impactful, although I doubt it will. Yes, it’s quite an easy fix. Considering how easy it is to use.

2 Likes

This script you provided me with should print out player found while a player is in the part named Zone, correct?
if so; on my end it doesn’t print out anything for some reason… no error aswell

1 Like

Did you add a part in the workspace named “Zone”. Did you also test this by going up to other players/rigs and seeing if the player is found? This won’t detect you because you are ignored in the overlap params table. It will only detect other characters.

1 Like

yes, ive changed the code to

local zone = workspace.map1.Zone

I don’t quite understand, the code you provided me will only work when there are two players…? really dont understand (also i have a startercharacter inserted, will this change anything? - it has all the parts a standard startercharacter must have such as humanoid etc)

1 Like
local zone = workspace.Zone -- This is just a part from the workspace you can use to visualise the hitbox
local Runservice = game:GetService("RunService")
while true do
local hitbox = workspace:GetPartBoundsInBox(workspace.Rig.PrimaryPart.CFrame, zone.Size) -- look up the syntax on documentation
	for i, part in hitbox do -- :GetPartBoundsInBox returns an array of baseparts detected so we can loop through them
		if part.Parent:FindFirstChildOfClass("Humanoid") then
			print("Player found!")
		end
	end
	Runservice.Heartbeat:Wait() -- Just so that we don't crash.
end

Just try this and tell me if it works, it has nothing ignored now so it will detect you aswell, I was making a hitbox for like combat games. But I understand you are trying to make a capture objective game, this will also detect other players in the region. Also, you need to resize your Zone part to be sort of like a region, because that is what the :GetPartBoundsInBox is using to detect who is in the region. So don’t just make it a default part. Resize the part to look like this:
Screenshot 2024-05-03 203037
Edit: DONT USE THE CODE I JUST GAVE USE THE CODE I GAVE IN MY NEXT REPLY!

All characters have humanoids, every player that joins your game will automatically have a humanoid by default. There is no limit to how many players my code will detect, it can detect more than two players. What I meant there was that did you try testing this by going up to other players and going around them? Because the hitbox is positioned on you, the player. But you can change that aswell if you want to be positioned to the zone part, you will just have to walk over to it.

local zone = workspace.Zone -- This is just a part from the workspace you can use to visualise the hitbox
local Runservice = game:GetService("RunService")
while true do
local hitbox = workspace:GetPartBoundsInBox(workspace.Zone.CFrame, zone.Size) -- look up the syntax on documentation
	for i, part in hitbox do -- :GetPartBoundsInBox returns an array of baseparts detected so we can loop through them
		if part.Parent:FindFirstChildOfClass("Humanoid") then
			print("Player found!")
		end
	end
	Runservice.Heartbeat:Wait() -- Just so that we don't crash.
end

Go up to the zone in workspace and go into it, turn off cancollide on the part and anchor the zone. It will detect any player. Edit: Don’t use this code that I gave now I know what you wanted to do:

-- DONT USE THIS CODE!
local zone = workspace.Zone -- This is just a part from the workspace you can use to visualise the hitbox
local Runservice = game:GetService("RunService")
while true do
local hitbox = workspace:GetPartBoundsInBox(workspace.Rig.PrimaryPart.CFrame, zone.Size) -- look up the syntax on documentation
	for i, part in hitbox do -- :GetPartBoundsInBox returns an array of baseparts detected so we can loop through them
		if part.Parent:FindFirstChildOfClass("Humanoid") then
			print("Player found!")
		end
	end
	Runservice.Heartbeat:Wait() -- Just so that we don't crash.
end
local zone = workspace.map1.Zone -- This is just a part from the workspace you can use to visualise the hitbox
local Runservice = game:GetService("RunService")
while true do
	local hitbox = workspace:GetPartBoundsInBox(workspace.Zone.CFrame, zone.Size) -- look up the syntax on documentation
	for i, part in hitbox do -- :GetPartBoundsInBox returns an array of baseparts detected so we can loop through them
		if part.Parent:FindFirstChildOfClass("Humanoid") then
			print("Player found!")
		end
	end
	Runservice.Heartbeat:Wait() -- Just so that we don't crash.
end

^^^current script

well, i tried going into the part by one player, two players, both different teams and same, still no output, the part is positioned as u told me to (a block with no cancollide and anchor)

just to clear things up:

I need a Capture objective code idea, that will use 2 teams (and team neutral or team 0)
when 1 player from team blue or red (lets say blue) enters the zone, points are given every second to team blue
when 1 player from team blue is in the zone but then another player from team red is in the zone, the points don’t go to anyone and the color of the gui above the objective turns to gray (basically no one is getting points)
HOWEVER when there is lets say 2 people on the objective from team blue, and 1 from red then team blue will get points, and team red wont

Is your script enabled? is it in serverscriptservice? and is it a server-script?