OMG I am so in love with this! I added different distances for the Egg Hunt game vs. Treasure Island and the results are terrific, IMNSHO. No more cluttered UI and the transparency value tells you how far away the object is. I love it! I will definitely be using this in both games. I’ll probably make it a powerup or reward but it makes such a difference in navigating a big map looking for things. I even like it combined with the minimap system that I’ve been using. Awesome work. And your code looks very clean and readable. Good job.
I believe that processWorkspaceDescendantAdded is being called whenever a workspace descendant is removed. It looks like you’re connecting the wrong function.
Great module though. I’m definitely using this in my game
Good catch! You’re totally right: it should be processWorkspaceDescendantRemoving
, not processWorkspaceDescendantAdded
! Thanks a bunch
I updated the game and the model
Sorry but this post makes no sense, put what in what? it’s not working for me whatever i do. could you elaborate? currently monkey-brained without my ADHD medication
No worries. If you want to make the indicators fade based on distance, place the code block above under where it says “local position = data[1].WorldPosition”. You can also just replace the entire “updateIndicatorPositions” function with the code under the “Example code” drop down.
You can also replace all the code inside “IndicatorClient” with the code below:
-- v.1.2
local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local createNewIndicator = require(script:WaitForChild("CreateNewIndicator"))
local updateIndicatorUI = require(script:WaitForChild("UpdateIndicatorUI"))
local camera = Workspace.CurrentCamera
local player = Players.LocalPlayer
local screenGui = Instance.new("ScreenGui")
screenGui.Name = "IndicatorGui"
screenGui.IgnoreGuiInset = true
screenGui.ResetOnSpawn = false
screenGui.Parent = player:WaitForChild("PlayerGui")
local indicatorTable = {}
-- {attachment, ui, connections}
local function removeIndicator(attachment)
for index, data in ipairs(indicatorTable) do
if data[1] == attachment then
data[2]:Destroy()
for _, connection in ipairs(data[3]) do
if connection then
connection:Disconnect()
end
end
table.remove(indicatorTable, index)
end
end
end
local function processWorkspaceDescendantAdded(descendant)
if descendant:IsA("Attachment") and descendant.Name == "Indicator" then
removeIndicator(descendant)
local newIndicator = createNewIndicator()
local connections = {}
local attributeChanged = descendant.AttributeChanged:Connect(function(name)
if (name == "Image") or (name == "Team") or (name == "Color") or (name == "Enabled") then
updateIndicatorUI(descendant, newIndicator)
end
end)
table.insert(connections, attributeChanged)
local data = {}
data[1] = descendant
data[2] = newIndicator
data[3] = connections
table.insert(indicatorTable, data)
updateIndicatorUI(descendant, newIndicator)
newIndicator.Parent = screenGui
end
end
for _, descendant in ipairs(Workspace:GetDescendants()) do
processWorkspaceDescendantAdded(descendant)
end
Workspace.DescendantAdded:Connect(processWorkspaceDescendantAdded)
local function processWorkspaceDescendantRemoving(descendant)
if descendant:IsA("Attachment") and descendant.Name == "Indicator" then
removeIndicator(descendant)
end
end
Workspace.DescendantRemoving:Connect(processWorkspaceDescendantAdded)
local function updateIndicatorPositions()
local viewportX = camera.ViewportSize.X
local viewportY = camera.ViewportSize.Y
if not indicatorTable[1] then return end
local bufferSize = indicatorTable[1][2].AbsoluteSize.X
local maxBoundsX = viewportX - (bufferSize * 2)
local maxBoundsY = viewportY - (bufferSize * 2)
local camCFrame = camera.CFrame
local screenHypotenuse = math.sqrt((maxBoundsX/2)^2+(maxBoundsY/2)^2)
local cameraForward
do
local cameraForward3D = camera.CFrame.LookVector
cameraForward = Vector2.new(cameraForward3D.X, cameraForward3D.Z).Unit
end
for _, data in ipairs(indicatorTable) do
local indicatorUI = data[2]
if indicatorUI.Visible == false then
continue
end
local position = data[1].WorldPosition
--
local maxDistance = 100
local minDistance = 10
local distance = (position - camCFrame.Position).Magnitude
local transparency = 0
if distance > minDistance then
transparency = math.clamp((distance-minDistance)/(maxDistance-minDistance), 0, 1)
end
data[2].Transparency = transparency
data[2].ArrowFrame.ArrowImage.ImageTransparency = transparency
data[2].IconImage.ImageTransparency = transparency
data[2].LayoutOrder = math.round(distance/(maxDistance - minDistance)*10)
--
local screenPosition3d, onScreen = camera:WorldToViewportPoint(position)
local xPosition = math.clamp(screenPosition3d.X, bufferSize, viewportX - bufferSize)
local yPosition = math.clamp(screenPosition3d.Y, bufferSize, viewportY - bufferSize)
if (xPosition == screenPosition3d.X) and (yPosition == screenPosition3d.Y) and onScreen then
indicatorUI.ArrowFrame.Visible = false
else
indicatorUI.ArrowFrame.Visible = true
local worldDirection = position - camCFrame.Position
local relativeDirection = camCFrame:VectorToObjectSpace(worldDirection)
local relativeDirection2D = Vector2.new(relativeDirection.X, relativeDirection.Y).Unit
local testScreenPoint = relativeDirection2D * screenHypotenuse
local angle = math.atan2(relativeDirection2D.X, relativeDirection2D.Y)
local screenPoint
if math.abs(testScreenPoint.Y) > maxBoundsY/2 then
screenPoint = relativeDirection2D * math.abs(maxBoundsY/2/relativeDirection2D.Y)
else
screenPoint = relativeDirection2D * math.abs(maxBoundsX/2/relativeDirection2D.X)
end
xPosition = viewportX / 2 + screenPoint.X
yPosition = viewportY / 2 - screenPoint.Y
indicatorUI.ArrowFrame.Rotation = math.deg(angle)
end
indicatorUI.Position = UDim2.fromOffset(xPosition, yPosition)
end
end
RunService.RenderStepped:Connect(updateIndicatorPositions)
ah! i also have another question, is there a way i could have the markers be shown despite them not being streamed?
Ah, this is something I hadn’t considered! I’m honestly not sure if I programmed everything such that it can work with stuff being streamed in and out.
Assuming the code works though, the indicators would just need to be created on the client. I can program something specifically for this case.
Note: This is only for StreamingEnabled, which loads and unloads things that are far away (which includes all the indicators this system uses).
Okay! I finished the code for this. Basically how it works:
- Takes all indicators in and added to Workspace
- Gives them an ID and sends their data through a RemoteEvent to the client
- The client takes that data and creates a new Indicator on the client
- The server also sends RemoteEvents to update Color, Team, Image, and Enabled (when the server changes those properties). NOTE: Position is not replicated with this new system.
- The indicators being destroyed on the server is also replicated through a RemoteEvent.
How to use:
- Get the model here: Manual Indicator Replicator - Roblox
- Place the entire thing into StarterGui
- Should be working after that
Use cases:
- Only for streaming enabled
- Doesn’t work for things that move (at least movement isn’t replicated). I programmed it mainly for static Indicators that don’t change much.
Note: Changing indicators through LocalScripts doesn’t work because the indicators aren’t in the same place on the client. They are all under a single client-only part in Workspace. (Changes on the server ones replicate to the client ones though).
Let me know if this gives you any problems
does this streamingenabled one have fading out?
nevermind, it works like a charm! thanks a million
Thank you for this great resource.
Any plans to add the the ability of changing indicators through LocalScripts?
I would really like to use this system for things like a tutorial or quest system where indicators are turned on/off just for a specific player. I feel like changing the player group for every specific quest or tutorial is perhaps not optimal.
To change an indicator from a LocalScript:
- Find/wait-for the indicator Attachment
- Change it’s attributes (Color, Image, Enabled, Team)
For your case, I’d recommend either:
- Having pre-made indicators on the server with Enabled false then changing Enabled to true on the client when you need them.
or
- Creating the indicators on the client when you need them and removing them when you don’t
Something to note about creating indicators, you need to name them “Indicator” before adding them to workspace for the connections to be created (it’s best practice anyways).
Let me know if you have any questions
Thank you for your reply.
I’m using the Manual Indicator Replicator for streaming enabled, sorry for being unclear.
I also read Changing indicators through LocalScripts doesn’t work because the indicators aren’t in the same place on the client.
So I guess my only option to change via localscript is to turn streaming enabled off?
Yeah, that system creates new indicators to represent the server sided ones and updates them when the server asks through remote events.
In that case you could use the second option, creating the indictors entirely on the client.
So, for example:
- Quest starts on server
- Replicate than info to the client (remote events, properties, etc.)
- Create a new indicator with the attributes (on the client)
- Remove the new indicator when the quest ends (on the client)
How can i make it only visible when looking at it and when you look away it doesn’t show the arrow around the screen?
Thank you so much! This is exactly what I needed.
Hi there, I have a question. Can you tell me what the license is for this code? I want to upload it to GitHub and use it. However, if your work is not licensed, it can be difficult legally…
I had a stroke trying to figure out how this works.
Hello,
has anyone done any modification or updates to this that they would like to share?
Thanks