Netcode nasıl kullanılır?


10

Ben netcode bir oyun motoru "içine" olabilir farklı yolları değerlendirmek ilgileniyorum. Şimdi çok oyunculu bir oyun tasarlıyorum ve şimdiye kadar (en azından) ağ soketlerini işlemek için, grafik döngüsünü ve komut dosyalarını işleyen motorun geri kalanından farklı olarak ayrı bir iş parçacığına ihtiyacım olduğunu belirledim.

Ağa bağlı bir oyunu tamamen tek iş parçacıklı hale getirmenin potansiyel bir yolu vardı, bu da her kareyi oluşturduktan sonra ağları kontrol etmeyi engellemeyen soketler kullanarak kontrol ettirmek. Ancak, ağ gecikmesine bir çerçeve oluşturmak için gereken süre eklendiğinden, bu açık bir şekilde uygun değildir: Ağ üzerinden gelen iletilerin, geçerli çerçeve oluşturma (ve oyun mantığı) tamamlanana kadar beklemesi gerekir. Ancak, en azından bu şekilde oyun, az ya da çok, pürüzsüz olmaya devam edecekti.

Ağ için ayrı bir iş parçacığına sahip olmak, oyunun ağa tamamen yanıt vermesini sağlar, örneğin sunucudan bir durum güncellemesi alındıktan sonra bir ACK paketini anında geri gönderebilir. Ancak oyun kodu ile ağ kodu arasındaki iletişimin en iyi yolu hakkında biraz kafam karıştı. Ağ iş parçacığı, alınan paketi bir kuyruğa zorlar ve oyun iş parçacığı döngü sırasında uygun zamanda kuyruktan okur, bu nedenle bu bire bir kare gecikmeden kurtulduk.

Ayrıca, paketlerin gönderilmesini işleyen ipliğin borudan gelen paketleri kontrol edenlerden ayrı olmasını isterim, çünkü ortasındayken bir tane gönderemezdi Gelen mesaj olup olmadığını kontrol etme. Ben onun selectbenzerliğini veya işlevini düşünüyorum .

Sanırım sorum şu, oyunu en iyi ağ yanıt hızı için tasarlamanın en iyi yolu nedir? Açıkça istemci sunucuya mümkün olan en kısa sürede kullanıcı girişi göndermelidir, bu yüzden her iki oyun döngüsünün içinde de olay işleme döngüsünden hemen sonra net gönderme kodunu alabilirim. Bu bir anlam ifade ediyor mu?

Yanıtlar:


13

Duyarlılığı yoksay. LAN'da ping önemsizdir. İnternette, 60-100 ms gidiş-dönüş bir nimettir. > 3K ani yükselmediğiniz lag tanrılarına dua edin. Bunun bir sorun olması için yazılımınızın çok düşük sayıda güncelleme / saniye hızında çalışması gerekir. 25 güncelleme / saniye çekim yaparsanız, bir paket aldığınızda ve üzerinde hareket ettiğinizde maksimum 40 ms süreniz vardır. Ve bu tek iş parçacıklı durum ...

Sisteminizi esneklik ve doğruluk için tasarlayın. İşte bir ağ alt sistemini oyun koduna nasıl bağlayacağım konusundaki fikrim: Mesajlaşma. Bir çok sorunun çözümü "mesajlaşma" olabilir. Sanırım laboratuvar farelerinde mesajlaşmayı iyileştiren kanser. Mesajlaşma, araba sigortamda bana 200 $ veya daha fazla tasarruf sağlıyor. Ancak, ciddi olarak, mesajlaşma muhtemelen iki bağımsız alt sistemi korurken, herhangi bir alt sistemi oyun koduna eklemenin en iyi yoludur.

Ağ alt sistemi ile oyun motoru arasındaki herhangi bir iletişim ve iki alt sistem arasındaki iletişim için mesajlaşmayı kullanın. Sistemler arası mesajlaşma, std :: list kullanılarak işaretçi tarafından iletilen bir veri bloğu olarak basit olabilir.

Giden mesajlar kuyruğuna ve ağ alt sistemindeki oyun motoruna bir referansa sahip olmanız yeterlidir. Oyun, giden kuyruğa gönderilmesini istediği iletileri dökebilir ve otomatik olarak gönderilmesini sağlayabilir veya belki "flushMessages ()" gibi bir işlev çağrıldığında. Oyun motorunun büyük, paylaşılan bir mesaj kuyruğu varsa, mesaj göndermek için gereken tüm alt sistemler (mantık, AI, fizik, ağ, vb.), Ana oyun döngüsünün tüm mesajları okuyabileceği tüm iletileri iletebilir. ve buna göre hareket edin.

