Grid inventory explanation

Hello devform it’s me again. and i am looking for another explanation, i am planning of adding a grid type inventory into my game, but i have no idea how to start.

by grid inventory i mean something like this Grid Type Inventory

i am aware that there are multiple topics on this subject but i never understood them well enough to try and implement a system of my own

4 Likes

There’s a page on the devhub about exactly this:

Hello, I see you have found my post!

Sorry if I took long to reply to your post.

Before I explain everything I just want to tell you that I haven’t optimized this code much at all so sorry if it isn’t the best.

Step 1

Before we start we need to actually generate the grid.

Each grid part looks like this:
image
:green_square:: What you see.
:red_square:: What the actual grid is.

Later we need to define the items, which I did like this:
image

I then wrote a function which generates the grid parts:

local function loadInventoryGrid()
	for gridPartNum = 1, gridPartAmount do
		local gridPartFrame = assets.UI.InvGridPart:Clone()
		gridPartFrame.Size = UDim2.new(0, gridPartSize, 0, gridPartSize)
		gridPartFrame.LayoutOrder = gridPartNum
		gridPartFrame.Name = gridPartNum
		gridPartFrame.Parent = invGrid
	end

	invMain.Size = UDim2.new(0, invGrid.UIGridLayout.AbsoluteContentSize.X, 0, invGrid.UIGridLayout.AbsoluteContentSize.Y)
end

And that is everything you need to generate the grid.

Step 2

The next step is to generate the items.

For this step I wrote another function:

local function loadInventoryItems()
	for _, item in pairs(itemInv:GetChildren()) do
		item.InvPosX.Value = -10000
		item.InvPosY.Value = -10000
	end
	
	sortItems(itemInv:GetChildren())

	for _, item in pairs(itemInv:GetChildren()) do
		local itemFrame = assets.UI.InvItem:Clone()
		itemFrame.Size = UDim2.new(0, item.InvSizeX.Value * gridPartSize, 0, item.InvSizeY.Value * gridPartSize)
		itemFrame.Position = UDim2.new(0, item.InvPosX.Value * 70 - 70, 0, item.InvPosY.Value * 70 - 70)

		--I will go over this later:
		itemFrame.Button.MouseButton1Down:Connect(function()
			currentlyDraggingItemFrame = itemFrame
			currentlyDraggingItem = item

			currentlyDraggingItemFrame.ZIndex = 4
			currentlyDraggingItemFrame.ItemImg.ZIndex = 5
			currentlyDraggingItemFrame.Button.ZIndex = 5

			local mousePos = userInputService:GetMouseLocation() - Vector2.new(0, 36)
			local itemPosOffset = Vector2.new(mousePos.X - currentlyDraggingItemFrame.Position.X.Offset, mousePos.Y - currentlyDraggingItemFrame.Position.Y.Offset)
			currentlyDraggingMouseOffset = itemPosOffset
		end)

		itemFrame.Parent = invMain
	end
end

In this function I have the sortItems function that puts the items into the correct places without overlapping. I also made a UI item template hence the “local itemFrame = assets.UI.InvItem:Clone()” but I don’t really have to go over that since the UI is very simple, it’s just a Frame with an ImageLabel inside.

That looks something like this:

local function sortItems(items)
	table.sort(items, function(a, b) return a.InvSizeX.Value * a.InvSizeY.Value > b.InvSizeX.Value * b.InvSizeY.Value end)

	for _, item in pairs(items) do
		local success, invPosX, invPosY = getNextEmptyDropPos(item)
		if (success) then
			item.InvPosX.Value = invPosX
			item.InvPosY.Value = invPosY
		end
	end
end

And in here I have got even more functions:

local function getNextEmptyDropPos(item)
	for y = 1, invGrid.UIGridLayout.AbsoluteCellCount.Y do
		for x = 1, invGrid.UIGridLayout.AbsoluteCellCount.X do
			if not (x >= invGrid.UIGridLayout.AbsoluteCellCount.X + 1) then
				x = math.clamp(x, 1, invGrid.UIGridLayout.AbsoluteCellCount.X - item.InvSizeX.Value + 1)
			end

			if not (y >= invGrid.UIGridLayout.AbsoluteCellCount.Y + 1) then
				y = math.clamp(y, 1, invGrid.UIGridLayout.AbsoluteCellCount.Y - item.InvSizeY.Value + 1)
			end

			local colliding = isColliding(item, x, y)
			if not (colliding) then
				return true, x, y
			end
		end
	end

	return false, nil, nil
