Capturable Base script

Hi, I am trying to make a capturable base, that tweens a flag up a pole when its being captured. The conditions though are if there are two or more people in the zone than the base stops being captured, and if there is one person touching it the flag goes up, and if they leave the base the flag stops moving. The idea was to use .Touched for when the player gets on, and then Heartbeat to check who is in the zone. But this does not make much sense because then .touched will keep firing, and heartbeat will keep going off, and then the tweens are constantly played. All in all I have the idea, but I’m going about it fully wrong. All help is appreciated


local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local flag = script.Parent.flag
local flagtop = script.Parent.top.Value
local flagbottom = script.Parent.bottom.Value
local fullWayTime = script.Parent.timeTotal
local totalDistance = flagtop - flagbottom

local function isPointInZone(point, zonePart)
	local relPoint = zonePart.CFrame:PointToObjectSpace(point)
	return math.abs(relPoint.X) < zonePart.Size.X * 0.5 and math.abs(relPoint.Z) < zonePart.Size.Z * 0.5
end

local tweenUp = TweenService:Create(flag, InfoUp, goalTop)
local tweenDown = TweenService:Create(flag, InfoDown, goalBottom)

local playersInZone = {}
local function onHeartbeat()
	for i, player in pairs(game.Players:GetPlayers()) do
		if player.Character and player.Character.PrimaryPart and isPointInZone(player.Character.PrimaryPart.Position, script.Parent) then
			table.insert(playersInZone, player.UserId)
		else
			playersInZone[player.UserId] = nil
		end
	end
	if #playersInZone > 1 then
		tweenUp:Pause()
		tweenDown:Pause()
	elseif #playersInZone > 0 then
		local pla = playersInZone[1]
		if flag.Color == pla.Team.TeamColor then --if your colour then
-- flag up, otherwise down first, change colour and then up
			tweenUp:play() 
		else
			tweenDown:play() 
		end
	else 
		tweenUp:Pause()
		tweenDown:Pause()
	end
end

local function onTouched(player)
	RunService.Heartbeat:Connect(onHeartbeat)
end

script.Parent.Touched:Connect(onTouched)



6 Likes

With the new version I propose, it should better handle entry and exit. I’ve also fixed some of the tweens and made the table for playersInZone work properly. Some assumptions are made in the script; change the variables as needed.

local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local flag = script.Parent.flag
local flagtop = script.Parent.top.Value
local flagbottom = script.Parent.bottom.Value
local fullWayTime = script.Parent.timeTotal.Value
local totalDistance = flagtop - flagbottom

local playersInZone = {}
local tweenUp
local tweenDown

local function isPointInZone(point, zonePart)
	local relPoint = zonePart.CFrame:PointToObjectSpace(point)
	return math.abs(relPoint.X) < zonePart.Size.X * 0.5 and math.abs(relPoint.Z) < zonePart.Size.Z * 0.5
end

local function updateTweens()
	local flagPosition = flag.Position.Y
	local timeUp = fullWayTime * (flagtop - flagPosition) / totalDistance
	local timeDown = fullWayTime * (flagPosition - flagbottom) / totalDistance
	local InfoUp = TweenInfo.new(timeUp)
	local InfoDown = TweenInfo.new(timeDown)
	local goalTop = {Position = Vector3.new(flag.Position.X, flagtop, flag.Position.Z)}
	local goalBottom = {Position = Vector3.new(flag.Position.X, flagbottom, flag.Position.Z)}
	tweenUp = TweenService:Create(flag, InfoUp, goalTop)
	tweenDown = TweenService:Create(flag, InfoDown, goalBottom)
end

local function onHeartbeat()
	local count = 0
	for userId, _ in pairs(playersInZone) do
		count = count + 1
	end

	updateTweens()

	if count > 1 then
		tweenUp:Pause()
		tweenDown:Pause()
	elseif count == 1 then
		local userId = next(playersInZone)
		local player = game.Players:GetPlayerByUserId(userId)

		if flag.Color == player.Team.TeamColor then
			tweenUp:Play()
		else
			tweenDown:Play()
		end
	else
		tweenUp:Pause()
		tweenDown:Pause()
	end
end

local function onTouched(otherPart)
	local character = otherPart.Parent
	local player = game.Players:GetPlayerFromCharacter(character)
	if player and isPointInZone(otherPart.Position, script.Parent) then
		playersInZone[player.UserId] = true
	end
end

local function onTouchEnded(otherPart)
	local character = otherPart.Parent
	local player = game.Players:GetPlayerFromCharacter(character)
	if player then
		playersInZone[player.UserId] = nil
	end
end

