Examine Service - Inspect Models

Updates:

0.1 - 28.12.2020
  • Examine Service was created.
0.2 - 29.12.2020
  • Connections is now a normal table. Probably best because it wasn’t a metatable before.
  • All CoreGUI’s are set back to enabled after an object has been examined/inspected.
  • Depth of Field and the Keys GUI Is now made in the module itself.

Download:


Code:

--[[
	DevForum:
	>	https://devforum.roblox.com/t/examine-service-inspect-models/948771
	
	Documentation:
	>	This is used to show an object up-close with 3D interaction using the mouse.
	
	Functions:
	>	ExamineService.new(model, speed, angleX, angleY)
	>	ExamineService:Render(model)
	
	Example:
	>	local ExamineService = require(DirectoryToTheModule)
	>	local AK47 = WorkspaceService:WaitForChild("AK47")
	>	local Object = ExamineService.new(AK47)
	>	ExamineService:Render(Object)
--]]

-- CLASS
local ExamineService = {}
ExamineService.__index = ExamineService

-- SERVICES
local Players = game:GetService("Players")
local WorkspaceService = game:GetService("Workspace")
local StarterGui = game:GetService("StarterGui")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local Lighting = game:GetService("Lighting")

-- VARIABLES
local Player = Players.LocalPlayer
local PlayerGui = Player.PlayerGui
local Mouse = Player:GetMouse()
local Camera = WorkspaceService.Camera
local CurrentCamera = WorkspaceService.CurrentCamera

-- TABLES
local Connections = {}

-- METHODS
function ExamineService.new(model, ratio, x, y)
	if Connections.IsExamining then
		return warn("[Examine Service] - Examining an object already!")
	end
	if not model then
		return warn("[Examine Service] - Model must be specified!")
	end
	if not (typeof(model) == "Instance") then
		return warn("[Examine Service] - Model must be an Instance!") 
	end
	if not model.PrimaryPart then
		return warn("[Examine Service] - Model must have a set PrimaryPart!")
	end
	
	-- METATABLE:
	local Object = {}
	setmetatable(ExamineService, Object)
	
	Object.Model = model:Clone()
	Object.Ratio = ratio or 0.0325
	Object.X = x or math.rad(90)
	Object.Y = y or math.rad(0)
	Object.DepthOfField = nil
	Object.Keys = nil
	
	return Object
end

function ExamineService:NewDepthOfField()
	local DepthOfField = Instance.new("DepthOfFieldEffect")
	DepthOfField.Name = "Effect"
	DepthOfField.FarIntensity = 1
	DepthOfField.FocusDistance = 0
	DepthOfField.InFocusRadius = 8
	DepthOfField.NearIntensity = 1
	DepthOfField.Parent = Lighting
	DepthOfField.Enabled = true
	return DepthOfField
end

function ExamineService:NewKeys()
	local ScreenGui = Instance.new("ScreenGui")
	ScreenGui.Name = "Keys"
	ScreenGui.IgnoreGuiInset = true
	ScreenGui.ResetOnSpawn = false
	ScreenGui.ZIndexBehavior = Enum.ZIndexBehavior.Global

	-- MAIN FRAME:
	local Frame = Instance.new("Frame")
	Frame.Name = "Main"
	Frame.AnchorPoint = Vector2.new(1, 1)
	Frame.BackgroundTransparency = 1
	Frame.Position = UDim2.new(1, 0, 1, 0)
	Frame.Size = UDim2.new(0.035, 0, 0.15, 0)
	Frame.ZIndex = 100

	-- ASPECT RATIO FOR FRAME:
	local UIAspectRatio = Instance.new("UIAspectRatioConstraint")
	UIAspectRatio.AspectRatio = 0.5
	UIAspectRatio.Parent = Frame

	-- MOUSE BUTTON ONE:
	local MouseButton1 = Instance.new("ImageButton")
	MouseButton1.Name = "MouseButton1"
	MouseButton1.AnchorPoint = Vector2.new(0.5, 0.5)
	MouseButton1.BackgroundTransparency = 1
	MouseButton1.Position = UDim2.new(0.5, 0, 0.275, 0)
	MouseButton1.Size = UDim2.new(0.8, 0, 0.8, 0)
	MouseButton1.ZIndex = 101
	MouseButton1.Image = "http://www.roblox.com/asset/?id=4905957737"
	MouseButton1.ScaleType = Enum.ScaleType.Slice
	MouseButton1.ImageRectOffset = Vector2.new(200, 600)
	MouseButton1.ImageRectSize = Vector2.new(100, 100)
	MouseButton1.Parent = Frame

	-- UI ASPECT RATIO FOR ICONS:
	local UIAspectRatioIcons = Instance.new("UIAspectRatioConstraint")
	UIAspectRatioIcons.AspectRatio = 1
	UIAspectRatioIcons.Parent = MouseButton1

	-- MOUSE BUTTON TWO:
	local MouseButton2 = MouseButton1:Clone()
	MouseButton2.Name = "MouseButton2"
	MouseButton2.Position = UDim2.new(0.5, 0, 0.725, 0)
	MouseButton2.ImageRectOffset = Vector2.new(400, 600)
	MouseButton2.Parent = Frame
	
	-- TEXT FOR ICONS:
	local Title = Instance.new("TextLabel")
	Title.Name = "Title"
	Title.BackgroundTransparency = 1
	Title.AnchorPoint = Vector2.new(1, 0.5)
	Title.Position = UDim2.new(0, 0, 0.5, 0)
	Title.Size = UDim2.new(1.7, 0, 0.5, 0)
	Title.ZIndex = 102
	Title.Font = Enum.Font.GothamBold
	Title.TextColor3 = Color3.fromRGB(255, 255, 255)
	Title.TextXAlignment = Enum.TextXAlignment.Right
	Title.TextScaled = true
	Title.Text = "Move"
	Title.Parent = MouseButton1
	local Title2 = Title:Clone()
	Title2.Text = "Cancel"
	Title2.Parent = MouseButton2
	
	-- SHOW ON CLIENT:
	Frame.Parent = ScreenGui
	ScreenGui.Parent = PlayerGui
	
	return ScreenGui
