Nesne Yönelimli Lua

Giriş

Bu eğitim, Garry's Mod tarafından dosyalardan yeni nesne sınıflarını tanımlamak için kullanılan yöntemleri tartışmaktadır. Bu eğitim GMod'daki uygulamaya odaklansa da, kod ve kavramlar başka yerlerde de kullanılabilir.

Nesne Nedir?

Programlamada bir nesne, belirli bir şekilde organize edilmiş ve yapılandırılmış bir veri koleksiyonudur ve bir sınıftan türetilmiştir. Sınıf örnekleri arasında Entity'ler, Silahlar (ki bunlar sadece süslü entity'lerdir) ve Panel'ler bulunur. Vektörler ve ConVar'lar gibi şeyler de sınıftır ancak bu eğitimin amacı olan dosyalardan oluşturulmazlar.

Çoğu sınıfın, özelliklerini miras aldığı bir temel sınıfı (baseclass) vardır. Daha fazla sınıf oluşturuldukça, bir temel sınıf ağacı oluşmaya başlar. Buna bir örnek, "weapon" sınıfını temel alan, o da "anim" sınıfını temel alan, o da "entity" sınıfını temel alan HL2 "weapon_pistol" silahıdır. Silah, pozisyon ve model gibi bir entity'nin tüm özelliklerine sahiptir ve ayrıca şarjör boyutu ve hasar gibi kendine özgü özellikleri de vardır.

Bu, Silahlar ve Ajanlardan oluşan varsayımsal bir sistemden bir kalıtım hiyerarşisi örneğidir.

Nesneler bir yapıcı (constructor) kullanılarak oluşturulur. Bir yapıcı temel olarak sınıfın bir kopyasını oluşturur ve onu yeni bir nesne olarak döndürür. Garry's Mod'daki yapıcı örnekleri ents.Create, vgui.Create ve Vector'dür.

Lua'da Nesneler

Yöntem 1: module

Lua'da iki tür nesne vardır: C++'da tanımlananlar (userdata olarak adlandırılır) ve Lua içinde tanımlananlar (özel tablolar). Bu durumda, Lua'da tanımlanan nesnelerle devam edeceğiz. Hadi yeni bir sınıf oluşturalım (sınıfları ayrı dosyalarda oluşturmaya daha sonra geleceğiz).

--Sınıfımızı tanımlayalım. Havaalanı nesneleri oluşturacağız. --Yapılan tüm yeni havaalanı nesnelerinin gerekli bazı özelliklerinin varsayılanlarını önceden ayarlayacağız. local AirportClass = {} AirportClass.Name = "Airport" AirportClass.Code = "ABC" AirportClass.City = "Cityville" AirportClass.State = "CA" --[[Şimdi, havaalanı nesnelerimizi türeteceğimiz, var olmayan bir havaalanını temsil eden temel bir tablomuz var. Havaalanı nesneleri oluşturmaya başlayabilmemiz için bir yapıcı (constructor) oluşturalım. Temel bir yapıcı aşağıdaki gibidir:]] function Airport(code) --Kod isteğe bağlı bir argümandır. local newAirport = table.Copy(AirportClass) --table.Copy bir Garry's Mod fonksiyonudur. Farklı bir API'de çoğaltmanız gerekirse kaynak koduna bakın. --Değiştirecek yeni bir kodumuz varsa, eski varsayılan Kod özelliğini geçersiz kılın: if code then newAirport.Code = code end --Yeni Nesnemizi döndür. return newAirport end --Şimdi bazı Havaalanı nesneleri oluşturmaya hazırız! Birkaç tane tanımlayarak başlayalım... local BWI = Airport("BWI") local LAX = Airport("LAX") local ORD = Airport("ORD") --Tamam, şimdi varlar. --AMA BEKLEYİN! Yeni havaalanlarımız orijinal BaseAirport sınıfıyla aynı! --Bu kabul edilemez. Haydi havaalanlarımızın özelliklerini değiştirelim. BWI.Name = "Thurgood Marshall" BWI.City = "Baltimore" BWI.State = "MD" LAX.Name = "Los Angeles International Airport" LAX.City = "Los Angeles" -- LAX.State = "CA" -- Eyalet varsayılan olarak "CA" olduğu için burada değiştirmemize gerek yok. ORD.City = "Chicago" ORD.State = "IL" ORD.Name = "O'Hare Airport"

