Unity Oyun Entegrasyonu
Bu kılavuz, Unity oyununuzun RemoteNex sistemiyle nasıl entegre edileceğini adım adım açıklar.
Her bölüm iki kategoriden birine girer:
| İşaret | Anlamı |
|---|---|
| 🔒 ZORUNLU | Her oyunda birebir aynıdır. Değiştirmeyin. |
| 🎮 OYUNA ÖZEL | Şablon sabittir, içeriği siz doldurursunuz. |
Mesaj Akışı
Bir kontrolcü butona bastığında verinin Unity'e ulaşana kadar geçtiği yol:
[HTML Kontrolcü]
window.ReactNativeWebView.postMessage("MOVE:CMD:START_GAME")
↓
[Mobil Uygulama]
"MOVE:" prefix'ini kaldırır → SendMove("CMD:START_GAME")
↓
[Sunucu]
PlayerId ve MOVE: ekler → "abc123:MOVE:CMD:START_GAME"
↓
[Unity — RemoteNex.OnInputReceived]
OnData("abc123:MOVE:CMD:START_GAME")
Bu akış sayesinde Unity'e her zaman PlayerId:MOVE:... formatında mesaj gelir.
🔒 1. Oyuncu Veri Modeli ve Temel Değişkenler
// ── REMOTENEX: OYUNCU VERİ MODELİ ─────────────────────────────────
[System.Serializable]
public class PlayerData
{
public string id; // Sunucudan gelen benzersiz oyuncu ID'si
public string displayName; // Görünen isim
public string role; // "M" = Master (oda kurucusu), "N" = Normal
// 🎮 OYUNA ÖZEL: buraya ek alanlar ekleyebilirsiniz
// public int score;
// public int health;
}
private Dictionary<string, PlayerData> players = new Dictionary<string, PlayerData>();
// ──────────────────────────────────────────────────────────────────
// ── REMOTENEX: OYUN DURUMU ─────────────────────────────────────────
private bool isGameActive = false;
// ──────────────────────────────────────────────────────────────────
Oyuncu ID'siyle O(1) erişim sağlar. Gelen her mesajda kimin gönderdiğini anında bulursunuz.
🔒 2. Event Subscription
// ── REMOTENEX: EVENT SUBSCRIPTION ─────────────────────────────────
void OnEnable()
{
RemoteNex.OnInputReceived += OnData;
}
void OnDisable()
{
RemoteNex.OnInputReceived -= OnData;
}
// ──────────────────────────────────────────────────────────────────
Start() yerine her zaman OnEnable() / OnDisable() kullanın. OnDisable'da temizlik yapılmazsa sahne yeniden yüklendiğinde memory leak ve çift tetiklenme oluşur.
🔒 3. HandleInput Metodu
Inspector üzerinden RemoteNexRelay bileşeniyle bağlantı kurulabilmesi için bu metodun adı her oyunda aynı olmalıdır.
// ── REMOTENEX: RELAY BAĞLANTISI ────────────────────────────────────
// Bu metodun adını değiştirmeyin.
public void HandleInput(string data)
{
OnData(data);
}
// ──────────────────────────────────────────────────────────────────
🔒 4. Merkezi Mesaj Handler (OnData)
Sunucudan gelen tüm mesajlar bu metoda düşer. Yapıyı koruyun; oyununuza özel komutlar yalnızca işaretli alana girer.
// ── REMOTENEX: MERKEZİ MESAJ HANDLER ──────────────────────────────
void OnData(string raw)
{
if (string.IsNullOrEmpty(raw)) return;
// 1. Oyuncu listesi — her zaman en önce işlenir
if (raw.Contains("PLAYERS:"))
{
ParsePlayerList(raw);
return;
}
// 2. Ses komutları — tüm oyunlarda standarttır
if (raw.Contains(":CMD:MUTE")) { AudioListener.volume = 0f; return; }
if (raw.Contains(":CMD:UNMUTE")) { AudioListener.volume = 1f; return; }
// 3. Mesajı parçala
// Gelen format: "PlayerId:MOVE:CMD:ACTION" veya "PlayerId:MOVE:CMD:ACTION:PARAM"
string[] parts = raw.Split(':');
if (parts.Length < 2) return;
string senderId = parts[0].Trim();
// Sistem mesajlarını ayıkla
if (string.IsNullOrEmpty(senderId) ||
senderId == "SERVER" ||
senderId.Contains("STATE") ||
senderId.Contains("CMD")) return;
// 4. "CMD" bloğunu bul
int cmdIndex = -1;
for (int i = 0; i < parts.Length; i++)
if (parts[i] == "CMD") { cmdIndex = i; break; }
if (cmdIndex == -1 || parts.Length <= cmdIndex + 1) return;
string action = parts[cmdIndex + 1].Trim();
// 5. Oyun yönetimi komutları — zorunlu
if (action == "START_GAME") { StartGame(); return; }
if (action == "RESTART_GAME") { RestartGame(); return; }
// 6. 🎮 OYUNA ÖZEL: Kendi komutlarınızı buraya ekleyin
if (!isGameActive) return;
// Örnek:
// if (action == "FIRE" && players.ContainsKey(senderId))
// players[senderId].Fire();
//
// if (action == "BUZZ" && isBuzzerOpen)
// HandleBuzz(senderId);
//
// if (action == "ANS_0" || action == "ANS_1" || action == "ANS_2" || action == "ANS_3")
// HandleAnswer(senderId, int.Parse(action.Split('_')[1]));
}
// ──────────────────────────────────────────────────────────────────
Yön Tuşu Parselamak (PRESS / RELEASE)
Kontrolcüden MOVE:UP:PRESS gibi tuş durumu geliyorsa CMD bloğu yoktur. Bu mesajları ayrıca kontrol edin:
// OnData içinde, CMD bloğu aramadan önce:
// Gelen format: "PlayerId:MOVE:UP:PRESS" veya "PlayerId:MOVE:UP:RELEASE"
if (parts.Length >= 4 && parts[1] == "MOVE" && parts[2] != "CMD")
{
string dir = parts[2].Trim(); // "UP", "DOWN", "LEFT", "RIGHT"
string state = parts[3].Trim(); // "PRESS", "RELEASE"
if (!isGameActive || !players.ContainsKey(senderId)) return;
// 🎮 OYUNA ÖZEL:
// HandleMovement(senderId, dir, state);
return;
}
Sayısal Parametre Parselamak (Joystick)
Joystick açısı ve gücü gibi sayısal veriler CMD bloğunun ardından gelir:
// Gelen format: "PlayerId:MOVE:CMD:MOVE:270.5:0.85"
if (action == "MOVE" && parts.Length > cmdIndex + 3)
{
try
{
float angle = float.Parse(parts[cmdIndex + 2].Replace(',', '.'),
System.Globalization.CultureInfo.InvariantCulture);
float power = float.Parse(parts[cmdIndex + 3].Replace(',', '.'),
System.Globalization.CultureInfo.InvariantCulture);
if (players.ContainsKey(senderId))
{
// 🎮 OYUNA ÖZEL:
// players[senderId].SetInput(angle, power);
}
}
catch { /* Hatalı veriyi sessizce yoksay */ }
}
🔒 5. Oyuncu Listesi Parser
Sunucu, oda durumu her değiştiğinde oyuncu listesini tüm istemcilere gönderir. Bu metot listeyi ayrıştırır, yeni oyuncuları ekler ve ayrılanları temizler.
// ── REMOTENEX: OYUNCU LİSTESİ PARSER ──────────────────────────────
// Gelen format: "ROOMCODE:PLAYERS:Ad1:Rol1:ID1,Ad2:Rol2:ID2,..."
void ParsePlayerList(string raw)
{
string[] splitData = raw.Split(new string[] { "PLAYERS:" },
System.StringSplitOptions.None);
if (splitData.Length < 2) return;
string listPart = splitData[1];
if (string.IsNullOrEmpty(listPart)) return;
string[] userList = listPart.Split(',');
HashSet<string> activeIds = new HashSet<string>();
foreach (var user in userList)
{
string[] p = user.Split(new char[] { ':' },
System.StringSplitOptions.RemoveEmptyEntries);
if (p.Length < 3) continue;
string pName = p[0].Trim();
string pRole = p[1].Trim(); // "M" veya "N"
string pId = p[2].Trim();
if (string.IsNullOrEmpty(pId)) continue;
activeIds.Add(pId);
if (!players.ContainsKey(pId))
{
players.Add(pId, new PlayerData
{
id = pId,
displayName = pName,
role = pRole
});
// 🎮 OYUNA ÖZEL: Yeni oyuncu katıldığında
// Örn: karakter spawn etme, skor satırı oluşturma...
// OnPlayerJoined(pId);
}
else
{
players[pId].displayName = pName;
players[pId].role = pRole;
}
}
// Bağlantısı kesilen oyuncuları temizle
var disconnected = new List<string>();
foreach (var id in players.Keys)
if (!activeIds.Contains(id)) disconnected.Add(id);
foreach (var id in disconnected)
{
// 🎮 OYUNA ÖZEL: Oyuncu ayrıldığında
// Örn: karakteri sil, bota dönüştür...
// OnPlayerLeft(id);
players.Remove(id);
}
}
// ──────────────────────────────────────────────────────────────────
🔒 6. Güvenilir Mesaj Gönderimi
Ağ kayıplarına karşı kritik durum mesajları (oyun başlangıcı, bitişi) birden fazla kez gönderilmelidir.
// ── REMOTENEX: GÜVENİLİR MESAJ GÖNDERİMİ ─────────────────────────
IEnumerator SendStateRoutine(string message)
{
for (int i = 0; i < 5; i++)
{
RemoteNex.SendData(message);
yield return new WaitForSeconds(0.15f);
}
}
// ──────────────────────────────────────────────────────────────────
Lobi gibi kritik olmayan durumlarda tek seferlik kullanım yeterlidir:
RemoteNex.SendData("STATE:LOBBY");
🎮 7. Oyun Durum Metodları
Bu metodların RemoteNex satırları sabittir; UI, spawn ve mekanik kodları oyundan oyuna değişir.
void ShowLobby()
{
isGameActive = false;
// 🎮 OYUNA ÖZEL: UI panellerini ayarla, objeleri sıfırla...
RemoteNex.SendData("STATE:LOBBY"); // 🔒 ZORUNLU
}
public void StartGame()
{
if (isGameActive) return; // 🔒 ZORUNLU — çift tetiklenmeyi engeller
isGameActive = true;
// 🎮 OYUNA ÖZEL: UI geçişleri, spawn, skor sıfırlama...
StartCoroutine(SendStateRoutine("STATE:GAME_STARTED")); // 🔒 ZORUNLU
}
public void RestartGame()
{
isGameActive = false;
StartGame();
}
void EndGame()
{
isGameActive = false;
// 🎮 OYUNA ÖZEL: Kazanan tespiti, sonuç ekranı...
StartCoroutine(SendStateRoutine("STATE:GAME_OVER")); // 🔒 ZORUNLU
}
📨 Mesaj Protokol Referansı
Unity'e Gelen Mesajlar
| Tip | Format |
|---|---|
| Oyuncu listesi | ROOMCODE:PLAYERS:Ad:Rol:ID,Ad:Rol:ID,... |
| Standart komut | PlayerId:MOVE:CMD:KOMUT_ADI |
| Parametreli komut | PlayerId:MOVE:CMD:KOMUT:PARAM1:PARAM2 |
| Yön tuşu | PlayerId:MOVE:YON:DURUM |
Örnekler:
// Oyuncu listesi (sunucudan)
123456:PLAYERS:Ali:M:abc123,Veli:N:xyz789
// Oyun başlatma / yeniden başlatma
abc123:MOVE:CMD:START_GAME
abc123:MOVE:CMD:RESTART_GAME
// Yön tuşları (basma / bırakma)
abc123:MOVE:UP:PRESS
abc123:MOVE:UP:RELEASE
abc123:MOVE:DOWN:PRESS
// Joystick (açı ve güç)
abc123:MOVE:CMD:MOVE:270.5:0.85
// Quiz — buzzer ve cevap seçimi
abc123:MOVE:CMD:BUZZ
abc123:MOVE:CMD:ANS_2
Unity'den Kontrolcülere Gönderilen Mesajlar
| Durum | Kullanım |
|---|---|
| Lobi | RemoteNex.SendData("STATE:LOBBY") |
| Oyun başladı | StartCoroutine(SendStateRoutine("STATE:GAME_STARTED")) |
| Oyun bitti | StartCoroutine(SendStateRoutine("STATE:GAME_OVER")) |
| Oyun sonrası bekleme | StartCoroutine(SendStateRoutine("STATE:POST_GAME")) |
| Özel durum | RemoteNex.SendData("STATE:ÖZEL_DURUM:...") |
📋 Kontrol Listesi
Oyununuzu göndermeden önce her maddeyi işaretleyin.
🔒 Zorunlu
-
PlayerDatasınıfı mevcut (id,displayName,role) -
Dictionary<string, PlayerData> playerstanımlı -
bool isGameActivetanımlı -
OnEnable→RemoteNex.OnInputReceived += OnData -
OnDisable→RemoteNex.OnInputReceived -= OnData -
public void HandleInput(string data)mevcut veOnData'yı çağırıyor -
OnData→ null kontrolü, PLAYERS önce, MUTE/UNMUTE var, CMD ayrıştırması var -
ParsePlayerList→ ekleme, güncelleme ve ayrılma işleniyor -
SendStateRoutinecoroutine mevcut -
ShowLobby→STATE:LOBBYgönderiliyor -
StartGame→isGameActiveguard var,STATE:GAME_STARTEDgönderiliyor -
RestartGame→isGameActive = falsesonraStartGame()çağrılıyor -
EndGame→STATE:GAME_OVERgönderiliyor - Sahnedeki GameManager objesinin adı GameManager (WebGL için kritik)
🎮 Oyuna Özel
-
PlayerData'ya gerekli alanlar eklendi (skor, can, takım vb.) - Katılma/ayrılma olaylarına tepki verildi
- Oyuna özgü komutlar
OnDataiçine eklendi - Oyun metodlarına mekanik kodlar eklendi
🐛 Sık Yapılan Hatalar
1. Event'i Start() içinde subscribe etmek
// ❌ YANLIŞ
void Start() { RemoteNex.OnInputReceived += OnData; }
// ✅ DOĞRU
void OnEnable() { RemoteNex.OnInputReceived += OnData; }
void OnDisable() { RemoteNex.OnInputReceived -= OnData; }
2. HandleInput metodunu yeniden adlandırmak
// ❌ YANLIŞ — Inspector bağlantısı kopar
public void MyInputHandler(string data) { ... }
// ✅ DOĞRU
public void HandleInput(string data) { OnData(data); }
3. isGameActive kontrolünü atlamak
// ❌ YANLIŞ — lobi ekranındayken de komutlar işlenir
if (action == "FIRE") DoFire();
// ✅ DOĞRU — oyun aktif değilse durdur
if (!isGameActive) return;
if (action == "FIRE") DoFire();
4. StartGame'i çift tetiklemeden korumamak
// ❌ YANLIŞ
public void StartGame() { isGameActive = true; }
// ✅ DOĞRU
public void StartGame()
{
if (isGameActive) return;
isGameActive = true;
}
5. Array erişimini güvenceye almamak
// ❌ YANLIŞ — IndexOutOfRangeException riski
string action = parts[cmdIndex + 1];
// ✅ DOĞRU
if (parts.Length > cmdIndex + 1)
string action = parts[cmdIndex + 1].Trim();
6. Dictionary'e kontrolsüz erişmek
// ❌ YANLIŞ — KeyNotFoundException fırlatır
players[senderId].Fire();
// ✅ DOĞRU
if (players.ContainsKey(senderId))
players[senderId].Fire();
7. GameManager objesinin adını değiştirmek
WebGL buildında React, Unity'e şu şekilde mesaj gönderir:
sendMessage("GameManager", "HandleInput", data);
Sahnenizdeki yönetici objenizin adı GameManager olmalıdır. Farklı bir ad kullanıyorsanız mesajlar Unity'e hiç ulaşmaz.