paint-brush
Unity DOTS ve ECS'yi Keşfetmek: Oyun Değiştirici mi?ile@deniskondratev
3,436 okumalar
3,436 okumalar

Unity DOTS ve ECS'yi Keşfetmek: Oyun Değiştirici mi?

ile Denis Kondratev12m2023/07/18
Read on Terminal Reader
Read this story w/o Javascript

Çok uzun; Okumak

Bu makale, oyun geliştirmeyi basit, sezgisel mimariyle optimize eden Unity'nin Veri Odaklı Teknoloji Yığını (DOTS) ve Varlık Bileşen Sistemini (ECS) ele alıyor. Bu teknolojiler, ek Unity paketleriyle birlikte yüksek performanslı, verimli oyun oluşturmaya olanak tanır. Bu sistemlerin avantajları Conway'in Hayat Oyunu algoritması örneğiyle gösterilmiştir.
featured image - Unity DOTS ve ECS'yi Keşfetmek: Oyun Değiştirici mi?
Denis Kondratev HackerNoon profile picture
0-item
1-item
2-item

Unity DOTS, geliştiricilerin modern işlemcilerin tüm potansiyelini kullanmalarına ve yüksek düzeyde optimize edilmiş, verimli oyunlar sunmalarına olanak tanır; biz de buna dikkat etmeye değer olduğunu düşünüyoruz.


Unity'nin Veri Odaklı Teknoloji Yığınını (DOTS) geliştirdiğini ilk duyurmasının üzerinden beş yıldan fazla zaman geçti. Unity 2023.3.0f1'in piyasaya sürülmesiyle nihayet resmi bir sürüm gördük. Peki Unity DOTS oyun geliştirme sektörü için neden bu kadar kritik ve bu teknoloji ne gibi avantajlar sunuyor?


Herkese merhaba! Adım Denis Kondratev ve MY.GAMES'te Unity Geliştiricisiyim. Unity DOTS'un ne olduğunu ve keşfetmeye değer olup olmadığını anlamaya istekliyseniz, bu büyüleyici konuyu derinlemesine incelemek için mükemmel bir fırsattır ve bu makalede tam da bunu yapacağız.


Varlık Bileşen Sistemi (ECS) nedir?

DOTS özünde Varlık Bileşen Sistemi (ECS) mimari modelini uygular. Bu kavramı basitleştirmek için şu şekilde tanımlayalım: ECS üç temel unsur üzerine kurulmuştur: Varlıklar, Bileşenler ve Sistemler.


Varlıklar kendi başlarına herhangi bir doğal işlevsellikten veya açıklamadan yoksundur. Bunun yerine, çeşitli Bileşenler için kap görevi görürler ve bu da onlara oyun mantığı, nesne oluşturma, ses efektleri ve daha fazlası için belirli özellikler kazandırır.


Bileşenler ise farklı türlerde gelir ve kendilerine ait bağımsız işleme yetenekleri olmadan yalnızca verileri depolar.


ECS çerçevesini tamamlayanlar, Bileşenleri işleyen, Varlık oluşturma ve yok etme işlerini yürüten ve Bileşenlerin eklenmesini veya kaldırılmasını yöneten Sistemlerdir .


Örneğin, bir "Space Shooter" oyunu oluştururken oyun alanında birden fazla nesne yer alacaktır: oyuncunun uzay gemisi, düşmanlar, asteroitler, ganimet, adını siz koyun.



Bu nesnelerin tümü, herhangi bir belirgin özelliği olmayan, kendi başlarına varlıklar olarak kabul edilir. Ancak onlara farklı bileşenler atayarak onlara benzersiz nitelikler kazandırabiliriz.


Göstermek için tüm bu nesnelerin oyun alanı üzerinde konumlara sahip olduğunu göz önünde bulundurarak, nesnenin koordinatlarını tutan bir konum bileşeni oluşturabiliriz. Ayrıca oyuncunun uzay gemisi, düşmanları ve asteroitleri için sağlık bileşenlerini de dahil edebiliriz; nesne çarpışmalarını ele almaktan sorumlu olan sistem, bu varlıkların sağlığını yönetecektir. Ek olarak, düşmanlara bir düşman türü bileşeni ekleyerek düşman kontrol sisteminin, atanmış türlere göre davranışlarını yönetmesini sağlayabiliriz.


Bu açıklama basit ve temel bir genel bakış sunsa da gerçek biraz daha karmaşıktır. Yine de ECS'nin temel konseptinin açık olduğuna inanıyorum. Bunu bir kenara bırakarak, bu yaklaşımın avantajlarına bakalım.

Varlık Bileşen Sisteminin faydaları