O kadar da zor değildi, değil mi? Bir sınıf tanımlayın, sınıfı bir yapıcıyla kopyalayın ve gerektiği gibi değiştirin. Şimdi dosya tabanlı sınıflara geçelim. Garry's Mod'da, her sınıfı tanımlamak için klasörler kullanan SWEP'ler, efektler ve SENT'ler gibi birkaç sistem vardır. Özünde, bu sistemler şu adımları izler:

Tanımlanacak tüm sınıfları bulun. Her sınıf klasörü/dosyası için:

  1. İçine sınıfın tanımlanacağı bir tablo oluşturun.
  2. Dosyaları çalıştırın.
  3. Sınıf tablosunu ileride kullanmak üzere bir yere kaydedin.
  4. O sınıfa ait mevcut nesneleri yenileyin. (Bunu eğitimimizde yapmayacağız.)
  5. Sınıf tablosunu silin. (Ama başka bir yere kaydettiğimizi değil. Onu daha sonra kullanacağız.)

Öyleyse bu sistemle envanter öğeleri yapalım. Aşağıdaki kod "lua/includes/modules/invitem.lua" dosyasına yazılmalıdır:

local string = string local table = table local error = error local Material = Material local baseclass = baseclass module("invitem") --Tüm envanter öğelerinin bir listesini oluşturun. local invitems = invitems or {} local allitems = allitems or {} --Tüm öğelerin bir şekilde temel aldığı kök temel sınıfımızı (baseclass) oluşturun. 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 --Bunu nesneyi oluşturduğumuzda ayarlarız. function invitems.item_baseitem:Init() end function invitems.item_baseitem:Remove() allitems[self.id] = nil self:OnRemove() end function invitems.item_baseitem:OnRemove() --Bu, sınıf başına geçersiz kılabileceğimiz fonksiyondur. end -- Bu iki fonksiyon, bu sistemin uyumlu olduğu Izgara Tabanlı Envanter Eğitimi içindir. function invitems.item_baseitem:GetSize() return self.Width, self.Height end function invitems.item_baseitem:GetIcon() return self.Icon end --baseclass.Set bir GMod fonksiyonudur. lua/includes/modules/baseclass.lua dosyasına bakın. baseclass.Set("item_baseitem", invitems.item_baseitem) --Bir sınıfı dahili öğe listemize kaydeder ve sınıfımızın temel sınıfını (baseclass) tanımlar. function Register(classtbl, name) name = string.lower(name) baseclass.Set( name, classtbl ) classtbl.BaseClass = baseclass.Get(classtbl.Base) invitems[ name ] = classtbl end --Sınıfı belirlemek için bir argüman alan yapıcımız (constructor). function Create(class) --Var olmayan sınıfların oluşturulmasını önleyin. if not invitems[class] then error("Tried to create new inventory item from non-existant class: "..class) end local newItem = table.Copy(invitems[class]) --Yeni nesnemizi oyundaki mevcut tüm öğelerin listesine ekleyin. local id = table.insert(allitems, newItem) --Ona benzersiz bir ID verin. newItem.UniqueID = id --Yeni öğeyi oluşturduğumuzda Init fonksiyonumuzu çağırın. newItem:Init() return newItem end --Tüm sınıfların bir tablosunu döndürür. function GetClasses() return invitems end --Kaydedilmiş listemizden belirli bir sınıfın sınıf tablosunu döndürür. function GetClassTable(classname) return invitems[classname] end --Sınıf tablosunun bir KOPYASINI döndürür, böylece orijinalini değiştirmeyiz. function GetClassTableCopy(classname) return table.Copy(invitems[classname]) end --Mevcut tüm öğe nesnelerinin bir listesini döndürür. function GetAll() return allitems end

Artık öğe sınıflarımızı kaydeden, yeni öğe nesneleri oluşturmamıza izin veren, talep üzerine belirli bir sınıfı görüntülememizi sağlayan ve oyundaki mevcut tüm öğe nesnelerinin takibini yapan bir modülümüz var. Ayrıca yeni sınıflarımızı temel alacağımız bir temel sınıfımız (baseclass) da var.

Yeni bir dosyada, lua/autorun/inventory.lua, tüm dosyalarımızı çalıştıralım:

