Highlighting parts dont work properly

local function createOrUpdateSelectionBox(targetPart, color, thickness)
	if not targetPart then return end
	local selectionBox = targetPart:FindFirstChild("OcclusionHighlight")
	if not selectionBox then
		selectionBox = Instance.new("SelectionBox")
		selectionBox.Name = "OcclusionHighlight"
		selectionBox.Adornee = targetPart
		selectionBox.Transparency = 1 -- Start invisible
		selectionBox.Parent = targetPart
	end
	selectionBox.Color3 = color
	selectionBox.LineThickness = thickness
	TweenService:Create(selectionBox, TWEEN_INFO, {Transparency = 0}):Play()
end

--other unrelated code

local function onCharacterAdded(newCharacter)
	cleanupAll() -- Clean up any state from a previous character

	character = newCharacter
	rootPart = newCharacter:WaitForChild("HumanoidRootPart")
	raycastParamsCeiling.FilterDescendantsInstances = {newCharacter}
	local humanoid = newCharacter:WaitForChild("Humanoid")

	-- When the new character dies
	humanoid.Died:Once(function()
		cleanupAll()
		pcall(function()
			ETouchConnect:Disconnect()
			STouchConnect:Disconnect()
		end)
	end)
	
	local HRP = character.HumanoidRootPart
	
	STouchConnect = HRP.Touched:connect(function(hit)

		if hit:HasTag("JumpPart") and not hit:FindFirstChild("OcclusionHighlight") then
			
			
			createOrUpdateSelectionBox(hit, Color3.new(1, 1, 1), SECONDARY_HIGHLIGHT_THICKNESS)
		end

	end)
	
	ETouchConnect = HRP.TouchEnded:Connect(function(hit)
		if hit and hit.Parent and hit:HasTag("JumpPart") then
			local selectionBox = hit:FindFirstChild("OcclusionHighlight")
			if selectionBox then
				hit:ClearAllChildren()
			end
		end
		
	end)
end
Full camera script

--================================================================
-- Services
--================================================================
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local Lighting = game:GetService("Lighting")

--================================================================
-- Configuration
--================================================================
local CEILING_RAY_DISTANCE = 60
local CEILING_HOLE_TRANSPARENCY = 0.7
local TWEEN_INFO = TweenInfo.new(0.25, Enum.EasingStyle.Sine)

-- Highlight styles
local PRIMARY_HIGHLIGHT_COLOR = Color3.new(1, 1, 1)
local PRIMARY_HIGHLIGHT_THICKNESS = 0.05
local SECONDARY_HIGHLIGHT_COLOR = Color3.new(0.7, 0.7, 0.7)
local SECONDARY_HIGHLIGHT_THICKNESS = 0.02

--================================================================
-- Variables
--================================================================
local player = Players.LocalPlayer
local camera = workspace.CurrentCamera

local occludingUnions = {} 
local ceilingFadedBlock = nil
local isUnderwater = false -- Tracks the visual state
local activeTweens = {}

local blurEffect = Lighting:FindFirstChildOfClass("BlurEffect") or Instance.new("BlurEffect", Lighting)
local colorCorrection = Lighting:FindFirstChildOfClass("ColorCorrectionEffect") or Instance.new("ColorCorrectionEffect", Lighting)

local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Include
overlapParams.CollisionGroup = "MazePhysics"
overlapParams.FilterDescendantsInstances = {workspace:WaitForChild("Blocks"), workspace.Lobby}

local raycastParamsCeiling = RaycastParams.new()
raycastParamsCeiling.FilterType = Enum.RaycastFilterType.Exclude
raycastParamsCeiling.FilterDescendantsInstances = {player.Character}

local CONFIG = {
	TWEEN_INFO = TweenInfo.new(0.8, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),

	-- Visual Settings
	UNDERWATER_BLUR = 10,
	UNDERWATER_CONTRAST = 0.4,
	UNDERWATER_TINT = Color3.fromRGB(26, 59, 65),
	UNDERWATER_BRIGHTNESS = 1,
	UNDERWATER_FOG_START = 0,
	UNDERWATER_FOG_END = 40,
	UNDERWATER_FOG_COLOR = Color3.fromRGB(65, 105, 125),
	ABOVEWATER_BLUR = 0,
	ABOVEWATER_CONTRAST = 0,
	ABOVEWATER_TINT = Color3.new(1, 1, 1),
	ABOVEWATER_BRIGHTNESS = 3,
	ABOVEWATER_FOG_START = 150,
	ABOVEWATER_FOG_END = 1000,
	ABOVEWATER_FOG_COLOR = Color3.fromRGB(197, 210, 228),
}
--================================================================
-- Helper Functions
--================================================================
local function Fade(Part) TweenService:Create(Part, TWEEN_INFO, {Transparency = 0.9}):Play() end
local function Appear(Part, Amount) TweenService:Create(Part, TWEEN_INFO, {Transparency = Amount}):Play() end