end

And then finally the isColliding and getOccupiedPositions functions:

local function isColliding(colliderItem, posX, posY)
	local occupiedPositions = getOccupiedPositions(itemInv, colliderItem)

	for sizeOffsetX = 0, colliderItem.InvSizeX.Value - 1 do
		for sizeOffsetY = 0, colliderItem.InvSizeY.Value - 1 do
			if (table.find(occupiedPositions, Vector2.new(posX + sizeOffsetX, posY + sizeOffsetY))) then
				return true
			end
		end
	end

	return false
end
local function getOccupiedPositions(inventory, ignoredItem)
	local occupiedPositions = {}
	for _, item in pairs(inventory:GetChildren()) do
		if not (item == ignoredItem) then
			for sizeOffsetX = 0, item.InvSizeX.Value - 1 do
				for sizeOffsetY = 0, item.InvSizeY.Value - 1 do
					table.insert(occupiedPositions, Vector2.new(item.InvPosX.Value + sizeOffsetX, item.InvPosY.Value + sizeOffsetY))
				end
			end
		end
	end
	
	return occupiedPositions
end

The isColliding function will come in handy a lot of the time.

That is everything for generating the items.
Now onto the last step where we will add item interaction!

Step 3

So firstly I added a MouseButton1Down event to the loadInventoryItems function to get the item the player was interacting with:

--// Located in the loadInventoryItems function!
itemFrame.Button.MouseButton1Down:Connect(function()
	currentlyDraggingItemFrame = itemFrame
	currentlyDraggingItem = item

	currentlyDraggingItemFrame.ZIndex = 4
	currentlyDraggingItemFrame.ItemImg.ZIndex = 5
	currentlyDraggingItemFrame.Button.ZIndex = 5

	local mousePos = userInputService:GetMouseLocation() - Vector2.new(0, 36)
	local itemPosOffset = Vector2.new(mousePos.X - currentlyDraggingItemFrame.Position.X.Offset, mousePos.Y - currentlyDraggingItemFrame.Position.Y.Offset)
	currentlyDraggingMouseOffset = itemPosOffset
end)

Next, add two UserInputService events to update the item position:

userInputService.InputChanged:Connect(function(input, gameProcessed)
	if (currentlyDraggingItemFrame) and (input.UserInputType == Enum.UserInputType.MouseMovement) then
		local mousePos = input.Position
		currentlyDraggingItemFrame.Position = UDim2.new(0, mousePos.X - currentlyDraggingMouseOffset.X, 0, mousePos.Y - currentlyDraggingMouseOffset.Y)

		for _, gridPart in pairs(invGrid:GetChildren()) do
			if not (gridPart:IsA("UIGridLayout")) then
				gridPart.Cosmetic.BackgroundColor3 = Color3.fromRGB(20, 20, 20)
			end
		end

		local dropPosX, dropPosY = getDropPos(currentlyDraggingItemFrame.Position.X.Offset, currentlyDraggingItemFrame.Position.Y.Offset, currentlyDraggingItem.InvSizeX.Value, currentlyDraggingItem.InvSizeY.Value)
		local colliding = isColliding(currentlyDraggingItem, dropPosX, dropPosY)
		for sizeOffsetX = 0, currentlyDraggingItem.InvSizeX.Value - 1 do
			for sizeOffsetY = 0, currentlyDraggingItem.InvSizeY.Value - 1 do
				local gridPart = getGridPartFromDropPos(dropPosX + sizeOffsetX, dropPosY + sizeOffsetY)
				if (gridPart) then
					if (colliding) then
						gridPart.Cosmetic.BackgroundColor3 = Color3.new(1, 0.15, 0.15)
					else
						gridPart.Cosmetic.BackgroundColor3 = Color3.new(0.85, 0.85, 0.85)
					end
				end
			end
		end
	end
end)
userInputService.InputEnded:Connect(function(input)
	if (currentlyDraggingItemFrame) and (input.UserInputType == Enum.UserInputType.MouseButton1) then
		local dropPosX, dropPosY = getDropPos(currentlyDraggingItemFrame.Position.X.Offset, currentlyDraggingItemFrame.Position.Y.Offset, currentlyDraggingItem.InvSizeX.Value, currentlyDraggingItem.InvSizeY.Value)
		local colliding = isColliding(currentlyDraggingItem, dropPosX, dropPosY)
		if not (colliding) then
			currentlyDraggingItemFrame.Position = UDim2.new(0, dropPosX * 70 - 70, 0, dropPosY * 70 - 70)

			currentlyDraggingItem.InvPosX.Value = dropPosX
			currentlyDraggingItem.InvPosY.Value = dropPosY
		else
			currentlyDraggingItemFrame.Position = UDim2.new(0, currentlyDraggingItem.InvPosX.Value * 70 - 70, 0, currentlyDraggingItem.InvPosY.Value * 70 - 70)
		end

		currentlyDraggingItemFrame.ZIndex = 2
		currentlyDraggingItemFrame.ItemImg.ZIndex = 3
		currentlyDraggingItemFrame.Button.ZIndex = 3

		currentlyDraggingItem = nil
		currentlyDraggingItemFrame = nil

		for _, gridPart in pairs(invGrid:GetChildren()) do
			if not (gridPart:IsA("UIGridLayout")) then
				gridPart.Cosmetic.BackgroundColor3 = Color3.fromRGB(20, 20, 20)
			end
		end
	end
end)

