Help detecting when a newly added part is seen by a player

Hello!

The title is a little vague, so I’ll help clear it up a bit:
I’m working on this character that acts similarly to Slenderman in a lot of games, where you look at them and he does damage over time while you’re looking at them.

To have this work, the player spawns in as Slenderman with a tagged HumanoidRootPart, which acts as the part of interest that, when a player is able to see it, deals damage to them. The only issue I have right now is detecting when that part is added into workspace, and then detecting when a player looks at that object.

I’ve tried a workspace.ChildAdded() and then repeating a loop to check AND damage the player if it’s the part of interest and it’s in the camera, but that just caused lag and didn’t work. For now, I’m stumped.

Any help would be greatly appreciated!

1 Like

Hey! I see what you’re trying to do, and I understand the challenge you’re facing. I’ve done something like this before

You want to create a character similar to Slenderman, where when a player looks at a specific part (the HumanoidRootPart), the player takes damage over time. The problem seems to be with detecting when that part is added to the workspace and checking if the player is looking at it.

Here’s a better way to approach it:

Instead of constantly checking in a loop (which causes lag), you can use the Camera’s WorldToScreenPoint method to check if the part is within the player’s view (i.e., if it’s visible on the screen). You can then trigger the damage over time when the player is looking at the part. This is more efficient because it avoids constantly checking all parts in the workspace.

Here’s how you could approach this:

  1. Detect when the part is added to the workspace using ChildAdded.
  2. Use Camera:WorldToScreenPoint() to check if the part is visible on the player’s screen.
  3. Damage the player when the part is in view.
local Players = game:GetService("Players")
local Camera = game.Workspace.CurrentCamera

local function isPartVisible(part)
    local screenPoint = Camera:WorldToScreenPoint(part.Position)
    
    -- Check if the part is actually within the screen boundaries
    local viewportSize = Camera.ViewportSize
    return screenPoint.X >= 0 and screenPoint.X <= viewportSize.X and screenPoint.Y >= 0 and screenPoint.Y <= viewportSize.Y
end

-- (Assuming you're using a Humanoid to apply damage)
local function damagePlayer(player)
    local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
    if humanoid then
        humanoid:TakeDamage(1)
    end
end

local function onPartAdded(part)
    if part:IsA("Model") and part:FindFirstChild("HumanoidRootPart") then
        local rootPart = part:FindFirstChild("HumanoidRootPart")
        
        game:GetService("RunService").Heartbeat:Connect(function()
            for _, player in pairs(Players:GetPlayers()) do
                if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
                    if isPartVisible(rootPart) then -- Check if the player's character is looking at the Slenderman (part)
                        damagePlayer(player)   -- Damage the player if the part is in their view
                    end
                end
            end
        end)
    end
end

game.Workspace.ChildAdded:Connect(onPartAdded)

What this script does:

  1. Detects when a part is added to the workspace (ChildAdded).
  2. Checks if the part is visible on the player’s screen using Camera:WorldToScreenPoint().
  3. Damages the player if they are looking at the part (Slenderman’s HumanoidRootPart).

Camera:WorldToScreenPoint() converts the part’s 3D position to a 2D screen space, which helps determine if it’s visible. BUT! If you have a custom camera script there may be a chance it could break this.

We check if the part is within the viewport boundaries to confirm if it’s visible on the screen.

The damage is applied to players looking at the Slenderman, and you can adjust the damage value as needed.

Why This Is Probably Better:

Instead of using a loop that checks every part all the time, we’re now only checking if the specific part is visible on the player’s screen. This is much more efficient and should reduce lag.
We also avoid the need for constantly checking the workspace, which was causing lag in your previous attempts.

I’m not 100% sure if this is what you are looking for, but lmk if you need more help

2 Likes

My original attempt was checking to see if a child was added into workspace and THEN checking if that child has a tag that knew if it was slenderman, then it would run a check every 0.1 seconds to see if that part was in the player’s camera. I’ll definitely give your script a shot, thank you so much!

This is actually really close to what I’m looking for, thank you! The only problem I’ve encountered right now is if the enemy is even behind the player, and I’m not sure why this is happening, but it’ll damage the player anyways. It still works as intended, damaging if its in sight, but why it can do this even if you’re not looking is beyond me.

https://gyazo.com/7d6eb3d53b6afaead2b3f12e4708433d

Actually, really easy fix. All I had to do was include the second parameter of the WorldToScreenView function, which is checking if it’s in the screen, and it seems to solve my problem. I’ll still mark your solution right, since I’m using pretty much entirely your code. Thank you so much!

Hey, just came across another issue I’m having trouble with!

This works great when spawning, but the effect still lingers when the part is removed. How could I fix this? I tried spawning the code blocks inside with a coroutine and closing it when the child is removed from workspace, but that doesn’t seem to fix my problem.

Heyo,

My first thought is to simply keep track of what it’s attacking, then disconnect afterward. Like this!

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Camera = workspace.CurrentCamera

local function isPartVisible(part)
    local screenPoint, onScreen = Camera:WorldToScreenPoint(part.Position)
    local viewportSize = Camera.ViewportSize
    return onScreen and screenPoint.X >= 0 and screenPoint.X <= viewportSize.X and screenPoint.Y >= 0 and screenPoint.Y <= viewportSize.Y
end

local function damagePlayer(player)
    local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
    if humanoid then
        humanoid:TakeDamage(1)
    end
end

local function onPartAdded(part)
    if part:IsA("Model") and part:FindFirstChild("HumanoidRootPart") then
        local rootPart = part:FindFirstChild("HumanoidRootPart")
        
        local connection
        connection = RunService.Heartbeat:Connect(function()
            if not rootPart or not rootPart:IsDescendantOf(workspace) then
                if connection then
                    connection:Disconnect()
                    connection = nil
                end
                return
            end

            for _, player in pairs(Players:GetPlayers()) do
                if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
                    if isPartVisible(rootPart) then
                        damagePlayer(player)
                    end
                end
            end
        end)

        -- Disconnect when the model is removed
        part.AncestryChanged:Connect(function(_, parent)
            if not parent and connection then
                connection:Disconnect()
                connection = nil
            end
        end)
    end
end

workspace.ChildAdded:Connect(onPartAdded)

Why This Works? Heartbeat connection is stored in a variable so we can disconnect it later.
We use AncestryChanged to detect when the Slenderman model (or part) is removed from the workspace and clean up the connection. It also prevents performance leaks and keeps logic clean so that’s a bonus.

If you did have multiple Slenderman-like characters, you might want to consider tracking each with a dictionary like local activeSlendermen = {} and cleaning each connection individually as needed.

Is that helpful?

So sorry, forgot to update you! I ended up solving it with another programmer in my project by just disconnecting the heartbeat function when slenderman is removed. I appreciate your response, though!

1 Like

Great to hear! Feel free to DM me if you need any help!

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