-- SelectionBox Management
local function createOrUpdateSelectionBox(targetPart, color, thickness)
	if not targetPart then return end
	local selectionBox = targetPart:FindFirstChild("OcclusionHighlight")
	if not selectionBox then
		selectionBox = Instance.new("SelectionBox")
		selectionBox.Name = "OcclusionHighlight"
		selectionBox.Adornee = targetPart
		selectionBox.Transparency = 1 -- Start invisible
		selectionBox.Parent = targetPart
	end
	selectionBox.Color3 = color
	selectionBox.LineThickness = thickness
	TweenService:Create(selectionBox, TWEEN_INFO, {Transparency = 0}):Play()
end

local function cancelActiveTweens()
	for _, tween in ipairs(activeTweens) do
		tween:Cancel()
	end
	activeTweens = {}
end

local function applyUnderwaterEffects()
	cancelActiveTweens()

	blurEffect.Enabled = true
	colorCorrection.Enabled = true

	table.insert(activeTweens, TweenService:Create(blurEffect, CONFIG.TWEEN_INFO, { Size = CONFIG.UNDERWATER_BLUR }))
	table.insert(activeTweens, TweenService:Create(colorCorrection, CONFIG.TWEEN_INFO, { Contrast = CONFIG.UNDERWATER_CONTRAST, TintColor = CONFIG.UNDERWATER_TINT }))
	table.insert(activeTweens, TweenService:Create(Lighting, CONFIG.TWEEN_INFO, { Brightness = CONFIG.UNDERWATER_BRIGHTNESS, FogStart = CONFIG.UNDERWATER_FOG_START, FogEnd = CONFIG.UNDERWATER_FOG_END, FogColor = CONFIG.UNDERWATER_FOG_COLOR }))
	for _, tween in ipairs(activeTweens) do tween:Play() end
end

local function removeUnderwaterEffects()
	cancelActiveTweens()

	local blurTween = TweenService:Create(blurEffect, CONFIG.TWEEN_INFO, { Size = CONFIG.ABOVEWATER_BLUR })
	local colorTween = TweenService:Create(colorCorrection, CONFIG.TWEEN_INFO, { Contrast = CONFIG.ABOVEWATER_CONTRAST, TintColor = CONFIG.ABOVEWATER_TINT })
	local lightingTween = TweenService:Create(Lighting, CONFIG.TWEEN_INFO, { Brightness = CONFIG.ABOVEWATER_BRIGHTNESS, FogStart = CONFIG.ABOVEWATER_FOG_START, FogEnd = CONFIG.ABOVEWATER_FOG_END, FogColor = CONFIG.ABOVEWATER_FOG_COLOR })

	blurTween.Completed:Connect(function() blurEffect.Enabled = false end)
	colorTween.Completed:Connect(function() colorCorrection.Enabled = false end)

	table.insert(activeTweens, blurTween)
	table.insert(activeTweens, colorTween)
	table.insert(activeTweens, lightingTween)

	blurTween:Play()
	colorTween:Play()
	lightingTween:Play()
end

function isPointInPart(point, part)
	local pointInObjectSpace = part.CFrame:PointToObjectSpace(point)
	local halfSize = part.Size / 2
	return (math.abs(pointInObjectSpace.X) <= halfSize.X) and
		(math.abs(pointInObjectSpace.Y) <= halfSize.Y) and
		(math.abs(pointInObjectSpace.Z) <= halfSize.Z)
end

local function removeSelectionBox(targetPart)
	if not targetPart then return end
	local selectionBox = targetPart:FindFirstChild("OcclusionHighlight")
	if selectionBox then
		local tween = TweenService:Create(selectionBox, TWEEN_INFO, {Transparency = 1})
		tween.Completed:Once(function()
			if selectionBox and selectionBox.Parent then
				selectionBox:Destroy()
			end
		end)
		tween:Play()
	end
end

