Displaying Viewport Models Perfectly

What does the code do and what are you not satisfied with?
It creates Viewport Frames that display an item’s model and the camera is positioned through premade constants based on what type of item it is, and it does the job, but the constants have to be set manually for every type of item

What potential improvements have you considered?
Possibly some sort of automation, but I can’t think of a method to do this that would display the item models perfectly

How (specifically) do you want to improve the code?
Either through automation, or some sort of improvement to what I’m currently doing that will make the constants easier to manually create

Viewport Constants Module
return {
	Sword = {
		frameFov = 2,
		frameDistance = 125,
		rotationOffset = CFrame.Angles(0, 0, 0),
		frameCameraAngle = CFrame.Angles(0, 0, math.rad(45))
	},
	Armor = {
		frameFov = 2,
		frameDistance = 175,
		rotationOffset = CFrame.Angles(0, math.rad(90), 0),
		frameCameraAngle = CFrame.Angles(0, 0, 0)
	},
	Scythe = {
		frameFov = 2,
		frameDistance = 200,
		rotationOffset = CFrame.Angles(0, math.rad(90), math.rad(90)),
		frameCameraAngle = CFrame.Angles(0, 0, 0)
	},
ItemList Module
-- SERVICES
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

-- MODULES
local modules = ReplicatedStorage:WaitForChild("Modules")

-- VARIABLES
local itemModels = ReplicatedStorage:WaitForChild("ItemModels")

local ItemList = {
	Sword_Of_Blue_Epicness = {
		type = "Weapon",
		modelViewportType = "Sword",
		model = itemModels:WaitForChild("SwordOfBlueEpicness"),
	},
	Knight_Armor = {
		type = "Armor",
		modelViewportType = "Armor",
		model = itemModels:WaitForChild("Knight Armor"),
	},
	Purple_Scythe = {
		type = "Weapon",
		modelViewportType = "Scythe",
		model = itemModels:WaitForChild("Scythe"),
	}
}


return ItemList
Function that handles the viewportframes
local function onItemUpdated(GUID, item)
	local itemFrame = itemFrames[GUID]
	if not itemFrame and item then
		itemFrame = itemFrameTemplate:Clone()
		itemFrame.Name = GUID
		itemFrame.Parent = backPackScrollingFrame
		itemFrames[GUID] = itemFrame
		
		local viewPort = itemFrame.ViewportFrame
		local itemInfo = ItemList[item.id]
		local infoModel = itemInfo.model
		
		local frameConstants = ViewportConstants[itemInfo.modelViewportType]
		local frameFov = frameConstants.frameFov
		local frameDistance = frameConstants.frameDistance
		local rotationOffset = frameConstants.rotationOffset
		local frameCameraAngle = frameConstants.frameCameraAngle
		
		local model = infoModel:Clone()
		model.Parent = viewPort
		local modelCF = model:GetBoundingBox()
		local modelPos = modelCF.Position
		local offsetModelCF = modelCF * rotationOffset
			
		local cameraCF = CFrame.new((offsetModelCF * CFrame.new(frameDistance, 0, 0).Position), modelPos) * frameCameraAngle
			
		local camera = Instance.new("Camera")
		camera.CFrame = cameraCF
		camera.Focus = cameraCF
		camera.FieldOfView = frameFov
		camera.Parent = viewPort
			
		viewPort.CurrentCamera = camera
		updateUISizes()
	elseif itemFrame and not item then
		itemFrame:Destroy()
	end
end

UI

2 Likes

Just curious because I want to know more about this stuff. Why are you using view ports rather than just taking an image or a render of the object and using that instead? Are they animated or something?

Images need to be created for every single item and making small changes isn’t possible without redoing the images

With viewports I just use the same viewport constants based on the item, so I could make the scythe constants once and then use them for every scythe

1 Like

You could perhaps use Model:GetBoundingBox() to automate the creation of Camera CFrames.

You could also use something like how ThumbnailCameras are generally created. If you have a Camera named ThumbnailCamera in a model, it will control the thumbnail of that model. The way they are commonly created is by orienting the camera in front of said model, then copying the workspace Camera and renaming the copy.

What you could is perhaps put Cameras in each of your models. (Perhaps keep their naming consistent to ThumbnailCamera so if you make, for example, packages out of them the packages show the
thumbnail as it appears in game) Then, you can have a server script place CFrameValues, or, just keep track of the camera CFrames on the server, and your thumbnail code could use the stored value to create new cameras.

(Also, if you’re interested)

Feature requests to make Camera instances replicate to the client

For the exact reason above I really would like Cameras to replicate to the client so that its not required to make a bunch of code for ViewportFrame cameras (and so Cameras can even be used at all in Team Create)… I made this feature request which would make it possible for Cameras to replicate to the client, and additionally, I link all of these feature requests/bug reports which share this:

Camera Instances don't replicate in Team Create
Opening Teamcreate Deletes Camera in Viewport Frames
Replicate Camera instances to clients
Viewportframe's Camera doesn't save in Team Create

1 Like

So the way I tackle this is with good old object oriented programming:
https://www.lua.org/pil/16.html

In my case, I have a carousel of animated NPC characters that I want to scroll through with my GUI. Each character is an instance (a concrete object) of my own custom “Character” class, which has custom properties that I can set in my .new function. All I have to do it give my .new function a model and it spits out a custom object/instance (in this case, myCharacter is that object):

myCharacter = Character.new(model)

I pass in an NPC of the ClassName model and pass it as the model argument. myCharacter returns my own custom class with a bunch of custom properties I set myself in my .new function:

-- These two lines are just standard for object oriented programming (OOP)
-- You can learn about what these do in Lua docs. For now just know it sets up
-- our Character class.
local Character = {}
Character.__index = Character

function Character.new(model)
    -- Standard metatable setup so we can reference "self"
    local self = {}
    setmetatable(self, Character)

    --Definitions
    self.Model = model
    self.Order = tonumber(model.Order.Value)
    self.Name = model.Name
    self.RootPart = model:FindFirstChild("HumanoidRootPart")

    --Automagic Positioning!!
    self.RootPart.Position = Vector3.new((self.Order-1) * 10, 0, 0) -- Sets the position based on the order from an IntValue
    self.RootPart.Orientation = Vector3.new(0,180,0) -- Rotates NPC to camera
    self.CameraCFrame = CFrame.new(self.RootPart.Position + Vector3.new(0,0,17)) -- Sets a standard position for the camera, but you can easily put a CFrameValue in the NPC and reference to that instead

    --Animation stuff deleted for this example
    return self
end

If I were you, I would make an Item class for your UI with each item in the backpack having a custom property for the Camera CFrame of the viewport camera if that makes any sense.

local Item = {}
Item.__index = Item

function Item.new(tool)
	local self = {}
	setmetatable(self, Item)
	
	self.tool = tool
	self.Name = tool.Name
	
	self.GUI = Instance.new("ScreenGui")
	self.ViewportFrame = Instance.new("ViewportFrame")
	
	self.ViewPortFrame.Position = UDim2.new(0.5,0,0.5,0) -- Move as you wish or have it automatically position itself!
	self.ViewportFrame.Parent = self.GUI
	self.GUI.Parent = game.Players.LocalPlayer.PlayerGui

	-- Camera Setup
	self.ViewportCamera = Instance.new("Camera")
    self.ViewportCamera.FieldOfView = 25
	self.ViewportCamera.CFrame = CFrame.new(tool.Handle.Position + Vector3.new(0,0,10))
	self.ViewPortFrame.CurrentCamera = self.ViewportCamera
	
	return self
end

local myNewItemBox = Item.new(tool_object_here)

Use this as guidance, as I haven’t tested this code for bugs.

1 Like