Gerçek Zamanlı Çok Oyunculu Sanal Gerçeklik Oyunu Nasıl Oluşturulur (Bölüm 2)

Yayınlanan: 2022-03-10
Kısa özet ↬ Bu öğreticide, oyunun gerçek zamanlı çok oyunculu öğeleriyle yakından ilişkili bir sanal gerçeklik oyunu için oyun mekaniklerini yazacaksınız.

Bu eğitim serisinde, oyuncuların bir bulmacayı çözmek için işbirliği yapması gereken web tabanlı çok oyunculu bir sanal gerçeklik oyunu oluşturacağız. Bu serinin ilk bölümünde oyunda yer alan küreleri tasarladık. Serinin bu bölümünde, oyun mekaniklerini ekleyeceğiz ve oyuncu çiftleri arasında iletişim protokolleri ayarlayacağız.

Buradaki oyun açıklaması serinin ilk bölümünden alınmıştır: Her oyuncu çiftine bir küre halkası verilir. Amaç, yükseltilmiş ve parlaksa bir kürenin “açık” olduğu tüm küreleri “açmaktır”. Bir küre daha alçak ve loşsa "kapalı"dır. Bununla birlikte, belirli "baskın" küreler komşularını etkiler: durum değiştirirse, komşuları da durum değiştirir. Oyuncu 2 çift sayılı küreleri kontrol edebilir ve oyuncu 1 tek sayılı küreleri kontrol edebilir. Bu, her iki oyuncuyu da bulmacayı çözmek için işbirliği yapmaya zorlar.

Bu eğitimdeki 8 adım 3 bölüme ayrılmıştır:

  1. Kullanıcı Arayüzünü Doldurma (1. ve 2. Adımlar)
  2. Oyun Mekaniği Ekleyin (3 - 5 arasındaki Adımlar)
  3. İletişim Kurulumu (Adım 6 - 8)

Bu bölüm, herkesin oynayabileceği, tamamen işlevsel bir çevrimiçi demo ile sona erecek. A-Frame VR ve birkaç A-Frame uzantısı kullanacaksınız.

Bitmiş kaynak kodunu burada bulabilirsiniz.

Birden fazla istemci arasında senkronize edilmiş bitmiş çok oyunculu oyun
Bitmiş çok oyunculu oyun, birden fazla istemci arasında senkronize edildi. (Büyük önizleme)

1. Görsel Göstergeler Ekleyin

Başlamak için, bir kürenin kimliğinin görsel göstergelerini ekleyeceğiz. #container-orb0 öğesinin ilk alt öğesi olarak yeni a-text VR öğesi ekleyin.

 <a-entity ...> <a-text class="orb-id" opacity="0.25" rotation="0 -90 0" value="4" color="#FFF" scale="3 3 3" position="0 -2 -0.25" material="side:double"></a-text> ... <a-entity position...> ... </a-entity> </a-entity>

Bir kürenin “bağımlılıkları”, değiştirildiğinde değiştireceği kürelerdir: örneğin, küre 1'in bağımlılık olarak küreler 2 ve 3'e sahip olduğunu söyleyin. Bu, küre 1 değiştirilirse, küreler 2 ve 3'ün de değiştirileceği anlamına gelir. .animation-position 'dan hemen sonra, bağımlılıkların görsel göstergelerini aşağıdaki gibi ekleyeceğiz.

 <a-animation class="animation-position" ... /> <a-text class="dep-right" opacity="0.25" rotation="0 -90 0" value="4" color="#FFF" scale="10 10 10" position="0 0 1" material="side:double" ></a-text> <a-text class="dep-left" opacity="0.25"rotation="0 -90 0" value="1" color="#FFF" scale="10 10 10" position="0 0 -3" material="side:double" ></a-text>

Kodunuzun 1. Adım için kaynak kodumuzla eşleştiğini doğrulayın. Küreniz şimdi aşağıdakilerle eşleşmelidir:

