Prerequesites
- Lua knowledge.
- An item system. Each item must have two functions:
- ITEM:GetIcon() -- Must return a Material() object.
- ITEM:GetSize() -- Must return a width and height in cells.
A system like this can be found at the Object Oriented Lua tutorial.
Note that this tutorial only discusses clientside handling of an inventory.
I'm too lazy for serverside networking...
Let's Get Started
So every item has a position in the backpack, and every item has a size in that backpack.
To start, you'll need a table.
We'll call it ply.Inv
ply.Inv will contain both the backpack AND the information about their inventory.
Let's create a few subtables and values in ply.Inv
ply.Inv.Backpack = {}
ply.Inv.Equipped = {}
ply.Inv.Weight = 0
(You can create whatever values you will need for your system. Weight might not be necessary, etc.)
Next, we're going to make the backpack table take the form of a 2-Dimensional table, with rows being the first key and each row containing columns. This would look something like:
for i
=1,
8 do
ply.Inv.Backpack
[i
] = {}
end
for k,v
in pairs(ply.Inv.Backpack)
do
for i
=1,
4 do
ply.Inv.Backpack
[k
][i
] = false
end
end
This will create an 8-wide, 4-tall 2D table.
So to get the item at coordinate 2,4 you would do ply.Inv.Backpack[2][4]
Simple enough.
But it's always simpler with a convenience function:
local plymeta
= FindMetaTable(
"Player")
function plymeta:
GetInvItem(x,y)
return self.Inv.Backpack
[x
][y
]
end
Ok, now to explain the basis of the system.
The idea is this:
We're going to create a new vgui element. This element will be a single square on the grid. With this we can have them shine and whatnot. When we finish designing this element we can replace the false's we set earlier.
When we put an item in a place on the grid, we get the size of the item (width and height). We then tell all the squares in that area that there is now an item on them. We aren't going to root the item to all the squares; just to the one which is in the top left of the item. The one which tells all the other squares. This will be the item's parent square.
So let's make these grid elements.
local PANEL
= {}
AccessorFunc(PANEL,
"m_ItemPanel",
"ItemPanel")
AccessorFunc(PANEL,
"m_Color",
"Color")
function PANEL:
Init()
self.m_Coords
= {x
=0,y
=0}
self:
SetSize(
30,
30)
self:
SetColor(
Color(
200,
200,
200))
self:
SetItemPanel(
false)
self:
Receiver(
"invitem",
function(pnl, item, drop, i, x, y)
if drop
then
item
= item
[1]
local x1,y1
= pnl:
GetPos()
local x2,y2
= item:
GetPos()
if math.
Dist(x1,y1,x2,y2)
<= 30 then
if not pnl:
GetItemPanel()
then
local itm
= item:
GetItem()
local x,y
= pnl:
GetCoords()
local itmw, itmh
= itm:
GetSize()
local full
= false
for i1
=x, (x
+itmw)
-1 do
if full
then break end
for i2
=y, (y
+itmh)
-1 do
if LocalPlayer():
GetInvItem(i1,i2):
GetItemPanel()
then
full
= true
break
end
end
end
if not full
then
for i1
=x, (x
+itmw)
-1 do
for i2
=y, (y
+itmh)
-1 do
LocalPlayer():
GetInvItem(i1,i2):
SetItemPanel(item)
end
end
item:
SetRoot(pnl)
item:
SetPos(
pnl:
GetPos())
end
end
end
else
end
end,
{})
end
function PANEL:
SetCoords(x,y)
self.m_Coords.x
= x
self.m_Coords.y
= y
end
function PANEL:
GetCoords()
return self.m_Coords.x, self.m_Coords.y
end
local col
function PANEL:
Paint(w,h)
draw.
NoTexture()
col
= self:
GetColor()
surface.
SetDrawColor(col.r,col.g,col.b,
255)
surface.
DrawRect(
0,
0,w
-2,h
-2)
surface.
SetDrawColor(
70,
70,
70,
255)
surface.
DrawRect(w
-2,
0,h,
2)
surface.
DrawRect(
0,h
-2,
2,w)
end
vgui.
Register(
"InvSlot", PANEL,
"DPanel")
Done. Now we add the whole functionality.
We have to assume that the item system involves unique items, not just arbitrary class id's, to identify each item.
The best way to do this would be to link each item with an entity index. This would work if the items are all entities at some point.
When you pick an item up it removes the entity version (saving its ent index) and when you drop it, it creates a new entity of the same kind with the same info.
This is all just concept. I'm not writing an item system in this tutorial.
So we would start by creating a slot panel for each inventory slot.
for k,v
in pairs(ply.Inv.Backpack)
do
for i
=1,
4 do
ply.Inv.Backpack
[k
][i
] = vgui.
Create(
"InvSlot")
ply.Inv.Backpack
[k
][i
]:
SetPos(k
*30,i
*30)
ply.Inv.Backpack
[k
][i
]:
SetCoords(k,i)
end
end
Icons. Icon's everywhere.
Next, let's make another vgui element. This will represent
an item in your backpack.
local PANEL
= {}
AccessorFunc(PANEL,
"m_Item",
"Item")
AccessorFunc(PANEL,
"m_Root",
"Root")
function PANEL:
Init()
self:
SetSize(
30,
30)
self:
SetItem(
false)
self:
SetColor(
Color(
100,
100,
100))
self:
SetDroppable(
"invitem")
end
function PANEL:
PaintOver(w,h)
draw.
NoTexture()
if self:
GetItem()
then
surface.
SetMaterial(
self:
GetItem():
GetIcon())
surface.
DrawTexturedRect(
0,
0,w,h)
end
end
local col
function PANEL:
Paint(w,h)
draw.
NoTexture()
col
= self:
GetColor()
surface.
SetDrawColor(col.r,col.g,col.b,
180)
surface.
DrawRect(
0,
0,w,h)
end
vgui.
Register(
"InvItem", PANEL,
"DPanel")
Ok, so now we have the actual items.
Remember we're only discussing the basics of a backpack system. You can add more if you want later.
Next, let's create a function which finds out whether there is room in the backpack for a given item.
If there is room it will return the panel to root to. If not then it will return false.
function IsRoomFor(item)
for k,v
in ipairs(
LocalPlayer().Inv.Backpack)
do
for k2, pnl
in ipairs(
LocalPlayer().Inv.Backpack
[k
])
do
if not pnl:
GetItemPanel()
then
local x,y
= pnl:
GetCoords()
local itmw, itmh
= item:
GetSize()
local full
= false
for i1
=x, (x
+itmw)
-1 do
if full
then break end
for i2
=y, (y
+itmh)
-1 do
if LocalPlayer():
GetInvItem(i1,i2):
GetItemPanel()
then
full
= true
break
end
end
end
if full
then
return pnl
end
end
end
end
return false
end
Most of this code is copied from the drag-n-drop functionality from earlier.
Next, let's make items get picked up.
function PickupItem(item)
local place
= IsRoomFor(item)
if place
then
local itm
= vgui.
Create(
"InvItem")
itm:
SetItem(item)
itm:
SetRoot(place)
itm:
SetPos(
place:
GetPos())
local x,y
= place:
GetCoords()
local itmw, itmh
= item:
GetSize()
for i1
=x, (x
+itmw)
-1 do
for i2
=y, (y
+itmh)
-1 do
LocalPlayer():
GetInvItem(i1,i2):
SetItemPanel(itm)
end
end
return true
else
return false
end
end Final Product
local ply
= LocalPlayer()
ply.Inv
= {}
ply.Inv.Backpack
= {}
ply.Inv.Equipped
= {}
ply.Inv.Weight
= 0
for i
=1,
8 do
ply.Inv.Backpack
[i
] = {}
end
for k,v
in pairs(ply.Inv.Backpack)
do
for i
=1,
4 do
ply.Inv.Backpack
[k
][i
] = false
end
end
local plymeta
= FindMetaTable(
"Player")
function plymeta:
GetInvItem(x,y)
return self.Inv.Backpack
[x
][y
]
end
local PANEL
= {}
AccessorFunc(PANEL,
"m_ItemPanel",
"ItemPanel")
AccessorFunc(PANEL,
"m_Color",
"Color")
function PANEL:
Init()
self.m_Coords
= {x
=0,y
=0}
self:
SetSize(
30,
30)
self:
SetColor(
Color(
200,
200,
200))
self:
SetItemPanel(
false)
self:
Receiver(
"invitem",
function(pnl, item, drop, i, x, y)
if drop
then
item
= item
[1]
local x1,y1
= pnl:
GetPos()
local x2,y2
= item:
GetPos()
if math.
Dist(x1,y1,x2,y2)
<= 30 then
if not pnl:
GetItemPanel()
then
local itm
= item:
GetItem()
local x,y
= pnl:
GetCoords()
local itmw, itmh
= itm:
GetSize()
local full
= false
for i1
=x, (x
+itmw)
-1 do
if full
then break end
for i2
=y, (y
+itmh)
-1 do
if LocalPlayer():
GetInvItem(i1,i2):
GetItemPanel()
then
full
= true
break
end
end
end
if not full
then
for i1
=x, (x
+itmw)
-1 do
for i2
=y, (y
+itmh)
-1 do
LocalPlayer():
GetInvItem(i1,i2):
SetItemPanel(item)
end
end
item:
SetRoot(pnl)
item:
SetPos(
pnl:
GetPos())
end
end
end
else
end
end,
{})
end
function PANEL:
SetCoords(x,y)
self.m_Coords.x
= x
self.m_Coords.y
= y
end
function PANEL:
GetCoords()
return self.m_Coords.x, self.m_Coords.y
end
local col
function PANEL:
Paint(w,h)
draw.
NoTexture()
col
= self:
GetColor()
surface.
SetDrawColor(col.r,col.g,col.b,
255)
surface.
DrawRect(
0,
0,w
-2,h
-2)
surface.
SetDrawColor(
70,
70,
70,
255)
surface.
DrawRect(w
-2,
0,
2,h)
surface.
DrawRect(
0,h
-2,w,
2)
end
vgui.
Register(
"InvSlot", PANEL,
"DPanel")
local dfram
= vgui.
Create(
"DFrame")
dfram:
SetSize(
ScrW()
/2,
ScrH()
/2)
dfram:
Center()
dfram:
MakePopup()
for k,v
in pairs(ply.Inv.Backpack)
do
for i
=1,
4 do
ply.Inv.Backpack
[k
][i
] = vgui.
Create(
"InvSlot", dfram)
ply.Inv.Backpack
[k
][i
]:
SetPos((k
*30)
+100,(i
*30)
+100)
ply.Inv.Backpack
[k
][i
]:
SetCoords(k,i)
end
end
PANEL
= {}
AccessorFunc(PANEL,
"m_Item",
"Item")
AccessorFunc(PANEL,
"m_Root",
"Root")
function PANEL:
Init()
self:
SetSize(
30,
30)
self:
SetItem(
false)
self:
SetColor(
Color(
100,
100,
100))
self:
SetDroppable(
"invitem")
end
function PANEL:
PaintOver(w,h)
draw.
NoTexture()
if self:
GetItem()
then
surface.
SetMaterial(
self:
GetItem():
GetIcon())
surface.
DrawTexturedRect(
0,
0,w,h)
end
end
local col
function PANEL:
Paint(w,h)
draw.
NoTexture()
col
= self:
GetColor()
surface.
SetDrawColor(col.r,col.g,col.b,
180)
surface.
DrawRect(
0,
0,w,h)
end
vgui.
Register(
"InvItem", PANEL,
"DPanel")
function IsRoomFor(item)
for k,v
in ipairs(
LocalPlayer().Inv.Backpack)
do
for k2, pnl
in ipairs(
LocalPlayer().Inv.Backpack
[k
])
do
if not pnl:
GetItemPanel()
then
local x,y
= pnl:
GetCoords()
local itmw, itmh
= item:
GetSize()
local full
= false
for i1
=x, (x
+itmw)
-1 do
if full
then break end
for i2
=y, (y
+itmh)
-1 do
if LocalPlayer():
GetInvItem(i1,i2):
GetItemPanel()
then
full
= true
break
end
end
end
if full
then
return pnl
end
end
end
end
return false
end
function PickupItem(item)
local place
= IsRoomFor(item)
if place
then
local itm
= vgui.
Create(
"InvItem")
itm:
SetItem(item)
itm:
SetRoot(place)
itm:
SetPos(
place:
GetPos())
local x,y
= place:
GetCoords()
local itmw, itmh
= item:
GetSize()
for i1
=x, (x
+itmw)
-1 do
for i2
=y, (y
+itmh)
-1 do
LocalPlayer():
GetInvItem(i1,i2):
SetItemPanel(itm)
end
end
return true
else
return false
end
end Test Results