-- Hole Highlight Management
local function createOrUpdateHoleHighlight(holePart, color, thickness, rotated)
	if not holePart then return end
	local highlightPart = holePart:FindFirstChild("HoleHighlightPart")
	if not highlightPart then
		highlightPart = Instance.new("Part")
		highlightPart.Name = "HoleHighlightPart"
		highlightPart.Size = Vector3.new(1, 10, 10)
		highlightPart.Transparency = 1
		highlightPart.CanCollide = false
		highlightPart.Anchored = false
		highlightPart.Parent = holePart

		local weld = Instance.new("Weld")
		weld.Part0 = highlightPart
		weld.Part1 = holePart

		-- Apply rotation relative to the hole's orientation
		if rotated then 
			weld.C0 = CFrame.fromAxisAngle(holePart.CFrame.UpVector, math.rad(45))
			highlightPart.Name = highlightPart.Name.. "1"
		end

		weld.Parent = highlightPart
	end

	-- Only create selection box if it doesn't exist, update colors if it does
	local selectionBox = highlightPart:FindFirstChild("OcclusionHighlight")
	if not selectionBox then
		createOrUpdateSelectionBox(highlightPart, color, thickness)
	else
		-- Just update the color/thickness without retriggering the fade-in animation
		selectionBox.Color3 = color
		selectionBox.LineThickness = thickness
	end

	return highlightPart
end

local function removeHoleHighlight(holePart)
	if not holePart then return end
	local highlightPart = holePart:FindFirstChild("HoleHighlightPart")
	if highlightPart then
		local selectionBox = highlightPart:FindFirstChild("OcclusionHighlight")
		
		if selectionBox then
			local tween = TweenService:Create(selectionBox, TWEEN_INFO, {Transparency = 1})
			tween.Completed:Once(function()
				if highlightPart and highlightPart.Parent then
					highlightPart:Destroy()
				end
			end)
			tween:Play()
		else
			highlightPart:Destroy()
		end
	end
end

-- Function to manage the visibility of stars within a block
local function setStarState(blockModel, isVisible)
	if not blockModel then return end
	for _, descendant in ipairs(blockModel:GetDescendants()) do
		if descendant:HasTag("Star") and descendant.Hitbox and descendant.Hitbox.CanTouch then
			local emitter = descendant:FindFirstChild("Attachment") and descendant.Attachment:FindFirstChild("ParticleEmitter")
			if isVisible then
				descendant.Transparency = 0
				if emitter then emitter.Enabled = true end
			else
				descendant.Transparency = 1
				if emitter then emitter.Enabled = false; emitter:Clear() end
			end
		end
	end
end

-- Function to restore a block to its default, fully visible state
local function restoreBlock(blockModel)
	if not blockModel or not blockModel.Parent then return end

	local mainPart = blockModel:FindFirstChild("Main")
	if mainPart then Appear(mainPart, 0) end

	local primaryPart = blockModel:FindFirstChild("Primary")
	if primaryPart then
		removeSelectionBox(primaryPart)
		for _, child in ipairs(primaryPart:GetChildren()) do
			if child.Name == "Hole" then
				removeHoleHighlight(child)
				Appear(child, 1)
			end
		end
	end

	setStarState(blockModel, true)
end

local STouchConnect
local ETouchConnect

-- Consolidated cleanup function using the new restoreBlock helper
local function cleanupAll()
	for part, _ in pairs(occludingUnions) do
		if part and part.Parent then -- Add safety check
			restoreBlock(part.Parent)
		end
	end
	occludingUnions = {}

	if ceilingFadedBlock then
		restoreBlock(ceilingFadedBlock)
		ceilingFadedBlock = nil
	end

	

end

-- Character Management
local function getPlayerBlock(playerToCheck)
	local character = playerToCheck.Character
	if not character then return nil end
	local rootPart = character:FindFirstChild("HumanoidRootPart")
	if not rootPart then return nil end

	local overlappingParts = workspace:GetPartsInPart(rootPart, overlapParams)
	for _, part in ipairs(overlappingParts) do
		if part.Name == "Primary" then
			return part.Parent
		end
	end
	return nil
end

--================================================================
-- Initialization & Character Handling
--================================================================
local character, rootPart


local function onCharacterAdded(newCharacter)
	cleanupAll() -- Clean up any state from a previous character

	character = newCharacter
	rootPart = newCharacter:WaitForChild("HumanoidRootPart")
	raycastParamsCeiling.FilterDescendantsInstances = {newCharacter}
	local humanoid = newCharacter:WaitForChild("Humanoid")

	-- When the new character dies, clean up all visual effects.
	-- :Once is used so the connection cleans itself up automatically.
	humanoid.Died:Once(function()
		cleanupAll()
		pcall(function()
			ETouchConnect:Disconnect()
			STouchConnect:Disconnect()
		end)
	end)
	
	local HRP = character.HumanoidRootPart
	
	STouchConnect = HRP.Touched:connect(function(hit)

		if hit:HasTag("JumpPart") and not hit:FindFirstChild("OcclusionHighlight") then
			
			
			createOrUpdateSelectionBox(hit, Color3.new(1, 1, 1), SECONDARY_HIGHLIGHT_THICKNESS)
		end

	end)
	
	ETouchConnect = HRP.TouchEnded:Connect(function(hit)
		if hit and hit.Parent and hit:HasTag("JumpPart") then
			local selectionBox = hit:FindFirstChild("OcclusionHighlight")
			if selectionBox then
				hit:ClearAllChildren()
			end
		end
		
	end)