Kürenin kimliği ve tetikleyeceği kürelerin kimlikleri için görsel göstergelere sahip küre
Kürenin kimliği ve tetikleyeceği kürelerin kimlikleri için görsel göstergelere sahip Küre (Büyük önizleme)

Bu, ihtiyaç duyacağımız ek görsel göstergeleri sonuçlandırıyor. Ardından, bu şablon küresini kullanarak VR sahnesine dinamik olarak küreler ekleyeceğiz.

2. Dinamik Olarak Küre Ekle

Bu adımda, bir seviyenin JSON-esque belirtimine göre küreler ekleyeceğiz. Bu, yeni seviyeleri kolayca belirlememizi ve oluşturmamızı sağlar. Bölüm 1'deki son adımdaki küreyi şablon olarak kullanacağız.

Başlamak için, jQuery'yi içe aktarın, çünkü bu DOM değişikliklerini ve dolayısıyla VR sahnesindeki değişiklikleri daha kolay hale getirecektir. A-Frame içe aktarma işleminden hemen sonra, aşağıdakileri L8'e ekleyin:

 <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

Bir dizi kullanarak bir seviye belirtin. Dizi, her bir kürenin "bağımlılıklarını" kodlayan nesne değişmezlerini içerecektir. <head> etiketinin içine aşağıdaki düzey yapılandırmasını ekleyin:

 <script> var orbs = [ {left: 1, right: 4}, {}, {on: true}, {}, {on: true} ]; </script>