if SERVER then AddCSLuaFile()--Sadece Garry's Mod'da gereklidir. AddCSLuaFile("../includes/modules/invitem.lua") end --Modülümüzü çalıştırın: require("invitem") --Sınıf dosyalarımızı çalıştıralım: --Sınıflar klasörümüzdeki tüm dosya ve klasörlerin bir listesini alın. --file.Find bir Garry's Mod fonksiyonudur. Yeniden yapmak isterseniz wiki'ye bakın. local files,folders = file.Find("items/*", "LUA") --Dosyalarımızı lua/items/ içinde saklayın --"item_dildo.lua" gibi tek başına bulunan dosyaları paylaşılan olarak kabul edin. for k,File in pairs(files)do local name = string.sub(File,1,string.find(File,"%.lua")-1) --Dosyaların içine yazdığı sınıf tablomuzu oluşturun. ITEM = {} --Gelecekte başvurmak üzere ClassName'imizi ayarlayın. ITEM.ClassName = name if SERVER then AddCSLuaFile("items/"..File) end include("items/"..File) if not ITEM.Base then ITEM.Base = "item_baseitem" end --Sınıf tablosunu kaydedin. invitem.Register(name,ITEM) --Sınıf tablosunu silin. ITEM = nil end --Her dosyayı, ör. init.lua, shared.lua ve cl_init.lua, kendi alanlarında dahil edin. for k,folder in pairs(folders)do local name = string.sub(folder,1,string.find(folder,"%.lua")-1) --Dosyaların içine yazdığı sınıf tablomuzu oluşturun. ITEM = {} --Gelecekte başvurmak üzere ClassName'imizi ayarlayın. ITEM.ClassName = name --Bu öğe için tüm dosyalarımızı dahil edin. local dir = "items/"..folder.."/" if SERVER then --file.Exists bir Garry's Mod fonksiyonudur. Yeniden yapmak isterseniz wiki'ye bakın. 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 --Sınıf tablosunu kaydedin. invitem.Register(name,ITEM) --Sınıf tablosunu silin. ITEM = nil end --Tüm sınıf dosyalarımızı dahil ettiğimize göre, şimdi temel sınıflarını (baseclass) miras almalarını sağlayalım. for classname,classtbl in pairs(invitem.GetClasses())do --table.Inherit bir tablodaki nil değerlerini başka bir tablodaki nil olmayan değerlerle değiştirir. lua/includes/extensions/table.lua dosyasına bakın. table.Inherit(classtbl,classtbl.BaseClass) end

Bununla birlikte, öğeleriniz otomatik olarak dahil edilmeli, kaydedilmeli ve miras alınmalıdır.

Bu sistemin çalışan bir örneğini buradan indirebilirsiniz. Kod test edilmemiştir ancak çalışması gerekir.

--[[Kullanıcı:Bobblehead|Bobblehead]] 00:01, 13 Aralık 2014 (UTC)

Yöntem 2: Metatablolar

Metatablolar, davranış biçimleri sayesinde nesne yönelimli programlamayı simüle etmek için kullanışlı bir araçtır.

Aynı metatabloya sahip nesneler, aynı metayöntemlere ve özel fonksiyonlara erişebilir.

Metatablomuzu oluşturarak başlayalım

Book = {} -- Bu bizim metatablomuzdur. "Sınıfımızı" temsil edecektir Book.__index = Book --Eğer bir anahtar tabloda bulunamazsa, metatablosunun __index'ine bakacaktır. --Bu, 'Book' tablosu için tanımladığımız herhangi bir fonksiyonun, metatablosu 'Book' olan herhangi bir nesne tarafından erişilebilir olacağı anlamına gelir

Şimdi, nesnemizi döndürecek ve genellikle "new" olarak adlandırılan bir fonksiyona ihtiyacımız var

Book = {} -- Bu bizim metatablomuzdur. "Sınıfımızı" temsil edecektir Book.__index = Book function Book:new( series, seriesNum, title, text ) -- Değişkenler isteğe bağlıdır -- Bu, döndüreceğimiz tablodur local EmptyBook = { series = series or "", seriesNum = seriesNum or 0, title = title or "", text = text or "", } setmetatable( EmptyBook, Book ) -- 'EmptyBook'un metatablosunu 'Book' olarak ayarla return EmptyBook -- Metatablosu 'Book' olan 'EmptyBook' tablosunu döndür. Bu bizim nesnemizdir --[[ Çoğu zaman, tablo fonksiyonun kendi içinde şu şekilde ayarlanacaktır: return setmetatable( { series = series or "", seriesNum = seriesNum or 0, title = title or "", text = text or "", }, Book ) ]] end

Şimdi temelimizi kurduğumuza göre, birkaç fonksiyon ekleyelim

