Aşağıdaki özelliklere sahip bir varlık sistemi varyantı uyguluyorum:
Bileşenleri birbirine bağlayan bir kimlikten biraz daha küçük olan bir varlık sınıfı
"Bileşen mantığı" olmayan bir grup bileşen sınıfı , sadece veri
Bir grup sistem sınıfı ("alt sistemler", "yöneticiler"). Bunlar tüm varlık mantık işlemlerini yapar. Çoğu temel durumda, sistemler sadece ilgilendikleri varlıkların listesini yineler ve her biri için bir eylemde bulunur
Tüm oyun sistemleri tarafından paylaşılan bir MessageChannel sınıfı nesnesi . Her sistem dinlemek için belirli mesaj türlerine abone olabilir ve aynı zamanda diğer sistemlere mesaj yayınlamak için kanalı kullanabilir.
Sistem mesajı işlemenin ilk değişkeni şuydu:
- Her oyun sisteminde art arda bir güncelleme çalıştırın
Bir sistem bir bileşene bir şey yaparsa ve bu işlem diğer sistemlere ilgi gösteriyorsa, sistem uygun bir mesaj gönderir (örneğin, bir sistem
messageChannel.Broadcast(new EntityMovedMessage(entity, oldPosition, newPosition))
bir varlık ne zaman taşınırsa)
Belirli bir mesaja abone olan her sistem, onun adı verilen mesaj işleme yöntemini alır.
Bir sistem bir olayla ilgileniyorsa ve olay işleme mantığı başka bir mesajın yayınlanmasını gerektiriyorsa, mesaj hemen yayınlanır ve başka bir mesaj işleme yöntemi zinciri çağrılır
Bu değişken çarpışma algılama sistemini optimize etmeye başlayana kadar iyiydi (varlık sayısı arttıkça yavaşlıyordu). İlk başta basit bir kaba kuvvet algoritması kullanarak her varlık çiftini yinelemeliydi. Daha sonra, belirli bir hücrenin alanı içindeki varlıkları depolayan bir hücre ızgarasına sahip bir "uzamsal indeksi" ekledim, böylece sadece komşu hücrelerdeki varlıklar üzerinde kontroller yapmayı sağladım.
Bir işletme her hareket ettiğinde, çarpışma sistemi işletmenin yeni pozisyonda bir şeyle çarpışıp çarpışmadığını kontrol eder. Öyleyse, bir çarpışma algılanır. Ve eğer her iki çarpışan varlık "fiziksel nesneler" ise (her ikisi de RigidBody bileşenine sahipler ve aynı alanı işgal etmeyecek şekilde birbirlerini uzağa itmek istiyorlarsa), özel bir katı cisim ayrıştırma sistemi, hareket sistemini varlıklardan bazılarına taşımasını ister. onları ayıracak belirli pozisyonlar. Bu da hareket sisteminin değişen varlık pozisyonları hakkında bildirimde bulunan mesajlar göndermesine neden olur. Çarpışma algılama sistemi tepki vermek içindir, çünkü uzaysal endeksini güncellemesi gerekir.
Bazı durumlarda, hücre içeriği (C # 'daki genel bir Varlık nesneleri Listesi) yinelenirken değiştirildiğinden ve yineleyici tarafından atılmasının bir istisna olmasına neden olduğu için bir soruna neden olur.
Peki ... çarpışma kontrolü yapılırken çarpışma sisteminin durmasını nasıl önleyebilirim?
Tabii ki hücre içeriğinin doğru bir şekilde yinelenmesini sağlayan bazı "akıllı" / "zor" bir mantık ekleyebilirim, ancak sorunun çarpışma sisteminde değil (diğer sistemlerde de benzer problemler yaşadım) olduğunu düşünüyorum. mesajlar sistemden sisteme giderken ele alınır. İhtiyacım olan, belirli bir olay işleme yönteminin herhangi bir kesinti olmadan işini yapmasını sağlamak için bir yol.
Ne denedim:
- Gelen mesaj kuyrukları . Bazı sistemler bir mesajı her yayınladığında, mesaj, ilgilenen sistemlerin mesaj sıralarına eklenir. Bu mesajlar her kareye bir sistem güncellemesi çağrıldığında işlenir. Sorun : eğer sistem A sistemin B kuyruğuna bir mesaj eklerse, eğer sistem B'nin sistem A'dan daha sonra güncellenmesi gerekiyorsa iyi çalışır (aynı oyun çerçevesinde); aksi takdirde mesajın bir sonraki oyun karesini işlemesine neden olur (bazı sistemler için istenmez)
- Giden mesaj kuyrukları . Bir sistem bir olayı ele alırken yayınladığı tüm mesajlar giden mesaj sırasına eklenir. İletilerin bir sistem güncellemesinin işlenmesini beklemesi gerekmez: ilk ileti işleyicisinin çalışması bittikten sonra "derhal" ele alınırlar. Mesajların kullanılması diğer mesajların yayınlanmasına neden olursa, onlar da giden bir kuyruğa eklenir, böylece tüm mesajlar aynı çerçevede ele alınır. Sorun: eğer varlık ömür boyu sistemi (bir sistemle varlık ömür boyu yönetimini uyguladım) bir varlık yaratırsa, bazı A ve B sistemlerini bu konuda bilgilendirir. Sistem A mesajı iletirken, sonuçta yaratılan varlığın tahrip olmasına neden olan bir mesaj zincirine neden olur (örneğin, bir mermi varlığının tam olarak bir engelle çarpıştığı ve merminin kendi kendini yok etmesine neden olduğu yerde yaratıldığı). Mesaj zinciri çözülürken, B sistemi varlık oluşturma mesajını alamaz. Böylece, eğer B sistemi varlık imha mesajıyla da ilgileniyorsa, onu alır ve sadece "zincir" çözüldükten sonra, başlangıç varlık oluşturma mesajını alır. Bu, imha mesajının göz ardı edilmesine, yaratma mesajının "kabul edilmesine" neden olur,
EDIT - SORULAR VE CEVAPLAR YANITLAR:
- Çarpışma sistemi üzerindeyken hücrenin içeriğini kim değiştirir?
Çarpışma sistemi bazı varlıklar ve komşuları üzerinde çarpışma kontrolleri yaparken, bir çarpışma algılanabilir ve varlık sistemi hemen başka sistemler tarafından tepki verilecek bir mesaj gönderir. Mesaja verilen tepki, diğer mesajların oluşturulmasına ve hemen ele alınmasına neden olabilir. Bu nedenle diğer bazı sistemler çarpışma sisteminin daha sonra hemen işlemesi gerektiğine dair bir mesaj oluşturabilir (örneğin, bir varlık taşındı, böylece çarpışma sistemi mekansal endeksini güncellemesi gerekiyordu), önceki çarpışma kontrolleri henüz bitmedi.
- Global bir giden mesaj sırası ile çalışamaz mısın?
Son zamanlarda tek bir küresel kuyruk denedim. Yeni sorunlara neden olur. Sorun: Bir tank varlığını bir duvar varlığına taşırım (tank klavyeyle kontrol edilir). Sonra tankın yönünü değiştirmeye karar verdim. Tankı ve duvarı her çerçeveye ayırmak için, CollidingRigidBodySeparationSystem, tankı duvardan mümkün olan en küçük miktarda uzaklaştırır. Ayırma yönü, tankın hareket yönünün tersi olmalıdır (oyun çizimi başladığında, tank hiç bir zaman duvara hareket etmemiş gibi görünmelidir). Ancak, yön YENİ yönün tersi olur, böylece tankı başlangıçta olduğundan farklı bir duvar tarafına hareket ettirir. Neden sorun ortaya çıkıyor: Mesajların şu anda nasıl ele alındığı (basitleştirilmiş kod):
public void Update(int deltaTime)
{
m_messageQueue.Enqueue(new TimePassedMessage(deltaTime));
while (m_messageQueue.Count > 0)
{
Message message = m_messageQueue.Dequeue();
this.Broadcast(message);
}
}
private void Broadcast(Message message)
{
if (m_messageListenersByMessageType.ContainsKey(message.GetType()))
{
// NOTE: all IMessageListener objects here are systems.
List<IMessageListener> messageListeners = m_messageListenersByMessageType[message.GetType()];
foreach (IMessageListener listener in messageListeners)
{
listener.ReceiveMessage(message);
}
}
}
Kod böyle akıyor (ilk oyun karesi olmadığını varsayalım):
- Sistemler TimePassedMessage'ı işlemeye başlar
- InputHandingSystem tuş basmalarını varlık hareketine dönüştürür (bu durumda, sol ok MoveWest hareketine dönüşür). Varlık işlemi ActionExecutor bileşeninde saklanır
- ActionExecutionSystem , varlık eylemine tepki olarak, mesaj sırasının sonuna bir MovementDirectionChangeRequestedMessage ekler
- MovementSystem , Velocity bileşen verisine dayanarak varlık pozisyonunu taşır ve sıranın sonuna PositionChangedMessage mesajı ekler. Hareket önceki karenin hareket yönü / hızı kullanılarak yapılır (kuzeye diyelim)
- Sistemler TimePassedMessage işlemeyi durduruyor
- Sistemler MovementDirectionChangeRequestedMessage işlemeye başlar
- MovementSystem varlık hızını / hareket yönünü istendiği gibi değiştirir
- Sistemler işlemeyi durdurur MovementDirectionChangeRequestedMessage
- Sistemler PositionChangedMessage'ı işlemeye başlar
- CollisionDetectionSystem bir varlığın taşınması nedeniyle başka bir varlığa çarptığını tespit etti (tank bir duvarın içine girdi). Sıraya bir CollisionOccuredMessage ekler
- Sistemler PositionChangedMessage işlemini durdurur
- Sistemler CollisionOccuredMessage'ı işlemeye başlar
- ÇarpışanRigidBodySeparationSystem, tankı ve duvarı ayırarak çarpışmaya tepki verir. Duvar statik olduğundan, sadece tank hareket ettirilir. Tankların hareket yönü, tankın nereden geldiğinin bir göstergesi olarak kullanılır. Ters yönde dengelenmiş
HATA: Tank bu çerçeveyi hareket ettirdiğinde, önceki çerçeveden hareket yönü kullanılarak hareket etti, ancak ayrıldığı zaman, THIS çerçevesinden hareket yönü, zaten farklı olsa bile kullanıldı. Böyle çalışması gerekmiyor!
Bu hatayı önlemek için eski hareket yönünün bir yere kaydedilmesi gerekir. Sadece bu hatayı düzeltmek için bazı bileşenlere ekleyebilirim, ancak bu durum mesajların temelde yanlış bir şekilde kullanıldığını göstermiyor mu? Ayırma sistemi neden kullandığı hareket yönünü önemsiyor? Bu sorunu zarif bir şekilde nasıl çözebilirim?
- Gördüğünüz sorunların bir kısmını hangi tarafa attığını belirlemek için, Aspect’le neler yaptıklarını görmek için gamadu.com/artemis sayfasını okumak isteyebilirsiniz.
Aslında, bir süredir Artemis ile tanıştım. Kaynak kodunu araştırdı, forumları vb. Okudum. Ama "Yönler" in yalnızca birkaç yerde bahsedildiğini gördüm ve anladığım kadarıyla temelde "Sistemler" anlamına geliyorlar. Ancak Artemis'in bazı sorunlarımı nasıl yönlendirdiğini anlayamıyorum. Mesaj bile kullanmıyor.
- Ayrıca bakınız: "Varlık iletişimi: Mesaj kuyruğu - Yayın / Abone vs Signal / Slots"
Varlık sistemleriyle ilgili tüm gamedev.stackexchange sorularını zaten okudum. Bu, karşılaştığım sorunları tartışmıyor gibi görünüyor. Bir şey mi kaçırıyorum?
- İki vakayı farklı ele alın, ızgarayı güncellemek çarpışma sisteminin bir parçası olduğu için hareket mesajlarına dayanması gerekmez
Neyi kastettiğinden emin değilim. Eski CollisionDetectionSystem uygulamaları bir güncelleme üzerindeki çarpışmaları kontrol eder (bir TimePassedMessage kullanıldığında), ancak performans nedeniyle çekleri en aza indirmem gerekiyordu. Bu yüzden, bir varlık hareket ettiğinde çarpışma kontrolüne geçtim (oyunumdaki çoğu varlık statiktir).