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)