end

function ExamineService:Render(model)
	if Connections.IsExamining or not model then
		return
	end
	Connections.IsExamining = true
	
	-- HIDE CORE:
	StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.All, false)
	
	-- OBJECT:
	self.Model = model.Model
	ExamineService.SetCollision(self.Model)
	
	-- EXTRA EFFECTS:
	self.DepthOfField = self:NewDepthOfField()
	self.Keys = self:NewKeys()
	
	-- VALUES:
	local Ratio = model.Ratio
	local X, Y = model.X, model.Y
	local diffX, diffY = 0, 0
	
	-- CAMERA SETUP:
	ExamineService.SetIdle(true)
	
	-- CAMERA RENDER:
	self.Model.Parent = WorkspaceService
	Connections.Render = RunService.RenderStepped:Connect(function()
		local Pos = CurrentCamera.CFrame * CFrame.new(0, 0, -5)
		self.Model:SetPrimaryPartCFrame(Pos * CFrame.Angles(0, X, Y))
	end)
	
	-- MOUSE MOVEMENT:
	local lastX, lastY = 0, 0
	Connections.Input = UserInputService.InputBegan:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			Connections.MouseMovement = Mouse.Move:Connect(function()
				diffX = (Mouse.X - lastX)
				diffY = (Mouse.Y - lastY)
				
				X = ExamineService.GetDirectionBasedOnDifference(diffX, X, Ratio)
				Y = ExamineService.GetDirectionBasedOnDifference(diffY, Y, Ratio)
				
				lastX = Mouse.X
				lastY = Mouse.Y
			end)
		elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
			ExamineService:Disconnect()
		end
	end)
	
	-- PAUSE MOVEMENT:
	Connections.Pause = UserInputService.InputEnded:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			if Connections.MouseMovement then
				if Connections.MouseMovement.Connected then
					Connections.MouseMovement:Disconnect()
				end
			end
		end
	end)
end

function ExamineService:Disconnect()
	ExamineService.SetIdle(false)
	
	-- DISCONNECT EVENTS:
	if Connections.Render then
		if Connections.Render.Connected then
			Connections.Render:Disconnect()
		end
	end
	if Connections.Input then
		if Connections.Input.Connected then
			Connections.Input:Disconnect()
		end
	end
	if Connections.Pause then
		if Connections.Pause.Connected then
			Connections.Pause:Disconnect()
		end
	end
	if Connections.MouseMovement then
		if Connections.MouseMovement.Connected then
			Connections.MouseMovement:Disconnect()
		end
	end
	if self.DepthOfField then
		self.DepthOfField:Destroy()
	end
	if self.Keys then
		self.Keys:Destroy()
	end	
	if self.Model then
		self.Model:Destroy()
	end
	
	-- CLEAR METATABLE:
	setmetatable(ExamineService, nil)
	
	-- SHOW CORE:
	StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.All, true)
	
	-- ENABLE EXAMINATION:
	Connections.IsExamining = false
end

-- FUNCTIONS
function ExamineService.SetCollision(model)
	for _, obj in pairs(model:GetDescendants()) do
		if obj:IsA("BasePart") then
			obj.CanCollide = false
			obj.CastShadow = false
		end
	end
end