script.Parent.Touched:Connect(onTouched)
script.Parent.TouchEnded:Connect(onTouchEnded)
RunService.Heartbeat:Connect(onHeartbeat)

Let me know if you need any clarification about the above code!

4 Likes

Thank you! That’s very helpful. I just wonder, is there a point in using tween if the tween is recreated every frame from heartbeat? Like would that hamper performance?

That’s a good point. I am not sure how better performant using a simple CFrame update per frame would be compared to tweens, but if I were to take a guess, I would probably say a simple CFrame change would be better.

Also, I noticed that this code would pause the movement of the flag if two people of the same team were present, essentially punishing having more of your own team in the area. I changed the code where having # people or more relative to the other team moves the flag at (#ofmoreplayers)^0.75 scale. ^0.75 is a root function that suppresses larger numbers at a lesser scale than the square root.

In the code below, I use displacement from the original spot (displacement = 0 at start) to determine where the flag is. Change the top displacement of the flag (currently at displacement = 4) to the amount you want the flag to move up from the bottom (in studs). Also, consider changing the direction values for up and down if you want going up to be faster/slower than going down, and also to determine how many seconds you want the capture to happen in. The scale moves the flag # studs per second. With the current code, a capture from starting point without interference from the other defending team would take 8 seconds (4/0.5).

I also use deltaTime as a parameter taken by the onHeartbeat function, to update the flag movement scale at the speed of the framerate. Thus, the flag will move at bigger increments if the framerate is lower than 60 FPS, which is intended to keep a constant speed since we are firing the onHeartbeat function every frame.

Here is the code below, let me know if you need any edits or changes to the way the speed of the flag is implemented (acceleration or deceleration at certain points?):

local RunService = game:GetService("RunService")
local flag = script.Parent.flag
local displacement = 0
local playersInZone = {}
local teamA = --certain directory to teams

local function isPointInZone(point, zonePart)
	local relPoint = zonePart.CFrame:PointToObjectSpace(point)
	return math.abs(relPoint.X) < zonePart.Size.X * 0.5 and math.abs(relPoint.Z) < zonePart.Size.Z * 0.5
end

local function onHeartbeat(deltaTime)
local countA = 0
local countB = 0

	for userId, teamColor in pairs(playersInZone) do
		if teamColor == teamA.TeamColor then
			countA = countA + 1
		else
			countB = countB + 1
		end
	end

	local scale = 0
	local direction = 0
	if countA > countB then
		scale = (countA - countB) ^ 0.75
		direction = 1
	elseif countB > countA then
		scale = (countB - countA) ^ 0.75
		direction = -1
	end
   if scale > 0 then
		local movementY = direction * scale * deltaTime

		displacement = displacement + movementY
		
		if displacement < 0 then
			movementY = movementY - displacement
			displacement = 0
		elseif displacement > 4 then
			movementY = movementY - (displacement - 4)
			displacement = 4
			print("Flag captured!")
		end

		flag.CFrame = flag.CFrame * CFrame.new(0, movementY, 0)
	end
end

local function onTouched(otherPart)
	local character = otherPart.Parent
	local player = game.Players:GetPlayerFromCharacter(character)
	if player and isPointInZone(otherPart.Position, script.Parent) then
		playersInZone[player.UserId] = player.Team.TeamColor
	end
end

local function onTouchEnded(otherPart)
	local character = otherPart.Parent
	local player = game.Players:GetPlayerFromCharacter(character)
	if player then
		playersInZone[player.UserId] = nil
	end
end

script.Parent.Touched:Connect(onTouched)
script.Parent.TouchEnded:Connect(onTouchEnded)
RunService.Heartbeat:Connect(onHeartbeat)

Let me know if you need anything else!

1 Like

Thanks! I like the delta time idea for heartbeat. I was just wondering how DeltaTime is expressed, like is it like a range of 0.9 or 1.1? I’m also curious, if there’s two players, one with a low frame rate, one with a high frame rate, then is the correct speed just replicated to each client from the server script, so it will go up at the same speed or are both FPS taken as input and then averaged? Or something else entirely?

1 Like
  1. deltaTime is a fraction (aka decimal). It is the amount of seconds it takes to render a frame. In an ideal condition, the game runs at 60 FPS, which means it takes 1/60 or ~0.0167 seconds per frame. This number is what deltaTime is. For eample, a game slowed to 45 FPS will have a value of 1/45 or ~0.0222 for deltaTime.

  2. deltaTime is the server FPS, since Roblo’s model attempts to synchronize the FPS across all clients. Thus, to the best of ability, the client system will try its best to emulate the server FPS as the client FPS.

1 Like