Varlık Bileşen Sistemi (ECS) yaklaşımının temel avantajlarından biri desteklediği mimari tasarımdır. Nesne yönelimli programlama (OOP), kalıtım ve kapsülleme gibi kalıplarla önemli bir miras taşır ve deneyimli programcılar bile geliştirmenin hararetinde mimari hatalar yapabilir, bu da uzun vadeli projelerde yeniden düzenlemeye veya karmaşık mantıklara yol açabilir.


Buna karşılık ECS, basit ve sezgisel bir mimari sağlar. Her şey doğal olarak izole edilmiş bileşenlere ve sistemlere ayrılıyor, bu da bu yaklaşımın anlaşılmasını ve geliştirilmesini kolaylaştırıyor; acemi geliştiriciler bile bu yaklaşımı minimum hatayla hızla kavrarlar.


ECS, karmaşık miras hiyerarşileri yerine yalıtılmış bileşenlerin ve davranış sistemlerinin oluşturulduğu bileşik bir yaklaşımı izler. Bu bileşenler ve sistemler kolayca eklenebilir veya kaldırılabilir, bu da varlık özelliklerinde ve davranışlarında esnek değişiklikler yapılmasına olanak tanır; bu yaklaşım, kodun yeniden kullanılabilirliğini büyük ölçüde artırır.


ECS'nin bir diğer önemli avantajı performans optimizasyonudur. ECS'de veriler, aynı veri türlerinin birbirine yakın yerleştirilmesiyle, bellekte bitişik ve optimize edilmiş bir şekilde depolanır. Bu, veri erişimini optimize eder, önbellek kayıplarını azaltır ve bellek erişim düzenlerini iyileştirir. Üstelik, ayrı veri bloklarından oluşan sistemlerin farklı süreçler arasında paralelleştirilmesi daha kolaydır, bu da geleneksel yaklaşımlara kıyasla olağanüstü performans kazanımlarına yol açar.

Unity DOTS paketlerini keşfetme

Unity DOTS, Unity'de ECS konseptini uygulayan, Unity Technologies tarafından sağlanan bir dizi teknolojiyi kapsar. Oyun geliştirmenin farklı yönlerini geliştirmek için tasarlanmış çeşitli paketler içerir; şimdi bunlardan birkaçına değinelim.


DOTS'un özü, tanıdık MonoBehaviours ve GameObjects'ten Entity Component System yaklaşımına geçişi kolaylaştıran Entities paketidir. Bu paket DOTS tabanlı geliştirmenin temelini oluşturur.


Unity Physics paketi, paralelleştirilmiş hesaplamalar yoluyla dikkate değer bir hıza ulaşarak oyunlarda fiziğin işlenmesine yeni bir yaklaşım getiriyor.


Ek olarak Havok Physics for Unity paketi, modern Havok Physics motoruyla entegrasyona olanak tanır. Bu motor, yüksek performanslı çarpışma algılama ve fiziksel simülasyon sunarak The Legend of Zelda: Breath of the Wild, Doom Eternal, Death Stranding, Mortal Kombat 11 ve daha fazlası gibi popüler oyunlara güç veriyor.


Death Stranding, diğer birçok video oyunu gibi popüler Havok Physics motorunu kullanıyor.


Entities Graphics paketi DOTS'ta görüntülemeye odaklanır. İşleme verilerinin verimli bir şekilde toplanmasını sağlar ve Universal Render Pipeline (URP) veya High Definition Render Pipeline (HDRP) gibi mevcut işleme hatlarıyla sorunsuz şekilde çalışır.


Bir şey daha, Unity aynı zamanda Netcode adında bir ağ teknolojisini de aktif olarak geliştiriyor. Düşük seviyeli çok oyunculu oyun geliştirme için Unity Transport, geleneksel yaklaşımlar için GameObjects için Netcode ve DOTS ilkeleriyle uyumlu dikkate değer Entities için Unity Netcode paketi gibi paketleri içerir. Bu paketler nispeten yenidir ve gelecekte gelişmeye devam edecektir.

Unity DOTS ve ötesinde performansı artırma

DOTS ile yakından ilişkili çeşitli teknolojiler, DOTS çerçevesinde ve ötesinde kullanılabilir. İş Sistemi paketi paralel hesaplamalarla kod yazmanın kolay bir yolunu sunar. İşin, kendi verileri üzerinde hesaplamalar yapan, iş adı verilen küçük parçalara bölünmesi etrafında döner. İş Sistemi, verimli yürütme için bu işleri iş parçacıklarına eşit olarak dağıtır.


Kod güvenliğini sağlamak için İş Sistemi, bölünebilir veri türlerinin işlenmesini destekler. Blitlenebilir veri türleri, yönetilen ve yönetilmeyen bellekte aynı temsile sahiptir ve yönetilen ve yönetilmeyen kod arasında aktarıldığında herhangi bir dönüşüm gerektirmez. Blitlenebilir türlere örnek olarak byte, sbyte, short, ushort, int, uint, long, ulong, float, double, IntPtr ve UIntPtr verilebilir. Blitlenebilir ilkel türlerin ve yalnızca bölünebilir türleri içeren yapıların tek boyutlu dizileri de blitlenebilir olarak kabul edilir.