In the first event we can see the getDropPos function which is used for the drop highlight. The function looks like this:

local function getDropPos(posX, posY, sizeX, sizeY)
	local dropPosX = math.floor(posX / gridPartSize + 1.5)
	local dropPosY = math.floor(posY / gridPartSize + 1.5)

	local dropPosXClamp = math.clamp(dropPosX, 1, invGrid.UIGridLayout.AbsoluteCellCount.X - sizeX + 1)
	local dropPosYClamp = math.clamp(dropPosY, 1, invGrid.UIGridLayout.AbsoluteCellCount.Y - sizeY + 1)

	return dropPosXClamp, dropPosYClamp
end

Pretty easy to make function, just some rounding to make the grid work. We can also see the getGridPartFromDropPos function which as the name suggests gets the gridPart (The grid UI we made earlier in step 1), the function is also a very easy to make function:

local function getGridPartFromDropPos(dropPosX, dropPosY)
	local gridPartPosYSum = (dropPosY - 1) * invGrid.UIGridLayout.AbsoluteCellCount.X
	local gridPart = invGrid:FindFirstChild(dropPosX + gridPartPosYSum)

	return gridPart
end

And that is everything for making a grid type inventory!

Code
local replicatedStorage = game:GetService("ReplicatedStorage")
local userInputService = game:GetService("UserInputService")

local assets = replicatedStorage:WaitForChild("Assets")
local itemInv = replicatedStorage:WaitForChild("Inventory")

local invMain = script.Parent
local invGrid = invMain:WaitForChild("InvGrid")

local gridPartSize = 70
local gridPartAmount = 98
local currentlyDraggingItem = nil
local currentlyDraggingItemFrame = nil
local currentlyDraggingMouseOffset = Vector2.new(0, 0)



local function getGridPartFromDropPos(dropPosX, dropPosY)
	local gridPartPosYSum = (dropPosY - 1) * invGrid.UIGridLayout.AbsoluteCellCount.X
	local gridPart = invGrid:FindFirstChild(dropPosX + gridPartPosYSum)

	return gridPart
end

local function getDropPos(posX, posY, sizeX, sizeY)
	local dropPosX = math.floor(posX / gridPartSize + 1.5)
	local dropPosY = math.floor(posY / gridPartSize + 1.5)

	local dropPosXClamp = math.clamp(dropPosX, 1, invGrid.UIGridLayout.AbsoluteCellCount.X - sizeX + 1)
	local dropPosYClamp = math.clamp(dropPosY, 1, invGrid.UIGridLayout.AbsoluteCellCount.Y - sizeY + 1)

	return dropPosXClamp, dropPosYClamp
end