-- İlk olarak, Kitabımızı yazdırmak için bir fonksiyon function Book:Print() if self:IsEmpty() then return end -- Boş bir kitabı yazdırmanın bir anlamı yok print( "\n====================================" ) -- Yazdırma işlemimizin ne zaman başladığını netleştirelim if self.seriesNum > 0 then -- Seri adını yalnızca bir serinin parçasıysa yazdır (Ve dolayısıyla seriesNum > 0) print( self.series .. " " .. self.seriesNum .. ": " .. self.title .. "\n" ) -- Güzel format else print( self.title .. "\n" ) -- Aksi takdirde, sadece başlığı yazdırabiliriz end print( self.text ) -- Ve tabii ki, metni yazdır print( "====================================\n") -- Yazdırma işlemimizin ne zaman bittiğini netleştirelim end -- Şimdi birkaç fonksiyon daha tanımlayacağız, bunlar sadece benim yapmak istediğim fonksiyonlar function Book:IsEmpty() return #self.text == 0 end function Book:SetSeries( series ) self.series = series end function Book:GetSeries() return self.series end function Book:SetSeriesNum( num ) self.seriesNum = num end function Book:GetSeriesNum() return self.seriesNum end function Book:SetTitle( title ) self.title = title end function Book:GetTitle() return self.title end function Book:AddText( text ) self.text = self.text .. text end function Book:AddLine() self.text = self.text .. "\n" end -- Metnin son satırını kaldır function Book:RemoveLine() if self:IsEmpty() then return end -- Metin yoksa bir satır silemezsiniz local index = string.find( self.text, "\n" ) if not index then self.text = "" return end -- Yalnızca 1 satır varsa, metni boş olarak ayarlayın local lastIndex = index while index do lastIndex = index index = string.find( self.text, "\n", index+1 ) end self.text = string.sub( self.text, 1, lastIndex-1 ) end

Son olarak, biraz metasi-hirbazlık kullanarak Book:new() ile aynı olan bir Book() fonksiyonu yapacağız

setmetatable( Book, {__call = Book.new } )

Yeni simüle edilmiş sınıfımızı - Kitap! - başarıyla oluşturduk.

Book = {} -- Bu bizim metatablomuzdur. "Sınıfımızı" temsil edecektir Book.__index = Book function Book:new( series, seriesNum, title, text ) local EmptyBook = { series = series or "", seriesNum = seriesNum or 0, title = title or "", text = text or "", } setmetatable( EmptyBook, Book ) return EmptyBook end function Book:Print() if self:IsEmpty() then return end -- Boş bir kitabı yazdırmanın bir anlamı yok print( "\n====================================" ) if self.seriesNum > 0 then print( self.series .. " " .. self.seriesNum .. ": " .. self.title .. "\n" ) else print( self.title .. "\n" ) end print( self.text ) print( "====================================\n") end function Book:IsEmpty() return #self.text == 0 end function Book:SetSeries( series ) self.series = series end function Book:GetSeries() return self.series end function Book:SetSeriesNum( num ) self.seriesNum = num end function Book:GetSeriesNum() return self.seriesNum end function Book:SetTitle( title ) self.title = title end function Book:GetTitle() return self.title end function Book:AddText( text ) self.text = self.text .. text end function Book:AddLine() self.text = self.text .. "\n" end function Book:RemoveLine() if self:IsEmpty() then return end local index = string.find( self.text, "\n" ) if not index then self.text = "" return end local lastIndex = index while index do lastIndex = index index = string.find( self.text, "\n", index+1 ) end self.text = string.sub( self.text, 1, lastIndex-1 ) end setmetatable( Book, {__call = Book.new } )

İşte Kitap sınıfını kullanan bir örnek kod

-- Seçenek #1: Boş bir kitap oluşturun, ardından verilerini tanımladığımız metafonksiyonlarla ayarlayın local myBook = Book() myBook:SetSeries( "My First Series" ) myBook:SetSeriesNum( 1 ) -- Serinin ilk kitabı myBook:SetTitle( "My First Book" ) myBook:AddText( "This is my very first book.\nThe End" ) myBook:Print() -- Seçenek #2: Tüm (veya bazı) verileriyle yeni bir kitap oluşturun local myBook2 = Book( "My First Series", 2, "My Second Book", "This is my second book.\nIt's a bit longer than the first one" ) myBook2:Print() myBook:RemoveLine() myBook:AddLine() myBook:AddText( "I changed it a bit" ) myBook:AddLine() myBook:AddText( "The End" ) myBook:SetSeries( "" ) myBook:SetSeriesNum( 0 ) myBook:Print()

Çıktı:

Tüm sunucular · GmodTR Launcher · Rehber · GMod Wiki · GmodTR nedir? · Sunucu ekle · Discord