Custom Inventory System: Full tutorial + Model

Hey everyone! :wave:

This is going to be my first community tutorial/resource guide to the public. I wanted to pick something in the middle grounds. Something that isn’t extremely basic, and something that isn’t over the top, especially since it’s my first time posting a tutorial like this. :sweat_smile:

That’s why I have chosen to put out to the public a fully functional advanced inventory system, as it’s something that I think not a ton has been covered about, especially with Roblox’s default inventory system already in place, and there being no way to customize that.

One more thing before I start the tutorial. I want to make it clear that I intend this to be a learning tool, and not just a mindless copy and paste where you don’t know what’s going on. I also want to make it clear that this tutorial is directed more towards intermediate scripters, or people who don’t quite understand how to do what this post is about.

:scroll: [If you’re stuck, I’ve provided a link to the the entire system/model so you can take a look yourself (down below)]

Alright, enough of my introduction. Let’s get into it!


First things first, open up Studio, and insert a ScreenGui asset into the StarterGui.

image

  • This will allow us to have a custom inventory GUI.
  • This will also be the holder for the system (everything related should go inside here).

Next, we need to insert a Frame asset into the ScreenGui, and resize and center it. You should also insert a UIAspectRatioConstraint into the frame, and set the aspect ratio so that the frame is elongated on the X (horizontal) axis.

Note : you may want to resize the frame on the Scale factor, so it fits nicely on all devices. Make sure to also set the AnchorPoint for the frame to be 0.5, 0.5, and then drag it back to where you need it.

  • This is going to be the hotbar for the inventory

Now we need to have “Slots” for the hotbar. We can do this by adding a UIListLayout set on the horizontal axis. Then we can add frames for however many slots you want.

This is what the system should look like so far:
image

  • You can customize it however you like, as long as it follows the basics of the system.

Next we need to create the “Bag”, or in other words, the whole inventory frame that stores your items (if they’re not in the hotbar). We can do this by creating a box frame, just like the hotbar frame, but this time have the UIAspectRatioConstraint’s AspectRatio set to 1.5

image

  • Again, you can customize it how you like as long as it follows the basics just like I said before.

We can then add in a ScrollingFrame to the bag frame, and a UIGridLayout to the ScrollingFrame and create a seperate frame identical to one of the slot frames (without the slotNum text). This will be our ItemTemplate frame, which we can duplicate and place inside the bag frame whenever a new item gets added to our inventory.

Note : It is important that the ScrollingFrame is inside of the bag frame, and set to the size of (1, 0, 1, 0) so that it is the full size of the bag frame. And the UIGridLayout goes inside the ScrollingFrame. The ItemTemplate frame must be outside of the ScrollingFrame, placed inside of the bag frame itself. (Also don’t forget to change the visibility to false)

Alright! That should be it for the base GUI setup. Now for fun part, scripting! However before we begin, here is what the entire system should look like now:

The bag frame should have its visibility set to false


To start, we can insert a LocalScript into the GUI, and call it whatever you want. We also need to add a ModuleScript inside the LocalScript, and call it whatever you want (keep in mind it will be referenced within the LocalScript)

image


First, let’s define some variables, beginning with important Services that we’re going to use, all the way down to the GUI itself and the enum table:

----[[VARIABLES]]----
local PLRS = game:GetService("Players") -- the players service
local UIS = game:GetService("UserInputService") -- the user input service, used for detecting inputs from the player/client
local SG = game:GetService("StarterGui")

local inventory_module = require(script:WaitForChild("INVENTORY_TASKS")) -- requring the module script

local player = PLRS.LocalPlayer -- the local player
local char = player.Character -- the player's character
local hum = char:WaitForChild("Humanoid") -- the character's humanoid
local backpack = player.Backpack -- the player's backpack (used to store all tools by default)

local gui = script.Parent -- the gui
local bag = gui:WaitForChild("BAG") -- the bag/inventory frame
local hotbar = gui:WaitForChild("HOTBAR") -- the hotbar frame

local itemTemplate = bag:WaitForChild("ItemTemplate")
local slotDragger = gui:WaitForChild("SLOT_DRAGGER")

local justEquipped = false
local currentFrameBeingHoveredOn = nil
local currentItemSelected = nil
local isDraggingItem = false
local currentMousePos = nil

-- a table used to convert all the slots to enums (this will be helpful when detecting player inputs)
local slotsToEnum = {
	[1] = Enum.KeyCode.One,
	[2] = Enum.KeyCode.Two,
	[3] = Enum.KeyCode.Three,
	[4] = Enum.KeyCode.Four,
	[5] = Enum.KeyCode.Five,
	[6] = Enum.KeyCode.Six,
}

SG:SetCoreGuiEnabled(Enum.CoreGuiType.Backpack, false) -- disabling the default roblox backpack

Once we have that down, we can start on the basic functions:

----[[FUNCTIONS]]----
local function toggleBag(toggle)
	if toggle == true then
		bag.Visible = true
	elseif toggle == false then
		bag.Visible = false
	end
end