Gerekli olmasa da, soketleri başka bir iş parçacığında çalıştırmanın iyi olduğunu söyleyebilirim. Bu tasarımdaki tek sorun, genellikle eşzamansız olmasıdır (paketlerin ne zaman gönderildiğini tam olarak bilmiyorsunuz) ve hata ayıklamayı ve zamanlama ile ilgili sorunların rastgele görünmesini / kaybolmasını zorlaştırabilmesidir. Yine de, uygun şekilde yapılırsa, bunların hiçbiri bir sorun olmamalıdır.

Daha yüksek bir seviyeden, oyun motorunun kendisinden ayrı bir ağ olduğunu söyleyebilirim. Oyun motoru soketleri veya tamponları umursamıyor, olayları önemsiyor. Olaylar "Oyuncu X bir şut attı" "T maçında bir patlama oldu" gibi şeyler. Bunlar doğrudan oyun motoru tarafından yorumlanabilir. Nereden üretildikleri (bir senaryo, bir müşterinin eylemi, bir AI oynatıcı, vb.) Önemli değil.

Ağ alt sisteminize olay gönderme / alma aracı olarak davranırsanız, yalnızca bir yuvada recv () çağrısı yapmanın bir takım avantajları elde edersiniz.

Örneğin, 50 küçük ileti (1-32 bayt uzunluğunda) alarak bant genişliğini optimize edebilir ve ağ alt sisteminin bunları büyük bir pakete paketlemesini ve göndermesini sağlayabilirsiniz. Belki de büyük bir şey olsaydı göndermeden önce onları sıkıştırabilir. Diğer tarafta, kod, oyun paketinin okuması için büyük paketi tekrar 50 ayrı olayda açabilir / paketten çıkarabilir. Tüm bunlar şeffaf bir şekilde gerçekleşebilir.

Diğer harika şeyler, saf bir istemciye + aynı makinede çalışan saf bir sunucuya sahip olarak paylaşılan bellek alanında mesajlaşma yoluyla iletişim kurarak ağ kodunuzu yeniden kullanan tek oyunculu oyun modunu içerir. Daha sonra, tek oyunculu oyununuz düzgün çalışırsa, uzak bir istemci (yani gerçek çok oyunculu) da çalışır. Ayrıca, tek oyunculu oyununuz doğru görüneceği veya tamamen yanlış olacağı için, müşteri tarafından hangi verilerin gerekli olduğunu önceden düşünmeye zorlar. Karıştırın ve eşleştirin, bir sunucu çalıştırın VE çok oyunculu bir oyunda müşteri olun - hepsi de aynı şekilde çalışır.


Basit bir std :: listesi veya mesaj iletmek için böyle bir şey kullanarak bahsettiniz. Bu StackOverflow için bir konu olabilir, ancak iş parçacıklarının aynı adres alanını paylaştığı doğrudur ve aynı anda sıraya ait bellekle birden çok iş parçacığının vidalanmasını aynı anda tuttuğum sürece, iyi olmalıyım? Sadece sıradaki gibi sıraya veri ayırabilir ve sadece bazı muteksler kullanabilir miyim?
Steven Lu

Evet doğru. Std :: list ile yapılan tüm çağrıları koruyan bir muteks.
PatrickB

Cevabınız için teşekkürler! Şu ana kadar iş parçacıklarımda çok ilerleme kaydediyorum. Kendi oyun motoruma sahip olmak harika bir duygu!
Steven Lu

4
Bu his yıpranacaktır. Ancak kazandığınız büyük pirinç olanlar yanınızda.
ChrisE

@StevenLu Biraz [son derece] geç, ama iş parçacıklarının aynı anda bellekle vidalanmasını önlemenin olabileceğini belirtmek istiyorum nasıl yapmaya çalıştığınıza ve ne kadar verimli olmak istediğinize bağlı olarak, son derece zor belirtmek istiyorum. Bunu bugün yapsaydınız, sizi birçok mükemmel açık kaynaklı eşzamanlı kuyruk uygulamasından birine yönlendirirdim, bu yüzden karmaşık bir tekerleği yeniden keşfetmenize gerek yok.
Monica'nın Davası

4

(En azından) ağ soketlerini işlemek için ayrı bir iş parçacığına ihtiyacım var

Hayır.

Ağa bağlı bir oyunu tamamen tek iş parçacıklı hale getirmenin potansiyel bir yolu vardı, bu da her kareyi oluşturduktan sonra ağları kontrol etmeyi engellemeyen soketler kullanarak kontrol ettirmek. Bununla birlikte, bu net bir şekilde optimum değildir, çünkü bir çerçevenin oluşturulması için gereken süre ağ gecikmesine eklenir:

Mutlaka önemli değil. Mantık ne zaman güncellenir? Henüz bir şey yapamıyorsanız ağdan veri almanın çok az anlamı vardır. Benzer şekilde, henüz söyleyecek bir şeyiniz yoksa yanıt vermenin çok az anlamı vardır.

örneğin, sunucudan bir durum güncellemesi alındıktan sonra bir ACK paketini anında geri gönderebilir.

Oyununuz o kadar hızlı ilerliyorsa, bir sonraki karenin oluşturulmasını beklemek önemli bir gecikmedir, o zaman ayrı ACK paketleri göndermenize gerek kalmayacak kadar veri gönderecektir - normal verilerinize ACK değerleri ekleyin eğer onlara ihtiyacınız varsa yükler.

Çoğu ağa bağlı oyun için, böyle bir oyun döngüsüne sahip olmak mükemmel bir şekilde mümkündür:

while 1:
    read_network_messages()
    read_local_input()
    update_world()
    send_network_updates()
    render_world()

Güncellemeleri, renderleme işleminden ayırabilirsiniz, bu da şiddetle tavsiye edilir, ancak belirli bir gereksiniminiz olmadığı sürece diğer her şey bu kadar basit kalabilir. Tam olarak ne tür bir oyun yapıyorsunuz?


7
Oluşturma işleminden yapılan güncellemenin ayrılması sadece şiddetle tavsiye edilmez, boktan olmayan bir motor istiyorsanız gereklidir.
AttackingHobo

Çoğu insan motor yapmıyor ve muhtemelen oyunların çoğu hala ikisini de ayırmıyor. Geçen zaman değerini güncelleme fonksiyonuna geçirmek için ayarlama çoğu durumda kabul edilebilir.
Kylotan

2

Ancak, ağ gecikmesine bir çerçevenin oluşturulması için gereken süre eklendiğinden, bu açıkça en uygun değildir: Ağ üzerinden gelen iletilerin geçerli çerçeve oluşturma (ve oyun mantığı) tamamlanana kadar beklemesi gerekir.

Bu hiç doğru değil. Mesajı ağ üzerinden gider iken alıcı geçerli çerçeveyi oluşturur. Ağ gecikmesi, istemci tarafında çok sayıda çerçeveye kenetlenir; evet- ama eğer müşteri çok az FPS'ye sahipse, bu büyük bir sorun, o zaman daha büyük problemleri var.


0

Ağ iletişimleri toplu olmalıdır. Her oyun işaretini gönderilen tek bir paket için çaba göstermelisiniz (genellikle bir çerçeve oluşturulduğunda, ancak gerçekten bağımsız olmalıdır).

Oyun varlıklarınız ağ alt sistemi (NSS) ile konuşuyor. NSS, mesajları, ACK'ları vb. Toplu olarak işler ve birkaç (umarım bir) en uygun boyutta UDP paketi (genellikle ~ 1500 bayt) gönderir. NSS yalnızca tek UDP paketleri gönderirken paketleri, kanalları, öncelikleri, yeniden gönderme vb. Öykünür.

Oyunların öğreticisini okuyun veya sadece Glenn Fiedler'in fikirlerinin çoğunu uygulayan ENet'i kullanın .

Ya da oyununuzda seğirme reaksiyonlarına ihtiyaç duymuyorsa TCP'yi kullanabilirsiniz. Sonra tüm yığınlama, yeniden gönderme ve ACK sorunları ortadan kalkar. Yine de bir NSS'nin bant genişliğini ve kanalları yönetmesini istersiniz.


0

Tamamen "duyarlılığı görmezden gelme". Zaten gecikmiş paketlere 40 ms daha gecikme ekleyerek kazanılacak çok az şey vardır. Birkaç kare (60 fps'de) ekliyorsanız, bir konum güncellemesinin işlenmesini başka bir çift kare geciktiriyorsunuz. Paketleri hızlı bir şekilde kabul etmek ve hızlı bir şekilde işlemek daha iyidir, böylece simülasyonun doğruluğunu geliştirirsiniz.

Ekranda görünenleri temsil etmek için gereken minimum durum bilgilerini düşünerek bant genişliğini optimize etmekte büyük bir başarı elde ettim. Sonra her veri bitine bakıp bunun için bir model seçiyoruz. Konum bilgisi zaman içinde delta değerleri olarak ifade edilebilir. Bunun için kendi istatistiksel modellerinizi kullanabilir ve yaşları bunlarda hata ayıklamak için harcayabilir veya size yardımcı olması için bir kütüphane kullanabilirsiniz. Bu kütüphanenin kayan nokta modelini kullanmayı tercih ediyorum DataBlock_Predict_Float Oyun sahnesi grafiği için kullanılan bant genişliğini optimize etmeyi gerçekten kolaylaştırıyor.

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.