Ana içeriğe geç

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:

İşaretAnlamı
🔒 ZORUNLUHer 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;
// ──────────────────────────────────────────────────────────────────
Neden Dictionary?

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;
}
// ──────────────────────────────────────────────────────────────────
Kritik

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

TipFormat
Oyuncu listesiROOMCODE:PLAYERS:Ad:Rol:ID,Ad:Rol:ID,...
Standart komutPlayerId:MOVE:CMD:KOMUT_ADI
Parametreli komutPlayerId:MOVE:CMD:KOMUT:PARAM1:PARAM2
Yön tuşuPlayerId: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

DurumKullanım
LobiRemoteNex.SendData("STATE:LOBBY")
Oyun başladıStartCoroutine(SendStateRoutine("STATE:GAME_STARTED"))
Oyun bittiStartCoroutine(SendStateRoutine("STATE:GAME_OVER"))
Oyun sonrası beklemeStartCoroutine(SendStateRoutine("STATE:POST_GAME"))
Özel durumRemoteNex.SendData("STATE:ÖZEL_DURUM:...")

📋 Kontrol Listesi

Oyununuzu göndermeden önce her maddeyi işaretleyin.

🔒 Zorunlu

  • PlayerData sınıfı mevcut (id, displayName, role)
  • Dictionary<string, PlayerData> players tanımlı
  • bool isGameActive tanımlı
  • OnEnableRemoteNex.OnInputReceived += OnData
  • OnDisableRemoteNex.OnInputReceived -= OnData
  • public void HandleInput(string data) mevcut ve OnData'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
  • SendStateRoutine coroutine mevcut
  • ShowLobbySTATE:LOBBY gönderiliyor
  • StartGameisGameActive guard var, STATE:GAME_STARTED gönderiliyor
  • RestartGameisGameActive = false sonra StartGame() çağrılıyor
  • EndGameSTATE:GAME_OVER gö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 OnData iç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.