end

-- Connect to character events
if player.Character then
	onCharacterAdded(player.Character)
end
player.CharacterAdded:Connect(onCharacterAdded)



--================================================================
-- Main Render Loop
--================================================================
local function onRenderStep()
	-- If character is dead or not fully loaded, do nothing.
	if not character or not character.Parent or not rootPart or not rootPart.Parent then
		return
	end

	isUnderwater = isPointInPart(camera.CFrame.Position, workspace.Map:WaitForChild("WaterMain"))
	if isUnderwater then
		workspace.OceanWaves:Stop()
		if not workspace.OceanDeep.IsPlaying then
			workspace.OceanDeep:Play()
		end

		applyUnderwaterEffects()
	else
		workspace.OceanDeep:Stop()
		if not workspace.OceanWaves.IsPlaying then
			workspace.OceanWaves:Play()
		end

		removeUnderwaterEffects()
	end

	local playerCurrentBlock = getPlayerBlock(player)

	local cameraTarget = character:WaitForChild("Head").Position
	local obscuringPlayerParts = camera:GetPartsObscuringTarget({cameraTarget}, {character})
	local currentlyObscuringPlayerSet = {}
	for _, part in ipairs(obscuringPlayerParts) do
		if part.Name == "Main" and part:IsA("UnionOperation") then 
			currentlyObscuringPlayerSet[part] = true 
		end
	end

	-- Restore blocks that are no longer occluded
	for part, _ in pairs(occludingUnions) do
		if not part or not part.Parent then occludingUnions[part] = nil; continue; end
		-- If the part is NOT in the current obscuring set...
		if not currentlyObscuringPlayerSet[part] then
			local blockModel = part.Parent
			occludingUnions[part] = nil
			if blockModel ~= ceilingFadedBlock then
				restoreBlock(blockModel)
			end
		end
	end

	-- Process all currently occluding blocks
	for part, _ in pairs(currentlyObscuringPlayerSet) do
		if not occludingUnions[part] then
			Fade(part)
			occludingUnions[part] = true
		end

		local occludedBlockModel = part.Parent
		-- This check now determines the HIGHLIGHT style, not whether fading occurs.
		local isPlayerInside = (occludedBlockModel == playerCurrentBlock)
		local primaryPart = occludedBlockModel:FindFirstChild("Primary")

		if primaryPart then
			local color, thickness = isPlayerInside and PRIMARY_HIGHLIGHT_COLOR or SECONDARY_HIGHLIGHT_COLOR, isPlayerInside and PRIMARY_HIGHLIGHT_THICKNESS or SECONDARY_HIGHLIGHT_THICKNESS
			createOrUpdateSelectionBox(primaryPart, color, thickness)

			for _, child in ipairs(primaryPart:GetChildren()) do
				if child.Name == "Hole" then
					if isPlayerInside then
						-- Player is inside this occluded block, make holes visible
						Appear(child, 0.2)
						createOrUpdateHoleHighlight(child, color, thickness)
					else
						-- Player is not inside. Make holes opaque unless this block is also the ceiling.
						if occludedBlockModel ~= ceilingFadedBlock then
							Appear(child, 1)
							removeHoleHighlight(child)
						end
					end
				end
			end
		end
		-- Stars fade when block is occluded, EXCEPT if player is inside that block
		setStarState(occludedBlockModel, isPlayerInside)
	end

	local function isPointInPart(point, part, tolerancePercent)
		
		tolerancePercent = tolerancePercent or 0.2

		local cf = part.CFrame
		local size = part.Size

		local localPoint = cf:PointToObjectSpace(point)

		-- Calculate the reduced bounds (inward tolerance)
		local halfSize = size / 2
		local effectiveHalfSize = halfSize * (1 - tolerancePercent)

		-- Check if the point is within the reduced bounds
		return math.abs(localPoint.X) <= effectiveHalfSize.X and
			math.abs(localPoint.Y) <= effectiveHalfSize.Y and
			math.abs(localPoint.Z) <= effectiveHalfSize.Z
	end

	if playerCurrentBlock then
		local head = character:FindFirstChild("Head")
		local blockAboveThisFrame = nil
		if head then
			local rayOrigin = head.Position
			local rayDirection = Vector3.yAxis * CEILING_RAY_DISTANCE
			-- Temporarily add player's current block to filter to avoid hitting its own ceiling
			local filterInstances = {character}
			local currentPrimary = playerCurrentBlock.PrimaryPart
			if currentPrimary then table.insert(filterInstances, currentPrimary) end
			raycastParamsCeiling.FilterDescendantsInstances = filterInstances

			local rayResult = workspace:Raycast(rayOrigin, rayDirection, raycastParamsCeiling)

			if rayResult and rayResult.Instance then
				local hitModel = rayResult.Instance:FindFirstAncestorWhichIsA("Model")
				if  hitModel 
					and hitModel.Parent == workspace.Blocks
					and hitModel ~= playerCurrentBlock 
					and isPointInPart(rayResult.Position, currentPrimary)
				then
					blockAboveThisFrame = hitModel
				end
			end
		end

		if blockAboveThisFrame ~= ceilingFadedBlock then
			-- Restore the previously faded ceiling block if it's no longer needed
			if ceilingFadedBlock then
				local mainPart = ceilingFadedBlock:FindFirstChild("Main")
				if mainPart and not occludingUnions[mainPart] then
					restoreBlock(ceilingFadedBlock)
				end
			end

			-- Fade the new ceiling block
			if blockAboveThisFrame then
				local mainPart = blockAboveThisFrame:FindFirstChild("Main")
				if mainPart and not occludingUnions[mainPart] then
					Fade(mainPart)
					setStarState(blockAboveThisFrame, false)
				end

				local primaryPart = blockAboveThisFrame:FindFirstChild("Primary")
				if primaryPart then
					createOrUpdateSelectionBox(primaryPart, SECONDARY_HIGHLIGHT_COLOR, SECONDARY_HIGHLIGHT_THICKNESS)
					for _, child in ipairs(primaryPart:GetChildren()) do
						if child.Name == "Hole" then
							Appear(child, CEILING_HOLE_TRANSPARENCY)
							createOrUpdateHoleHighlight(child, SECONDARY_HIGHLIGHT_COLOR, SECONDARY_HIGHLIGHT_THICKNESS)
						end
					end
				end
			end
			ceilingFadedBlock = blockAboveThisFrame
		end
	else
		if ceilingFadedBlock then
			local mainPart = ceilingFadedBlock:FindFirstChild("Main")
			if mainPart and not occludingUnions[mainPart] then
				restoreBlock(ceilingFadedBlock)
			end
			ceilingFadedBlock = nil 
		end
	end
	
	
	
