Object "X-Ray/Highlighting" System

Hey everyone! I created the V1 of this module for a game I’m working on and it was refined by @GollyGreg on GitHub.

The source can be found here: GitHub - Imaginaerume/rbx-ObjectHighlighter: This module allows you to make an object or model act as "Always on Top" and layer over the normal 3D game world.
Readme below V

rbx-ObjectHighlighter

This module allows you to make an object or model act as “Always on Top” (or X-Ray) and layer over the normal 3D game world.

Table of Contents

Purpose

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.

Screenshots


Implementations.highlightColor example


Implementations.worldColor example

Code Example

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 :slight_smile:

232 Likes

This is some next-level wallhacks. Lol

Great Resource!

Will probably be using it in my game for an ability.

18 Likes

Very nice! I considered doing this as well, but I’m very tired of creating ViewportFrame projects by now😅

I can see a lot of uses for this, and I hope lots of people use it!

I hope someone uses it to let you see your objective, like an object you have to go get. Would be dope.

All in all, great product! Kudos for sharing!

11 Likes

I see through the lies of this module!

16 Likes

That would actually be a pretty cool feature to add into one of my games.
Thanks for sharing!

3 Likes

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.

Keep up the awesome work.:+1:

7 Likes

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.

7 Likes

This is GREAT for thermal scopes. Nice work!

7 Likes

Really impressed work! A lot of people could use this especially for people making FPS, or any kind of co-op game

3 Likes

I saw polyguns do this before viewport frames were a thing. How’d they pull it off?

3 Likes

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.

9 Likes

This looks exactly like Soldier 76’s ultimate in Overwatch haha, I like it!

2 Likes

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’ve been working with @Imaginaerum on the project to get it feature-ready for use in real games.

Features a new api, examples. Base code is the same, but reorganized. Read me has been upgraded.

Please fill out any Issues on the repository page if anything arises.

1 Like

Very nice.

I plan on making a few demos of this just to play around, so I’ll be sure to post them here.
Thank you for your contribution to the community!

1 Like

We should showcase demos on the project page if you ever have them hosted somewhere.

This is fun!

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.

External Media

Edit: Forgot to include my code :man_facepalming:t2:

{
		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,
	}

Edit: Made a simple library dedicated to this

9 Likes

That’s actually sick dude! Nice to know I’m not the only one who creates things inspired from Apex Legends lol.

3 Likes

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.

2 Likes

That’s what I was going to do next but I guess you beat me to that one :stuck_out_tongue_winking_eye::yum:

3 Likes