Dev oyuncu sınıflarından nasıl kaçınabilirim?


46

Bir oyunda neredeyse her zaman bir oyuncu sınıfı vardır. Oyuncu genellikle oyunda çok şey yapabilir, bu da benim için bu sınıfın, oyuncunun yapabileceği her bir işlevsellik parçasını desteklemek için bir ton değişkenle büyük olması anlamına geliyor. Her parça kendi başına oldukça küçük ama bir araya gelip binlerce kod satırına sahibim ve değişiklik yapmak için neye ihtiyaç duyduğunuzu bulmaktan korkutucu ve acı verici hale geliyor. Temel olarak tüm oyun için genel bir kontrol olan bir şeyle bu sorunu nasıl önlersiniz?


26
Birden fazla dosya veya bir dosya, kod bir yere gitmek zorunda. Oyunlar karmaşık. İhtiyacınız olanı bulmak için iyi yöntem adları ve tanımlayıcı yorumlar yazın. Değişiklik yapmaktan korkmayın - sadece test edin. Ve çalışmanızı yedekleyin :)
Chris McFarland

7
Bir yere gitmesi gerekiyor ama kod tasarımı esneklik ve bakım açısından önemli. Binlerce satırlık bir sınıf veya kod grubuna sahip olmak da beni etkilemez.
user441521,

17
@ChrisMcFarland yedekleme yapmayı önermez, XD sürüm kodunu önerin.
GameDeveloper

1
@ChrisMcFarland GameDeveloper ile aynı fikirdeyim. Git, svn, TFS, ... gibi sürüm kontrolüne sahip olmak, büyük değişiklikleri çok daha kolay geri almak ve projenizi, donanım arızasını veya dosya bozulmasını yanlışlıkla silmek gibi şeylerden kolayca kurtarabilmeniz nedeniyle gelişimi çok daha kolaylaştırır.
Nzall

3
@ TylerH: Kesinlikle katılmıyorum. Yedeklemeler, birçok keşif değişikliğini bir araya getirmeye izin vermiyor, değişiklik setlerine çok yakın herhangi bir meta veriyi bağlayamıyor, aynı zamanda çok geliştiricili iş akışlarına izin vermiyor. Sürüm kontrolünü çok güçlü bir nokta içi yedekleme sistemi gibi kullanabilirsiniz, ancak bu araçların potansiyelinin çoğunu kaçırıyor.
Phoshi

Yanıtlar:


67

Genellikle bir varlık bileşen sistemi kullanırsınız (Bir varlık bileşen sistemi, bileşen tabanlı bir mimaridir). Bu, başka varlıklar yaratmayı da kolaylaştırır ve ayrıca düşmanları / NPC'leri oynatıcı ile aynı bileşenlere sahip kılabilir.

Bu yaklaşım, nesneye yönelik bir yaklaşım olarak tam tersi yönde ilerler. Oyunda her şey bir varlıktır. Varlık, içinde herhangi bir oyun mekaniği olmayan bir durumdur. Bileşenlerin bir listesi ve bunları manipüle etmenin bir yolu var.

Örneğin, oynatıcı bir konum bileşenine, bir animasyon bileşenine ve bir giriş bileşenine sahiptir ve kullanıcı alana bastığında, oynatıcının zıplamasını istersiniz.

Bunu oyuncuya, atlama denilen animatiom bileşenini atlama animasyonunda değiştiren ve oyuncuya pozisyon bileşeninde pozitif bir hıza sahip kılan bir atlama bileşeni vererek başarabilirsiniz. Giriş bileşeninde boşluk tuşunu dinlersiniz ve atlama bileşenini çağırırsınız. (Bu sadece bir örnek, hareket için bir kontrolör bileşenine sahip olmalısınız).

Bu, kodun daha küçük, yeniden kullanılabilir modüllere bölünmesine yardımcı olur ve daha organize bir projeyle sonuçlanabilir.


Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
MichaelHouse

8
Taşınması gereken yorumları taşımayı anlarken, cevabın doğruluğunu sorgulayanları da taşımayın. Bu açık olmalı, değil mi?
bug-a-lot

20

Oyunlar bu konuda benzersiz değil; tanrı sınıfları her yerde anti-paterndir.

Ortak bir çözüm, büyük sınıfı daha küçük sınıflardan oluşan bir ağaçta parçalamaktır. Oyuncunun bir envanteri varsa, envanter yönetimini bir parçası yapmayın class Player. Bunun yerine, bir oluşturun class Inventory. Bu bir üye class Player, ancak dahili olarak class Inventoryçok fazla kod sarabilir.

Başka bir örnek: Bir oyuncu karakterinin NPC'lerle ilişkileri olabilir, bu nedenle class Relationhem Playernesneyi hem de nesneyi referansta NPCbuluyorsunuz, ancak hiçbirine ait değilsiniz .