function ExamineService.GetDirectionBasedOnDifference(Diff, Axis, Ratio)
	if Diff < 0 then
		Axis = Axis - 1 * Ratio
	elseif Diff > 0 then
		Axis = Axis - -1 * Ratio
	end
	return Axis
end

function ExamineService.SetIdle(state)
	if state then
		Player.CameraMinZoomDistance = 32
		Player.CameraMaxZoomDistance = 32
	else
		Player.CameraMinZoomDistance = 0.5
		Player.CameraMaxZoomDistance = 64
	end
end

return ExamineService

Preview:

https://gyazo.com/555333ce0a8196eb9193fb94f8f50fa7


What is this?

This is a module that will allow you to (Examine / Inspect / Look) at an object in front of your screen with control from your mouse.

Why did you make this?

I haven’t seen anything open-sourced like this, and I did this to get a better understanding of OOP and Metatables. This might need some tweaks & updates considering I’ve never used metatables, nor dealt with OOP.

How do I use this?

First of all, the object you want to use must have a set PrimaryPart. There are two primary functions for this.

local Object = ExamineService.new(model, speed, x, y) -- Creates the object with custom settings.
ExamineService:Render(Object) -- Shows it visually.

It’s best to keep the speed (ratio) at around 0.0325 for easy control.

:lock: This must only be used on the Client.

When will you update this?

I’ll try to update it in my spare time or if I get a suggestion on what could be improved, considering I have time. It would be interesting to add a feature where you could move an object with acceleration and to utilize TweenService. That will probably be the main focus if I update this.

Do I have to give credit?

Short answer, no. This was made for fun. I wouldn’t say no to be credited, but as said. It is not required.


Poll:

Is this useful for you?
  • Yes
  • Maybe
  • No

0 voters


Notice:

:warning: Please let me know if something is broken and a fix should be expected asap.

41 Likes

This is pretty cool and, your code is really clean, I’m sure this will be really useful. :+1:

2 Likes

Can you make a MainModule version so it’ll automatically update?

This’d be pretty useful when I’m making games that might use this, although I can’t right now as I’m making a VR game instead.

1 Like

I’m little confused as to what this table on line 22 is for. No, i mean why does __index exist? It is not metatable at all.

-- CLASS
local Connections = {}
Connections.__index = Connections

just my findings…

1 Like

Thanks for letting me know, I just fixed it.

1 Like

I’ll definitely do that when this becomes more advanced.

Very cool and interesting module, I probably use it. But there are a couple of questions:

Will there be a sway effect? To make the model move and stop smoothly
And if it can be done at all, will it be possible to rotate the model in the viewport frame?

Useful module, thanks. I’m a pretty lazy scripter so I needed something quick that works. This does everything intended except for mobile support.

To add mobile support:

  1. Add a new Activated connection under Title2 creation (somewhere around line 150)
	local Title2 = Title:Clone()
	Title2.Text = "Cancel"
	Title2.Parent = MouseButton2
	--new connection
	Connections.M2 = MouseButton2.Activated:Connect(function()
		ExamineService:Disconnect()
	end)
  1. Add Touch events in the Mouse Movement section
local lastX, lastY = 0, 0
	Connections.Input = UserInputService.InputBegan:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			Connections.MouseMovement = Mouse.Move:Connect(function()
				diffX = (Mouse.X - lastX)
				diffY = (Mouse.Y - lastY)
				
				X = ExamineService.GetDirectionBasedOnDifference(diffX, X, Ratio)
				Y = ExamineService.GetDirectionBasedOnDifference(diffY, Y, Ratio)
				
				lastX = Mouse.X
				lastY = Mouse.Y
			end)
-- new touch detections for mobile support
		elseif input.UserInputType == Enum.UserInputType.Touch then
			local function onTouchMoved(touch, gameProcessedEvent)
				local oldPosition = touch.Position - touch.Delta
				--print("Touch moved from " .. tostring(oldPosition) .. "to " .. tostring(touch.Position))
				
				diffX = (touch.Position.X - lastX)
				diffY = (touch.Position.Y - lastY)
				
				X = ExamineService.GetDirectionBasedOnDifference(diffX, X, Ratio * 2.5)
				Y = ExamineService.GetDirectionBasedOnDifference(diffY, Y, Ratio * 2.5)
				
				lastX = touch.Position.X
				lastY = touch.Position.Y
			end

			Connections.TouchMovement = UserInputService.TouchMoved:Connect(onTouchMoved)
--end mobile support add
		elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
			ExamineService:Disconnect()
		end
	end)

The camera doesn’t get locked, so you might want to add code to do so. I haven’t played with that part yet.

3 Likes

Can you create a model that operates based on proximity prompts, similar to those in real games? I struggle with scripting, and there always seems to be an issue whenever I try.