local function inputBegan(input, processed)
	if justEquipped == true then
		justEquipped = false
	end

	for _, item in pairs(backpack:GetChildren()) do
		if item:IsA("Tool") then
			if item.slotIn.Value ~= 0 then
				if input.KeyCode == slotsToEnum[item.slotIn.Value] then
					justEquipped = true
					inventory_module.ItemUnequip(hum, hotbar)
					inventory_module.ItemEquip(item, hum, hotbar)
				end
			end
		end
	end

	if justEquipped == false then
		for _, item in pairs(char:GetChildren()) do
			if item:IsA("Tool") then
				if item.slotIn.Value ~= 0 then
					if input.KeyCode == slotsToEnum[item.slotIn.Value] then
						inventory_module.ItemUnequip(hum, hotbar)
					end
				end
			end
		end
	end

	if input.KeyCode == Enum.KeyCode.G and not processed then
		if bag.Visible == true then
			toggleBag(false)
		elseif bag.Visible == false then
			toggleBag(true)
		end
	elseif input.UserInputType == Enum.UserInputType.MouseButton1 then
		isDraggingItem = true
		inventory_module.MouseDown(currentMousePos, slotDragger, backpack, bag, hotbar, itemTemplate)
	end
end

local function inputEnded(input, processed)
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		isDraggingItem = false
		inventory_module.MouseUp(slotDragger, hotbar, bag, backpack, itemTemplate)
	end
end

bag.MouseEnter:Connect(function()
	inventory_module.HoverFrameChange(true, bag)
end)

bag.MouseLeave:Connect(function()
	if isDraggingItem == false then
		inventory_module.HoverFrameChange(false)
	end
end)

for _, slot in pairs(hotbar:GetChildren()) do
	if slot:IsA("Frame") then
		slot.MouseEnter:Connect(function()
			inventory_module.HoverFrameChange(true, slot)
		end)

		slot.MouseLeave:Connect(function()
			if isDraggingItem == false then
				inventory_module.HoverFrameChange(false)
			end
		end)
	end
end

backpack.ChildAdded:Connect(function(child)
	if child:IsA("Tool") then
		inventory_module.BagLoad(bag, backpack, itemTemplate)
	end
end)

backpack.ChildRemoved:Connect(function(child)
	if child:IsA("Tool") then
		inventory_module.BagLoad(bag, backpack, itemTemplate)
	end
end)

UIS.InputBegan:Connect(inputBegan)
UIS.InputEnded:Connect(inputEnded)
inventory_module.BagLoad(bag, backpack, itemTemplate)
inventory_module.HotbarLoad(hotbar, backpack)

And here is the ModuleScript as well:

local module = {}

local UIS = game:GetService("UserInputService")

local slotsToNums = {
	["SLOT1"] = 1,
	["SLOT2"] = 2,
	["SLOT3"] = 3,
	["SLOT4"] = 4,
	["SLOT5"] = 5
}

currentFrameBeingHoveredOn = nil
currentItemSelected = nil
isDraggingItem = false

function module.HoverFrameChange(toggle, frame)
	if toggle == true then
		currentFrameBeingHoveredOn = frame
	else
		currentFrameBeingHoveredOn = nil
	end
end

function module.BagLoad(bag, backpack, itemTemplate)
	for _, frame in pairs(bag.ScrollingFrame:GetChildren()) do
		if frame:IsA("Frame") then
			frame.Visible = false
		end
	end

	for _, item in pairs(backpack:GetChildren()) do
		if item:IsA("Tool") and item.slotIn.Value == 0 then
			if not bag.ScrollingFrame:FindFirstChild(item.Name) then
				local newItemFrame = itemTemplate:Clone()
				newItemFrame.Name = item.Name
				newItemFrame.itemImage.Image = item.TextureId
				newItemFrame.Visible = true
				newItemFrame.Parent = bag.ScrollingFrame

				newItemFrame.MouseEnter:Connect(function()
					module.HoverFrameChange(true, newItemFrame)
				end)

				newItemFrame.MouseLeave:Connect(function()
					if isDraggingItem == false then
						module.HoverFrameChange(false)
					end
				end)
			elseif bag.ScrollingFrame:FindFirstChild(item.Name) then
				bag.ScrollingFrame:FindFirstChild(item.Name).Visible = true

				bag.ScrollingFrame:FindFirstChild(item.Name).MouseEnter:Connect(function()
					module.HoverFrameChange(true, bag.ScrollingFrame:FindFirstChild(item.Name))
				end)

				bag.ScrollingFrame:FindFirstChild(item.Name).MouseLeave:Connect(function()
					if isDraggingItem == false then
						module.HoverFrameChange(false)
					end
				end)
			end
		end
	end
end

function module.HotbarLoad(hotbar, backpack)
	for _, item in pairs(backpack:GetChildren()) do
		if item:IsA("Tool") and item.slotIn.Value ~= 0 then
			hotbar:FindFirstChild("SLOT"..tostring(item.slotIn.Value)).itemImage.Image = item.TextureId
		end
	end
end

