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 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. Unity 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. 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. Varlıklar ise farklı türlerde gelir ve kendilerine ait bağımsız işleme yetenekleri olmadan yalnızca verileri depolar. Bileşenler 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. (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. Nesne yönelimli programlama 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 paketidir. Bu paket DOTS tabanlı geliştirmenin temelini oluşturur. Entities paketi, paralelleştirilmiş hesaplamalar yoluyla dikkate değer bir hıza ulaşarak oyunlarda fiziğin işlenmesine yeni bir yaklaşım getiriyor. Unity Physics Ek olarak 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. Havok Physics for Unity 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. Entities Graphics 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 paketi gibi paketleri içerir. Bu paketler nispeten yenidir ve gelecekte gelişmeye devam edecektir. Entities için Unity Netcode Unity DOTS ve ötesinde performansı artırma DOTS ile yakından ilişkili çeşitli teknolojiler, DOTS çerçevesinde ve ötesinde kullanılabilir. 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. İş Sistemi 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 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. Koleksiyonlar Bir diğer önemli paket, yüksek derecede optimize edilmiş kod oluşturmak için İş Sistemiyle birlikte kullanılabilen . Burst derleyicisi belirli kod kullanım sınırlamalarıyla birlikte gelse de önemli bir performans artışı sağlar. Burst Compiler'dır 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 . 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. Conway'in Hayat Oyunu algoritması 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ı, adı verilen tek boyutlu bir dizide saklanır. Başlangıçta geçici sonuçları yazıyoruz ve hesaplamalar tamamlandıktan sonra bunları tekrar kopyalıyoruz. Bu yaklaşım gereklidir çünkü nihai sonucun doğrudan yazılması sonraki hesaplamaları etkileyecektir. _cellStates _tempResults'a _cellStates'e _cellStates'e 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; } } alanına 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 niteliğine sahip olan alanı için yazma işlemi yalnızca 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. CellStates [ReadOnly] [WriteOnly] TempResults Execute(int index) Ş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, 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 kullanırız. Ancak o zaman elde edilen sonucu geri kopyalayabiliriz. Schedule() jobHandler.Complete() işlevini _cellStates'e 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, niteliğini eklemeniz yeterlidir. [BurstCompile] SimülasyonJob'a [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.