Evet, bunun nasıl yapılacağına dair fikirler arıyordum. Zihniyet neydi, çünkü kodlamanın birçoğu küçük bir işlevsellik içerdiğinden, kodlamanın doğal olmasının yanı sıra, benim için, o küçük işlevsellik parçalarını kırmak benim için hiç de doğal değildi. Ancak, tüm bu küçük işlevsellik parçalarının oyuncu sınıfını devasa yapmaya başladığı ortaya çıkıyor.
user441521

1
İnsanlar genellikle bir şeyin bir tanrı sınıfı veya tanrı nesnesi olduğunu söyler, oyunda her diğer sınıfı / nesneyi içerdiğinde ve yönetdiğinde.
Bálint

11

1) Oyuncu: Devlet makine + bileşen tabanlı mimari.

Player için olağan bileşenler: HealthSystem, MovementSystem, InventorySystem, ActionSystem. Bunların hepsi gibi sınıflar class HealthSystem.

Update()Orada kullanılmasını tavsiye etmiyorum (Her zamanki gibi bazı eylemler için ihtiyaç duymadığınız sürece, sağlık sistemindeki güncellemelerden bir anlam çıkarmazlar. Bunlar nadiren meydana gelir. Ayrıca düşünebileceğiniz bir durum - oyuncu zehirlenir ve ona ihtiyacınız olur. zaman zaman sağlık kaybediyor - burada coroutines kullanmayı öneriyorum.Başka bir sağlık veya çalışan gücü sürekli yenilerken, sadece şu anki sağlığı veya gücü alırsınız ve zaman geldiğinde o seviyeye kadar doldurmak için koroini çağırırsınız. Hasar gördü ya da tekrar koşmaya başladı .. Tamam, bu biraz kapalıydı ama umarım faydalı olmuştur) .

Durumlar: LootState, RunState, WalkState, AttackState, IDLEState.

Her devlet miras kalır interface IState. IStateÖrneğimizde sadece örnek olarak 4 yöntem var.Loot() Run() Walk() Attack()

Ayrıca, class InputControllerher kullanıcının Girişini kontrol ettiğimiz bir yere sahibiz.

Şimdi gerçek örnek olarak: InputControllerOyuncunun herhangi bir tuşa basıp basmadığını WASD or arrowsve sonra da o tuşuna basıp basmadığını kontrol ederiz Shift. O sadece basılırsa WASDo zaman diyoruz _currentPlayerState.Walk();bu happends ve sahip olduğumuz currentPlayerStateeşit olacak şekilde WalkStatedaha sonra WalkState.Walk() biz tüm bileşenlerin bu devlet için gerekli olan - bu durumda MovementSystembiz oyuncu hamle yapmak, böylece public void Walk() { _playerMovementSystem.Walk(); }sen burada ne var bakın? - İkinci davranış katmanımız var ve bu kod sürdürme ve hata ayıklama için çok iyi.

Şimdi ikinci davaya: peki ya WASD+ Shiftbasarsak? Ama önceki durumumuz öyleydi WalkState. Bu durumda Run()haber verilecek InputController(bu kadar karışmaz, Run()biz var çünkü denir WASD+ Shiftcheck InputControllerdeğil çünkü WalkState). Dediğimiz zaman _currentPlayerState.Run();içinde WalkState- biz anahtara sahip olduğunu biliyoruz _currentPlayerStateiçin RunStateve biz de bunu Run()bir WalkStateve biz kaybetmek eyleme bu çerçeveyi istemiyoruz çünkü farklı bir devlet ile artık bu yöntem içinde tekrar diyoruz ama. Ve şimdi elbette ararız _playerMovementSystem.Run();.

Ancak LootStateoyuncu düğmeyi bırakana kadar yürüyemiyor veya koşamıyorsa ne olacak? Eh, bu durumda yağmalamaya başladığımızda, örneğin düğmeye Ebasıldığında çağrı _currentPlayerState.Loot();yaparız LootStateve şimdi oradan çağırırız. Orada örneğin, menzil içinde yağma yapacak bir şey varsa almak için toplama yöntemini çağırırız. Animasyonun olduğu ya da başlattığımız yer olan coroutine'i çağırıyoruz ve koroutinin kırılmadığı sürece, oynatıcının hala düğmeyi tutup tutmadığını kontrol ediyoruz. Ama ya oyuncu basarsa WASD? - _currentPlayerState.Walk();denir, ama işte devlet makineyle ilgili güzel şey,LootState.Walk()Hiçbir şey yapmayan veya bir özellik olarak yaptığım gibi boş bir yöntemimiz var - oyuncular şöyle diyor: "Hey dostum, bunu henüz yağmalamadım, bekleyebilir misin?" Yağmalamayı bitirdiğinde, değişiyoruz IDLEState.