function module.MouseDown(currentMousePos, slotDragger, backpack, bag, hotbar, itemTemplate)
	if isDraggingItem == false and currentFrameBeingHoveredOn then
		if currentFrameBeingHoveredOn:IsDescendantOf(bag) == true then
			isDraggingItem = true
			slotDragger.itemImage.Image = currentFrameBeingHoveredOn.itemImage.Image
			slotDragger.Visible = true
			currentFrameBeingHoveredOn.Visible = false

			for _, item in pairs(backpack:GetChildren()) do
				if item:IsA("Tool") then
					if item.Name == currentFrameBeingHoveredOn.Name then
						currentItemSelected = item
					end
				end
			end

			print(currentFrameBeingHoveredOn.Name)

			while isDraggingItem == true do
				currentMousePos = UIS:GetMouseLocation()
				slotDragger.Position = UDim2.fromOffset(currentMousePos.X, currentMousePos.Y)
				task.wait()
			end
			module.BagLoad(bag, backpack, itemTemplate)
		elseif currentFrameBeingHoveredOn:IsDescendantOf(hotbar) == true then
			for _, item in pairs(backpack:GetChildren()) do
				if item:IsA("Tool") then
					if item.slotIn.Value == slotsToNums[currentFrameBeingHoveredOn.Name] then
						currentItemSelected = item
						break
					else
						currentItemSelected = nil
					end
				end
			end

			if currentItemSelected then
				isDraggingItem = true
				slotDragger.itemImage.Image = currentFrameBeingHoveredOn.itemImage.Image
				slotDragger.Visible = true
				currentFrameBeingHoveredOn.itemImage.Image = ""

				while isDraggingItem == true do
					currentMousePos = UIS:GetMouseLocation()
					slotDragger.Position = UDim2.fromOffset(currentMousePos.X, currentMousePos.Y)
					task.wait()
				end
			end
		end
	end
end

function module.MouseUp(slotDragger, hotbar, bag, backpack, itemTemplate)
	isDraggingItem = false

	if currentItemSelected and currentFrameBeingHoveredOn then
		if currentFrameBeingHoveredOn:IsDescendantOf(hotbar) == true then
			for _, otherItem in pairs(backpack:GetChildren()) do
				if otherItem:IsA("Tool") and otherItem ~= currentItemSelected and otherItem.slotIn.Value == slotsToNums[currentFrameBeingHoveredOn.Name] then
					if currentItemSelected.slotIn.Value == 0 and otherItem.slotIn.Value ~= 0 then
						hotbar:FindFirstChild("SLOT"..tostring(otherItem.slotIn.Value)).itemImage.Image = ""
					end
					otherItem.slotIn.Value = currentItemSelected.slotIn.Value
				end
			end
			currentItemSelected.slotIn.Value = slotsToNums[currentFrameBeingHoveredOn.Name]
		elseif currentFrameBeingHoveredOn == bag then
			currentItemSelected.slotIn.Value = 0
		end
	end

	module.BagLoad(bag, backpack, itemTemplate)
	module.HotbarLoad(hotbar, backpack)

	slotDragger.Visible = false
	currentItemSelected = nil
end

function module.ItemEquip(item, hum, hotbar)
	hum:EquipTool(item)
	hotbar:FindFirstChild("SLOT"..tostring(item.slotIn.Value)).UIStroke.Color = Color3.fromRGB(255, 255, 255)
end

function module.ItemUnequip(hum, hotbar)
	hum:UnequipTools()

	for _, slot in pairs(hotbar:GetChildren()) do
		if slot:IsA("Frame") then
			slot.UIStroke.Color = Color3.fromRGB(20, 20, 20)
		end
	end
end

return module

If you’re wanting to add items for the inventory, you’ll need to add an IntValue, called “slotIn”

And it’s as simple (well, not really, but you get the point) as that!

I hope you learned something from this, and if not, I hope I helped you if you were in need of a custom inventory system. It’s fairly basic, but it gets the job done, and if you want, you can expand on it as much as you like.

If you have any questions, please by all means, feel free to ask me! :smile:

Here is the link to the system/model as well as a showcase of the final product:

Custom Inventory System Model

15 Likes

this looks good but can you tell me how I can connect this with my trading system so if I get a tool from a trade it appears in the inventory and when I die it saves?

1 Like

how could i make so there’s only 3 slots and when they are full, you can’t pick more tools?

because the OP made it pretty modular, you can just change the slots to nums table to only have 3 slots, and if a new tool is added to the backpack, check if there are more than 3 tools in backpack & the character, and discard the overflow

2 Likes

Just throwing this out here incase anyone wanted to add some clean animations to the GUI/clean up the gui a little.

Go through the code if you do not trust me it is up to you.

This version does use an external framework called Fusion for handling animations etc.

Download file:
inv.rbxm (61.7 KB)

Preview (Fixed the issue where the numbers still show):
robloxapp-20240509-1334368.wmv (208.2 KB)

1 Like

Hi, thanks for sharing.

What devices does this support?

i believe it depends on what keys are set in the slotsToEnum table. If you set them to be gamepad keys, then it should support console, but if you make it keyboard keys, then it would be pc supported. Since this only uses UserInputService, it can’t really support mobile players; unless you modify it. though I think you could just make the slots clickable to support mobile