/* ╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ GEOSUS UNITY TEST CLIENT - KOMPLETNÍ PRŮVODCE ║ ║ ║ ║ Tento soubor je součástí testovacího klienta pro hru GeoSus - multiplayer GPS hru inspirovanou ║ ║ hrou Among Us. Tento klient slouží jako referenční implementace pro studenty, kteří chtějí ║ ║ postavit vlastní hru na základě GeoSus serveru. ║ ║ ║ ╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 📚 OBSAH DOKUMENTACE: ║ ║ ═══════════════════ ║ ║ ║ ║ 1. ARCHITEKTURA APLIKACE ║ ║ 2. SÍŤOVÁ KOMUNIKACE ║ ║ 3. HERNÍ LOGIKA ║ ║ 4. UŽIVATELSKÉ ROZHRANÍ ║ ║ 5. VYKRESLOVÁNÍ MAPY ║ ║ 6. STATISTIKY HRÁČŮ ║ ║ ║ ╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 📁 STRUKTURA SOUBORŮ: ║ ║ ═════════════════════ ║ ║ ║ ║ UnityTestClient_Main.cs - Hlavní třída, inicializace, herní smyčka ║ ║ UnityTestClient_Network.cs - Síťová komunikace, zpracování zpráv ║ ║ UnityTestClient_UI.cs - Veškeré uživatelské rozhraní (IMGUI) ║ ║ UnityTestClient_Map.cs - Vykreslování mapy pomocí 3D objektů ║ ║ UnityTestClient_Game.cs - Herní mechaniky (pohyb, tasky, sabotáže) ║ ║ UnityTestClient_Stats.cs - HTTP API pro statistiky hráčů ║ ║ ║ ╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 🎮 1. ARCHITEKTURA APLIKACE ║ ║ ═══════════════════════════ ║ ║ ║ ║ Aplikace používá PARTIAL CLASS pattern - všechny soubory definují stejnou třídu ║ ║ UnityTestClient, která dědí z MonoBehaviour. Unity je automaticky spojí dohromady. ║ ║ ║ ║ Hlavní herní stavy (AppState): ║ ║ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ║ ║ │ MainMenu │───▶│ Lobby │───▶│ Loading │───▶│ InGame │ ║ ║ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ ║ ║ │ │ │ ║ ║ │ │ ▼ ║ ║ │ │ ┌─────────────┐ ║ ║ │ │ │ Meeting │ ║ ║ │ │ └─────────────┘ ║ ║ │ │ │ ║ ║ ▼ ▼ ▼ ║ ║ ┌─────────────────────────────────────────────────────────────────────┐ ║ ║ │ GameEnded │ ║ ║ └─────────────────────────────────────────────────────────────────────┘ ║ ║ ║ ╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 🌐 2. SÍŤOVÁ KOMUNIKACE ║ ║ ═══════════════════════ ║ ║ ║ ║ Komunikace probíhá přes TCP socket s AES-256 šifrováním. ║ ║ ║ ║ Handshake proces: ║ ║ 1. Klient posílá ClientHello s UUID a jménem ║ ║ 2. Server odpovídá ServerHello s RSA veřejným klíčem ║ ║ 3. Klient generuje AES klíč, zašifruje ho RSA a pošle KeyExchange ║ ║ 4. Server potvrdí KeyExchangeAck - od teď je vše šifrované AES ║ ║ ║ ║ Formát zpráv: ║ ║ ┌────────────┬──────────────────────────────────────────┐ ║ ║ │ 4 bajty │ N bajtů │ ║ ║ │ (délka) │ (JSON data, po handshake šifrovaná AES) │ ║ ║ └────────────┴──────────────────────────────────────────┘ ║ ║ ║ ║ Důležité typy zpráv: ║ ║ • CreateLobby/JoinLobby - vytvoření/připojení do lobby ║ ║ • StartGame - spuštění hry (pouze owner) ║ ║ • UpdatePosition - aktualizace pozice hráče ║ ║ • KillAttempt - pokus o zabití (pouze impostor) ║ ║ • ReportBody/CallEmergencyMeeting - svolání schůze ║ ║ • CastVote - hlasování ║ ║ • CompleteTask - dokončení úkolu (server validuje pozici) ║ ║ • StartSabotage - spuštění sabotáže (pouze impostor) ║ ║ • ActivateRepairStation - oprava sabotáže ║ ║ ║ ╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 🎯 3. HERNÍ LOGIKA ║ ║ ═════════════════ ║ ║ ║ ║ Role hráčů: ║ ║ • Crew (posádka) - plní úkoly, hlasuje, reportuje těla ║ ║ • Impostor - zabíjí, sabotuje, předstírá plnění úkolů ║ ║ ║ ║ Herní fáze (GamePhase): ║ ║ • Lobby - čekání na hráče, nastavení hry ║ ║ • Loading - načítání mapových dat z Overpass API ║ ║ • Playing - hlavní herní fáze ║ ║ • Meeting - diskuze před hlasováním ║ ║ • Voting - hlasování o vyloučení ║ ║ • Ended - konec hry ║ ║ ║ ║ Typy úkolů: ║ ║ • Všechny úkoly jsou INSTANT - stačí přijít na místo a stisknout USE ║ ║ • Server validuje pozici hráče (musí být do 5m od úkolu) ║ ║ • Duchové (mrtví crew) mohou také plnit úkoly ║ ║ ║ ║ Sabotáže (SabotageType): ║ ║ • CommsBlackout - blokuje reporty a emergency meetings, 1 opravná stanice ║ ║ • CriticalMeltdown - časový limit, 2 stanice musí opravovat současně ║ ║ ║ ╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 🖥️ 4. UŽIVATELSKÉ ROZHRANÍ ║ ║ ═══════════════════════════ ║ ║ ║ ║ UI používá Unity IMGUI (OnGUI) pro jednoduchost a přenositelnost. ║ ║ Pro produkční hru doporučujeme použít Unity UI (Canvas) nebo UI Toolkit. ║ ║ ║ ║ Škálování UI: ║ ║ • Používáme relativní jednotky (procenta obrazovky) ║ ║ • GUI.matrix pro globální škálování ║ ║ • Responzivní layout pomocí GUILayout ║ ║ ║ ║ Hlavní obrazovky: ║ ║ • MainMenu - připojení, vytvoření lobby, statistiky ║ ║ • Lobby - seznam hráčů, nastavení, chat ║ ║ • HUD - role, úkoly, minimap, sabotáže ║ ║ • Meeting - hlasovací panel, timer ║ ║ • GameEnd - výsledky, statistiky ║ ║ ║ ╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 🗺️ 5. VYKRESLOVÁNÍ MAPY ║ ║ ═══════════════════════ ║ ║ ║ ║ Mapa se vykresluje pomocí 3D GameObjects v prostoru Unity: ║ ║ ║ ║ Převod GPS → Unity souřadnice: ║ ║ • Střed mapy (playAreaCenter) = Vector3(0, 0, 0) ║ ║ • 1 metr reálně = 1 Unity jednotka ║ ║ • Latitude → Z osa, Longitude → X osa ║ ║ ║ ║ Vrstvy mapy (od spodu): ║ ║ 1. Podklad (zelená plocha) - Y = 0 ║ ║ 2. Oblasti (parky, voda) - Y = 0.01 ║ ║ 3. Cesty - Y = 0.02 ║ ║ 4. Budovy - Y = 0 až výška budovy ║ ║ 5. POI markery - Y = 0.5 ║ ║ 6. Hráči - Y = 1 ║ ║ 7. Markery (úkoly, těla) - Y = 0.3 ║ ║ ║ ║ Dynamické objekty: ║ ║ • Hráči - capsule s barvou podle stavu ║ ║ • Těla - ležící kapsle ║ ║ • Úkoly - žluté diamanty ║ ║ • Opravné stanice - červené/zelené krychle ║ ║ ║ ╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 📊 6. STATISTIKY HRÁČŮ ║ ║ ═══════════════════════ ║ ║ ║ ║ Server poskytuje HTTP REST API na portu 8088: ║ ║ ║ ║ GET /stats/{playerId} - statistiky konkrétního hráče ║ ║ GET /leaderboard - žebříček hráčů ║ ║ GET /health - stav serveru ║ ║ ║ ║ Statistiky zahrnují: ║ ║ • Počet her, výher, proher ║ ║ • Zabití, smrti, K/D ratio ║ ║ • Dokončené úkoly ║ ║ • Win rate jako impostor/crew ║ ║ • Přesnost hlasování ║ ║ ║ ╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 🚀 JAK ZAČÍT: ║ ║ ═════════════ ║ ║ ║ ║ 1. Přidejte VŠECHNY soubory UnityTestClient_*.cs do Unity projektu ║ ║ 2. Přidejte ClientSDK (Protocol.cs, Encryption.cs, EventDispatcher.cs, GameClient.cs) ║ ║ 3. Vytvořte prázdný GameObject a přidejte komponentu UnityTestClient ║ ║ 4. Nastavte v Inspectoru serverHost a serverPort ║ ║ 5. Spusťte server (dotnet run v Server/) ║ ║ 6. Spusťte hru v Unity ║ ║ ║ ║ Pro vlastní hru: ║ ║ • Nahraďte IMGUI za Unity UI Canvas ║ ║ • Přidejte vlastní 3D modely místo primitivních tvarů ║ ║ • Implementujte GPS pohyb místo WASD (Input.location) ║ ║ • Přidejte zvuky, particle efekty, animace ║ ║ ║ ╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ ⚠️ DŮLEŽITÉ POZNÁMKY: ║ ║ ═════════════════════ ║ ║ ║ ║ • GameClient.Update() MUSÍ být volán každý frame pro zpracování síťových událostí ║ ║ • Pozice hráče se odesílá automaticky každých 100ms ║ ║ • Server má anti-cheat - příliš rychlý pohyb způsobí varování/kick ║ ║ • Všechny herní akce (zabití, hlasování) validuje server ║ ║ • MapData může být null pokud Overpass API není dostupné ║ ║ ║ ╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝ */ using UnityEngine; using System; using System.Collections.Generic; using GeoSus.Client; /// /// Hlavní třída Unity testovacího klienta pro GeoSus. /// Tato partial třída obsahuje inicializaci, herní smyčku a správu stavů. /// /// POUŽITÍ: /// 1. Přidejte tento skript na prázdný GameObject /// 2. Nastavte serverHost a serverPort v Inspectoru /// 3. Spusťte hru /// public partial class UnityTestClient : MonoBehaviour { #region ═══════════════════════════════════════════════════════════════════ // KONFIGURACE SERVERU // ════════════════════════════════════════════════════════════════════════ // Tyto hodnoty nastavte v Unity Inspectoru nebo zde přímo v kódu. // ServerHost je IP adresa nebo hostname serveru. // ServerPort je TCP port pro herní komunikaci (výchozí 7777). // HttpPort je port pro REST API statistik (výchozí 8088). #endregion [Header("Nastavení serveru")] [Tooltip("IP adresa nebo hostname GeoSus serveru")] public string serverHost = "127.0.0.1"; [Tooltip("TCP port pro herní komunikaci")] public int serverPort = 7777; [Tooltip("HTTP port pro statistiky API (ignorováno pokud useHttps=true)")] public int httpPort = 8088; [Tooltip("Použít HTTPS pro Stats API (pro produkční server)")] public bool useHttps = false; #region ═══════════════════════════════════════════════════════════════════ // HERNÍ STAVY // ════════════════════════════════════════════════════════════════════════ // AppState určuje, která obrazovka se zobrazuje a jaká logika běží. // Toto je KLIENTSKÝ stav, nezaměňovat s GamePhase ze serveru. #endregion /// /// Stav aplikace určující aktuální obrazovku. /// MainMenu -> Lobby -> Loading -> InGame -> GameEnded /// public enum AppState { /// Hlavní menu - připojení, vytvoření/vstup do lobby MainMenu, /// V lobby - čekání na hráče, nastavení hry Lobby, /// Načítání - stahování mapových dat Loading, /// Ve hře - hlavní gameplay InGame, /// Konec hry - zobrazení výsledků GameEnded } #region ═══════════════════════════════════════════════════════════════════ // HLAVNÍ PROMĚNNÉ // ════════════════════════════════════════════════════════════════════════ #endregion /// /// Aktuální stav aplikace (která obrazovka je aktivní) /// [HideInInspector] public AppState currentState = AppState.MainMenu; /// /// Instance herního klienta z ClientSDK. /// Obsahuje veškerou síťovou logiku a herní data. /// protected GameClient client; /// /// Unikátní ID tohoto klienta (generuje se při startu) /// protected string clientUuid; /// /// Zobrazované jméno hráče /// protected string displayName = "Hráč"; /// /// Fronta notifikací k zobrazení na obrazovce /// protected Queue notifications = new Queue(); /// /// Aktuálně zobrazená notifikace (null = žádná) /// protected NotificationData currentNotification; /// /// Čas konce zobrazení aktuální notifikace /// protected float notificationEndTime; /// /// Chybová zpráva k zobrazení (null = žádná chyba) /// protected string errorMessage; /// /// Čas zmizení chybové zprávy /// protected float errorMessageEndTime; /// /// Hlavní herní kamera /// protected Camera playerCamera; /// /// Výška kamery nad mapou /// protected float cameraHeight = 100f; #region ═══════════════════════════════════════════════════════════════════ // DATOVÉ STRUKTURY // ════════════════════════════════════════════════════════════════════════ #endregion /// /// Struktura pro notifikaci na obrazovce /// protected struct NotificationData { /// Text notifikace public string message; /// Barva pozadí public Color color; /// Doba zobrazení v sekundách public float duration; /// Ikona (emoji nebo text) public string icon; } #region ═══════════════════════════════════════════════════════════════════ // UNITY LIFECYCLE // ════════════════════════════════════════════════════════════════════════ // Unity volá tyto metody automaticky: // Awake() - při vytvoření objektu (před Start) // Start() - před prvním Update // Update() - každý frame // OnDestroy() - při zničení objektu // OnGUI() - pro vykreslení IMGUI (může být vícekrát za frame) #endregion /// /// Inicializace při vytvoření objektu. /// Generujeme unikátní UUID pro tohoto klienta. /// void Awake() { // Generujeme unikátní ID klienta // V produkční hře byste toto ukládali do PlayerPrefs clientUuid = Guid.NewGuid().ToString("N").Substring(0, 8); // Načteme uložené jméno hráče (pokud existuje) displayName = PlayerPrefs.GetString("PlayerName", "Hráč" + UnityEngine.Random.Range(1, 999)); Debug.Log($"[GeoSus] Klient inicializován s UUID: {clientUuid}"); } /// /// Inicializace před prvním frame. /// void Start() { // Nastavení kamery pro top-down pohled SetupCamera(); Debug.Log("[GeoSus] Test klient připraven"); } /// /// Hlavní herní smyčka - volá se každý frame. /// DŮLEŽITÉ: Zde musíme volat client.Update() pro zpracování síťových událostí! /// void Update() { // ═══════════════════════════════════════════════════════════════════ // KRITICKÉ: Zpracování síťových událostí // ═══════════════════════════════════════════════════════════════════ // GameClient používá EventDispatcher pro thread-safe přenos událostí // ze síťového vlákna do hlavního Unity vlákna. // BEZ TOHOTO VOLÁNÍ nebudete dostávat žádné události ze serveru! if (client != null) { client.Update(); } // Zpracování vstupu podle aktuálního stavu switch (currentState) { case AppState.InGame: // Herní logika - pohyb, akce HandlePlayerInput(); UpdateGameLogic(); break; } // Aktualizace notifikací UpdateNotifications(); // Aktualizace herních objektů na mapě if (currentState == AppState.InGame || currentState == AppState.Loading) { UpdateMapObjects(); } // Automatické obnovování statistik UpdateStatsAutoRefresh(); } /// /// Úklid při zničení objektu. /// DŮLEŽITÉ: Vždy se odpojte od serveru při ukončení! /// void OnDestroy() { // Odpojení od serveru if (client != null) { client.Disconnect("Aplikace ukončena"); client.Dispose(); client = null; } // Úklid mapových objektů CleanupMapObjects(); Debug.Log("[GeoSus] Klient ukončen"); } /// Příznak, zda jsou GUI styly inicializované private bool stylesInitialized = false; /// /// Vykreslení IMGUI - volá se každý frame (může i vícekrát). /// Veškeré UI se vykresluje zde. /// void OnGUI() { // Inicializace stylů při prvním volání OnGUI if (!stylesInitialized) { InitializeUIStyles(); stylesInitialized = true; } // Aplikace škálování UI pro různé rozlišení ApplyUIScaling(); // Vykreslení UI podle aktuálního stavu switch (currentState) { case AppState.MainMenu: DrawMainMenu(); break; case AppState.Lobby: DrawLobbyScreen(); break; case AppState.Loading: DrawLoadingScreen(); break; case AppState.InGame: DrawGameHUD(); DrawMeetingPanel(); // Zobrazí se jen pokud je meeting aktivní break; case AppState.GameEnded: DrawGameEndScreen(); break; } // Overlay prvky (notifikace, chyby) - vždy navrchu DrawNotifications(); DrawErrorMessage(); // Debug panel (pouze v editoru) #if UNITY_EDITOR DrawDebugPanel(); #endif } #region ═══════════════════════════════════════════════════════════════════ // POMOCNÉ METODY // ════════════════════════════════════════════════════════════════════════ #endregion /// /// Zobrazí notifikaci na obrazovce. /// Notifikace se řadí do fronty a zobrazují postupně. /// /// Text zprávy /// Barva pozadí /// Ikona/emoji (volitelné) protected void ShowNotification(string message, Color color, string icon) { ShowNotification(message, color, 3f, icon); } /// /// Zobrazí notifikaci na obrazovce. /// Notifikace se řadí do fronty a zobrazují postupně. /// /// Text zprávy /// Barva pozadí /// Doba zobrazení v sekundách /// Ikona/emoji (volitelné) protected void ShowNotification(string message, Color color, float duration = 3f, string icon = "") { notifications.Enqueue(new NotificationData { message = message, color = color, duration = duration, icon = icon }); } /// /// Aktualizace fronty notifikací /// private void UpdateNotifications() { // Pokud aktuální notifikace vypršela, zobrazíme další if (currentNotification.message != null && Time.time > notificationEndTime) { currentNotification = default; } // Pokud není žádná notifikace a fronta není prázdná, zobrazíme další if (currentNotification.message == null && notifications.Count > 0) { currentNotification = notifications.Dequeue(); notificationEndTime = Time.time + currentNotification.duration; } } /// /// Zobrazí chybovou zprávu /// /// Text chyby /// Doba zobrazení protected void ShowError(string message, float duration = 5f) { errorMessage = message; errorMessageEndTime = Time.time + duration; Debug.LogError($"[GeoSus] {message}"); } /// /// Nastavení kamery pro top-down pohled /// private void SetupCamera() { // Najdeme nebo vytvoříme hlavní kameru playerCamera = Camera.main; if (playerCamera == null) { GameObject camObj = new GameObject("MainCamera"); playerCamera = camObj.AddComponent(); camObj.tag = "MainCamera"; } // Nastavení pro top-down pohled playerCamera.orthographic = true; playerCamera.orthographicSize = 50f; // Výchozí zoom (50 metrů od středu) playerCamera.transform.position = new Vector3(0, cameraHeight, 0); playerCamera.transform.rotation = Quaternion.Euler(90, 0, 0); playerCamera.backgroundColor = new Color(0.2f, 0.3f, 0.2f); // Tmavě zelená playerCamera.clearFlags = CameraClearFlags.SolidColor; // Přidáme naši komponentu pro ovládání kamery if (playerCamera.GetComponent() == null) { playerCamera.gameObject.AddComponent(); } } #region ═══════════════════════════════════════════════════════════════════ // DEBUG PANEL // ════════════════════════════════════════════════════════════════════════ #endregion /// /// Debug panel - zobrazuje interní stav klienta /// Pouze v Unity Editoru /// private void DrawDebugPanel() { GUILayout.BeginArea(new Rect(10, Screen.height - 150, 300, 140)); GUI.Box(new Rect(0, 0, 300, 140), ""); GUILayout.Label($"DEBUG", richTextStyle); GUILayout.Label($"State: {currentState}"); GUILayout.Label($"Connected: {client?.IsConnected ?? false}"); GUILayout.Label($"LobbyId: {client?.LobbyId ?? "null"}"); GUILayout.Label($"Phase: {client?.CurrentLobbyState?.Phase.ToString() ?? "null"}"); GUILayout.Label($"Role: {client?.MyRole?.ToString() ?? "null"}"); GUILayout.Label($"Ping: {client?.Ping ?? 0}ms"); GUILayout.EndArea(); } } /// /// Jednoduchý kontroler kamery pro top-down pohled. /// Umožňuje pohyb pomocí WASD/šipek a zoom pomocí kolečka myši. /// /// POZNÁMKA: V produkční hře byste toto pravděpodobně spojili /// s pohybem hráče - kamera sleduje hráče. /// public class CameraController : MonoBehaviour { [Header("Nastavení kamery")] [Tooltip("Rychlost pohybu kamery")] public float moveSpeed = 50f; [Tooltip("Rychlost zoomu")] public float zoomSpeed = 10f; [Tooltip("Minimální zoom (orthographic size)")] public float minZoom = 10f; [Tooltip("Maximální zoom (orthographic size)")] public float maxZoom = 200f; private Camera cam; void Start() { cam = GetComponent(); } void Update() { // Pohyb kamery - WASD nebo šipky // POZNÁMKA: V plné hře se kamera obvykle pohybuje s hráčem float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); Vector3 movement = new Vector3(h, 0, v) * moveSpeed * Time.deltaTime; transform.position += movement; // Zoom - kolečko myši float scroll = Input.GetAxis("Mouse ScrollWheel"); if (scroll != 0 && cam.orthographic) { cam.orthographicSize = Mathf.Clamp( cam.orthographicSize - scroll * zoomSpeed, minZoom, maxZoom ); } } }