Object Oriented Lua

From Garry's Mod
(Difference between revisions)
Jump to: navigation, search
m (fixed a couple bugs in the code)
m (fixed another bug)
Line 28: Line 28:
 
function Airport(code) --Code is an optional argument.
 
function Airport(code) --Code is an optional argument.
 
 
local newAirport = table.Copy(BaseAirport)
+
local newAirport = table.Copy(AirportClass)
 
--table.Copy is a Garry's Mod function. Look for it in the source code should you need to replicate it in a different API.
 
--table.Copy is a Garry's Mod function. Look for it in the source code should you need to replicate it in a different API.
 
 

Revision as of 00:44, 5 January 2016

Introduction

This tutorial discusses the methods used by Garry's Mod to define new objects classes from files. Though this tutorial focuses on application in GMod, the code and concepts can be used elsewhere.

What is an Object?

An object in programming is a collection of data which is organized and structured in a specific manner and is derived from a class. Examples of classes are Entities, Weapons (which are just fancy entities), and Panels. Things like Vectors and ConVars are also classes but they aren't created from files, which is the purpose of this tutorial.

Most classes have a baseclass, from which they inherit their properties. As more classes are created, a baseclass tree begins to form. An example of this is the HL2 "weapon_pistol", which is based off the "weapon" class, which is based off the "anim" class, which is based off the "entity" class. The weapon has all the properties of an entity, such as position and model, and it has its own special properties, such as clip size and damage.

This is an example of an inheritance hierarchy from a hypothetical system comprised of Weapons and Agents. class-diagram-methods.png

Objects are created using a constructor. A constructor basically makes a copy of the class and returns it as a new object. Examples of constructors in Garry's Mod are ents.Create, vgui.Create, and Vector.

Objects in Lua