Ayrıca, class BaseState : IStatetüm bu varsayılan yöntemler davranışının uygulandığı, ancak bunları sınıf türlerinde virtualyapabilmeniz overrideiçin çağrılan başka bir komut dosyası da yapabilirsiniz class LootState : BaseState.


Bileşen tabanlı sistem harika, beni rahatsız eden tek şey, birçoğu Örnekler. Ve daha fazla hafıza alır ve çöp toplayıcı için çalışır. Örneğin, 1000 düşman örneğiniz varsa. Hepsinde 4 bileşen var. 1000 yerine 1000 nesne. Mb, birlik gameobject'in tüm bileşenlerini göz önünde bulundurursak, çok büyük bir şey değil (performans testleri yapmadım).


2) Kalıtım temelli mimari. Her ne kadar bileşenlerden tamamen kurtulamayacağımızı fark edeceksin - temiz ve çalışma koduna sahip olmak istiyorsak bu imkansız. Ayrıca, uygun durumlarda kullanılması şiddetle tavsiye edilen Tasarım Kalıplarını kullanmak istiyorsak (bunları fazla kullanmayın, buna aşırı lisans denir).

Bir oyunda çıkması gereken tüm özelliklere sahip bir Player sınıfımız olduğunu hayal edin. Sağlığı, manası veya enerjisi vardır, yeteneklerini taşıyabilir, çalıştırabilir ve kullanabilir, bir envantere sahip olabilir, eşya üretebilir, eşyaları yağmalayabilir, hatta bazı barikatlar veya taretler inşa edebilir.

Öncelikle, Envanter, Zanaat, Hareket, Yapı bileşenlerine dayalı olması gerektiğini söyleyeceğim, çünkü böyle AddItemToInventoryArray()bir yönteme sahip olmak oyuncunun sorumluluğu değil - oyuncu PutItemToInventory()daha önce tarif edilen yöntemi çağıracak bir yönteme sahip olsa da (2 kat - farklı katmanlara bağlı olarak bazı koşullar ekleyin).

Bina ile başka bir örnek. Oyuncu gibi bir şey arayabilir OpenBuildingWindow(), ancak Buildinggeri kalan her şeyle ilgilenir ve kullanıcı belirli bir bina oluşturmaya karar verdiğinde, oyuncuya gerekli tüm bilgileri iletir Build(BuildingInfo someBuildingInfo)ve oyuncu gerekli tüm animasyonlarla oluşturmaya başlar.

KATI - OOP prensipleri. S - tek sorumluluk: önceki örneklerde gördüklerimiz. Evet, tamam ama Miras nerede?

Burada: oyuncunun sağlığı ve diğer özellikleri başka bir işletme tarafından ele alınmalı mı? Bence değil. Sağlığı olmayan bir oyuncu olamaz, eğer bir tane varsa, kalıtımsal değiliz. Örneğin, elimizdeki IDamagable, LivingEntity, IGameActor, GameActor. IDamagableTabii ki TakeDamage().

class LivinEntity : IDamagable {

   private float _health; // For fields that are the same between Instances I would use Flyweight Pattern.

   public void TakeDamage() {
       ....
   }
}

class GameActor : LivingEntity, IGameActor {
    // Here goes state machine and other attached components needed.
}

class Player : GameActor {
   // Inventory, Building, Crafting.... components.
}

Yani burada bileşenleri mirastan ayırmadım ama gördüğünüz gibi onları karıştırabiliriz. Ayrıca, örneğin farklı sistemlerimiz varsa ve gerekenden daha fazla kod yazmak istemiyorsak, örneğin Bina sistemi için bazı temel sınıflar oluşturabiliriz. Aslında, farklı tipte binalara da sahip olabiliriz ve aslında bu yapıyı temel alarak yapmanın iyi bir yolu yok!

OrganicBuilding : Building, TechBuilding : Building. Genel işlemler veya bina özellikleri için 2 bileşen oluşturup oraya iki kez kod yazmanıza gerek yoktur. Ve sonra onları farklı bir şekilde ekleyin, kalıtım gücünü ve daha sonra polimorfizm ve kapsülleme gücünü kullanabilirsiniz.


Arada bir şeyler kullanmanızı öneririm. Ve bileşenlerin fazla kullanılmaması.


Oyun Programlama Kalıpları hakkında bu kitabı okumanı şiddetle tavsiye ediyorum - WEB'de ücretsiz.


Bu gece daha sonra kazacağım ama FYI birliği kullanmıyorum bu yüzden bazılarını iyi ayarlamalıyım.
user441521

