/*
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ║
║ 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
);
}
}
}