Bununla birlikte, değişken bir bölünebilir tür dizisi içeren türler, kendileri bölünebilir olarak kabul edilmez. Bu sınırlamayı gidermek için Unity, işlerde kullanılmak üzere bir dizi yönetilmeyen veri yapısı sağlayan Koleksiyonlar paketini geliştirdi. Bu koleksiyonlar yapılandırılmıştır ve Unity mekanizmalarını kullanarak verileri yönetilmeyen bellekte saklar. Bu koleksiyonların Disposal() yöntemini kullanarak serbest bırakılması geliştiricinin sorumluluğundadır.


Bir diğer önemli paket, yüksek derecede optimize edilmiş kod oluşturmak için İş Sistemiyle birlikte kullanılabilen Burst Compiler'dır . Burst derleyicisi belirli kod kullanım sınırlamalarıyla birlikte gelse de önemli bir performans artışı sağlar.

Performansı Job System ve Burst Compile ile ölçme

Belirtildiği gibi, Job System ve Burst Compiler, DOTS'un doğrudan bileşenleri değildir ancak verimli ve hızlı paralel hesaplamaların programlanmasında değerli yardım sağlar. Pratik bir örnek kullanarak yeteneklerini test edelim: uygulama Conway'in Hayat Oyunu algoritması . Bu algoritmada bir alan, her biri canlı ya da ölü olabilen hücrelere bölünür. Her turda, her hücre için canlı komşuların sayısını kontrol ediyoruz ve durumları belirli kurallara göre güncelleniyor.



Bu algoritmanın geleneksel yaklaşımı kullanarak uygulanması şu şekildedir:


 private void SimulateStep() { Profiler.BeginSample(nameof(SimulateStep)); for (var i = 0; i < width; i++) { for (var j = 0; j < height; j++) { var aliveNeighbours = CountAliveNeighbours(i, j); var index = i * height + j; var isAlive = aliveNeighbours switch { 2 => _cellStates[index], 3 => true, _ => false }; _tempResults[index] = isAlive; } } _tempResults.CopyTo(_cellStates); Profiler.EndSample(); } private int CountAliveNeighbours(int x, int y) { var count = 0; for (var i = x - 1; i <= x + 1; i++) { if (i < 0 || i >= width) continue; for (var j = y - 1; j <= y + 1; j++) { if (j < 0 || j >= height) continue; if (_cellStates[i * width + j]) { count++; } } } return count; }


Hesaplamalar için harcanan süreyi ölçmek için Profiler'a işaretçiler ekledim. Hücrelerin durumları, _cellStates adı verilen tek boyutlu bir dizide saklanır. Başlangıçta geçici sonuçları _tempResults'a yazıyoruz ve hesaplamalar tamamlandıktan sonra bunları tekrar _cellStates'e kopyalıyoruz. Bu yaklaşım gereklidir çünkü nihai sonucun doğrudan _cellStates'e yazılması sonraki hesaplamaları etkileyecektir.


1000x1000 hücrelik bir alan oluşturdum ve performansı ölçmek için programı çalıştırdım. Sonuçlar burada:



Sonuçlardan da görüldüğü gibi hesaplamalar 380 ms sürmüştür.


Şimdi performansı artırmak için Job System ve Burst Compiler'ı uygulayalım. İlk olarak Conway'in Hayat Oyunu algoritmasını yürütmekten sorumlu İşi yaratacağız.


 public struct SimulationJob : IJobParallelFor { public int Width; public int Height; [ReadOnly] public NativeArray<bool> CellStates; [WriteOnly] public NativeArray<bool> TempResults; public void Execute(int index) { var i = index / Height; var j = index % Height; var aliveNeighbours = CountAliveNeighbours(i, j); var isAlive = aliveNeighbours switch { 2 => CellStates[index], 3 => true, _ => false }; TempResults[index] = isAlive; } private int CountAliveNeighbours(int x, int y) { var count = 0; for (var i = x - 1; i <= x + 1; i++) { if (i < 0 || i >= Width) continue; for (var j = y - 1; j <= y + 1; j++) { if (j < 0 || j >= Height) continue; if (CellStates[i * Width + j]) { count++; } } } return count; } }


CellStates alanına [ReadOnly] niteliğini atadım, böylece herhangi bir iş parçacığından dizinin tüm değerlerine sınırsız erişime izin verdim. Ancak [WriteOnly] niteliğine sahip olan TempResults alanı için yazma işlemi yalnızca Execute(int index) yönteminde belirtilen indeks üzerinden yapılabilmektedir. Farklı bir indekse değer yazmaya çalışmak bir uyarı üretecektir. Bu, çok iş parçacıklı modda çalışırken veri güvenliğini sağlar.