Şimdilik, her kürenin “sağına” ve “soluna” yalnızca bir bağımlılığı olabilir. Yukarıda orbs bildirdikten hemen sonra, sayfa yüklendiğinde çalışacak bir işleyici ekleyin. Bu işleyici (1) şablon küresini çoğaltacak ve (2) sağlanan seviye konfigürasyonunu kullanarak şablon küresini kaldıracaktır:

 $(document).ready(function() { function populateTemplate(orb, template, i, total) {} function remove(selector) {} for (var i=0; i < orbs.length; i++) { var orb = orbs[i]; var template = $('#template').clone(); template = populateTemplate(orb, template, i, orbs.length); $('#carousel').append(template); } remove('#template'); } function clickOrb(i) {}

Ardından, bir seçici verildiğinde VR sahnesinden bir öğeyi kaldıran remove işlevini doldurun. Neyse ki, A-Frame, DOM'deki değişiklikleri gözlemler ve bu nedenle, öğeyi DOM'den çıkarmak, onu VR sahnesinden çıkarmak için yeterlidir. remove işlevini aşağıdaki gibi doldurun.

 function remove(selector) { var el = document.querySelector(selector); el.parentNode.removeChild(el); }

Bir küre üzerindeki tıklama eylemini basitçe tetikleyen clickOrb işlevini doldurun.

 function clickOrb(i) { document.querySelector("#container-orb" + i).click(); }

Ardından, populateTemplate işlevini yazmaya başlayın. Bu işlevde, .container alarak başlayın. Küre için bu kap, ayrıca önceki adımda eklediğimiz görsel göstergeleri içerir. Ayrıca, bağımlılıklarına bağlı olarak kürenin onclick davranışını değiştirmemiz gerekecek. Bir sola bağımlılık varsa, bunu yansıtmak için hem görsel göstergeyi hem de onclick davranışını değiştirin; aynısı bir sağa bağımlılık için de geçerlidir:

 function populateTemplate(orb, template, i, total) { var container = template.find('.container'); var onclick = 'document.querySelector("#light-orb' + i + '").emit("switch");'; if (orb.left || orb.right) { if (orb.left) { onclick += 'clickOrb(' + orb.left + ');'; container.find('.dep-left').attr('value', orb.left); } if (orb.right) { onclick += 'clickOrb(' + orb.right + ');'; container.find('.dep-right').attr('value', orb.right); } } else { container.find('.dep-left').remove(); container.find('.dep-right').remove(); } }

Yine de populateTemplate işlevinde, küre kimliğinin tümünde ve kapsayıcısının öğelerinde küre kimliğini doğru şekilde ayarlayın.

 container.find('.orb-id').attr('value', i); container.attr('id', 'container-orb' + i); template.find('.orb').attr('id', 'orb' + i); template.find('.light-orb').attr('id', 'light-orb' + i); template.find('.clickable').attr('data-id', i);

Yine de populateTemplate işlevinde, onclick davranışını ayarlayın, rastgele çekirdeği ayarlayın, böylece her bir küre görsel olarak farklı olur ve son olarak, kürenin dönüş konumunu kimliğine göre ayarlayın.

 container.attr('onclick', onclick); container.find('lp-sphere').attr('seed', i); template.attr('rotation', '0 ' + (360 / total * i) + ' 0');

İşlevin sonunda, template yukarıdaki tüm konfigürasyonlarla birlikte döndürün.

 return template;

Belge yükleme işleyicisinin içinde ve remove('#template') ile şablonu kaldırdıktan sonra, başlangıçta açık olacak şekilde yapılandırılan küreleri açın.

 $(document).ready(function() { ... setTimeout(function() { for (var i=0; i < orbs.length; i++) { var orb = orbs[i]; if (orb.on) { document.querySelector("#container-orb" + i).click(); } } }, 1000); });

Bu, Javascript değişikliklerini tamamlar. Ardından, şablonun varsayılan ayarlarını 'kapalı' bir küreninkiyle değiştireceğiz. #container-orb0 için konumu ve ölçeği aşağıdaki şekilde değiştirin:

 position="8 0.5 0" scale="0.5 0.5 0.5"

Ardından, #light-orb0 yoğunluğunu 0 olarak değiştirin.

 intensity="0"

Kaynak kodunuzun 2. Adım için kaynak kodumuzla eşleştiğini doğrulayın.

VR sahneniz artık dinamik olarak doldurulmuş 5 küre içermelidir. Kürelerden biri ayrıca aşağıdaki gibi görsel bağımlılık göstergelerine sahip olmalıdır:

Tüm küreler, şablon küre kullanılarak dinamik olarak doldurulur
Tüm küreler, şablon küresi kullanılarak dinamik olarak doldurulur (Büyük önizleme)

Bu, dinamik olarak küre eklemedeki ilk bölümü tamamlar. Bir sonraki bölümde oyun mekaniklerini ekleyerek üç adım atacağız. Spesifik olarak, oyuncu, oyuncu kimliğine bağlı olarak yalnızca belirli küreler arasında geçiş yapabilecektir.

3. Terminal Durumu Ekle

Bu adımda, bir terminal durumu ekleyeceğiz. Tüm küreler başarıyla açılırsa, oyuncu bir "zafer" sayfası görür. Bunu yapmak için, tüm kürelerin durumunu izlemeniz gerekecek. Bir küre her açılıp kapatıldığında, dahili durumumuzu güncellememiz gerekecek. toggleOrb bir yardımcı işlevin bizim için güncelleme durumu olduğunu söyleyin. Bir kürenin durumu her değiştiğinde toggleOrb işlevini çağırın: (1) aşırı yük işleyicisine bir tıklama dinleyicisi ekleyin ve (2) bir toggleOrb(i); clickOrb için çağrı. Son olarak, (3) boş bir toggleOrb tanımlayın.

 $(document).ready(function() { ... $('.orb').on('click', function() { var id = $(this).attr('data-id') toggleOrb(id); }); }); function toggleOrb(i) {} function clickOrb(i) { ... toggleOrb(i); }

Basit olması için, oyun durumunu belirtmek için seviye konfigürasyonumuzu kullanacağız. ith küresinin on durumunu değiştirmek için toggleOrb kullanın. toggleOrb , tüm küreler açıksa ayrıca bir terminal durumunu tetikleyebilir.

 function toggleOrb(i) { orbs[i].on = !orbs[i].on; if (orbs.every(orb => orb.on)) console.log('Victory!'); }

Kodunuzun 3. Adım için kaynak kodumuzla eşleşip eşleşmediğini iki kez kontrol edin.

Bu, oyun için "tek oyuncu" modunu tamamlar. Bu noktada tamamen işlevsel bir sanal gerçeklik oyununuz var. Ancak şimdi çok oyunculu bileşeni yazmanız ve oyun mekaniği aracılığıyla işbirliğini teşvik etmeniz gerekecek.

4. Oyuncu Nesnesi Oluşturun

Bu adımda, oyuncu kimliğine sahip bir oyuncu için bir soyutlama oluşturacağız. Bu oyuncu kimliği daha sonra sunucu tarafından atanacaktır.

Şimdilik, bu sadece global bir değişken olacak. orbs tanımladıktan hemen sonra bir oyuncu kimliği tanımlayın:

 var orbs = ... var current_player_id = 1;

Kodunuzun 4. Adım için kaynak kodumuzla eşleşip eşleşmediğini iki kez kontrol edin. Sonraki adımda, oynatıcının hangi küreleri kontrol edebileceğini belirlemek için bu oyuncu kimliği kullanılacaktır.

5. Koşullu Küreleri Aç/Kapat

Bu adımda, küre değiştirme davranışını değiştireceğiz. Spesifik olarak, oyuncu 1 tek sayılı küreleri kontrol edebilir ve oyuncu 2 çift sayılı küreleri kontrol edebilir. İlk olarak, bu mantığı kürelerin durum değiştirdiği her iki yerde de uygulayın:

 $('.orb').on('click', function() { var id = ... if (!allowedToToggle(id)) return false; ... } ... function clickOrb(i) { if (!allowedToToggle(id)) return; ... }

İkinci olarak, clickOrb allowedToToggle tanımlayın. Mevcut oyuncu oyuncu 1 ise, tek sayılı kimlikler bir doğruluk-y değeri döndürür ve böylece 1. oyuncunun tek sayılı küreleri kontrol etmesine izin verilir. Oyuncu 2 için bunun tersi geçerlidir. Diğer tüm oyuncuların küreleri kontrol etmesine izin verilmez.

 function allowedToToggle(id) { if (current_player_id == 1) { return id % 2; } else if (current_player_id == 2) { return !(id % 2); } return false; }

Kodunuzun 5. Adım için kaynak kodumuzla eşleşip eşleşmediğini iki kez kontrol edin. Varsayılan olarak, oyuncu 1. oyuncudur. Bu, 1. oyuncu olarak önizlemenizde yalnızca tek sayılı küreleri kontrol edebileceğiniz anlamına gelir. Bu, oyun mekaniği ile ilgili bölümü sonlandırıyor.

Bir sonraki bölümde, bir sunucu aracılığıyla iki oyuncu arasındaki iletişimi kolaylaştıracağız.

6. WebSocket ile Sunucu Kurulumu

Bu adımda, (1) oyuncu kimliklerini ve (2) geçiş mesajlarını takip etmek için basit bir sunucu kuracaksınız. Bu mesajlar oyun durumunu içerecektir, böylece oyuncular her birinin diğerinin gördüklerini gördüğünden emin olabilir.

İstemci tarafı kaynak kodu olarak önceki index.html dosyanıza başvuracağız. Bu adımda koda sunucu tarafı kaynak kodu olarak değineceğiz. glitch.com'a gidin, sağ üstteki "yeni proje"ye tıklayın ve açılır menüden "merhaba-ekspres"e tıklayın.

Soldaki panelden “package.json” öğesini seçin ve dependencies socket-io ekleyin. dependencies sözlüğünüz şimdi aşağıdakilerle eşleşmelidir.

 "dependencies": { "express": "^4.16.4", "socketio": "^1.0.0" },

Sol panelden “index.js”yi seçin ve bu dosyanın içeriğini aşağıdaki minimal socket.io Hello World ile değiştirin:

 const express = require("express"); const app = express(); var http = require('http').Server(app); var io = require('socket.io')(http); /** * Run application on port 3000 */ var port = process.env.PORT || 3000; http.listen(port, function(){ console.log('listening on *:', port); });

Yukarıdakiler, temel bir ekspres uygulama için soket.io'yu 3000 numaralı bağlantı noktasında kurar. Ardından, biri aktif oyuncuların listesini korumak için, diğeri ise atanmamış en küçük oyuncu kimliğini korumak için olmak üzere iki global değişken tanımlayın.

 /** * Maintain player IDs */ var playerIds = []; var smallestPlayerId = 1;

Ardından, yeni bir oyuncu kimliği oluşturan ve yeni oyuncu kimliğini playerIds dizisine ekleyerek "alındı" olarak işaretleyen getPlayerId işlevini tanımlayın. Özellikle, işlev en küçükPlayerId öğesini işaretler ve ardından bir sonraki en smallestPlayerId alınmamış tamsayıyı arayarak smallestPlayerId öğesini günceller.

 function getPlayerId() { var playerId = smallestPlayerId; playerIds.push(playerId); while (playerIds.includes(smallestPlayerId)) { smallestPlayerId++; } return playerId; }

En smallestPlayerId uygun şekilde güncelleyen ve sağlanan playerId başka bir oyuncunun bu kimliği alabilmesi için serbest removePlayer işlevini tanımlayın.

 function removePlayer(playerId) { if (playerId < smallestPlayerId) { smallestPlayerId = playerId; } var index = playerIds.indexOf(playerId); playerIds.splice(index, 1); }

Son olarak, yukarıdaki yöntem çiftini kullanarak yeni oyuncuları kaydeden ve bağlantısı kesilmiş oyuncuların kaydını iptal eden bir çift soket olay işleyicisi tanımlayın.

 /** * Handle socket interactions */ io.on('connection', function(socket) { socket.on('newPlayer', function() { socket.playerId = getPlayerId(); console.log("new player: ", socket.playerId); socket.emit('playerId', socket.playerId); }); socket.on('disconnect', function() { if (socket.playerId === undefined) return; console.log("disconnected player: ", socket.playerId); removePlayer(socket.playerId); }); });

Kodunuzun Adım 6 için kaynak kodumuzla eşleşip eşleşmediğini iki kez kontrol edin. Bu, temel oyuncu kaydı ve kayıt silme işlemini tamamlar. Her istemci artık sunucu tarafından oluşturulan oyuncu kimliğini kullanabilir.

Bir sonraki adımda, istemciyi sunucu tarafından yayılan oyuncu kimliğini alacak ve kullanacak şekilde değiştireceğiz.

7. Oyuncu Kimliğini Uygulayın

Bu sonraki iki adımda, çok oyunculu deneyimin ilkel bir sürümünü tamamlayacağız. Başlamak için, oyuncu kimliği atamasını istemci tarafına entegre edin. Özellikle, her müşteri sunucudan bir oyuncu kimliği isteyecektir. Adım 4'te ve daha önce çalıştığımız istemci tarafı index.html geri dönün.

socket.io head içe aktarın:

 <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.js"></script>

Belge yükleme işleyicisinden sonra, soketin örneğini oluşturun ve bir newPlayer olayı yayınlayın. Yanıt olarak, sunucu tarafı, playerId olayını kullanarak yeni bir oyuncu kimliği oluşturacaktır. Aşağıda, lightful.glitch.me yerine Glitch proje önizlemeniz için URL'yi kullanın. Aşağıdaki demo URL'yi kullanabilirsiniz, ancak yaptığınız herhangi bir kod değişikliği elbette yansıtılmayacaktır.

 $(document).ready(function() { ... }); socket = io("https://lightful.glitch.me"); socket.emit('newPlayer'); socket.on('playerId', function(player_id) { current_player_id = player_id; console.log(" * You are now player", current_player_id); });

Kodunuzun 7. Adım için kaynak kodumuzla eşleştiğini doğrulayın. Şimdi, çok oyunculu bir oyunun iki tarafını oynamak için oyununuzu iki farklı tarayıcıya veya sekmeye yükleyebilirsiniz. Oyuncu 1 tek sayılı küreleri kontrol edebilecek ve oyuncu 2 çift sayılı küreleri kontrol edebilecek.

Ancak, 1. oyuncu için küreler arasında geçiş yapmanın 2. oyuncu için küre durumunu etkilemeyeceğini unutmayın. Ardından, oyun durumlarını senkronize etmemiz gerekiyor.

8. Oyun Durumunu Senkronize Et

Bu adımda, 1. ve 2. oyuncuların aynı küre durumlarını görmeleri için oyun durumlarını senkronize edeceğiz. 1. oyuncu için küre 1 açıksa, 2. oyuncu için de açık olmalıdır. İstemci tarafında, küre geçişlerini hem anons edeceğiz hem de dinleyeceğiz. Duyurmak için, değiştirilen kürenin kimliğini ileteceğiz.

Her iki toggleOrb çağrısından önce, aşağıdaki socket.emit çağrısını ekleyin.

 $(document).ready(function() { ... $('.orb').on('click', function() { ... socket.emit('toggleOrb', id); toggleOrb(id); }); }); ... function clickOrb(i) { ... socket.emit('toggleOrb', i); toggleOrb(i); }

Ardından, küre geçişlerini dinleyin ve ilgili küreyi değiştirin. playerId soket olay dinleyicisinin hemen altına, toggleOrb olayı için başka bir dinleyici ekleyin.

 socket.on('toggleOrb', function(i) { document.querySelector("#container-orb" + i).click(); toggleOrb(i); });

Bu, istemci tarafı kodunda yapılan değişiklikleri tamamlar. Kodunuzun, Adım 8 için kaynak kodumuzla eşleşip eşleşmediğini iki kez kontrol edin.

Sunucu tarafının artık değiştirilen küre kimliğini alması ve yayınlaması gerekiyor. Sunucu tarafı index.js aşağıdaki dinleyiciyi ekleyin. Bu dinleyici doğrudan soket disconnect dinleyicisinin altına yerleştirilmelidir.

 socket.on('toggleOrb', function(i) { socket.broadcast.emit('toggleOrb', i); });

Kodunuzun 8. Adım için kaynak kodumuzla eşleştiğini iki kez kontrol edin. Şimdi, bir pencerede yüklenen 1. oyuncu ve ikinci bir pencerede yüklenen 2. oyuncu aynı oyun durumunu görecektir. Bununla çok oyunculu bir sanal gerçeklik oyununu tamamladınız. Ayrıca iki oyuncu, hedefi tamamlamak için işbirliği yapmalıdır. Nihai ürün aşağıdakilerle eşleşecektir.

Birden fazla istemci arasında senkronize edilmiş bitmiş çok oyunculu oyun
Bitmiş çok oyunculu oyun, birden fazla istemci arasında senkronize edildi. (Büyük önizleme)

Çözüm

Bu, çok oyunculu bir sanal gerçeklik oyunu yaratma konusundaki eğitimimizi tamamlıyor. Bu süreçte, A-Frame VR'de 3 boyutlu modelleme ve WebSockets kullanan gerçek zamanlı çok oyunculu deneyimler de dahil olmak üzere bir dizi konuya değindiniz.

Dokunduğumuz kavramlardan yola çıkarak, iki oyuncu için daha sorunsuz bir deneyim nasıl sağlarsınız? Bu, oyun durumunun senkronize olup olmadığını kontrol etmeyi ve aksi takdirde kullanıcıyı uyarmayı içerebilir. Terminal durumu ve oynatıcı bağlantı durumu için basit görsel göstergeler de yapabilirsiniz.

Oluşturduğumuz çerçeve ve tanıttığımız kavramlar göz önüne alındığında, artık bu soruları yanıtlayacak ve çok daha fazlasını inşa edecek araçlara sahipsiniz.

Bitmiş kaynak kodunu burada bulabilirsiniz.

Atlamadan sonra daha fazlası! Aşağıdan okumaya devam edin ↓