Derma Kullanımına Giriş
Giriş
Bu, sizi Derma hakkında sıfır bilgiden alıp, tüm temel konular üzerinden geçirerek ilk kullanıcı arayüzü (UI) projenizi oluşturmak için sağlam bir temel atmayı amaçlayan bir dizi eğitimin ilkidir.
Garry's Mod'a özel Kullanıcı Arayüzleri (UI) dünyasına hoş geldiniz. Başlamadan önce net bir kelime dağarcığı tanımlayalım. Source motoru, varsayılan olarak kullanıcı arayüzünü VGUI (Valves Graphical User Interface) ve CPP kullanarak çizer. Garry's Mod'da ise Lua ve Derma kullanırız. Derma, Garry ve TAD2020 tarafından UI çizmeyi kolaylaştırmak için oluşturulmuştur. Arka planda, Lua kodumuz VGUI koduna erişerek UI elemanları oluşturmamızı, özelleştirmemizi ve yok etmemizi sağlar.
Bölüm 1: Panel (DPanel)
Paneller her şeyin köküdür, kullandığınız ve oluşturduğunuz diğer elemanların çoğu (hepsi değil) bu panellerden oluşacaktır. Motor, varsayılan olarak neredeyse hiçbir varsayılan özelliği olmayan "Panel" adında bir VGUI kontrolü sağlar. Derma, motor panelinden miras alırken işlevselliğini daha esnek olacak şekilde genişleten "DPanel" adlı bir kontrol sunar. Çoğu zaman DPanel kullanmak daha iyi olacaktır, bu yüzden odak noktamız bu olacak.
Panel Oluşturma
Bir paneli kullanmak için bir tane oluşturmanız gerekir. En yaygın yöntem vgui.Create'tir, şu şekilde:
Bu kod parçacığı Lua'da bir DPanel nesnesi oluşturur ve ona erişebilmemiz için myFirstPanel değişkeninde saklar. Daha sonra bu değişkeni fonksiyonları çalıştırmak veya aşağıdaki gibi özellikleri ayarlamak için kullanabiliriz:
- Boyut - Panelin Genişliği ve Yüksekliği
- Konum - Sol-üst köşenin nerede olduğu
- Çizim Ayarları - Renk ve benzeri özellikler
Özellikleri Ayarlama
Paneller kendi başlarına pek bir anlam ifade etmezler, bunun yerine Lego tuğlaları gibi davranarak daha büyük bileşenler oluşturmak için birden fazla örneğini birleştirmenize ve bağlamanıza olanak tanırlar.
Daha önce de belirtildiği gibi, Panellerin Boyut, Konum ve Çizim Ayarları (daha sonra ele alacağımız Paint fonksiyonu olarak daha iyi bilinir) gibi özellikleri vardır. Derma'nın yaptığı şeylerden biri de bize bu özellikleri ayarlamak için Lua fonksiyonları sağlamaktır.
İşte onlarla deneme yapabilmeniz için daha yaygın fonksiyonlardan bazılarına bir örnek.
Bunlar oldukça açıklayıcı olmalı, ancak bu fonksiyonların sayıları nasıl kullandığını akılda tutmak önemlidir. Paneller sol üstten sağ alta doğru çizilir. Bu, :SetSize() kullandığınızda, sol üst pikselin konumunun aynı noktada kalacağı, sağ alt pikselin ise dışarı doğru fırlayacağı anlamına gelir. Aynı şey :SetPos() için de geçerlidir, bu fonksiyon sol üst pikseli ekrandaki yerel koordinatlara taşıyacaktır (ekranınızın sol üstü 0,0 olacak şekilde).
Bu fonksiyonlar okundukça yorumlanır. Eğer :Center() kullanıp ardından :SetSize( w, h ) kullanırsam, panel önce ortalanıp sonra boyutu değiştiği için ortalanmış görünmeyecektir.
Görünürlüğü Kontrol Etme
Bu bölümü okurken, gizli panelleri tespit etmenize yardımcı olması için vgui_visualizelayout 1 kullanın. Bu fonksiyon, paneller GÜNCELLENDİĞİNDE kırmızı renkte yanıp sönmesini sağlar.
Görünürlük, elemanlar içinde neyi görüp neyi göremediğinizi kontrol etmek için önemlidir. Derma bize basit bir komut olan Panel:SetVisible'i sağlar. Paneller, ebeveynlerinin durumuna göre otomatik olarak gösterilir ve gizlenir. Yani bir paneli gizlerseniz, tüm alt elemanları da gizlenir.
Bir panelin görünürlüğü, şeffaf bir panelden farklıdır. Şeffaf bir panel yine de güncellenir ve görünür gibi davranır, oysa gizli bir panel doğru şekilde güncellenmez.
bir DPanel, DPanel:SetPaintBackground kullanılarak şeffaf hale getirilebilir.
Sonuç
İşte bu kadar, başlamak için ihtiyacınız olan her şey. Bir sonraki bölüme başlamadan önce, Panel sınıfında bulunan farklı fonksiyonlarla denemeler yaparak nelerin mümkün olduğunu görün. İyi bir kontrol noktası, daha karmaşık alanlara geçmeden önce Panel:Remove ile oluşturulmuş panelleri nasıl kaldıracağınızı çözmektir.
İşte bu bölümde ele aldığımız her şeyi detaylandıran bir kod bloğu:
Bölüm 2: Etiketler ve Düğmeler (DLabel / DButton)
Son bölüm tamamen dikdörtgenler oluşturmakla ilgiliydi, ancak kullanıcı arayüzleri sadece dikdörtgenlerden oluşmaz. Arayüzümüzün metin içermesini ve onunla bir miktar etkileşim olmasını isteriz, işte bu noktada Etiketler ve Düğmeler devreye girer. Derma için bu elemanlar DLabel ve DButton'dır.
Şimdi bu elemanları kullanmaya başlamadan önce kalıtımı (inheritance) hatırlamak çok önemlidir. DLabel, Label adında motor tabanlı bir panelden türetilirken, DButton elemanı DLabel'dan türetilir. Bu kademeli kalıtımı hatırlamak önemlidir çünkü bu paneller içinde hangi fonksiyonlara erişip hangilerine erişemeyeceğinizi hatırlamanıza yardımcı olacaktır. Bu elemanların bir sonraki bölüme ayrılmasının nedeni budur.
İstisnalar olsa da, motor panelinden türeyen elemanlar genellikle düzen ve gruplama gibi şeyler için kullanılır. Motor etiket panelinden türeyen elemanlar ise daha çok fare etkileşimi için kullanılır.
Etiket Ekleme
Etiketlerimizi ve düğmelerimizi eklemeden önce bir temel panel oluşturalım:
Şimdi bir DLabel ekleyelim
Panelinize metin eklemek gerçekten bu kadar basit!
Düğme Ekleme
Şimdi myBasePanel üzerine bir yere, paneli bizim için kapatacak bir DButton ekleyelim. Umarım Bölüm 1'de panel kaldırmayı tetiklemenin bir yolunu bulmuşsunuzdur, böylece bu, panel yönetimini kolaylaştıracaktır.
Kolay! şimdi düğmemizi ayarladığımıza göre, tıklarsanız hiçbir şey yapmadığını fark edeceksiniz. Bunun nedeni, ilgili hook'larda herhangi bir davranış ayarlamamış olmamızdır.
DButton, metotlarını DLabel'dan miras aldığı ve DLabel'ın DLabel:DoClick metoduna sahip olduğu için, düğmemiz için o hook'u kullanabiliriz. Eğer bu tam olarak mantıklı gelmiyorsa, bu hook'u nasıl ve neden kullanabildiğimizi anlamak için bir an durup düşünün. Kontroller daha karmaşık hale geldikçe kalıtımı şimdi anlamak çok önemlidir.
Bu fonksiyonu myCloseButton panelimizin oluşturulmasının altında bir yerde tanımlayalım.
bu ikisi arasındaki tek fark Lua Self Anahtar Kelimesi'nin nasıl ele alındığıdır
Oluşturduğumuz düğme çok basittir, sadece mesajımızı yazdırır ve ardından temel panelimizi kaldırır, bu da onun ve tüm alt elemanlarının silinmek üzere işaretlenmesine neden olur.
Sonuç
Artık temel panel kontrolü hakkında daha da güçlü bir anlayışa sahip olmalısınız. Farklı şeyler yapan düğmeler oluşturarak denemeler yapın. Belki bir hesap makinesi? Düzen sistemini Bölüm 3'te ele alacağız.
Kodun tamamı şöyle görünür:
Bölüm 3: Düzen Sistemi (Layout System)
Önceki bölümler tamamen eleman oluşturmakla ilgiliydi. Panel:SetPos kullanarak bazı temel konumlandırmaları ele alsak da, bu gerçekten sadece statik elemanlar için en iyi sonucu verir. Eğer katmanlı elemanlarınız varsa ve ebeveyn boyutunu değiştirirse, alt elemanları uygun şekilde güncelleyebilmek istersiniz.
PerformLayout
Panel:PerformLayout, düzen sisteminin bel kemiğidir. Bir panelin alt elemanlarını nasıl düzenleyeceğini bilmesi gerektiğinde bu hook çağrılır. Bu, panel oluşturma, yeniden boyutlandırma veya paneller eklendiğinde veya kaldırıldığında otomatik olarak çağrılabilir. Veya, Panel:InvalidateLayout ile manuel olarak çağrılabilir, ancak bu genellikle gerekli olmayacaktır.
Yaygın Düzen Teknikleri
Burada, PerformLayout hook'u içinde panellerimizi düzenlemenin en yaygın yollarından bazılarını inceleyelim.
1. Manuel Konumlandırma
Bunu zaten biliyorsunuz! Bu sadece istediğiniz kesin koordinatları ve boyutları belirtmeyi içerir. Bu, hassas kontrol sunar ancak uygulayabileceğiniz esnek veya dinamik tasarım miktarını sınırlar.
Bölüm 2'den Manuel Konumlandırma Örnekleri
2. Yaslama (Docking)
Yaslama (Docking) çok güçlüdür. Alt elemana, ebeveyninin bir veya daha fazla kenarına yapışmasını ve mevcut geri kalan alanı doldurmasını söylersiniz.
Panel:Dock fonksiyonu, panelin nasıl yaslanacağını belirtmek için 1'den 5'e kadar sayılar alır. Ancak bu sayıların ne anlama geldiğini hatırlamak yerine, kodumuzu daha okunabilir hale getirmek için DOCK enum'ını kullanabiliriz.
| Dock Enum'u | Değer | Açıklama |
|---|---|---|
| NODOCK | 0 | Yaslama |
| FILL | 1 | Diğer paneller tarafından alınmayan kalan alanın olabildiğince fazlasını doldur. |
| LEFT | 2 | Panelin yüksekliğini maksimize ederek ebeveynin sol duvarına yapış. |
| RIGHT | 3 | Panelin yüksekliğini maksimize ederek ebeveynin sağ duvarına yapış. |
| TOP | 4 | Genişliği maksimize ederek ebeveynin üst duvarına yapış. |
| BOTTOM | 5 | Genişliği maksimize ederek ebeveynin alt duvarına yapış. |
Şimdi, bu yöntemi kullanırsanız, alt elemanları yaslama sırasının önemli olduğunu unutmamanız gerekir!
Bu kodu çalıştırdıktan sonra, başlık ve kapatma düğmelerinin ebeveyne göre otomatik olarak konumlandığını ve boyutlandığını fark edeceksiniz. Şimdi Panel:DockMargin ve Panel:DockPadding kullanarak bu fonksiyonların görünümünü iyileştirmeye devam edebiliriz. Margin, ebeveynin diğer alt elemanları arasındaki mesafeyi ifade eder. Padding, paneli doldurabileceği kenardan uzağa doğru küçültmeyi ifade eder. Eğer bu hala kafa karıştırıcıysa, işlevsellik aynı olduğu için web geliştirmedeki farkı araştırabilirsiniz.
3. İçeriğe Göre Boyutlandırma
Bazen, ebeveyni doldurmak yerine, ebeveynin alt elemanlarına göre küçülmesini istersiniz. İşte bu noktada Panel:SizeToContents ve Panel:SizeToChildren devreye girer. :SizeToContents() fonksiyonunu, metnine tam sığacak kadar büyük yapmak için DLabel'ımızda kullandığımız Bölüm 2'den hatırlayabilirsiniz. :SizeToChildren(), tüm panelin alt elemanlarının sınırlayıcı kutusunu (bounding box) hesapladığı ve boyutu bu sınırlayıcı kutuyu barındırabilecek minimum boyuta ayarladığı için bundan farklıdır.
4. Hizalama
Bu fonksiyonlar, elemanları ebeveynleri tarafından ayrılan alan içinde konumlandırmaya yardımcı olur (genellikle yaslama veya bir boyut ayarladıktan sonra kullanılır). Örneğin, Panel:SetContentAlignment metni DLabel'ın sınırları içinde ortalardı. Birçok panel ayrıca, paneli ebeveyni içinde ortalayan Panel:Center gibi yardımcı fonksiyonlara sahiptir.
Diğer Hizalama Fonksiyonları:
Konteyner Panelleri ve Düzen Mantığı
Temel DPanel'in PerformLayout'u yalnızca manuel konumlandırmalara ve yaslamaya uysa da, özel konteyner panelleri çok daha akıllı PerformLayout fonksiyonlarına sahiptir.
- DPanelList:
PerformLayout'u, alt elemanları dikey olarak (bir liste gibi) düzenler ve aralığı otomatik olarak ekler. - DIconLayout:
PerformLayout'u, alt elemanları bir ızgara şeklinde düzenler ve yatayda yer kalmadığında onları bir sonraki satıra kaydırır. - DForm Etiketleri ve kontrolleri genellikle ayar menüleri için kullanılan düzgün sıralar halinde düzenler.
Bu özel panelleri bir sonraki bölümde daha detaylı inceleyeceğiz.
Perde Arkası: Düzen Nasıl Gerçekleşir
- Tetikleyici: Verilen panelin düzenini geçersiz kılan bir şey olur
- Panel:PerformLayout Çağrısı
- Yürütme:
:PerformLayout()hook'unda ne varsa yürütülür
Sonuç
Bu bölüm, düzen sisteminin nasıl çalıştığını ve bir kullanıcının panellerini organize etmek için kullanabileceği farklı yöntemleri ele almayı tamamladı.
Bir sonraki bölümde Özel Konteyner Panelleri'ni ele alacağız.
Bölüm 4: Konteyner Panelleri
Bölüm 3'te farklı düzen yöntemlerini ve :PerformLayout() hook'unu kullanarak elemanları düzenlemeyi ele aldık. Bu, düğmeler veya etiketler gibi şeyleri yerleştirmek için harika olsa da, uzun bir öğe listemiz varsa bu yöntemler hızla gereksiz hale gelebilir. İşte bu noktada konteyner panelleri kullanışlı hale gelir. Konteyner panelleri, diğer panellerden oluşan bir koleksiyonu tutmak ve organize etmek için özel olarak tasarlanmış panellerdir.
Popüler Konteyner Panelleri: DScrollPanel, DTileLayout, DIconLayout, DListLayout, DGrid, DDrawer
DScrollPanel ile Kaydırma
En popüler konteyner paneli DScrollPanel'dir. Bu panelin tek bir amacı vardır, alt elemanları arasında gezinmenizi sağlamak. Bunu, fare kaydırmalarını algılayarak veya alt elemanları kaydırma benzeri bir şekilde yeniden konumlandırmak için kullanılabilecek bir kaydırma çubuğu oluşturarak yapar.
Genellikle gerçek interaktif elemanlarınızı DScrollPanel'in kendisine eklemezsiniz. Bunun yerine, temel boş bir Panel oluşturur ve elemanlarınızı ona eklersiniz. Bu, DScrollPanel'in o görünmez gruplama panelini kaydırabilmesi ve tüm interaktif elemanları kaydırma sırasında birbirlerine göre göreceli konumlarında tutabilmesi içindir.
`DIconLayout` ile Izgara Düzenleri
Peki ya öğeleri bir masaüstündeki simgeler veya bir envanter ızgarasındaki öğeler gibi bir ızgarada düzenlemek isterseniz? DIconLayout bunun için tasarlanmıştır. Alt elemanlarını satırlar ve sütunlar halinde düzenler.
Diğer Konteynerler (DListView, DForm, vb.)
Diğer özel konteynerler şunlardır:
- DListView: Sütunlar ve satırlarla elektronik tablo benzeri görünümler oluşturur. Skor tabloları veya ayrıntılı listeler için harikadır. Öğe eklemek
AddLineve her sütun için veri belirtmeyi içerir. - DForm: Ayar menüleri oluşturmak için kullanışlıdır. Etiketleri ve kontrolleri (onay kutuları, metin girişleri gibi) düzgün sıralar halinde düzenlemeye yardımcı olur.
- DCategoryList: Her biri diğer panelleri içeren, daraltılabilir kategoriler oluşturur.
Bunlar aynı prensipler üzerine kuruludur: Alt elemanlarını belirli şekillerde düzenlemek için özel PerformLayout fonksiyonlarına sahiptirler.
Sonuç
Çoğu eleman görünür olmak ve etkileşime girilmek üzere tasarlanmış olsa da, bazıları öyle değildir; bazıları, benzer kodun birden çok örneğini tek bir noktada toplayarak geliştiricilere çok zaman ve kod karmaşıklığından tasarruf sağlar.
Bir sonraki bölüm, panellerinizin güzel görünmesini sağlamak için "skinning" ve "painting" konularını ele alacaktır.
Bölüm 5: Görünüm (Skinning) ve Boyama (Painting)
Son bölümde konteyner panellerini ele aldığımıza göre, artık panelleri oluşturma, yönetme ve hareket ettirme ile ilgili tüm temel bilgilere sahibiz. Ancak henüz nasıl göründüklerini ele almadık. İşte bu noktada "painting" (boyama) ve "skinning" (görünüm) devreye girecek. Başlamadan önce bu iki terim arasındaki farkı bilmelisiniz. Painting, belirli bir paneli boyamayı ifade eder; bu genellikle Draw ve Surface gibi kütüphaneleri içerir. Skinning ise farklı bir yaklaşımdır; bu, GWEN gibi kütüphaneler kullanarak bir "skin" (görünüm) oluşturmayı ve boyama fonksiyonlarının referans alacağı UI sprite sheet'leri (arayüz resim dosyaları) oluşturmayı içerir.
Her birinin kendi güçlü ve zayıf yönleri vardır, ancak genel olarak, painting yapmak daha kolaydır ama her bir panel için ayrı ayrı yapılması gerekir. Skinning, başlangıçta daha fazla yatırım gerektirir ancak tüm elemanlarda tek bir yerden kontrol edilen tutarlı bir görünüm sağlayabilir.
Derma'da Çizim
Skinning, tüm paneller için "varsayılan" tutarlı bir görünüm sağlar. Painting, belirli bir panel için bu görünümü geçersiz kılmanıza olanak tanır. Bir paneli boyamak, ister bir skin'den ister başka bir kaynaktan gelsin, varsayılan görünümü her zaman geçersiz kılar.
Paint Fonksiyonu
Her bir panelin bir :Paint( w, h ) fonksiyonu olacaktır. Panel, nasıl görünmesi gerektiğini belirlemek için bunu kullanır. Bu hook, panelin görünür olduğu her karede çağrılır. Paint hook'u her zaman iki argümanla çağrılır: mevcut panelin genişliği ve yüksekliği (genellikle w ve h olarak belirtilir).
İşte bir Paint Hook'u oluşturmanın iki yoluna bir örnek Self Açık (Explicit)
veya self'in örtük (implicit) olması
Her iki seçenek de doğrudur ve kişisel tercihe bağlıdır. Self kullanımı hakkında daha fazla bilgi için Beginner_Tutorial_Self konusuna bakın.
Boyama Sistemi
Çizim için kullanacağınız araçlar her zaman Surface kütüphanesinden gelecektir. Bu kütüphane, istediğiniz her türlü kullanıcı arayüzünü oluşturmak için ihtiyacınız olan her şeyi sağlar. Ancak bazen daha karmaşık görevleri surface'ta yapmak zor olabilir, bu yüzden Draw kütüphanesi oluşturulmuştur. Bu kütüphane hala altta surface kütüphanesini kullanır ancak daha kullanışlı fonksiyonlara ve arka planda daha karmaşık surface hileleri yapan fonksiyonlara sahiptir. Genel olarak, bir şey sadece surface'ta yapılabiliyorsa, onu surface'ta yapın. Eğer bir şey karmaşık ve surface'ta yapması zorsa, draw'ı kullanın.
Burada size surface'ta mevcut olan bazı (hepsi değil) fonksiyonları sunuyoruz:
- Surface.SetDrawColor
- Surface.DrawRect
- Surface.SetFont
- Surface.SetTextColor
- Surface.SetTextPos
- Surface.DrawText
Surface.SetDrawColor ve Surface.SetFont gibi fonksiyonlarımız olduğunu fark edeceksiniz. Bunun nedeni, surface'ın surface.DrawRect gibi diğer tüm fonksiyonlarda kullanmak üzere bu fonksiyonlardan gelen son girdiyi saklayarak çalışmasıdır. Surface kullanarak çizim yapmaya çalışmadan önce her zaman rengi veya fontu ayarlamak isteyeceksiniz, çünkü bunu yapmamak yanlış renk/font çizilmesine neden olabilir.
İşte draw ile kullanılabilen bazı (hepsi değil) fonksiyonlar:
Burada bu fonksiyonların kaynak kodunu incelerseniz, hepsinin sadece bir dizi surface fonksiyonu olduğunu görürsünüz. Bu fonksiyonları saf surface formatında yazabilir ve aynı sonuçları alabilirsiniz, ancak bu kütüphanenin amacı bu daha karmaşık surface taktiklerinin kolaylığı ve kullanım rahatlığıdır.
Görünüm (Skinning) Sistemi
Oluşturduğunuz her bir panelin bir boyama fonksiyonu olacaktır, ancak bunları sırf tutarlı bir görünüm elde etmek için neredeyse aynı surface ve draw fonksiyonlarıyla doldurmak inanılmaz derecede tekrarlayıcı ve israf olabilir.
İşte bu noktada Skin'ler (Görünümler) devreye girer. Her Skin, tüm standart Derma elemanları için varsayılan görünümü ve hissi tanımlar. Standartlaştırılmış renkler, fontlar veya hatta düğmeler, etiketler ve çerçeveler gibi farklı türdeki elemanlar için varsayılan boyama fonksiyonları içerir.
Bütün bunlar derma.SkinHook kullanılarak yapılır, bazen Derma_Hook kullanılır ki bu da ilk fonksiyon için süslü bir sarmalayıcıdır (wrapper) ancak burada bahsedilmeyi gerektirecek kadar sık kullanılır.
Birçok varsayılan derma panelinin boyama fonksiyonlarının içinde bu fonksiyonlardan birini şu şekilde yazılmış bulacaksınız:
bu satır esasen mevcut skin'e bir düğme türü eleman için Paint işlemini nasıl gerçekleştireceğini sorar ve panel tablosunu ve boyutlarını iletir.
sonuç olarak skin içinde aşağıdaki fonksiyon çağrılır
Garry's Mod, varsayılan olarak bir skin ile gelir, default.lua skin'i. Bu, lua/skins/ içinde yer almaktadır. Bu skin hakkında daha ayrıntılı bir inceleme için Derma_Skin_Creation konusuna bakın.
Paint Hook'unu Kullanma
Önceki örneklerden aşağıdaki koda aşina olmalısınız:
Şimdi, bu belirli düğmeye (myButton) farklı bir Paint fonksiyonu kullanmasını söyleyelim.
Doğrudan myButton.Paint'e yeni bir fonksiyon atadık. Artık her karede, Garry's Mod varsayılan DButton boyama mantığını (ki bu derma.SkinHook kullanır) çağırmak yerine, bizim fonksiyonumuzu çağıracak.
Çizim kodunu özel Paint fonksiyonumuzun içine ekleyelim. Şunları istiyoruz:
- Normalde yeşil bir arka plan.
- Üzerine gelindiğinde daha parlak yeşil bir arka plan.
- Tıklandığında (basılı tutulduğunda) daha koyu yeşil bir arka plan.
- Beyaz metin.
Yeşil bir düğmesi olan bir pencere görmelisiniz. Farenizi üzerine getirdiğinizde parlak yeşile döner. Tıklayıp basılı tuttuğunuzda koyu yeşile döner. Metin baştan sona beyaz kalır. Varsayılan düğme görünümünü başarıyla geçersiz kıldınız!
Sonuç
Painting ve skinning konularını ele aldığımıza göre, Derma'ya giriş bilginizi tamamlamış bulunuyorsunuz. Umarız şimdiye kadar bazı temel UI elemanları oluşturmaya ve bunları kendi zevkinize göre özelleştirmeye başlamak için yeterli bilgi birikimine sahip olmuşsunuzdur.
Tebrikler ve Derma'ya hoş geldiniz!
Tüm sunucular · GmodTR Launcher · Rehber · GMod Wiki · GmodTR nedir? · Sunucu ekle · Discord