Şimdi normal koddan Job'umuzu başlatalım:


 private void SimulateStepWithJob() { Profiler.BeginSample(nameof(SimulateStepWithJob)); var job = new SimulationJob { Width = width, Height = height, CellStates = _cellStates, TempResults = _tempResults }; var jobHandler = job.Schedule(width * height, 4); jobHandler.Complete(); job.TempResults.CopyTo(_cellStates); Profiler.EndSample(); }


Gerekli tüm verileri kopyaladıktan sonra, Schedule() yöntemini kullanarak işin yürütülmesini planlıyoruz. Bu zamanlamanın hesaplamaları hemen yürütmediğini unutmamak önemlidir: bu eylemler ana iş parçacığından başlatılır ve yürütme, farklı iş parçacıkları arasında dağıtılan çalışanlar aracılığıyla gerçekleşir. İşin tamamlanmasını beklemek için jobHandler.Complete() işlevini kullanırız. Ancak o zaman elde edilen sonucu _cellStates'e geri kopyalayabiliriz.


Hızı ölçelim:



Yürütme hızı neredeyse on kat arttı ve yürütme süresi artık yaklaşık 42 ms'dir. Profiler penceresinde iş yükünün 17 çalışan arasında dağıtıldığını görebiliriz. Bu sayı, test ortamındaki 10 çekirdekli ve 20 iş parçacıklı Intel® Core™ i9-10900 işlemci iş parçacığı sayısından biraz daha azdır. Daha az çekirdeğe sahip işlemcilerde sonuçlar farklılık gösterse de işlemcinin gücünün tam olarak kullanılmasını sağlayabiliriz.


Ancak hepsi bu kadar değil; önemli kod optimizasyonu sağlayan ancak belirli kısıtlamalarla birlikte gelen Burst Compiler'ı kullanmanın zamanı geldi. Burst Compiler'ı etkinleştirmek için, [BurstCompile] niteliğini SimülasyonJob'a eklemeniz yeterlidir.


 [BurstCompile] public struct SimulationJob : IJobParallelFor { ... }


Tekrar ölçelim:



Sonuçlar en iyimser beklentileri bile aşıyor: Hız, ilk sonuca kıyasla neredeyse 200 kat arttı. Artık 1 milyon hücrenin hesaplama süresi 2 ms'yi geçmiyor. Profiler'da Burst Compiler ile derlenen kod tarafından yürütülen parçalar yeşil renkle vurgulanır.

Çözüm

Çok iş parçacıklı hesaplamaların kullanımı her zaman gerekli olmayabilir ve Burst Compiler'ın kullanımı her zaman mümkün olmayabilir, ancak işlemci geliştirmede çok çekirdekli mimarilere doğru ortak bir eğilim gözlemleyebiliriz. Bu, onların tüm güçlerinden yararlanmaya hazırlıklı olmamız gerektiği anlamına geliyor. ECS ve özellikle Unity DOTS, bu paradigmaya mükemmel bir şekilde uyum sağlıyor.


Unity DOTS'un en azından ilgiyi hak ettiğine inanıyorum. Her durum için en iyi çözüm olmasa da ECS birçok oyunda değerini kanıtlayabilir.


Unity DOTS çerçevesi, veri odaklı ve çok iş parçacıklı yaklaşımıyla Unity oyunlarındaki performansı optimize etmek için muazzam bir potansiyel sunuyor. Geliştiriciler, Entity Component System mimarisini benimseyerek ve Job System ile Burst Compiler gibi teknolojilerden yararlanarak yeni performans ve ölçeklenebilirlik seviyelerinin kilidini açabilir.


Oyun geliştirme gelişmeye devam ettikçe ve donanım ilerledikçe Unity DOTS'u benimsemek giderek daha değerli hale geliyor. Geliştiricilere, modern işlemcilerin tüm potansiyelinden yararlanma ve yüksek düzeyde optimize edilmiş, verimli oyunlar sunma gücü verir. Unity DOTS her proje için ideal çözüm olmasa da performans odaklı geliştirme ve ölçeklenebilirlik arayanlar için şüphesiz büyük umut vaat ediyor.


Unity DOTS, performansı artırarak, paralel hesaplamalara olanak tanıyarak ve çok çekirdekli işlemenin geleceğini kucaklayarak oyun geliştiricilerine önemli ölçüde fayda sağlayabilecek güçlü bir çerçevedir. Modern donanımdan tam anlamıyla yararlanmak ve Unity oyunlarının performansını optimize etmek için keşfetmeye ve benimsenmesini düşünmeye değer.