Init
This commit is contained in:
675
Assets/UnityTestClient/UnityTestClient.cs
Normal file
675
Assets/UnityTestClient/UnityTestClient.cs
Normal file
@@ -0,0 +1,675 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 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;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Stav aplikace určující aktuální obrazovku.
|
||||
/// MainMenu -> Lobby -> Loading -> InGame -> GameEnded
|
||||
/// </summary>
|
||||
public enum AppState
|
||||
{
|
||||
/// <summary>Hlavní menu - připojení, vytvoření/vstup do lobby</summary>
|
||||
MainMenu,
|
||||
/// <summary>V lobby - čekání na hráče, nastavení hry</summary>
|
||||
Lobby,
|
||||
/// <summary>Načítání - stahování mapových dat</summary>
|
||||
Loading,
|
||||
/// <summary>Ve hře - hlavní gameplay</summary>
|
||||
InGame,
|
||||
/// <summary>Konec hry - zobrazení výsledků</summary>
|
||||
GameEnded
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// HLAVNÍ PROMĚNNÉ
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Aktuální stav aplikace (která obrazovka je aktivní)
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
public AppState currentState = AppState.MainMenu;
|
||||
|
||||
/// <summary>
|
||||
/// Instance herního klienta z ClientSDK.
|
||||
/// Obsahuje veškerou síťovou logiku a herní data.
|
||||
/// </summary>
|
||||
protected GameClient client;
|
||||
|
||||
/// <summary>
|
||||
/// Unikátní ID tohoto klienta (generuje se při startu)
|
||||
/// </summary>
|
||||
protected string clientUuid;
|
||||
|
||||
/// <summary>
|
||||
/// Zobrazované jméno hráče
|
||||
/// </summary>
|
||||
protected string displayName = "Hráč";
|
||||
|
||||
/// <summary>
|
||||
/// Fronta notifikací k zobrazení na obrazovce
|
||||
/// </summary>
|
||||
protected Queue<NotificationData> notifications = new Queue<NotificationData>();
|
||||
|
||||
/// <summary>
|
||||
/// Aktuálně zobrazená notifikace (null = žádná)
|
||||
/// </summary>
|
||||
protected NotificationData currentNotification;
|
||||
|
||||
/// <summary>
|
||||
/// Čas konce zobrazení aktuální notifikace
|
||||
/// </summary>
|
||||
protected float notificationEndTime;
|
||||
|
||||
/// <summary>
|
||||
/// Chybová zpráva k zobrazení (null = žádná chyba)
|
||||
/// </summary>
|
||||
protected string errorMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Čas zmizení chybové zprávy
|
||||
/// </summary>
|
||||
protected float errorMessageEndTime;
|
||||
|
||||
/// <summary>
|
||||
/// Hlavní herní kamera
|
||||
/// </summary>
|
||||
protected Camera playerCamera;
|
||||
|
||||
/// <summary>
|
||||
/// Výška kamery nad mapou
|
||||
/// </summary>
|
||||
protected float cameraHeight = 100f;
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// DATOVÉ STRUKTURY
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Struktura pro notifikaci na obrazovce
|
||||
/// </summary>
|
||||
protected struct NotificationData
|
||||
{
|
||||
/// <summary>Text notifikace</summary>
|
||||
public string message;
|
||||
/// <summary>Barva pozadí</summary>
|
||||
public Color color;
|
||||
/// <summary>Doba zobrazení v sekundách</summary>
|
||||
public float duration;
|
||||
/// <summary>Ikona (emoji nebo text)</summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Inicializace při vytvoření objektu.
|
||||
/// Generujeme unikátní UUID pro tohoto klienta.
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inicializace před prvním frame.
|
||||
/// </summary>
|
||||
void Start()
|
||||
{
|
||||
// Nastavení kamery pro top-down pohled
|
||||
SetupCamera();
|
||||
|
||||
Debug.Log("[GeoSus] Test klient připraven");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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í!
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Úklid při zničení objektu.
|
||||
/// DŮLEŽITÉ: Vždy se odpojte od serveru při ukončení!
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>Příznak, zda jsou GUI styly inicializované</summary>
|
||||
private bool stylesInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Vykreslení IMGUI - volá se každý frame (může i vícekrát).
|
||||
/// Veškeré UI se vykresluje zde.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Zobrazí notifikaci na obrazovce.
|
||||
/// Notifikace se řadí do fronty a zobrazují postupně.
|
||||
/// </summary>
|
||||
/// <param name="message">Text zprávy</param>
|
||||
/// <param name="color">Barva pozadí</param>
|
||||
/// <param name="icon">Ikona/emoji (volitelné)</param>
|
||||
protected void ShowNotification(string message, Color color, string icon)
|
||||
{
|
||||
ShowNotification(message, color, 3f, icon);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Zobrazí notifikaci na obrazovce.
|
||||
/// Notifikace se řadí do fronty a zobrazují postupně.
|
||||
/// </summary>
|
||||
/// <param name="message">Text zprávy</param>
|
||||
/// <param name="color">Barva pozadí</param>
|
||||
/// <param name="duration">Doba zobrazení v sekundách</param>
|
||||
/// <param name="icon">Ikona/emoji (volitelné)</param>
|
||||
protected void ShowNotification(string message, Color color, float duration = 3f, string icon = "")
|
||||
{
|
||||
notifications.Enqueue(new NotificationData
|
||||
{
|
||||
message = message,
|
||||
color = color,
|
||||
duration = duration,
|
||||
icon = icon
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aktualizace fronty notifikací
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Zobrazí chybovou zprávu
|
||||
/// </summary>
|
||||
/// <param name="message">Text chyby</param>
|
||||
/// <param name="duration">Doba zobrazení</param>
|
||||
protected void ShowError(string message, float duration = 5f)
|
||||
{
|
||||
errorMessage = message;
|
||||
errorMessageEndTime = Time.time + duration;
|
||||
Debug.LogError($"[GeoSus] {message}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nastavení kamery pro top-down pohled
|
||||
/// </summary>
|
||||
private void SetupCamera()
|
||||
{
|
||||
// Najdeme nebo vytvoříme hlavní kameru
|
||||
playerCamera = Camera.main;
|
||||
if (playerCamera == null)
|
||||
{
|
||||
GameObject camObj = new GameObject("MainCamera");
|
||||
playerCamera = camObj.AddComponent<Camera>();
|
||||
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<CameraController>() == null)
|
||||
{
|
||||
playerCamera.gameObject.AddComponent<CameraController>();
|
||||
}
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// DEBUG PANEL
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Debug panel - zobrazuje interní stav klienta
|
||||
/// Pouze v Unity Editoru
|
||||
/// </summary>
|
||||
private void DrawDebugPanel()
|
||||
{
|
||||
GUILayout.BeginArea(new Rect(10, Screen.height - 150, 300, 140));
|
||||
GUI.Box(new Rect(0, 0, 300, 140), "");
|
||||
|
||||
GUILayout.Label($"<color=yellow><b>DEBUG</b></color>", 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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<Camera>();
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/UnityTestClient/UnityTestClient.cs.meta
Normal file
2
Assets/UnityTestClient/UnityTestClient.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 719b1c3bbdc301646b2dbfa653d9a455
|
||||
673
Assets/UnityTestClient/UnityTestClient_Game.cs
Normal file
673
Assets/UnityTestClient/UnityTestClient_Game.cs
Normal file
@@ -0,0 +1,673 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ HERNÍ MECHANIKY - UnityTestClient_Game.cs ║
|
||||
║ ║
|
||||
║ Tento soubor obsahuje veškeré herní mechaniky: ║
|
||||
║ • Pohyb hráče (WASD + myš) ║
|
||||
║ • Interakce (úkoly, reporty, opravy) ║
|
||||
║ • Kill mechanika (pro impostory) ║
|
||||
║ • Sabotáže a jejich opravy ║
|
||||
║ • Emergency meeting ║
|
||||
║ • Hlasování ║
|
||||
║ ║
|
||||
║ OVLÁDÁNÍ: ║
|
||||
║ • WASD - pohyb hráče ║
|
||||
║ • Myš - otáčení kamery (volitelné) ║
|
||||
║ • E - interakce (úkoly, report, oprava) ║
|
||||
║ • Q - kill (pouze impostor) ║
|
||||
║ • Tab - mapa/statistiky ║
|
||||
║ • Escape - menu ║
|
||||
║ ║
|
||||
║ SYSTÉM ÚKOLŮ: ║
|
||||
║ • Všechny úkoly jsou INSTANT - stačí přijít na místo (do 5m) a stisknout E ║
|
||||
║ • Server validuje pozici hráče - nemůžete dokončit úkol na dálku ║
|
||||
║ • Duchové (mrtví crew) mohou dokončovat úkoly - pomáhají týmu vyhrát ║
|
||||
║ • Impostoři NEMOHOU dokončovat úkoly ║
|
||||
║ ║
|
||||
║ SABOTÁŽE: ║
|
||||
║ • CommsBlackout - blokuje reporty a emergency meetings ║
|
||||
║ - 1 opravná stanice, libovolný hráč opraví sám ║
|
||||
║ • CriticalMeltdown - časový limit na opravu! ║
|
||||
║ - 2 stanice musí být opravovány SOUČASNĚ dvěma hráči ║
|
||||
║ - Pokud čas vyprší, impostoři vyhrávají ║
|
||||
║ ║
|
||||
║ POZNÁMKA: ║
|
||||
║ V reálné mobilní hře by se pozice hráče aktualizovala podle GPS (Input.location). ║
|
||||
║ Tento test klient umožňuje simulovat pohyb pomocí WASD pro testování. ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
|
||||
*/
|
||||
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeoSus.Client;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// LOKÁLNÍ TYPY PRO UNITY (kopie z Protocol.cs pro kompatibilitu)
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>
|
||||
/// Reprezentace úkolu hráče
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class PlayerTask
|
||||
{
|
||||
public string Id;
|
||||
public string Name;
|
||||
public string Description;
|
||||
public TaskType Type;
|
||||
public Position Location;
|
||||
public bool IsCompleted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opravná stanice pro sabotáže
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class RepairStation
|
||||
{
|
||||
public string Id;
|
||||
public string Name;
|
||||
public Position Position;
|
||||
public bool IsActive;
|
||||
}
|
||||
|
||||
public partial class UnityTestClient
|
||||
{
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// GAME PROMĚNNÉ
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Pohyb
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Rychlost pohybu hráče (Unity jednotky/s)</summary>
|
||||
protected float moveSpeed = 10f;
|
||||
|
||||
/// <summary>Aktuální pozice hráče v Unity souřadnicích</summary>
|
||||
protected Vector3 currentPlayerPosition;
|
||||
|
||||
/// <summary>Interval odesílání pozice na server (sekundy)</summary>
|
||||
protected float positionUpdateInterval = 0.5f;
|
||||
|
||||
/// <summary>Čas posledního odeslání pozice</summary>
|
||||
protected float lastPositionUpdate;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Opravy
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Právě opravujeme?</summary>
|
||||
protected bool isRepairing = false;
|
||||
|
||||
/// <summary>ID aktivní opravné stanice</summary>
|
||||
protected string activeRepairStation = null;
|
||||
|
||||
/// <summary>Progress opravy (0-1)</summary>
|
||||
protected float repairProgress = 0f;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Sabotáže
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Aktuální sabotáž (null = žádná)</summary>
|
||||
protected SabotageStartedPayload currentSabotage = null;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Kill
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Cooldown killu (sekundy)</summary>
|
||||
protected float killCooldown = 25f;
|
||||
|
||||
/// <summary>Čas posledního killu</summary>
|
||||
protected float lastKillTime = -100f;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Konec hry
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Data o konci hry</summary>
|
||||
protected GameEndedPayload gameEndData = null;
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// POHYB HRÁČE
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Zpracování vstupu hráče.
|
||||
/// Volá se každý frame v Update().
|
||||
///
|
||||
/// POZNÁMKA PRO STUDENTY:
|
||||
/// Input.GetAxis vrací hodnotu -1 až 1 pro plynulý pohyb.
|
||||
/// "Horizontal" = A/D nebo šipky vlevo/vpravo
|
||||
/// "Vertical" = W/S nebo šipky nahoru/dolů
|
||||
/// </summary>
|
||||
protected void HandlePlayerInput()
|
||||
{
|
||||
// Pohyb pouze během hraní
|
||||
if (currentState != AppState.InGame) return;
|
||||
|
||||
// Kontrola, zda jsme naživu - bezpečný přístup
|
||||
if (client?.PlayerPositions != null &&
|
||||
client.PlayerPositions.TryGetValue(clientUuid, out var myInfo))
|
||||
{
|
||||
if (myInfo.State != PlayerState.Alive)
|
||||
{
|
||||
// Duch může létat, ale neposílá pozici
|
||||
HandleGhostMovement();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Kontrola, zda není meeting - ale během arrival fáze se můžeme hýbat
|
||||
var phase = client?.CurrentLobbyState?.Phase;
|
||||
if (phase == GamePhase.Meeting)
|
||||
{
|
||||
// Během arrival fáze (před ArrivalDeadline) se můžeme hýbat
|
||||
if (currentMeeting != null && DateTime.UtcNow < currentMeeting.ArrivalDeadline)
|
||||
{
|
||||
// OK - můžeme se hýbat k meeting pointu
|
||||
}
|
||||
else
|
||||
{
|
||||
return; // Po arrival deadline se nehýbeme
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// WASD POHYB
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
float horizontal = Input.GetAxis("Horizontal"); // A/D
|
||||
float vertical = Input.GetAxis("Vertical"); // W/S
|
||||
|
||||
if (horizontal != 0 || vertical != 0)
|
||||
{
|
||||
// Směr pohybu
|
||||
Vector3 movement = new Vector3(horizontal, 0, vertical).normalized;
|
||||
|
||||
// Aplikace rychlosti a delta time
|
||||
Vector3 newPosition = currentPlayerPosition + movement * moveSpeed * Time.deltaTime;
|
||||
|
||||
// Kontrola hranic herní oblasti
|
||||
if (IsPositionInPlayArea(newPosition))
|
||||
{
|
||||
currentPlayerPosition = newPosition;
|
||||
UpdateCameraPosition();
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ODESÍLÁNÍ POZICE NA SERVER
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
if (Time.time - lastPositionUpdate >= positionUpdateInterval)
|
||||
{
|
||||
SendPositionToServer();
|
||||
lastPositionUpdate = Time.time;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// KLÁVESOVÉ ZKRATKY PRO AKCE
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
// E - Interakce (USE)
|
||||
if (Input.GetKeyDown(KeyCode.E))
|
||||
{
|
||||
PerformPrimaryAction();
|
||||
}
|
||||
|
||||
// Q - Kill (pouze impostor)
|
||||
if (Input.GetKeyDown(KeyCode.Q) && client?.MyRole == PlayerRole.Impostor)
|
||||
{
|
||||
TryKillNearbyPlayer();
|
||||
}
|
||||
|
||||
// R - Emergency meeting
|
||||
if (Input.GetKeyDown(KeyCode.R))
|
||||
{
|
||||
CallEmergencyMeeting();
|
||||
}
|
||||
|
||||
// Escape - Menu
|
||||
if (Input.GetKeyDown(KeyCode.Escape))
|
||||
{
|
||||
// TODO: Toggle pause menu
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pohyb ducha (mrtvý hráč)
|
||||
/// </summary>
|
||||
private void HandleGhostMovement()
|
||||
{
|
||||
float horizontal = Input.GetAxis("Horizontal");
|
||||
float vertical = Input.GetAxis("Vertical");
|
||||
|
||||
if (horizontal != 0 || vertical != 0)
|
||||
{
|
||||
Vector3 movement = new Vector3(horizontal, 0, vertical).normalized;
|
||||
currentPlayerPosition += movement * moveSpeed * 1.5f * Time.deltaTime; // Duch je rychlejší
|
||||
UpdateCameraPosition();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kontrola, zda je pozice v hrací oblasti
|
||||
/// </summary>
|
||||
private bool IsPositionInPlayArea(Vector3 position)
|
||||
{
|
||||
// Vzdálenost od středu
|
||||
float distance = Vector3.Distance(position, Vector3.zero);
|
||||
return distance <= (float)mapRadius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aktualizace pozice kamery podle hráče
|
||||
/// </summary>
|
||||
private void UpdateCameraPosition()
|
||||
{
|
||||
if (playerCamera == null) return;
|
||||
|
||||
// Top-down kamera následuje hráče
|
||||
Vector3 cameraPos = currentPlayerPosition;
|
||||
cameraPos.y = cameraHeight;
|
||||
playerCamera.transform.position = cameraPos;
|
||||
|
||||
// Aktualizace lokálního hráče vizuálu
|
||||
UpdateLocalPlayerVisual();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inicializace pozice hráče při startu hry
|
||||
/// </summary>
|
||||
protected void InitializePlayerPosition()
|
||||
{
|
||||
// Nastavíme hráče na střed mapy
|
||||
currentPlayerPosition = Vector3.zero;
|
||||
UpdateCameraPosition();
|
||||
Debug.Log("[GeoSus] Pozice hráče inicializována na střed mapy");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Odeslání pozice hráče na server
|
||||
/// </summary>
|
||||
private void SendPositionToServer()
|
||||
{
|
||||
if (client == null || !client.IsConnected) return;
|
||||
|
||||
// Převod na GPS
|
||||
Position gpsPosition = UnityToGPS(currentPlayerPosition);
|
||||
|
||||
// Odeslání
|
||||
client.UpdatePosition(gpsPosition);
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// INTERAKCE
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Provede primární akci (USE/REPORT/REPAIR)
|
||||
/// </summary>
|
||||
protected void PerformPrimaryAction()
|
||||
{
|
||||
if (client == null) return;
|
||||
|
||||
// Kontrola stavu hráče - bezpečný přístup
|
||||
if (client.PlayerPositions != null && client.PlayerPositions.TryGetValue(clientUuid, out var myState))
|
||||
{
|
||||
if (myState.State != PlayerState.Alive)
|
||||
{
|
||||
ShowNotification("Jsi mrtvý!", Color.gray, 2f, "💀");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// PRIORITA 1: Report těla
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
var body = client.FindNearbyBody(5.0);
|
||||
if (body != null)
|
||||
{
|
||||
// Použijeme přímo BodyId property
|
||||
ReportBody(body.BodyId);
|
||||
return;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// PRIORITA 2: Oprava stanice
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
if (currentSabotage != null)
|
||||
{
|
||||
var station = FindNearbyRepairStation(5.0);
|
||||
if (station != null)
|
||||
{
|
||||
if (!isRepairing)
|
||||
{
|
||||
StartRepair(station.StationId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// PRIORITA 3: Úkol (pouze crew)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
if (client.MyRole == PlayerRole.Crew)
|
||||
{
|
||||
var task = client.FindNearbyTask(5.0);
|
||||
if (task != null)
|
||||
{
|
||||
TryCompleteTask(task);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ShowNotification("Nic v dosahu!", Color.yellow, "❓");
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// ÚKOLY
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Pokus o dokončení úkolu - pošle CompleteTask na server.
|
||||
/// Server ověří pozici a označí jako dokončený.
|
||||
/// </summary>
|
||||
private void TryCompleteTask(object taskObj)
|
||||
{
|
||||
if (client == null) return;
|
||||
|
||||
// Dynamicky získáme vlastnosti úkolu přes reflexi
|
||||
var taskType = taskObj.GetType();
|
||||
string taskId = taskType.GetField("TaskId")?.GetValue(taskObj) as string ??
|
||||
taskType.GetProperty("TaskId")?.GetValue(taskObj) as string ?? "unknown";
|
||||
string taskName = taskType.GetField("Name")?.GetValue(taskObj) as string ??
|
||||
taskType.GetProperty("Name")?.GetValue(taskObj) as string ?? "Úkol";
|
||||
|
||||
// Pošleme CompleteTask na server
|
||||
client.CompleteTask(taskId);
|
||||
|
||||
ShowNotification($"Provádím: {taskName}...", Color.cyan, "📋");
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// REPORT TĚLA
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Nahlášení těla
|
||||
/// </summary>
|
||||
/// <param name="bodyId">ID těla</param>
|
||||
protected void ReportBody(string bodyId)
|
||||
{
|
||||
if (client == null) return;
|
||||
|
||||
client.ReportBody(bodyId);
|
||||
|
||||
ShowNotification("Tělo nahlášeno!", Color.red, "🚨");
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// KILL
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Pokus o zabití blízkého hráče
|
||||
/// </summary>
|
||||
private void TryKillNearbyPlayer()
|
||||
{
|
||||
if (client?.MyRole != PlayerRole.Impostor) return;
|
||||
|
||||
// Kontrola cooldownu
|
||||
if (Time.time - lastKillTime < killCooldown)
|
||||
{
|
||||
float remaining = killCooldown - (Time.time - lastKillTime);
|
||||
ShowNotification($"Cooldown: {remaining:F0}s", Color.yellow, "⏳");
|
||||
return;
|
||||
}
|
||||
|
||||
// Najdi blízkého hráče
|
||||
string targetId = client.FindNearbyPlayer(5.0, true);
|
||||
|
||||
if (targetId != null)
|
||||
{
|
||||
AttemptKill(targetId);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowNotification("Nikdo v dosahu!", Color.yellow, "🔪");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pokus o zabití konkrétního hráče
|
||||
/// </summary>
|
||||
/// <param name="targetId">UUID cíle</param>
|
||||
protected void AttemptKill(string targetId)
|
||||
{
|
||||
if (client == null) return;
|
||||
|
||||
client.Kill(targetId);
|
||||
lastKillTime = Time.time;
|
||||
|
||||
string targetName = GetPlayerName(targetId);
|
||||
ShowNotification($"Útočíš na {targetName}!", Color.red, "🔪");
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// SABOTÁŽE
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Spuštění sabotáže
|
||||
/// </summary>
|
||||
/// <param name="type">Typ sabotáže</param>
|
||||
protected void StartSabotage(SabotageType type)
|
||||
{
|
||||
if (client?.MyRole != PlayerRole.Impostor) return;
|
||||
if (currentSabotage != null) return; // Již běží sabotáž
|
||||
|
||||
client.Send(new StartSabotage { SabotageType = type });
|
||||
|
||||
string sabName = type == SabotageType.CommsBlackout ? "Comms Blackout" : "Critical Meltdown";
|
||||
ShowNotification($"Sabotáž: {sabName}!", new Color(1f, 0.5f, 0f), "⚠");
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// OPRAVY
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Oprava stanice - INSTANT
|
||||
/// Server opraví stanici okamžitě při přijetí ActivateRepairStation.
|
||||
/// Budoucí pokročilý klient může simulovat progress bar a poslat request až po uplynutí času.
|
||||
/// </summary>
|
||||
/// <param name="stationId">ID opravné stanice</param>
|
||||
private void StartRepair(string stationId)
|
||||
{
|
||||
if (currentSabotage == null) return;
|
||||
|
||||
// Pošleme serveru - ten opraví INSTANT
|
||||
client.Send(new ActivateRepairStation { StationId = stationId });
|
||||
|
||||
ShowNotification("Stanice opravena!", Color.green, "✅");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aktualizace průběhu opravy - NEPOUŽÍVÁ SE (oprava je instant)
|
||||
/// Ponecháno pro budoucí implementaci s progress barem.
|
||||
/// </summary>
|
||||
protected void UpdateRepairProgress()
|
||||
{
|
||||
// Oprava je nyní instant - tato metoda není potřeba
|
||||
// Budoucí klient může implementovat lokální progress bar:
|
||||
// if (!isRepairing) return;
|
||||
// repairProgress += Time.deltaTime / 3f; // 3 sekundy
|
||||
// if (repairProgress >= 1f) {
|
||||
// client.Send(new ActivateRepairStation { StationId = activeRepairStation });
|
||||
// isRepairing = false;
|
||||
// }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ukončení opravy (úspěšné) - NEPOUŽÍVÁ SE (oprava je instant)
|
||||
/// </summary>
|
||||
private void StopRepair()
|
||||
{
|
||||
// Oprava je instant - tato metoda není potřeba
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Zrušení opravy - NEPOUŽÍVÁ SE (oprava je instant)
|
||||
/// </summary>
|
||||
private void CancelRepair()
|
||||
{
|
||||
// Oprava je instant - tato metoda není potřeba
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// EMERGENCY MEETING
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Svolání emergency meetingu
|
||||
/// </summary>
|
||||
protected void CallEmergencyMeeting()
|
||||
{
|
||||
if (client == null) return;
|
||||
|
||||
// Kontrola sabotáže - nelze volat během comms blackout
|
||||
if (currentSabotage?.Type == SabotageType.CommsBlackout)
|
||||
{
|
||||
ShowNotification("Komunikace odpojeny!", Color.red, "📡");
|
||||
return;
|
||||
}
|
||||
|
||||
client.CallEmergencyMeeting();
|
||||
|
||||
ShowNotification("Emergency Meeting!", Color.red, "🔔");
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// HLASOVÁNÍ
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Odeslání hlasu
|
||||
/// </summary>
|
||||
/// <param name="targetId">UUID hráče nebo null pro skip</param>
|
||||
protected void CastVote(string targetId)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
Debug.LogError("[GeoSus] CastVote: client je null!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canVote)
|
||||
{
|
||||
Debug.LogWarning("[GeoSus] CastVote: canVote je false!");
|
||||
return;
|
||||
}
|
||||
|
||||
string voteText = targetId == null ? "SKIP" : GetPlayerName(targetId);
|
||||
Debug.Log($"[GeoSus] Hlasování: {voteText} (targetId: {targetId ?? "null"})");
|
||||
|
||||
client.Vote(targetId);
|
||||
myVote = targetId ?? "skip";
|
||||
canVote = false;
|
||||
|
||||
ShowNotification($"Hlasoval jsi: {voteText}", Color.cyan, 2f, "🗳");
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// POMOCNÉ METODY
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Získání jména hráče podle UUID
|
||||
/// </summary>
|
||||
protected string GetPlayerName(string playerId)
|
||||
{
|
||||
if (client?.CurrentLobbyState?.Players == null) return playerId;
|
||||
|
||||
foreach (var player in client.CurrentLobbyState.Players)
|
||||
{
|
||||
if (player.ClientUuid == playerId)
|
||||
{
|
||||
return player.DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
return playerId.Substring(0, 8) + "...";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inicializace herního stavu při startu hry
|
||||
/// </summary>
|
||||
protected void InitializeGameState()
|
||||
{
|
||||
// Reset pozice na střed
|
||||
currentPlayerPosition = Vector3.zero;
|
||||
|
||||
// Reset úkolů
|
||||
totalTasksCompleted = 0;
|
||||
totalTasksRequired = client?.MyTasks?.Count ?? 0;
|
||||
myCompletedTaskIds.Clear();
|
||||
|
||||
// Reset oprav
|
||||
isRepairing = false;
|
||||
activeRepairStation = null;
|
||||
repairProgress = 0f;
|
||||
|
||||
// Reset kill cooldownu
|
||||
lastKillTime = Time.time; // Cooldown na začátku
|
||||
|
||||
// Reset sabotáže
|
||||
currentSabotage = null;
|
||||
|
||||
// Reset meetingu
|
||||
currentMeeting = null;
|
||||
meetingVotes.Clear();
|
||||
myVote = null;
|
||||
canVote = false;
|
||||
|
||||
// Reset konce hry
|
||||
gameEndData = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Game loop - aktualizace herní logiky.
|
||||
/// Volá se v Update().
|
||||
/// </summary>
|
||||
protected void UpdateGameLogic()
|
||||
{
|
||||
if (currentState != AppState.InGame) return;
|
||||
|
||||
// Aktualizace opravy
|
||||
UpdateRepairProgress();
|
||||
|
||||
// Aktualizace mapových objektů
|
||||
UpdateMapObjects();
|
||||
}
|
||||
}
|
||||
2
Assets/UnityTestClient/UnityTestClient_Game.cs.meta
Normal file
2
Assets/UnityTestClient/UnityTestClient_Game.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 829e2eaa53b86d347912031dba7711da
|
||||
1532
Assets/UnityTestClient/UnityTestClient_Map.cs
Normal file
1532
Assets/UnityTestClient/UnityTestClient_Map.cs
Normal file
File diff suppressed because it is too large
Load Diff
2
Assets/UnityTestClient/UnityTestClient_Map.cs.meta
Normal file
2
Assets/UnityTestClient/UnityTestClient_Map.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d9f27ae239808c44b27975df7eefd38
|
||||
1196
Assets/UnityTestClient/UnityTestClient_Network.cs
Normal file
1196
Assets/UnityTestClient/UnityTestClient_Network.cs
Normal file
File diff suppressed because it is too large
Load Diff
2
Assets/UnityTestClient/UnityTestClient_Network.cs.meta
Normal file
2
Assets/UnityTestClient/UnityTestClient_Network.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cdc71cfbe967d754189f3382fee78c6c
|
||||
589
Assets/UnityTestClient/UnityTestClient_Stats.cs
Normal file
589
Assets/UnityTestClient/UnityTestClient_Stats.cs
Normal file
@@ -0,0 +1,589 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ STATISTIKY A HTTP API - UnityTestClient_Stats.cs ║
|
||||
║ ║
|
||||
║ Tento soubor obsahuje komunikaci s HTTP Stats API serveru: ║
|
||||
║ • Načítání statistik hráče ║
|
||||
║ • Načítání leaderboardu ║
|
||||
║ • Health check serveru ║
|
||||
║ ║
|
||||
║ API ENDPOINTY (port 8088): ║
|
||||
║ • GET /stats/{playerId} - statistiky konkrétního hráče ║
|
||||
║ • GET /leaderboard?limit=N - top N hráčů podle win rate ║
|
||||
║ • GET /health - stav serveru (aktivní lobby, hráči) ║
|
||||
║ ║
|
||||
║ STATISTIKY ZAHRNUJÍ: ║
|
||||
║ • Počet her, výher, proher ║
|
||||
║ • Win rate celkově, jako impostor, jako crew ║
|
||||
║ • Počet zabití, smrtí, K/D ratio ║
|
||||
║ • Dokončené úkoly ║
|
||||
║ • Přesnost hlasování (správně odhalení impostoři) ║
|
||||
║ ║
|
||||
║ POZNÁMKA PRO STUDENTY: ║
|
||||
║ Unity používá UnityWebRequest pro HTTP komunikaci. ║
|
||||
║ Všechny requesty musí běžet jako coroutiny (StartCoroutine). ║
|
||||
║ JSON parsing: JsonUtility (jednodušší) nebo Newtonsoft.Json (flexibilnější). ║
|
||||
║ ║
|
||||
║ Pro vlastní hru můžete rozšířit statistiky o: ║
|
||||
║ • Achievements/Badges ║
|
||||
║ • Sezónní ranky ║
|
||||
║ • Historii her ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
|
||||
*/
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public partial class UnityTestClient
|
||||
{
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// STATS PROMĚNNÉ
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>Port pro Stats HTTP API</summary>
|
||||
protected int statsApiPort = 8088;
|
||||
|
||||
/// <summary>URL pro Stats API (automaticky používá HTTPS pokud je povoleno)</summary>
|
||||
protected string StatsApiUrl => useHttps
|
||||
? $"https://{serverHost}"
|
||||
: $"http://{serverHost}:{statsApiPort}";
|
||||
|
||||
/// <summary>Načtené statistiky hráče</summary>
|
||||
protected PlayerStatistics playerStats = null;
|
||||
|
||||
/// <summary>Načtený leaderboard</summary>
|
||||
protected List<LeaderboardEntry> leaderboard = null;
|
||||
|
||||
/// <summary>Stav zdraví serveru</summary>
|
||||
protected ServerHealthStatus healthStatus = null;
|
||||
|
||||
/// <summary>Probíhá načítání statistik?</summary>
|
||||
protected bool isLoadingStats = false;
|
||||
|
||||
/// <summary>Probíhá načítání leaderboardu?</summary>
|
||||
protected bool isLoadingLeaderboard = false;
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// DATOVÉ STRUKTURY
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Statistiky hráče.
|
||||
/// Mapuje se z JSON response z /stats/{playerId}.
|
||||
/// POZOR: Názvy polí musí přesně odpovídat JSON z serveru!
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class PlayerStatistics
|
||||
{
|
||||
/// <summary>UUID hráče</summary>
|
||||
public string clientUuid;
|
||||
|
||||
/// <summary>Zobrazované jméno</summary>
|
||||
public string displayName;
|
||||
|
||||
/// <summary>Celkový počet odehraných her</summary>
|
||||
public int totalGames;
|
||||
|
||||
/// <summary>Počet her jako crew</summary>
|
||||
public int gamesAsCrew;
|
||||
|
||||
/// <summary>Počet her jako impostor</summary>
|
||||
public int gamesAsImpostor;
|
||||
|
||||
/// <summary>Počet výher jako crew</summary>
|
||||
public int crewWins;
|
||||
|
||||
/// <summary>Počet výher jako impostor</summary>
|
||||
public int impostorWins;
|
||||
|
||||
/// <summary>Win rate jako crew (0-1)</summary>
|
||||
public float crewWinRate;
|
||||
|
||||
/// <summary>Win rate jako impostor (0-1)</summary>
|
||||
public float impostorWinRate;
|
||||
|
||||
/// <summary>Počet zabití (jako impostor)</summary>
|
||||
public int totalKills;
|
||||
|
||||
/// <summary>Počet smrtí</summary>
|
||||
public int totalDeaths;
|
||||
|
||||
/// <summary>Kill/Death ratio</summary>
|
||||
public float killDeathRatio;
|
||||
|
||||
/// <summary>Počet dokončených úkolů</summary>
|
||||
public int tasksCompleted;
|
||||
|
||||
/// <summary>Průměrný počet tasků za hru</summary>
|
||||
public float averageTasksPerGame;
|
||||
|
||||
/// <summary>Počet nahlášených těl</summary>
|
||||
public int bodiesReported;
|
||||
|
||||
/// <summary>Počet svolaných emergency meetingů</summary>
|
||||
public int emergencyMeetingsCalled;
|
||||
|
||||
/// <summary>Kolikrát byl vyhozen hlasováním</summary>
|
||||
public int timesVotedOut;
|
||||
|
||||
/// <summary>Počet úspěšných hlasů (správně identifikovaný impostor)</summary>
|
||||
public int successfulVotes;
|
||||
|
||||
/// <summary>Celkový čas hraní v sekundách</summary>
|
||||
public long totalPlaytimeSeconds;
|
||||
|
||||
/// <summary>Počet cheat incidentů</summary>
|
||||
public int cheatIncidents;
|
||||
|
||||
/// <summary>Kdy byl naposledy viděn</summary>
|
||||
public string lastSeen;
|
||||
|
||||
// Pomocné vlastnosti pro zobrazení v UI
|
||||
public int GamesWon => crewWins + impostorWins;
|
||||
public float WinRate => totalGames > 0 ? (float)GamesWon / totalGames : 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Položka leaderboardu.
|
||||
/// POZOR: Názvy polí musí přesně odpovídat JSON z serveru!
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class LeaderboardEntry
|
||||
{
|
||||
/// <summary>UUID hráče</summary>
|
||||
public string clientUuid;
|
||||
|
||||
/// <summary>Zobrazované jméno</summary>
|
||||
public string displayName;
|
||||
|
||||
/// <summary>Celkový počet her</summary>
|
||||
public int totalGames;
|
||||
|
||||
/// <summary>Výhry jako crew</summary>
|
||||
public int crewWins;
|
||||
|
||||
/// <summary>Výhry jako impostor</summary>
|
||||
public int impostorWins;
|
||||
|
||||
/// <summary>Počet zabití</summary>
|
||||
public int totalKills;
|
||||
|
||||
/// <summary>Počet dokončených tasků</summary>
|
||||
public int tasksCompleted;
|
||||
|
||||
// Pomocné vlastnosti
|
||||
public int TotalWins => crewWins + impostorWins;
|
||||
public float WinRate => totalGames > 0 ? (float)TotalWins / totalGames : 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper pro leaderboard response (JSON array).
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class LeaderboardResponse
|
||||
{
|
||||
public List<LeaderboardEntry> entries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stav serveru.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class ServerHealthStatus
|
||||
{
|
||||
/// <summary>Stav: "healthy" nebo "unhealthy"</summary>
|
||||
public string status;
|
||||
|
||||
/// <summary>Verze serveru</summary>
|
||||
public string version;
|
||||
|
||||
/// <summary>Uptime v sekundách</summary>
|
||||
public long uptimeSeconds;
|
||||
|
||||
/// <summary>Počet aktivních lobby</summary>
|
||||
public int activeLobbies;
|
||||
|
||||
/// <summary>Počet připojených hráčů</summary>
|
||||
public int connectedPlayers;
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// NAČÍTÁNÍ STATISTIK
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Spustí načítání statistik hráče.
|
||||
/// Výsledek bude v playerStats.
|
||||
/// </summary>
|
||||
protected void FetchPlayerStats()
|
||||
{
|
||||
Debug.Log($"[Stats] FetchPlayerStats called. isLoadingStats={isLoadingStats}, clientUuid={clientUuid}");
|
||||
|
||||
if (isLoadingStats)
|
||||
{
|
||||
Debug.Log("[Stats] Already loading, skipping");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(clientUuid))
|
||||
{
|
||||
Debug.Log("[Stats] clientUuid is empty, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[Stats] Starting coroutine for player {clientUuid}");
|
||||
StartCoroutine(FetchPlayerStatsCoroutine(clientUuid));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine pro načtení statistik hráče.
|
||||
///
|
||||
/// POZNÁMKA PRO STUDENTY:
|
||||
/// Coroutiny v Unity umožňují asynchronní operace.
|
||||
/// yield return čeká na dokončení operace před pokračováním.
|
||||
/// UnityWebRequest.Get vytváří HTTP GET request.
|
||||
/// </summary>
|
||||
/// <param name="playerId">UUID hráče</param>
|
||||
private IEnumerator FetchPlayerStatsCoroutine(string playerId)
|
||||
{
|
||||
isLoadingStats = true;
|
||||
|
||||
string url = $"{StatsApiUrl}/stats/{playerId}";
|
||||
Debug.Log($"[Stats] Fetching from: {url}");
|
||||
|
||||
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
||||
{
|
||||
// Nastavení timeoutu
|
||||
request.timeout = 10;
|
||||
|
||||
// Odeslání requestu a čekání na odpověď
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
Debug.Log($"[Stats] Request completed. Result: {request.result}, Code: {request.responseCode}");
|
||||
|
||||
// Kontrola chyb
|
||||
if (request.result == UnityWebRequest.Result.Success)
|
||||
{
|
||||
// Parsování JSON
|
||||
try
|
||||
{
|
||||
string json = request.downloadHandler.text;
|
||||
playerStats = JsonUtility.FromJson<PlayerStatistics>(json);
|
||||
|
||||
Debug.Log($"[Stats] Loaded stats for {playerStats.displayName}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[Stats] Failed to parse stats: {e.Message}");
|
||||
playerStats = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Chyba při requestu
|
||||
Debug.LogWarning($"[Stats] Failed to fetch stats: {request.error}");
|
||||
|
||||
// Pokud hráč neexistuje (404), vytvoříme prázdné statistiky
|
||||
if (request.responseCode == 404)
|
||||
{
|
||||
playerStats = new PlayerStatistics
|
||||
{
|
||||
clientUuid = playerId,
|
||||
displayName = displayName,
|
||||
totalGames = 0,
|
||||
gamesAsCrew = 0,
|
||||
gamesAsImpostor = 0,
|
||||
crewWins = 0,
|
||||
impostorWins = 0,
|
||||
crewWinRate = 0,
|
||||
impostorWinRate = 0,
|
||||
totalKills = 0,
|
||||
totalDeaths = 0,
|
||||
killDeathRatio = 0,
|
||||
tasksCompleted = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isLoadingStats = false;
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// LEADERBOARD
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Spustí načítání leaderboardu.
|
||||
/// </summary>
|
||||
/// <param name="limit">Počet hráčů k načtení (max 100)</param>
|
||||
protected void FetchLeaderboard(int limit = 10)
|
||||
{
|
||||
if (isLoadingLeaderboard) return;
|
||||
|
||||
StartCoroutine(FetchLeaderboardCoroutine(limit));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine pro načtení leaderboardu.
|
||||
/// </summary>
|
||||
private IEnumerator FetchLeaderboardCoroutine(int limit)
|
||||
{
|
||||
isLoadingLeaderboard = true;
|
||||
|
||||
string url = $"{StatsApiUrl}/leaderboard?limit={limit}";
|
||||
|
||||
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
||||
{
|
||||
request.timeout = 10;
|
||||
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
if (request.result == UnityWebRequest.Result.Success)
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = request.downloadHandler.text;
|
||||
|
||||
// JsonUtility nemá přímou podporu pro arrays, potřebujeme wrapper
|
||||
// nebo použijeme jednoduchý workaround
|
||||
string wrappedJson = "{\"entries\":" + json + "}";
|
||||
LeaderboardResponse response = JsonUtility.FromJson<LeaderboardResponse>(wrappedJson);
|
||||
leaderboard = response.entries;
|
||||
|
||||
Debug.Log($"[Stats] Loaded leaderboard with {leaderboard.Count} entries");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[Stats] Failed to parse leaderboard: {e.Message}");
|
||||
leaderboard = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[Stats] Failed to fetch leaderboard: {request.error}");
|
||||
}
|
||||
}
|
||||
|
||||
isLoadingLeaderboard = false;
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// HEALTH CHECK
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Spustí health check serveru.
|
||||
/// </summary>
|
||||
protected void CheckServerHealth()
|
||||
{
|
||||
StartCoroutine(CheckServerHealthCoroutine());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine pro health check.
|
||||
/// </summary>
|
||||
private IEnumerator CheckServerHealthCoroutine()
|
||||
{
|
||||
string url = $"{StatsApiUrl}/health";
|
||||
|
||||
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
||||
{
|
||||
request.timeout = 5;
|
||||
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
if (request.result == UnityWebRequest.Result.Success)
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = request.downloadHandler.text;
|
||||
healthStatus = JsonUtility.FromJson<ServerHealthStatus>(json);
|
||||
|
||||
Debug.Log($"[Stats] Server health: {healthStatus.status}, " +
|
||||
$"uptime: {healthStatus.uptimeSeconds}s, " +
|
||||
$"lobbies: {healthStatus.activeLobbies}, " +
|
||||
$"players: {healthStatus.connectedPlayers}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[Stats] Failed to parse health: {e.Message}");
|
||||
healthStatus = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[Stats] Health check failed: {request.error}");
|
||||
healthStatus = new ServerHealthStatus
|
||||
{
|
||||
status = "unreachable",
|
||||
version = "unknown"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// UI PRO LEADERBOARD
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Vykreslení leaderboard panelu.
|
||||
/// Volá se z DrawStatsTab().
|
||||
/// </summary>
|
||||
protected void DrawLeaderboardPanel()
|
||||
{
|
||||
if (leaderboard == null)
|
||||
{
|
||||
GUILayout.Label("Načítám leaderboard...", labelStyle);
|
||||
|
||||
if (GUILayout.Button("Načíst", buttonStyle))
|
||||
{
|
||||
FetchLeaderboard(10);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.Label("TOP HRÁČI", subtitleStyle);
|
||||
GUILayout.Space(5);
|
||||
|
||||
// Hlavička
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("#", GUILayout.Width(30));
|
||||
GUILayout.Label("Jméno", GUILayout.Width(150));
|
||||
GUILayout.Label("Skóre", GUILayout.Width(80));
|
||||
GUILayout.Label("Win%", GUILayout.Width(60));
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// Položky
|
||||
int rank = 1;
|
||||
foreach (var entry in leaderboard)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
// Speciální barvy pro top 3
|
||||
if (rank == 1) GUI.color = Color.yellow;
|
||||
else if (rank == 2) GUI.color = new Color(0.8f, 0.8f, 0.8f);
|
||||
else if (rank == 3) GUI.color = new Color(0.8f, 0.5f, 0.2f);
|
||||
else GUI.color = Color.white;
|
||||
|
||||
GUILayout.Label($"{rank}.", GUILayout.Width(30));
|
||||
GUILayout.Label(entry.displayName, GUILayout.Width(150));
|
||||
GUILayout.Label(entry.TotalWins.ToString(), GUILayout.Width(80));
|
||||
GUILayout.Label($"{entry.WinRate * 100:F0}%", GUILayout.Width(60));
|
||||
|
||||
GUI.color = Color.white;
|
||||
GUILayout.EndHorizontal();
|
||||
rank++;
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
if (GUILayout.Button("Obnovit", buttonStyle))
|
||||
{
|
||||
leaderboard = null;
|
||||
FetchLeaderboard(10);
|
||||
}
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// UI PRO SERVER HEALTH
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Vykreslení panelu stavu serveru.
|
||||
/// </summary>
|
||||
protected void DrawServerHealthPanel()
|
||||
{
|
||||
GUILayout.BeginVertical(boxStyle);
|
||||
|
||||
GUILayout.Label("STAV SERVERU", subtitleStyle);
|
||||
|
||||
if (healthStatus == null)
|
||||
{
|
||||
GUILayout.Label("Neznámý", labelStyle);
|
||||
|
||||
if (GUILayout.Button("Zkontrolovat", buttonStyle))
|
||||
{
|
||||
CheckServerHealth();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Status
|
||||
if (healthStatus.status == "healthy")
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
GUILayout.Label("● Online", labelStyle);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.red;
|
||||
GUILayout.Label("● Offline", labelStyle);
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
|
||||
// Detaily
|
||||
GUILayout.Label($"Verze: {healthStatus.version}", labelStyle);
|
||||
|
||||
// Uptime
|
||||
TimeSpan uptime = TimeSpan.FromSeconds(healthStatus.uptimeSeconds);
|
||||
GUILayout.Label($"Uptime: {uptime.Days}d {uptime.Hours}h {uptime.Minutes}m", labelStyle);
|
||||
|
||||
// Aktivita
|
||||
GUILayout.Label($"Lobby: {healthStatus.activeLobbies}", labelStyle);
|
||||
GUILayout.Label($"Hráči: {healthStatus.connectedPlayers}", labelStyle);
|
||||
|
||||
GUILayout.Space(5);
|
||||
|
||||
if (GUILayout.Button("Obnovit", buttonStyle))
|
||||
{
|
||||
healthStatus = null;
|
||||
CheckServerHealth();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// AUTOMATICKÉ NAČÍTÁNÍ
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Interval automatického obnovení statistik (sekundy)
|
||||
/// </summary>
|
||||
protected float statsRefreshInterval = 60f;
|
||||
|
||||
/// <summary>Čas posledního obnovení</summary>
|
||||
protected float lastStatsRefresh = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Automatické načítání statistik.
|
||||
/// Volá se v Update().
|
||||
/// </summary>
|
||||
protected void UpdateStatsAutoRefresh()
|
||||
{
|
||||
if (currentState != AppState.MainMenu) return;
|
||||
if (string.IsNullOrEmpty(clientUuid)) return;
|
||||
|
||||
if (Time.time - lastStatsRefresh >= statsRefreshInterval)
|
||||
{
|
||||
FetchPlayerStats();
|
||||
CheckServerHealth();
|
||||
lastStatsRefresh = Time.time;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/UnityTestClient/UnityTestClient_Stats.cs.meta
Normal file
2
Assets/UnityTestClient/UnityTestClient_Stats.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b7bb01435f46b842b79fe4836d1b44e
|
||||
1642
Assets/UnityTestClient/UnityTestClient_UI.cs
Normal file
1642
Assets/UnityTestClient/UnityTestClient_UI.cs
Normal file
File diff suppressed because it is too large
Load Diff
2
Assets/UnityTestClient/UnityTestClient_UI.cs.meta
Normal file
2
Assets/UnityTestClient/UnityTestClient_UI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebca6f90ee582ea448b7a028626d8636
|
||||
Reference in New Issue
Block a user