In Lua, there are two types of objects: those defined in C++ (called userdata) and those defined within Lua (special tables). In this case, we're going to stick with Lua defined objects. Let's create a new class (we'll get to creating classes in separate files later).

--[[Define our class. We're going to create airport objects.
We are going to preset the defaults for some of the required properties of all new airport objects made.]]
local AirportClass = {}
AirportClass.Name = "Airport"
AirportClass.Code = "ABC"
AirportClass.City = "Cityville"
AirportClass.State = "CA"

--[[So now we have a basic table which represents a non-existant airport which we will derive our airports objects from.
Let's make a constructor so we can start creating airport objects. A basic constructor is as follows:]]
function Airport(code) --Code is an optional argument.
	
	local newAirport = table.Copy(AirportClass)
	--table.Copy is a Garry's Mod function. Look for it in the source code should you need to replicate it in a different API.
	
	--Override the old default Code property should we have a new code to replace it with:
	if code then
		newAirport.Code = code
	end
	
	--Return our new Object.
	return newAirport

end

--Now we're ready to create some Airport objects! Let's start by defining a few...
local BWI = Airport("BWI")
local LAX = Airport("LAX")
local ORD = Airport("ORD")

--[[Okay, they exist now.
BUT WAIT! Our new airports are the same as the original BaseAirport class!
This is unacceptable. Let's go ahead and change the specifics of our airports.]]
BWI.Name = "Thurgood Marshall"
BWI.City = "Baltimore"
BWI.State = "MD"

LAX.Name = "Los Angeles International Airport"
LAX.City = "Los Angeles"
-- LAX.State = "CA"
-- The state is "CA" by default, so we don't need to change it here.

ORD.City = "Chicago"
ORD.State = "IL"
ORD.Name = "O'Hare Airport"

That wasn't so hard right? Define a class, copy the class with a constructor, and modify it as needed. Let's move on to file-based classes. In Garry's Mod, there are a few systems which use folders to define each class, such as SWEPs, effects, and SENTs. In essence, these systems follow these steps:

Find all classes to be defined. For each class folder/file:

  1. Create a table for the class to be defined into.
  2. Run the file(s).
  3. Save the class table somewhere for future use.
  4. Refresh existing objects of that class. (We won't do this in our tutorial.)
  5. Delete the class table. (But not the one we saved elsewhere. We use that later.)


So let's make inventory items with this system. The following goes in "lua/includes/modules/invitem.lua":


local string = string
local table = table
local error = error
local Material = Material
local baseclass = baseclass

module("invitem")

--Create a list of all inventory items.
local invitems = invitems or {}
local allitems = allitems or {}

--Create our root baseclass, with all items are based off somewhere down the line.
invitems.item_baseitem = {}
invitems.item_baseitem.Icon = Material("vgui/items/baseweapon.png")
invitems.item_baseitem.Name = "Base Item"
invitems.item_baseitem.Width = 1
invitems.item_baseitem.Height = 1
invitems.item_baseitem.Weight = 1
invitems.item_baseitem.Owner = NULL
invitems.item_baseitem.BaseClass = {}
invitems.item_baseitem.UniqueID = -1 --We set this when we create the object.
function invitems.item_baseitem:Init()

end
function invitems.item_baseitem:Remove()
	allitems[self.id] = nil
	self:OnRemove()
end
function invitems.item_baseitem:OnRemove()
	--This is the function we can override per-class.
end
-- These two functions are for the Grid-Based Inventory Tutorial, which this system is compatable with.
function invitems.item_baseitem:GetSize()
	return self.Width, self.Height
end
function invitems.item_baseitem:GetIcon()
	return self.Icon
end
--baseclass.Set is a GMod function. See lua/includes/modules/baseclass.lua
baseclass.Set("item_baseitem", invitems.item_baseitem)


--Saves a class to our internal list of items, and defines our class's baseclass.
function Register(classtbl, name)

	name = string.lower(name)
	
	baseclass.Set( name, classtbl )
	
	classtbl.BaseClass = baseclass.Get(classtbl.Base)
	
	invitems[ name ] = classtbl
	
end

--Our constructor, which takes an argument to determine the class.
function Create(class)
	--Prevent non-existant classes from being created.
	if not invitems[class] then error("Tried to create new inventory item from non-existant class: "..class) end
	
	local newItem = table.Copy(invitems[class])
	
	--Add our new object to the list of all items currently in the game.
	local id = table.insert(allitems, newItem)
	--Give it a unique ID.
	newItem.UniqueID = id
	
	--Call our Init function when we create the new item.
	newItem:Init()
	
	return newItem
end

--Returns a table of all classes.
function GetClasses()
	return invitems
end

--Returns the class table of a given class from our saved list.
function GetClassTable(classname)
	return invitems[classname]
end

--Returns a COPY of the class table, so we don't modify the original.
function GetClassTableCopy(classname)
	return table.Copy(invitems[classname])
end

--Returns a list of all current items objects.
function GetAll()
	return allitems
end

So now we have a module which registers our item classes, lets us create new item objects, allows us to view a given class on demand, and keeps track of all current item objects within the game. We also have a baseclass to base our new classes on.

In a new file, lua/autorun/inventory.lua, let's run all our files:


if SERVER then
	AddCSLuaFile()--Only needed on Garry's Mod.
	AddCSLuaFile("../includes/modules/invitem.lua")
end


--Run our module:
require("invitem")


--Let's run our class files:

--Get a list of all files and folders in our classes folder.
--file.Find is a Garry's Mod function. See the wiki if you want to remake it.
local files,folders = file.Find("items/*", "LUA") --Store our files in lua/items/

--Consider any solo files to be shared, such as "item_dildo.lua"
for k,File in pairs(files)do

	local name = string.sub(File,1,string.find(File,"%.lua")-1)
	
	--Create our class table, which the files write to.
	ITEM = {}
	
	--Set our ClassName for future reference.
	ITEM.ClassName = name

	if SERVER then
		AddCSLuaFile("items/"..File)
	end
	include("items/"..File)
	
	if not ITEM.Base then ITEM.Base = "item_baseitem" end
	
	--Register the class table.
	invitem.Register(name,ITEM)
	
	--Delete the class table.
	ITEM = nil
end

--Include each file, e.g. init.lua, shared.lua, and cl_init.lua, in their respective domains.
for k,folder in pairs(folders)do

	local name = string.sub(folder,1,string.find(folder,"%.lua")-1)
	
	--Create our class table, which the files write to.
	ITEM = {}
	
	--Set our ClassName for future reference.
	ITEM.ClassName = name
	
	--Include all of our files for this item.
	local dir = "items/"..folder.."/"
	if SERVER then
		--file.Exists is a Garry's Mod function. See the wiki if you want to remake it.
		if file.Exists(dir.."shared.lua", "LUA") then
			AddCSLuaFile(dir.."shared.lua")
		end
		if file.Exists(dir.."cl_init.lua", "LUA") then
			AddCSLuaFile(dir.."cl_init.lua")
		end
		
		if file.Exists(dir.."init.lua", "LUA") then
			include(dir.."init.lua")
		end
	end
	
	if file.Exists(dir.."shared.lua", "LUA") then
		include(dir.."shared.lua")
	end
	
	if CLIENT then
		if file.Exists(dir.."cl_init.lua", "LUA") then
			include(dir.."cl_init.lua")
		end
	end
	
	if not ITEM.Base then ITEM.Base = "item_baseitem" end
	
	--Register the class table.
	invitem.Register(name,ITEM)
	
	--Delete the class table.
	ITEM = nil
end

--Now that we've included all our class files, let's make them inherit their baseclasses.
for classname,classtbl in pairs(invitem.GetClasses())do

	--table.Inherit replaces nil values from one table with non-nil values from another table. See lua/includes/extensions/table.lua
	table.Inherit(classtbl,classtbl.BaseClass)

end

With that, your items should be automatically included, registered, and inherited.

You can download a working example of this system here. The code is untested but should work.

--Bobblehead 00:01, 13 December 2014 (UTC)

Personal tools
Navigation