local function getOccupiedPositions(inventory, ignoredItem)
	local occupiedPositions = {}
	for _, item in pairs(inventory:GetChildren()) do
		if not (item == ignoredItem) then
			for sizeOffsetX = 0, item.InvSizeX.Value - 1 do
				for sizeOffsetY = 0, item.InvSizeY.Value - 1 do
					table.insert(occupiedPositions, Vector2.new(item.InvPosX.Value + sizeOffsetX, item.InvPosY.Value + sizeOffsetY))
				end
			end
		end
	end
	
	return occupiedPositions
end

local function isColliding(colliderItem, posX, posY)
	local occupiedPositions = getOccupiedPositions(itemInv, colliderItem)

	for sizeOffsetX = 0, colliderItem.InvSizeX.Value - 1 do
		for sizeOffsetY = 0, colliderItem.InvSizeY.Value - 1 do
			if (table.find(occupiedPositions, Vector2.new(posX + sizeOffsetX, posY + sizeOffsetY))) then
				return true
			end
		end
	end

	return false
end

local function getNextEmptyDropPos(item)
	for y = 1, invGrid.UIGridLayout.AbsoluteCellCount.Y do
		for x = 1, invGrid.UIGridLayout.AbsoluteCellCount.X do
			if not (x >= invGrid.UIGridLayout.AbsoluteCellCount.X + 1) then
				x = math.clamp(x, 1, invGrid.UIGridLayout.AbsoluteCellCount.X - item.InvSizeX.Value + 1)
			end

			if not (y >= invGrid.UIGridLayout.AbsoluteCellCount.Y + 1) then
				y = math.clamp(y, 1, invGrid.UIGridLayout.AbsoluteCellCount.Y - item.InvSizeY.Value + 1)
			end

			local colliding = isColliding(item, x, y)
			if not (colliding) then
				return true, x, y
			end
		end
	end

	return false, nil, nil
end

local function sortItems(items)
	table.sort(items, function(a, b) return a.InvSizeX.Value * a.InvSizeY.Value > b.InvSizeX.Value * b.InvSizeY.Value end)

	for _, item in pairs(items) do
		local success, invPosX, invPosY = getNextEmptyDropPos(item)
		if (success) then
			item.InvPosX.Value = invPosX
			item.InvPosY.Value = invPosY
		end
	end
end

userInputService.InputChanged:Connect(function(input, gameProcessed)
	if (currentlyDraggingItemFrame) and (input.UserInputType == Enum.UserInputType.MouseMovement) then
		local mousePos = input.Position
		currentlyDraggingItemFrame.Position = UDim2.new(0, mousePos.X - currentlyDraggingMouseOffset.X, 0, mousePos.Y - currentlyDraggingMouseOffset.Y)

		for _, gridPart in pairs(invGrid:GetChildren()) do
			if not (gridPart:IsA("UIGridLayout")) then
				gridPart.Cosmetic.BackgroundColor3 = Color3.fromRGB(20, 20, 20)
			end
		end

		local dropPosX, dropPosY = getDropPos(currentlyDraggingItemFrame.Position.X.Offset, currentlyDraggingItemFrame.Position.Y.Offset, currentlyDraggingItem.InvSizeX.Value, currentlyDraggingItem.InvSizeY.Value)
		local colliding = isColliding(currentlyDraggingItem, dropPosX, dropPosY)
		for sizeOffsetX = 0, currentlyDraggingItem.InvSizeX.Value - 1 do
			for sizeOffsetY = 0, currentlyDraggingItem.InvSizeY.Value - 1 do
				local gridPart = getGridPartFromDropPos(dropPosX + sizeOffsetX, dropPosY + sizeOffsetY)
				if (gridPart) then
					if (colliding) then
						gridPart.Cosmetic.BackgroundColor3 = Color3.new(1, 0.15, 0.15)
					else
						gridPart.Cosmetic.BackgroundColor3 = Color3.new(0.85, 0.85, 0.85)
					end
				end
			end
		end
	end
end)