end

local Collectables = require(game.ReplicatedStorage.Collectables)
Collectables.Animate()

local TNT = require(game.ReplicatedStorage.TNT)

game.ReplicatedStorage.Remotes.TNTClient.OnClientEvent:Connect(function(Position, Range)
	TNT.OnClient(Position, Range)
end)

local SoundFolder = game.ReplicatedStorage.WarningSounds:GetChildren()
task.spawn(function()
	while task.wait(10) do
		if not isUnderwater then continue end

		local Volume = math.random(.3, 2)
		local Sound = SoundFolder[math.random(1, #SoundFolder)]
		Sound.Volume = if Volume > 0 then Volume else .5
		Sound:Play()
	end
end)

local connection = RunService.RenderStepped:Connect(onRenderStep)
Players.PlayerRemoving:Connect(function(plr)
	if plr == player and connection then
		cleanupAll()
		connection:Disconnect()
	end
end)

1 Like

For more context; I’m trying to make it that if a player is touching a part w a specific tag it gets highlighted. And hen untouched removing that highlight. However sometimes the highlights don’t remove. Touching it again fixes the issue but this sytem is still buggy. The video is a little old as the highlight issue is more rare now.

2 Likes

TouchEnded isn’t reliable. From what I’ve seen, it sometimes fires even when a touch hasn’t ended and sometimes doesn’t end up firing at all.

IMO you should instead use Touched to detect when a touch started then use GetPartsInPart or the character’s position periodically after that to determine when the touch ends (e.g. there are no character parts within the box).

Also remember that there is a max of 31 highlights per client!

3 Likes

its been increased recently to 200 something if im not wrong. and i know i said highlights but its really using selection boxes. just wanted to lyk

2 Likes

Did you figure out the solution or do you need help implementing :GetPartsInPart?

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.