To provide an generic and extendable solution to adopting ViewportFrames as a means to render an object on top of a 3D environment.
This is intended to replace older solutions such as rbx-XRayAdornment. Our previous issues with this old xray module is that it made it really hard to balance the state of the target object with the actual rendering process.
There was some difficulty in making a good “only-render-when-obstructed” highlighted object. All attempts were messy and oddly coupled with the creation of the XRayAdornment itself.
Our philosophy is to separate highlight state from highlight render rules.
Check out our other annotated examples in the examples directory!
local ObjectHighlighter = require(game.ReplicatedStorage:FindFirstChild("ObjectHighlighter"))
local targetModel = game.Workspace.MyModel --Replace this with the path to your model
-- This screen gui will contain our ViewportFrames
local myScreenGui = Instance.new("ScreenGui")
myScreenGui.Name = "ObjectHighlighter"
myScreenGui.Parent = game.Players.LocalPlayer.PlayerGui
local myRenderer = ObjectHighlighter.createRenderer(myScreenGui)
local myHighlight = ObjectHighlighter.createFromTarget(targetModel)
-- Apply our highlight object to our Renderer stack.
-- We can add as many highlight objects to a renderer as we need
myRenderer:addToStack(myHighlight)
game:GetService("RunService").RenderStepped:Connect(function(dt)
-- Our renderer will not render until it steps
myRenderer:step(dt)
end)
Feel free to post here or DM me or @GollyGreg any questions, comments, issues or feedback
Wow… This absolutely blew my mind. Great job with this, who knew X-ray would could be converted into ROBLOX, specially when it was made by the community itself.
Saw a handful of people making these style setups when viewport frames where first being tested, really cool to see that you packaged your implementation up for distribution!
Although I’ll probably make my own it makes me happy to see someone release it for the community in such an accessible format so more games can enjoy the benefits. Great for creating games where you can see your team through walls (like L4D) and if asymmetric the “enemy” team can see players at all times.
Also solid for FPS style games where medics need to be able to see where low health team members are and doing a color-slide from orange-red as those team members lose more health.
CloneTrooper1019 did this as well for The Stalker 2. He open-sourced the code on Pastebin. What he did was replicate the characters to a Table and later placed them in the Camera and scaled and positioned them based on the character’s position relative to the camera’s CFrame. I could only make an educated guess that the developers of Polyguns did it in a fashion similar to this.
I’m pretty sure they easily just inserted SurfaceGuis for each side of the character then made it AlwaysOnTop, since the models are all squares which is much more simpler.
I made an extremely rough prototype of a “Digital Threat” scope, inspired by Apex Legends.
It checks if the object is within a GUI (the red dot gui) using WorldToScreenPoint, and then does a single raycast to check if the part is obscured.
If the part is in the gui and visible, it updates the highlight and makes it visible.
Plenty of bugs to work out here, but I wanted to show a cool usage idea.
{
onBeforeRender = function(_, _)
return true
end,
onRender = function(_, worldPart, viewportPart, highlight)
--Only update within frame
local v,oS = Cam:WorldToScreenPoint(worldPart.CFrame.Position)
if oS and insideGUI(v, gui) then
--Only update if not obscured
local blockingPart = workspace:FindPartOnRay(Ray.new(Cam.CFrame.Position,(worldPart.CFrame.Position - Cam.CFrame.Position).unit * 500), Cam)
if blockingPart and not blockingPart:IsDescendantOf(worldPart.Parent) then
viewportPart.Transparency = 1
else
viewportPart.Transparency = worldPart.Transparency
viewportPart.CFrame = worldPart.CFrame
viewportPart.Color = highlight.color
end
else
--not within gui
viewportPart.Transparency = 1
end
end,
onAdded = function(_, viewportPart, highlight)
local function clearTextures(instance)
if instance:IsA("MeshPart") then
instance.TextureID = ""
elseif instance:IsA("UnionOperation") then
instance.UsePartColor = true
elseif instance:IsA("SpecialMesh") then
instance.TextureId = ""
end
end
local function colorObject(instance)
if instance:IsA("BasePart") then
instance.Color = highlight.color
end
end
for _, object in pairs(viewportPart:GetDescendants()) do
clearTextures(object)
colorObject(object)
end
clearTextures(viewportPart)
colorObject(viewportPart)
end,
}
I actually replicated Bloodhound’s tactical ability using this; the Eye of the Allfather. That thing that momentarily reveals the location of nearby players at the time of the use.