userInputService.InputEnded:Connect(function(input)
	if (currentlyDraggingItemFrame) and (input.UserInputType == Enum.UserInputType.MouseButton1) then
		local dropPosX, dropPosY = getDropPos(currentlyDraggingItemFrame.Position.X.Offset, currentlyDraggingItemFrame.Position.Y.Offset, currentlyDraggingItem.InvSizeX.Value, currentlyDraggingItem.InvSizeY.Value)
		local colliding = isColliding(currentlyDraggingItem, dropPosX, dropPosY)
		if not (colliding) then
			currentlyDraggingItemFrame.Position = UDim2.new(0, dropPosX * 70 - 70, 0, dropPosY * 70 - 70)

			currentlyDraggingItem.InvPosX.Value = dropPosX
			currentlyDraggingItem.InvPosY.Value = dropPosY
		else
			currentlyDraggingItemFrame.Position = UDim2.new(0, currentlyDraggingItem.InvPosX.Value * 70 - 70, 0, currentlyDraggingItem.InvPosY.Value * 70 - 70)
		end

		currentlyDraggingItemFrame.ZIndex = 2
		currentlyDraggingItemFrame.ItemImg.ZIndex = 3
		currentlyDraggingItemFrame.Button.ZIndex = 3

		currentlyDraggingItem = nil
		currentlyDraggingItemFrame = nil

		for _, gridPart in pairs(invGrid:GetChildren()) do
			if not (gridPart:IsA("UIGridLayout")) then
				gridPart.Cosmetic.BackgroundColor3 = Color3.fromRGB(20, 20, 20)
			end
		end
	end
end)

local function loadInventoryItems()
	for _, item in pairs(itemInv:GetChildren()) do
		item.InvPosX.Value = -10000
		item.InvPosY.Value = -10000
	end
	
	sortItems(itemInv:GetChildren())

	for _, item in pairs(itemInv:GetChildren()) do
		local itemFrame = assets.UI.InvItem:Clone()
		itemFrame.Size = UDim2.new(0, item.InvSizeX.Value * gridPartSize, 0, item.InvSizeY.Value * gridPartSize)
		itemFrame.Position = UDim2.new(0, item.InvPosX.Value * 70 - 70, 0, item.InvPosY.Value * 70 - 70)

		itemFrame.Button.MouseButton1Down:Connect(function()
			currentlyDraggingItemFrame = itemFrame
			currentlyDraggingItem = item

			currentlyDraggingItemFrame.ZIndex = 4
			currentlyDraggingItemFrame.ItemImg.ZIndex = 5
			currentlyDraggingItemFrame.Button.ZIndex = 5

			local mousePos = userInputService:GetMouseLocation() - Vector2.new(0, 36)
			local itemPosOffset = Vector2.new(mousePos.X - currentlyDraggingItemFrame.Position.X.Offset, mousePos.Y - currentlyDraggingItemFrame.Position.Y.Offset)
			currentlyDraggingMouseOffset = itemPosOffset
		end)

		itemFrame.Parent = invMain
	end
end

local function loadInventoryGrid()
	for gridPartNum = 1, gridPartAmount do
		local gridPartFrame = assets.UI.InvGridPart:Clone()
		gridPartFrame.Size = UDim2.new(0, gridPartSize, 0, gridPartSize)
		gridPartFrame.LayoutOrder = gridPartNum
		gridPartFrame.Name = gridPartNum
		gridPartFrame.Parent = invGrid
	end

	invMain.Size = UDim2.new(0, invGrid.UIGridLayout.AbsoluteContentSize.X, 0, invGrid.UIGridLayout.AbsoluteContentSize.Y)
end

loadInventoryGrid()
loadInventoryItems()

--// I didn't go over this but this is just the button in the top left corner that resets all of the items.
script.Parent.Parent.ReloadItems.MouseButton1Click:Connect(function()
	for _, itemFrame in pairs(invMain:GetChildren()) do
		if itemFrame.Name == "InvItem" then
			itemFrame:Destroy()
		end
	end

	loadInventoryItems()
end)

If you got any questions make a reply to my post!

22 Likes

i seem to have missed something up along the way. would it be possible for you to link the rblx file

GridTypeInventory.rbxl (43.4 KB)

Here you go!

9 Likes

i absolutely love you bro thank you some much for all your help

3 Likes

itemFrame.Position = UDim2.new(0, item.InvPosX.Value * 70 - 70, 0, item.InvPosY.Value * 70 - 70) what does 70 - 70 means?

2 Likes