Oh, sry burada bir Birlik etiketi olduğunu düşündüm, benim hatam. Tek şey MonoBehavior - sadece Unity editöründeki sahnedeki her Örnek için temel bir sınıftır. Physics.OverlapSphere () 'a gelince - çerçeve boyunca küre çarpıştırıcısı yaratan ve dokunduğu dokunuşları kontrol eden bir yöntem. Coroutines sahte Güncelleme gibi, onların aramaları oyuncu PC'de fps daha küçük miktarlara azaltılabilir - performans için iyi. Start () - Örnek oluşturulduğunda yalnızca bir kez çağrılan bir yöntem. Her şey başka her yere uygulanmalı. Bir sonraki bölümde Unity ile hiçbir şey kullanmayacağım. Üzgünüm. Umarım bu bir şeyi netleştirmiştir.
Candid Moon _Max_

Birliği daha önce kullandım, bu yüzden fikri anlıyorum. Ayrıca coroutinleri olan Lua'yı kullanıyorum, bu yüzden işler oldukça iyi çevirmelidir.
kullanici441521

Unity etiketinin eksikliği göz önüne alındığında, bu cevap biraz Unity'e özgü görünüyor. Daha genel hale getirip birliği daha iyi bir örnek haline getirdiyseniz, bu çok daha iyi bir cevap olurdu.
Pharap

@CandidMoon Evet, bu daha iyi.
Pharap

4

Bu soruna gümüş mermi yoktur, ancak neredeyse tamamı “kaygıların ayrılması” ilkesi etrafında dönen farklı yaklaşımlar vardır. Diğer cevaplar, popüler bileşen tabanlı yaklaşımı zaten tartıştı, ancak bileşen tabanlı çözüm yerine veya bununla birlikte kullanılabilecek başka yaklaşımlar da var. Varlık denetleyici yaklaşımını tartışacağım çünkü bu sorun için tercih ettiğim çözümlerden biri.

Birincisi, bir Playersınıf fikri en başta yanıltıcıdır. Pek çok insan aslında bir çok karakterin ortak olduğu bir oyuncu karakteri, npc karakterleri ve canavarları / düşmanları farklı sınıflar olarak düşünmeye meyillidirler: hepsi ekranda çekilir, hepsi taşınabilir, hepsinin envanteri vb. var.

Bu düşünce tarzı, oyuncu karakterlerinin, oyuncu olmayan karakterlerin ve canavarların / düşmanların Entityfarklı muamele görmekten çok ' s' olarak değerlendirildiği bir yaklaşıma yol açar . Tabii ki, farklı davranmaları gerekiyor - oyuncu karakterinin giriş üzerinden kontrol edilmesi gerekiyor ve npcs ai'ye ihtiyaç duyuyor.

Bunun çözümü s'yi Controllerkontrol etmek için kullanılan sınıflara sahip olmaktır Entity. Bunu yaparak, tüm ağır mantık denetleyicide sona erer ve tüm veriler ve ortaklıklar varlıkta depolanır.

Ek olarak, alt sınıflara Controllerayrılarak InputControllerve AIControlleroyuncunun Entityodadaki herhangi birini etkin bir şekilde kontrol etmesini sağlar . Bu yaklaşım aynı zamanda , bir ağ akışından gelen komutlarla çalışan bir RemoteControllerveya NetworkControllersınıfına sahip olarak çok oyunculu oyuncuya yardımcı olur .

Bu, Controllereğer dikkatli olmazsanız , mantığın birçoğunun içine atlanmasına neden olabilir. Bundan kaçınmanın yolu Controller, diğerlerinden oluşmuş olanlara sahip olmak Controllerya da Controllerişlevselliği sağlamak, çeşitli özelliklerine bağlıdır Controller. Örneğin, AIControllerbuna DecisionTreebağlı olacaktı ve a , a ( OnGround, Artan ve Azalan durumlarını içeren bir durum makinesi içeren) gibi PlayerCharacterControllerçeşitli diğer öğelerden oluşabilir . Bunun ek bir yararı, yeni özellikler eklendikçe yenilerin eklenmesi olabilir - bir oyun envanter sistemi olmadan başlar ve bir tane eklenirse, bunun için bir denetleyici daha sonra ele alınabilir.ControllerMovementControllerJumpControllerInventoryUIControllerController


Bunun fikrini seviyorum ama aynı kodu bırakarak tüm kodu kontrolör sınıfına transfer etmiş gibi görünüyor.
user441521,

@ user441521 Yeni ekleyeceğim ek bir paragraf olduğunu fark ettim ancak tarayıcım kilitlendiğinde kaybettim. Şimdi ekleyeceğim. Temel olarak, farklı denetleyicilerin bunları birleşik denetleyicilere oluşturabilmesi için her denetleyicinin farklı şeyleri işleyebilmesini sağlayabilirsiniz. örn. AggregateController.Controllers = {JumpController (keybinds), MoveController (keybinds), EnvanterUIController (keybinds, uisystem)}
Pharap
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.