meta patch
This commit is contained in:
@@ -132,7 +132,20 @@ public class GameClient : IDisposable
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Disconnect(string reason = "User disconnected")
|
||||
/// <summary>
|
||||
/// Tears down the socket and crypto session. When `transient` is true
|
||||
/// (network drop, decrypt-failure cascade, anything we expect to retry),
|
||||
/// the lobby/role/task/state caches are preserved so the post-reconnect
|
||||
/// flow can re-associate via Reconnect(LobbyId). Default false matches
|
||||
/// pre-P9 behavior (full state wipe) for explicit user disconnects.
|
||||
///
|
||||
/// Critical for the P9 reconnect bug: previously every Disconnect path
|
||||
/// nuked LobbyId, so by the time GameManager_Network's reconnect coroutine
|
||||
/// fired, the client had no idea which lobby it had been in - the
|
||||
/// post-handshake Reconnect call had nothing to send and the server
|
||||
/// answered the next vote/action with NOT_IN_LOBBY.
|
||||
/// </summary>
|
||||
public void Disconnect(string reason = "User disconnected", bool transient = false)
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_tcpClient?.Close();
|
||||
@@ -140,15 +153,22 @@ public class GameClient : IDisposable
|
||||
_stream = null;
|
||||
_encryption?.Dispose();
|
||||
_encryption = null;
|
||||
|
||||
LobbyId = null;
|
||||
JoinCode = null;
|
||||
CurrentLobbyState = null;
|
||||
MyRole = null;
|
||||
MyTasks.Clear();
|
||||
PlayerPositions.Clear();
|
||||
Bodies.Clear();
|
||||
|
||||
|
||||
if (!transient)
|
||||
{
|
||||
LobbyId = null;
|
||||
JoinCode = null;
|
||||
CurrentLobbyState = null;
|
||||
MyRole = null;
|
||||
MyTasks.Clear();
|
||||
PlayerPositions.Clear();
|
||||
Bodies.Clear();
|
||||
}
|
||||
// PlayerPositions are stale anyway after a drop, but we keep them so
|
||||
// the UI doesn't blink avatars off-map mid-meeting; the next position
|
||||
// broadcast overwrites them. LastEventId is intentionally preserved
|
||||
// so the Reconnect message can ask the server for missed events.
|
||||
|
||||
Dispatcher.Post(() => OnDisconnected?.Invoke(reason));
|
||||
}
|
||||
|
||||
@@ -236,7 +256,8 @@ public class GameClient : IDisposable
|
||||
decryptFailures++;
|
||||
if (decryptFailures >= 3)
|
||||
{
|
||||
Disconnect("Too many decryption failures");
|
||||
// Transient: keep LobbyId for the reconnect coroutine.
|
||||
Disconnect("Too many decryption failures", transient: true);
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
@@ -253,7 +274,9 @@ public class GameClient : IDisposable
|
||||
}
|
||||
catch (Exception ex) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
Disconnect($"Connection error: {ex.Message}");
|
||||
// Transient: TCP RST / read failure is exactly what reconnect was
|
||||
// designed for. Keep LobbyId so post-reconnect flow can re-attach.
|
||||
Disconnect($"Connection error: {ex.Message}", transient: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +316,35 @@ public class GameClient : IDisposable
|
||||
{
|
||||
LobbyId = r.LobbyId;
|
||||
JoinCode = r.JoinCode;
|
||||
CurrentLobbyState = r.LobbyState;
|
||||
// Ensure we always have a valid lobby state with the creator as owner
|
||||
if (r.LobbyState != null)
|
||||
{
|
||||
CurrentLobbyState = r.LobbyState;
|
||||
if (string.IsNullOrEmpty(CurrentLobbyState.OwnerId))
|
||||
CurrentLobbyState.OwnerId = ClientUuid;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentLobbyState = new LobbyState
|
||||
{
|
||||
LobbyId = r.LobbyId ?? "",
|
||||
JoinCode = r.JoinCode ?? "",
|
||||
OwnerId = ClientUuid
|
||||
};
|
||||
}
|
||||
// Make sure creator appears in the player list
|
||||
if (CurrentLobbyState.Players == null)
|
||||
CurrentLobbyState.Players = new System.Collections.Generic.List<PlayerInfo>();
|
||||
if (!CurrentLobbyState.Players.Any(p => p.ClientUuid == ClientUuid))
|
||||
{
|
||||
CurrentLobbyState.Players.Insert(0, new PlayerInfo
|
||||
{
|
||||
ClientUuid = ClientUuid,
|
||||
DisplayName = DisplayName,
|
||||
IsOwner = true,
|
||||
State = PlayerState.Alive
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -303,6 +354,22 @@ public class GameClient : IDisposable
|
||||
LobbyId = r.LobbyId;
|
||||
CurrentLobbyState = r.LobbyState;
|
||||
JoinCode = r.LobbyState?.JoinCode;
|
||||
// Ensure self is in the player list
|
||||
if (CurrentLobbyState != null)
|
||||
{
|
||||
if (CurrentLobbyState.Players == null)
|
||||
CurrentLobbyState.Players = new System.Collections.Generic.List<PlayerInfo>();
|
||||
if (!CurrentLobbyState.Players.Any(p => p.ClientUuid == ClientUuid))
|
||||
{
|
||||
CurrentLobbyState.Players.Add(new PlayerInfo
|
||||
{
|
||||
ClientUuid = ClientUuid,
|
||||
DisplayName = DisplayName,
|
||||
IsOwner = CurrentLobbyState.OwnerId == ClientUuid,
|
||||
State = PlayerState.Alive
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -344,7 +411,6 @@ public class GameClient : IDisposable
|
||||
var joinedPayload = evt.GetPayload<PlayerJoinedPayload>();
|
||||
if (joinedPayload != null && CurrentLobbyState?.Players != null)
|
||||
{
|
||||
// Check if player already exists
|
||||
bool exists = CurrentLobbyState.Players.Any(p => p.ClientUuid == joinedPayload.ClientUuid);
|
||||
if (!exists)
|
||||
{
|
||||
@@ -352,7 +418,7 @@ public class GameClient : IDisposable
|
||||
{
|
||||
ClientUuid = joinedPayload.ClientUuid,
|
||||
DisplayName = joinedPayload.DisplayName,
|
||||
IsOwner = false,
|
||||
IsOwner = joinedPayload.ClientUuid == CurrentLobbyState.OwnerId,
|
||||
IsReady = false,
|
||||
State = PlayerState.Alive
|
||||
});
|
||||
@@ -465,24 +531,32 @@ public class GameClient : IDisposable
|
||||
|
||||
#region Game Actions
|
||||
|
||||
public void CreateLobby(Position? center = null, int impostorCount = 1, int taskCount = 5, string? password = null, double playAreaRadius = 500)
|
||||
public void CreateLobby(Position? center = null, int impostorCount = 1, int taskCount = 5, string? password = null, double playAreaRadius = 500, GameSettingsOverrides? settings = null)
|
||||
{
|
||||
// DisplayName is sent on every CreateLobby/JoinLobby so the server
|
||||
// picks up the live nickname (typed into the input field after the
|
||||
// ClientHello handshake fired). Without this the server uses the
|
||||
// ClientHello-time name, which is the GameManager prefab default
|
||||
// for any user who immediately created/joined a lobby.
|
||||
Send(new CreateLobby
|
||||
{
|
||||
PlayAreaCenter = center,
|
||||
PlayAreaRadius = playAreaRadius,
|
||||
ImpostorCount = impostorCount,
|
||||
TaskCount = taskCount,
|
||||
Password = password
|
||||
Password = password,
|
||||
Settings = settings,
|
||||
DisplayName = DisplayName
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void JoinLobby(string joinCode, string? password = null)
|
||||
{
|
||||
Send(new JoinLobby
|
||||
{
|
||||
JoinCode = joinCode.ToUpperInvariant(),
|
||||
Password = password
|
||||
Password = password,
|
||||
DisplayName = DisplayName
|
||||
});
|
||||
}
|
||||
|
||||
@@ -491,6 +565,7 @@ public class GameClient : IDisposable
|
||||
Send(new LeaveLobby());
|
||||
LobbyId = null;
|
||||
JoinCode = null;
|
||||
CurrentLobbyState = null;
|
||||
}
|
||||
|
||||
public void StartGame()
|
||||
|
||||
@@ -49,6 +49,11 @@ public enum PlayerRole { Crew, Impostor }
|
||||
public enum PlayerState { Alive, Dead }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
// NOTE: `Voting` is reserved-but-unused on the wire as of 2026. The server
|
||||
// keeps the entire vote cycle inside `Meeting` and uses MeetingStartedPayload
|
||||
// timestamps (DiscussionEndTime / VotingEndTime) to distinguish sub-phases.
|
||||
// The enum value is preserved here for serialization compatibility with old
|
||||
// saves; new code should not assign it.
|
||||
public enum GamePhase { Lobby, Loading, Playing, Meeting, Voting, Ended }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
@@ -184,6 +189,24 @@ public class CreateLobby : Message
|
||||
|
||||
[JsonProperty("taskCount")]
|
||||
public int TaskCount { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// P13b: optional per-lobby settings overrides supplied by the host.
|
||||
/// Any field left null falls through to the server's current default
|
||||
/// (snapshotted at lobby creation, immutable thereafter for this lobby).
|
||||
/// </summary>
|
||||
[JsonProperty("settings")]
|
||||
public GameSettingsOverrides? Settings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional. Live host display name from the nickname input field at
|
||||
/// the moment of CreateLobby. ClientHello-time name is stale because
|
||||
/// the handshake fires before the user has typed anything; this lets
|
||||
/// the server pick up the freshly-typed name without a separate
|
||||
/// rename round-trip.
|
||||
/// </summary>
|
||||
[JsonProperty("displayName")]
|
||||
public string? DisplayName { get; set; }
|
||||
}
|
||||
|
||||
public class CreateLobbyResponse : Message
|
||||
@@ -209,12 +232,19 @@ public class CreateLobbyResponse : Message
|
||||
public class JoinLobby : Message
|
||||
{
|
||||
public override string Type => "JoinLobby";
|
||||
|
||||
|
||||
[JsonProperty("joinCode")]
|
||||
public string JoinCode { get; set; } = "";
|
||||
|
||||
|
||||
[JsonProperty("password")]
|
||||
public string? Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional. Live joiner display name from the nickname input field
|
||||
/// at the moment of Join. See CreateLobby.DisplayName for rationale.
|
||||
/// </summary>
|
||||
[JsonProperty("displayName")]
|
||||
public string? DisplayName { get; set; }
|
||||
}
|
||||
|
||||
public class JoinLobbyResponse : Message
|
||||
@@ -623,17 +653,26 @@ public class PlayerEjectedPayload
|
||||
public PlayerRole Role { get; set; }
|
||||
}
|
||||
|
||||
public class TaskStartedPayload
|
||||
{
|
||||
[JsonProperty("clientUuid")]
|
||||
public string ClientUuid { get; set; } = "";
|
||||
|
||||
[JsonProperty("taskId")]
|
||||
public string TaskId { get; set; } = "";
|
||||
}
|
||||
|
||||
public class TaskCompletedPayload
|
||||
{
|
||||
[JsonProperty("clientUuid")]
|
||||
public string ClientUuid { get; set; } = "";
|
||||
|
||||
|
||||
[JsonProperty("taskId")]
|
||||
public string TaskId { get; set; } = "";
|
||||
|
||||
|
||||
[JsonProperty("totalCompleted")]
|
||||
public int TotalCompleted { get; set; }
|
||||
|
||||
|
||||
[JsonProperty("totalTasks")]
|
||||
public int TotalTasks { get; set; }
|
||||
}
|
||||
@@ -713,10 +752,10 @@ public class RepairStartedPayload
|
||||
{
|
||||
[JsonProperty("sabotageId")]
|
||||
public string SabotageId { get; set; } = "";
|
||||
|
||||
|
||||
[JsonProperty("stationId")]
|
||||
public string StationId { get; set; } = "";
|
||||
|
||||
|
||||
[JsonProperty("playerId")]
|
||||
public string PlayerId { get; set; } = "";
|
||||
}
|
||||
@@ -725,10 +764,10 @@ public class RepairStoppedPayload
|
||||
{
|
||||
[JsonProperty("sabotageId")]
|
||||
public string SabotageId { get; set; } = "";
|
||||
|
||||
|
||||
[JsonProperty("stationId")]
|
||||
public string StationId { get; set; } = "";
|
||||
|
||||
|
||||
[JsonProperty("playerId")]
|
||||
public string PlayerId { get; set; } = "";
|
||||
}
|
||||
@@ -790,6 +829,162 @@ public class LobbyState
|
||||
/// <summary>True if map data has been loaded (or Overpass is disabled)</summary>
|
||||
[JsonProperty("mapDataReady")]
|
||||
public bool MapDataReady { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// P13b: full per-lobby settings snapshot. Clients use this for HUD
|
||||
/// (button visibility, countdown timings, etc.) instead of hardcoded
|
||||
/// values. Always populated for new server builds; old client builds
|
||||
/// can ignore the field.
|
||||
/// </summary>
|
||||
[JsonProperty("settings")]
|
||||
public GameSettings? Settings { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// P13b: per-lobby gameplay settings on the wire. Server populates this from
|
||||
/// its per-lobby snapshot so clients can drive HUD logic from authoritative
|
||||
/// values rather than hardcoded constants.
|
||||
/// </summary>
|
||||
public class GameSettings
|
||||
{
|
||||
// Round shape
|
||||
[JsonProperty("maxPlayers")]
|
||||
public int MaxPlayers { get; set; }
|
||||
|
||||
[JsonProperty("impostorCount")]
|
||||
public int ImpostorCount { get; set; }
|
||||
|
||||
[JsonProperty("taskCount")]
|
||||
public int TaskCount { get; set; }
|
||||
|
||||
[JsonProperty("tiePolicy")]
|
||||
public string TiePolicy { get; set; } = "NoEject";
|
||||
|
||||
// Distances (m)
|
||||
[JsonProperty("killDistanceM")]
|
||||
public double KillDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("reportDistanceM")]
|
||||
public double ReportDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("taskStartDistanceM")]
|
||||
public double TaskStartDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("meetingArrivalRadiusM")]
|
||||
public double MeetingArrivalRadiusM { get; set; }
|
||||
|
||||
[JsonProperty("emergencyMeetingCallRadiusM")]
|
||||
public double EmergencyMeetingCallRadiusM { get; set; }
|
||||
|
||||
[JsonProperty("repairStationDistanceM")]
|
||||
public double RepairStationDistanceM { get; set; }
|
||||
|
||||
// Cooldowns / counts
|
||||
[JsonProperty("killCooldownMs")]
|
||||
public int KillCooldownMs { get; set; }
|
||||
|
||||
[JsonProperty("emergencyMeetingCooldownMs")]
|
||||
public int EmergencyMeetingCooldownMs { get; set; }
|
||||
|
||||
[JsonProperty("maxEmergencyMeetingsPerPlayer")]
|
||||
public int MaxEmergencyMeetingsPerPlayer { get; set; }
|
||||
|
||||
// Meeting phases (ms)
|
||||
[JsonProperty("arrivalBaseMs")]
|
||||
public int ArrivalBaseMs { get; set; }
|
||||
|
||||
[JsonProperty("allowedLateMs")]
|
||||
public int AllowedLateMs { get; set; }
|
||||
|
||||
[JsonProperty("discussionPhaseMs")]
|
||||
public int DiscussionPhaseMs { get; set; }
|
||||
|
||||
[JsonProperty("votingPhaseMs")]
|
||||
public int VotingPhaseMs { get; set; }
|
||||
|
||||
// Sabotage
|
||||
[JsonProperty("sabotageCooldownMs")]
|
||||
public int SabotageCooldownMs { get; set; }
|
||||
|
||||
[JsonProperty("commsBlackoutDurationMs")]
|
||||
public int CommsBlackoutDurationMs { get; set; }
|
||||
|
||||
[JsonProperty("criticalMeltdownDeadlineMs")]
|
||||
public int CriticalMeltdownDeadlineMs { get; set; }
|
||||
|
||||
[JsonProperty("repairStationHoldMs")]
|
||||
public int RepairStationHoldMs { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// P13b: host-supplied overrides at CreateLobby. Every field is nullable so
|
||||
/// the host can opt into changing only what they care about; null = use the
|
||||
/// server's current default at the moment of lobby creation.
|
||||
/// </summary>
|
||||
public class GameSettingsOverrides
|
||||
{
|
||||
[JsonProperty("maxPlayers")]
|
||||
public int? MaxPlayers { get; set; }
|
||||
|
||||
[JsonProperty("impostorCount")]
|
||||
public int? ImpostorCount { get; set; }
|
||||
|
||||
[JsonProperty("taskCount")]
|
||||
public int? TaskCount { get; set; }
|
||||
|
||||
[JsonProperty("tiePolicy")]
|
||||
public string? TiePolicy { get; set; }
|
||||
|
||||
[JsonProperty("killDistanceM")]
|
||||
public double? KillDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("reportDistanceM")]
|
||||
public double? ReportDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("taskStartDistanceM")]
|
||||
public double? TaskStartDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("meetingArrivalRadiusM")]
|
||||
public double? MeetingArrivalRadiusM { get; set; }
|
||||
|
||||
[JsonProperty("emergencyMeetingCallRadiusM")]
|
||||
public double? EmergencyMeetingCallRadiusM { get; set; }
|
||||
|
||||
[JsonProperty("repairStationDistanceM")]
|
||||
public double? RepairStationDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("killCooldownMs")]
|
||||
public int? KillCooldownMs { get; set; }
|
||||
|
||||
[JsonProperty("emergencyMeetingCooldownMs")]
|
||||
public int? EmergencyMeetingCooldownMs { get; set; }
|
||||
|
||||
[JsonProperty("maxEmergencyMeetingsPerPlayer")]
|
||||
public int? MaxEmergencyMeetingsPerPlayer { get; set; }
|
||||
|
||||
[JsonProperty("arrivalBaseMs")]
|
||||
public int? ArrivalBaseMs { get; set; }
|
||||
|
||||
[JsonProperty("allowedLateMs")]
|
||||
public int? AllowedLateMs { get; set; }
|
||||
|
||||
[JsonProperty("discussionPhaseMs")]
|
||||
public int? DiscussionPhaseMs { get; set; }
|
||||
|
||||
[JsonProperty("votingPhaseMs")]
|
||||
public int? VotingPhaseMs { get; set; }
|
||||
|
||||
[JsonProperty("sabotageCooldownMs")]
|
||||
public int? SabotageCooldownMs { get; set; }
|
||||
|
||||
[JsonProperty("commsBlackoutDurationMs")]
|
||||
public int? CommsBlackoutDurationMs { get; set; }
|
||||
|
||||
[JsonProperty("criticalMeltdownDeadlineMs")]
|
||||
public int? CriticalMeltdownDeadlineMs { get; set; }
|
||||
|
||||
[JsonProperty("repairStationHoldMs")]
|
||||
public int? RepairStationHoldMs { get; set; }
|
||||
}
|
||||
|
||||
// Map data classes for rendering - compact format from server
|
||||
|
||||
@@ -4,145 +4,751 @@ using Subsystems;
|
||||
using System.Collections;
|
||||
using System;
|
||||
using TMPro;
|
||||
/*
|
||||
GameManager - hlavní tøida pro správu hry
|
||||
GameManager_Network - subsystém pro správu komunikace se serverem
|
||||
GameManager_Game - subsystém pro správu logiky hry (sabotáže, tasky, atd.)
|
||||
GameManager_Map - subsystém pro správu mapy a prostøedí
|
||||
GameManager_Input - subsystém pro správu vstupu od hráèe
|
||||
GameManager_UI - subsystém pro správu uživatelského rozhraní
|
||||
GamaManager_Stats - subsystém pro správu statistik pro server
|
||||
*/
|
||||
using UnityEngine.SceneManagement;
|
||||
public class GameManager : MonoBehaviour
|
||||
{
|
||||
[Header("Subsystems")]
|
||||
protected GameManager_Network networkSubsystem;
|
||||
protected GameManager_UI uiSubsystem;
|
||||
protected GameManager_Map mapSubsystem;
|
||||
protected GameManager_Input inputSubsystem;
|
||||
// Singleton
|
||||
public static GameManager Instance { get; private set; }
|
||||
|
||||
protected GameClient gameClient;
|
||||
[Header("Subsystems")]
|
||||
public GameManager_Network networkSubsystem;
|
||||
public GameManager_UI uiSubsystem;
|
||||
public GameManager_Map mapSubsystem;
|
||||
public GameManager_Input inputSubsystem;
|
||||
public GameManager_Tasks taskSubsystem;
|
||||
|
||||
public GameClient gameClient;
|
||||
|
||||
[Header("Player Info")]
|
||||
public string displayName;
|
||||
|
||||
[Header("UI Elements")]
|
||||
public Canvas JoinCreateLobby;
|
||||
public Canvas InLobby;
|
||||
public Canvas LoadingScreen;
|
||||
public Canvas GameScreen;
|
||||
[Header("Scene Management")]
|
||||
[SerializeField] public string firstMenuScene = "main menu asi idk lol";
|
||||
|
||||
[Header("UI Elements (Client.unity)")]
|
||||
// Canvas names in Client.unity — found at runtime in OnSceneLoaded
|
||||
private const string CanvasNameJoinCreate = "LobbySelector";
|
||||
private const string CanvasNameInLobby = "InLobby";
|
||||
private const string CanvasNameLoading = "LoadingScreen";
|
||||
private const string CanvasNameGame = "InGame";
|
||||
|
||||
[Header("Map")]
|
||||
public GameObject MapCenterPoint;
|
||||
// MapCenterPoint and Player are in Client.unity — wired at runtime in OnSceneLoaded.
|
||||
// buildingSettings/pathwaySettings/areaSettings must be assigned in SampleScene Inspector.
|
||||
public BuildingSettings buildingSettings;
|
||||
public PathwaySettings pathwaySettings;
|
||||
public AreaSettings areaSettings;
|
||||
|
||||
[Header("GPS")]
|
||||
public GameObject Player;
|
||||
[Header("Lobby Settings")]
|
||||
public double pendingRadius = 500;
|
||||
public int pendingImpostorCount = 1;
|
||||
public int pendingTaskCount = 5;
|
||||
/// <summary>
|
||||
/// P13b/c: full settings overrides accumulated by HostLobbyUI before the
|
||||
/// host taps "Create". Null = host didn't change anything beyond the three
|
||||
/// flat fields above; server falls through to its current defaults for
|
||||
/// every field. Each field is independently nullable so the host can
|
||||
/// opt into changing only what they care about.
|
||||
/// </summary>
|
||||
public GameSettingsOverrides pendingSettings;
|
||||
|
||||
[Header("Task Minigames (round-robin)")]
|
||||
// Names MUST match the scene file names in Assets/Scenes (case-sensitive)
|
||||
// and each one MUST be enabled in EditorBuildSettings, or LoadSceneAsync
|
||||
// will silently fail and the task button will appear dead.
|
||||
[SerializeField] public string[] minigameScenes = {
|
||||
"MiniGame-Kabely V10",
|
||||
"MiniGame-insertkeys",
|
||||
"MiniGame-FlappyBird",
|
||||
"MiniGame-ThrowInHole",
|
||||
"MiniGame-Satelit"
|
||||
};
|
||||
|
||||
[Header("Debug")]
|
||||
public bool testMode = false;
|
||||
private GameClient _secondClient;
|
||||
private GameClient _thirdClient;
|
||||
private GameManager_Network _secondNetwork;
|
||||
private GameManager_Network _thirdNetwork;
|
||||
/// <summary>
|
||||
/// When true, draw a small GPS status banner across the top of every
|
||||
/// screen. Useful for diagnosing why CreateLobby is blocked or why a
|
||||
/// joiner's position isn't updating - failures otherwise only show up
|
||||
/// in logcat which most users can't reach. Toggle off for release.
|
||||
/// </summary>
|
||||
public bool showGPSDebugOverlay = true;
|
||||
|
||||
/// <summary>
|
||||
/// Number of in-process test client bots to spawn alongside the host
|
||||
/// when testMode is on. Each gets its own GameClient + Network and
|
||||
/// joins the host's lobby automatically. Bots are switchable via
|
||||
/// number keys 1..N (host = 0). Default 3 keeps memory reasonable;
|
||||
/// bump for stress-testing voting / sabotage flows.
|
||||
/// </summary>
|
||||
public int testClientCount = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Per-bot network + display-name + sim-position state. The active slot
|
||||
/// (host = 0, bots = 1..N) gets WASD on the next tick.
|
||||
/// </summary>
|
||||
private class TestBot
|
||||
{
|
||||
public GameClient Client;
|
||||
public GameManager_Network Network;
|
||||
public string DisplayName;
|
||||
public GeoSus.Client.Position SimPosition;
|
||||
public bool Joined;
|
||||
public float LastSendTime;
|
||||
}
|
||||
private System.Collections.Generic.List<TestBot> _testBots = new System.Collections.Generic.List<TestBot>();
|
||||
/// <summary>Slot 0 = host (real player), 1..N = test bot index.</summary>
|
||||
private int _activeClientSlot = 0;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
// Keep the screen on while the player is in the app. A geographic
|
||||
// social-deduction game asks the user to walk around for 5-15 minutes
|
||||
// staring at the map; default Android sleep timeout (15-60s) blacks
|
||||
// the screen out mid-round, drops GPS updates, and requires the
|
||||
// player to re-unlock the phone. Two layers of belt-and-suspenders:
|
||||
// (1) Unity's Screen.sleepTimeout, which works on most devices and
|
||||
// is one line, but is overridden by some MIUI/EMUI ROMs.
|
||||
// (2) Android FLAG_KEEP_SCREEN_ON on the activity window, harder for
|
||||
// OEM ROMs to override and the standard pattern for navigation/maps
|
||||
// apps. Wrapped in #if UNITY_ANDROID so editor/iOS skip it.
|
||||
Screen.sleepTimeout = SleepTimeout.NeverSleep;
|
||||
AcquireAndroidWakelock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set FLAG_KEEP_SCREEN_ON on the Unity activity's window. This is the
|
||||
/// standard navigation/maps-app pattern and survives ROM-level overrides
|
||||
/// of Unity's Screen.sleepTimeout. No-op on non-Android platforms.
|
||||
/// </summary>
|
||||
private static void AcquireAndroidWakelock()
|
||||
{
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
try
|
||||
{
|
||||
using (var player = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
||||
using (var activity = player.GetStatic<AndroidJavaObject>("currentActivity"))
|
||||
{
|
||||
// addFlags must run on the UI thread. Capture activity into a
|
||||
// local for the closure - AndroidJavaObject can be reused.
|
||||
var act = activity;
|
||||
act.Call("runOnUiThread", new AndroidJavaRunnable(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var window = act.Call<AndroidJavaObject>("getWindow"))
|
||||
{
|
||||
// WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
const int FLAG_KEEP_SCREEN_ON = 0x00000080;
|
||||
window.Call("addFlags", FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[Wakelock] addFlags failed: " + ex.Message);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[Wakelock] Android JNI bridge failed: " + ex.Message);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
DontDestroyOnLoad(this);
|
||||
if (displayName == null || displayName == "")
|
||||
{
|
||||
displayName = GenerateUsername();
|
||||
}
|
||||
// The prefab default in SampleScene.unity is "Hrac" (Czech for
|
||||
// "player"). Treat it as equivalent to "no name set" so users who
|
||||
// never customize their name don't all show up identically. This
|
||||
// override only fires at startup; users who explicitly type "Hrac"
|
||||
// into the nickname field will still send "Hrac" via the live
|
||||
// DisplayName payload field.
|
||||
if (string.IsNullOrEmpty(displayName) || displayName == "Hrac")
|
||||
displayName = PlayerPrefs.GetString("PlayerName", GenerateUsername());
|
||||
|
||||
gameClient = new GameClient(GenerateUUID(), displayName);
|
||||
networkSubsystem = new GameManager_Network(gameClient, this);
|
||||
mapSubsystem = new GameManager_Map(gameClient, null, buildingSettings, pathwaySettings, areaSettings);
|
||||
uiSubsystem = new GameManager_UI(gameClient);
|
||||
inputSubsystem = new GameManager_Input(gameClient, null, testMode);
|
||||
taskSubsystem = new GameManager_Tasks(gameClient, minigameScenes, this);
|
||||
|
||||
if (testMode)
|
||||
{
|
||||
_secondClient = new GameClient(GenerateUUID(), GenerateUsername());
|
||||
_secondNetwork = new GameManager_Network(_secondClient);
|
||||
_thirdClient = new GameClient(GenerateUUID(), GenerateUsername());
|
||||
_thirdNetwork = new GameManager_Network(_thirdClient);
|
||||
|
||||
_secondNetwork.OpenConection();
|
||||
_thirdNetwork.OpenConection();
|
||||
}
|
||||
gameClient = new GameClient(GenerateUUID(), displayName);
|
||||
uiSubsystem = new GameManager_UI(gameClient, JoinCreateLobby, InLobby, LoadingScreen, GameScreen);
|
||||
networkSubsystem = new GameManager_Network(gameClient);
|
||||
mapSubsystem = new GameManager_Map(gameClient, MapCenterPoint, buildingSettings, pathwaySettings, areaSettings);
|
||||
inputSubsystem = new GameManager_Input(gameClient, Player, testMode);
|
||||
networkSubsystem.OpenConection();
|
||||
}
|
||||
private void Update()
|
||||
{
|
||||
if (gameClient.CurrentLobbyState != null)
|
||||
{
|
||||
uiSubsystem.UpdateLobbyUI();
|
||||
}
|
||||
try
|
||||
{
|
||||
if (gameClient.CurrentLobbyState.MapDataReady)
|
||||
int n = Mathf.Max(0, testClientCount);
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
mapSubsystem.BuildMap();
|
||||
gameClient.CurrentLobbyState.MapDataReady = false;
|
||||
var bot = new TestBot
|
||||
{
|
||||
DisplayName = "TestBot" + (i + 1),
|
||||
};
|
||||
bot.Client = new GameClient(GenerateUUID(), bot.DisplayName);
|
||||
bot.Network = new GameManager_Network(bot.Client, null);
|
||||
bot.Network.OpenConnection();
|
||||
_testBots.Add(bot);
|
||||
}
|
||||
}
|
||||
catch (NullReferenceException ex) { }
|
||||
inputSubsystem.positionCheck();
|
||||
|
||||
networkSubsystem.OpenConnection();
|
||||
|
||||
// Start GPS immediately at app launch. Acquiring a fix on a cold
|
||||
// device can take 5-30 seconds; if we wait until CreateLobby is
|
||||
// pressed, the lobby will be seeded with bad coords. Starting here
|
||||
// means the user's normal navigation through the menus gives the
|
||||
// GPS subsystem time to settle.
|
||||
inputSubsystem?.EnsureGPSStarted();
|
||||
|
||||
// Load main menu after GameManager is ready
|
||||
if (!string.IsNullOrEmpty(firstMenuScene))
|
||||
SceneManager.LoadScene(firstMenuScene, LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a GPS status banner across the top of every screen. We use OnGUI
|
||||
/// rather than a uGUI Canvas element because OnGUI works without any
|
||||
/// scene wiring - we want this visible from the very first frame, on
|
||||
/// every screen, even if the lobby canvas hasn't been bound yet. This is
|
||||
/// a debug overlay; toggle showGPSDebugOverlay off for release builds.
|
||||
/// </summary>
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!showGPSDebugOverlay) return;
|
||||
if (inputSubsystem == null) return;
|
||||
|
||||
protected string GenerateUUID()
|
||||
{
|
||||
string UUID = System.Guid.NewGuid().ToString();
|
||||
Debug.Log(UUID);
|
||||
return UUID;
|
||||
}
|
||||
protected string GenerateUsername()
|
||||
{
|
||||
string Username = UnityEngine.Random.Range(0,10).ToString() + UnityEngine.Random.Range(0, 10).ToString() + UnityEngine.Random.Range(0, 10).ToString() + UnityEngine.Random.Range(0, 10).ToString();
|
||||
Debug.Log(Username);
|
||||
return Username;
|
||||
}
|
||||
public void CreateLobbyButton()
|
||||
{
|
||||
networkSubsystem.CrateLobby(50.7727264, 15.0719876);
|
||||
if (testMode)
|
||||
var diag = inputSubsystem.GpsDiagnostic;
|
||||
var label = "GPS: " + diag;
|
||||
|
||||
// Scale font size to screen so it's legible on phones (HDPI) and
|
||||
// editor (lower DPI) alike. Phones tend to have ~400dpi; the
|
||||
// editor game view runs at ~100dpi.
|
||||
int fontSize = Mathf.Max(14, Screen.width / 50);
|
||||
|
||||
var style = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
StartCoroutine(ConnectTestClients());
|
||||
fontSize = fontSize,
|
||||
fontStyle = FontStyle.Bold,
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
wordWrap = false,
|
||||
normal = { textColor = Color.white }
|
||||
};
|
||||
|
||||
// Width covers most of the screen so longer error strings don't get
|
||||
// clipped. Height auto-fits the chosen font size.
|
||||
float pad = fontSize * 0.5f;
|
||||
float bannerH = fontSize * 2f;
|
||||
var rect = new Rect(pad, pad, Screen.width - pad * 2, bannerH);
|
||||
|
||||
// Translucent black background for legibility against the map.
|
||||
var prevColor = GUI.color;
|
||||
GUI.color = new Color(0f, 0f, 0f, 0.65f);
|
||||
GUI.Box(rect, GUIContent.none);
|
||||
GUI.color = prevColor;
|
||||
|
||||
// Indent the label inside the box.
|
||||
var textRect = new Rect(rect.x + pad, rect.y, rect.width - pad * 2, rect.height);
|
||||
GUI.Label(textRect, label, style);
|
||||
|
||||
// Second row: position-source picker (tap to cycle) + active client
|
||||
// indicator (testMode only). Both are diagnostic; the source picker
|
||||
// is the recovery path when one backend silently fails on a phone.
|
||||
float row2Y = rect.y + bannerH + pad * 0.5f;
|
||||
var btnStyle = new GUIStyle(GUI.skin.button)
|
||||
{
|
||||
fontSize = Mathf.Max(12, fontSize - 2),
|
||||
fontStyle = FontStyle.Bold,
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
};
|
||||
|
||||
// Source button: shows current source name + invites tap.
|
||||
string sourceLabel = "Source: " + inputSubsystem.CurrentSourceName + " [tap to cycle]";
|
||||
// Width sized to the text so the touch area matches the label.
|
||||
Vector2 sourceSize = btnStyle.CalcSize(new GUIContent(sourceLabel));
|
||||
float sourceW = Mathf.Min(Screen.width - pad * 2, sourceSize.x + pad * 2);
|
||||
var sourceRect = new Rect(pad, row2Y, sourceW, bannerH);
|
||||
if (GUI.Button(sourceRect, sourceLabel, btnStyle))
|
||||
{
|
||||
inputSubsystem.CycleNextPositionSource();
|
||||
}
|
||||
|
||||
// Active-client indicator (only when we have test bots).
|
||||
if (testMode && _testBots.Count > 0)
|
||||
{
|
||||
string slot = _activeClientSlot == 0 ? "Host" : ("Bot " + _activeClientSlot);
|
||||
string indicator = $"WASD: {slot} (0..{_testBots.Count} to switch)";
|
||||
var indStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = Mathf.Max(12, fontSize - 2),
|
||||
fontStyle = FontStyle.Bold,
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
normal = { textColor = new Color(0.9f, 1f, 0.4f) },
|
||||
};
|
||||
Vector2 indSize = indStyle.CalcSize(new GUIContent(indicator));
|
||||
var indRect = new Rect(sourceRect.xMax + pad, row2Y, indSize.x + pad * 2, bannerH);
|
||||
GUI.color = new Color(0f, 0f, 0f, 0.65f);
|
||||
GUI.Box(indRect, GUIContent.none);
|
||||
GUI.color = prevColor;
|
||||
GUI.Label(new Rect(indRect.x + pad, indRect.y, indRect.width, indRect.height), indicator, indStyle);
|
||||
}
|
||||
}
|
||||
public void JoinLobbyButton()
|
||||
|
||||
private void Update()
|
||||
{
|
||||
TMP_InputField joinCode = JoinCreateLobby.transform.Find("InputCode").GetComponent<TMP_InputField>();
|
||||
if (joinCode.text != null && joinCode.text != "")
|
||||
// Tick the SDK dispatcher so callbacks fire on main thread
|
||||
gameClient?.Update();
|
||||
if (testMode)
|
||||
{
|
||||
networkSubsystem.JoinLobby(joinCode.text);
|
||||
for (int i = 0; i < _testBots.Count; i++)
|
||||
_testBots[i].Client?.Update();
|
||||
HandleTestBotInput();
|
||||
}
|
||||
|
||||
if (gameClient?.CurrentLobbyState != null)
|
||||
{
|
||||
uiSubsystem?.UpdateLobbyUI();
|
||||
taskSubsystem?.UpdateProximity();
|
||||
}
|
||||
if (gameClient?.MyRole == PlayerRole.Impostor)
|
||||
UpdateKillCooldown();
|
||||
|
||||
inputSubsystem?.positionCheck();
|
||||
|
||||
if (testMode) StepActiveTestBot();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number-key handling for slot switching. 0 = host, 1..N = test bot N.
|
||||
/// Suppress host WASD when a non-host bot is active so the host capsule
|
||||
/// doesn't drift while the user is moving a bot. Only fires when
|
||||
/// testMode is on; release builds never see this path.
|
||||
/// </summary>
|
||||
private void HandleTestBotInput()
|
||||
{
|
||||
// 0 = host. 1..9 = bots (capped by Unity KeyCode.Alpha9).
|
||||
if (Input.GetKeyDown(KeyCode.Alpha0)) _activeClientSlot = 0;
|
||||
for (int i = 1; i <= 9 && i <= _testBots.Count; i++)
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Alpha0 + i)) _activeClientSlot = i;
|
||||
}
|
||||
|
||||
// Tell the host's input subsystem to ignore WASD when a bot is active.
|
||||
if (inputSubsystem != null)
|
||||
inputSubsystem.SuppressWasd = (_activeClientSlot != 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the active slot is a bot, step its sim position from WASD axes
|
||||
/// and send to the server. Idle bots get a periodic keep-alive so their
|
||||
/// avatars don't time out.
|
||||
/// </summary>
|
||||
private void StepActiveTestBot()
|
||||
{
|
||||
if (_testBots.Count == 0) return;
|
||||
var state = gameClient?.CurrentLobbyState;
|
||||
if (state == null || state.MapData == null) return;
|
||||
|
||||
// Lazy-init each bot's sim position to the lobby's map center on
|
||||
// first lobby state. Until the bot has joined a lobby it can't
|
||||
// send position updates.
|
||||
for (int i = 0; i < _testBots.Count; i++)
|
||||
{
|
||||
var bot = _testBots[i];
|
||||
if (!bot.Joined) continue;
|
||||
if (bot.SimPosition.Lat == 0 && bot.SimPosition.Lon == 0)
|
||||
{
|
||||
// Spawn each bot in a small ring around the map center so
|
||||
// they don't all stack on top of each other on frame one.
|
||||
double offsetLat = 0.00003 * Mathf.Cos(i * Mathf.PI * 2f / Mathf.Max(1, _testBots.Count));
|
||||
double offsetLon = 0.00003 * Mathf.Sin(i * Mathf.PI * 2f / Mathf.Max(1, _testBots.Count));
|
||||
bot.SimPosition = new GeoSus.Client.Position(
|
||||
state.MapData.Center.Lat + offsetLat,
|
||||
state.MapData.Center.Lon + offsetLon);
|
||||
bot.Client.UpdatePosition(bot.SimPosition);
|
||||
bot.LastSendTime = Time.time;
|
||||
}
|
||||
}
|
||||
|
||||
// WASD only drives the active bot.
|
||||
if (_activeClientSlot >= 1 && _activeClientSlot <= _testBots.Count)
|
||||
{
|
||||
var bot = _testBots[_activeClientSlot - 1];
|
||||
if (bot.Joined)
|
||||
{
|
||||
float dx = Input.GetAxis("Horizontal");
|
||||
float dy = Input.GetAxis("Vertical");
|
||||
const double speed = 0.00001;
|
||||
bool moved = Mathf.Abs(dx) > 0.001f || Mathf.Abs(dy) > 0.001f;
|
||||
if (moved)
|
||||
{
|
||||
bot.SimPosition = new GeoSus.Client.Position(
|
||||
bot.SimPosition.Lat + dy * speed,
|
||||
bot.SimPosition.Lon + dx * speed);
|
||||
}
|
||||
// Send on movement OR on keep-alive cadence so the server
|
||||
// doesn't drop our presence.
|
||||
bool dueKeepAlive = (Time.time - bot.LastSendTime) >= 1.0f;
|
||||
if (moved || dueKeepAlive)
|
||||
{
|
||||
bot.Client.UpdatePosition(bot.SimPosition);
|
||||
bot.LastSendTime = Time.time;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("Join code is empty!");
|
||||
// No bot is active. All bots get keep-alive only.
|
||||
for (int i = 0; i < _testBots.Count; i++)
|
||||
{
|
||||
var bot = _testBots[i];
|
||||
if (!bot.Joined) continue;
|
||||
if ((Time.time - bot.LastSendTime) >= 1.0f)
|
||||
{
|
||||
bot.Client.UpdatePosition(bot.SimPosition);
|
||||
bot.LastSendTime = Time.time;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
}
|
||||
void OnDisable()
|
||||
{
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// After Client.unity loads, re-bind all canvas/HUD references because
|
||||
/// those GameObjects don't exist in the Art menu scenes.
|
||||
/// </summary>
|
||||
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
if (scene.name == "Client")
|
||||
{
|
||||
var roots = scene.GetRootGameObjects();
|
||||
|
||||
// Find a root or deep GameObject by name in the loaded scene
|
||||
GameObject FindGO(string n) {
|
||||
foreach (var go in roots) {
|
||||
if (go.name == n) return go;
|
||||
var found = go.transform.Find(n);
|
||||
if (found != null) return found.gameObject;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Canvas FindCanvas(string n) {
|
||||
var go = FindGO(n);
|
||||
return go != null ? go.GetComponent<Canvas>() : null;
|
||||
}
|
||||
|
||||
// ── Build HUD BEFORE BindClientScene so FindTMP/Find can locate new elements ──
|
||||
var inGameGO = FindGO("InGame");
|
||||
if (inGameGO != null)
|
||||
{
|
||||
var builder = inGameGO.GetComponent<InGameHUDBuilder>()
|
||||
?? inGameGO.AddComponent<InGameHUDBuilder>();
|
||||
builder.BuildNow();
|
||||
}
|
||||
|
||||
// ── Wire canvases (after HUD is built) ──
|
||||
// Apply our standard CanvasScaler (1080x1920 reference, match=0.5)
|
||||
// to every canvas in the scene before binding so layouts scale
|
||||
// identically across phones and tablets without per-device tweaks.
|
||||
var cJoin = FindCanvas(CanvasNameJoinCreate);
|
||||
var cLobby = FindCanvas(CanvasNameInLobby);
|
||||
var cLoad = FindCanvas(CanvasNameLoading);
|
||||
var cGame = FindCanvas(CanvasNameGame);
|
||||
InGameHUDBuilder.ConfigureCanvasScaler(cJoin);
|
||||
InGameHUDBuilder.ConfigureCanvasScaler(cLobby);
|
||||
InGameHUDBuilder.ConfigureCanvasScaler(cLoad);
|
||||
InGameHUDBuilder.ConfigureCanvasScaler(cGame);
|
||||
uiSubsystem?.BindClientScene(cJoin, cLobby, cLoad, cGame);
|
||||
|
||||
// ── Wire map center point and player capsule ──
|
||||
var mapCenter = FindGO("MapCenterPoint");
|
||||
var player = FindGO("Capsule");
|
||||
mapSubsystem?.SetMapCenterPoint(mapCenter);
|
||||
inputSubsystem?.SetPlayerObject(player);
|
||||
|
||||
// ── Attach camera controller to Main Camera ──
|
||||
var mainCamGO = FindGO("Main Camera");
|
||||
if (mainCamGO != null)
|
||||
{
|
||||
var camCtrl = mainCamGO.GetComponent<MapCameraController>()
|
||||
?? mainCamGO.AddComponent<MapCameraController>();
|
||||
camCtrl.SetTarget(player);
|
||||
}
|
||||
|
||||
// If MapDataReady arrived before Client scene finished loading,
|
||||
// this will build the map now that scene references are valid.
|
||||
networkSubsystem?.OnClientSceneReady();
|
||||
}
|
||||
else if (scene.name == "create" || scene.name == "join loading")
|
||||
{
|
||||
// Lobby scene just loaded — ensure LobbyDisplayUI refreshes once
|
||||
// its Start() has run and registered itself (happens before Update).
|
||||
uiSubsystem?.NotifyLobbyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private float _killCooldownSeconds = 0f;
|
||||
private const float KillCooldownDuration = 20f;
|
||||
|
||||
private void UpdateKillCooldown()
|
||||
{
|
||||
if (_killCooldownSeconds > 0)
|
||||
{
|
||||
_killCooldownSeconds -= Time.deltaTime;
|
||||
// Mirror into GameState so UI reads from the single source of truth
|
||||
if (networkSubsystem?.State != null)
|
||||
networkSubsystem.State.KillCooldownRemaining = _killCooldownSeconds;
|
||||
uiSubsystem?.SetKillCooldownText($"Kill: {Mathf.CeilToInt(_killCooldownSeconds)}s");
|
||||
}
|
||||
else
|
||||
{
|
||||
_killCooldownSeconds = 0f;
|
||||
if (networkSubsystem?.State != null)
|
||||
networkSubsystem.State.KillCooldownRemaining = 0;
|
||||
uiSubsystem?.SetKillCooldownText("");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the ActionButton. Routes to kill / report / emergency / use-task
|
||||
/// depending on current proximity state.
|
||||
/// </summary>
|
||||
public void PerformAction()
|
||||
{
|
||||
if (uiSubsystem == null || uiSubsystem.IsPlayerDead) return;
|
||||
|
||||
bool isImpostor = gameClient?.MyRole == PlayerRole.Impostor;
|
||||
|
||||
// P13b: pull per-lobby distances from the server-snapshotted settings
|
||||
// instead of hardcoding 5m for every check. ?? fallback keeps the
|
||||
// pre-P13b behavior on old server builds that don't ship settings.
|
||||
var settings = networkSubsystem?.State?.Settings;
|
||||
double reportDist = settings?.ReportDistanceM ?? 5.0;
|
||||
double emergencyDist = settings?.EmergencyMeetingCallRadiusM ?? 5.0;
|
||||
double killDist = settings?.KillDistanceM ?? 5.0;
|
||||
|
||||
// 1. Nearby task → USE
|
||||
var nearbyTask = taskSubsystem?.NearbyTask;
|
||||
if (nearbyTask != null && !isImpostor)
|
||||
{
|
||||
taskSubsystem.TriggerNearbyTask();
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Nearby body → REPORT
|
||||
if (!uiSubsystem.IsCommsBlackout)
|
||||
{
|
||||
var nearbyBody = gameClient?.FindNearbyBody(reportDist);
|
||||
if (nearbyBody != null)
|
||||
{
|
||||
gameClient.ReportBody(nearbyBody.BodyId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Near map centre → EMERGENCY
|
||||
if (gameClient?.CurrentLobbyState?.MapData != null)
|
||||
{
|
||||
double distToCenter = gameClient.MyPosition.DistanceTo(gameClient.CurrentLobbyState.MapData.Center);
|
||||
if (distToCenter <= emergencyDist)
|
||||
{
|
||||
gameClient.CallEmergencyMeeting();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Impostor kill
|
||||
if (isImpostor && _killCooldownSeconds <= 0)
|
||||
{
|
||||
var targetUuid = gameClient?.FindNearbyPlayer(killDist);
|
||||
if (!string.IsNullOrEmpty(targetUuid))
|
||||
{
|
||||
gameClient.Kill(targetUuid);
|
||||
_killCooldownSeconds = KillCooldownDuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Called by Impostor sabotage buttons.</summary>
|
||||
public void StartSabotage(int typeIndex)
|
||||
{
|
||||
gameClient?.Send(new GeoSus.Client.StartSabotage { SabotageType = (SabotageType)typeIndex });
|
||||
}
|
||||
|
||||
/// <summary>Called by the meeting vote buttons. Pass null to skip.</summary>
|
||||
public void CastVote(string targetUuid)
|
||||
{
|
||||
gameClient?.Vote(targetUuid);
|
||||
}
|
||||
|
||||
protected string GenerateUUID()
|
||||
{
|
||||
return System.Guid.NewGuid().ToString();
|
||||
}
|
||||
protected string GenerateUsername()
|
||||
{
|
||||
return "Player" + UnityEngine.Random.Range(1000, 9999).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pull the nickname input field's current text into displayName +
|
||||
/// gameClient.DisplayName + PlayerPrefs before sending a network
|
||||
/// action. Defensive against any TMP_InputField / soft-keyboard race
|
||||
/// where the user types and immediately taps a button: onValueChanged
|
||||
/// normally fires before the click handler in the same frame, but
|
||||
/// some Android keyboards batch text events oddly. Call this at the
|
||||
/// top of any Create/Join/Rename flow. No-op if the input field
|
||||
/// doesn't exist in the current scene.
|
||||
/// </summary>
|
||||
private void CommitNicknameFromInput()
|
||||
{
|
||||
var nameGO = GameObject.Find("name");
|
||||
if (nameGO == null) return;
|
||||
var field = nameGO.GetComponent<TMPro.TMP_InputField>();
|
||||
if (field == null) return;
|
||||
// Force the InputField to flush any pending soft-keyboard text.
|
||||
// ForceLabelUpdate() is harmless if there's nothing pending.
|
||||
field.ForceLabelUpdate();
|
||||
string typed = (field.text ?? "").Trim();
|
||||
if (string.IsNullOrEmpty(typed)) return;
|
||||
if (typed == displayName) return; // already in sync, skip the writes
|
||||
displayName = typed;
|
||||
if (gameClient != null) gameClient.DisplayName = typed;
|
||||
PlayerPrefs.SetString("PlayerName", typed);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
// Called by HostLobbyUI
|
||||
public void CreateLobbyButton()
|
||||
{
|
||||
CommitNicknameFromInput();
|
||||
// Refuse to create a lobby without a real GPS fix. The previous
|
||||
// behavior of silently using a hardcoded Czechia fallback meant the
|
||||
// game always started at the same place no matter where the host was,
|
||||
// and the player capsule would spawn miles away in coordinate space
|
||||
// because they're at their real GPS while the map was built around
|
||||
// the fallback. Both bugs share this single gate.
|
||||
if (inputSubsystem?.LastKnownPosition == null)
|
||||
{
|
||||
// testMode bypasses the GPS gate entirely so debug runs still work.
|
||||
if (!testMode)
|
||||
{
|
||||
// Surface the actual GPS state in both logs and the toast
|
||||
// instead of the generic "Waiting for GPS fix..." that hides
|
||||
// permission/timeout/device-disabled distinctions.
|
||||
string diag = inputSubsystem?.GpsDiagnostic ?? "no input subsystem";
|
||||
Debug.LogWarning("[GameManager] CreateLobby blocked. " + diag);
|
||||
uiSubsystem?.ShowToast("Cannot create lobby. " + diag);
|
||||
inputSubsystem?.EnsureGPSStarted();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var pos = inputSubsystem?.LastKnownPosition;
|
||||
double lat = pos?.Lat ?? 0;
|
||||
double lon = pos?.Lon ?? 0;
|
||||
networkSubsystem.CreateLobby(lat, lon, pendingRadius, pendingImpostorCount, pendingTaskCount, pendingSettings);
|
||||
if (testMode) StartCoroutine(ConnectTestClients());
|
||||
}
|
||||
|
||||
// Called by JoinLobbyUI with the code from the input field
|
||||
public void JoinLobbyButton(string code)
|
||||
{
|
||||
CommitNicknameFromInput();
|
||||
if (!string.IsNullOrEmpty(code))
|
||||
networkSubsystem.JoinLobby(code);
|
||||
else
|
||||
Debug.LogWarning("Join code is empty!");
|
||||
}
|
||||
|
||||
public void LeaveLobbyButton()
|
||||
{
|
||||
networkSubsystem.LeaveLobby();
|
||||
}
|
||||
|
||||
public void StartGameButton()
|
||||
{
|
||||
networkSubsystem.StartGame();
|
||||
}
|
||||
|
||||
void OnApplicationQuit()
|
||||
{
|
||||
gameClient.Disconnect();
|
||||
_secondClient?.Disconnect();
|
||||
_thirdClient?.Disconnect();
|
||||
gameClient?.Disconnect();
|
||||
for (int i = 0; i < _testBots.Count; i++)
|
||||
_testBots[i].Client?.Disconnect();
|
||||
}
|
||||
|
||||
IEnumerator ConnectTestClients()
|
||||
{
|
||||
yield return new WaitForSeconds(2f);
|
||||
_secondNetwork.JoinLobby(gameClient.CurrentLobbyState.JoinCode);
|
||||
_thirdNetwork.JoinLobby(gameClient.CurrentLobbyState.JoinCode);
|
||||
if (_testBots.Count == 0) yield break;
|
||||
|
||||
// Wait until host lobby code exists
|
||||
float wait = 0f;
|
||||
while ((gameClient?.CurrentLobbyState == null || string.IsNullOrEmpty(gameClient.CurrentLobbyState.JoinCode)) && wait < 20f)
|
||||
{
|
||||
wait += 0.25f;
|
||||
yield return new WaitForSeconds(0.25f);
|
||||
}
|
||||
|
||||
var joinCode = gameClient?.CurrentLobbyState?.JoinCode;
|
||||
if (string.IsNullOrEmpty(joinCode))
|
||||
{
|
||||
Debug.LogWarning("[TestMode] Could not join test bots: join code not available.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Wait until every bot's client has finished its TCP handshake.
|
||||
// IsReady flips once ClientHello + ClientHelloAck round-trip.
|
||||
wait = 0f;
|
||||
bool allReady;
|
||||
do
|
||||
{
|
||||
allReady = true;
|
||||
for (int i = 0; i < _testBots.Count; i++)
|
||||
{
|
||||
if (_testBots[i].Client == null || !_testBots[i].Client.IsReady)
|
||||
{
|
||||
allReady = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!allReady)
|
||||
{
|
||||
wait += 0.25f;
|
||||
yield return new WaitForSeconds(0.25f);
|
||||
}
|
||||
} while (!allReady && wait < 20f);
|
||||
|
||||
if (!allReady)
|
||||
{
|
||||
Debug.LogWarning("[TestMode] Some test bots not ready, joining the ready ones only.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < _testBots.Count; i++)
|
||||
{
|
||||
var bot = _testBots[i];
|
||||
if (bot.Client != null && bot.Client.IsReady)
|
||||
{
|
||||
bot.Network?.JoinLobby(joinCode);
|
||||
bot.Joined = true;
|
||||
}
|
||||
}
|
||||
Debug.Log($"[TestMode] {_testBots.Count} bot(s) joined lobby with code {joinCode}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using UnityEditor;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace Subsystems{
|
||||
[System.Serializable]
|
||||
public class BuildingSettings
|
||||
{
|
||||
public Material ResidentalBuildingsMat;
|
||||
public float ResidentalBuildingHeight;
|
||||
public Material ResidentialBuildingsMat;
|
||||
public float ResidentialBuildingHeight;
|
||||
public Material CommercialBuildingsMat;
|
||||
public float CommercialBuildingHeight;
|
||||
public Material IndustrialBuildingsMat;
|
||||
@@ -64,7 +64,44 @@ namespace Subsystems{
|
||||
private BuildingSettings _buildingSettings;
|
||||
private PathwaySettings _pathwaySettings;
|
||||
private AreaSettings _areaSettings;
|
||||
private const float _metersPerUnit = 1f;
|
||||
private const float _metersPerUnit = 1f;
|
||||
|
||||
// ── Layer Y separation (single source of truth for vertical stacking) ───
|
||||
// Areas at the bottom, paths above areas, buildings extruded upward from
|
||||
// their own base, POIs floating well above everything else. Z-fighting
|
||||
// happens when adjacent geometry shares a Y; these constants keep each
|
||||
// logical layer at a distinct elevation.
|
||||
private const float kAreaBaseY = 0.10f;
|
||||
private const float kPathY = 0.30f;
|
||||
private const float kBuildingBaseY = 0.50f;
|
||||
private const float kPoiY = 2.00f;
|
||||
|
||||
// Render-queue forcing was tried in P3 to disambiguate same-Y geometry
|
||||
// but turned out to be the cause of the "blank map in mobile game view,
|
||||
// fine in scene view" regression: forcing transparent-class shaders
|
||||
// (default queue 3000+) into the Geometry range (2000-2150) breaks
|
||||
// their depth-write/blend assumptions on mobile shader paths. The
|
||||
// editor's scene view masks it because it uses different render paths
|
||||
// and post-process is off there. Queue forcing removed in P8;
|
||||
// disambiguation is now via Y-layering + per-area Y-stagger alone,
|
||||
// which the depth buffer resolves correctly even on weak mobile GPUs.
|
||||
|
||||
// ── Marker sizing (top-down camera, units = meters) ─────────────────
|
||||
// The camera's orthographic size pushes "1 meter" to a small fraction
|
||||
// of the screen. Markers need to be visibly larger than buildings'
|
||||
// footprints for instant recognition.
|
||||
private const float kMarkerHeight = 8f; // pillar height
|
||||
private const float kMarkerRadius = 3f; // pillar radius (cylinder X/Z)
|
||||
private const float kMarkerY = 4f; // base Y so pillar centers ~mid-height
|
||||
private const float kLabelY = 9f; // text label sits above pillar top
|
||||
private const float kLabelFontSize = 14f; // 3D text size in world units
|
||||
|
||||
// Runtime marker collections
|
||||
private Dictionary<string, GameObject> _taskMarkers = new Dictionary<string, GameObject>();
|
||||
private Dictionary<string, GameObject> _bodyMarkers = new Dictionary<string, GameObject>();
|
||||
private Dictionary<string, GameObject> _playerAvatars = new Dictionary<string, GameObject>();
|
||||
private List<GameObject> _sabotageMarkers = new List<GameObject>();
|
||||
|
||||
public GameManager_Map(GameClient gameClient, GameObject mapCenterPoint, BuildingSettings buildingSettings, PathwaySettings pathwaySettings, AreaSettings areaSettings)
|
||||
{
|
||||
_gameClient = gameClient;
|
||||
@@ -73,8 +110,25 @@ namespace Subsystems{
|
||||
_pathwaySettings = pathwaySettings;
|
||||
_areaSettings = areaSettings;
|
||||
}
|
||||
|
||||
public bool IsSceneReady => _mapCenterPoint != null;
|
||||
|
||||
/// <summary>Called from OnSceneLoaded when Client.unity is loaded so the
|
||||
/// MapCenterPoint (which lives in Client.unity) can be wired at runtime.</summary>
|
||||
public void SetMapCenterPoint(GameObject go) { _mapCenterPoint = go; }
|
||||
public void BuildMap()
|
||||
{
|
||||
if (_mapCenterPoint == null)
|
||||
{
|
||||
Debug.LogWarning("[Map] BuildMap skipped: MapCenterPoint is not yet bound.");
|
||||
return;
|
||||
}
|
||||
if (_gameClient?.CurrentLobbyState?.MapData == null)
|
||||
{
|
||||
Debug.LogWarning("[Map] BuildMap skipped: no MapData in CurrentLobbyState.");
|
||||
return;
|
||||
}
|
||||
|
||||
ClearChildren();
|
||||
_centerPosition = _gameClient.CurrentLobbyState.MapData.Center;
|
||||
GameObject buildingsRoot = new GameObject("Buildings");
|
||||
@@ -108,7 +162,133 @@ namespace Subsystems{
|
||||
GameObject a = BuildAreaMesh(area);
|
||||
a.transform.parent = areaRoot.transform;
|
||||
}
|
||||
//TODO: POIs
|
||||
|
||||
GameObject poiRoot = new GameObject("POIs");
|
||||
poiRoot.transform.parent = _mapCenterPoint.transform;
|
||||
int poiCount = 0;
|
||||
foreach (var poi in _gameClient.CurrentLobbyState.MapData.GetPOIs())
|
||||
{
|
||||
GameObject p = BuildPOIMarker(poi);
|
||||
if (p != null) { p.transform.parent = poiRoot.transform; poiCount++; }
|
||||
}
|
||||
|
||||
// Diagnostic - if the user reports "map missing in game view" but
|
||||
// the counts here are non-zero, the bug is camera/culling related,
|
||||
// not a build issue.
|
||||
int buildings = _gameClient.CurrentLobbyState.MapData.GetBuildings()?.Count ?? 0;
|
||||
int paths = _gameClient.CurrentLobbyState.MapData.GetPathways()?.Count ?? 0;
|
||||
int areas = _gameClient.CurrentLobbyState.MapData.GetAreas()?.Count ?? 0;
|
||||
Debug.Log($"[Map] BuildMap done: {buildings} buildings, {paths} paths, " +
|
||||
$"{areas} areas, {poiCount} POIs. MapCenterPoint={_mapCenterPoint.name} " +
|
||||
$"layer={_mapCenterPoint.layer} pos={_mapCenterPoint.transform.position} " +
|
||||
$"scale={_mapCenterPoint.transform.localScale}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a tall, brightly-colored pillar for a Point of Interest with
|
||||
/// a 3D text label above it (e.g. "FOOD", "SHOP"). The label is laid
|
||||
/// flat on the XZ plane facing UP so it reads correctly under the
|
||||
/// orthogonal top-down camera.
|
||||
/// </summary>
|
||||
private GameObject BuildPOIMarker(MapPOI poi)
|
||||
{
|
||||
if (poi == null) return null;
|
||||
var color = ColorForPOI(poi.POIType);
|
||||
string label = LabelForPOI(poi.POIType);
|
||||
var pos = poi.Location.ToLocalVector3(_centerPosition);
|
||||
return CreateMarkerWithLabel($"POI_{poi.POIType}_{poi.Id}", pos, color, label);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared marker builder: tall colored cylinder pillar + 3D text label
|
||||
/// above it. Used by POIs, tasks, bodies, and sabotage stations so
|
||||
/// they all share a visual language ("colored pillar with a name").
|
||||
/// </summary>
|
||||
private GameObject CreateMarkerWithLabel(string name, Vector3 worldPos, Color color, string label)
|
||||
{
|
||||
var go = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
|
||||
go.name = name;
|
||||
|
||||
// Strip the auto-added collider - markers are visual only.
|
||||
var col = go.GetComponent<Collider>();
|
||||
if (col != null) UnityEngine.Object.Destroy(col);
|
||||
|
||||
go.transform.position = worldPos + Vector3.up * kMarkerY;
|
||||
// Cylinder's default unit is 2 tall, 1 wide. Scale Y by half of
|
||||
// kMarkerHeight (built-in is 2 units), X/Z by kMarkerRadius.
|
||||
go.transform.localScale = new Vector3(kMarkerRadius, kMarkerHeight * 0.5f, kMarkerRadius);
|
||||
|
||||
var mr = go.GetComponent<MeshRenderer>();
|
||||
if (mr != null)
|
||||
{
|
||||
// One .material access -> single clone of the primitive's
|
||||
// default mat. Don't touch renderQueue (P3 regression cause).
|
||||
var inst = mr.material;
|
||||
if (inst != null) inst.color = color;
|
||||
}
|
||||
|
||||
// 3D text label - lays flat on top of the pillar facing up.
|
||||
// Parented to the marker so it follows position changes.
|
||||
var labelGO = new GameObject("Label");
|
||||
labelGO.transform.SetParent(go.transform, worldPositionStays: false);
|
||||
// Local Y offset: pillar's local scale Y is kMarkerHeight/2, but
|
||||
// the cylinder primitive is 2 units tall in local space, so its
|
||||
// top is at local +1. Label sits a hair above that.
|
||||
labelGO.transform.localPosition = new Vector3(0, 1.05f, 0);
|
||||
// Rotate 90 around X so the text quad's normal points +Y (toward
|
||||
// the top-down camera). The default TMP forward is +Z.
|
||||
labelGO.transform.localRotation = Quaternion.Euler(90f, 0f, 0f);
|
||||
// Compensate for the cylinder's non-uniform parent scale so the
|
||||
// text size in world units matches kLabelFontSize regardless of
|
||||
// how the pillar was scaled.
|
||||
labelGO.transform.localScale = new Vector3(
|
||||
1f / kMarkerRadius,
|
||||
1f / (kMarkerHeight * 0.5f),
|
||||
1f / kMarkerRadius);
|
||||
|
||||
var tmp = labelGO.AddComponent<TextMeshPro>();
|
||||
tmp.text = label;
|
||||
tmp.fontSize = kLabelFontSize;
|
||||
tmp.color = Color.white;
|
||||
tmp.fontStyle = FontStyles.Bold;
|
||||
tmp.alignment = TextAlignmentOptions.Center;
|
||||
tmp.outlineColor = Color.black;
|
||||
tmp.outlineWidth = 0.25f;
|
||||
// Reasonable bounds so the text mesh isn't auto-clipped.
|
||||
var rt = tmp.rectTransform;
|
||||
rt.sizeDelta = new Vector2(20, 4);
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
private static Color ColorForPOI(MapPOIType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MapPOIType.FoodDrink: return new Color(1.00f, 0.55f, 0.00f); // orange
|
||||
case MapPOIType.Shop: return new Color(0.20f, 0.60f, 1.00f); // blue
|
||||
case MapPOIType.Health: return new Color(0.96f, 0.27f, 0.27f); // red
|
||||
case MapPOIType.Transport: return new Color(0.85f, 0.85f, 0.20f); // yellow
|
||||
case MapPOIType.Culture: return new Color(0.65f, 0.30f, 0.95f); // purple
|
||||
case MapPOIType.Landmark: return new Color(0.95f, 0.85f, 0.40f); // gold
|
||||
case MapPOIType.Recreation: return new Color(0.30f, 0.85f, 0.30f); // green
|
||||
default: return new Color(0.75f, 0.75f, 0.80f); // muted grey
|
||||
}
|
||||
}
|
||||
|
||||
private static string LabelForPOI(MapPOIType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MapPOIType.FoodDrink: return "FOOD";
|
||||
case MapPOIType.Shop: return "SHOP";
|
||||
case MapPOIType.Health: return "HEALTH";
|
||||
case MapPOIType.Transport: return "TRANSIT";
|
||||
case MapPOIType.Culture: return "CULTURE";
|
||||
case MapPOIType.Landmark: return "LANDMARK";
|
||||
case MapPOIType.Recreation: return "PARK";
|
||||
default: return "POI";
|
||||
}
|
||||
}
|
||||
void ClearChildren()
|
||||
{
|
||||
@@ -125,9 +305,12 @@ namespace Subsystems{
|
||||
{
|
||||
var building = new GameObject($"Building_{b.Name ?? "Unknown"}");
|
||||
|
||||
// Výpočet středu budovy
|
||||
// Výpočet středu budovy. Lift the base above kPathY so building
|
||||
// walls visibly extrude *upward* from above the road/area layer
|
||||
// instead of starting at ground (which made them clip into paved
|
||||
// areas that share their footprint).
|
||||
Vector3 center = CalculatePolygonCenter(b.Outline);
|
||||
building.transform.position = center;
|
||||
building.transform.position = center + Vector3.up * kBuildingBaseY;
|
||||
|
||||
// Vytvoření mesh pro budovu
|
||||
MeshFilter meshFilter = building.AddComponent<MeshFilter>();
|
||||
@@ -138,8 +321,8 @@ namespace Subsystems{
|
||||
switch (b.BuildingType.ToLower())
|
||||
{
|
||||
case "residential":
|
||||
mat = _buildingSettings.ResidentalBuildingsMat;
|
||||
height = _buildingSettings.ResidentalBuildingHeight;
|
||||
mat = _buildingSettings.ResidentialBuildingsMat;
|
||||
height = _buildingSettings.ResidentialBuildingHeight;
|
||||
break;
|
||||
case "commercial":
|
||||
mat = _buildingSettings.CommercialBuildingsMat;
|
||||
@@ -158,8 +341,12 @@ namespace Subsystems{
|
||||
meshFilter.mesh = mesh;
|
||||
|
||||
//TODO: material by type
|
||||
// Použijeme barvu podle typu budovy
|
||||
meshRenderer.material = mat;
|
||||
// Použijeme barvu podle typu budovy. Use sharedMaterial to keep
|
||||
// the project's Material asset reference - no clone, no leak.
|
||||
// Y-position alone disambiguates building geometry from area/path
|
||||
// layers; we don't need renderQueue overrides (which broke mobile
|
||||
// rendering for transparent-class shaders in P3).
|
||||
meshRenderer.sharedMaterial = mat;
|
||||
|
||||
// Přidání collideru pro interakci
|
||||
building.AddComponent<MeshCollider>();
|
||||
@@ -218,15 +405,19 @@ namespace Subsystems{
|
||||
break;
|
||||
}
|
||||
|
||||
line.material = mat;
|
||||
// sharedMaterial avoids the LineRenderer cloning the project's
|
||||
// shared path Material on every BuildMap call. Queue overrides
|
||||
// dropped (P3 mobile-render regression cause).
|
||||
line.sharedMaterial = mat;
|
||||
line.widthMultiplier = width;
|
||||
|
||||
// Nastavení bodů cesty
|
||||
// Nastavení bodů cesty - kPathY sits above all area polygons but
|
||||
// below building bases, so paths visibly run on top of areas.
|
||||
line.positionCount = w.Points.Count;
|
||||
for (int i = 0; i < w.Points.Count; i++)
|
||||
{
|
||||
Vector3 pos = w.Points[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center);
|
||||
pos.y = 0.1f; // Mírně nad zemí
|
||||
pos.y = kPathY;
|
||||
line.SetPosition(i, pos);
|
||||
}
|
||||
return path;
|
||||
@@ -269,13 +460,58 @@ namespace Subsystems{
|
||||
break;
|
||||
}
|
||||
|
||||
meshRenderer.material = mat;
|
||||
// sharedMaterial: no per-area material clone. Render-queue forcing
|
||||
// dropped in P8 (caused mobile-render regression). The Y-stagger
|
||||
// below alone now drives "smaller polygon on top of larger one"
|
||||
// depth ordering - which is what the depth buffer was always
|
||||
// designed to do, and works on mobile GPUs with weak precision
|
||||
// because the stagger spread (0.04 units) is well above any
|
||||
// reasonable depth-buffer epsilon.
|
||||
meshRenderer.sharedMaterial = mat;
|
||||
|
||||
area.transform.position = new Vector3(0, 0.05f, 0); // Těsně nad zemí
|
||||
// Y stagger: smaller polygons sit a hair higher than larger ones,
|
||||
// so depth-test draws them on top of bigger area polygons they sit
|
||||
// inside (e.g. a playground inside a park). Total spread is 0.04
|
||||
// units - visually invisible but plenty for the depth buffer.
|
||||
float yStagger = ComputeAreaYStagger(a.Outline);
|
||||
area.transform.position = new Vector3(0, kAreaBaseY + yStagger, 0);
|
||||
|
||||
return area;
|
||||
}
|
||||
//TODO: POIs
|
||||
|
||||
/// <summary>
|
||||
/// Returns a non-negative size proxy used to bucket areas by footprint.
|
||||
/// Larger polygons return higher numbers; used inversely for queue/Y.
|
||||
/// </summary>
|
||||
private float AreaSizeBucket(List<Position> outline)
|
||||
{
|
||||
if (outline == null || outline.Count < 3) return 1f;
|
||||
// Cheap bbox area in lat-lon space scaled by 1e6 - we only need a
|
||||
// monotonic ordering, not a real geographic area.
|
||||
double minLat = outline[0].Lat, maxLat = outline[0].Lat;
|
||||
double minLon = outline[0].Lon, maxLon = outline[0].Lon;
|
||||
for (int i = 1; i < outline.Count; i++)
|
||||
{
|
||||
if (outline[i].Lat < minLat) minLat = outline[i].Lat;
|
||||
if (outline[i].Lat > maxLat) maxLat = outline[i].Lat;
|
||||
if (outline[i].Lon < minLon) minLon = outline[i].Lon;
|
||||
if (outline[i].Lon > maxLon) maxLon = outline[i].Lon;
|
||||
}
|
||||
double bbox = (maxLat - minLat) * (maxLon - minLon) * 1e6;
|
||||
return (float)System.Math.Max(0.001, bbox);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smaller areas get a higher Y so they render on top of any larger
|
||||
/// area they overlap. Returns a value in [0, 0.04] units.
|
||||
/// </summary>
|
||||
private float ComputeAreaYStagger(List<Position> outline)
|
||||
{
|
||||
float bucket = AreaSizeBucket(outline);
|
||||
// Inverse mapping: huge area -> 0, tiny area -> 0.04.
|
||||
float t = Mathf.Clamp01(1f - bucket / (bucket + 50f));
|
||||
return t * 0.04f;
|
||||
}
|
||||
#endregion
|
||||
#region Polygon Utils
|
||||
private Vector3 CalculatePolygonCenter(List<Position> points)
|
||||
@@ -287,19 +523,52 @@ namespace Subsystems{
|
||||
}
|
||||
return center / points.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signed XZ shoelace area for a polygon expressed in local Vector3.
|
||||
/// Positive = CCW (Unity left-handed Y-up: upward-facing normal),
|
||||
/// negative = CW (downward-facing normal -> top face invisible from
|
||||
/// above unless we reverse the winding before triangulating).
|
||||
/// </summary>
|
||||
private static float PolygonSignedAreaXZ(List<Vector3> verts)
|
||||
{
|
||||
float area = 0f;
|
||||
int n = verts.Count;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
var a = verts[i];
|
||||
var b = verts[(i + 1) % n];
|
||||
area += (b.x - a.x) * (a.z + b.z);
|
||||
}
|
||||
return area * 0.5f;
|
||||
}
|
||||
private Mesh CreateExtrudedPolygonMesh(List<Position> outline, float height)
|
||||
{
|
||||
Mesh mesh = new Mesh();
|
||||
|
||||
// Reject degenerates - Recast/Overpass can hand back 1-2 vertex
|
||||
// outlines on broken ways. Empty mesh -> renderer draws nothing,
|
||||
// safer than a malformed triangle list.
|
||||
if (outline == null || outline.Count < 3) return mesh;
|
||||
|
||||
// Convert to local space first so we can run a winding check, then
|
||||
// reverse if needed. Without this, CW outlines from Overpass yield
|
||||
// downward-facing top normals and the building roof is invisible
|
||||
// from the top-down map camera.
|
||||
int vertexCount = outline.Count;
|
||||
var localVerts = new List<Vector3>(vertexCount);
|
||||
Vector3 center = CalculatePolygonCenter(outline);
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
localVerts.Add(outline[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center) - center);
|
||||
|
||||
if (PolygonSignedAreaXZ(localVerts) < 0f)
|
||||
localVerts.Reverse();
|
||||
|
||||
// Vertices - spodní a horní podstava
|
||||
Vector3[] vertices = new Vector3[vertexCount * 2];
|
||||
Vector3 center = CalculatePolygonCenter(outline);
|
||||
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
Vector3 pos = outline[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center) - center;
|
||||
Vector3 pos = localVerts[i];
|
||||
vertices[i] = pos; // Spodní
|
||||
vertices[i + vertexCount] = pos + Vector3.up * height; // Horní
|
||||
}
|
||||
@@ -343,25 +612,30 @@ namespace Subsystems{
|
||||
{
|
||||
Mesh mesh = new Mesh();
|
||||
|
||||
int vertexCount = outline.Count;
|
||||
Vector3[] vertices = new Vector3[vertexCount];
|
||||
Vector3 center = CalculatePolygonCenter(outline);
|
||||
// Reject degenerates (matches CreateExtrudedPolygonMesh).
|
||||
if (outline == null || outline.Count < 3) return mesh;
|
||||
|
||||
int vertexCount = outline.Count;
|
||||
var localVerts = new List<Vector3>(vertexCount);
|
||||
Vector3 center = CalculatePolygonCenter(outline);
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
vertices[i] = outline[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center) - center;
|
||||
}
|
||||
localVerts.Add(outline[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center) - center);
|
||||
|
||||
// Force CCW so RecalculateNormals produces an upward-facing normal.
|
||||
// CW polygons from Overpass would otherwise render as black voids
|
||||
// when the top-down camera looks at their back face.
|
||||
if (PolygonSignedAreaXZ(localVerts) < 0f)
|
||||
localVerts.Reverse();
|
||||
|
||||
Vector3[] vertices = localVerts.ToArray();
|
||||
|
||||
// Triangulace - fan pattern
|
||||
List<int> triangles = new List<int>();
|
||||
if (vertexCount >= 3)
|
||||
for (int i = 1; i < vertexCount - 1; i++)
|
||||
{
|
||||
for (int i = 1; i < vertexCount - 1; i++)
|
||||
{
|
||||
triangles.Add(0);
|
||||
triangles.Add(i);
|
||||
triangles.Add(i + 1);
|
||||
}
|
||||
triangles.Add(0);
|
||||
triangles.Add(i);
|
||||
triangles.Add(i + 1);
|
||||
}
|
||||
|
||||
mesh.vertices = vertices;
|
||||
@@ -371,5 +645,164 @@ namespace Subsystems{
|
||||
return mesh;
|
||||
}
|
||||
#endregion
|
||||
#region Markers
|
||||
|
||||
public void CreateTaskMarkers(List<GeoSus.Client.GameTask> tasks)
|
||||
{
|
||||
if (_mapCenterPoint == null) return;
|
||||
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0)
|
||||
{
|
||||
var md = _gameClient?.CurrentLobbyState?.MapData;
|
||||
if (md != null) _centerPosition = md.Center;
|
||||
}
|
||||
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0) return;
|
||||
var taskColor = new Color(0.20f, 0.95f, 0.55f); // bright green - "GO HERE"
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
if (_taskMarkers.ContainsKey(task.TaskId)) continue;
|
||||
var pos = task.Location.ToLocalVector3(_centerPosition);
|
||||
var go = CreateMarkerWithLabel($"Task_{task.TaskId}", pos, taskColor, "TASK");
|
||||
go.transform.parent = _mapCenterPoint.transform;
|
||||
|
||||
// Pulsing point light so the task literally glows on the map.
|
||||
var light = go.AddComponent<Light>();
|
||||
light.color = taskColor;
|
||||
light.intensity = 3f;
|
||||
light.range = 25f;
|
||||
_taskMarkers[task.TaskId] = go;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveTaskMarker(string taskId)
|
||||
{
|
||||
if (_taskMarkers.TryGetValue(taskId, out var go))
|
||||
{
|
||||
UnityEngine.Object.Destroy(go);
|
||||
_taskMarkers.Remove(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateBodyMarker(string bodyId, Position location)
|
||||
{
|
||||
if (_mapCenterPoint == null) return;
|
||||
if (_bodyMarkers.ContainsKey(bodyId)) return;
|
||||
var pos = location.ToLocalVector3(_centerPosition);
|
||||
// Bright red pillar with "BODY" label - players need to see this
|
||||
// from across the map to call it in.
|
||||
var go = CreateMarkerWithLabel($"Body_{bodyId}", pos,
|
||||
new Color(0.96f, 0.18f, 0.18f), "BODY");
|
||||
go.transform.parent = _mapCenterPoint?.transform;
|
||||
_bodyMarkers[bodyId] = go;
|
||||
}
|
||||
|
||||
public void ClearBodyMarkers()
|
||||
{
|
||||
foreach (var go in _bodyMarkers.Values)
|
||||
if (go) UnityEngine.Object.Destroy(go);
|
||||
_bodyMarkers.Clear();
|
||||
}
|
||||
|
||||
// ── Player avatar sizing ────────────────────────────────────────────
|
||||
// The default Unity capsule primitive is 2m tall in local space. The
|
||||
// map camera defaults to 150m orthographic-ish height (see
|
||||
// MapCameraController), so anything smaller than ~3m world-size is a
|
||||
// pixel on screen. Original code used scale=0.4 (~0.8m capsule) which
|
||||
// was invisible. Markers (POIs/tasks/bodies) are 8m pillars; players
|
||||
// need to be visibly distinct from those AND from each other. The
|
||||
// local player gets a halo light + larger scale so the user can find
|
||||
// themselves on the map at a glance.
|
||||
private const float kLocalPlayerScale = 4f; // ~8m capsule (matches marker height)
|
||||
private const float kRemotePlayerScale = 2f; // ~4m capsule (smaller than markers)
|
||||
private const float kLocalPlayerHaloRange = 18f;
|
||||
private const float kLocalPlayerHaloIntensity = 2.5f;
|
||||
|
||||
public void UpdatePlayerAvatars(Dictionary<string, PlayerPositionInfo> positions, string myUuid)
|
||||
{
|
||||
if (_mapCenterPoint == null) return;
|
||||
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0)
|
||||
{
|
||||
var md = _gameClient?.CurrentLobbyState?.MapData;
|
||||
if (md != null) _centerPosition = md.Center;
|
||||
}
|
||||
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0) return;
|
||||
foreach (var kvp in positions)
|
||||
{
|
||||
string uuid = kvp.Key;
|
||||
var info = kvp.Value;
|
||||
bool isLocal = uuid == myUuid;
|
||||
if (!_playerAvatars.TryGetValue(uuid, out var go) || go == null)
|
||||
{
|
||||
go = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
go.name = $"Player_{uuid.Substring(0, Mathf.Min(8, uuid.Length))}";
|
||||
go.transform.parent = _mapCenterPoint?.transform;
|
||||
// Strip the auto-collider - avatars are visual only and the
|
||||
// collider would interact with the map's MeshColliders.
|
||||
var col = go.GetComponent<Collider>();
|
||||
if (col != null) UnityEngine.Object.Destroy(col);
|
||||
|
||||
float scale = isLocal ? kLocalPlayerScale : kRemotePlayerScale;
|
||||
go.transform.localScale = Vector3.one * scale;
|
||||
|
||||
if (isLocal)
|
||||
{
|
||||
// Halo light around the local player so the user can
|
||||
// find themselves at a glance even at the widest zoom.
|
||||
// Range/intensity tuned so it reads as "this is me"
|
||||
// without bleeding far enough to drown POI markers.
|
||||
var halo = go.AddComponent<Light>();
|
||||
halo.color = new Color(0.30f, 1.00f, 0.55f); // matches green capsule color
|
||||
halo.intensity = kLocalPlayerHaloIntensity;
|
||||
halo.range = kLocalPlayerHaloRange;
|
||||
}
|
||||
|
||||
_playerAvatars[uuid] = go;
|
||||
}
|
||||
|
||||
// Lift the avatar so the bottom of the capsule sits roughly at
|
||||
// ground level despite the larger scale. Capsule's local pivot
|
||||
// is at center, height = 2 * localScale.y world units, so we
|
||||
// raise by half the local height.
|
||||
float halfHeight = (isLocal ? kLocalPlayerScale : kRemotePlayerScale);
|
||||
go.transform.position = info.Position.ToLocalVector3(_centerPosition)
|
||||
+ Vector3.up * halfHeight;
|
||||
|
||||
var mr = go.GetComponent<MeshRenderer>();
|
||||
if (mr)
|
||||
{
|
||||
if (isLocal) mr.material.color = new Color(0.30f, 1.00f, 0.55f);
|
||||
else if (info.State == GeoSus.Client.PlayerState.Dead) mr.material.color = Color.grey;
|
||||
else mr.material.color = Color.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateSabotageMarkers(List<RepairStationInfo> stations)
|
||||
{
|
||||
var color = new Color(1.0f, 0.55f, 0.0f); // strong orange = repair urgency
|
||||
foreach (var station in stations)
|
||||
{
|
||||
var pos = station.Location.ToLocalVector3(_centerPosition);
|
||||
var go = CreateMarkerWithLabel($"Sabotage_{station.StationId}", pos,
|
||||
color, "REPAIR");
|
||||
go.transform.parent = _mapCenterPoint?.transform;
|
||||
|
||||
// Repair stations also pulse light so impostors and crew see
|
||||
// the urgency from across the map.
|
||||
var light = go.AddComponent<Light>();
|
||||
light.color = color;
|
||||
light.intensity = 4f;
|
||||
light.range = 30f;
|
||||
_sabotageMarkers.Add(go);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearSabotageMarkers()
|
||||
{
|
||||
foreach (var go in _sabotageMarkers)
|
||||
if (go) UnityEngine.Object.Destroy(go);
|
||||
_sabotageMarkers.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Subsystems;
|
||||
using System.Linq;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Subsystems
|
||||
{
|
||||
@@ -13,9 +14,34 @@ namespace Subsystems
|
||||
private const string _serverAddress = "geosus.honzuvkod.dev";
|
||||
private const int _serverPort = 7777;
|
||||
private GameClient _gameClient;
|
||||
private GameManager_Map _mapSubsystem;
|
||||
public async void OpenConection()
|
||||
private GameManager _manager;
|
||||
private bool _pendingMapBuild;
|
||||
|
||||
/// <summary>
|
||||
/// Authoritative game state; written here, read by GameManager_UI.
|
||||
/// </summary>
|
||||
public GameState State { get; } = new GameState();
|
||||
|
||||
public GameManager_Network(GameClient gameClient, GameManager manager)
|
||||
{
|
||||
_gameClient = gameClient;
|
||||
_manager = manager;
|
||||
RegisterEventHandlers();
|
||||
}
|
||||
|
||||
public async void OpenConnection()
|
||||
{
|
||||
// Snapshot the lobby we believed we were in BEFORE the new connect
|
||||
// attempt. If the client SDK preserved it across a transient drop
|
||||
// (P9 fix), this is non-null and we'll send a Reconnect message
|
||||
// post-handshake to re-associate with the lobby on the server side.
|
||||
// Without it, the next CastVote / TaskComplete / etc. would arrive
|
||||
// on a fresh connection the server doesn't recognize and bounce
|
||||
// with NOT_IN_LOBBY.
|
||||
var rejoinLobbyId = _gameClient.LobbyId;
|
||||
|
||||
int retries = 0;
|
||||
int delayMs = 5000;
|
||||
while (true)
|
||||
{
|
||||
Task<bool> state = _gameClient.ConnectAsync(_serverAddress, _serverPort);
|
||||
@@ -23,139 +49,544 @@ namespace Subsystems
|
||||
if (state.Result)
|
||||
{
|
||||
Debug.Log("Connected to server.");
|
||||
|
||||
// Re-attach to the prior lobby if we had one. Server-side
|
||||
// HandleReconnectAsync will replay missed events and ack
|
||||
// with a ReconnectResponse carrying the snapshot.
|
||||
if (!string.IsNullOrEmpty(rejoinLobbyId))
|
||||
{
|
||||
Debug.Log($"Re-associating with lobby {rejoinLobbyId} after reconnect.");
|
||||
_gameClient.Reconnect(rejoinLobbyId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
retries++;
|
||||
if (retries >= 10)
|
||||
{
|
||||
Debug.Log("Failed to connect to server");
|
||||
Debug.LogError("Failed to connect after 10 attempts. Giving up.");
|
||||
break;
|
||||
}
|
||||
await Task.Delay(5000);
|
||||
Debug.Log($"Failed to connect (attempt {retries}). Retrying in {delayMs / 1000}s...");
|
||||
await Task.Delay(delayMs);
|
||||
delayMs = Mathf.Min(delayMs * 2, 30000);
|
||||
}
|
||||
}
|
||||
public GameManager_Network(GameClient gameClient)
|
||||
{
|
||||
_gameClient = gameClient;
|
||||
RegisterEventHandlers();
|
||||
}
|
||||
|
||||
public void RegisterEventHandlers()
|
||||
{
|
||||
_gameClient.OnConnected += OnConnected;
|
||||
_gameClient.OnConnected += OnConnected;
|
||||
_gameClient.OnDisconnected += OnDisconnected;
|
||||
_gameClient.OnError += OnError;
|
||||
_gameClient.OnMessage += OnMessage;
|
||||
_gameClient.OnGameEvent += OnGameEvent;
|
||||
_gameClient.OnError += OnError;
|
||||
_gameClient.OnMessage += OnMessage;
|
||||
_gameClient.OnGameEvent += OnGameEvent;
|
||||
}
|
||||
|
||||
private void OnConnected()
|
||||
{
|
||||
Debug.Log("Successfully connected to the server.");
|
||||
// Tear the reconnect overlay down once the socket is healthy.
|
||||
// No-op if it wasn't shown.
|
||||
_manager?.uiSubsystem?.HideReconnecting();
|
||||
}
|
||||
|
||||
private void OnError(string e) => Debug.LogError($"Network error: {e}");
|
||||
|
||||
private void OnDisconnected(string reason)
|
||||
{
|
||||
Debug.Log($"Host disconnected due to {reason}");
|
||||
Debug.Log($"Disconnected: {reason}");
|
||||
// Show the reconnect overlay only if the user is mid-game; we
|
||||
// don't want it flashing during a clean shutdown ("Disposed") or
|
||||
// before a real game has started.
|
||||
if (reason != "Disposed" && State.Phase != GamePhase.Lobby)
|
||||
_manager?.uiSubsystem?.ShowReconnecting();
|
||||
|
||||
if (reason != "Disposed" && _manager != null)
|
||||
_manager.StartCoroutine(ReconnectAfterDelay(3f));
|
||||
}
|
||||
private void OnError(string error)
|
||||
|
||||
private IEnumerator ReconnectAfterDelay(float seconds)
|
||||
{
|
||||
Debug.LogError($"Network error: {error}");
|
||||
yield return new UnityEngine.WaitForSeconds(seconds);
|
||||
Debug.Log("Attempting to reconnect...");
|
||||
OpenConnection();
|
||||
}
|
||||
|
||||
private void OnMessage(Message message)
|
||||
{
|
||||
switch (message.Type)
|
||||
{
|
||||
case "GameEvent":
|
||||
OnGameEvent(message as GameEvent);
|
||||
break;
|
||||
case "CreateLobbyResponse":
|
||||
Debug.Log("Received CreateLobbyResponse message");
|
||||
HandleCreateLobbyResponse(message as CreateLobbyResponse);
|
||||
break;
|
||||
case "JoinLobbyResponse":
|
||||
Debug.Log("Received JoinLobbyResponse message");
|
||||
HandleJoinLobbyResponse(message as JoinLobbyResponse);
|
||||
break;
|
||||
case "PositionBroadcast":
|
||||
HandlePositionBroadcast(message as PositionBroadcast);
|
||||
break;
|
||||
case "Error":
|
||||
HandleErrorMessage(message as ErrorMessage);
|
||||
break;
|
||||
case "Ack":
|
||||
Debug.Log("Received Ack message");
|
||||
case "GameEvent":
|
||||
break;
|
||||
default:
|
||||
Debug.Log("Received message of type: " + message.Type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// P9 defensive path: if the server tells us NOT_IN_LOBBY but we still
|
||||
/// believe we have a lobby (LobbyId preserved across the transient
|
||||
/// disconnect), the lobby association on the server's side of the new
|
||||
/// connection is missing - typically a race between OpenConnection's
|
||||
/// Reconnect call and an in-flight action message that beat it. Retry
|
||||
/// the Reconnect; if the second attempt also bounces, the lobby really
|
||||
/// is gone and we'll surface the error to the user.
|
||||
/// </summary>
|
||||
private void HandleErrorMessage(ErrorMessage err)
|
||||
{
|
||||
if (err == null) return;
|
||||
Debug.Log($"Server error: code={err.ErrorCode} text={err.ErrorText}");
|
||||
|
||||
if (err.ErrorCode == "NOT_IN_LOBBY" && !string.IsNullOrEmpty(_gameClient.LobbyId))
|
||||
{
|
||||
Debug.Log($"NOT_IN_LOBBY but we still have LobbyId={_gameClient.LobbyId}; resending Reconnect.");
|
||||
_gameClient.Reconnect(_gameClient.LobbyId);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGameEvent(GameEvent gameEvent)
|
||||
{
|
||||
// Always sync player list from lobby state after any event
|
||||
SyncPlayersFromLobby();
|
||||
|
||||
switch (gameEvent.EventType)
|
||||
{
|
||||
case "PlayerJoined":
|
||||
Debug.Log($"Player {gameEvent.GetPayload<PlayerJoinedPayload>().DisplayName} joined");
|
||||
break;
|
||||
case "PlayerLeft":
|
||||
Debug.Log($"Player {gameEvent.GetPayload<PlayerLeftPayload>()} left");
|
||||
case "HostChanged":
|
||||
_manager?.uiSubsystem?.NotifyLobbyChanged();
|
||||
break;
|
||||
|
||||
case "GameStarting":
|
||||
Debug.Log("Game is starting!");
|
||||
break;
|
||||
case "GameStarted":
|
||||
Debug.Log("Game started");
|
||||
State.Phase = GamePhase.Loading;
|
||||
HandleGameStarting();
|
||||
break;
|
||||
|
||||
case "MapDataReady":
|
||||
Debug.Log("Map data ready");
|
||||
HandleMapDataReady();
|
||||
break;
|
||||
case "PlayerMapDataReceived":
|
||||
Debug.Log("Player map data recieved");
|
||||
|
||||
case "GameStarted":
|
||||
State.Phase = GamePhase.Playing;
|
||||
break;
|
||||
|
||||
case "RoleAssigned":
|
||||
HandleRoleAssigned(gameEvent);
|
||||
break;
|
||||
|
||||
case "TaskCompleted":
|
||||
HandleTaskCompleted(gameEvent);
|
||||
break;
|
||||
|
||||
case "PlayerKilled":
|
||||
HandlePlayerKilled(gameEvent);
|
||||
break;
|
||||
|
||||
case "BodyReported":
|
||||
case "EmergencyMeetingCalled":
|
||||
Toast("Meeting called! Head to the meeting point.");
|
||||
break;
|
||||
|
||||
case "MeetingStarted":
|
||||
HandleMeetingStarted(gameEvent);
|
||||
break;
|
||||
|
||||
case "PlayerArrivedAtMeeting":
|
||||
HandlePlayerArrivedAtMeeting(gameEvent);
|
||||
break;
|
||||
|
||||
case "PlayerVoted":
|
||||
HandlePlayerVoted(gameEvent);
|
||||
break;
|
||||
|
||||
case "VotingClosed":
|
||||
HandleVotingClosed(gameEvent);
|
||||
break;
|
||||
|
||||
case "GameEnded":
|
||||
HandleGameEnded(gameEvent);
|
||||
break;
|
||||
|
||||
case "ReturnedToLobby":
|
||||
HandleReturnedToLobby();
|
||||
break;
|
||||
|
||||
case "SabotageStarted":
|
||||
HandleSabotageStarted(gameEvent);
|
||||
break;
|
||||
|
||||
case "RepairStarted":
|
||||
HandleRepairStarted(gameEvent);
|
||||
break;
|
||||
|
||||
case "RepairStopped":
|
||||
HandleRepairStopped(gameEvent);
|
||||
break;
|
||||
|
||||
case "SabotageRepaired":
|
||||
case "SabotageMeltdown":
|
||||
case "SabotageExpired":
|
||||
State.ActiveSabotage = null;
|
||||
State.ActiveRepairs.Clear();
|
||||
_manager?.uiSubsystem?.HideSabotageTimer();
|
||||
_manager?.mapSubsystem?.ClearSabotageMarkers();
|
||||
break;
|
||||
|
||||
case "TaskStarted":
|
||||
// Server now broadcasts when a player begins a task. Phase 1
|
||||
// only acks; Phase 2/3 will surface this to other players.
|
||||
break;
|
||||
|
||||
case "MapDataError":
|
||||
Debug.Log("Received MapData server error");
|
||||
HandleMapDataError(gameEvent);
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Log("Received GameEvent of type: " + gameEvent.EventType);
|
||||
Debug.Log("GameEvent: " + gameEvent.EventType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Lobby responses ───────────────────────────────────────────────────
|
||||
|
||||
private void HandleCreateLobbyResponse(CreateLobbyResponse message)
|
||||
{
|
||||
if (message == null) return;
|
||||
if (message.Success)
|
||||
{
|
||||
Debug.Log("Lobby created successfully. Join Code: " + message.JoinCode + ", Lobby ID: " + message.LobbyId);
|
||||
Debug.Log($"Lobby created. Code: {message.JoinCode}");
|
||||
// P13b: snapshot the server's authoritative settings into
|
||||
// GameState so HUD / proximity code can read distances and
|
||||
// cooldowns from a single source of truth instead of hardcodes.
|
||||
State.Settings = _gameClient.CurrentLobbyState?.Settings;
|
||||
SceneManager.LoadScene("create", LoadSceneMode.Single);
|
||||
_manager?.uiSubsystem?.NotifyLobbyChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Failed to create lobby: " + message.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleJoinLobbyResponse(JoinLobbyResponse message)
|
||||
{
|
||||
if (message == null) return;
|
||||
if (message.Success)
|
||||
{
|
||||
Debug.Log("Lobby created successfully." + ", Lobby ID: " + message.LobbyId);
|
||||
Debug.Log($"Joined lobby: {message.LobbyId}");
|
||||
// P13b: same settings snapshot path as host. Joiners read the
|
||||
// server's snapshot taken at lobby creation; they cannot edit.
|
||||
State.Settings = _gameClient.CurrentLobbyState?.Settings;
|
||||
// Unified lobby: both host and joiners land on create.unity.
|
||||
// LobbyDisplayUI handles the role split internally (start
|
||||
// button for host, waiting text for joiners).
|
||||
SceneManager.LoadScene("create", LoadSceneMode.Single);
|
||||
_manager?.uiSubsystem?.NotifyLobbyChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Failed to create lobby: " + message.Error);
|
||||
Debug.LogError("Failed to join lobby: " + message.Error);
|
||||
}
|
||||
}
|
||||
public void CrateLobby(double lat, double lon)
|
||||
|
||||
// ── Game flow ─────────────────────────────────────────────────────────
|
||||
|
||||
private void HandleGameStarting()
|
||||
{
|
||||
_gameClient.CreateLobby(new Position(lat, lon));
|
||||
_pendingMapBuild = false;
|
||||
// Reset per-game state
|
||||
State.MyRole = null;
|
||||
State.IsDead = false;
|
||||
State.MyTasks = new List<GameTask>();
|
||||
State.MyCompletedTaskIds = new HashSet<string>();
|
||||
State.TotalCompleted = 0;
|
||||
State.TotalRequired = 0;
|
||||
State.ActiveMeeting = null;
|
||||
State.LastVoteResult = null;
|
||||
State.VotedPlayerIds = new HashSet<string>();
|
||||
State.ActiveSabotage = null;
|
||||
State.GameEndData = null;
|
||||
State.KillCooldownRemaining = 0;
|
||||
SceneManager.LoadScene("Client", LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
private void HandleMapDataReady()
|
||||
{
|
||||
_pendingMapBuild = true;
|
||||
TryBuildMapAndMarkers();
|
||||
}
|
||||
|
||||
public void OnClientSceneReady()
|
||||
{
|
||||
TryBuildMapAndMarkers();
|
||||
}
|
||||
|
||||
private void TryBuildMapAndMarkers()
|
||||
{
|
||||
if (!_pendingMapBuild) return;
|
||||
if (_manager?.mapSubsystem == null || !_manager.mapSubsystem.IsSceneReady) return;
|
||||
if (_gameClient?.CurrentLobbyState?.MapData == null) return;
|
||||
|
||||
_manager.mapSubsystem.BuildMap();
|
||||
_manager.mapSubsystem.CreateTaskMarkers(_gameClient.MyTasks);
|
||||
_pendingMapBuild = false;
|
||||
Debug.Log("[Network] Map built.");
|
||||
}
|
||||
|
||||
private void HandleRoleAssigned(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<RoleAssignedPayload>();
|
||||
if (payload == null || payload.ClientUuid != _gameClient.ClientUuid) return;
|
||||
|
||||
State.MyRole = payload.Role;
|
||||
State.MyTasks = payload.Tasks ?? new List<GameTask>();
|
||||
State.MyCompletedTaskIds.Clear();
|
||||
|
||||
Debug.Log($"Role: {payload.Role}, Tasks: {State.MyTasks.Count}");
|
||||
_manager?.taskSubsystem?.Initialize(State.MyTasks);
|
||||
}
|
||||
|
||||
private void HandleTaskCompleted(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<TaskCompletedPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
// Track if it's our task
|
||||
if (payload.ClientUuid == _gameClient.ClientUuid)
|
||||
State.MyCompletedTaskIds.Add(payload.TaskId);
|
||||
|
||||
State.TotalCompleted = payload.TotalCompleted;
|
||||
State.TotalRequired = payload.TotalTasks;
|
||||
|
||||
_manager?.uiSubsystem?.UpdateTaskProgress(payload.TotalCompleted, payload.TotalTasks);
|
||||
_manager?.mapSubsystem?.RemoveTaskMarker(payload.TaskId);
|
||||
}
|
||||
|
||||
private void HandlePlayerKilled(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<PlayerKilledPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
_manager?.mapSubsystem?.CreateBodyMarker(payload.BodyId, payload.Location);
|
||||
|
||||
if (payload.VictimId == _gameClient.ClientUuid)
|
||||
{
|
||||
State.IsDead = true;
|
||||
_manager?.uiSubsystem?.OnLocalPlayerDied();
|
||||
}
|
||||
|
||||
// Update player state in our list
|
||||
var p = State.Players.Find(x => x.ClientUuid == payload.VictimId);
|
||||
if (p != null) p.State = PlayerState.Dead;
|
||||
}
|
||||
|
||||
private void HandleMeetingStarted(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<MeetingStartedPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
State.Phase = GamePhase.Meeting;
|
||||
State.ActiveMeeting = payload;
|
||||
State.VotedPlayerIds = new HashSet<string>();
|
||||
State.ArrivedPlayerIds = new HashSet<string>();
|
||||
State.VoterTargets = new Dictionary<string, string>();
|
||||
State.VoteTallies = new Dictionary<string, int>();
|
||||
State.MyVoteTarget = null;
|
||||
State.LastVoteResult = null;
|
||||
|
||||
SyncPlayersFromLobby();
|
||||
_manager?.uiSubsystem?.ShowMeetingPanel(State.Players, payload);
|
||||
}
|
||||
|
||||
private void HandlePlayerArrivedAtMeeting(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<PlayerArrivedAtMeetingPayload>();
|
||||
if (payload == null) return;
|
||||
State.ArrivedPlayerIds.Add(payload.ClientUuid);
|
||||
}
|
||||
|
||||
private void HandlePlayerVoted(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<PlayerVotedPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
// Server allows vote changes within a 2s rate limit, so we always
|
||||
// overwrite the voter's previous target rather than appending.
|
||||
string target = payload.TargetId ?? GameState.VoteSkip;
|
||||
|
||||
State.VotedPlayerIds.Add(payload.VoterId);
|
||||
State.VoterTargets[payload.VoterId] = target;
|
||||
RecomputeVoteTallies();
|
||||
|
||||
if (payload.VoterId == _gameClient.ClientUuid)
|
||||
State.MyVoteTarget = target;
|
||||
}
|
||||
|
||||
private void RecomputeVoteTallies()
|
||||
{
|
||||
State.VoteTallies.Clear();
|
||||
foreach (var t in State.VoterTargets.Values)
|
||||
{
|
||||
if (string.IsNullOrEmpty(t)) continue;
|
||||
State.VoteTallies.TryGetValue(t, out var count);
|
||||
State.VoteTallies[t] = count + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleVotingClosed(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<VotingClosedPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
State.Phase = GamePhase.Playing;
|
||||
State.ActiveMeeting = null;
|
||||
State.LastVoteResult = payload;
|
||||
|
||||
// Mark ejected player dead in our list
|
||||
if (!string.IsNullOrEmpty(payload.EjectedPlayerId))
|
||||
{
|
||||
var p = State.Players.Find(x => x.ClientUuid == payload.EjectedPlayerId);
|
||||
if (p != null) p.State = PlayerState.Dead;
|
||||
}
|
||||
|
||||
_manager?.uiSubsystem?.ShowVoteResult(payload, State.Players);
|
||||
_manager?.mapSubsystem?.ClearBodyMarkers();
|
||||
}
|
||||
|
||||
private void HandleGameEnded(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<GameEndedPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
State.Phase = GamePhase.Ended;
|
||||
State.GameEndData = payload;
|
||||
|
||||
// If the round ended while the meeting/vote-result overlay was
|
||||
// still up (e.g. ejection won the game outright), the auto-close
|
||||
// coroutine would otherwise fire 5s later and tear down the
|
||||
// meeting panel while the GameEndPanel sits on top - leaving a
|
||||
// glimpse of the dead overlay during the transition.
|
||||
_manager?.uiSubsystem?.HideMeetingPanel();
|
||||
_manager?.uiSubsystem?.ShowGameEndPanel(payload, _gameClient.ClientUuid);
|
||||
}
|
||||
|
||||
private void HandleReturnedToLobby()
|
||||
{
|
||||
State.Phase = GamePhase.Lobby;
|
||||
_manager?.uiSubsystem?.HideMeetingPanel();
|
||||
// Bodies survive the scene reload because the marker GameObjects are
|
||||
// parented under MapCenterPoint (which lives in the persistent
|
||||
// Client.unity scene). Without this clear, returning to lobby and
|
||||
// starting a new round leaves stale corpses on the map of the new
|
||||
// round. Server already cleared its `_bodies` set in
|
||||
// ProcessReturnToLobby; this is the client-side mirror that was
|
||||
// missing in HandleVotingClosed's symmetry.
|
||||
_manager?.mapSubsystem?.ClearBodyMarkers();
|
||||
_manager?.mapSubsystem?.ClearSabotageMarkers();
|
||||
// Unified lobby: regardless of role, return to create.unity.
|
||||
SceneManager.LoadScene("create", LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
private void HandleSabotageStarted(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<SabotageStartedPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
State.ActiveSabotage = payload;
|
||||
State.ActiveRepairs.Clear();
|
||||
|
||||
_manager?.mapSubsystem?.CreateSabotageMarkers(payload.RepairStations);
|
||||
if (payload.Type == SabotageType.CriticalMeltdown && payload.Deadline.HasValue)
|
||||
_manager?.uiSubsystem?.ShowSabotageTimer(payload.Deadline.Value);
|
||||
if (payload.Type == SabotageType.CommsBlackout)
|
||||
_manager?.uiSubsystem?.SetCommsBlackout(true);
|
||||
}
|
||||
|
||||
private void HandleRepairStarted(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<RepairStartedPayload>();
|
||||
if (payload == null || string.IsNullOrEmpty(payload.StationId)) return;
|
||||
State.ActiveRepairs.Add(payload.StationId);
|
||||
}
|
||||
|
||||
private void HandleRepairStopped(GameEvent evt)
|
||||
{
|
||||
// A player abandoned a repair station mid-fix. The station is no
|
||||
// longer counted as active for the simultaneous-repair coaching;
|
||||
// the marker stays on the map until the sabotage resolves.
|
||||
var payload = evt.GetPayload<RepairStoppedPayload>();
|
||||
if (payload != null && !string.IsNullOrEmpty(payload.StationId))
|
||||
State.ActiveRepairs.Remove(payload.StationId);
|
||||
}
|
||||
|
||||
private void HandleMapDataError(GameEvent evt)
|
||||
{
|
||||
// Server failed to fetch Overpass data. Without this the loading
|
||||
// screen would hang forever. Drop back to lobby and surface the
|
||||
// failure so the player can re-host or try a different center.
|
||||
Debug.LogError("[Network] Server could not generate map data.");
|
||||
State.Phase = GamePhase.Lobby;
|
||||
_manager?.uiSubsystem?.ShowToast("Map fetch failed. Returning to lobby.");
|
||||
LeaveLobby();
|
||||
}
|
||||
|
||||
private void HandlePositionBroadcast(PositionBroadcast broadcast)
|
||||
{
|
||||
if (broadcast == null) return;
|
||||
_manager?.mapSubsystem?.UpdatePlayerAvatars(_gameClient.PlayerPositions, _gameClient.ClientUuid);
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
private void SyncPlayersFromLobby()
|
||||
{
|
||||
var lobby = _gameClient.CurrentLobbyState;
|
||||
if (lobby?.Players != null)
|
||||
State.Players = lobby.Players;
|
||||
}
|
||||
|
||||
private void Toast(string message)
|
||||
{
|
||||
State.ToastMessage = message;
|
||||
State.ToastExpiry = UnityEngine.Time.time + 4f;
|
||||
}
|
||||
|
||||
// ── Send helpers ──────────────────────────────────────────────────────
|
||||
|
||||
public void CreateLobby(double lat, double lon, double radius = 500, int impostorCount = 1, int taskCount = 5, GameSettingsOverrides settings = null)
|
||||
{
|
||||
_gameClient.CreateLobby(new Position(lat, lon), impostorCount, taskCount, null, radius, settings);
|
||||
}
|
||||
|
||||
public void JoinLobby(string joinCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
_gameClient.JoinLobby(joinCode);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogError("Error joining lobby: " + ex.Message);
|
||||
}
|
||||
try { _gameClient.JoinLobby(joinCode); }
|
||||
catch (System.Exception ex) { Debug.LogError("JoinLobby error: " + ex.Message); }
|
||||
}
|
||||
|
||||
public void LeaveLobby()
|
||||
{
|
||||
_gameClient.Disconnect();
|
||||
Application.Quit();
|
||||
_gameClient.LeaveLobby();
|
||||
State.Phase = GamePhase.Lobby;
|
||||
SceneManager.LoadScene(_manager?.firstMenuScene ?? "main menu asi idk lol", LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
public void StartGame()
|
||||
{
|
||||
_gameClient.StartGame();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
328
Assets/GameManager/GameManager_Tasks.cs
Normal file
328
Assets/GameManager/GameManager_Tasks.cs
Normal file
@@ -0,0 +1,328 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using GeoSus.Client;
|
||||
|
||||
namespace Subsystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Round-robin task-to-minigame assignment, proximity detection, additive scene launch.
|
||||
/// </summary>
|
||||
public class GameManager_Tasks
|
||||
{
|
||||
private class TaskEntry
|
||||
{
|
||||
public GeoSus.Client.GameTask ServerTask;
|
||||
public string MinigameScene;
|
||||
public bool Completed;
|
||||
}
|
||||
|
||||
private GameClient _gameClient;
|
||||
private string[] _minigameScenes;
|
||||
private MonoBehaviour _host; // GameManager MonoBehaviour for coroutines
|
||||
private List<TaskEntry> _tasks = new List<TaskEntry>();
|
||||
private bool _minigameOpen;
|
||||
private string _loadedMinigameScene;
|
||||
private Camera _hostCameraSuspended;
|
||||
private GameObject _hostInGameHudHidden;
|
||||
|
||||
// Proximity state (checked every frame in UpdateProximity)
|
||||
public GeoSus.Client.GameTask NearbyTask { get; private set; }
|
||||
|
||||
// P13b: per-check distances pulled from the server-snapshotted lobby
|
||||
// settings (null-fallback to 5m matches the old hardcoded behavior).
|
||||
// Different actions use different fields so a host can tune e.g. a
|
||||
// long-range "spotter" task radius without also widening kill range.
|
||||
private const float ProximityRadiusFallback = 5f;
|
||||
|
||||
public GameManager_Tasks(GameClient gameClient, string[] minigameScenes, MonoBehaviour host)
|
||||
{
|
||||
_gameClient = gameClient;
|
||||
_minigameScenes = minigameScenes ?? new string[0];
|
||||
_host = host;
|
||||
}
|
||||
|
||||
/// <summary>Called by Network subsystem when RoleAssigned fires.</summary>
|
||||
public void Initialize(List<GeoSus.Client.GameTask> serverTasks)
|
||||
{
|
||||
_tasks.Clear();
|
||||
if (_minigameScenes.Length == 0) return;
|
||||
|
||||
for (int i = 0; i < serverTasks.Count; i++)
|
||||
{
|
||||
_tasks.Add(new TaskEntry
|
||||
{
|
||||
ServerTask = serverTasks[i],
|
||||
MinigameScene = _minigameScenes[i % _minigameScenes.Length],
|
||||
Completed = false
|
||||
});
|
||||
}
|
||||
|
||||
// Create map markers
|
||||
GameManager.Instance?.mapSubsystem?.CreateTaskMarkers(serverTasks);
|
||||
Debug.Log($"[Tasks] Initialized {_tasks.Count} tasks.");
|
||||
}
|
||||
|
||||
/// <summary>Called every frame from GameManager.Update().</summary>
|
||||
public void UpdateProximity()
|
||||
{
|
||||
if (_minigameOpen) return;
|
||||
|
||||
// P13b: distances now come from the per-lobby settings snapshot
|
||||
// instead of one hardcoded 5m radius for everything. ?? fallback
|
||||
// matches the old behavior when running against an old server.
|
||||
var state = GameManager.Instance?.networkSubsystem?.State;
|
||||
var settings = state?.Settings;
|
||||
double taskDist = settings?.TaskStartDistanceM ?? ProximityRadiusFallback;
|
||||
double reportDist = settings?.ReportDistanceM ?? ProximityRadiusFallback;
|
||||
double emergencyDist = settings?.EmergencyMeetingCallRadiusM?? ProximityRadiusFallback;
|
||||
double killDist = settings?.KillDistanceM ?? ProximityRadiusFallback;
|
||||
|
||||
NearbyTask = null;
|
||||
var myPos = _gameClient.MyPosition;
|
||||
if (myPos.Lat == 0 && myPos.Lon == 0) return;
|
||||
|
||||
foreach (var entry in _tasks)
|
||||
{
|
||||
if (entry.Completed) continue;
|
||||
double dist = myPos.DistanceTo(entry.ServerTask.Location);
|
||||
if (dist <= taskDist)
|
||||
{
|
||||
NearbyTask = entry.ServerTask;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Drive the action button in UI
|
||||
var ui = GameManager.Instance?.uiSubsystem;
|
||||
if (ui == null || ui.IsPlayerDead) return;
|
||||
|
||||
bool isImpostor = _gameClient.MyRole == GeoSus.Client.PlayerRole.Impostor;
|
||||
|
||||
if (!isImpostor && NearbyTask != null)
|
||||
{
|
||||
ui.SetActionButton("USE", true, () => GameManager.Instance?.PerformAction());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check body proximity
|
||||
if (!ui.IsCommsBlackout)
|
||||
{
|
||||
var body = _gameClient.FindNearbyBody(reportDist);
|
||||
if (body != null)
|
||||
{
|
||||
ui.SetActionButton("REPORT", true, () => GameManager.Instance?.PerformAction());
|
||||
return;
|
||||
}
|
||||
|
||||
// Emergency meeting proximity
|
||||
if (_gameClient.CurrentLobbyState?.MapData != null)
|
||||
{
|
||||
double dist = myPos.DistanceTo(_gameClient.CurrentLobbyState.MapData.Center);
|
||||
if (dist <= emergencyDist)
|
||||
{
|
||||
ui.SetActionButton("EMERGENCY", true, () => GameManager.Instance?.PerformAction());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Impostor kill
|
||||
if (isImpostor)
|
||||
{
|
||||
var target = _gameClient.FindNearbyPlayer(killDist);
|
||||
if (!string.IsNullOrEmpty(target))
|
||||
{
|
||||
ui.SetActionButton("KILL", true, () => GameManager.Instance?.PerformAction());
|
||||
// Hide sabotage menu while a kill is on offer (cleaner HUD).
|
||||
ui.SetSabotageMenuVisible(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing nearby
|
||||
ui.SetActionButton("", false);
|
||||
|
||||
// P13g: persistent sabotage menu for impostors when no proximity
|
||||
// action is on offer. Hidden when state isn't suitable - dead,
|
||||
// not-impostor, in meeting, sabotage already active, or comms
|
||||
// blackout (the impostor's own sabotage triggers a UI lock).
|
||||
bool inPlayingPhase = state != null && state.Phase == GeoSus.Client.GamePhase.Playing;
|
||||
bool sabotageActive = state?.ActiveSabotage != null;
|
||||
bool showSabMenu = isImpostor && !ui.IsPlayerDead && inPlayingPhase &&
|
||||
!sabotageActive && !ui.IsCommsBlackout;
|
||||
ui.SetSabotageMenuVisible(showSabMenu);
|
||||
}
|
||||
|
||||
/// <summary>Called externally (e.g., GameManager.PerformAction) to launch the nearby task.</summary>
|
||||
public void TriggerNearbyTask()
|
||||
{
|
||||
OnUsePressed();
|
||||
}
|
||||
|
||||
private void OnUsePressed()
|
||||
{
|
||||
if (NearbyTask == null || _minigameOpen) return;
|
||||
var entry = _tasks.Find(t => t.ServerTask.TaskId == NearbyTask.TaskId);
|
||||
if (entry != null) _host.StartCoroutine(LaunchMinigame(entry));
|
||||
}
|
||||
|
||||
private IEnumerator LaunchMinigame(TaskEntry entry)
|
||||
{
|
||||
_minigameOpen = true;
|
||||
Debug.Log($"[Tasks] Launching minigame '{entry.MinigameScene}' for task '{entry.ServerTask.Name}'");
|
||||
|
||||
// Validate that the scene name resolves to a build-included scene.
|
||||
// LoadSceneAsync silently returns null when the scene name doesn't
|
||||
// match (case-sensitive) or isn't in EditorBuildSettings, which
|
||||
// leaves the action button looking dead from the player's POV.
|
||||
if (string.IsNullOrEmpty(entry.MinigameScene) ||
|
||||
!Application.CanStreamedLevelBeLoaded(entry.MinigameScene))
|
||||
{
|
||||
Debug.LogError($"[Tasks] Minigame scene '{entry.MinigameScene}' is not loadable. " +
|
||||
$"Check the scene name (case-sensitive) and that it's enabled in Build Settings.");
|
||||
GameManager.Instance?.uiSubsystem?.ShowToast(
|
||||
$"Task scene missing: {entry.MinigameScene}");
|
||||
_minigameOpen = false;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Inform server that task started
|
||||
_gameClient.Send(new TaskStart { TaskId = entry.ServerTask.TaskId });
|
||||
|
||||
// Disable the host scene's main camera while the minigame is up.
|
||||
// With both cameras enabled the minigame's UI/3D content would
|
||||
// fight the host's map camera for screen space, and what gets
|
||||
// drawn depends on Camera.depth which isn't guaranteed across
|
||||
// scenes. Restored in FinishMinigame.
|
||||
_hostCameraSuspended = Camera.main;
|
||||
if (_hostCameraSuspended != null) _hostCameraSuspended.enabled = false;
|
||||
|
||||
// Hide the persistent InGame HUD canvas (if present). It lives
|
||||
// in Client.unity and renders Screen Space - Overlay so it would
|
||||
// otherwise stack on top of the minigame's UI regardless of
|
||||
// which scene is active. SetActive(false) is reversible.
|
||||
_hostInGameHudHidden = GameObject.Find("InGame");
|
||||
if (_hostInGameHudHidden != null && _hostInGameHudHidden.activeSelf)
|
||||
_hostInGameHudHidden.SetActive(false);
|
||||
else
|
||||
_hostInGameHudHidden = null; // nothing to restore
|
||||
|
||||
var op = SceneManager.LoadSceneAsync(entry.MinigameScene, LoadSceneMode.Additive);
|
||||
if (op == null)
|
||||
{
|
||||
Debug.LogError($"[Tasks] LoadSceneAsync returned null for '{entry.MinigameScene}'.");
|
||||
GameManager.Instance?.uiSubsystem?.ShowToast(
|
||||
$"Task scene failed to load: {entry.MinigameScene}");
|
||||
if (_hostCameraSuspended != null) { _hostCameraSuspended.enabled = true; _hostCameraSuspended = null; }
|
||||
_minigameOpen = false;
|
||||
yield break;
|
||||
}
|
||||
yield return op;
|
||||
|
||||
_loadedMinigameScene = entry.MinigameScene;
|
||||
|
||||
// CRITICAL: switch the active scene to the loaded minigame.
|
||||
// LoadSceneMode.Additive stacks scenes without changing which one
|
||||
// is "active" - and an inactive scene's RenderSettings, ambient
|
||||
// light, and skybox don't drive rendering. The host (Client.unity)
|
||||
// remains active and its lighting context still applies, which
|
||||
// is the root cause of "task opens to white screen": the
|
||||
// minigame's content loads but its visuals don't take over.
|
||||
// Without SetActiveScene, even minigames that ARE wired up
|
||||
// correctly render against the host's lighting and look broken.
|
||||
Scene scene = SceneManager.GetSceneByName(entry.MinigameScene);
|
||||
if (scene.IsValid()) SceneManager.SetActiveScene(scene);
|
||||
|
||||
// Diagnostic: count cameras / canvases / lights in the loaded
|
||||
// scene. If the white screen persists after this fix, the
|
||||
// numbers tell us whether the scene is missing rendering bits
|
||||
// (camera=0, canvas=0) or if the issue is elsewhere.
|
||||
int camCount = 0, canvasCount = 0, lightCount = 0;
|
||||
foreach (var root in scene.GetRootGameObjects())
|
||||
{
|
||||
camCount += root.GetComponentsInChildren<Camera>(true).Length;
|
||||
canvasCount += root.GetComponentsInChildren<Canvas>(true).Length;
|
||||
lightCount += root.GetComponentsInChildren<Light>(true).Length;
|
||||
}
|
||||
Debug.Log($"[Tasks] Loaded '{entry.MinigameScene}': cameras={camCount}, " +
|
||||
$"canvases={canvasCount}, lights={lightCount}, " +
|
||||
$"activeScene={SceneManager.GetActiveScene().name}");
|
||||
|
||||
// Find the ITask component in the newly loaded scene
|
||||
ITask taskComponent = null;
|
||||
foreach (var root in scene.GetRootGameObjects())
|
||||
{
|
||||
taskComponent = root.GetComponentInChildren<ITask>();
|
||||
if (taskComponent != null) break;
|
||||
}
|
||||
|
||||
if (taskComponent == null)
|
||||
{
|
||||
Debug.LogWarning($"[Tasks] No ITask found in '{entry.MinigameScene}'. " +
|
||||
$"Either the minigame's controller script isn't attached to a GameObject in the scene, " +
|
||||
$"or the script doesn't implement ITask. Auto-completing.");
|
||||
yield return FinishMinigame(entry, true);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Set task metadata
|
||||
taskComponent.TaskID = entry.ServerTask.TaskId;
|
||||
taskComponent.TaskName = entry.ServerTask.Name;
|
||||
taskComponent.TaskLocation = (entry.ServerTask.Location.Lat, entry.ServerTask.Location.Lon);
|
||||
|
||||
bool done = false;
|
||||
taskComponent.Initialize(t => { done = true; });
|
||||
|
||||
// Wait for completion or exit
|
||||
yield return new WaitUntil(() => done);
|
||||
|
||||
yield return FinishMinigame(entry, done);
|
||||
}
|
||||
|
||||
private IEnumerator FinishMinigame(TaskEntry entry, bool completed)
|
||||
{
|
||||
if (completed)
|
||||
{
|
||||
entry.Completed = true;
|
||||
_gameClient.CompleteTask(entry.ServerTask.TaskId);
|
||||
Debug.Log($"[Tasks] Task '{entry.ServerTask.Name}' completed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"[Tasks] Task '{entry.ServerTask.Name}' exited without completion.");
|
||||
}
|
||||
|
||||
// Unload minigame scene. Switch the active scene back to the
|
||||
// host BEFORE the unload so we don't end up with no active
|
||||
// scene mid-frame (Unity will complain and lighting flickers).
|
||||
if (!string.IsNullOrEmpty(_loadedMinigameScene))
|
||||
{
|
||||
var hostScene = SceneManager.GetSceneByName("Client");
|
||||
if (hostScene.IsValid()) SceneManager.SetActiveScene(hostScene);
|
||||
|
||||
var unload = SceneManager.UnloadSceneAsync(_loadedMinigameScene);
|
||||
yield return unload;
|
||||
_loadedMinigameScene = null;
|
||||
}
|
||||
|
||||
// Re-enable the host camera that was suspended during the minigame.
|
||||
if (_hostCameraSuspended != null)
|
||||
{
|
||||
_hostCameraSuspended.enabled = true;
|
||||
_hostCameraSuspended = null;
|
||||
}
|
||||
|
||||
// Re-show the InGame HUD canvas hidden at minigame entry.
|
||||
if (_hostInGameHudHidden != null)
|
||||
{
|
||||
_hostInGameHudHidden.SetActive(true);
|
||||
_hostInGameHudHidden = null;
|
||||
}
|
||||
|
||||
_minigameOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/GameManager/GameManager_Tasks.cs.meta
Normal file
11
Assets/GameManager/GameManager_Tasks.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27a123dbda9eef8ba4815c0c0d30b6fb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,71 +1,934 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Subsystems;
|
||||
using GeoSus.Client;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
|
||||
namespace Subsystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads from GameManager_Network.State (the authoritative GameState) and drives
|
||||
/// all in-game canvas panels. No business logic lives here.
|
||||
/// </summary>
|
||||
public class GameManager_UI
|
||||
{
|
||||
private GameClient _gameClient;
|
||||
private Canvas _CreateJoinLobby;
|
||||
private Canvas _InLobby;
|
||||
private Canvas _LoadingScreen;
|
||||
private Canvas _GameScreen;
|
||||
public GameManager_UI(GameClient gameClient, Canvas CreateJoinLobby, Canvas InLobby, Canvas LoadingScreen, Canvas GameScreen)
|
||||
private GameState _state => GameManager.Instance?.networkSubsystem?.State;
|
||||
|
||||
// ── Canvas refs (wired by BindClientScene from Client.unity) ──────────
|
||||
public Canvas ClientCreateJoinLobby;
|
||||
public Canvas ClientInLobby;
|
||||
public Canvas ClientLoadingScreen;
|
||||
public Canvas ClientGameScreen;
|
||||
|
||||
// ── HUD element refs (resolved once in BindClientScene) ───────────────
|
||||
private TMP_Text _roleText;
|
||||
private TMP_Text _taskListText;
|
||||
private TMP_Text _taskProgressText;
|
||||
private Button _actionButton;
|
||||
private TMP_Text _actionButtonText;
|
||||
private TMP_Text _killCooldownText;
|
||||
private GameObject _sabotagePanel;
|
||||
private TMP_Text _sabotageTimerText;
|
||||
private GameObject _meetingPanel;
|
||||
private TMP_Text _meetingHeader;
|
||||
private TMP_Text _meetingPhaseLabel;
|
||||
private TMP_Text _meetingPhaseCountdown;
|
||||
private Image _meetingPhaseProgressBar;
|
||||
private TMP_Text _myVoteIndicator;
|
||||
private GameObject _meetingScrollGO;
|
||||
private Transform _meetingScrollContent;
|
||||
private TMP_Text _meetingFallbackText;
|
||||
private GameObject _voteResultPanel;
|
||||
private TMP_Text _voteResultText;
|
||||
private Button _skipButton;
|
||||
private GameObject _gameEndPanel;
|
||||
private TMP_Text _gameEndText;
|
||||
private RectTransform _returnToLobbyBtn;
|
||||
private TMP_Text _toastText;
|
||||
private GameObject _toastGO;
|
||||
private GameObject _reconnectOverlay;
|
||||
|
||||
// ── Internal state ────────────────────────────────────────────────────
|
||||
private bool _isDead;
|
||||
private bool _commsBlackout;
|
||||
private DateTime _sabotageMeltdownDeadline;
|
||||
private bool _sabotageTimerActive;
|
||||
private volatile bool _lobbyDirty;
|
||||
|
||||
// Meeting vote-row references rebuilt each meeting
|
||||
private readonly List<GameObject> _voteRows = new List<GameObject>();
|
||||
private string _pendingVoteResultDisplay; // shown after voting
|
||||
private Coroutine _meetingCloseCoroutine; // tracked so phase changes can cancel it
|
||||
|
||||
public GameManager_UI(GameClient gameClient) { _gameClient = gameClient; }
|
||||
|
||||
public void NotifyLobbyChanged() => _lobbyDirty = true;
|
||||
public bool IsCommsBlackout => _commsBlackout;
|
||||
public bool IsPlayerDead => _isDead;
|
||||
|
||||
// ── Scene binding ─────────────────────────────────────────────────────
|
||||
|
||||
public void BindClientScene(Canvas createJoin, Canvas inLobby, Canvas loading, Canvas game)
|
||||
{
|
||||
_gameClient = gameClient;
|
||||
_CreateJoinLobby = CreateJoinLobby;
|
||||
_LoadingScreen = LoadingScreen;
|
||||
_GameScreen = GameScreen;
|
||||
_InLobby = InLobby;
|
||||
_CreateJoinLobby.enabled = true;
|
||||
_InLobby.enabled = false;
|
||||
_GameScreen.enabled = false;
|
||||
_LoadingScreen.enabled = false;
|
||||
ClientCreateJoinLobby = createJoin;
|
||||
ClientInLobby = inLobby;
|
||||
ClientLoadingScreen = loading;
|
||||
ClientGameScreen = game;
|
||||
|
||||
foreach (var c in new[] { createJoin, inLobby, loading, game })
|
||||
EnsureCanvasReady(c);
|
||||
|
||||
if (createJoin) createJoin.gameObject.SetActive(false);
|
||||
if (inLobby) inLobby.gameObject.SetActive(false);
|
||||
if (loading) loading.gameObject.SetActive(false);
|
||||
if (game) game.gameObject.SetActive(false);
|
||||
|
||||
if (game == null) return;
|
||||
var t = game.transform;
|
||||
|
||||
_roleText = FindTMP(t, "Role");
|
||||
_taskListText = FindTMP(t, "TaskList");
|
||||
_taskProgressText = FindTMP(t, "TaskProgress");
|
||||
_killCooldownText = FindTMP(t, "KillCooldown");
|
||||
_sabotageTimerText = FindTMP(t, "SabotageTimer");
|
||||
_gameEndText = FindTMP(t, "GameEndText");
|
||||
_toastText = FindTMP(t, "Toast");
|
||||
_meetingHeader = FindTMP(t, "MeetingHeader");
|
||||
_meetingPhaseLabel = FindTMP(t, "MeetingPhaseLabel");
|
||||
_meetingPhaseCountdown = FindTMP(t, "MeetingPhaseCountdown");
|
||||
_myVoteIndicator = FindTMP(t, "MyVoteIndicator");
|
||||
_meetingFallbackText = FindTMP(t, "MeetingPlayerList");
|
||||
_voteResultText = FindTMP(t, "VoteResult");
|
||||
_meetingScrollContent = FindTransform(t, "MeetingContent");
|
||||
_meetingScrollGO = FindTransformGO(t, "_MeetingScroll");
|
||||
|
||||
var progressBarGO = FindTransformGO(t, "MeetingPhaseProgressBar");
|
||||
if (progressBarGO != null) _meetingPhaseProgressBar = progressBarGO.GetComponent<Image>();
|
||||
|
||||
var skipGO = FindTransformGO(t, "SkipButton");
|
||||
if (skipGO != null) _skipButton = skipGO.GetComponent<Button>();
|
||||
|
||||
var actionGO = t.Find("ActionButton");
|
||||
if (actionGO != null)
|
||||
{
|
||||
_actionButton = actionGO.GetComponent<Button>();
|
||||
_actionButtonText = actionGO.GetComponentInChildren<TMP_Text>();
|
||||
}
|
||||
|
||||
_sabotagePanel = t.Find("SabotagePanel")?.gameObject;
|
||||
_meetingPanel = t.Find("MeetingPanel")?.gameObject;
|
||||
_gameEndPanel = t.Find("GameEndPanel")?.gameObject;
|
||||
_voteResultPanel = FindTransformGO(t, "VoteResultPanel");
|
||||
_toastGO = FindTransformGO(t, "Toast");
|
||||
_reconnectOverlay = FindTransformGO(t, "ReconnectOverlay");
|
||||
|
||||
var retBtn = FindTransform(t, "ReturnToLobbyButton");
|
||||
if (retBtn != null) _returnToLobbyBtn = retBtn as RectTransform;
|
||||
|
||||
if (_meetingPanel) _meetingPanel.SetActive(false);
|
||||
if (_gameEndPanel) _gameEndPanel.SetActive(false);
|
||||
if (_voteResultPanel) _voteResultPanel.SetActive(false);
|
||||
if (_toastGO) _toastGO.SetActive(false);
|
||||
if (_reconnectOverlay) _reconnectOverlay.SetActive(false);
|
||||
}
|
||||
|
||||
// ── Update (called every frame from GameManager.Update) ───────────────
|
||||
|
||||
public void UpdateLobbyUI()
|
||||
{
|
||||
if (_gameClient.CurrentLobbyState == null)
|
||||
var lobbyState = _gameClient.CurrentLobbyState;
|
||||
if (lobbyState == null) return;
|
||||
|
||||
if (_lobbyDirty)
|
||||
{
|
||||
_CreateJoinLobby.enabled = true;
|
||||
_InLobby.enabled = false;
|
||||
_GameScreen.enabled = false;
|
||||
_LoadingScreen.enabled = false;
|
||||
return;
|
||||
_lobbyDirty = false;
|
||||
LobbyDisplayUI.RefreshAll(lobbyState);
|
||||
}
|
||||
else if (_gameClient.CurrentLobbyState.Phase == GamePhase.Loading)
|
||||
|
||||
if (ClientGameScreen == null) return;
|
||||
|
||||
switch (lobbyState.Phase)
|
||||
{
|
||||
_CreateJoinLobby.enabled = false;
|
||||
_InLobby.enabled = false;
|
||||
_GameScreen.enabled = false;
|
||||
_LoadingScreen.enabled = true;
|
||||
return;
|
||||
case GamePhase.Loading:
|
||||
SetCanvases(false, false, true, false);
|
||||
break;
|
||||
case GamePhase.Lobby:
|
||||
SetCanvases(false, true, false, false);
|
||||
break;
|
||||
case GamePhase.Playing:
|
||||
case GamePhase.Meeting:
|
||||
case GamePhase.Voting:
|
||||
SetCanvases(false, false, false, true);
|
||||
UpdateGameHUD();
|
||||
break;
|
||||
case GamePhase.Ended:
|
||||
SetCanvases(false, false, false, true);
|
||||
break;
|
||||
}
|
||||
else if (_gameClient.CurrentLobbyState.Phase == GamePhase.Lobby)
|
||||
|
||||
TickToast();
|
||||
}
|
||||
|
||||
// ── Game HUD tick ─────────────────────────────────────────────────────
|
||||
|
||||
private void UpdateGameHUD()
|
||||
{
|
||||
var s = _state;
|
||||
if (s == null) return;
|
||||
|
||||
// Role
|
||||
if (_roleText != null)
|
||||
{
|
||||
_InLobby.enabled = true;
|
||||
_CreateJoinLobby.enabled = false;
|
||||
var playerList = _InLobby.transform.Find("PlayerList").GetComponent<TMPro.TMP_Text>();
|
||||
playerList.text = "";
|
||||
foreach (var player in _gameClient.CurrentLobbyState.Players)
|
||||
string ghostSuffix = s.IsDead ? " (GHOST)" : "";
|
||||
_roleText.text = $"{s.MyRole?.ToString() ?? "?"}{ghostSuffix}";
|
||||
_roleText.color = s.MyRole == PlayerRole.Impostor ? new Color(0.9f,0.2f,0.2f) : new Color(0.2f,0.8f,1f);
|
||||
}
|
||||
|
||||
// Task list with checkmarks
|
||||
if (_taskListText != null)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var task in s.MyTasks)
|
||||
{
|
||||
playerList.text += player.DisplayName + "\n";
|
||||
bool done = s.MyCompletedTaskIds.Contains(task.TaskId);
|
||||
string mark = done ? "<color=#2DB84B>✓</color>" : "○";
|
||||
sb.AppendLine($"{mark} {task.Name}");
|
||||
}
|
||||
_InLobby.transform.Find("JoinCode").GetComponent<TMPro.TMP_Text>().text = _gameClient.CurrentLobbyState.JoinCode;
|
||||
return;
|
||||
_taskListText.text = sb.ToString();
|
||||
}
|
||||
else if (_gameClient.CurrentLobbyState.Phase == GamePhase.Playing)
|
||||
|
||||
// Global task progress
|
||||
if (_taskProgressText != null && s.TotalRequired > 0)
|
||||
_taskProgressText.text = $"Tasks: {s.TotalCompleted}/{s.TotalRequired}";
|
||||
|
||||
// Kill cooldown
|
||||
if (_killCooldownText != null)
|
||||
{
|
||||
_CreateJoinLobby.enabled = false;
|
||||
_InLobby.enabled = false;
|
||||
_GameScreen.enabled = true;
|
||||
_LoadingScreen.enabled = false;
|
||||
_GameScreen.transform.Find("Role").GetComponent<TMPro.TMP_Text>().text = _gameClient.MyRole.ToString() ;
|
||||
bool show = s.KillCooldownRemaining > 0;
|
||||
_killCooldownText.gameObject.SetActive(show);
|
||||
if (show) _killCooldownText.text = $"Kill: {Mathf.CeilToInt(s.KillCooldownRemaining)}s";
|
||||
}
|
||||
|
||||
// Sabotage banner - meltdown countdown plus simultaneous-repair coaching
|
||||
if (_sabotageTimerActive && _sabotageTimerText != null)
|
||||
{
|
||||
double remaining = (_sabotageMeltdownDeadline - DateTime.UtcNow).TotalSeconds;
|
||||
string head = remaining > 0 ? $"⚠ MELTDOWN: {remaining:F0}s" : "⚠ MELTDOWN!";
|
||||
|
||||
// For multi-station sabotages, surface how many of the required
|
||||
// simultaneous repair stations are currently active. This is
|
||||
// what makes "you're alone, you need a partner" obvious.
|
||||
int required = s.ActiveSabotage?.RequiredSimultaneousRepairs ?? 0;
|
||||
if (required > 1)
|
||||
{
|
||||
int active = s.ActiveRepairs.Count;
|
||||
head += $" <size=32>{active}/{required} stations active</size>";
|
||||
}
|
||||
_sabotageTimerText.text = head;
|
||||
}
|
||||
|
||||
// Keep meeting sub-phase strip, countdown, vote gating, tallies and
|
||||
// my-vote indicator fresh each frame.
|
||||
UpdateMeetingPhaseStrip();
|
||||
}
|
||||
|
||||
// ── Kill cooldown helper (called from GameManager) ────────────────────
|
||||
|
||||
// ── Reconnect overlay ─────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Show a full-screen "Reconnecting..." overlay. Call when the socket
|
||||
/// drops mid-game; the server keeps the player slot for ~60s before
|
||||
/// removing them so a brief disconnect is recoverable.
|
||||
/// </summary>
|
||||
public void ShowReconnecting()
|
||||
{
|
||||
if (_reconnectOverlay) _reconnectOverlay.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide the reconnect overlay. Call from OnConnected once the socket
|
||||
/// is healthy again.
|
||||
/// </summary>
|
||||
public void HideReconnecting()
|
||||
{
|
||||
if (_reconnectOverlay) _reconnectOverlay.SetActive(false);
|
||||
}
|
||||
|
||||
public void SetKillCooldownText(string text)
|
||||
{
|
||||
if (_killCooldownText == null) return;
|
||||
bool show = !string.IsNullOrEmpty(text);
|
||||
_killCooldownText.gameObject.SetActive(show);
|
||||
if (show) _killCooldownText.text = text;
|
||||
}
|
||||
|
||||
public void UpdateTaskProgress(int completed, int total)
|
||||
{
|
||||
if (_taskProgressText != null)
|
||||
_taskProgressText.text = $"Tasks: {completed}/{total}";
|
||||
}
|
||||
|
||||
// ── Action button ─────────────────────────────────────────────────────
|
||||
|
||||
public void SetActionButton(string label, bool visible, UnityEngine.Events.UnityAction onClick = null)
|
||||
{
|
||||
if (_actionButton == null) return;
|
||||
_actionButton.gameObject.SetActive(visible);
|
||||
if (_actionButtonText != null) _actionButtonText.text = label;
|
||||
if (onClick != null)
|
||||
{
|
||||
_actionButton.onClick.RemoveAllListeners();
|
||||
_actionButton.onClick.AddListener(onClick);
|
||||
}
|
||||
}
|
||||
|
||||
// ── P13g: Impostor sabotage menu ──────────────────────────────────────
|
||||
// The audit found that the production HUD never had an impostor
|
||||
// sabotage trigger - GameManager.StartSabotage exists, the wire path
|
||||
// is intact (StartSabotage -> server -> SabotageStarted broadcast +
|
||||
// station markers), but no UI ever called it. So sabotages literally
|
||||
// never fired in production. This menu fixes that gap with a runtime-
|
||||
// built two-button overlay (no scene file change, no prefab needed).
|
||||
|
||||
private GameObject _sabotageMenuRoot;
|
||||
private Button _sabotageBlackoutBtn;
|
||||
private Button _sabotageMeltdownBtn;
|
||||
|
||||
private void EnsureSabotageMenu()
|
||||
{
|
||||
if (_sabotageMenuRoot != null || ClientGameScreen == null) return;
|
||||
|
||||
var canvasRT = ClientGameScreen.transform as RectTransform;
|
||||
if (canvasRT == null) return;
|
||||
|
||||
// Root container - top-right corner, vertical stack.
|
||||
_sabotageMenuRoot = new GameObject("ImpostorSabotageMenu", typeof(RectTransform), typeof(CanvasRenderer));
|
||||
var rootRT = _sabotageMenuRoot.GetComponent<RectTransform>();
|
||||
rootRT.SetParent(canvasRT, worldPositionStays: false);
|
||||
rootRT.anchorMin = new Vector2(1, 1);
|
||||
rootRT.anchorMax = new Vector2(1, 1);
|
||||
rootRT.pivot = new Vector2(1, 1);
|
||||
rootRT.anchoredPosition = new Vector2(-24, -180); // below the top-right safe-area
|
||||
rootRT.sizeDelta = new Vector2(360, 240);
|
||||
|
||||
_sabotageBlackoutBtn = BuildSabotageOption(rootRT, "📡 BLACKOUT",
|
||||
new Color(0.20f, 0.55f, 1.0f), 0, () => GameManager.Instance?.StartSabotage(0));
|
||||
|
||||
_sabotageMeltdownBtn = BuildSabotageOption(rootRT, "☢️ MELTDOWN",
|
||||
new Color(1.0f, 0.30f, 0.30f), 1, () => GameManager.Instance?.StartSabotage(1));
|
||||
|
||||
_sabotageMenuRoot.SetActive(false);
|
||||
}
|
||||
|
||||
private static Button BuildSabotageOption(RectTransform parent, string label, Color tint, int slot, UnityEngine.Events.UnityAction onClick)
|
||||
{
|
||||
// Each button: 360w x 110h, stacked vertically with 10px gap.
|
||||
var go = new GameObject($"SabBtn_{slot}", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button));
|
||||
var rt = go.GetComponent<RectTransform>();
|
||||
rt.SetParent(parent, worldPositionStays: false);
|
||||
rt.anchorMin = new Vector2(0, 1);
|
||||
rt.anchorMax = new Vector2(1, 1);
|
||||
rt.pivot = new Vector2(0.5f, 1);
|
||||
rt.anchoredPosition = new Vector2(0, -slot * 120);
|
||||
rt.sizeDelta = new Vector2(0, 110);
|
||||
|
||||
var img = go.GetComponent<Image>();
|
||||
img.color = new Color(tint.r * 0.4f, tint.g * 0.4f, tint.b * 0.4f, 0.92f);
|
||||
|
||||
// Border via outline component
|
||||
var outline = go.AddComponent<Outline>();
|
||||
outline.effectColor = tint;
|
||||
outline.effectDistance = new Vector2(2, -2);
|
||||
|
||||
// Text child
|
||||
var txtGO = new GameObject("Label", typeof(RectTransform));
|
||||
var txtRT = txtGO.GetComponent<RectTransform>();
|
||||
txtRT.SetParent(rt, worldPositionStays: false);
|
||||
txtRT.anchorMin = Vector2.zero;
|
||||
txtRT.anchorMax = Vector2.one;
|
||||
txtRT.offsetMin = Vector2.zero;
|
||||
txtRT.offsetMax = Vector2.zero;
|
||||
var tmp = txtGO.AddComponent<TextMeshProUGUI>();
|
||||
tmp.text = label;
|
||||
tmp.alignment = TextAlignmentOptions.Center;
|
||||
tmp.fontSize = 36;
|
||||
tmp.color = Color.white;
|
||||
tmp.fontStyle = FontStyles.Bold;
|
||||
|
||||
var btn = go.GetComponent<Button>();
|
||||
btn.onClick.AddListener(onClick);
|
||||
return btn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// P13g: show the impostor sabotage menu when the local player is
|
||||
/// alive impostor in the Playing phase with no active sabotage and
|
||||
/// not in a meeting. Driven from GameManager_Tasks.UpdateProximity.
|
||||
/// </summary>
|
||||
public void SetSabotageMenuVisible(bool visible)
|
||||
{
|
||||
if (visible) EnsureSabotageMenu();
|
||||
if (_sabotageMenuRoot != null && _sabotageMenuRoot.activeSelf != visible)
|
||||
_sabotageMenuRoot.SetActive(visible);
|
||||
}
|
||||
|
||||
// ── Player state ──────────────────────────────────────────────────────
|
||||
|
||||
public void OnLocalPlayerDied()
|
||||
{
|
||||
_isDead = true;
|
||||
if (_state != null) _state.IsDead = true;
|
||||
}
|
||||
|
||||
// ── Meeting ───────────────────────────────────────────────────────────
|
||||
|
||||
public void ShowMeetingAlert()
|
||||
{
|
||||
ShowToast("⚠ Meeting called! Head to the meeting point.");
|
||||
}
|
||||
|
||||
public void ShowMeetingPanel(List<PlayerInfo> players, MeetingStartedPayload payload)
|
||||
{
|
||||
if (_meetingPanel == null) return;
|
||||
_meetingPanel.SetActive(true);
|
||||
|
||||
if (_meetingHeader != null)
|
||||
_meetingHeader.text = payload.Type == MeetingType.BodyReport ? "BODY REPORTED!" : "EMERGENCY MEETING!";
|
||||
|
||||
// Make sure the result subpanel is hidden at start of a fresh meeting,
|
||||
// and the scroll list is visible (results phase will swap them).
|
||||
if (_voteResultPanel) _voteResultPanel.SetActive(false);
|
||||
if (_meetingScrollGO) _meetingScrollGO.SetActive(true);
|
||||
if (_myVoteIndicator) _myVoteIndicator.text = "";
|
||||
|
||||
BuildMeetingVoteRows(players);
|
||||
UpdateMeetingPhaseStrip();
|
||||
}
|
||||
|
||||
private void BuildMeetingVoteRows(List<PlayerInfo> players)
|
||||
{
|
||||
// Clear old rows
|
||||
foreach (var r in _voteRows) if (r) UnityEngine.Object.Destroy(r);
|
||||
_voteRows.Clear();
|
||||
|
||||
if (_meetingScrollContent == null || players == null)
|
||||
{
|
||||
// Fall back to text list
|
||||
if (_meetingFallbackText != null)
|
||||
{
|
||||
_meetingFallbackText.gameObject.SetActive(true);
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var p in players ?? new List<PlayerInfo>())
|
||||
sb.AppendLine($"{p.DisplayName} [{p.State}]");
|
||||
_meetingFallbackText.text = sb.ToString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
string myId = _gameClient.ClientUuid;
|
||||
bool canVote = !_isDead;
|
||||
|
||||
// Dynamic row height: spread the available scroll-area height
|
||||
// across however many players we have. Clamps so rows never get
|
||||
// tinier than legible (small phone, many players -> 80px) or
|
||||
// ridiculously tall (tablet, two players -> 140px).
|
||||
float rowH = ComputeVoteRowHeight(players.Count);
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
bool isMe = player.ClientUuid == myId;
|
||||
bool isAlive = player.State == PlayerState.Alive;
|
||||
var row = BuildVoteRow(player, isMe, isAlive, canVote && isAlive && !isMe, rowH);
|
||||
row.transform.SetParent(_meetingScrollContent, false);
|
||||
_voteRows.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute a per-row height that fills the scroll viewport when there
|
||||
/// are few players, and shrinks (until scrolling kicks in) when there
|
||||
/// are many. Inputs are CanvasScaler reference coordinates, so the
|
||||
/// values are device-independent.
|
||||
/// </summary>
|
||||
private float ComputeVoteRowHeight(int playerCount)
|
||||
{
|
||||
if (playerCount <= 0) return 110f;
|
||||
// The scroll area occupies y=0.18 to y=0.74 of the canvas (per
|
||||
// InGameHUDBuilder.BuildMeetingPanel) and reference height is 1920.
|
||||
const float referenceHeight = 1920f;
|
||||
const float scrollFraction = 0.74f - 0.18f; // 0.56
|
||||
float available = referenceHeight * scrollFraction;
|
||||
float h = available / playerCount;
|
||||
return Mathf.Clamp(h, 80f, 140f);
|
||||
}
|
||||
|
||||
private GameObject BuildVoteRow(PlayerInfo player, bool isMe, bool isAlive, bool canVote, float rowH)
|
||||
{
|
||||
var go = new GameObject($"VoteRow_{player.ClientUuid}");
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.sizeDelta = new Vector2(0, rowH);
|
||||
var le = go.AddComponent<LayoutElement>();
|
||||
le.minHeight = le.preferredHeight = rowH;
|
||||
|
||||
var bg = go.AddComponent<Image>();
|
||||
bg.color = isMe ? new Color(0.12f,0.18f,0.30f) : new Color(0.10f,0.12f,0.20f);
|
||||
|
||||
// Dead overlay
|
||||
if (!isAlive)
|
||||
{
|
||||
bg.color = new Color(0.08f,0.08f,0.10f,0.7f);
|
||||
}
|
||||
|
||||
// Name label - left 50% (was 65%, gave width back to tally + button)
|
||||
var namRT = MakeChild("Name", rt);
|
||||
namRT.anchorMin = new Vector2(0,0); namRT.anchorMax = new Vector2(0.50f,1);
|
||||
namRT.offsetMin = new Vector2(16,6); namRT.offsetMax = new Vector2(0,-6);
|
||||
var namTmp = namRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
namTmp.text = (player.IsOwner ? "👑 " : "") + (player.DisplayName ?? "???");
|
||||
namTmp.fontSize = 36;
|
||||
namTmp.color = !isAlive ? Color.gray : (isMe ? Color.white : new Color(0.73f,0.8f,0.88f));
|
||||
namTmp.fontStyle = isMe ? FontStyles.Bold : FontStyles.Normal;
|
||||
namTmp.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
|
||||
// Tally column - middle 18%, shows live vote count for this player
|
||||
var tallyRT = MakeChild("Tally", rt);
|
||||
tallyRT.anchorMin = new Vector2(0.50f,0); tallyRT.anchorMax = new Vector2(0.66f,1);
|
||||
tallyRT.offsetMin = Vector2.zero; tallyRT.offsetMax = Vector2.zero;
|
||||
var tallyTmp = tallyRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
tallyTmp.text = "";
|
||||
tallyTmp.fontSize = 30;
|
||||
tallyTmp.fontStyle = FontStyles.Bold;
|
||||
tallyTmp.color = new Color(1f,0.72f,0.10f); // C_YELLOW-ish
|
||||
tallyTmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
// Vote button - right 30% (interactability is updated each frame)
|
||||
var voteBtnRT = MakeChild("VoteBtn", rt);
|
||||
voteBtnRT.anchorMin = new Vector2(0.68f,0.10f); voteBtnRT.anchorMax = new Vector2(0.95f,0.90f);
|
||||
var voteBg = voteBtnRT.gameObject.AddComponent<Image>();
|
||||
voteBg.color = canVote ? new Color(0.2f,0.6f,1f) : new Color(0.2f,0.2f,0.2f,0.5f);
|
||||
var voteBtn = voteBtnRT.gameObject.AddComponent<Button>();
|
||||
voteBtn.targetGraphic = voteBg;
|
||||
voteBtn.interactable = canVote;
|
||||
string capturedId = player.ClientUuid;
|
||||
voteBtn.onClick.AddListener(() => GameManager.Instance?.CastVote(capturedId));
|
||||
var voteTxtRT = MakeChild("Txt", voteBtnRT);
|
||||
Stretch(voteTxtRT);
|
||||
var voteTmp = voteTxtRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
voteTmp.text = isAlive ? "VOTE" : "DEAD";
|
||||
voteTmp.fontSize = 28;
|
||||
voteTmp.fontStyle = FontStyles.Bold;
|
||||
voteTmp.color = Color.white;
|
||||
voteTmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
// Voted-by-this-player checkmark (shown when the row's player has cast a vote)
|
||||
var votedRT = MakeChild("VotedTick", rt);
|
||||
votedRT.anchorMin = new Vector2(0.95f,0.20f); votedRT.anchorMax = new Vector2(1f,0.80f);
|
||||
var vtTmp = votedRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
vtTmp.text = "✓"; vtTmp.fontSize = 34;
|
||||
vtTmp.color = new Color(0.18f,0.75f,0.30f); vtTmp.alignment = TextAlignmentOptions.Center;
|
||||
votedRT.gameObject.SetActive(false);
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-frame meeting UI update. Computes the meeting sub-phase from the
|
||||
/// timestamps in MeetingStartedPayload (server doesn't broadcast a
|
||||
/// discrete discussion-end event) and uses it to drive the countdown
|
||||
/// label, progress bar, vote-button interactivity, live tallies, and
|
||||
/// "Your vote: X" indicator.
|
||||
/// </summary>
|
||||
private void UpdateMeetingPhaseStrip()
|
||||
{
|
||||
var s = _state;
|
||||
if (s == null) return;
|
||||
// Only run if we're actually in a meeting; phase Playing skips the work.
|
||||
if (s.Phase != GamePhase.Meeting && s.LastVoteResult == null) return;
|
||||
|
||||
var sub = s.GetMeetingSubPhase();
|
||||
|
||||
// ── Sub-phase label + countdown text + progress bar ───────────────
|
||||
string label;
|
||||
switch (sub)
|
||||
{
|
||||
case MeetingSubPhase.Arrival: label = "ARRIVAL"; break;
|
||||
case MeetingSubPhase.Discussion: label = "DISCUSSION"; break;
|
||||
case MeetingSubPhase.Voting: label = "VOTING"; break;
|
||||
case MeetingSubPhase.Resolved: label = "RESULTS"; break;
|
||||
default: label = ""; break;
|
||||
}
|
||||
if (_meetingPhaseLabel != null) _meetingPhaseLabel.text = label;
|
||||
|
||||
if (s.ActiveMeeting != null && sub != MeetingSubPhase.Resolved)
|
||||
{
|
||||
var deadline = s.GetMeetingSubPhaseDeadline(sub);
|
||||
var remaining = (deadline - DateTime.UtcNow).TotalSeconds;
|
||||
if (remaining < 0) remaining = 0;
|
||||
|
||||
if (_meetingPhaseCountdown != null)
|
||||
{
|
||||
int mins = (int)(remaining / 60);
|
||||
int secs = (int)(remaining % 60);
|
||||
string verb = sub == MeetingSubPhase.Voting ? "Voting ends in"
|
||||
: sub == MeetingSubPhase.Discussion ? "Voting begins in"
|
||||
: "Arrival ends in";
|
||||
_meetingPhaseCountdown.text = $"{verb} {mins}:{secs:D2}";
|
||||
}
|
||||
|
||||
// Progress bar drains over the current sub-phase. The server
|
||||
// doesn't tell us when the meeting started, so we can only
|
||||
// compute a meaningful fill for Discussion (start = arrival
|
||||
// deadline) and Voting (start = discussion end / arrival
|
||||
// deadline). Arrival's start time is unknown here; show full.
|
||||
if (_meetingPhaseProgressBar != null)
|
||||
{
|
||||
if (sub == MeetingSubPhase.Arrival)
|
||||
{
|
||||
_meetingPhaseProgressBar.fillAmount = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
DateTime start = sub == MeetingSubPhase.Discussion
|
||||
? s.ActiveMeeting.ArrivalDeadline
|
||||
: (s.ActiveMeeting.DiscussionEndTime ?? s.ActiveMeeting.ArrivalDeadline);
|
||||
var total = (deadline - start).TotalSeconds;
|
||||
var elapsed = (DateTime.UtcNow - start).TotalSeconds;
|
||||
float fill = total > 0.001
|
||||
? Mathf.Clamp01(1f - (float)(elapsed / total))
|
||||
: 0f;
|
||||
_meetingPhaseProgressBar.fillAmount = fill;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_meetingPhaseCountdown != null) _meetingPhaseCountdown.text = "";
|
||||
if (_meetingPhaseProgressBar != null) _meetingPhaseProgressBar.fillAmount = 0f;
|
||||
}
|
||||
|
||||
// ── Vote button gating + per-row tally / voted-indicator ──────────
|
||||
bool votingOpen = sub == MeetingSubPhase.Voting && !_isDead;
|
||||
bool iAmArrived = s.ActiveMeeting == null
|
||||
|| s.ArrivedPlayerIds.Contains(_gameClient.ClientUuid);
|
||||
|
||||
// Skip button mirrors the same gate
|
||||
if (_skipButton != null) _skipButton.interactable = votingOpen && iAmArrived;
|
||||
|
||||
foreach (var row in _voteRows)
|
||||
{
|
||||
if (row == null) continue;
|
||||
string rowUuid = row.name.Replace("VoteRow_", "");
|
||||
|
||||
// Voted-tick: this row's player has cast a vote
|
||||
var tick = row.transform.Find("VotedTick")?.gameObject;
|
||||
if (tick != null) tick.SetActive(s.VotedPlayerIds.Contains(rowUuid));
|
||||
|
||||
// Tally text: how many votes is this row's player receiving?
|
||||
var tally = row.transform.Find("Tally")?.GetComponent<TMP_Text>();
|
||||
if (tally != null)
|
||||
{
|
||||
s.VoteTallies.TryGetValue(rowUuid, out var count);
|
||||
tally.text = count > 0 ? count.ToString() : "";
|
||||
}
|
||||
|
||||
// Vote button: gate by sub-phase + arrival + alive + not-self
|
||||
var btnGO = row.transform.Find("VoteBtn")?.gameObject;
|
||||
if (btnGO != null)
|
||||
{
|
||||
var btn = btnGO.GetComponent<Button>();
|
||||
var btnImg = btnGO.GetComponent<Image>();
|
||||
var rowPlayer = s.Players?.FirstOrDefault(p => p.ClientUuid == rowUuid);
|
||||
bool isMe = rowUuid == _gameClient.ClientUuid;
|
||||
bool rowAlive = rowPlayer?.State == PlayerState.Alive;
|
||||
|
||||
bool canPress = votingOpen && iAmArrived && rowAlive && !isMe;
|
||||
if (btn != null) btn.interactable = canPress;
|
||||
if (btnImg != null)
|
||||
btnImg.color = canPress ? new Color(0.2f,0.6f,1f)
|
||||
: new Color(0.2f,0.2f,0.2f,0.5f);
|
||||
|
||||
// Mark the row's button if it's the local player's chosen vote
|
||||
if (s.MyVoteTarget != null && s.MyVoteTarget == rowUuid && btnImg != null)
|
||||
btnImg.color = new Color(0.2f,0.75f,0.30f); // green = your vote
|
||||
}
|
||||
}
|
||||
|
||||
// ── My vote indicator strip ───────────────────────────────────────
|
||||
if (_myVoteIndicator != null)
|
||||
{
|
||||
if (s.LastVoteResult != null) _myVoteIndicator.text = "";
|
||||
else if (!iAmArrived) _myVoteIndicator.text = "Travel to the meeting point to vote";
|
||||
else if (sub == MeetingSubPhase.Discussion) _myVoteIndicator.text = "Discussion - voting opens shortly";
|
||||
else if (sub == MeetingSubPhase.Arrival) _myVoteIndicator.text = "Waiting for players to arrive";
|
||||
else if (s.MyVoteTarget == null) _myVoteIndicator.text = "Cast your vote";
|
||||
else if (s.MyVoteTarget == GameState.VoteSkip) _myVoteIndicator.text = "You voted: SKIP";
|
||||
else
|
||||
{
|
||||
var target = s.Players?.FirstOrDefault(p => p.ClientUuid == s.MyVoteTarget);
|
||||
_myVoteIndicator.text = $"You voted for: {target?.DisplayName ?? s.MyVoteTarget}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AppendVoteInstruction()
|
||||
{
|
||||
// no-op - vote instructions are embedded in the row buttons
|
||||
}
|
||||
|
||||
public void ShowVoteResult(VotingClosedPayload payload, List<PlayerInfo> players)
|
||||
{
|
||||
// Swap scroll list out, result subpanel in. They occupy the same
|
||||
// anchor region (0.18-0.74) so the result text replaces the vote
|
||||
// rows rather than overlapping them.
|
||||
if (_meetingScrollGO != null) _meetingScrollGO.SetActive(false);
|
||||
if (_voteResultPanel != null) _voteResultPanel.SetActive(true);
|
||||
// Skip + my-vote strips are no longer relevant once voting ended.
|
||||
if (_skipButton != null) _skipButton.gameObject.SetActive(false);
|
||||
if (_myVoteIndicator != null) _myVoteIndicator.text = "";
|
||||
|
||||
if (_voteResultText != null)
|
||||
{
|
||||
// Build a compact tally summary alongside the headline.
|
||||
var sb = new System.Text.StringBuilder();
|
||||
if (payload.WasTie)
|
||||
sb.AppendLine("⚖ TIE — nobody ejected.");
|
||||
else if (string.IsNullOrEmpty(payload.EjectedPlayerId))
|
||||
sb.AppendLine("Nobody ejected (skip).");
|
||||
else
|
||||
{
|
||||
var ej = players?.Find(p => p.ClientUuid == payload.EjectedPlayerId);
|
||||
sb.AppendLine($"🚪 {ej?.DisplayName ?? payload.EjectedPlayerId} ejected!");
|
||||
}
|
||||
|
||||
if (payload.VoteCounts != null && payload.VoteCounts.Count > 0)
|
||||
{
|
||||
sb.AppendLine();
|
||||
foreach (var kv in payload.VoteCounts.OrderByDescending(p => p.Value))
|
||||
{
|
||||
if (kv.Value <= 0) continue;
|
||||
string name = kv.Key == GameState.VoteSkip
|
||||
? "(skip)"
|
||||
: (players?.Find(p => p.ClientUuid == kv.Key)?.DisplayName ?? kv.Key);
|
||||
sb.AppendLine($"<size=24>{name}: {kv.Value}</size>");
|
||||
}
|
||||
}
|
||||
_voteResultText.text = sb.ToString();
|
||||
}
|
||||
|
||||
// Auto-close meeting panel after 5 s. Track the handle so we can
|
||||
// cancel it if the game ends or returns to lobby before it fires
|
||||
// (otherwise the coroutine fires mid-GameEndPanel and hides nothing
|
||||
// useful while the meeting overlay sits visibly stacked on top).
|
||||
CancelMeetingAutoClose();
|
||||
var gm = GameManager.Instance;
|
||||
if (gm != null) _meetingCloseCoroutine = gm.StartCoroutine(CloseMeetingAfterDelay(5f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide the meeting/vote panels immediately and cancel any pending
|
||||
/// auto-close coroutine. Resets internal toggles (skip/result/scroll
|
||||
/// visibility) so the next meeting starts from a clean state. Safe to
|
||||
/// call from any phase transition.
|
||||
/// </summary>
|
||||
public void HideMeetingPanel()
|
||||
{
|
||||
CancelMeetingAutoClose();
|
||||
if (_meetingPanel) _meetingPanel.SetActive(false);
|
||||
if (_voteResultPanel) _voteResultPanel.SetActive(false);
|
||||
if (_meetingScrollGO) _meetingScrollGO.SetActive(true);
|
||||
if (_skipButton) _skipButton.gameObject.SetActive(true);
|
||||
if (_myVoteIndicator) _myVoteIndicator.text = "";
|
||||
if (_meetingPhaseLabel) _meetingPhaseLabel.text = "";
|
||||
if (_meetingPhaseCountdown) _meetingPhaseCountdown.text = "";
|
||||
if (_meetingPhaseProgressBar) _meetingPhaseProgressBar.fillAmount = 0f;
|
||||
}
|
||||
|
||||
private void CancelMeetingAutoClose()
|
||||
{
|
||||
if (_meetingCloseCoroutine != null)
|
||||
{
|
||||
var gm = GameManager.Instance;
|
||||
if (gm != null) gm.StopCoroutine(_meetingCloseCoroutine);
|
||||
_meetingCloseCoroutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator CloseMeetingAfterDelay(float delay)
|
||||
{
|
||||
yield return new UnityEngine.WaitForSeconds(delay);
|
||||
// Use HideMeetingPanel so we restore the scroll/skip/indicator
|
||||
// state for the next meeting, not just hide the root panel.
|
||||
HideMeetingPanel();
|
||||
_meetingCloseCoroutine = null;
|
||||
}
|
||||
|
||||
// ── Sabotage ──────────────────────────────────────────────────────────
|
||||
|
||||
public void ShowSabotageTimer(DateTime deadline)
|
||||
{
|
||||
_sabotageMeltdownDeadline = deadline;
|
||||
_sabotageTimerActive = true;
|
||||
if (_sabotagePanel) _sabotagePanel.SetActive(true);
|
||||
if (_sabotageTimerText) _sabotageTimerText.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void HideSabotageTimer()
|
||||
{
|
||||
_sabotageTimerActive = false;
|
||||
if (_sabotagePanel) _sabotagePanel.SetActive(false);
|
||||
SetCommsBlackout(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the comms-blackout flag and (when active) raise the sabotage
|
||||
/// banner with a clear "comms down" message. The flag is read by
|
||||
/// GameManager_Tasks.UpdateProximity to suppress the REPORT/EMERGENCY
|
||||
/// action button while comms are jammed - this gives the player the
|
||||
/// visible reason why those buttons disappeared.
|
||||
/// </summary>
|
||||
public void SetCommsBlackout(bool active)
|
||||
{
|
||||
_commsBlackout = active;
|
||||
if (active)
|
||||
{
|
||||
if (_sabotagePanel) _sabotagePanel.SetActive(true);
|
||||
if (_sabotageTimerText)
|
||||
{
|
||||
_sabotageTimerText.gameObject.SetActive(true);
|
||||
_sabotageTimerText.text = "📡 COMMS DOWN — reports & meetings disabled";
|
||||
}
|
||||
}
|
||||
else if (!_sabotageTimerActive)
|
||||
{
|
||||
// Only tear the banner down if no meltdown timer is using it.
|
||||
if (_sabotagePanel) _sabotagePanel.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Game end ──────────────────────────────────────────────────────────
|
||||
|
||||
public void ShowGameEndPanel(GameEndedPayload payload, string myUuid)
|
||||
{
|
||||
if (_gameEndPanel) _gameEndPanel.SetActive(true);
|
||||
if (_gameEndText != null)
|
||||
{
|
||||
bool won = payload.Winners?.Contains(myUuid) ?? false;
|
||||
string title = won ? "<color=#FFB800>🏆 VICTORY</color>" : "<color=#C43232>💔 DEFEAT</color>";
|
||||
string faction = payload.WinningFaction == "Impostor" ? "Impostors win!" : "Crew wins!";
|
||||
|
||||
// Non-owners can't actually return to lobby themselves; tell
|
||||
// them who they're waiting on so the panel doesn't read as
|
||||
// "tap leave or stare at the wall." If we can't find an
|
||||
// owner record, fall back to a generic message.
|
||||
string waitMessage = "";
|
||||
if (!_gameClient.IsOwner)
|
||||
{
|
||||
var s = _state;
|
||||
var host = s?.Players?.Find(p => p.IsOwner);
|
||||
string hostName = host?.DisplayName ?? "the host";
|
||||
waitMessage = $"\n\n<size=32>Waiting for {hostName} to return to lobby...</size>";
|
||||
}
|
||||
_gameEndText.text = $"{title}\n{faction}\n<size=38>{payload.Reason}</size>{waitMessage}";
|
||||
}
|
||||
|
||||
// Show "Return to Lobby" only for the host
|
||||
if (_returnToLobbyBtn != null)
|
||||
_returnToLobbyBtn.gameObject.SetActive(_gameClient.IsOwner);
|
||||
}
|
||||
|
||||
// ── Toast ─────────────────────────────────────────────────────────────
|
||||
|
||||
public void ShowToast(string message)
|
||||
{
|
||||
if (_state != null) { _state.ToastMessage = message; _state.ToastExpiry = UnityEngine.Time.time + 4f; }
|
||||
if (_toastGO == null) return;
|
||||
_toastGO.SetActive(true);
|
||||
if (_toastText != null) _toastText.text = message;
|
||||
}
|
||||
|
||||
private void TickToast()
|
||||
{
|
||||
var s = _state;
|
||||
if (_toastGO == null) return;
|
||||
|
||||
if (s != null && !string.IsNullOrEmpty(s.ToastMessage) && UnityEngine.Time.time < s.ToastExpiry)
|
||||
{
|
||||
_toastGO.SetActive(true);
|
||||
if (_toastText != null) _toastText.text = s.ToastMessage;
|
||||
}
|
||||
else
|
||||
{
|
||||
_toastGO.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Canvas switching ──────────────────────────────────────────────────
|
||||
|
||||
private void SetCanvases(bool createJoin, bool inLobby, bool loading, bool game)
|
||||
{
|
||||
EnsureCanvasReady(ClientCreateJoinLobby);
|
||||
EnsureCanvasReady(ClientInLobby);
|
||||
EnsureCanvasReady(ClientLoadingScreen);
|
||||
EnsureCanvasReady(ClientGameScreen);
|
||||
|
||||
if (ClientCreateJoinLobby) ClientCreateJoinLobby.gameObject.SetActive(createJoin);
|
||||
if (ClientInLobby) ClientInLobby.gameObject.SetActive(inLobby);
|
||||
if (ClientLoadingScreen) ClientLoadingScreen.gameObject.SetActive(loading);
|
||||
if (ClientGameScreen) ClientGameScreen.gameObject.SetActive(game);
|
||||
}
|
||||
|
||||
// ── Utilities ─────────────────────────────────────────────────────────
|
||||
|
||||
private static void EnsureCanvasReady(Canvas canvas)
|
||||
{
|
||||
if (canvas == null) return;
|
||||
if (!canvas.enabled) canvas.enabled = true;
|
||||
var t = canvas.transform;
|
||||
if (t != null)
|
||||
{
|
||||
var s = t.localScale;
|
||||
if (Mathf.Abs(s.x) < 0.001f || Mathf.Abs(s.y) < 0.001f || Mathf.Abs(s.z) < 0.001f)
|
||||
t.localScale = Vector3.one;
|
||||
}
|
||||
}
|
||||
|
||||
private static TMP_Text FindTMP(Transform root, string name)
|
||||
{
|
||||
if (root == null) return null;
|
||||
foreach (var tmp in root.GetComponentsInChildren<TMP_Text>(true))
|
||||
if (tmp != null && tmp.name == name) return tmp;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Transform FindTransform(Transform root, string name)
|
||||
{
|
||||
if (root == null) return null;
|
||||
foreach (Transform t in root.GetComponentsInChildren<Transform>(true))
|
||||
if (t.name == name) return t;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static GameObject FindTransformGO(Transform root, string name)
|
||||
=> FindTransform(root, name)?.gameObject;
|
||||
|
||||
private static RectTransform MakeChild(string name, RectTransform parent)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.SetParent(parent, false);
|
||||
rt.localScale = Vector3.one;
|
||||
return rt;
|
||||
}
|
||||
|
||||
private static void Stretch(RectTransform rt)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,50 +4,18 @@ using UnityEngine;
|
||||
|
||||
public enum TaskType
|
||||
{
|
||||
Task //TODO: Typy úkolù
|
||||
Task
|
||||
}
|
||||
|
||||
|
||||
|
||||
public interface ITask
|
||||
{
|
||||
public string TaskID { get; } // Unikátní ID úkolu pro server
|
||||
public TaskType TaskType { get; } // Typ úkolu
|
||||
public string TaskName { get; } // Viditelný název úkolu
|
||||
public (double, double) TaskLocation { get; } // Polohy na mapì
|
||||
public bool IsCompleted { get; } // Stav dokonèení úkolu
|
||||
public string TaskID { get; set; } // Unikátní ID úkolu pro server
|
||||
public TaskType TaskType { get; set; } // Typ úkolu
|
||||
public string TaskName { get; set; } // Viditelný název úkolu
|
||||
public (double, double) TaskLocation { get; set; } // Poloha na mapě
|
||||
public bool IsCompleted { get; } // Stav dokončení úkolu
|
||||
|
||||
void Initialize(Action<ITask> onCompleted); // Vytvoøení tasku + naètení postupu
|
||||
void ExitTask(Action<ITask> onExit); // Pøi opuštìní úkolu poslat hotovo / uložit postup / reset
|
||||
void Complete(); // Oznaèit úkol jako dokonèený, poslat na server a zavøít
|
||||
|
||||
}
|
||||
/* Ukázoková implementace ITask
|
||||
public class Wires : ITask{
|
||||
public string TaskID { get; set; } // Unikátní ID úkolu pro server
|
||||
public TaskType TaskType { get; set; } // Typ úkolu
|
||||
public string TaskName { get; set; } // Viditelný název úkolu
|
||||
public (double, double) TaskLocation { get; set; } // Poloha na mapì
|
||||
public bool IsCompleted { get; private set; } // Stav dokonèení úkolu
|
||||
private Action<ITask> _onCompleted;
|
||||
|
||||
public void Initialize(Action<ITask> onCompleted) // Vytvoøení tasku
|
||||
{
|
||||
IsCompleted = false;
|
||||
_onCompleted = onCompleted;
|
||||
}
|
||||
public void ExitTask(Action<ITask> onExit) //Zavøení tasku
|
||||
{
|
||||
onExit?.Invoke(this);
|
||||
}
|
||||
public void Complete() // Dokonèení tasku a zavøení
|
||||
{
|
||||
IsCompleted = true;
|
||||
_onCompleted?.Invoke(this);
|
||||
ExitTask(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
void Initialize(Action<ITask> onCompleted); // Vytvoření tasku
|
||||
void ExitTask(Action<ITask> onExit); // Při opuštění úkolu
|
||||
void Complete(); // Označit úkol jako dokončený
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2230bf768ecb84610af77bea6cdd7074
|
||||
guid: 6b37670de43269e4f984694475e75510
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
assetBundleVariant:
|
||||
8
Assets/Materials/Map.meta
Normal file
8
Assets/Materials/Map.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16e75919ca33104489742cddec34a46f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Materials/Map/Area_Default.mat
Normal file
38
Assets/Materials/Map/Area_Default.mat
Normal file
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Area_Default
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Glossiness: 0.2
|
||||
- _Metallic: 0
|
||||
m_Colors:
|
||||
- _Color: {r: 0.55, g: 0.78, b: 0.45, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/Materials/Map/Area_Default.mat.meta
Normal file
8
Assets/Materials/Map/Area_Default.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57c69638d7afff039a48123e7b4ade7b
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Materials/Map/Area_Forest.mat
Normal file
38
Assets/Materials/Map/Area_Forest.mat
Normal file
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Area_Forest
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Glossiness: 0.2
|
||||
- _Metallic: 0
|
||||
m_Colors:
|
||||
- _Color: {r: 0.15, g: 0.45, b: 0.15, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/Materials/Map/Area_Forest.mat.meta
Normal file
8
Assets/Materials/Map/Area_Forest.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef4ea3212671d276649ca366ed6e7f0c
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Materials/Map/Area_Park.mat
Normal file
38
Assets/Materials/Map/Area_Park.mat
Normal file
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Area_Park
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Glossiness: 0.2
|
||||
- _Metallic: 0
|
||||
m_Colors:
|
||||
- _Color: {r: 0.3, g: 0.7, b: 0.3, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/Materials/Map/Area_Park.mat.meta
Normal file
8
Assets/Materials/Map/Area_Park.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77b772132676975a3462e3aca2cfac0c
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Materials/Map/Area_Water.mat
Normal file
38
Assets/Materials/Map/Area_Water.mat
Normal file
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Area_Water
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Glossiness: 0.2
|
||||
- _Metallic: 0
|
||||
m_Colors:
|
||||
- _Color: {r: 0.2, g: 0.5, b: 0.9, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/Materials/Map/Area_Water.mat.meta
Normal file
8
Assets/Materials/Map/Area_Water.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eff7cc5b4e29f7918320d3979c3d0cb1
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Materials/Map/Building_Commercial.mat
Normal file
38
Assets/Materials/Map/Building_Commercial.mat
Normal file
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Building_Commercial
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Glossiness: 0.2
|
||||
- _Metallic: 0
|
||||
m_Colors:
|
||||
- _Color: {r: 0.62, g: 0.62, b: 0.64, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/Materials/Map/Building_Commercial.mat.meta
Normal file
8
Assets/Materials/Map/Building_Commercial.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cade7a38e47c1b875f65cda2cf3b77c5
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Materials/Map/Building_Default.mat
Normal file
38
Assets/Materials/Map/Building_Default.mat
Normal file
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Building_Default
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Glossiness: 0.2
|
||||
- _Metallic: 0
|
||||
m_Colors:
|
||||
- _Color: {r: 0.75, g: 0.74, b: 0.72, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/Materials/Map/Building_Default.mat.meta
Normal file
8
Assets/Materials/Map/Building_Default.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a50367e9d7460063dec2f85d18dde35
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Materials/Map/Building_Industrial.mat
Normal file
38
Assets/Materials/Map/Building_Industrial.mat
Normal file
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Building_Industrial
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Glossiness: 0.2
|
||||
- _Metallic: 0
|
||||
m_Colors:
|
||||
- _Color: {r: 0.4, g: 0.38, b: 0.36, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/Materials/Map/Building_Industrial.mat.meta
Normal file
8
Assets/Materials/Map/Building_Industrial.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4179f65aa390e873e96480f462d9fc3a
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Materials/Map/Building_Residential.mat
Normal file
38
Assets/Materials/Map/Building_Residential.mat
Normal file
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Building_Residential
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Glossiness: 0.2
|
||||
- _Metallic: 0
|
||||
m_Colors:
|
||||
- _Color: {r: 0.9, g: 0.82, b: 0.7, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/Materials/Map/Building_Residential.mat.meta
Normal file
8
Assets/Materials/Map/Building_Residential.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d52b379b821c9ed624c15e9bd1420204
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Materials/Map/Road_Cycleway.mat
Normal file
38
Assets/Materials/Map/Road_Cycleway.mat
Normal file
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Road_Cycleway
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Glossiness: 0.2
|
||||
- _Metallic: 0
|
||||
m_Colors:
|
||||
- _Color: {r: 0.35, g: 0.7, b: 0.35, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/Materials/Map/Road_Cycleway.mat.meta
Normal file
8
Assets/Materials/Map/Road_Cycleway.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ecf5df4890c0486002bf43baf684bab
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Materials/Map/Road_Default.mat
Normal file
38
Assets/Materials/Map/Road_Default.mat
Normal file
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Road_Default
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Glossiness: 0.2
|
||||
- _Metallic: 0
|
||||
m_Colors:
|
||||
- _Color: {r: 0.4, g: 0.4, b: 0.42, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/Materials/Map/Road_Default.mat.meta
Normal file
8
Assets/Materials/Map/Road_Default.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f831020d62c88ea9dfde83f98d539f55
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Materials/Map/Road_Footway.mat
Normal file
38
Assets/Materials/Map/Road_Footway.mat
Normal file
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Road_Footway
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Glossiness: 0.2
|
||||
- _Metallic: 0
|
||||
m_Colors:
|
||||
- _Color: {r: 0.82, g: 0.76, b: 0.6, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/Materials/Map/Road_Footway.mat.meta
Normal file
8
Assets/Materials/Map/Road_Footway.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a40e1c43763bb5c9b97d7999ac3c6f9d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Materials/Map/Road_Path.mat
Normal file
38
Assets/Materials/Map/Road_Path.mat
Normal file
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Road_Path
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Glossiness: 0.2
|
||||
- _Metallic: 0
|
||||
m_Colors:
|
||||
- _Color: {r: 0.8, g: 0.74, b: 0.58, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/Materials/Map/Road_Path.mat.meta
Normal file
8
Assets/Materials/Map/Road_Path.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8c268a7008ed5031da152380545235d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -167,10 +167,10 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 800, y: 600}
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
m_ScreenMatchMode: 0
|
||||
m_MatchWidthOrHeight: 0
|
||||
m_PhysicalUnit: 3
|
||||
@@ -507,10 +507,10 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 800, y: 600}
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
m_ScreenMatchMode: 0
|
||||
m_MatchWidthOrHeight: 0
|
||||
m_PhysicalUnit: 3
|
||||
@@ -1262,7 +1262,7 @@ MonoBehaviour:
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls:
|
||||
- m_Target: {fileID: 1353866372}
|
||||
- m_Target: {fileID: 0}
|
||||
m_TargetAssemblyTypeName: GameManager, Assembly-CSharp
|
||||
m_MethodName: CreateLobbyButton
|
||||
m_Mode: 1
|
||||
@@ -1508,7 +1508,7 @@ MonoBehaviour:
|
||||
m_text: Loading...
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_sharedMaterial: {fileID: 2100000, guid: 9ad269c99dcf42b7aedefd83dd5a7b9d, type: 2}
|
||||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
@@ -1873,7 +1873,7 @@ MonoBehaviour:
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls:
|
||||
- m_Target: {fileID: 1353866372}
|
||||
- m_Target: {fileID: 0}
|
||||
m_TargetAssemblyTypeName: GameManager, Assembly-CSharp
|
||||
m_MethodName: JoinLobbyButton
|
||||
m_Mode: 1
|
||||
@@ -2142,7 +2142,7 @@ MonoBehaviour:
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls:
|
||||
- m_Target: {fileID: 1353866372}
|
||||
- m_Target: {fileID: 0}
|
||||
m_TargetAssemblyTypeName: GameManager, Assembly-CSharp
|
||||
m_MethodName: LeaveLobbyButton
|
||||
m_Mode: 1
|
||||
@@ -2192,96 +2192,6 @@ CanvasRenderer:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1304493835}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &1353866370
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1353866371}
|
||||
- component: {fileID: 1353866372}
|
||||
m_Layer: 0
|
||||
m_Name: Game
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1353866371
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1353866370}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 56.6539, y: 0, z: 0.06448}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &1353866372
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1353866370}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9e2c3e4ba4e36ea40a686e58feca4d2b, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::GameManager
|
||||
displayName: Player
|
||||
JoinCreateLobby: {fileID: 1403738864}
|
||||
InLobby: {fileID: 12226903}
|
||||
LoadingScreen: {fileID: 247614966}
|
||||
GameScreen: {fileID: 1631266632}
|
||||
MapCenterPoint: {fileID: 216559629}
|
||||
buildingSettings:
|
||||
ResidentalBuildingsMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
ResidentalBuildingHeight: 3
|
||||
CommercialBuildingsMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
CommercialBuildingHeight: 10
|
||||
IndustrialBuildingsMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
IndustrialBuildingHeight: 5
|
||||
DefaultBuildingMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
DefaultBuildingHeight: 3
|
||||
pathwaySettings:
|
||||
FootwayMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
FootwayWidth: 1
|
||||
PathMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
PathWidth: 1
|
||||
StepsMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
StepsWidth: 2
|
||||
CyclewayMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
CyclewayWidth: 2
|
||||
PedestrianMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
PedestrianWidth: 2
|
||||
RoadMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
RoadWidth: 5
|
||||
ServiceMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
ServiceWidth: 3
|
||||
ResidentialMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
ResidentialWidth: 5
|
||||
TrackMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
TrackWidth: 5
|
||||
DefaultMat: {fileID: 2100000, guid: 6744524496c8e1549882277283c132cc, type: 2}
|
||||
DefaultWidth: 5
|
||||
areaSettings:
|
||||
ParkMat: {fileID: 2100000, guid: 5a46533bdf4003449bc9146ccef44e27, type: 2}
|
||||
GardenMat: {fileID: 2100000, guid: 5a46533bdf4003449bc9146ccef44e27, type: 2}
|
||||
PlaygroundMat: {fileID: 2100000, guid: 5a46533bdf4003449bc9146ccef44e27, type: 2}
|
||||
ForestMat: {fileID: 2100000, guid: 5a46533bdf4003449bc9146ccef44e27, type: 2}
|
||||
GrassMat: {fileID: 2100000, guid: 5a46533bdf4003449bc9146ccef44e27, type: 2}
|
||||
WaterMat: {fileID: 2100000, guid: 5a46533bdf4003449bc9146ccef44e27, type: 2}
|
||||
DefaultMat: {fileID: 2100000, guid: 5a46533bdf4003449bc9146ccef44e27, type: 2}
|
||||
Player: {fileID: 1161233721}
|
||||
testMode: 1
|
||||
--- !u!1 &1403738861
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -2330,10 +2240,10 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 800, y: 600}
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
m_ScreenMatchMode: 0
|
||||
m_MatchWidthOrHeight: 0
|
||||
m_PhysicalUnit: 3
|
||||
@@ -2727,10 +2637,10 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 800, y: 600}
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
m_ScreenMatchMode: 0
|
||||
m_MatchWidthOrHeight: 0
|
||||
m_PhysicalUnit: 3
|
||||
@@ -2865,7 +2775,7 @@ MonoBehaviour:
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls:
|
||||
- m_Target: {fileID: 1353866372}
|
||||
- m_Target: {fileID: 0}
|
||||
m_TargetAssemblyTypeName: GameManager, Assembly-CSharp
|
||||
m_MethodName: StartGameButton
|
||||
m_Mode: 1
|
||||
@@ -3423,7 +3333,6 @@ SceneRoots:
|
||||
m_Roots:
|
||||
- {fileID: 1010702372}
|
||||
- {fileID: 442151208}
|
||||
- {fileID: 1353866371}
|
||||
- {fileID: 1403738865}
|
||||
- {fileID: 157221436}
|
||||
- {fileID: 12226904}
|
||||
|
||||
@@ -1223,6 +1223,54 @@ CanvasRenderer:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1614023714}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &1630498899
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1630498901}
|
||||
- component: {fileID: 1630498900}
|
||||
m_Layer: 0
|
||||
m_Name: UIManager
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1630498900
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1630498899}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: cef2287cbad97c8b8a4451dfb6a8e472, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::ConfirmLeaveUI
|
||||
yesButton: {fileID: 0}
|
||||
noButton: {fileID: 0}
|
||||
mainMenuScene: main menu asi idk lol
|
||||
previousScene: create
|
||||
--- !u!4 &1630498901
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1630498899}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0.21709, y: 0, z: 3.25431}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1703330983
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -1311,7 +1359,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
@@ -1698,3 +1746,4 @@ SceneRoots:
|
||||
- {fileID: 32068840}
|
||||
- {fileID: 1105375119}
|
||||
- {fileID: 1431640583}
|
||||
- {fileID: 1630498901}
|
||||
|
||||
@@ -1311,7 +1311,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
|
||||
@@ -931,7 +931,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
|
||||
@@ -154,10 +154,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2022427224}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 216, y: -205}
|
||||
m_SizeDelta: {x: 200, y: 200}
|
||||
m_AnchorMin: {x: 0.5, y: 1}
|
||||
m_AnchorMax: {x: 0.5, y: 1}
|
||||
m_AnchoredPosition: {x: -466.8166, y: -731.971}
|
||||
m_SizeDelta: {x: 246.139, y: 113.3993}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &202338697
|
||||
MonoBehaviour:
|
||||
@@ -447,8 +447,8 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 89
|
||||
m_fontSizeBase: 89
|
||||
m_fontSize: 33.4
|
||||
m_fontSizeBase: 33.4
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 18
|
||||
@@ -485,7 +485,7 @@ MonoBehaviour:
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: -127.167816, y: -17.406898, z: -156.3833, w: -0.33364105}
|
||||
m_margin: {x: -173.94716, y: 11.8302, z: -294.52856, w: 16.477688}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
@@ -1028,8 +1028,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0}
|
||||
m_AnchorMax: {x: 0.5, y: 0}
|
||||
m_AnchoredPosition: {x: 16, y: 472}
|
||||
m_SizeDelta: {x: 1250, y: 425}
|
||||
m_AnchoredPosition: {x: 0, y: 139}
|
||||
m_SizeDelta: {x: 511.7281, y: 173.9875}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &960568416
|
||||
CanvasRenderer:
|
||||
@@ -1254,10 +1254,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2022427224}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0}
|
||||
m_AnchorMax: {x: 0.5, y: 0}
|
||||
m_AnchoredPosition: {x: -10, y: 1911}
|
||||
m_SizeDelta: {x: 1000, y: 1500}
|
||||
m_AnchorMin: {x: 0.5, y: 1}
|
||||
m_AnchorMax: {x: 0.5, y: 1}
|
||||
m_AnchoredPosition: {x: 23.000046, y: -475.1214}
|
||||
m_SizeDelta: {x: 1230.6951, y: 850.4949}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &982479698
|
||||
CanvasRenderer:
|
||||
@@ -1389,142 +1389,6 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1163536068
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1163536069}
|
||||
- component: {fileID: 1163536071}
|
||||
- component: {fileID: 1163536070}
|
||||
m_Layer: 5
|
||||
m_Name: max players
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1163536069
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1163536068}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2022427224}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 1}
|
||||
m_AnchorMax: {x: 0.5, y: 1}
|
||||
m_AnchoredPosition: {x: -356, y: -2039}
|
||||
m_SizeDelta: {x: 1000, y: 300}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1163536070
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1163536068}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: 'Current players:'
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8a13e20c49f7db440aa1e34c53005080, type: 2}
|
||||
m_sharedMaterial: {fileID: 4955521229866657731, guid: 8a13e20c49f7db440aa1e34c53005080, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4292214979
|
||||
m_fontColor: {r: 0.76470596, g: 0, b: 0.83921576, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 120
|
||||
m_fontSizeBase: 120
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 0
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 12.227722, y: 33.534668, z: -194.49167, w: 37.32251}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!222 &1163536071
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1163536068}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &1191044984
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -1557,10 +1421,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2022427224}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0}
|
||||
m_AnchorMax: {x: 0.5, y: 0}
|
||||
m_AnchoredPosition: {x: -10, y: 2077}
|
||||
m_SizeDelta: {x: 700, y: 150}
|
||||
m_AnchorMin: {x: 0.5, y: 1}
|
||||
m_AnchorMax: {x: 0.5, y: 1}
|
||||
m_AnchoredPosition: {x: 23, y: -381}
|
||||
m_SizeDelta: {x: 861.4866, y: 85.0495}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &1191044986
|
||||
CanvasRenderer:
|
||||
@@ -1632,10 +1496,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1641797426}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.07750001, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: -75, y: -11}
|
||||
m_SizeDelta: {x: -707, y: -218}
|
||||
m_AnchorMin: {x: 0.5, y: 1}
|
||||
m_AnchorMax: {x: 0.5, y: 1}
|
||||
m_AnchoredPosition: {x: -36, y: -761}
|
||||
m_SizeDelta: {x: 215, y: 1282}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1211383658
|
||||
MonoBehaviour:
|
||||
@@ -1768,10 +1632,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2022427224}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0}
|
||||
m_AnchorMax: {x: 0.5, y: 0}
|
||||
m_AnchoredPosition: {x: -10, y: 2326}
|
||||
m_SizeDelta: {x: 700, y: 150}
|
||||
m_AnchorMin: {x: 0.5, y: 1}
|
||||
m_AnchorMax: {x: 0.5, y: 1}
|
||||
m_AnchoredPosition: {x: 23.000046, y: -239.81787}
|
||||
m_SizeDelta: {x: 861.4866, y: 85.0495}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &1443244313
|
||||
CanvasRenderer:
|
||||
@@ -1845,10 +1709,10 @@ RectTransform:
|
||||
- {fileID: 1211383657}
|
||||
m_Father: {fileID: 2022427224}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0}
|
||||
m_AnchorMax: {x: 0.5, y: 0}
|
||||
m_AnchoredPosition: {x: -10, y: 1911}
|
||||
m_SizeDelta: {x: 1000, y: 1500}
|
||||
m_AnchorMin: {x: 0.5, y: 1}
|
||||
m_AnchorMax: {x: 0.5, y: 1}
|
||||
m_AnchoredPosition: {x: -0.000015259, y: -493}
|
||||
m_SizeDelta: {x: 1058.6465, y: 909.2553}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!95 &1641797427
|
||||
Animator:
|
||||
@@ -1910,6 +1774,50 @@ MonoBehaviour:
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &1685875757
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1685875759}
|
||||
- component: {fileID: 1685875758}
|
||||
m_Layer: 0
|
||||
m_Name: UIManager
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1685875758
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1685875757}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 290610b7d8fb7ea675982694abac90ef, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::LobbyDisplayUI
|
||||
--- !u!4 &1685875759
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1685875757}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0.21709, y: 0, z: 3.25431}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1756995537
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -2055,7 +1963,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 1}
|
||||
m_AnchorMax: {x: 0.5, y: 1}
|
||||
m_AnchoredPosition: {x: -454, y: -2275}
|
||||
m_AnchoredPosition: {x: -275, y: -199.87}
|
||||
m_SizeDelta: {x: 1000, y: 300}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1950902273
|
||||
@@ -2245,12 +2153,12 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
m_ReferenceResolution: {x: 1080, y: 1920}
|
||||
m_ScreenMatchMode: 0
|
||||
m_MatchWidthOrHeight: 0
|
||||
m_MatchWidthOrHeight: 0.5
|
||||
m_PhysicalUnit: 3
|
||||
m_FallbackScreenDPI: 96
|
||||
m_DefaultSpriteDPI: 96
|
||||
@@ -2292,8 +2200,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 1756995538}
|
||||
- {fileID: 1163536069}
|
||||
- {fileID: 1950902272}
|
||||
- {fileID: 202338696}
|
||||
- {fileID: 720431058}
|
||||
- {fileID: 982479697}
|
||||
@@ -2301,6 +2207,7 @@ RectTransform:
|
||||
- {fileID: 1443244312}
|
||||
- {fileID: 1191044985}
|
||||
- {fileID: 960568415}
|
||||
- {fileID: 1950902272}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
@@ -2317,3 +2224,4 @@ SceneRoots:
|
||||
- {fileID: 999622380}
|
||||
- {fileID: 514215624}
|
||||
- {fileID: 794999493}
|
||||
- {fileID: 1685875759}
|
||||
|
||||
@@ -858,7 +858,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
|
||||
@@ -1147,12 +1147,12 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
m_ReferenceResolution: {x: 1080, y: 1920}
|
||||
m_ScreenMatchMode: 0
|
||||
m_MatchWidthOrHeight: 0
|
||||
m_MatchWidthOrHeight: 0.5
|
||||
m_PhysicalUnit: 3
|
||||
m_FallbackScreenDPI: 96
|
||||
m_DefaultSpriteDPI: 96
|
||||
@@ -1451,6 +1451,53 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::CudlikZmenaSceny
|
||||
nazevCiloveSceny: main menu asi idk lol
|
||||
--- !u!1 &1699332091
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1699332093}
|
||||
- component: {fileID: 1699332092}
|
||||
m_Layer: 0
|
||||
m_Name: UIManager
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1699332092
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1699332091}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 60a81c1cb4f98a5b490fac0d3c1686b5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::HostLobbyUI
|
||||
radiusSlider: {fileID: 0}
|
||||
radiusInput: {fileID: 0}
|
||||
createButton: {fileID: 0}
|
||||
--- !u!4 &1699332093
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1699332091}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0.21709, y: 0, z: 3.25431}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1736735564
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -2064,3 +2111,4 @@ SceneRoots:
|
||||
- {fileID: 787926194}
|
||||
- {fileID: 2039144269}
|
||||
- {fileID: 795876785}
|
||||
- {fileID: 1699332093}
|
||||
|
||||
@@ -776,7 +776,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
@@ -2153,6 +2153,57 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &2109176766
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2109176768}
|
||||
- component: {fileID: 2109176767}
|
||||
m_Layer: 0
|
||||
m_Name: UiManager
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &2109176767
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2109176766}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 290610b7d8fb7ea675982694abac90ef, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::LobbyDisplayUI
|
||||
lobbyCodeText: {fileID: 0}
|
||||
playerListText: {fileID: 0}
|
||||
maxPlayersText: {fileID: 0}
|
||||
ownNameText: {fileID: 0}
|
||||
otherNamesText: {fileID: 0}
|
||||
statusText: {fileID: 0}
|
||||
startButton: {fileID: 0}
|
||||
--- !u!4 &2109176768
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2109176766}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0.21709, y: 0, z: 3.25431}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -2162,3 +2213,4 @@ SceneRoots:
|
||||
- {fileID: 84759318}
|
||||
- {fileID: 1628273077}
|
||||
- {fileID: 1867671051}
|
||||
- {fileID: 2109176768}
|
||||
|
||||
@@ -1091,7 +1091,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
@@ -1517,6 +1517,53 @@ CanvasRenderer:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1279141238}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &1474942541
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1474942543}
|
||||
- component: {fileID: 1474942542}
|
||||
m_Layer: 0
|
||||
m_Name: UIManager
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1474942542
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1474942541}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0e0ca5d57a20e05215c36664ab8ff60e, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::JoinLobbyUI
|
||||
codeInput: {fileID: 0}
|
||||
joinButton: {fileID: 0}
|
||||
errorText: {fileID: 0}
|
||||
--- !u!4 &1474942543
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1474942541}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0.21709, y: 0, z: 3.25431}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1484655921
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -2414,3 +2461,4 @@ SceneRoots:
|
||||
- {fileID: 649350973}
|
||||
- {fileID: 1153626690}
|
||||
- {fileID: 1620739952}
|
||||
- {fileID: 1474942543}
|
||||
|
||||
@@ -1358,7 +1358,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
@@ -2017,6 +2017,152 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 4.07, y: 171.97, z: 0}
|
||||
--- !u!1 &1944735669
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1944735672}
|
||||
- component: {fileID: 1944735671}
|
||||
- component: {fileID: 1944735670}
|
||||
m_Layer: 0
|
||||
m_Name: UIManage
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1944735670
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1944735669}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a1305c74eacfaf90fd98134860492d46, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::PlayerNameInput
|
||||
--- !u!114 &1944735671
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1944735669}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 2da0c512f12947e489f739169773d7ca, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TMP_InputField
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 0}
|
||||
m_TextViewport: {fileID: 0}
|
||||
m_TextComponent: {fileID: 0}
|
||||
m_Placeholder: {fileID: 0}
|
||||
m_VerticalScrollbar: {fileID: 0}
|
||||
m_VerticalScrollbarEventHandler: {fileID: 0}
|
||||
m_LayoutGroup: {fileID: 0}
|
||||
m_ScrollSensitivity: 1
|
||||
m_ContentType: 0
|
||||
m_InputType: 0
|
||||
m_AsteriskChar: 42
|
||||
m_KeyboardType: 0
|
||||
m_LineType: 0
|
||||
m_HideMobileInput: 0
|
||||
m_HideSoftKeyboard: 0
|
||||
m_CharacterValidation: 0
|
||||
m_RegexValue:
|
||||
m_GlobalPointSize: 14
|
||||
m_CharacterLimit: 0
|
||||
m_OnEndEdit:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_OnSubmit:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_OnSelect:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_OnDeselect:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_OnTextSelection:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_OnEndTextSelection:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_OnValueChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_OnTouchScreenKeyboardStatusChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_CaretColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
|
||||
m_CustomCaretColor: 0
|
||||
m_SelectionColor: {r: 0.65882355, g: 0.80784315, b: 1, a: 0.7529412}
|
||||
m_Text:
|
||||
m_CaretBlinkRate: 0.85
|
||||
m_CaretWidth: 1
|
||||
m_ReadOnly: 0
|
||||
m_RichText: 1
|
||||
m_GlobalFontAsset: {fileID: 0}
|
||||
m_OnFocusSelectAll: 1
|
||||
m_ResetOnDeActivation: 1
|
||||
m_KeepTextSelectionVisible: 0
|
||||
m_RestoreOriginalTextOnEscape: 1
|
||||
m_isRichTextEditingAllowed: 0
|
||||
m_LineLimit: 0
|
||||
isAlert: 0
|
||||
m_InputValidator: {fileID: 0}
|
||||
m_ShouldActivateOnSelect: 1
|
||||
--- !u!4 &1944735672
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1944735669}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0.21709, y: 0, z: 3.25431}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1967775146
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -2252,3 +2398,4 @@ SceneRoots:
|
||||
- {fileID: 1729948460}
|
||||
- {fileID: 1276018793}
|
||||
- {fileID: 1922038244}
|
||||
- {fileID: 1944735672}
|
||||
|
||||
@@ -396,7 +396,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 800, y: 600}
|
||||
|
||||
371
Assets/Scenes/MiniGame-FlappyBird.unity
Normal file
371
Assets/Scenes/MiniGame-FlappyBird.unity
Normal file
@@ -0,0 +1,371 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!29 &1
|
||||
OcclusionCullingSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_OcclusionBakeSettings:
|
||||
smallestOccluder: 5
|
||||
smallestHole: 0.25
|
||||
backfaceThreshold: 100
|
||||
m_SceneGUID: 00000000000000000000000000000000
|
||||
m_OcclusionCullingData: {fileID: 0}
|
||||
--- !u!104 &2
|
||||
RenderSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 10
|
||||
m_Fog: 0
|
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
m_FogMode: 3
|
||||
m_FogDensity: 0.01
|
||||
m_LinearFogStart: 0
|
||||
m_LinearFogEnd: 300
|
||||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||
m_AmbientIntensity: 1
|
||||
m_AmbientMode: 0
|
||||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_HaloStrength: 0.5
|
||||
m_FlareStrength: 1
|
||||
m_FlareFadeSpeed: 3
|
||||
m_HaloTexture: {fileID: 0}
|
||||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_DefaultReflectionMode: 0
|
||||
m_DefaultReflectionResolution: 128
|
||||
m_ReflectionBounces: 1
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 0}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 13
|
||||
m_BakeOnSceneLoad: 0
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_EnvironmentLightingMode: 0
|
||||
m_EnableBakedLightmaps: 1
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_LightmapEditorSettings:
|
||||
serializedVersion: 12
|
||||
m_Resolution: 2
|
||||
m_BakeResolution: 40
|
||||
m_AtlasSize: 1024
|
||||
m_AO: 0
|
||||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAmbientOcclusion: 0
|
||||
m_Padding: 2
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
m_TextureCompression: 1
|
||||
m_ReflectionCompression: 2
|
||||
m_MixedBakeMode: 2
|
||||
m_BakeBackend: 1
|
||||
m_PVRSampling: 1
|
||||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 512
|
||||
m_PVRBounces: 2
|
||||
m_PVREnvironmentSampleCount: 256
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_PVRFilteringMode: 1
|
||||
m_PVRDenoiserTypeDirect: 1
|
||||
m_PVRDenoiserTypeIndirect: 1
|
||||
m_PVRDenoiserTypeAO: 1
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVREnvironmentMIS: 1
|
||||
m_PVRCulling: 1
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 1
|
||||
m_PVRFilteringGaussRadiusAO: 1
|
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_ExportTrainingData: 0
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_LightProbeSampleCountMultiplier: 4
|
||||
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_LightingSettings: {fileID: 0}
|
||||
--- !u!196 &4
|
||||
NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 3
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
agentSlope: 45
|
||||
agentClimb: 0.4
|
||||
ledgeDropHeight: 0
|
||||
maxJumpAcrossDistance: 0
|
||||
minRegionArea: 2
|
||||
manualCellSize: 0
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
buildHeightMesh: 0
|
||||
maxJobWorkers: 0
|
||||
preserveTilesOutsideBounds: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &942839824
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 942839826}
|
||||
- component: {fileID: 942839825}
|
||||
m_Layer: 0
|
||||
m_Name: FlappyBird
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &942839825
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 942839824}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 19096191e142d154e956c7169cca9a1e, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::FlappyBirdAllInOne
|
||||
rb: {fileID: 0}
|
||||
jumpForce: 5
|
||||
isDead: 0
|
||||
pipePrefab: {fileID: 0}
|
||||
spawnPoint: {fileID: 0}
|
||||
spawnRate: 2
|
||||
heightOffset: 2
|
||||
pipeSpeed: 2
|
||||
scoreText: {fileID: 0}
|
||||
gameOverPanel: {fileID: 0}
|
||||
--- !u!4 &942839826
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 942839824}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0.21709, y: 0, z: 3.25431}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1109769279
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1109769282}
|
||||
- component: {fileID: 1109769281}
|
||||
- component: {fileID: 1109769280}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!81 &1109769280
|
||||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1109769279}
|
||||
m_Enabled: 1
|
||||
--- !u!20 &1109769281
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1109769279}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_ClearFlags: 1
|
||||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
||||
m_projectionMatrixMode: 1
|
||||
m_GateFitMode: 2
|
||||
m_FOVAxisMode: 0
|
||||
m_Iso: 200
|
||||
m_ShutterSpeed: 0.005
|
||||
m_Aperture: 16
|
||||
m_FocusDistance: 10
|
||||
m_FocalLength: 50
|
||||
m_BladeCount: 5
|
||||
m_Curvature: {x: 2, y: 11}
|
||||
m_BarrelClipping: 0.25
|
||||
m_Anamorphism: 0
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
near clip plane: 0.3
|
||||
far clip plane: 1000
|
||||
field of view: 60
|
||||
orthographic: 0
|
||||
orthographic size: 5
|
||||
m_Depth: -1
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingPath: -1
|
||||
m_TargetTexture: {fileID: 0}
|
||||
m_TargetDisplay: 0
|
||||
m_TargetEye: 3
|
||||
m_HDR: 1
|
||||
m_AllowMSAA: 1
|
||||
m_AllowDynamicResolution: 0
|
||||
m_ForceIntoRT: 0
|
||||
m_OcclusionCulling: 1
|
||||
m_StereoConvergence: 10
|
||||
m_StereoSeparation: 0.022
|
||||
--- !u!4 &1109769282
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1109769279}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1847134981
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1847134983}
|
||||
- component: {fileID: 1847134982}
|
||||
m_Layer: 0
|
||||
m_Name: Directional Light
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!108 &1847134982
|
||||
Light:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1847134981}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 11
|
||||
m_Type: 1
|
||||
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
|
||||
m_Intensity: 1
|
||||
m_Range: 10
|
||||
m_SpotAngle: 30
|
||||
m_InnerSpotAngle: 21.80208
|
||||
m_CookieSize: 10
|
||||
m_Shadows:
|
||||
m_Type: 2
|
||||
m_Resolution: -1
|
||||
m_CustomResolution: -1
|
||||
m_Strength: 1
|
||||
m_Bias: 0.05
|
||||
m_NormalBias: 0.4
|
||||
m_NearPlane: 0.2
|
||||
m_CullingMatrixOverride:
|
||||
e00: 1
|
||||
e01: 0
|
||||
e02: 0
|
||||
e03: 0
|
||||
e10: 0
|
||||
e11: 1
|
||||
e12: 0
|
||||
e13: 0
|
||||
e20: 0
|
||||
e21: 0
|
||||
e22: 1
|
||||
e23: 0
|
||||
e30: 0
|
||||
e31: 0
|
||||
e32: 0
|
||||
e33: 1
|
||||
m_UseCullingMatrixOverride: 0
|
||||
m_Cookie: {fileID: 0}
|
||||
m_DrawHalo: 0
|
||||
m_Flare: {fileID: 0}
|
||||
m_RenderMode: 0
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingLayerMask: 1
|
||||
m_Lightmapping: 4
|
||||
m_LightShadowCasterMode: 0
|
||||
m_AreaSize: {x: 1, y: 1}
|
||||
m_BounceIntensity: 1
|
||||
m_ColorTemperature: 6570
|
||||
m_UseColorTemperature: 0
|
||||
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_UseBoundingSphereOverride: 0
|
||||
m_UseViewFrustumForShadowCasterCull: 1
|
||||
m_ForceVisible: 0
|
||||
m_ShadowRadius: 0
|
||||
m_ShadowAngle: 0
|
||||
m_LightUnit: 1
|
||||
m_LuxAtDistance: 1
|
||||
m_EnableSpotReflector: 1
|
||||
--- !u!4 &1847134983
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1847134981}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
|
||||
m_LocalPosition: {x: 0, y: 3, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Roots:
|
||||
- {fileID: 1109769282}
|
||||
- {fileID: 1847134983}
|
||||
- {fileID: 942839826}
|
||||
12
Assets/Scenes/MiniGame-FlappyBird.unity.meta
Normal file
12
Assets/Scenes/MiniGame-FlappyBird.unity.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
<<<<<<<< HEAD:Assets/Materials.meta
|
||||
guid: 2230bf768ecb84610af77bea6cdd7074
|
||||
folderAsset: yes
|
||||
========
|
||||
guid: 15742d157dfd1e42fb34ae90434eee41
|
||||
>>>>>>>> origin/main:Assets/Scenes/MiniGame-FlappyBird.unity.meta
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
316
Assets/Scenes/MiniGame-Satelit.unity
Normal file
316
Assets/Scenes/MiniGame-Satelit.unity
Normal file
@@ -0,0 +1,316 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!29 &1
|
||||
OcclusionCullingSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_OcclusionBakeSettings:
|
||||
smallestOccluder: 5
|
||||
smallestHole: 0.25
|
||||
backfaceThreshold: 100
|
||||
m_SceneGUID: 00000000000000000000000000000000
|
||||
m_OcclusionCullingData: {fileID: 0}
|
||||
--- !u!104 &2
|
||||
RenderSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 10
|
||||
m_Fog: 0
|
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
m_FogMode: 3
|
||||
m_FogDensity: 0.01
|
||||
m_LinearFogStart: 0
|
||||
m_LinearFogEnd: 300
|
||||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||
m_AmbientIntensity: 1
|
||||
m_AmbientMode: 0
|
||||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_HaloStrength: 0.5
|
||||
m_FlareStrength: 1
|
||||
m_FlareFadeSpeed: 3
|
||||
m_HaloTexture: {fileID: 0}
|
||||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_DefaultReflectionMode: 0
|
||||
m_DefaultReflectionResolution: 128
|
||||
m_ReflectionBounces: 1
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 0}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 13
|
||||
m_BakeOnSceneLoad: 0
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_EnvironmentLightingMode: 0
|
||||
m_EnableBakedLightmaps: 1
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_LightmapEditorSettings:
|
||||
serializedVersion: 12
|
||||
m_Resolution: 2
|
||||
m_BakeResolution: 40
|
||||
m_AtlasSize: 1024
|
||||
m_AO: 0
|
||||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAmbientOcclusion: 0
|
||||
m_Padding: 2
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
m_TextureCompression: 1
|
||||
m_ReflectionCompression: 2
|
||||
m_MixedBakeMode: 2
|
||||
m_BakeBackend: 1
|
||||
m_PVRSampling: 1
|
||||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 512
|
||||
m_PVRBounces: 2
|
||||
m_PVREnvironmentSampleCount: 256
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_PVRFilteringMode: 1
|
||||
m_PVRDenoiserTypeDirect: 1
|
||||
m_PVRDenoiserTypeIndirect: 1
|
||||
m_PVRDenoiserTypeAO: 1
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVREnvironmentMIS: 1
|
||||
m_PVRCulling: 1
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 1
|
||||
m_PVRFilteringGaussRadiusAO: 1
|
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_ExportTrainingData: 0
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_LightProbeSampleCountMultiplier: 4
|
||||
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_LightingSettings: {fileID: 0}
|
||||
--- !u!196 &4
|
||||
NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 3
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
agentSlope: 45
|
||||
agentClimb: 0.4
|
||||
ledgeDropHeight: 0
|
||||
maxJumpAcrossDistance: 0
|
||||
minRegionArea: 2
|
||||
manualCellSize: 0
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
buildHeightMesh: 0
|
||||
maxJobWorkers: 0
|
||||
preserveTilesOutsideBounds: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &1353791398
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1353791400}
|
||||
- component: {fileID: 1353791399}
|
||||
m_Layer: 0
|
||||
m_Name: Directional Light
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!108 &1353791399
|
||||
Light:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1353791398}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 11
|
||||
m_Type: 1
|
||||
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
|
||||
m_Intensity: 1
|
||||
m_Range: 10
|
||||
m_SpotAngle: 30
|
||||
m_InnerSpotAngle: 21.80208
|
||||
m_CookieSize: 10
|
||||
m_Shadows:
|
||||
m_Type: 2
|
||||
m_Resolution: -1
|
||||
m_CustomResolution: -1
|
||||
m_Strength: 1
|
||||
m_Bias: 0.05
|
||||
m_NormalBias: 0.4
|
||||
m_NearPlane: 0.2
|
||||
m_CullingMatrixOverride:
|
||||
e00: 1
|
||||
e01: 0
|
||||
e02: 0
|
||||
e03: 0
|
||||
e10: 0
|
||||
e11: 1
|
||||
e12: 0
|
||||
e13: 0
|
||||
e20: 0
|
||||
e21: 0
|
||||
e22: 1
|
||||
e23: 0
|
||||
e30: 0
|
||||
e31: 0
|
||||
e32: 0
|
||||
e33: 1
|
||||
m_UseCullingMatrixOverride: 0
|
||||
m_Cookie: {fileID: 0}
|
||||
m_DrawHalo: 0
|
||||
m_Flare: {fileID: 0}
|
||||
m_RenderMode: 0
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingLayerMask: 1
|
||||
m_Lightmapping: 4
|
||||
m_LightShadowCasterMode: 0
|
||||
m_AreaSize: {x: 1, y: 1}
|
||||
m_BounceIntensity: 1
|
||||
m_ColorTemperature: 6570
|
||||
m_UseColorTemperature: 0
|
||||
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_UseBoundingSphereOverride: 0
|
||||
m_UseViewFrustumForShadowCasterCull: 1
|
||||
m_ForceVisible: 0
|
||||
m_ShadowRadius: 0
|
||||
m_ShadowAngle: 0
|
||||
m_LightUnit: 1
|
||||
m_LuxAtDistance: 1
|
||||
m_EnableSpotReflector: 1
|
||||
--- !u!4 &1353791400
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1353791398}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
|
||||
m_LocalPosition: {x: 0, y: 3, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
|
||||
--- !u!1 &1502660080
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1502660083}
|
||||
- component: {fileID: 1502660082}
|
||||
- component: {fileID: 1502660081}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!81 &1502660081
|
||||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1502660080}
|
||||
m_Enabled: 1
|
||||
--- !u!20 &1502660082
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1502660080}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_ClearFlags: 1
|
||||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
||||
m_projectionMatrixMode: 1
|
||||
m_GateFitMode: 2
|
||||
m_FOVAxisMode: 0
|
||||
m_Iso: 200
|
||||
m_ShutterSpeed: 0.005
|
||||
m_Aperture: 16
|
||||
m_FocusDistance: 10
|
||||
m_FocalLength: 50
|
||||
m_BladeCount: 5
|
||||
m_Curvature: {x: 2, y: 11}
|
||||
m_BarrelClipping: 0.25
|
||||
m_Anamorphism: 0
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
near clip plane: 0.3
|
||||
far clip plane: 1000
|
||||
field of view: 60
|
||||
orthographic: 0
|
||||
orthographic size: 5
|
||||
m_Depth: -1
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingPath: -1
|
||||
m_TargetTexture: {fileID: 0}
|
||||
m_TargetDisplay: 0
|
||||
m_TargetEye: 3
|
||||
m_HDR: 1
|
||||
m_AllowMSAA: 1
|
||||
m_AllowDynamicResolution: 0
|
||||
m_ForceIntoRT: 0
|
||||
m_OcclusionCulling: 1
|
||||
m_StereoConvergence: 10
|
||||
m_StereoSeparation: 0.022
|
||||
--- !u!4 &1502660083
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1502660080}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Roots:
|
||||
- {fileID: 1502660083}
|
||||
- {fileID: 1353791400}
|
||||
7
Assets/Scenes/MiniGame-Satelit.unity.meta
Normal file
7
Assets/Scenes/MiniGame-Satelit.unity.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 070c4a89027b6e9a28e16859d55f8c66
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
435
Assets/Scenes/MiniGame-ThrowInHole.unity
Normal file
435
Assets/Scenes/MiniGame-ThrowInHole.unity
Normal file
@@ -0,0 +1,435 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!29 &1
|
||||
OcclusionCullingSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_OcclusionBakeSettings:
|
||||
smallestOccluder: 5
|
||||
smallestHole: 0.25
|
||||
backfaceThreshold: 100
|
||||
m_SceneGUID: 00000000000000000000000000000000
|
||||
m_OcclusionCullingData: {fileID: 0}
|
||||
--- !u!104 &2
|
||||
RenderSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 10
|
||||
m_Fog: 0
|
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
m_FogMode: 3
|
||||
m_FogDensity: 0.01
|
||||
m_LinearFogStart: 0
|
||||
m_LinearFogEnd: 300
|
||||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||
m_AmbientIntensity: 1
|
||||
m_AmbientMode: 0
|
||||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_HaloStrength: 0.5
|
||||
m_FlareStrength: 1
|
||||
m_FlareFadeSpeed: 3
|
||||
m_HaloTexture: {fileID: 0}
|
||||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_DefaultReflectionMode: 0
|
||||
m_DefaultReflectionResolution: 128
|
||||
m_ReflectionBounces: 1
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 0}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 13
|
||||
m_BakeOnSceneLoad: 0
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_EnvironmentLightingMode: 0
|
||||
m_EnableBakedLightmaps: 1
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_LightmapEditorSettings:
|
||||
serializedVersion: 12
|
||||
m_Resolution: 2
|
||||
m_BakeResolution: 40
|
||||
m_AtlasSize: 1024
|
||||
m_AO: 0
|
||||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAmbientOcclusion: 0
|
||||
m_Padding: 2
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
m_TextureCompression: 1
|
||||
m_ReflectionCompression: 2
|
||||
m_MixedBakeMode: 2
|
||||
m_BakeBackend: 1
|
||||
m_PVRSampling: 1
|
||||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 512
|
||||
m_PVRBounces: 2
|
||||
m_PVREnvironmentSampleCount: 256
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_PVRFilteringMode: 1
|
||||
m_PVRDenoiserTypeDirect: 1
|
||||
m_PVRDenoiserTypeIndirect: 1
|
||||
m_PVRDenoiserTypeAO: 1
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVREnvironmentMIS: 1
|
||||
m_PVRCulling: 1
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 1
|
||||
m_PVRFilteringGaussRadiusAO: 1
|
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_ExportTrainingData: 0
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_LightProbeSampleCountMultiplier: 4
|
||||
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_LightingSettings: {fileID: 0}
|
||||
--- !u!196 &4
|
||||
NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 3
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
agentSlope: 45
|
||||
agentClimb: 0.4
|
||||
ledgeDropHeight: 0
|
||||
maxJumpAcrossDistance: 0
|
||||
minRegionArea: 2
|
||||
manualCellSize: 0
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
buildHeightMesh: 0
|
||||
maxJobWorkers: 0
|
||||
preserveTilesOutsideBounds: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &110756971
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 110756974}
|
||||
- component: {fileID: 110756973}
|
||||
- component: {fileID: 110756972}
|
||||
m_Layer: 0
|
||||
m_Name: ThrowInHole
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &110756972
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 110756971}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 071f79f81861c2741a92d8b044457d94, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::ObjectSpawner
|
||||
objectPrefabs: []
|
||||
holePrefab: {fileID: 0}
|
||||
objectCount: 3
|
||||
holeCount: 1
|
||||
holesMove: 0
|
||||
holeMoveSpeed: 2
|
||||
minX: -3.5
|
||||
maxX: 3.5
|
||||
minY: -5
|
||||
maxY: 4
|
||||
objectParent: {fileID: 0}
|
||||
holeParent: {fileID: 0}
|
||||
--- !u!114 &110756973
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 110756971}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a819c02c3679b5a449b41052d2e6b3c9, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::LevelManager
|
||||
itemsToScore: 3
|
||||
OnAllItemsScored:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!4 &110756974
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 110756971}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0.21709, y: 0, z: 3.25431}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &131462842
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 131462844}
|
||||
- component: {fileID: 131462843}
|
||||
m_Layer: 0
|
||||
m_Name: Directional Light
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!108 &131462843
|
||||
Light:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 131462842}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 11
|
||||
m_Type: 1
|
||||
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
|
||||
m_Intensity: 1
|
||||
m_Range: 10
|
||||
m_SpotAngle: 30
|
||||
m_InnerSpotAngle: 21.80208
|
||||
m_CookieSize: 10
|
||||
m_Shadows:
|
||||
m_Type: 2
|
||||
m_Resolution: -1
|
||||
m_CustomResolution: -1
|
||||
m_Strength: 1
|
||||
m_Bias: 0.05
|
||||
m_NormalBias: 0.4
|
||||
m_NearPlane: 0.2
|
||||
m_CullingMatrixOverride:
|
||||
e00: 1
|
||||
e01: 0
|
||||
e02: 0
|
||||
e03: 0
|
||||
e10: 0
|
||||
e11: 1
|
||||
e12: 0
|
||||
e13: 0
|
||||
e20: 0
|
||||
e21: 0
|
||||
e22: 1
|
||||
e23: 0
|
||||
e30: 0
|
||||
e31: 0
|
||||
e32: 0
|
||||
e33: 1
|
||||
m_UseCullingMatrixOverride: 0
|
||||
m_Cookie: {fileID: 0}
|
||||
m_DrawHalo: 0
|
||||
m_Flare: {fileID: 0}
|
||||
m_RenderMode: 0
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingLayerMask: 1
|
||||
m_Lightmapping: 4
|
||||
m_LightShadowCasterMode: 0
|
||||
m_AreaSize: {x: 1, y: 1}
|
||||
m_BounceIntensity: 1
|
||||
m_ColorTemperature: 6570
|
||||
m_UseColorTemperature: 0
|
||||
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_UseBoundingSphereOverride: 0
|
||||
m_UseViewFrustumForShadowCasterCull: 1
|
||||
m_ForceVisible: 0
|
||||
m_ShadowRadius: 0
|
||||
m_ShadowAngle: 0
|
||||
m_LightUnit: 1
|
||||
m_LuxAtDistance: 1
|
||||
m_EnableSpotReflector: 1
|
||||
--- !u!4 &131462844
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 131462842}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
|
||||
m_LocalPosition: {x: 0, y: 3, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
|
||||
--- !u!1 &168381609
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 168381612}
|
||||
- component: {fileID: 168381611}
|
||||
- component: {fileID: 168381610}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!81 &168381610
|
||||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 168381609}
|
||||
m_Enabled: 1
|
||||
--- !u!20 &168381611
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 168381609}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_ClearFlags: 1
|
||||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
||||
m_projectionMatrixMode: 1
|
||||
m_GateFitMode: 2
|
||||
m_FOVAxisMode: 0
|
||||
m_Iso: 200
|
||||
m_ShutterSpeed: 0.005
|
||||
m_Aperture: 16
|
||||
m_FocusDistance: 10
|
||||
m_FocalLength: 50
|
||||
m_BladeCount: 5
|
||||
m_Curvature: {x: 2, y: 11}
|
||||
m_BarrelClipping: 0.25
|
||||
m_Anamorphism: 0
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
near clip plane: 0.3
|
||||
far clip plane: 1000
|
||||
field of view: 60
|
||||
orthographic: 0
|
||||
orthographic size: 5
|
||||
m_Depth: -1
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingPath: -1
|
||||
m_TargetTexture: {fileID: 0}
|
||||
m_TargetDisplay: 0
|
||||
m_TargetEye: 3
|
||||
m_HDR: 1
|
||||
m_AllowMSAA: 1
|
||||
m_AllowDynamicResolution: 0
|
||||
m_ForceIntoRT: 0
|
||||
m_OcclusionCulling: 1
|
||||
m_StereoConvergence: 10
|
||||
m_StereoSeparation: 0.022
|
||||
--- !u!4 &168381612
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 168381609}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1787410631
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1787410633}
|
||||
- component: {fileID: 1787410632}
|
||||
m_Layer: 0
|
||||
m_Name: Satelit
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1787410632
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1787410631}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 375a1ddbfc192413b48906965449af87, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::SatelitTask
|
||||
--- !u!4 &1787410633
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1787410631}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0.21709, y: 0, z: 3.25431}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Roots:
|
||||
- {fileID: 168381612}
|
||||
- {fileID: 131462844}
|
||||
- {fileID: 110756974}
|
||||
- {fileID: 1787410633}
|
||||
7
Assets/Scenes/MiniGame-ThrowInHole.unity.meta
Normal file
7
Assets/Scenes/MiniGame-ThrowInHole.unity.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d34a25fb4e1abb111a62c1802c0477d4
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -38,12 +38,12 @@ RenderSettings:
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 0}
|
||||
m_IndirectSpecularColor: {r: 0.18028378, g: 0.22571412, b: 0.30692285, a: 1}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 12
|
||||
serializedVersion: 13
|
||||
m_BakeOnSceneLoad: 0
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
@@ -119,6 +119,100 @@ NavMeshSettings:
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &90211612
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 90211614}
|
||||
- component: {fileID: 90211613}
|
||||
m_Layer: 0
|
||||
m_Name: GameManager
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &90211613
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 90211612}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 22bf82e679cf6e1419440d236360ba3b, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::GameManager
|
||||
displayName: Hrac
|
||||
firstMenuScene: main menu asi idk lol
|
||||
buildingSettings:
|
||||
ResidentialBuildingsMat: {fileID: 2100000, guid: d52b379b821c9ed624c15e9bd1420204, type: 2}
|
||||
ResidentialBuildingHeight: 5
|
||||
CommercialBuildingsMat: {fileID: 2100000, guid: cade7a38e47c1b875f65cda2cf3b77c5, type: 2}
|
||||
CommercialBuildingHeight: 5
|
||||
IndustrialBuildingsMat: {fileID: 2100000, guid: 4179f65aa390e873e96480f462d9fc3a, type: 2}
|
||||
IndustrialBuildingHeight: 5
|
||||
DefaultBuildingMat: {fileID: 2100000, guid: 1a50367e9d7460063dec2f85d18dde35, type: 2}
|
||||
DefaultBuildingHeight: 5
|
||||
pathwaySettings:
|
||||
FootwayMat: {fileID: 2100000, guid: a40e1c43763bb5c9b97d7999ac3c6f9d, type: 2}
|
||||
FootwayWidth: 2
|
||||
PathMat: {fileID: 2100000, guid: b8c268a7008ed5031da152380545235d, type: 2}
|
||||
PathWidth: 2
|
||||
StepsMat: {fileID: 2100000, guid: b8c268a7008ed5031da152380545235d, type: 2}
|
||||
StepsWidth: 2
|
||||
CyclewayMat: {fileID: 2100000, guid: 9ecf5df4890c0486002bf43baf684bab, type: 2}
|
||||
CyclewayWidth: 2
|
||||
PedestrianMat: {fileID: 2100000, guid: b8c268a7008ed5031da152380545235d, type: 2}
|
||||
PedestrianWidth: 2
|
||||
RoadMat: {fileID: 2100000, guid: f831020d62c88ea9dfde83f98d539f55, type: 2}
|
||||
RoadWidth: 2
|
||||
ServiceMat: {fileID: 2100000, guid: f831020d62c88ea9dfde83f98d539f55, type: 2}
|
||||
ServiceWidth: 2
|
||||
ResidentialMat: {fileID: 2100000, guid: d52b379b821c9ed624c15e9bd1420204, type: 2}
|
||||
ResidentialWidth: 2
|
||||
TrackMat: {fileID: 2100000, guid: b8c268a7008ed5031da152380545235d, type: 2}
|
||||
TrackWidth: 2
|
||||
DefaultMat: {fileID: 2100000, guid: 57c69638d7afff039a48123e7b4ade7b, type: 2}
|
||||
DefaultWidth: 2
|
||||
areaSettings:
|
||||
ParkMat: {fileID: 2100000, guid: 77b772132676975a3462e3aca2cfac0c, type: 2}
|
||||
GardenMat: {fileID: 2100000, guid: 77b772132676975a3462e3aca2cfac0c, type: 2}
|
||||
PlaygroundMat: {fileID: 2100000, guid: 77b772132676975a3462e3aca2cfac0c, type: 2}
|
||||
ForestMat: {fileID: 2100000, guid: ef4ea3212671d276649ca366ed6e7f0c, type: 2}
|
||||
GrassMat: {fileID: 2100000, guid: 77b772132676975a3462e3aca2cfac0c, type: 2}
|
||||
WaterMat: {fileID: 2100000, guid: eff7cc5b4e29f7918320d3979c3d0cb1, type: 2}
|
||||
DefaultMat: {fileID: 2100000, guid: 57c69638d7afff039a48123e7b4ade7b, type: 2}
|
||||
pendingRadius: 500
|
||||
pendingImpostorCount: 1
|
||||
pendingTaskCount: 5
|
||||
minigameScenes:
|
||||
- MiniGame-Kabely V10
|
||||
- MiniGame-insertkeys
|
||||
- MiniGame-FlappyBird
|
||||
- MiniGame-ThrowInHole
|
||||
- MiniGame-Satelit
|
||||
testMode: 0
|
||||
--- !u!4 &90211614
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 90211612}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0.21709, y: 0, z: 3.25431}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &330585543
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -224,38 +318,6 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_RenderShadows: 1
|
||||
m_RequiresDepthTextureOption: 2
|
||||
m_RequiresOpaqueTextureOption: 2
|
||||
m_CameraType: 0
|
||||
m_Cameras: []
|
||||
m_RendererIndex: -1
|
||||
m_VolumeLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 1
|
||||
m_VolumeTrigger: {fileID: 0}
|
||||
m_VolumeFrameworkUpdateModeOption: 2
|
||||
m_RenderPostProcessing: 1
|
||||
m_Antialiasing: 0
|
||||
m_AntialiasingQuality: 2
|
||||
m_StopNaN: 0
|
||||
m_Dithering: 0
|
||||
m_ClearDepth: 1
|
||||
m_AllowXRRendering: 1
|
||||
m_AllowHDROutput: 1
|
||||
m_UseScreenCoordOverride: 0
|
||||
m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_RequiresDepthTexture: 0
|
||||
m_RequiresColorTexture: 0
|
||||
m_Version: 2
|
||||
m_TaaSettings:
|
||||
quality: 3
|
||||
frameInfluence: 0.1
|
||||
jitterScale: 1
|
||||
mipBias: 0
|
||||
varianceClampScale: 0.9
|
||||
contrastAdaptiveSharpening: 0
|
||||
--- !u!1 &410087039
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -336,6 +398,9 @@ Light:
|
||||
m_ForceVisible: 0
|
||||
m_ShadowRadius: 0
|
||||
m_ShadowAngle: 0
|
||||
m_LightUnit: 1
|
||||
m_LuxAtDistance: 1
|
||||
m_EnableSpotReflector: 1
|
||||
--- !u!4 &410087041
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -363,17 +428,6 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Version: 3
|
||||
m_UsePipelineSettings: 1
|
||||
m_AdditionalLightsShadowResolutionTier: 2
|
||||
m_LightLayerMask: 1
|
||||
m_RenderingLayers: 1
|
||||
m_CustomShadowLayers: 0
|
||||
m_ShadowLayerMask: 1
|
||||
m_ShadowRenderingLayers: 1
|
||||
m_LightCookieSize: {x: 1, y: 1}
|
||||
m_LightCookieOffset: {x: 0, y: 0}
|
||||
m_SoftShadowQuality: 1
|
||||
--- !u!1 &832575517
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -403,11 +457,6 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 172515602e62fb746b5d573b38a5fe58, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IsGlobal: 1
|
||||
priority: 0
|
||||
blendDistance: 0
|
||||
weight: 1
|
||||
sharedProfile: {fileID: 11400000, guid: 10fc4df2da32a41aaa32d77bc913491c, type: 2}
|
||||
--- !u!4 &832575519
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -430,3 +479,4 @@ SceneRoots:
|
||||
- {fileID: 330585546}
|
||||
- {fileID: 410087041}
|
||||
- {fileID: 832575519}
|
||||
- {fileID: 90211614}
|
||||
|
||||
@@ -830,7 +830,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 1600, y: 900}
|
||||
|
||||
49
Assets/Scripts/ConfirmLeaveUI.cs
Normal file
49
Assets/Scripts/ConfirmLeaveUI.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
/// <summary>
|
||||
/// Attach to a manager GameObject in "are u sure.unity".
|
||||
/// "yes" = confirm leave lobby and go to main menu.
|
||||
/// "no" = go back to previous lobby scene.
|
||||
/// </summary>
|
||||
public class ConfirmLeaveUI : MonoBehaviour
|
||||
{
|
||||
[Header("Optional refs (auto-found by name if null)")]
|
||||
public Button yesButton;
|
||||
public Button noButton;
|
||||
|
||||
[Tooltip("Scene to load after leaving lobby")]
|
||||
public string mainMenuScene = "main menu asi idk lol";
|
||||
|
||||
[Tooltip("Scene to go back to when player presses No")]
|
||||
public string previousScene = "create";
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (yesButton == null)
|
||||
{
|
||||
var go = GameObject.Find("yes");
|
||||
if (go != null) yesButton = go.GetComponent<Button>();
|
||||
}
|
||||
if (noButton == null)
|
||||
{
|
||||
var go = GameObject.Find("no");
|
||||
if (go != null) noButton = go.GetComponent<Button>();
|
||||
}
|
||||
|
||||
if (yesButton != null) yesButton.onClick.AddListener(OnYesClicked);
|
||||
if (noButton != null) noButton.onClick.AddListener(OnNoClicked);
|
||||
}
|
||||
|
||||
private void OnYesClicked()
|
||||
{
|
||||
GameManager.Instance?.LeaveLobbyButton();
|
||||
SceneManager.LoadScene(mainMenuScene, LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
private void OnNoClicked()
|
||||
{
|
||||
SceneManager.LoadScene(previousScene, LoadSceneMode.Single);
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/ConfirmLeaveUI.cs.meta
Normal file
11
Assets/Scripts/ConfirmLeaveUI.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cef2287cbad97c8b8a4451dfb6a8e472
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,68 +0,0 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
public class GPSManager : MonoBehaviour
|
||||
{
|
||||
[Header("GPS settings")]
|
||||
public float Accuracy = 10f;
|
||||
public float UpdateDistance = 5f;
|
||||
public int MaxWait = 20;
|
||||
|
||||
[Header("GPS coordinates")]
|
||||
private double[] LastCoords = new double[2];
|
||||
private double[] FailsafeCoords = new double[] { 50.7727878, 15.0718625 };
|
||||
private double? LastTime;
|
||||
|
||||
void Start()
|
||||
{
|
||||
StartCoroutine(UpdateGPS());
|
||||
}
|
||||
|
||||
public double[] GetLastCoords()
|
||||
{
|
||||
if (LastCoords[0] == 0 && LastCoords[1] == 0) { return FailsafeCoords; }
|
||||
return LastCoords;
|
||||
}
|
||||
IEnumerator UpdateGPS()
|
||||
{
|
||||
if (!Input.location.isEnabledByUser)
|
||||
{
|
||||
Debug.Log("GPS not enabled by user");
|
||||
LastCoords = FailsafeCoords;
|
||||
LastTime = null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
Input.location.Start(Accuracy, UpdateDistance);
|
||||
|
||||
while (Input.location.status == LocationServiceStatus.Initializing && MaxWait > 0)
|
||||
{
|
||||
yield return new WaitForSeconds(1);
|
||||
MaxWait--;
|
||||
}
|
||||
|
||||
if (MaxWait < 1)
|
||||
{
|
||||
Debug.Log("GPS timed out");
|
||||
LastCoords = FailsafeCoords;
|
||||
LastTime = null;
|
||||
yield break;
|
||||
}
|
||||
if (Input.location.status == LocationServiceStatus.Failed)
|
||||
{
|
||||
Debug.Log("GPS failed to determine device location");
|
||||
LastCoords = FailsafeCoords;
|
||||
LastTime = null;
|
||||
yield break;
|
||||
}
|
||||
else
|
||||
{
|
||||
LastCoords[0] = Input.location.lastData.latitude;
|
||||
LastCoords[1] = Input.location.lastData.longitude;
|
||||
LastTime = Input.location.lastData.timestamp;
|
||||
|
||||
Debug.Log("GPS location: " + LastCoords[0] + ", " + LastCoords[1] + " (time: " + LastTime + ")");
|
||||
}
|
||||
yield return StartCoroutine(UpdateGPS());
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f23d4bd550984f49b2c2a8bcbe09106
|
||||
123
Assets/Scripts/GameState.cs
Normal file
123
Assets/Scripts/GameState.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using GeoSus.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Sub-phase derived client-side from the timestamps in MeetingStartedPayload.
|
||||
/// Server doesn't broadcast a discrete "discussion ended" event - it embeds
|
||||
/// DiscussionEndTime and VotingEndTime in the meeting-start event and gates
|
||||
/// vote acceptance on those timestamps. We compute the matching client view
|
||||
/// by comparing UtcNow to those values every frame.
|
||||
/// </summary>
|
||||
public enum MeetingSubPhase
|
||||
{
|
||||
Arrival, // before ArrivalDeadline; players are still en route to meeting point
|
||||
Discussion, // arrival deadline passed; talk only, votes server-rejected
|
||||
Voting, // discussion ended; votes accepted until VotingEndTime
|
||||
Resolved // VotingClosed received OR votingEndTime in the past
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Single source of truth for all in-game state on the client.
|
||||
/// Updated exclusively by GameManager_Network; read by GameManager_UI.
|
||||
/// </summary>
|
||||
public class GameState
|
||||
{
|
||||
// ── Phase / Role ──────────────────────────────────────────────────────────
|
||||
public GamePhase Phase { get; set; } = GamePhase.Lobby;
|
||||
public PlayerRole? MyRole { get; set; }
|
||||
public bool IsDead { get; set; }
|
||||
|
||||
// ── Settings (P13b) ───────────────────────────────────────────────────────
|
||||
/// <summary>
|
||||
/// Per-lobby settings snapshot from the server. Populated on
|
||||
/// LobbyJoined / LobbyCreated; immutable for the lifetime of the lobby.
|
||||
/// Null on old server builds - callers must use null-coalescing fallbacks
|
||||
/// to whatever default they previously hardcoded.
|
||||
/// </summary>
|
||||
public GameSettings Settings { get; set; }
|
||||
|
||||
// ── Tasks ─────────────────────────────────────────────────────────────────
|
||||
public List<GameTask> MyTasks { get; set; } = new List<GameTask>();
|
||||
public HashSet<string> MyCompletedTaskIds { get; set; } = new HashSet<string>();
|
||||
public int TotalCompleted { get; set; }
|
||||
public int TotalRequired { get; set; }
|
||||
|
||||
// ── Players ───────────────────────────────────────────────────────────────
|
||||
public List<PlayerInfo> Players { get; set; } = new List<PlayerInfo>();
|
||||
|
||||
// ── Meeting ───────────────────────────────────────────────────────────────
|
||||
public MeetingStartedPayload ActiveMeeting { get; set; }
|
||||
public VotingClosedPayload LastVoteResult { get; set; }
|
||||
public HashSet<string> VotedPlayerIds { get; set; } = new HashSet<string>();
|
||||
public HashSet<string> ArrivedPlayerIds { get; set; } = new HashSet<string>();
|
||||
|
||||
/// <summary>Per-voter latest vote target. Voter ClientUuid -> target ClientUuid, or VoteSkip for skip.</summary>
|
||||
public Dictionary<string, string> VoterTargets { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>Live vote tallies, keyed by target ClientUuid or VoteSkip. Derived from VoterTargets.</summary>
|
||||
public Dictionary<string, int> VoteTallies { get; set; } = new Dictionary<string, int>();
|
||||
|
||||
/// <summary>Local player's latest vote target. Null = haven't voted; VoteSkip = skip; otherwise target ClientUuid.</summary>
|
||||
public string MyVoteTarget { get; set; }
|
||||
|
||||
/// <summary>Sentinel for "skip" votes in VoterTargets / VoteTallies / MyVoteTarget.</summary>
|
||||
public const string VoteSkip = "__SKIP__";
|
||||
|
||||
/// <summary>
|
||||
/// Derive the current meeting sub-phase from ActiveMeeting + LastVoteResult.
|
||||
/// Returns Arrival when no meeting is active (caller should also gate on Phase).
|
||||
/// </summary>
|
||||
public MeetingSubPhase GetMeetingSubPhase()
|
||||
{
|
||||
if (LastVoteResult != null) return MeetingSubPhase.Resolved;
|
||||
var m = ActiveMeeting;
|
||||
if (m == null) return MeetingSubPhase.Arrival;
|
||||
var now = DateTime.UtcNow;
|
||||
if (now >= m.VotingEndTime) return MeetingSubPhase.Resolved;
|
||||
if (m.DiscussionEndTime.HasValue && now < m.DiscussionEndTime.Value)
|
||||
{
|
||||
// Server enforces: arrival deadline AND discussion-end gate voting.
|
||||
// While arrival is still open we surface "Arrival" so players know
|
||||
// others may still be travelling; once arrival deadline passes we
|
||||
// surface "Discussion" until the voting window opens.
|
||||
return now < m.ArrivalDeadline ? MeetingSubPhase.Arrival : MeetingSubPhase.Discussion;
|
||||
}
|
||||
return MeetingSubPhase.Voting;
|
||||
}
|
||||
|
||||
/// <summary>End-of-current-sub-phase boundary as a UTC DateTime, used for countdown rendering.</summary>
|
||||
public DateTime GetMeetingSubPhaseDeadline(MeetingSubPhase sub)
|
||||
{
|
||||
var m = ActiveMeeting;
|
||||
if (m == null) return DateTime.UtcNow;
|
||||
switch (sub)
|
||||
{
|
||||
case MeetingSubPhase.Arrival:
|
||||
return m.ArrivalDeadline;
|
||||
case MeetingSubPhase.Discussion:
|
||||
return m.DiscussionEndTime ?? m.VotingEndTime;
|
||||
case MeetingSubPhase.Voting:
|
||||
return m.VotingEndTime;
|
||||
default:
|
||||
return m.VotingEndTime;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Sabotage ──────────────────────────────────────────────────────────────
|
||||
public SabotageStartedPayload ActiveSabotage { get; set; }
|
||||
|
||||
/// <summary>StationIds currently being repaired (server broadcasts RepairStarted/RepairStopped).</summary>
|
||||
public HashSet<string> ActiveRepairs { get; set; } = new HashSet<string>();
|
||||
|
||||
// ── End game ──────────────────────────────────────────────────────────────
|
||||
public GameEndedPayload GameEndData { get; set; }
|
||||
|
||||
// ── Kill cooldown (tracked by GameManager, reflected here for UI) ─────────
|
||||
public float KillCooldownRemaining { get; set; }
|
||||
|
||||
// ── Notification (toast) ─────────────────────────────────────────────────
|
||||
public string ToastMessage { get; set; }
|
||||
public float ToastExpiry { get; set; } // UnityEngine.Time.time
|
||||
}
|
||||
|
||||
2
Assets/Scripts/GameState.cs.meta
Normal file
2
Assets/Scripts/GameState.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82b3963a05498b68baf483476d0d81f4
|
||||
559
Assets/Scripts/HostLobbyUI.cs
Normal file
559
Assets/Scripts/HostLobbyUI.cs
Normal file
@@ -0,0 +1,559 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.SceneManagement;
|
||||
using TMPro;
|
||||
using GeoSus.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Lives on host lobby.unity - the *settings* screen reached when the host
|
||||
/// chooses "Host" from the main menu. Originally three fixed-position
|
||||
/// controls (radius slider + impostor/task steppers). P13c expanded this to
|
||||
/// a full scrollable settings panel covering every per-lobby setting the
|
||||
/// server accepts: round shape, distances (kill / report / task / meeting /
|
||||
/// emergency / repair), cooldowns (kill / emergency / sabotage), meeting
|
||||
/// phase timings (arrival / late / discussion / voting), and sabotage
|
||||
/// timings (comms / meltdown / repair hold). All values get accumulated
|
||||
/// into GameManager.pendingSettings (a GameSettingsOverrides) and shipped
|
||||
/// with the CreateLobby request so the server can stamp them into the
|
||||
/// per-lobby snapshot at lobby creation time.
|
||||
///
|
||||
/// The scene already contains the art team's named UI:
|
||||
/// Canvas
|
||||
/// ├── Panel - full-screen background
|
||||
/// ├── RawImage - decorative logo
|
||||
/// ├── radius - TMP text "Game radius:\n" (HIDDEN, P13c)
|
||||
/// ├── idk - TMP text container (HIDDEN, P13c)
|
||||
/// ├── back - back button
|
||||
/// ├── Zmekole_geosusv2 - 3D globe object
|
||||
/// └── stvořit - "Create Lobby" button
|
||||
///
|
||||
/// We DO NOT destroy any of these. Instead we resolve them by name, wire
|
||||
/// the buttons, hide the now-unused labels, and inject a ScrollRect with
|
||||
/// the full settings panel anchored above the "stvořit" button. Anchor
|
||||
/// offsets are in the canvas's portrait reference units (1080x1920).
|
||||
/// </summary>
|
||||
public class HostLobbyUI : MonoBehaviour
|
||||
{
|
||||
// ── Colour palette (forwarded from UITheme) ──────────────────────────────
|
||||
static readonly Color C_TRACK = UITheme.SurfaceDim;
|
||||
static readonly Color C_FILL = UITheme.Accent;
|
||||
static readonly Color C_HANDLE = UITheme.TextHi;
|
||||
static readonly Color C_BTN_BG = UITheme.Surface;
|
||||
static readonly Color C_BTN_HI = UITheme.Accent;
|
||||
static readonly Color C_TEXT = UITheme.TextHi;
|
||||
static readonly Color C_HEADER = UITheme.Accent;
|
||||
|
||||
// ── Live values (mirrored into GameManager.pending* + pendingSettings) ──
|
||||
private float _radius = 500f;
|
||||
private int _impostors = 1;
|
||||
private int _tasks = 5;
|
||||
private int _maxPlayers = 10;
|
||||
private int _killDist = 5;
|
||||
private int _reportDist = 5;
|
||||
private int _taskDist = 5;
|
||||
private int _meetingArrival = 10;
|
||||
private int _emergencyCallRadius = 5;
|
||||
private int _repairDist = 5;
|
||||
private int _killCooldownS = 20;
|
||||
private int _emergencyCooldownS = 60;
|
||||
private int _maxEmergencyMeetings = 1;
|
||||
private int _arrivalBaseS = 60;
|
||||
private int _allowedLateS = 10;
|
||||
private int _discussionS = 60;
|
||||
private int _votingS = 60;
|
||||
private int _sabotageCooldownS = 60;
|
||||
private int _commsDurationS = 60;
|
||||
private int _meltdownDeadlineS = 90;
|
||||
private int _repairHoldS = 10;
|
||||
|
||||
// ── Layout constants ────────────────────────────────────────────────────
|
||||
const float ROW_HEIGHT = 130f;
|
||||
const float HEADER_HEIGHT = 90f;
|
||||
const float SLIDER_HEIGHT = 160f;
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Wire emoji fallback before any TMP component renders, so labels
|
||||
// with emoji glyphs display properly on the very first frame rather
|
||||
// than as tofu boxes that get fixed on a later refresh.
|
||||
UITheme.EnsureEmojiFontFallback();
|
||||
|
||||
var gm = GameManager.Instance;
|
||||
if (gm != null)
|
||||
{
|
||||
// Carry over whatever the host had previously selected (if they
|
||||
// back out and come back in, settings survive).
|
||||
_radius = (float)gm.pendingRadius;
|
||||
_impostors = gm.pendingImpostorCount;
|
||||
_tasks = gm.pendingTaskCount;
|
||||
|
||||
// Kick off GPS init NOW so by the time the host taps "Create
|
||||
// Lobby" we have a real position fix to seed the play area with.
|
||||
gm.inputSubsystem?.EnsureGPSStarted();
|
||||
}
|
||||
|
||||
var canvasGO = GameObject.Find("Canvas");
|
||||
if (canvasGO == null)
|
||||
{
|
||||
Debug.LogError("[HostLobbyUI] No Canvas found in host lobby scene.");
|
||||
return;
|
||||
}
|
||||
var canvas = canvasGO.transform;
|
||||
|
||||
// ── Bind existing scene buttons ─────────────────────────────────────
|
||||
var backBtn = FindButton(canvas, "back");
|
||||
if (backBtn != null)
|
||||
{
|
||||
backBtn.onClick.RemoveAllListeners();
|
||||
backBtn.onClick.AddListener(() => SceneManager.LoadScene("main menu asi idk lol"));
|
||||
}
|
||||
else Debug.LogWarning("[HostLobbyUI] 'back' button not found.");
|
||||
|
||||
var createBtn = FindButton(canvas, "stvořit");
|
||||
if (createBtn != null)
|
||||
{
|
||||
createBtn.onClick.RemoveAllListeners();
|
||||
createBtn.onClick.AddListener(OnCreateClicked);
|
||||
}
|
||||
else Debug.LogWarning("[HostLobbyUI] 'stvořit' button not found.");
|
||||
|
||||
// ── Hide the art team's "radius" / "idk" labels ─────────────────────
|
||||
// They were placeholders for the small set of settings we previously
|
||||
// exposed; the scrollable panel below now owns all of that real estate.
|
||||
var radiusLabel = FindTMP(canvas, "radius");
|
||||
if (radiusLabel != null) radiusLabel.gameObject.SetActive(false);
|
||||
var settingsLabel = FindTMP(canvas, "idk");
|
||||
if (settingsLabel != null) settingsLabel.gameObject.SetActive(false);
|
||||
|
||||
// ── Build the scrollable settings panel ────────────────────────────
|
||||
// Pass in the actual back/create button transforms so the scroll
|
||||
// bounds can be measured against them at runtime, instead of
|
||||
// hardcoded against canvas reference units that may or may not
|
||||
// match where the art team actually placed the buttons.
|
||||
BuildSettingsScroll(canvas,
|
||||
backBtn != null ? backBtn.transform : null,
|
||||
createBtn != null ? createBtn.transform : null);
|
||||
}
|
||||
|
||||
// ── Action handlers ──────────────────────────────────────────────────────
|
||||
|
||||
void OnCreateClicked()
|
||||
{
|
||||
var gm = GameManager.Instance;
|
||||
if (gm == null) return;
|
||||
// Keep the legacy flat fields populated for any caller that still
|
||||
// reads them directly, and ALSO populate pendingSettings with the
|
||||
// full override snapshot so the server can stamp it into the lobby.
|
||||
gm.pendingRadius = _radius;
|
||||
gm.pendingImpostorCount = _impostors;
|
||||
gm.pendingTaskCount = _tasks;
|
||||
gm.pendingSettings = BuildOverrides();
|
||||
gm.CreateLobbyButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pack the current control values into a wire-shape GameSettingsOverrides.
|
||||
/// Every field is non-null - the server treats null as "use my default",
|
||||
/// but since this UI exposes every field we always have a concrete value
|
||||
/// to ship. Time fields are exposed in seconds and converted to ms here.
|
||||
/// </summary>
|
||||
GameSettingsOverrides BuildOverrides()
|
||||
{
|
||||
return new GameSettingsOverrides
|
||||
{
|
||||
// Round shape
|
||||
MaxPlayers = _maxPlayers,
|
||||
ImpostorCount = _impostors,
|
||||
TaskCount = _tasks,
|
||||
// Distances (m)
|
||||
KillDistanceM = _killDist,
|
||||
ReportDistanceM = _reportDist,
|
||||
TaskStartDistanceM = _taskDist,
|
||||
MeetingArrivalRadiusM = _meetingArrival,
|
||||
EmergencyMeetingCallRadiusM = _emergencyCallRadius,
|
||||
RepairStationDistanceM = _repairDist,
|
||||
// Cooldowns / counts
|
||||
KillCooldownMs = _killCooldownS * 1000,
|
||||
EmergencyMeetingCooldownMs = _emergencyCooldownS * 1000,
|
||||
MaxEmergencyMeetingsPerPlayer = _maxEmergencyMeetings,
|
||||
// Meeting phases (ms)
|
||||
ArrivalBaseMs = _arrivalBaseS * 1000,
|
||||
AllowedLateMs = _allowedLateS * 1000,
|
||||
DiscussionPhaseMs = _discussionS * 1000,
|
||||
VotingPhaseMs = _votingS * 1000,
|
||||
// Sabotage
|
||||
SabotageCooldownMs = _sabotageCooldownS * 1000,
|
||||
CommsBlackoutDurationMs = _commsDurationS * 1000,
|
||||
CriticalMeltdownDeadlineMs = _meltdownDeadlineS * 1000,
|
||||
RepairStationHoldMs = _repairHoldS * 1000,
|
||||
};
|
||||
}
|
||||
|
||||
// ── Settings ScrollRect ─────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Build the scrollable panel: ScrollRect → Viewport (RectMask2D) →
|
||||
/// Content (VerticalLayoutGroup + ContentSizeFitter). Each setting goes
|
||||
/// in as a row with a LayoutElement.preferredHeight so the VLG can stack
|
||||
/// them and the ContentSizeFitter can compute the scroll height.
|
||||
/// Pattern mirrors InGameHUDBuilder.BuildMeetingScroll.
|
||||
///
|
||||
/// Bounds are MEASURED against the actual back/stvořit RectTransforms
|
||||
/// at runtime - not hardcoded against canvas reference units. The
|
||||
/// previous version pinned the scroll to 280..1700 in 1080x1920 ref
|
||||
/// space, which overlapped the create button on real layouts and made
|
||||
/// it un-tappable. We now place the scroll's bottom edge a fixed margin
|
||||
/// above stvořit's top edge, and its top edge a fixed margin below
|
||||
/// back's bottom edge, falling back to the old constants only if either
|
||||
/// button is missing.
|
||||
/// </summary>
|
||||
void BuildSettingsScroll(Transform canvas, Transform backT, Transform stvoritT)
|
||||
{
|
||||
// Force the canvas's layout to settle so GetWorldCorners returns the
|
||||
// real positioned rects rather than authored-time placeholders.
|
||||
Canvas.ForceUpdateCanvases();
|
||||
|
||||
const float MARGIN = 40f; // px (in canvas reference units) above/below buttons
|
||||
|
||||
var canvasRT = canvas as RectTransform;
|
||||
// Pivot offset so we can express Y as "from canvas bottom" rather
|
||||
// than the pivot-relative form InverseTransformPoint hands back.
|
||||
float pivotYOffset = canvasRT != null
|
||||
? canvasRT.pivot.y * canvasRT.rect.height
|
||||
: 0f;
|
||||
|
||||
float scrollBottomY = 280f; // safe fallback if stvořit is missing
|
||||
float scrollTopY = 1700f; // safe fallback if back is missing
|
||||
|
||||
if (canvasRT != null && stvoritT is RectTransform stvoritRT)
|
||||
{
|
||||
var corners = new Vector3[4];
|
||||
stvoritRT.GetWorldCorners(corners); // [0]=BL, [1]=TL, [2]=TR, [3]=BR
|
||||
var topLocal = canvasRT.InverseTransformPoint(corners[1]);
|
||||
scrollBottomY = topLocal.y + pivotYOffset + MARGIN;
|
||||
}
|
||||
if (canvasRT != null && backT is RectTransform backRT)
|
||||
{
|
||||
var corners = new Vector3[4];
|
||||
backRT.GetWorldCorners(corners);
|
||||
var bottomLocal = canvasRT.InverseTransformPoint(corners[0]);
|
||||
scrollTopY = bottomLocal.y + pivotYOffset - MARGIN;
|
||||
}
|
||||
|
||||
// Sanity-clamp: if either button is positioned weirdly (e.g. back
|
||||
// is below the create button, or they overlap), the computed
|
||||
// bounds would be a negative-height rect that draws as a 1px
|
||||
// sliver. Detect and fall back to the portrait-reference defaults.
|
||||
if (scrollTopY <= scrollBottomY + 200f)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[HostLobbyUI] Scroll bounds collapsed ({scrollBottomY:F0}..{scrollTopY:F0}); " +
|
||||
"falling back to 280..1700 portrait defaults.");
|
||||
scrollBottomY = 280f;
|
||||
scrollTopY = 1700f;
|
||||
}
|
||||
|
||||
var scrollRT = MakeRT("SettingsScroll", canvas);
|
||||
Anchor(scrollRT, new Vector2(0.05f, 0), new Vector2(0.95f, 0),
|
||||
new Vector2(0, scrollBottomY), new Vector2(0, scrollTopY));
|
||||
|
||||
// Subtle dark backdrop so the scroll area visually separates from
|
||||
// the canvas's full-screen Panel underneath.
|
||||
var bgImg = scrollRT.gameObject.AddComponent<Image>();
|
||||
bgImg.color = new Color(0f, 0f, 0f, 0.55f);
|
||||
|
||||
var sr = scrollRT.gameObject.AddComponent<ScrollRect>();
|
||||
|
||||
var vp = MakeRT("Viewport", scrollRT);
|
||||
Stretch(vp);
|
||||
vp.gameObject.AddComponent<RectMask2D>();
|
||||
|
||||
var content = MakeRT("Content", vp);
|
||||
content.anchorMin = new Vector2(0, 1);
|
||||
content.anchorMax = new Vector2(1, 1);
|
||||
content.pivot = new Vector2(0.5f, 1);
|
||||
var vlg = content.gameObject.AddComponent<VerticalLayoutGroup>();
|
||||
vlg.childControlWidth = true;
|
||||
vlg.childControlHeight = false;
|
||||
vlg.childForceExpandWidth = true;
|
||||
vlg.padding = new RectOffset(20, 20, 20, 20);
|
||||
vlg.spacing = 12;
|
||||
var csf = content.gameObject.AddComponent<ContentSizeFitter>();
|
||||
csf.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
sr.viewport = vp;
|
||||
sr.content = content;
|
||||
sr.horizontal = false;
|
||||
sr.vertical = true;
|
||||
sr.scrollSensitivity = 80;
|
||||
sr.movementType = ScrollRect.MovementType.Clamped;
|
||||
|
||||
// ── Round shape ────────────────────────────────────────────────────
|
||||
AddHeader(content, "Round");
|
||||
AddSlider(content, "Game radius", 100, 2000, _radius,
|
||||
v => _radius = v,
|
||||
v => Mathf.RoundToInt(v) + " m");
|
||||
AddStepper(content, "Max players", _maxPlayers, 4, 15, v => _maxPlayers = v);
|
||||
AddStepper(content, "Impostors", _impostors, 1, 4, v => _impostors = v);
|
||||
AddStepper(content, "Tasks per crew", _tasks, 1, 15, v => _tasks = v);
|
||||
|
||||
// ── Distances ──────────────────────────────────────────────────────
|
||||
AddHeader(content, "Distances (m)");
|
||||
AddStepper(content, "Kill", _killDist, 1, 30, v => _killDist = v);
|
||||
AddStepper(content, "Report", _reportDist, 1, 30, v => _reportDist = v);
|
||||
AddStepper(content, "Task start", _taskDist, 1, 30, v => _taskDist = v);
|
||||
AddStepper(content, "Meeting arrival", _meetingArrival, 5, 50, v => _meetingArrival = v);
|
||||
AddStepper(content, "Emergency call", _emergencyCallRadius, 1, 30, v => _emergencyCallRadius = v);
|
||||
AddStepper(content, "Repair station", _repairDist, 1, 30, v => _repairDist = v);
|
||||
|
||||
// ── Cooldowns ──────────────────────────────────────────────────────
|
||||
AddHeader(content, "Cooldowns (s)");
|
||||
AddStepper(content, "Kill cooldown", _killCooldownS, 5, 120, v => _killCooldownS = v);
|
||||
AddStepper(content, "Emergency cd", _emergencyCooldownS, 10, 600, v => _emergencyCooldownS = v);
|
||||
AddStepper(content, "Max emergencies", _maxEmergencyMeetings, 0, 10, v => _maxEmergencyMeetings = v);
|
||||
AddStepper(content, "Sabotage cd", _sabotageCooldownS, 10, 600, v => _sabotageCooldownS = v);
|
||||
|
||||
// ── Meeting phases ─────────────────────────────────────────────────
|
||||
AddHeader(content, "Meeting phases (s)");
|
||||
AddStepper(content, "Arrival time", _arrivalBaseS, 30, 300, v => _arrivalBaseS = v);
|
||||
AddStepper(content, "Allowed late", _allowedLateS, 0, 120, v => _allowedLateS = v);
|
||||
AddStepper(content, "Discussion", _discussionS, 10, 300, v => _discussionS = v);
|
||||
AddStepper(content, "Voting", _votingS, 10, 300, v => _votingS = v);
|
||||
|
||||
// ── Sabotage ───────────────────────────────────────────────────────
|
||||
AddHeader(content, "Sabotage (s)");
|
||||
AddStepper(content, "Comms duration", _commsDurationS, 10, 600, v => _commsDurationS = v);
|
||||
AddStepper(content, "Meltdown deadline", _meltdownDeadlineS, 30, 600, v => _meltdownDeadlineS = v);
|
||||
AddStepper(content, "Repair hold", _repairHoldS, 1, 60, v => _repairHoldS = v);
|
||||
}
|
||||
|
||||
void AddHeader(RectTransform parent, string text)
|
||||
{
|
||||
var rt = MakeRT("Header_" + text, parent);
|
||||
var le = rt.gameObject.AddComponent<LayoutElement>();
|
||||
le.preferredHeight = HEADER_HEIGHT;
|
||||
le.minHeight = HEADER_HEIGHT;
|
||||
|
||||
var bg = rt.gameObject.AddComponent<Image>();
|
||||
bg.color = new Color(C_HEADER.r, C_HEADER.g, C_HEADER.b, 0.45f);
|
||||
|
||||
var txtRT = MakeRT("Txt", rt);
|
||||
Stretch(txtRT);
|
||||
var tmp = txtRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
tmp.text = text;
|
||||
tmp.fontSize = 44;
|
||||
tmp.color = C_TEXT;
|
||||
tmp.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
tmp.fontStyle = FontStyles.Bold;
|
||||
tmp.margin = new Vector4(20, 0, 20, 0);
|
||||
}
|
||||
|
||||
void AddStepper(RectTransform parent, string label, int initial, int min, int max, System.Action<int> onChange)
|
||||
{
|
||||
var rt = MakeRT("Row_" + label, parent);
|
||||
var le = rt.gameObject.AddComponent<LayoutElement>();
|
||||
le.preferredHeight = ROW_HEIGHT;
|
||||
le.minHeight = ROW_HEIGHT;
|
||||
BuildStepperRow(rt, label, initial, min, max, onChange);
|
||||
}
|
||||
|
||||
void AddSlider(RectTransform parent, string label, float min, float max, float initial,
|
||||
System.Action<float> onChange, System.Func<float, string> formatter)
|
||||
{
|
||||
var rt = MakeRT("Row_" + label, parent);
|
||||
var le = rt.gameObject.AddComponent<LayoutElement>();
|
||||
le.preferredHeight = SLIDER_HEIGHT;
|
||||
le.minHeight = SLIDER_HEIGHT;
|
||||
|
||||
// Top half: label (left) + value readout (right)
|
||||
var labelRT = MakeRT("Label", rt);
|
||||
Anchor(labelRT, new Vector2(0, 0.55f), new Vector2(0.7f, 1f), Vector2.zero, Vector2.zero);
|
||||
var lblTmp = labelRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
lblTmp.text = label;
|
||||
lblTmp.fontSize = 36;
|
||||
lblTmp.color = C_TEXT;
|
||||
lblTmp.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
lblTmp.fontStyle = FontStyles.Bold;
|
||||
|
||||
var valRT = MakeRT("Val", rt);
|
||||
Anchor(valRT, new Vector2(0.7f, 0.55f), new Vector2(1f, 1f), Vector2.zero, Vector2.zero);
|
||||
var valTmp = valRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
valTmp.text = formatter(initial);
|
||||
valTmp.fontSize = 36;
|
||||
valTmp.color = C_TEXT;
|
||||
valTmp.alignment = TextAlignmentOptions.MidlineRight;
|
||||
valTmp.fontStyle = FontStyles.Bold;
|
||||
|
||||
// Bottom half: slider
|
||||
var sliderRT = MakeRT("Slider", rt);
|
||||
Anchor(sliderRT, new Vector2(0, 0.05f), new Vector2(1, 0.45f),
|
||||
Vector2.zero, Vector2.zero);
|
||||
var slider = sliderRT.gameObject.AddComponent<Slider>();
|
||||
slider.minValue = min;
|
||||
slider.maxValue = max;
|
||||
slider.value = initial;
|
||||
BuildSliderVisuals(slider);
|
||||
slider.onValueChanged.AddListener(v =>
|
||||
{
|
||||
onChange?.Invoke(v);
|
||||
valTmp.text = formatter(v);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Stepper row: [Label] [-] [value] [+] ─────────────────────────────
|
||||
void BuildStepperRow(RectTransform parent, string label, int initial,
|
||||
int min, int max, System.Action<int> onChange)
|
||||
{
|
||||
int captured = Mathf.Clamp(initial, min, max);
|
||||
|
||||
// Left half: label
|
||||
var lblRT = MakeRT("Label", parent);
|
||||
Anchor(lblRT, new Vector2(0, 0), new Vector2(0.55f, 1), Vector2.zero, Vector2.zero);
|
||||
var lblTmp = lblRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
lblTmp.text = label;
|
||||
lblTmp.fontSize = 32;
|
||||
lblTmp.color = C_TEXT;
|
||||
lblTmp.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
lblTmp.fontStyle = FontStyles.Bold;
|
||||
|
||||
// Minus button
|
||||
var minusRT = MakeRT("Minus", parent);
|
||||
Anchor(minusRT, new Vector2(0.55f, 0.10f), new Vector2(0.69f, 0.90f), Vector2.zero, Vector2.zero);
|
||||
var minusBtn = MakeButton(minusRT, "-", C_BTN_BG);
|
||||
|
||||
// Value label
|
||||
var valRT = MakeRT("Val", parent);
|
||||
Anchor(valRT, new Vector2(0.69f, 0), new Vector2(0.83f, 1), Vector2.zero, Vector2.zero);
|
||||
var valTmp = valRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
valTmp.text = captured.ToString();
|
||||
valTmp.fontSize = 40;
|
||||
valTmp.color = C_TEXT;
|
||||
valTmp.alignment = TextAlignmentOptions.Center;
|
||||
valTmp.fontStyle = FontStyles.Bold;
|
||||
|
||||
// Plus button
|
||||
var plusRT = MakeRT("Plus", parent);
|
||||
Anchor(plusRT, new Vector2(0.83f, 0.10f), new Vector2(0.97f, 0.90f), Vector2.zero, Vector2.zero);
|
||||
var plusBtn = MakeButton(plusRT, "+", C_BTN_HI);
|
||||
|
||||
minusBtn.onClick.AddListener(() =>
|
||||
{
|
||||
captured = Mathf.Max(min, captured - 1);
|
||||
valTmp.text = captured.ToString();
|
||||
onChange?.Invoke(captured);
|
||||
});
|
||||
plusBtn.onClick.AddListener(() =>
|
||||
{
|
||||
captured = Mathf.Min(max, captured + 1);
|
||||
valTmp.text = captured.ToString();
|
||||
onChange?.Invoke(captured);
|
||||
});
|
||||
}
|
||||
|
||||
Button MakeButton(RectTransform rt, string label, Color bg)
|
||||
{
|
||||
var img = rt.gameObject.AddComponent<Image>();
|
||||
img.color = bg;
|
||||
var btn = rt.gameObject.AddComponent<Button>();
|
||||
btn.targetGraphic = img;
|
||||
|
||||
var txtRT = MakeRT("Txt", rt);
|
||||
Stretch(txtRT);
|
||||
var txtTmp = txtRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
txtTmp.text = label;
|
||||
txtTmp.fontSize = 52;
|
||||
txtTmp.color = C_TEXT;
|
||||
txtTmp.alignment = TextAlignmentOptions.Center;
|
||||
txtTmp.fontStyle = FontStyles.Bold;
|
||||
return btn;
|
||||
}
|
||||
|
||||
// ── Slider visual fill-in (Unity gives you no default if you AddComponent) ─
|
||||
void BuildSliderVisuals(Slider s)
|
||||
{
|
||||
var sRT = s.GetComponent<RectTransform>();
|
||||
|
||||
var bgRT = MakeRT("Background", sRT);
|
||||
Stretch(bgRT);
|
||||
var bgImg = bgRT.gameObject.AddComponent<Image>();
|
||||
bgImg.color = C_TRACK;
|
||||
|
||||
var fillArea = MakeRT("Fill Area", sRT);
|
||||
Anchor(fillArea, new Vector2(0, 0.30f), new Vector2(1, 0.70f),
|
||||
new Vector2(8, 0), new Vector2(-8, 0));
|
||||
var fillRT = MakeRT("Fill", fillArea);
|
||||
fillRT.anchorMin = Vector2.zero;
|
||||
fillRT.anchorMax = Vector2.one;
|
||||
fillRT.offsetMin = Vector2.zero;
|
||||
fillRT.offsetMax = Vector2.zero;
|
||||
var fillImg = fillRT.gameObject.AddComponent<Image>();
|
||||
fillImg.color = C_FILL;
|
||||
s.fillRect = fillRT;
|
||||
|
||||
var handleArea = MakeRT("Handle Slide Area", sRT);
|
||||
Stretch(handleArea);
|
||||
var handleRT = MakeRT("Handle", handleArea);
|
||||
handleRT.sizeDelta = new Vector2(40, 40);
|
||||
handleRT.anchorMin = new Vector2(0, 0.5f);
|
||||
handleRT.anchorMax = new Vector2(0, 0.5f);
|
||||
handleRT.anchoredPosition = Vector2.zero;
|
||||
var hImg = handleRT.gameObject.AddComponent<Image>();
|
||||
hImg.color = C_HANDLE;
|
||||
s.handleRect = handleRT;
|
||||
s.targetGraphic = hImg;
|
||||
}
|
||||
|
||||
// ── Scene-binding helpers ────────────────────────────────────────────────
|
||||
|
||||
static Button FindButton(Transform root, string name)
|
||||
{
|
||||
var t = FindByName(root, name);
|
||||
return t != null ? t.GetComponent<Button>() : null;
|
||||
}
|
||||
|
||||
static TMP_Text FindTMP(Transform root, string name)
|
||||
{
|
||||
var t = FindByName(root, name);
|
||||
return t != null ? t.GetComponent<TMP_Text>() : null;
|
||||
}
|
||||
|
||||
static Transform FindByName(Transform root, string name)
|
||||
{
|
||||
if (root == null) return null;
|
||||
if (root.name == name) return root;
|
||||
foreach (Transform child in root)
|
||||
{
|
||||
var found = FindByName(child, name);
|
||||
if (found != null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ── Layout helpers (shared with the injected controls) ───────────────────
|
||||
|
||||
static RectTransform MakeRT(string name, Transform parent)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.SetParent(parent, false);
|
||||
rt.localScale = Vector3.one;
|
||||
return rt;
|
||||
}
|
||||
|
||||
static void Stretch(RectTransform rt)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero;
|
||||
rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = Vector2.zero;
|
||||
rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
static void Anchor(RectTransform rt, Vector2 aMin, Vector2 aMax,
|
||||
Vector2 offMin, Vector2 offMax)
|
||||
{
|
||||
rt.anchorMin = aMin;
|
||||
rt.anchorMax = aMax;
|
||||
rt.offsetMin = offMin;
|
||||
rt.offsetMax = offMax;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/HostLobbyUI.cs.meta
Normal file
11
Assets/Scripts/HostLobbyUI.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60a81c1cb4f98a5b490fac0d3c1686b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
505
Assets/Scripts/InGameHUDBuilder.cs
Normal file
505
Assets/Scripts/InGameHUDBuilder.cs
Normal file
@@ -0,0 +1,505 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Programmatically builds the complete in-game HUD inside the InGame canvas (Client.unity).
|
||||
///
|
||||
/// Call BuildNow() from GameManager.OnSceneLoaded BEFORE BindClientScene so GameManager_UI
|
||||
/// can locate named children.
|
||||
///
|
||||
/// Named elements expected by GameManager_UI (Transform.Find / FindTMP):
|
||||
/// ActionButton – proxim action button
|
||||
/// SabotagePanel – top-of-screen sabotage banner
|
||||
/// SabotageTimer – TMP countdown text inside SabotagePanel
|
||||
/// MeetingPanel – full-screen voting overlay
|
||||
/// MeetingHeader – TMP title (event type)
|
||||
/// MeetingPhaseLabel – TMP sub-phase label (DISCUSSION / VOTING / RESULTS / ARRIVAL)
|
||||
/// MeetingPhaseProgressBar – Image filled, drains with countdown
|
||||
/// MeetingPhaseCountdown – TMP "0:24" countdown text
|
||||
/// MeetingPlayerList – TMP player list (text fallback)
|
||||
/// MyVoteIndicator – TMP "You voted for: X" strip
|
||||
/// SkipButton – skip-vote button
|
||||
/// VoteResultPanel – sub-panel shown after voting
|
||||
/// VoteResult – TMP result text
|
||||
/// GameEndPanel – full-screen end-of-game overlay
|
||||
/// GameEndText – TMP result text
|
||||
/// ReconnectOverlay – full-screen "reconnecting..." overlay
|
||||
/// ReconnectMessage – TMP "Reconnecting..." headline
|
||||
/// ReconnectSubtext – TMP secondary message
|
||||
/// KillCooldown – TMP kill-cooldown label
|
||||
/// TaskList – TMP task name list
|
||||
/// TaskProgress – TMP global task progress
|
||||
/// Toast – TMP toast notification
|
||||
/// </summary>
|
||||
public class InGameHUDBuilder : MonoBehaviour
|
||||
{
|
||||
// ── Palette ───────────────────────────────────────────────────────────────
|
||||
// Aliases to UITheme so this file's existing local references keep working
|
||||
// without a wholesale rename pass. New code should call UITheme.* directly;
|
||||
// the C_* names stay as a transitional crutch for in-flight edits.
|
||||
static readonly Color C_BG = UITheme.Bg;
|
||||
static readonly Color C_BAR = UITheme.Surface;
|
||||
static readonly Color C_ACCENT = UITheme.Accent;
|
||||
static readonly Color C_GREEN = UITheme.Success;
|
||||
static readonly Color C_RED = UITheme.Danger;
|
||||
static readonly Color C_ORANGE = UITheme.Warning;
|
||||
static readonly Color C_YELLOW = UITheme.Caution;
|
||||
static readonly Color C_MUTED = UITheme.TextLo;
|
||||
static readonly Color C_ROW_A = UITheme.RowA;
|
||||
static readonly Color C_ROW_B = UITheme.RowB;
|
||||
|
||||
private bool _built;
|
||||
|
||||
// Reference resolution constants kept here as public surface so external
|
||||
// callers (other UI scripts that grew to use these) don't break. Forward
|
||||
// to UITheme so there's still one source of truth.
|
||||
public const float kReferenceWidth = UITheme.ReferenceWidth;
|
||||
public const float kReferenceHeight = UITheme.ReferenceHeight;
|
||||
public const float kMatchWidthHeight = UITheme.MatchWidthOrHeight;
|
||||
|
||||
public void BuildNow() { if (!_built) { _built = true; Build(); } }
|
||||
void Start() { if (!_built) Build(); }
|
||||
|
||||
void Build()
|
||||
{
|
||||
var rt = GetComponent<RectTransform>();
|
||||
if (rt == null) return;
|
||||
|
||||
// Wire up emoji fallback before any TMP component renders. Idempotent
|
||||
// and cheap on subsequent calls.
|
||||
UITheme.EnsureEmojiFontFallback();
|
||||
|
||||
// Make sure the host canvas has a CanvasScaler with our reference
|
||||
// resolution. Without this, RectTransform offsets are interpreted as
|
||||
// raw pixels and the layout looks correct only on whichever device
|
||||
// the project was last opened against.
|
||||
ConfigureCanvasScaler(GetComponentInParent<Canvas>());
|
||||
|
||||
// Apply Screen.safeArea so iOS notches and Android punch-hole cameras
|
||||
// don't eat the top/bottom bar. We anchor the host RectTransform to
|
||||
// the safe rectangle so all child anchors inherit the inset.
|
||||
rt.anchorMin = Vector2.zero;
|
||||
rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = Vector2.zero;
|
||||
rt.offsetMax = Vector2.zero;
|
||||
ApplySafeArea(rt);
|
||||
|
||||
BuildTopBar(rt);
|
||||
BuildTaskPanel(rt);
|
||||
BuildTaskProgress(rt);
|
||||
BuildBottomBar(rt);
|
||||
BuildActionButton(rt);
|
||||
BuildSabotagePanel(rt);
|
||||
BuildMeetingPanel(rt);
|
||||
BuildGameEndPanel(rt);
|
||||
BuildReconnectOverlay(rt);
|
||||
BuildSpectatePanel(rt);
|
||||
BuildToast(rt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply the project's standard CanvasScaler config to a Canvas. Idempotent -
|
||||
/// adds the component if missing, otherwise updates settings in-place.
|
||||
/// </summary>
|
||||
public static void ConfigureCanvasScaler(Canvas canvas)
|
||||
{
|
||||
if (canvas == null) return;
|
||||
var scaler = canvas.GetComponent<CanvasScaler>()
|
||||
?? canvas.gameObject.AddComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(kReferenceWidth, kReferenceHeight);
|
||||
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
|
||||
scaler.matchWidthOrHeight = kMatchWidthHeight;
|
||||
scaler.referencePixelsPerUnit = 100f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Anchors the given RectTransform to the screen's safe rectangle, so all
|
||||
/// children inherit the inset. Called once at build time; the safe area
|
||||
/// rarely changes after the app launches and a full HUD rebuild on rotation
|
||||
/// would be the simpler way to handle that case.
|
||||
/// </summary>
|
||||
public static void ApplySafeArea(RectTransform rt)
|
||||
{
|
||||
if (rt == null) return;
|
||||
var safe = Screen.safeArea;
|
||||
var screenSize = new Vector2(Screen.width, Screen.height);
|
||||
if (screenSize.x <= 0 || screenSize.y <= 0) return;
|
||||
|
||||
Vector2 aMin = safe.position;
|
||||
Vector2 aMax = safe.position + safe.size;
|
||||
aMin.x /= screenSize.x; aMin.y /= screenSize.y;
|
||||
aMax.x /= screenSize.x; aMax.y /= screenSize.y;
|
||||
|
||||
rt.anchorMin = aMin;
|
||||
rt.anchorMax = aMax;
|
||||
rt.offsetMin = Vector2.zero;
|
||||
rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
// ── Top bar ───────────────────────────────────────────────────────────────
|
||||
void BuildTopBar(RectTransform parent)
|
||||
{
|
||||
var bar = Child("_TopBar", parent);
|
||||
Anchor(bar, new Vector2(0,1), new Vector2(1,1), new Vector2(0,-90f), Vector2.zero);
|
||||
bar.pivot = new Vector2(0.5f,1f);
|
||||
Img(bar, C_BAR);
|
||||
|
||||
// Kill cooldown – right half, hidden by default
|
||||
var cd = Child("KillCooldown", bar);
|
||||
cd.anchorMin = new Vector2(0.5f, 0); cd.anchorMax = Vector2.one;
|
||||
cd.offsetMin = new Vector2(0, 6); cd.offsetMax = new Vector2(-12, -6);
|
||||
var cdTmp = cd.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
cdTmp.text = ""; cdTmp.fontSize = 32; cdTmp.color = C_ORANGE;
|
||||
cdTmp.fontStyle = FontStyles.Bold; cdTmp.alignment = TextAlignmentOptions.MidlineRight;
|
||||
cd.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
// ── Task panel (right side) ───────────────────────────────────────────────
|
||||
void BuildTaskPanel(RectTransform parent)
|
||||
{
|
||||
var panel = Child("_TaskPanel", parent);
|
||||
panel.anchorMin = new Vector2(1,0.35f); panel.anchorMax = new Vector2(1,0.88f);
|
||||
panel.pivot = new Vector2(1,0.5f); panel.sizeDelta = new Vector2(280,0);
|
||||
Img(panel, new Color(0.05f,0.06f,0.12f,0.85f));
|
||||
|
||||
var hdr = Child("_Hdr", panel);
|
||||
Anchor(hdr, new Vector2(0,1), new Vector2(1,1), new Vector2(0,-44), Vector2.zero);
|
||||
hdr.pivot = new Vector2(0.5f,1f);
|
||||
Img(hdr, new Color(0.2f,0.6f,1f,0.5f));
|
||||
TxtChild(hdr,"MY TASKS",26,Color.white,TextAlignmentOptions.Center,bold:true);
|
||||
|
||||
var body = Child("TaskList", panel);
|
||||
body.anchorMin = Vector2.zero; body.anchorMax = Vector2.one;
|
||||
body.offsetMin = new Vector2(8,8); body.offsetMax = new Vector2(-8,-48);
|
||||
var t = body.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
t.text = ""; t.fontSize = 22; t.color = Color.white; t.alignment = TextAlignmentOptions.TopLeft;
|
||||
t.enableWordWrapping = true;
|
||||
}
|
||||
|
||||
// ── Task progress (above bottom bar) ─────────────────────────────────────
|
||||
void BuildTaskProgress(RectTransform parent)
|
||||
{
|
||||
var prog = Child("TaskProgress", parent);
|
||||
Anchor(prog, new Vector2(0,0), new Vector2(1,0), new Vector2(-20,120), new Vector2(20,160));
|
||||
var t = prog.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
t.text = ""; t.fontSize = 28; t.color = Color.white;
|
||||
t.fontStyle = FontStyles.Bold; t.alignment = TextAlignmentOptions.Center;
|
||||
}
|
||||
|
||||
// ── Bottom bar ────────────────────────────────────────────────────────────
|
||||
void BuildBottomBar(RectTransform parent)
|
||||
{
|
||||
var bar = Child("_BottomBar", parent);
|
||||
Anchor(bar, Vector2.zero, new Vector2(1,0), Vector2.zero, new Vector2(0,110));
|
||||
bar.pivot = new Vector2(0.5f,0);
|
||||
Img(bar, C_BAR);
|
||||
|
||||
var recBtn = Child("_RecenterBtn", bar);
|
||||
recBtn.anchorMin = new Vector2(0.82f,0.08f); recBtn.anchorMax = new Vector2(0.98f,0.92f);
|
||||
var recBg = Img(recBtn, C_ACCENT);
|
||||
var recButton = recBtn.gameObject.AddComponent<Button>();
|
||||
recButton.targetGraphic = recBg;
|
||||
recButton.onClick.AddListener(() => MapCameraController.Instance?.Recenter());
|
||||
TxtChild(recBtn,"⊙",42,Color.white,TextAlignmentOptions.Center,bold:true);
|
||||
}
|
||||
|
||||
// ── Action button (DIRECT child so Transform.Find works) ─────────────────
|
||||
void BuildActionButton(RectTransform parent)
|
||||
{
|
||||
var btn = Child("ActionButton", parent);
|
||||
Anchor(btn, new Vector2(0.15f,0), new Vector2(0.80f,0), new Vector2(0,12), new Vector2(0,102));
|
||||
btn.pivot = new Vector2(0.5f,0);
|
||||
var bg = Img(btn, C_GREEN);
|
||||
var button = btn.gameObject.AddComponent<Button>();
|
||||
button.targetGraphic = bg;
|
||||
|
||||
var txtRt = Child("Text", btn);
|
||||
Stretch(txtRt);
|
||||
var tmp = txtRt.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
tmp.text = "ACTION"; tmp.fontSize = 44; tmp.fontStyle = FontStyles.Bold;
|
||||
tmp.color = Color.white; tmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
btn.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
// ── Sabotage panel (top strip) ────────────────────────────────────────────
|
||||
void BuildSabotagePanel(RectTransform parent)
|
||||
{
|
||||
var panel = Child("SabotagePanel", parent);
|
||||
panel.anchorMin = new Vector2(0,1); panel.anchorMax = new Vector2(1,1);
|
||||
panel.pivot = new Vector2(0.5f,1f); panel.sizeDelta = new Vector2(0,80);
|
||||
Img(panel, new Color(0.76f,0.19f,0.19f,0.92f));
|
||||
|
||||
var timer = Child("SabotageTimer", panel);
|
||||
Stretch(timer);
|
||||
var t = timer.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
t.text = "SABOTAGE!"; t.fontSize = 48; t.fontStyle = FontStyles.Bold;
|
||||
t.color = Color.white; t.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
panel.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
// ── Meeting panel (full screen overlay) ───────────────────────────────────
|
||||
//
|
||||
// Layout (vertical, top to bottom):
|
||||
// 0.90 - 1.00 MeetingHeader - "EMERGENCY MEETING" / "BODY REPORTED"
|
||||
// 0.83 - 0.90 MeetingPhaseLabel - "ARRIVAL" / "DISCUSSION" / "VOTING" / "RESULTS"
|
||||
// 0.79 - 0.83 MeetingPhaseProgressBar - thin fill bar that drains as countdown runs
|
||||
// 0.74 - 0.79 MeetingPhaseCountdown - "0:24" countdown text
|
||||
// 0.18 - 0.74 _MeetingScroll - vote rows (or VoteResultPanel when resolved)
|
||||
// 0.14 - 0.18 MyVoteIndicator - "You voted for: X" or "Voting hasn't started"
|
||||
// 0.04 - 0.14 SkipButton - skip vote (will move into the row list in P2.7)
|
||||
void BuildMeetingPanel(RectTransform parent)
|
||||
{
|
||||
var panel = Child("MeetingPanel", parent);
|
||||
Stretch(panel);
|
||||
Img(panel, new Color(0.04f,0.05f,0.14f,0.97f));
|
||||
|
||||
// Title
|
||||
var hdr = Child("MeetingHeader", panel);
|
||||
Anchor(hdr, new Vector2(0,0.90f), new Vector2(1,1), Vector2.zero, Vector2.zero);
|
||||
var hdrTmp = hdr.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
hdrTmp.text = "EMERGENCY MEETING"; hdrTmp.fontSize = 52;
|
||||
hdrTmp.fontStyle = FontStyles.Bold; hdrTmp.color = C_ORANGE;
|
||||
hdrTmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
// Sub-phase label (DISCUSSION / VOTING / RESULTS / ARRIVAL)
|
||||
var phaseLbl = Child("MeetingPhaseLabel", panel);
|
||||
Anchor(phaseLbl, new Vector2(0,0.83f), new Vector2(1,0.90f), Vector2.zero, Vector2.zero);
|
||||
var phaseLblTmp = phaseLbl.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
phaseLblTmp.text = ""; phaseLblTmp.fontSize = 32;
|
||||
phaseLblTmp.fontStyle = FontStyles.Bold; phaseLblTmp.color = C_ACCENT;
|
||||
phaseLblTmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
// Phase progress bar (drains as countdown elapses)
|
||||
var progBg = Child("MeetingPhaseProgressBg", panel);
|
||||
Anchor(progBg, new Vector2(0.10f,0.79f), new Vector2(0.90f,0.83f), Vector2.zero, Vector2.zero);
|
||||
Img(progBg, new Color(0.10f,0.13f,0.22f,1f));
|
||||
var progFill = Child("MeetingPhaseProgressBar", progBg);
|
||||
progFill.anchorMin = new Vector2(0,0); progFill.anchorMax = new Vector2(1,1);
|
||||
progFill.offsetMin = Vector2.zero; progFill.offsetMax = Vector2.zero;
|
||||
var fillImg = progFill.gameObject.AddComponent<Image>();
|
||||
fillImg.color = C_ACCENT;
|
||||
fillImg.type = Image.Type.Filled;
|
||||
fillImg.fillMethod = Image.FillMethod.Horizontal;
|
||||
fillImg.fillAmount = 0f;
|
||||
|
||||
// Countdown text under the bar
|
||||
var cd = Child("MeetingPhaseCountdown", panel);
|
||||
Anchor(cd, new Vector2(0,0.74f), new Vector2(1,0.79f), Vector2.zero, Vector2.zero);
|
||||
var cdTmp = cd.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
cdTmp.text = ""; cdTmp.fontSize = 28;
|
||||
cdTmp.color = new Color(0.8f,0.85f,0.95f);
|
||||
cdTmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
// Scrollable player vote list
|
||||
var scrollArea = Child("_MeetingScroll", panel);
|
||||
Anchor(scrollArea, new Vector2(0,0.18f), new Vector2(1,0.74f), Vector2.zero, Vector2.zero);
|
||||
BuildMeetingScroll(scrollArea);
|
||||
|
||||
// Text fallback (hidden by default; shown if scroll build fails)
|
||||
var fallback = Child("MeetingPlayerList", panel);
|
||||
Anchor(fallback, new Vector2(0,0.18f), new Vector2(1,0.74f), new Vector2(8,0), new Vector2(-8,0));
|
||||
var fallbackTmp = fallback.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
fallbackTmp.text = ""; fallbackTmp.fontSize = 28; fallbackTmp.color = Color.white;
|
||||
fallbackTmp.alignment = TextAlignmentOptions.TopLeft;
|
||||
fallback.gameObject.SetActive(false); // hidden - scroll list used instead
|
||||
|
||||
// "Your vote: X" indicator strip
|
||||
var myVote = Child("MyVoteIndicator", panel);
|
||||
Anchor(myVote, new Vector2(0.05f,0.14f), new Vector2(0.95f,0.18f), Vector2.zero, Vector2.zero);
|
||||
var myVoteTmp = myVote.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
myVoteTmp.text = ""; myVoteTmp.fontSize = 26;
|
||||
myVoteTmp.color = new Color(0.73f,0.8f,0.88f);
|
||||
myVoteTmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
// Skip button (will be merged into the vote list as a row in P2.7)
|
||||
var skip = Child("SkipButton", panel);
|
||||
Anchor(skip, new Vector2(0.05f,0.04f), new Vector2(0.95f,0.14f), Vector2.zero, Vector2.zero);
|
||||
var skipBg = Img(skip, C_MUTED);
|
||||
var skipBtn = skip.gameObject.AddComponent<Button>();
|
||||
skipBtn.targetGraphic = skipBg;
|
||||
skipBtn.onClick.AddListener(() => GameManager.Instance?.CastVote(null));
|
||||
TxtChild(skip, "⏭ SKIP", 36, Color.white, TextAlignmentOptions.Center, bold: true);
|
||||
|
||||
// Vote-result sub-panel - now sized to *replace* the scroll area when
|
||||
// results arrive, instead of squeezing into the bottom strip alongside
|
||||
// skip/my-vote (which caused the previous overlap).
|
||||
var resultPanel = Child("VoteResultPanel", panel);
|
||||
Anchor(resultPanel, new Vector2(0,0.18f), new Vector2(1,0.74f), Vector2.zero, Vector2.zero);
|
||||
Img(resultPanel, new Color(0.05f,0.05f,0.15f,0.95f));
|
||||
var resultText = Child("VoteResult", resultPanel);
|
||||
Stretch(resultText);
|
||||
var rtTmp = resultText.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
rtTmp.text = ""; rtTmp.fontSize = 34; rtTmp.color = C_YELLOW;
|
||||
rtTmp.fontStyle = FontStyles.Bold; rtTmp.alignment = TextAlignmentOptions.Center;
|
||||
resultPanel.gameObject.SetActive(false);
|
||||
|
||||
panel.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
void BuildMeetingScroll(RectTransform rt)
|
||||
{
|
||||
var sr = rt.gameObject.AddComponent<ScrollRect>();
|
||||
|
||||
var vp = Child("Viewport", rt);
|
||||
Stretch(vp);
|
||||
vp.gameObject.AddComponent<RectMask2D>();
|
||||
|
||||
var content = Child("MeetingContent", vp);
|
||||
content.anchorMin = new Vector2(0,1); content.anchorMax = new Vector2(1,1);
|
||||
content.pivot = new Vector2(0.5f,1);
|
||||
var vlg = content.gameObject.AddComponent<VerticalLayoutGroup>();
|
||||
vlg.childControlWidth = true; vlg.childControlHeight = false;
|
||||
vlg.childForceExpandWidth = true; vlg.spacing = 4;
|
||||
var csf = content.gameObject.AddComponent<ContentSizeFitter>();
|
||||
csf.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
sr.viewport = vp; sr.content = content;
|
||||
sr.horizontal = false; sr.vertical = true; sr.scrollSensitivity = 60;
|
||||
}
|
||||
|
||||
// ── Game-end panel ────────────────────────────────────────────────────────
|
||||
void BuildGameEndPanel(RectTransform parent)
|
||||
{
|
||||
var panel = Child("GameEndPanel", parent);
|
||||
Stretch(panel);
|
||||
Img(panel, new Color(0,0,0,0.90f));
|
||||
|
||||
// Result text (upper half)
|
||||
var txt = Child("GameEndText", panel);
|
||||
Anchor(txt, new Vector2(0,0.4f), new Vector2(1,0.9f), Vector2.zero, Vector2.zero);
|
||||
var tmp = txt.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
tmp.text = ""; tmp.fontSize = 72; tmp.fontStyle = FontStyles.Bold;
|
||||
tmp.color = Color.white; tmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
// "Return to lobby" button – owner only (GameManager_UI shows/hides it)
|
||||
var retBtn = Child("ReturnToLobbyButton", panel);
|
||||
Anchor(retBtn, new Vector2(0.15f,0.22f), new Vector2(0.85f,0.36f), Vector2.zero, Vector2.zero);
|
||||
var retBg = Img(retBtn, C_GREEN);
|
||||
var retButton = retBtn.gameObject.AddComponent<Button>();
|
||||
retButton.targetGraphic = retBg;
|
||||
retButton.onClick.AddListener(() => GameManager.Instance?.gameClient?.ReturnToLobby());
|
||||
TxtChild(retBtn, "▶ RETURN TO LOBBY", 38, Color.white, TextAlignmentOptions.Center, bold: true);
|
||||
retBtn.gameObject.SetActive(false); // shown only for host
|
||||
|
||||
// "Leave" button
|
||||
var leaveBtn = Child("LeaveGameButton", panel);
|
||||
Anchor(leaveBtn, new Vector2(0.3f,0.08f), new Vector2(0.7f,0.20f), Vector2.zero, Vector2.zero);
|
||||
var leaveBg = Img(leaveBtn, C_RED);
|
||||
var leaveButton = leaveBtn.gameObject.AddComponent<Button>();
|
||||
leaveButton.targetGraphic = leaveBg;
|
||||
leaveButton.onClick.AddListener(() => GameManager.Instance?.LeaveLobbyButton());
|
||||
TxtChild(leaveBtn, "✕ LEAVE", 34, Color.white, TextAlignmentOptions.Center, bold: true);
|
||||
|
||||
panel.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
// ── Reconnect overlay (visible while the socket is dropping/reconnecting) ─
|
||||
void BuildReconnectOverlay(RectTransform parent)
|
||||
{
|
||||
var panel = Child("ReconnectOverlay", parent);
|
||||
Stretch(panel);
|
||||
Img(panel, new Color(0.04f,0.05f,0.14f,0.90f));
|
||||
|
||||
var msg = Child("ReconnectMessage", panel);
|
||||
Anchor(msg, new Vector2(0,0.45f), new Vector2(1,0.65f), Vector2.zero, Vector2.zero);
|
||||
var msgTmp = msg.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
msgTmp.text = "Reconnecting..."; msgTmp.fontSize = 56;
|
||||
msgTmp.fontStyle = FontStyles.Bold; msgTmp.color = C_YELLOW;
|
||||
msgTmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
var sub = Child("ReconnectSubtext", panel);
|
||||
Anchor(sub, new Vector2(0,0.35f), new Vector2(1,0.45f), Vector2.zero, Vector2.zero);
|
||||
var subTmp = sub.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
subTmp.text = "Server keeps your slot for up to 60 seconds.";
|
||||
subTmp.fontSize = 28; subTmp.color = new Color(0.73f,0.8f,0.88f);
|
||||
subTmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
panel.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
// ── Spectate panel (visible after death; dim banner so the player still
|
||||
// sees the live map but understands they're spectating) ────────────────
|
||||
void BuildSpectatePanel(RectTransform parent)
|
||||
{
|
||||
var panel = Child("SpectatePanel", parent);
|
||||
// Top strip only - we don't want to occlude the map. Just enough to
|
||||
// communicate "you're dead, watching live."
|
||||
panel.anchorMin = new Vector2(0, 1); panel.anchorMax = new Vector2(1, 1);
|
||||
panel.pivot = new Vector2(0.5f, 1f); panel.sizeDelta = new Vector2(0, 96);
|
||||
Img(panel, new Color(0f, 0f, 0f, 0.65f));
|
||||
|
||||
var label = Child("SpectateLabel", panel);
|
||||
Stretch(label);
|
||||
var t = label.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
UITheme.StyleText(t, UITheme.FontTitle, UITheme.TextHi,
|
||||
TextAlignmentOptions.Center, bold: true);
|
||||
t.text = "👻 YOU ARE DEAD - SPECTATING";
|
||||
|
||||
var sub = Child("SpectateSub", panel);
|
||||
Anchor(sub, new Vector2(0, 0), new Vector2(1, 0.45f), Vector2.zero, Vector2.zero);
|
||||
var st = sub.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
UITheme.StyleText(st, UITheme.FontSmall, UITheme.TextMid,
|
||||
TextAlignmentOptions.Center);
|
||||
st.text = "Crew can finish tasks as ghosts. Impostors cannot kill you.";
|
||||
|
||||
panel.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
// ── Toast notification ────────────────────────────────────────────────────
|
||||
void BuildToast(RectTransform parent)
|
||||
{
|
||||
var toast = Child("Toast", parent);
|
||||
Anchor(toast, new Vector2(0.05f,0.88f), new Vector2(0.95f,0.94f), Vector2.zero, Vector2.zero);
|
||||
Img(toast, new Color(0.1f,0.1f,0.2f,0.92f));
|
||||
TxtChild(toast, "", 30, C_YELLOW, TextAlignmentOptions.Center, bold: true);
|
||||
toast.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
RectTransform Child(string name, RectTransform parent)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.SetParent(parent, false);
|
||||
rt.localScale = Vector3.one;
|
||||
return rt;
|
||||
}
|
||||
|
||||
Image Img(RectTransform rt, Color c)
|
||||
{
|
||||
var img = rt.gameObject.AddComponent<Image>();
|
||||
img.color = c;
|
||||
return img;
|
||||
}
|
||||
|
||||
void Stretch(RectTransform rt)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
// min/max by anchor + absolute offset corners
|
||||
void Anchor(RectTransform rt, Vector2 aMin, Vector2 aMax, Vector2 offsetMin, Vector2 offsetMax)
|
||||
{
|
||||
rt.anchorMin = aMin; rt.anchorMax = aMax;
|
||||
rt.offsetMin = offsetMin; rt.offsetMax = offsetMax;
|
||||
}
|
||||
|
||||
TextMeshProUGUI TxtChild(RectTransform parent, string text, float size, Color color,
|
||||
TextAlignmentOptions align, bool bold = false)
|
||||
{
|
||||
var rt = Child("Txt", parent);
|
||||
Stretch(rt);
|
||||
var tmp = rt.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
tmp.text = text; tmp.fontSize = size; tmp.color = color; tmp.alignment = align;
|
||||
if (bold) tmp.fontStyle = FontStyles.Bold;
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
|
||||
2
Assets/Scripts/InGameHUDBuilder.cs.meta
Normal file
2
Assets/Scripts/InGameHUDBuilder.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f269d8f8742088e5fbad88cd1d352180
|
||||
147
Assets/Scripts/JoinLobbyUI.cs
Normal file
147
Assets/Scripts/JoinLobbyUI.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
/// <summary>
|
||||
/// Attach to any manager GO in join lobby.unity.
|
||||
/// Converts the "code" button GO into a working TMP_InputField at runtime
|
||||
/// and wires the join button to call GameManager.JoinLobbyButton().
|
||||
/// </summary>
|
||||
public class JoinLobbyUI : MonoBehaviour
|
||||
{
|
||||
private TMP_InputField _codeInput;
|
||||
private TMP_Text _errorText;
|
||||
|
||||
void Start()
|
||||
{
|
||||
// ── Build proper code input from the "code" Button GO ─────────────────
|
||||
var codeGO = GameObject.Find("code");
|
||||
if (codeGO != null)
|
||||
{
|
||||
var rt = codeGO.GetComponent<RectTransform>();
|
||||
if (rt != null)
|
||||
{
|
||||
// Remove Button — it swallows click events before input field can act
|
||||
var btn = codeGO.GetComponent<Button>();
|
||||
if (btn != null) DestroyImmediate(btn);
|
||||
var oldField = codeGO.GetComponent<TMP_InputField>();
|
||||
if (oldField != null) DestroyImmediate(oldField);
|
||||
|
||||
// Clear art-team child text labels
|
||||
var kill = new System.Collections.Generic.List<GameObject>();
|
||||
foreach (Transform child in rt) kill.Add(child.gameObject);
|
||||
foreach (var go in kill) DestroyImmediate(go);
|
||||
|
||||
// Background
|
||||
var img = codeGO.GetComponent<Image>();
|
||||
if (img == null) img = codeGO.AddComponent<Image>();
|
||||
img.color = new Color(0.08f, 0.10f, 0.20f, 0.92f);
|
||||
|
||||
// Viewport > Placeholder + Text
|
||||
var vpRT = MakeChild("Text Area", rt);
|
||||
vpRT.anchorMin = Vector2.zero;
|
||||
vpRT.anchorMax = Vector2.one;
|
||||
vpRT.offsetMin = new Vector2(18f, 6f);
|
||||
vpRT.offsetMax = new Vector2(-18f, -6f);
|
||||
vpRT.gameObject.AddComponent<RectMask2D>();
|
||||
|
||||
var phRT = MakeChild("Placeholder", vpRT);
|
||||
Stretch(phRT);
|
||||
var ph = phRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
ph.text = "Enter lobby code...";
|
||||
ph.fontSize = 48;
|
||||
ph.color = new Color(0.55f, 0.60f, 0.70f, 0.85f);
|
||||
ph.fontStyle = FontStyles.Italic;
|
||||
ph.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
var txtRT = MakeChild("Text", vpRT);
|
||||
Stretch(txtRT);
|
||||
var txt = txtRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
txt.text = "";
|
||||
txt.fontSize = 52;
|
||||
txt.color = Color.white;
|
||||
txt.fontStyle = FontStyles.Bold;
|
||||
txt.alignment = TextAlignmentOptions.Center;
|
||||
txt.characterSpacing = 8f;
|
||||
|
||||
_codeInput = codeGO.AddComponent<TMP_InputField>();
|
||||
_codeInput.textViewport = vpRT;
|
||||
_codeInput.textComponent = txt;
|
||||
_codeInput.placeholder = ph;
|
||||
_codeInput.targetGraphic = img;
|
||||
_codeInput.characterLimit = 8;
|
||||
_codeInput.characterValidation = TMP_InputField.CharacterValidation.Alphanumeric;
|
||||
_codeInput.keyboardType = TouchScreenKeyboardType.Default;
|
||||
_codeInput.shouldHideMobileInput = false;
|
||||
// Auto-uppercase as user types
|
||||
_codeInput.onValueChanged.AddListener(v =>
|
||||
_codeInput.SetTextWithoutNotify(v.ToUpperInvariant()));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Wire the join button ───────────────────────────────────────────────
|
||||
// Art team named the button "připojit" with literal quote marks in the name
|
||||
var joinBtnGO = FindGOByNameContains("ipojit");
|
||||
if (joinBtnGO != null)
|
||||
{
|
||||
var joinBtn = joinBtnGO.GetComponent<Button>();
|
||||
if (joinBtn == null) joinBtn = joinBtnGO.AddComponent<Button>();
|
||||
joinBtn.onClick.AddListener(OnJoinClicked);
|
||||
}
|
||||
|
||||
// ── Error label (optional) ─────────────────────────────────────────────
|
||||
var errGO = GameObject.Find("error") ?? GameObject.Find("ErrorText");
|
||||
if (errGO != null)
|
||||
{
|
||||
_errorText = errGO.GetComponent<TMP_Text>();
|
||||
if (_errorText != null) _errorText.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
void OnJoinClicked()
|
||||
{
|
||||
var gm = GameManager.Instance;
|
||||
if (gm == null) return;
|
||||
|
||||
string code = _codeInput != null ? _codeInput.text.Trim() : "";
|
||||
if (string.IsNullOrEmpty(code))
|
||||
{
|
||||
ShowError("Enter a lobby code!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_errorText != null) _errorText.gameObject.SetActive(false);
|
||||
gm.JoinLobbyButton(code);
|
||||
}
|
||||
|
||||
void ShowError(string msg)
|
||||
{
|
||||
if (_errorText == null) return;
|
||||
_errorText.text = msg;
|
||||
_errorText.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
// Finds a GO whose name contains the substring (handles Art-team quoted names)
|
||||
GameObject FindGOByNameContains(string substring)
|
||||
{
|
||||
foreach (var go in FindObjectsOfType<GameObject>())
|
||||
if (go.name.Contains(substring)) return go;
|
||||
return null;
|
||||
}
|
||||
|
||||
RectTransform MakeChild(string name, RectTransform parent)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.SetParent(parent, false);
|
||||
rt.localScale = Vector3.one;
|
||||
return rt;
|
||||
}
|
||||
|
||||
void Stretch(RectTransform rt)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero;
|
||||
rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/JoinLobbyUI.cs.meta
Normal file
11
Assets/Scripts/JoinLobbyUI.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e0ca5d57a20e05215c36664ab8ff60e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
411
Assets/Scripts/LobbyDisplayUI.cs
Normal file
411
Assets/Scripts/LobbyDisplayUI.cs
Normal file
@@ -0,0 +1,411 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
using GeoSus.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Lives on create.unity, the post-creation lobby view. The scene already
|
||||
/// contains the art team's named UI:
|
||||
///
|
||||
/// Canvas
|
||||
/// ├── Panel - full-screen background
|
||||
/// ├── max players - TMP "Max players: " label (we append the count)
|
||||
/// ├── lobby code - TMP "Lobby code: " label (we append the code)
|
||||
/// ├── smazatbutton - "delete/leave" button
|
||||
/// ├── lobby info - info button (currently no-op)
|
||||
/// ├── player list neglow - decorative glow behind the player list
|
||||
/// ├── player list - container for the player list (originally held
|
||||
/// │ a single placeholder text child)
|
||||
/// ├── tuff jmeno - hardcoded player slot 1 (placeholder)
|
||||
/// ├── netuff jmeno - hardcoded player slot 2 (placeholder)
|
||||
/// └── stvořit - "Start Game" button
|
||||
///
|
||||
/// We DO NOT destroy any of these. Instead we resolve them by name, wire the
|
||||
/// buttons, update text labels live, hide the placeholder slots, and inject
|
||||
/// a ScrollRect inside the existing 'player list' container so any number of
|
||||
/// players can be displayed.
|
||||
///
|
||||
/// GameManager_UI calls RefreshAll(LobbyState) when the lobby state changes;
|
||||
/// we cache the latest state and apply it on the next Update so all scene
|
||||
/// references stay on the main thread.
|
||||
/// </summary>
|
||||
public class LobbyDisplayUI : MonoBehaviour
|
||||
{
|
||||
// ── Static hub so GameManager_UI can push state updates ──────────────────
|
||||
private static readonly HashSet<LobbyDisplayUI> _all = new HashSet<LobbyDisplayUI>();
|
||||
public static void RefreshAll(LobbyState state)
|
||||
{
|
||||
foreach (var ui in _all) ui._pending = state;
|
||||
}
|
||||
|
||||
// ── Resolved scene refs ──────────────────────────────────────────────────
|
||||
private TMP_Text _codeLabel; // "lobby code" - prefix + JoinCode appended
|
||||
private TMP_Text _maxPlayersLabel; // "max players" - prefix + count appended
|
||||
private Button _leaveBtn; // "smazatbutton"
|
||||
private Button _infoBtn; // "lobby info"
|
||||
private Button _startBtn; // "stvořit"
|
||||
private RectTransform _playerListRT; // "player list" container - scroll lives inside
|
||||
private TMP_Text _waitingForHostText; // injected; visible to non-host only
|
||||
|
||||
// ── Injected scroll list (added once at Start, populated on every refresh) ─
|
||||
private RectTransform _scrollContent;
|
||||
private readonly List<GameObject> _rows = new List<GameObject>();
|
||||
|
||||
private LobbyState _pending;
|
||||
|
||||
// ── Colour palette (forwarded from UITheme) ──────────────────────────────
|
||||
static readonly Color C_ROW_A = UITheme.RowA;
|
||||
static readonly Color C_ROW_B = UITheme.RowB;
|
||||
static readonly Color C_DIVIDER = UITheme.SurfaceAlt;
|
||||
static readonly Color C_ACCENT = UITheme.Accent;
|
||||
static readonly Color C_GOLD = UITheme.Caution;
|
||||
static readonly Color C_WHITE = UITheme.TextHi;
|
||||
static readonly Color C_SOFT = UITheme.TextMid;
|
||||
|
||||
void OnEnable() => _all.Add(this);
|
||||
void OnDisable() => _all.Remove(this);
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Wire emoji fallback ASAP so player-list rows with emoji glyphs
|
||||
// (host crown, "you" badge, etc.) render correctly on the first
|
||||
// refresh.
|
||||
UITheme.EnsureEmojiFontFallback();
|
||||
|
||||
var canvasGO = GameObject.Find("Canvas");
|
||||
if (canvasGO == null)
|
||||
{
|
||||
Debug.LogError("[LobbyDisplayUI] No Canvas found in create scene.");
|
||||
return;
|
||||
}
|
||||
var canvas = canvasGO.transform;
|
||||
|
||||
// ── Bind existing scene elements ────────────────────────────────────
|
||||
_codeLabel = FindTMP(canvas, "lobby code");
|
||||
_maxPlayersLabel = FindTMP(canvas, "max players");
|
||||
_leaveBtn = FindButton(canvas, "smazatbutton");
|
||||
_infoBtn = FindButton(canvas, "lobby info");
|
||||
_startBtn = FindButton(canvas, "stvořit");
|
||||
var listGO = FindByName(canvas, "player list");
|
||||
_playerListRT = listGO != null ? listGO as RectTransform : null;
|
||||
|
||||
// ── Wire buttons (preserve existing AudioSource OnClick by appending) ──
|
||||
if (_leaveBtn != null)
|
||||
{
|
||||
// Don't clear; the art team's AudioSource.Play hook should still
|
||||
// fire alongside the leave action.
|
||||
_leaveBtn.onClick.AddListener(() => GameManager.Instance?.LeaveLobbyButton());
|
||||
}
|
||||
else Debug.LogWarning("[LobbyDisplayUI] 'smazatbutton' not found.");
|
||||
|
||||
if (_infoBtn != null)
|
||||
{
|
||||
// Tap-to-copy the lobby code to clipboard. Cheap useful action that
|
||||
// gives the existing info button real behavior.
|
||||
_infoBtn.onClick.AddListener(() =>
|
||||
{
|
||||
var code = GameManager.Instance?.gameClient?.CurrentLobbyState?.JoinCode;
|
||||
if (!string.IsNullOrEmpty(code)) GUIUtility.systemCopyBuffer = code;
|
||||
});
|
||||
}
|
||||
|
||||
if (_startBtn != null)
|
||||
{
|
||||
_startBtn.onClick.AddListener(() => GameManager.Instance?.StartGameButton());
|
||||
}
|
||||
else Debug.LogWarning("[LobbyDisplayUI] 'stvořit' not found.");
|
||||
|
||||
// ── Hide the hardcoded placeholder slots ────────────────────────────
|
||||
// These were kept in the scene as visual previews of what filled rows
|
||||
// would look like; with a working scrollable list they're redundant.
|
||||
var tuff = FindByName(canvas, "tuff jmeno");
|
||||
var netuff = FindByName(canvas, "netuff jmeno");
|
||||
if (tuff) tuff.gameObject.SetActive(false);
|
||||
if (netuff) netuff.gameObject.SetActive(false);
|
||||
|
||||
// ── Override the player list's RectTransform to a sensible portrait
|
||||
// layout. The art team's anchored position + size were calibrated
|
||||
// for a different reference resolution and the element only filled
|
||||
// a third of the viewable area at 1080x1920. Stretch it to fill
|
||||
// the central portion of the screen with even insets.
|
||||
if (_playerListRT != null)
|
||||
{
|
||||
_playerListRT.anchorMin = new Vector2(0, 0);
|
||||
_playerListRT.anchorMax = new Vector2(1, 1);
|
||||
_playerListRT.pivot = new Vector2(0.5f, 0.5f);
|
||||
// Top inset = 320 (room for header / lobby code), bottom inset =
|
||||
// 380 (room for the start button or waiting message), 60px sides.
|
||||
_playerListRT.offsetMin = new Vector2(60, 380);
|
||||
_playerListRT.offsetMax = new Vector2(-60, -320);
|
||||
_playerListRT.anchoredPosition = Vector2.zero;
|
||||
BuildScrollList(_playerListRT);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[LobbyDisplayUI] 'player list' container not found - " +
|
||||
"falling back to no list rendering.");
|
||||
}
|
||||
|
||||
// Also re-anchor the "player list neglow" decoration to track the new
|
||||
// player list region, so the glow doesn't float empty offscreen.
|
||||
var neglow = FindByName(canvas, "player list neglow") as RectTransform;
|
||||
if (neglow != null)
|
||||
{
|
||||
neglow.anchorMin = new Vector2(0, 0);
|
||||
neglow.anchorMax = new Vector2(1, 1);
|
||||
neglow.pivot = new Vector2(0.5f, 0.5f);
|
||||
neglow.offsetMin = new Vector2(40, 360);
|
||||
neglow.offsetMax = new Vector2(-40, -300);
|
||||
}
|
||||
|
||||
// ── Inject "Waiting for host..." text for non-host players ──────────
|
||||
// Visible only when the local player isn't the lobby owner; sits in
|
||||
// the same vertical strip as the start button so the screen has a
|
||||
// single consistent action zone for both roles.
|
||||
var waitGO = MakeRT("WaitingForHost", canvas);
|
||||
Anchor(waitGO,
|
||||
new Vector2(0.05f, 0), new Vector2(0.95f, 0),
|
||||
new Vector2(0, 60), new Vector2(0, 240));
|
||||
_waitingForHostText = waitGO.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
_waitingForHostText.text = "⌛ Waiting for host to start the game...";
|
||||
_waitingForHostText.fontSize = 38;
|
||||
_waitingForHostText.color = new Color(0.73f, 0.80f, 0.88f);
|
||||
_waitingForHostText.fontStyle = FontStyles.Italic;
|
||||
_waitingForHostText.alignment = TextAlignmentOptions.Center;
|
||||
_waitingForHostText.gameObject.SetActive(false); // toggled per refresh
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
var gm = GameManager.Instance;
|
||||
if (gm?.gameClient?.CurrentLobbyState != null)
|
||||
_pending = gm.gameClient.CurrentLobbyState;
|
||||
|
||||
if (_pending != null)
|
||||
{
|
||||
Refresh(_pending);
|
||||
_pending = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Scroll list construction ─────────────────────────────────────────────
|
||||
void BuildScrollList(RectTransform listRoot)
|
||||
{
|
||||
// Strip any existing children of `player list` (typically one
|
||||
// placeholder TMP). Don't touch the listRoot itself - it has the art
|
||||
// team's anchoring + glow pairing we want to preserve.
|
||||
var kill = new List<GameObject>();
|
||||
foreach (Transform child in listRoot) kill.Add(child.gameObject);
|
||||
foreach (var go in kill) DestroyImmediate(go);
|
||||
|
||||
// ScrollRect on the list root
|
||||
var sr = listRoot.gameObject.GetComponent<ScrollRect>()
|
||||
?? listRoot.gameObject.AddComponent<ScrollRect>();
|
||||
sr.horizontal = false; sr.vertical = true;
|
||||
sr.movementType = ScrollRect.MovementType.Elastic;
|
||||
sr.elasticity = 0.1f;
|
||||
sr.scrollSensitivity = 80f;
|
||||
|
||||
// Viewport (with mask for clipping)
|
||||
var viewport = MakeRT("Viewport", listRoot);
|
||||
Stretch(viewport);
|
||||
var vpImg = viewport.gameObject.AddComponent<Image>();
|
||||
vpImg.color = new Color(0,0,0,0);
|
||||
viewport.gameObject.AddComponent<RectMask2D>();
|
||||
|
||||
// Content - populated from State.Players
|
||||
var content = MakeRT("Content", viewport);
|
||||
content.anchorMin = new Vector2(0, 1);
|
||||
content.anchorMax = new Vector2(1, 1);
|
||||
content.pivot = new Vector2(0.5f, 1);
|
||||
content.sizeDelta = Vector2.zero;
|
||||
content.anchoredPosition = Vector2.zero;
|
||||
var vlg = content.gameObject.AddComponent<VerticalLayoutGroup>();
|
||||
vlg.childControlWidth = true;
|
||||
vlg.childControlHeight = false;
|
||||
vlg.childForceExpandWidth = true;
|
||||
vlg.childForceExpandHeight = false;
|
||||
vlg.spacing = 4;
|
||||
var csf = content.gameObject.AddComponent<ContentSizeFitter>();
|
||||
csf.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
sr.viewport = viewport;
|
||||
sr.content = content;
|
||||
|
||||
_scrollContent = content;
|
||||
}
|
||||
|
||||
// ── State refresh ────────────────────────────────────────────────────────
|
||||
void Refresh(LobbyState state)
|
||||
{
|
||||
// Lobby code label (preserve art team prefix)
|
||||
if (_codeLabel != null)
|
||||
{
|
||||
const string prefix = "Lobby code: ";
|
||||
_codeLabel.text = prefix + (state.JoinCode ?? "------");
|
||||
}
|
||||
|
||||
// Max players label - we use this for live player count too. The art
|
||||
// team's prefix "Max players: " gets appended with current/max.
|
||||
if (_maxPlayersLabel != null)
|
||||
{
|
||||
int n = state.Players != null ? state.Players.Count : 0;
|
||||
_maxPlayersLabel.text = $"Players: {n}";
|
||||
}
|
||||
|
||||
// Unified screen for both owner and joiner. Host gets the start
|
||||
// button + working settings; non-host gets a clear "waiting" message
|
||||
// in the same screen real estate so the layout stays consistent.
|
||||
bool isHost = GameManager.Instance?.gameClient?.IsOwner ?? false;
|
||||
if (_startBtn != null)
|
||||
_startBtn.gameObject.SetActive(isHost);
|
||||
if (_waitingForHostText != null)
|
||||
{
|
||||
_waitingForHostText.gameObject.SetActive(!isHost);
|
||||
_waitingForHostText.text = state.Phase == GamePhase.Loading
|
||||
? "⏳ Downloading map data..."
|
||||
: "⌛ Waiting for host to start the game...";
|
||||
}
|
||||
|
||||
if (_scrollContent == null) return;
|
||||
|
||||
// Clear and rebuild rows. Player count is small (<= 15 per server
|
||||
// config), so a full rebuild every refresh is cheap and avoids the
|
||||
// bookkeeping of incremental updates.
|
||||
foreach (var row in _rows) Destroy(row);
|
||||
_rows.Clear();
|
||||
|
||||
if (state.Players == null) return;
|
||||
|
||||
string myId = GameManager.Instance?.gameClient?.ClientUuid ?? "";
|
||||
for (int i = 0; i < state.Players.Count; i++)
|
||||
{
|
||||
var p = state.Players[i];
|
||||
bool isMe = p.ClientUuid == myId;
|
||||
var row = BuildRow(p.DisplayName ?? "???", isMe, p.IsOwner,
|
||||
i % 2 == 0 ? C_ROW_A : C_ROW_B);
|
||||
row.transform.SetParent(_scrollContent, false);
|
||||
_rows.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
GameObject BuildRow(string playerName, bool isMe, bool isHostPlayer, Color bg)
|
||||
{
|
||||
const float ROW_H = 130f;
|
||||
var go = new GameObject("PlayerRow");
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.sizeDelta = new Vector2(0f, ROW_H);
|
||||
var le = go.AddComponent<LayoutElement>();
|
||||
le.minHeight = ROW_H;
|
||||
le.preferredHeight = ROW_H;
|
||||
|
||||
var bgImg = go.AddComponent<Image>();
|
||||
bgImg.color = bg;
|
||||
|
||||
// Bottom divider line
|
||||
var divRT = MakeRT("Div", rt);
|
||||
divRT.anchorMin = new Vector2(0, 0); divRT.anchorMax = new Vector2(1, 0);
|
||||
divRT.pivot = new Vector2(0.5f, 0);
|
||||
divRT.offsetMin = new Vector2(20, 0); divRT.offsetMax = new Vector2(-20, 2);
|
||||
var divImg = divRT.gameObject.AddComponent<Image>();
|
||||
divImg.color = C_DIVIDER;
|
||||
|
||||
float nameLeft = 24f;
|
||||
if (isHostPlayer)
|
||||
{
|
||||
var crownRT = MakeRT("Crown", rt);
|
||||
crownRT.anchorMin = new Vector2(0, 0.5f); crownRT.anchorMax = new Vector2(0, 0.5f);
|
||||
crownRT.pivot = new Vector2(0, 0.5f);
|
||||
crownRT.sizeDelta = new Vector2(90, 90);
|
||||
crownRT.anchoredPosition = new Vector2(18, 0);
|
||||
var crownTmp = crownRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
crownTmp.text = "👑"; crownTmp.fontSize = 52;
|
||||
crownTmp.color = C_GOLD; crownTmp.alignment = TextAlignmentOptions.Center;
|
||||
nameLeft = 118f;
|
||||
}
|
||||
|
||||
// Name label
|
||||
float nameMaxX = isMe ? 0.68f : 1f;
|
||||
var nameRT = MakeRT("Name", rt);
|
||||
nameRT.anchorMin = new Vector2(0, 0); nameRT.anchorMax = new Vector2(nameMaxX, 1);
|
||||
nameRT.offsetMin = new Vector2(nameLeft, 6); nameRT.offsetMax = new Vector2(-10, -6);
|
||||
var nt = nameRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
nt.text = playerName; nt.fontSize = 48;
|
||||
nt.color = isMe ? C_WHITE : C_SOFT;
|
||||
nt.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
nt.fontStyle = isMe ? FontStyles.Bold : FontStyles.Normal;
|
||||
nt.overflowMode = TextOverflowModes.Ellipsis;
|
||||
|
||||
// "YOU" badge
|
||||
if (isMe)
|
||||
{
|
||||
var badgeRT = MakeRT("YouBadge", rt);
|
||||
badgeRT.anchorMin = new Vector2(0.68f, 0.22f);
|
||||
badgeRT.anchorMax = new Vector2(1f, 0.78f);
|
||||
badgeRT.offsetMin = Vector2.zero;
|
||||
badgeRT.offsetMax = new Vector2(-20f, 0);
|
||||
var badgeImg = badgeRT.gameObject.AddComponent<Image>();
|
||||
badgeImg.color = C_ACCENT;
|
||||
var badgeTxtRT = MakeRT("Txt", badgeRT);
|
||||
Stretch(badgeTxtRT);
|
||||
var badgeTmp = badgeTxtRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
badgeTmp.text = "YOU"; badgeTmp.fontSize = 30;
|
||||
badgeTmp.color = C_WHITE; badgeTmp.fontStyle = FontStyles.Bold;
|
||||
badgeTmp.alignment = TextAlignmentOptions.Center;
|
||||
}
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
// ── Scene-binding helpers ────────────────────────────────────────────────
|
||||
|
||||
static Button FindButton(Transform root, string name)
|
||||
{
|
||||
var t = FindByName(root, name);
|
||||
return t != null ? t.GetComponent<Button>() : null;
|
||||
}
|
||||
|
||||
static TMP_Text FindTMP(Transform root, string name)
|
||||
{
|
||||
var t = FindByName(root, name);
|
||||
if (t == null) return null;
|
||||
return t.GetComponent<TMP_Text>() ?? t.GetComponentInChildren<TMP_Text>();
|
||||
}
|
||||
|
||||
static Transform FindByName(Transform root, string name)
|
||||
{
|
||||
if (root == null) return null;
|
||||
if (root.name == name) return root;
|
||||
foreach (Transform child in root)
|
||||
{
|
||||
var found = FindByName(child, name);
|
||||
if (found != null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ── Layout helpers ───────────────────────────────────────────────────────
|
||||
|
||||
static RectTransform MakeRT(string name, Transform parent)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.SetParent(parent, false);
|
||||
rt.localScale = Vector3.one;
|
||||
return rt;
|
||||
}
|
||||
|
||||
static void Stretch(RectTransform rt)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
static void Anchor(RectTransform rt, Vector2 aMin, Vector2 aMax,
|
||||
Vector2 offMin, Vector2 offMax)
|
||||
{
|
||||
rt.anchorMin = aMin; rt.anchorMax = aMax;
|
||||
rt.offsetMin = offMin; rt.offsetMax = offMax;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/LobbyDisplayUI.cs.meta
Normal file
11
Assets/Scripts/LobbyDisplayUI.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 290610b7d8fb7ea675982694abac90ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
216
Assets/Scripts/MapCameraController.cs
Normal file
216
Assets/Scripts/MapCameraController.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Attach to Main Camera in Client.unity.
|
||||
/// Top-down perspective camera that follows the local player capsule.
|
||||
///
|
||||
/// Features:
|
||||
/// • Auto-follow player when tracking (can be paused by dragging)
|
||||
/// • Single-finger touch drag (or mouse drag) to pan
|
||||
/// • Pinch gesture (or mouse scroll wheel) to zoom (changes camera height)
|
||||
/// • Double-tap anywhere to instantly recenter on player
|
||||
/// • Static Recenter() method called by the HUD recenter button
|
||||
/// </summary>
|
||||
public class MapCameraController : MonoBehaviour
|
||||
{
|
||||
// ── Singleton (weak — no DontDestroyOnLoad needed, camera lives in Client.unity) ──
|
||||
public static MapCameraController Instance { get; private set; }
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────────────────
|
||||
public void SetTarget(GameObject target) { _target = target; }
|
||||
public void Recenter() { _isTracking = true; _resumeTimer = 0f; }
|
||||
|
||||
// ── Tuning ────────────────────────────────────────────────────────────────
|
||||
private const float FollowSmoothing = 8f; // lerp speed when tracking
|
||||
private const float DefaultHeight = 150f; // camera Y (metres above ground)
|
||||
private const float MinHeight = 30f; // closest zoom
|
||||
private const float MaxHeight = 350f; // furthest zoom
|
||||
private const float PinchZoomSens = 1.2f; // multiplier for pinch speed
|
||||
private const float ScrollZoomSens = 30f; // world-units per scroll tick
|
||||
private const float ResumeDelay = 3.5f; // s after drag ends before auto-tracking resumes
|
||||
private const float DoubleTapWindow = 0.32f; // s between taps to count as double
|
||||
private const float DragThreshold = 8f; // pixels moved before drag starts
|
||||
|
||||
// ── Runtime state ─────────────────────────────────────────────────────────
|
||||
private Camera _cam;
|
||||
private GameObject _target;
|
||||
private float _currentHeight;
|
||||
private bool _isTracking = true;
|
||||
private float _resumeTimer;
|
||||
|
||||
// Drag
|
||||
private bool _dragActive;
|
||||
private Vector2 _lastDragScreen;
|
||||
|
||||
// Pinch
|
||||
private float _pinchStartDist = -1f;
|
||||
private float _pinchStartHeight;
|
||||
|
||||
// Double-tap
|
||||
private int _tapCount;
|
||||
private float _tapTimer;
|
||||
|
||||
// ── MonoBehaviour ─────────────────────────────────────────────────────────
|
||||
void Awake()
|
||||
{
|
||||
Instance = this;
|
||||
_cam = GetComponent<Camera>();
|
||||
if (_cam == null) { Debug.LogError("[MapCamera] No Camera component!"); return; }
|
||||
|
||||
// Keep existing perspective mode — just ensure straight-down orientation
|
||||
transform.rotation = Quaternion.Euler(90f, 0f, 0f);
|
||||
_currentHeight = transform.position.y > 1f ? transform.position.y : DefaultHeight;
|
||||
transform.position = new Vector3(transform.position.x, _currentHeight, transform.position.z);
|
||||
}
|
||||
|
||||
void OnEnable() { Instance = this; }
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
HandleInput();
|
||||
FollowTarget();
|
||||
}
|
||||
|
||||
// ── Target following ──────────────────────────────────────────────────────
|
||||
void FollowTarget()
|
||||
{
|
||||
if (!_isTracking || _target == null) return;
|
||||
Vector3 tp = _target.transform.position;
|
||||
Vector3 dest = new Vector3(tp.x, _currentHeight, tp.z);
|
||||
transform.position = Vector3.Lerp(transform.position, dest, Time.deltaTime * FollowSmoothing);
|
||||
}
|
||||
|
||||
// ── Input ─────────────────────────────────────────────────────────────────
|
||||
void HandleInput()
|
||||
{
|
||||
// Auto-resume tracking after a period of no dragging
|
||||
if (!_isTracking)
|
||||
{
|
||||
_resumeTimer += Time.deltaTime;
|
||||
if (_resumeTimer >= ResumeDelay) _isTracking = true;
|
||||
}
|
||||
|
||||
// Double-tap timer
|
||||
_tapTimer += Time.deltaTime;
|
||||
if (_tapTimer > DoubleTapWindow) _tapCount = 0;
|
||||
|
||||
int tc = Input.touchCount;
|
||||
|
||||
if (tc == 2)
|
||||
{
|
||||
HandlePinch();
|
||||
return;
|
||||
}
|
||||
|
||||
_pinchStartDist = -1f; // reset pinch when not 2 fingers
|
||||
|
||||
if (tc == 1)
|
||||
{
|
||||
Touch t = Input.GetTouch(0);
|
||||
switch (t.phase)
|
||||
{
|
||||
case TouchPhase.Began:
|
||||
OnPointerDown(t.position);
|
||||
break;
|
||||
case TouchPhase.Moved:
|
||||
case TouchPhase.Stationary:
|
||||
OnPointerDrag(t.position);
|
||||
break;
|
||||
case TouchPhase.Ended:
|
||||
case TouchPhase.Canceled:
|
||||
OnPointerUp();
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Mouse fallback (editor / desktop)
|
||||
if (Input.GetMouseButtonDown(0)) OnPointerDown(Input.mousePosition);
|
||||
else if (Input.GetMouseButton(0)) OnPointerDrag(Input.mousePosition);
|
||||
else if (Input.GetMouseButtonUp(0)) OnPointerUp();
|
||||
|
||||
float scroll = Input.GetAxis("Mouse ScrollWheel");
|
||||
if (Mathf.Abs(scroll) > 0.001f)
|
||||
{
|
||||
_currentHeight = Mathf.Clamp(_currentHeight - scroll * ScrollZoomSens, MinHeight, MaxHeight);
|
||||
transform.position = new Vector3(transform.position.x, _currentHeight, transform.position.z);
|
||||
}
|
||||
}
|
||||
|
||||
void OnPointerDown(Vector2 screenPos)
|
||||
{
|
||||
_lastDragScreen = screenPos;
|
||||
_dragActive = false;
|
||||
|
||||
// Double-tap detection
|
||||
_tapCount++;
|
||||
_tapTimer = 0f;
|
||||
if (_tapCount >= 2)
|
||||
{
|
||||
_tapCount = 0;
|
||||
Recenter();
|
||||
}
|
||||
}
|
||||
|
||||
void OnPointerDrag(Vector2 screenPos)
|
||||
{
|
||||
Vector2 screenDelta = screenPos - _lastDragScreen;
|
||||
|
||||
if (!_dragActive && screenDelta.magnitude > DragThreshold)
|
||||
{
|
||||
_dragActive = true;
|
||||
_isTracking = false;
|
||||
_resumeTimer = 0f;
|
||||
}
|
||||
|
||||
if (_dragActive)
|
||||
{
|
||||
// Pan: move camera so that the world point under the finger stays fixed.
|
||||
// Because the camera faces straight down, we can use a simpler formula:
|
||||
// pixels → world = (camera height / focal length in pixels) ratio.
|
||||
// For perspective: visible half-height at ground = height * tan(fov/2)
|
||||
// world_per_pixel = 2 * height * tan(fov/2) / screenHeight
|
||||
float halfFovRad = _cam.fieldOfView * 0.5f * Mathf.Deg2Rad;
|
||||
float worldPerPixelY = 2f * _currentHeight * Mathf.Tan(halfFovRad) / Screen.height;
|
||||
float worldPerPixelX = worldPerPixelY * ((float)Screen.width / Screen.height);
|
||||
|
||||
// Flip: dragging right moves world right (camera moves left)
|
||||
transform.position += new Vector3(
|
||||
-screenDelta.x * worldPerPixelX,
|
||||
0f,
|
||||
-screenDelta.y * worldPerPixelY
|
||||
);
|
||||
}
|
||||
|
||||
_lastDragScreen = screenPos;
|
||||
}
|
||||
|
||||
void OnPointerUp()
|
||||
{
|
||||
_dragActive = false;
|
||||
}
|
||||
|
||||
// ── Pinch zoom ────────────────────────────────────────────────────────────
|
||||
void HandlePinch()
|
||||
{
|
||||
Touch t0 = Input.GetTouch(0);
|
||||
Touch t1 = Input.GetTouch(1);
|
||||
|
||||
if (t0.phase == TouchPhase.Began || t1.phase == TouchPhase.Began)
|
||||
{
|
||||
_pinchStartDist = Vector2.Distance(t0.position, t1.position);
|
||||
_pinchStartHeight = _currentHeight;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pinchStartDist <= 0f) return;
|
||||
|
||||
float currentDist = Vector2.Distance(t0.position, t1.position);
|
||||
if (currentDist < 1f) return;
|
||||
|
||||
// Closer fingers = zoom in (lower height)
|
||||
float ratio = _pinchStartDist / currentDist;
|
||||
_currentHeight = Mathf.Clamp(_pinchStartHeight * ratio * PinchZoomSens, MinHeight, MaxHeight);
|
||||
transform.position = new Vector3(transform.position.x, _currentHeight, transform.position.z);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/MapCameraController.cs.meta
Normal file
2
Assets/Scripts/MapCameraController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2108dcbe61d3945f2aa588f69100e95f
|
||||
@@ -1,500 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
|
||||
public class MapRenderer : MonoBehaviour
|
||||
{
|
||||
[Header("Overpass settings")]
|
||||
public const string overpassUrl = "https://mapz.honzuvkod.dev/api/interpreter";
|
||||
public float queryRadiusMeters = 200f; // radius around lat/lon to query
|
||||
|
||||
[Header("Location (lat, lon)")]
|
||||
public GPSManager gpsManager;
|
||||
private double latitude = 50.7727878;
|
||||
private double longitude = 15.0718625;
|
||||
|
||||
[Header("Building settings")]
|
||||
public Material buildingMaterial;
|
||||
public float defaultFloorHeight = 3.0f; // meters per level
|
||||
public float defaultBuildingHeight = 6.0f; // if no tags
|
||||
|
||||
[Header("Road settings")]
|
||||
public Material roadMaterial;
|
||||
public float defaultRoadWidth = 4.0f; // meters
|
||||
public float motorwayWidth = 10.0f;
|
||||
public float primaryWidth = 8.0f;
|
||||
public float secondaryWidth = 6.0f;
|
||||
public float tertiaryWidth = 5.0f;
|
||||
|
||||
|
||||
[Header("Misc")]
|
||||
public float _metersPerUnit = 1f; // scale: 1 unit = 1 meter
|
||||
|
||||
|
||||
[Header("Storage")]
|
||||
Dictionary<long, Vector2> nodes = new Dictionary<long, Vector2>(); // id -> latlon
|
||||
List<Way> parsedWays = new List<Way>();
|
||||
|
||||
void Start()
|
||||
{
|
||||
StartCoroutine(RenderMap());
|
||||
}
|
||||
IEnumerator RenderMap()
|
||||
{
|
||||
ClearChildren();
|
||||
|
||||
double[] GPS = gpsManager.GetLastCoords();
|
||||
latitude = GPS[0];
|
||||
longitude = GPS[1];
|
||||
|
||||
string q = $"[out:xml][timeout:90];(way[\"building\"](around:{queryRadiusMeters.ToString().Replace(",", ".")},{latitude.ToString().Replace(",", ".")},{longitude.ToString().Replace(",", ".")});way[\"highway\"](around:{queryRadiusMeters.ToString().Replace(",", ".")},{latitude.ToString().Replace(",", ".")},{longitude.ToString().Replace(",", ".")}););(._;>;);out body;";
|
||||
|
||||
WWWForm form = new WWWForm();
|
||||
form.AddField("data", q);
|
||||
|
||||
using (UnityWebRequest www = UnityWebRequest.Post(overpassUrl, form))
|
||||
{
|
||||
www.downloadHandler = new DownloadHandlerBuffer();
|
||||
yield return www.SendWebRequest();
|
||||
|
||||
if (www.result != UnityWebRequest.Result.Success)
|
||||
{
|
||||
Debug.LogError("Overpass request failed: " + www.error);
|
||||
yield break;
|
||||
}
|
||||
|
||||
string xml = www.downloadHandler.text;
|
||||
ParseOverpassXml(xml);
|
||||
|
||||
GameObject buildingsRoot = new GameObject("Buildings");
|
||||
buildingsRoot.transform.parent = this.transform;
|
||||
|
||||
GameObject roadsRoot = new GameObject("Roads");
|
||||
roadsRoot.transform.parent = this.transform;
|
||||
|
||||
foreach (var w in parsedWays)
|
||||
{
|
||||
if (w.tags.ContainsKey("building"))
|
||||
{
|
||||
GameObject b = BuildBuildingMesh(w);
|
||||
b.transform.parent = buildingsRoot.transform;
|
||||
}
|
||||
else if (w.tags.ContainsKey("highway"))
|
||||
{
|
||||
GameObject r = BuildRoadMesh(w);
|
||||
r.transform.parent = roadsRoot.transform;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log("Map generation complete: " + parsedWays.Count + " ways, " + nodes.Count + " nodes.");
|
||||
}
|
||||
yield return StartCoroutine(RenderMap());
|
||||
}
|
||||
|
||||
void ClearChildren()
|
||||
{
|
||||
List<GameObject> toDestroy = new List<GameObject>();
|
||||
foreach (Transform t in transform)
|
||||
toDestroy.Add(t.gameObject);
|
||||
foreach (var g in toDestroy)
|
||||
DestroyImmediate(g);
|
||||
}
|
||||
|
||||
#region Overpass XML parsing
|
||||
class Way
|
||||
{
|
||||
public long id;
|
||||
public List<long> nodeRefs = new List<long>();
|
||||
public Dictionary<string, string> tags = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ParseOverpassXml(string xmlText)
|
||||
{
|
||||
nodes.Clear();
|
||||
parsedWays.Clear();
|
||||
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.LoadXml(xmlText);
|
||||
|
||||
XmlNode osm = doc.SelectSingleNode("/osm");
|
||||
if (osm == null) return;
|
||||
|
||||
// parse nodes
|
||||
foreach (XmlNode node in osm.SelectNodes("node"))
|
||||
{
|
||||
long id = long.Parse(node.Attributes["id"].Value, CultureInfo.InvariantCulture);
|
||||
double lat = double.Parse(node.Attributes["lat"].Value, CultureInfo.InvariantCulture);
|
||||
double lon = double.Parse(node.Attributes["lon"].Value, CultureInfo.InvariantCulture);
|
||||
nodes[id] = new Vector2((float)lat, (float)lon);
|
||||
}
|
||||
|
||||
// parse ways
|
||||
foreach (XmlNode wayNode in osm.SelectNodes("way"))
|
||||
{
|
||||
Way w = new Way();
|
||||
w.id = long.Parse(wayNode.Attributes["id"].Value, CultureInfo.InvariantCulture);
|
||||
foreach (XmlNode child in wayNode.ChildNodes)
|
||||
{
|
||||
if (child.Name == "nd")
|
||||
{
|
||||
long r = long.Parse(child.Attributes["ref"].Value, CultureInfo.InvariantCulture);
|
||||
w.nodeRefs.Add(r);
|
||||
}
|
||||
else if (child.Name == "tag")
|
||||
{
|
||||
string k = child.Attributes["k"].Value;
|
||||
string v = child.Attributes["v"].Value;
|
||||
w.tags[k] = v;
|
||||
}
|
||||
}
|
||||
parsedWays.Add(w);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Utilities: latlon to local meters
|
||||
// Convert latitude/longitude to local XY meters relative to center point
|
||||
Vector3 LatLonToLocal(double lat, double lon)
|
||||
{
|
||||
// Use simple equirectangular projection around center (latitude, longitude)
|
||||
double lat0 = latitude;
|
||||
double lon0 = longitude;
|
||||
double dLat = (lat - lat0) * Mathf.Deg2Rad;
|
||||
double dLon = (lon - lon0) * Mathf.Deg2Rad;
|
||||
double R = 6378137.0; // Earth radius in meters
|
||||
double x = R * dLon * Math.Cos(lat0 * Mathf.Deg2Rad);
|
||||
double y = R * dLat;
|
||||
return new Vector3((float)x / _metersPerUnit, 0f, (float)y / _metersPerUnit);
|
||||
}
|
||||
Vector3 NodeIdToLocal(long nodeId)
|
||||
{
|
||||
if (!nodes.ContainsKey(nodeId))
|
||||
return Vector3.zero;
|
||||
Vector2 latlon = nodes[nodeId];
|
||||
return LatLonToLocal(latlon.x, latlon.y);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Mesh builders
|
||||
GameObject BuildBuildingMesh(Way w)
|
||||
{
|
||||
// gather polygon points
|
||||
List<Vector3> poly = new List<Vector3>();
|
||||
foreach (var id in w.nodeRefs)
|
||||
{
|
||||
Vector3 p = NodeIdToLocal(id);
|
||||
poly.Add(p);
|
||||
}
|
||||
|
||||
// ensure closed
|
||||
if (poly.Count < 3) return null;
|
||||
if ((poly[0] - poly[poly.Count - 1]).sqrMagnitude > 0.0001f)
|
||||
poly.Add(poly[0]);
|
||||
|
||||
// determine height
|
||||
float height = defaultBuildingHeight;
|
||||
if (w.tags.ContainsKey("height"))
|
||||
{
|
||||
if (TryParseHeight(w.tags["height"], out float h)) height = h;
|
||||
}
|
||||
else if (w.tags.ContainsKey("building:levels"))
|
||||
{
|
||||
if (float.TryParse(w.tags["building:levels"], NumberStyles.Float, CultureInfo.InvariantCulture, out float levels))
|
||||
height = Mathf.Max(0.5f, levels * defaultFloorHeight);
|
||||
}
|
||||
else if (w.tags.ContainsKey("levels"))
|
||||
{
|
||||
if (float.TryParse(w.tags["levels"], NumberStyles.Float, CultureInfo.InvariantCulture, out float levels))
|
||||
height = Mathf.Max(0.5f, levels * defaultFloorHeight);
|
||||
}
|
||||
|
||||
// create GameObject
|
||||
GameObject go = new GameObject("Building_" + w.id);
|
||||
MeshFilter mf = go.AddComponent<MeshFilter>();
|
||||
MeshRenderer mr = go.AddComponent<MeshRenderer>();
|
||||
mr.material = buildingMaterial;
|
||||
|
||||
// generate mesh: roof (triangulated polygon) + walls (extruded quads)
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.name = "BuildingMesh_" + w.id;
|
||||
|
||||
// Convert poly to 2D points (XZ plane)
|
||||
List<Vector2> poly2D = new List<Vector2>();
|
||||
for (int i = 0; i < poly.Count - 1; i++) // omit last repeated point
|
||||
poly2D.Add(new Vector2(poly[i].x, poly[i].z));
|
||||
|
||||
// triangulate roof
|
||||
List<int> roofTris = Triangulate(poly2D);
|
||||
if (roofTris == null || roofTris.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("Triangulation failed for building " + w.id);
|
||||
return go;
|
||||
}
|
||||
|
||||
// Build vertices: roof vertices at y=height, walls vertices (2 per poly vertex)
|
||||
int n = poly2D.Count;
|
||||
// Build vertices and triangles with NO SHARED VERTICES (flat shading)
|
||||
List<Vector3> verts = new List<Vector3>();
|
||||
List<int> triangles = new List<int>();
|
||||
List<Vector2> uvs = new List<Vector2>();
|
||||
|
||||
// Roof triangles - each triangle gets its own vertices
|
||||
for (int i = 0; i < roofTris.Count; i += 3)
|
||||
{
|
||||
int idx0 = roofTris[i];
|
||||
int idx1 = roofTris[i + 1];
|
||||
int idx2 = roofTris[i + 2];
|
||||
|
||||
Vector2 p0 = poly2D[idx0];
|
||||
Vector2 p1 = poly2D[idx1];
|
||||
Vector2 p2 = poly2D[idx2];
|
||||
|
||||
int baseIdx = verts.Count;
|
||||
verts.Add(new Vector3(p0.x, height / _metersPerUnit, p0.y));
|
||||
verts.Add(new Vector3(p1.x, height / _metersPerUnit, p1.y));
|
||||
verts.Add(new Vector3(p2.x, height / _metersPerUnit, p2.y));
|
||||
|
||||
triangles.Add(baseIdx);
|
||||
triangles.Add(baseIdx + 1);
|
||||
triangles.Add(baseIdx + 2);
|
||||
|
||||
uvs.Add(new Vector2(p0.x, p0.y));
|
||||
uvs.Add(new Vector2(p1.x, p1.y));
|
||||
uvs.Add(new Vector2(p2.x, p2.y));
|
||||
}
|
||||
|
||||
// Walls - each quad gets its own 4 vertices
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int iNext = (i + 1) % n;
|
||||
Vector2 p0 = poly2D[i];
|
||||
Vector2 p1 = poly2D[iNext];
|
||||
|
||||
int baseIdx = verts.Count;
|
||||
verts.Add(new Vector3(p0.x, height / _metersPerUnit, p0.y)); // top left
|
||||
verts.Add(new Vector3(p0.x, 0, p0.y)); // bottom left
|
||||
verts.Add(new Vector3(p1.x, 0, p1.y)); // bottom right
|
||||
verts.Add(new Vector3(p1.x, height / _metersPerUnit, p1.y)); // top right
|
||||
|
||||
triangles.Add(baseIdx);
|
||||
triangles.Add(baseIdx + 1);
|
||||
triangles.Add(baseIdx + 2);
|
||||
|
||||
triangles.Add(baseIdx);
|
||||
triangles.Add(baseIdx + 2);
|
||||
triangles.Add(baseIdx + 3);
|
||||
|
||||
uvs.Add(new Vector2(0, 1));
|
||||
uvs.Add(new Vector2(0, 0));
|
||||
uvs.Add(new Vector2(1, 0));
|
||||
uvs.Add(new Vector2(1, 1));
|
||||
}
|
||||
|
||||
mesh.SetVertices(verts);
|
||||
mesh.SetTriangles(triangles, 0);
|
||||
mesh.SetUVs(0, uvs);
|
||||
mesh.RecalculateNormals();
|
||||
mesh.RecalculateBounds();
|
||||
|
||||
mf.mesh = mesh;
|
||||
|
||||
// Center object (using first roof vertex as reference)
|
||||
Vector3 centroid = Vector3.zero;
|
||||
for (int i = 0; i < roofTris.Count; i += 3)
|
||||
{
|
||||
centroid += verts[i];
|
||||
}
|
||||
centroid /= (roofTris.Count / 3);
|
||||
go.transform.position = centroid * -1f;
|
||||
|
||||
// Move the roof/walls vertices back to local space
|
||||
Vector3[] adjustedVerts = mesh.vertices;
|
||||
for (int i = 0; i < adjustedVerts.Length; i++) adjustedVerts[i] += centroid;
|
||||
mesh.vertices = adjustedVerts;
|
||||
mesh.RecalculateNormals();
|
||||
mesh.RecalculateBounds();
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
GameObject BuildRoadMesh(Way w)
|
||||
{
|
||||
// build polyline
|
||||
List<Vector3> pts = new List<Vector3>();
|
||||
foreach (var id in w.nodeRefs)
|
||||
pts.Add(NodeIdToLocal(id));
|
||||
if (pts.Count < 2) return null;
|
||||
|
||||
float width = defaultRoadWidth;
|
||||
if (w.tags.ContainsKey("width") && float.TryParse(w.tags["width"], NumberStyles.Float, CultureInfo.InvariantCulture, out float wv))
|
||||
width = wv;
|
||||
else if (w.tags.ContainsKey("highway"))
|
||||
{
|
||||
// simple heuristic
|
||||
string h = w.tags["highway"];
|
||||
if (h == "motorway") width = motorwayWidth;
|
||||
else if (h == "primary") width = primaryWidth;
|
||||
else if (h == "secondary") width = secondaryWidth;
|
||||
else if (h == "tertiary") width = tertiaryWidth;
|
||||
else width = defaultRoadWidth;
|
||||
}
|
||||
|
||||
GameObject go = new GameObject("Road_" + w.id);
|
||||
MeshFilter mf = go.AddComponent<MeshFilter>();
|
||||
MeshRenderer mr = go.AddComponent<MeshRenderer>();
|
||||
mr.material = roadMaterial;
|
||||
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.name = "RoadMesh_" + w.id;
|
||||
|
||||
List<Vector3> verts = new List<Vector3>();
|
||||
List<int> tris = new List<int>();
|
||||
List<Vector2> uvs = new List<Vector2>();
|
||||
|
||||
// build quad strip
|
||||
for (int i = 0; i < pts.Count; i++)
|
||||
{
|
||||
Vector3 p = pts[i];
|
||||
Vector3 dir;
|
||||
if (i == 0) dir = (pts[i + 1] - p).normalized;
|
||||
else if (i == pts.Count - 1) dir = (p - pts[i - 1]).normalized;
|
||||
else dir = (pts[i + 1] - pts[i - 1]).normalized;
|
||||
|
||||
Vector3 normal = Vector3.Cross(dir, Vector3.up).normalized;
|
||||
Vector3 left = p + normal * (width * 0.5f / _metersPerUnit);
|
||||
Vector3 right = p - normal * (width * 0.5f / _metersPerUnit);
|
||||
verts.Add(left);
|
||||
verts.Add(right);
|
||||
uvs.Add(new Vector2(0, i));
|
||||
uvs.Add(new Vector2(1, i));
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
int baseIdx = verts.Count - 4;
|
||||
tris.Add(baseIdx + 0);
|
||||
tris.Add(baseIdx + 2);
|
||||
tris.Add(baseIdx + 1);
|
||||
|
||||
tris.Add(baseIdx + 1);
|
||||
tris.Add(baseIdx + 2);
|
||||
tris.Add(baseIdx + 3);
|
||||
}
|
||||
}
|
||||
|
||||
mesh.SetVertices(verts);
|
||||
mesh.SetTriangles(tris, 0);
|
||||
mesh.SetUVs(0, uvs);
|
||||
mesh.RecalculateNormals();
|
||||
mesh.RecalculateBounds();
|
||||
|
||||
mf.mesh = mesh;
|
||||
go.transform.position = Vector3.zero;
|
||||
return go;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
bool TryParseHeight(string s, out float meters)
|
||||
{
|
||||
// try to parse heights like "12", "12.5m", "40 ft"
|
||||
s = s.Trim();
|
||||
meters = 0f;
|
||||
if (s.EndsWith("m")) s = s.Substring(0, s.Length - 1).Trim();
|
||||
if (float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out float v))
|
||||
{
|
||||
meters = v;
|
||||
return true;
|
||||
}
|
||||
// fallback: try to extract number
|
||||
StringBuilder num = new StringBuilder();
|
||||
foreach (char c in s)
|
||||
if ((c >= '0' && c <= '9') || c == '.' || c == ',') num.Append(c == ',' ? '.' : c);
|
||||
if (num.Length > 0 && float.TryParse(num.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out v))
|
||||
{
|
||||
meters = v; return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Basic ear clipping triangulation for simple polygons (2D)
|
||||
List<int> Triangulate(List<Vector2> poly)
|
||||
{
|
||||
List<int> indices = new List<int>();
|
||||
int n = poly.Count;
|
||||
if (n < 3) return indices;
|
||||
|
||||
List<int> V = new List<int>();
|
||||
for (int i = 0; i < n; i++) V.Add(i);
|
||||
|
||||
int guard = 0;
|
||||
while (V.Count > 3 && guard < 10000)
|
||||
{
|
||||
bool earFound = false;
|
||||
for (int i = 0; i < V.Count; i++)
|
||||
{
|
||||
int prev = V[(i - 1 + V.Count) % V.Count];
|
||||
int curr = V[i];
|
||||
int next = V[(i + 1) % V.Count];
|
||||
|
||||
Vector2 a = poly[prev];
|
||||
Vector2 b = poly[curr];
|
||||
Vector2 c = poly[next];
|
||||
|
||||
if (!IsConvex(a, b, c)) continue;
|
||||
|
||||
bool hasPointInside = false;
|
||||
for (int j = 0; j < V.Count; j++)
|
||||
{
|
||||
int vi = V[j];
|
||||
if (vi == prev || vi == curr || vi == next) continue;
|
||||
if (PointInTriangle(poly[vi], a, b, c)) { hasPointInside = true; break; }
|
||||
}
|
||||
if (hasPointInside) continue;
|
||||
|
||||
// ear found
|
||||
indices.Add(prev);
|
||||
indices.Add(curr);
|
||||
indices.Add(next);
|
||||
V.RemoveAt(i);
|
||||
earFound = true;
|
||||
break;
|
||||
}
|
||||
if (!earFound) break;
|
||||
guard++;
|
||||
}
|
||||
|
||||
if (V.Count == 3)
|
||||
{
|
||||
indices.Add(V[0]); indices.Add(V[1]); indices.Add(V[2]);
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
bool IsConvex(Vector2 a, Vector2 b, Vector2 c)
|
||||
{
|
||||
return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) < 0f; // changed > to
|
||||
}
|
||||
bool PointInTriangle(Vector2 p, Vector2 a, Vector2 b, Vector2 c)
|
||||
{
|
||||
float area = TriangleArea(a, b, c);
|
||||
float area1 = TriangleArea(p, b, c);
|
||||
float area2 = TriangleArea(a, p, c);
|
||||
float area3 = TriangleArea(a, b, p);
|
||||
return Mathf.Abs(area - (area1 + area2 + area3)) < 1e-3f;
|
||||
}
|
||||
|
||||
float TriangleArea(Vector2 a, Vector2 b, Vector2 c)
|
||||
{
|
||||
return Mathf.Abs((a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) * 0.5f);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54e66fbdb6a33134a934139bbf7252ef
|
||||
109
Assets/Scripts/PlayerNameInput.cs
Normal file
109
Assets/Scripts/PlayerNameInput.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
/// <summary>
|
||||
/// Attach to any GO in the main menu scene (e.g. UIManage).
|
||||
/// Finds the "name" canvas button at runtime and converts it into a
|
||||
/// fully functional TMP_InputField — preserving its RectTransform position/size.
|
||||
/// </summary>
|
||||
public class PlayerNameInput : MonoBehaviour
|
||||
{
|
||||
void Start()
|
||||
{
|
||||
var nameGO = GameObject.Find("name");
|
||||
if (nameGO == null) { Debug.LogError("[PlayerNameInput] 'name' GO not found."); return; }
|
||||
|
||||
var rt = nameGO.GetComponent<RectTransform>();
|
||||
if (rt == null) { Debug.LogError("[PlayerNameInput] 'name' has no RectTransform."); return; }
|
||||
|
||||
// Remove incompatible components (Button blocks input; old broken TMP_InputField)
|
||||
var btn = nameGO.GetComponent<Button>();
|
||||
if (btn != null) DestroyImmediate(btn);
|
||||
var oldField = nameGO.GetComponent<TMP_InputField>();
|
||||
if (oldField != null) DestroyImmediate(oldField);
|
||||
|
||||
// Remove all child GOs (Art-team text label children)
|
||||
var kill = new System.Collections.Generic.List<GameObject>();
|
||||
foreach (Transform child in rt) kill.Add(child.gameObject);
|
||||
foreach (var go in kill) DestroyImmediate(go);
|
||||
|
||||
// Keep / ensure background Image
|
||||
var img = nameGO.GetComponent<Image>();
|
||||
if (img == null) img = nameGO.AddComponent<Image>();
|
||||
img.color = new Color(0.08f, 0.10f, 0.20f, 0.92f);
|
||||
|
||||
// Build viewport > (Placeholder + Text) child hierarchy required by TMP_InputField
|
||||
var viewportRT = MakeChild("Text Area", rt);
|
||||
viewportRT.anchorMin = Vector2.zero;
|
||||
viewportRT.anchorMax = Vector2.one;
|
||||
viewportRT.offsetMin = new Vector2(14f, 4f);
|
||||
viewportRT.offsetMax = new Vector2(-14f, -4f);
|
||||
viewportRT.gameObject.AddComponent<RectMask2D>();
|
||||
|
||||
var phRT = MakeChild("Placeholder", viewportRT);
|
||||
Stretch(phRT);
|
||||
var ph = phRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
ph.text = "Enter your name...";
|
||||
ph.fontSize = 40;
|
||||
ph.color = new Color(0.55f, 0.60f, 0.70f, 0.85f);
|
||||
ph.fontStyle = FontStyles.Italic;
|
||||
ph.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
|
||||
var txtRT = MakeChild("Text", viewportRT);
|
||||
Stretch(txtRT);
|
||||
var txt = txtRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
txt.text = "";
|
||||
txt.fontSize = 40;
|
||||
txt.color = Color.white;
|
||||
txt.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
|
||||
// Add TMP_InputField and wire all required references
|
||||
var field = nameGO.AddComponent<TMP_InputField>();
|
||||
field.textViewport = viewportRT;
|
||||
field.textComponent = txt;
|
||||
field.placeholder = ph;
|
||||
field.targetGraphic = img;
|
||||
field.characterLimit = 32;
|
||||
field.keyboardType = TouchScreenKeyboardType.Default;
|
||||
field.shouldHideMobileInput = false;
|
||||
|
||||
// Restore saved name
|
||||
string saved = PlayerPrefs.GetString("PlayerName", "");
|
||||
if (!string.IsNullOrEmpty(saved))
|
||||
field.SetTextWithoutNotify(saved);
|
||||
|
||||
field.onValueChanged.AddListener(OnNameChanged);
|
||||
|
||||
// Write initial value to GameManager if present
|
||||
if (!string.IsNullOrEmpty(saved))
|
||||
OnNameChanged(saved);
|
||||
}
|
||||
|
||||
void OnNameChanged(string value)
|
||||
{
|
||||
PlayerPrefs.SetString("PlayerName", value);
|
||||
PlayerPrefs.Save();
|
||||
var gm = GameManager.Instance;
|
||||
if (gm == null) return;
|
||||
gm.displayName = value;
|
||||
if (gm.gameClient != null)
|
||||
gm.gameClient.DisplayName = value;
|
||||
}
|
||||
|
||||
RectTransform MakeChild(string name, RectTransform parent)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.SetParent(parent, false);
|
||||
rt.localScale = Vector3.one;
|
||||
return rt;
|
||||
}
|
||||
|
||||
void Stretch(RectTransform rt)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero;
|
||||
rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/PlayerNameInput.cs.meta
Normal file
11
Assets/Scripts/PlayerNameInput.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1305c74eacfaf90fd98134860492d46
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
324
Assets/Scripts/UITheme.cs
Normal file
324
Assets/Scripts/UITheme.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Centralized design tokens + styling helpers for every Canvas in the game.
|
||||
/// One source of truth for color, typography, spacing, animation timing, and
|
||||
/// emoji-capable font configuration. Replaces the per-file palette duplication
|
||||
/// that scattered across InGameHUDBuilder, HostLobbyUI, LobbyDisplayUI etc.
|
||||
///
|
||||
/// Why this exists (P10):
|
||||
/// - "UI looks tiny on phone, bleeds off-screen during voting, font drops
|
||||
/// emoji glyphs" - all symptoms of palette/typography/sizing values being
|
||||
/// defined per-file with no shared scale. Centralizing fixes the
|
||||
/// whole-app inconsistency, not just the worst offender.
|
||||
/// - Emoji rendering on Android needs TMP_Settings.fallbackFontAssets
|
||||
/// pointed at a Noto Color Emoji TMP_FontAsset. We do this at runtime
|
||||
/// so the host project doesn't need a pre-baked TMP_Settings asset
|
||||
/// change (which would touch a binary scriptable object).
|
||||
///
|
||||
/// Visual direction:
|
||||
/// - Dark base (#0A0E1A near-black) with one strong accent (teal-cyan
|
||||
/// #00C8C8). Readable in sunlight, doesn't clash with the orange/red
|
||||
/// impostor signals or the green map.
|
||||
/// - Typography ramp: one display weight, one body, one mono for codes.
|
||||
/// Sizes follow a major-third scale (1.25x ratio) so headlines
|
||||
/// dominate without shouting.
|
||||
/// - Spacing on an 8pt grid. Touch targets minimum 88pt (44pt @ 2x).
|
||||
/// - Animation: subtle. Fade 150ms, slide 200ms, ease-out. Game's tense -
|
||||
/// UI shouldn't be cute.
|
||||
/// </summary>
|
||||
public static class UITheme
|
||||
{
|
||||
// ── Palette ───────────────────────────────────────────────────────────────
|
||||
// Names follow purpose, not literal color. Adding a new component? Use
|
||||
// Bg/Surface/Accent/Success/Danger/Warning/Muted - not raw hex.
|
||||
|
||||
public static readonly Color Bg = H("#0A0E1A"); // page background
|
||||
public static readonly Color Surface = H("#121829"); // cards, panels
|
||||
public static readonly Color SurfaceAlt = H("#1A2138"); // elevated/active
|
||||
public static readonly Color SurfaceDim = H("#0E1322"); // recessed (input)
|
||||
public static readonly Color RowA = H("#1A2035"); // alt row
|
||||
public static readonly Color RowB = H("#161C2E"); // alt row
|
||||
public static readonly Color Border = new Color(1f, 1f, 1f, 0.10f);
|
||||
|
||||
public static readonly Color Accent = H("#00C8C8"); // teal-cyan, primary CTA
|
||||
public static readonly Color AccentDim = H("#0A8A8A"); // pressed state
|
||||
public static readonly Color Success = H("#2DB84B"); // task done, you-voted
|
||||
public static readonly Color Danger = H("#E04040"); // impostor, eject, kill
|
||||
public static readonly Color Warning = H("#F08C1A"); // sabotage, meltdown
|
||||
public static readonly Color Caution = H("#FFB800"); // reconnect, info
|
||||
|
||||
public static readonly Color TextHi = new Color(0.96f, 0.97f, 0.99f); // primary
|
||||
public static readonly Color TextMid = new Color(0.78f, 0.83f, 0.91f); // secondary
|
||||
public static readonly Color TextLo = new Color(0.55f, 0.62f, 0.75f); // tertiary/disabled
|
||||
public static readonly Color TextOnAccent= H("#001818"); // black-ish on bright accent
|
||||
|
||||
static Color H(string hex)
|
||||
{
|
||||
ColorUtility.TryParseHtmlString(hex, out var c);
|
||||
return c;
|
||||
}
|
||||
|
||||
// ── Typography ────────────────────────────────────────────────────────────
|
||||
// Sizes are in TMP "px" units which scale with the CanvasScaler reference.
|
||||
// Our reference is 1080x1920 portrait; these values produce the intended
|
||||
// physical size on a typical 6" phone (~5mm tall body text).
|
||||
|
||||
public const float FontDisplay = 64f; // hero (game-end win/loss)
|
||||
public const float FontHeadline = 44f; // section header
|
||||
public const float FontTitle = 32f; // panel header
|
||||
public const float FontBody = 26f; // standard text
|
||||
public const float FontSmall = 22f; // captions, helper text
|
||||
public const float FontTiny = 18f; // tiny meta, debug
|
||||
|
||||
// Action button text scales separately because thumb-zone CTAs need to
|
||||
// dominate visually even on a small screen.
|
||||
public const float FontActionBtn= 40f;
|
||||
|
||||
// ── Spacing (8pt grid) ────────────────────────────────────────────────────
|
||||
public const float S1 = 4f;
|
||||
public const float S2 = 8f;
|
||||
public const float S3 = 12f;
|
||||
public const float S4 = 16f;
|
||||
public const float S5 = 24f;
|
||||
public const float S6 = 32f;
|
||||
public const float S7 = 48f;
|
||||
public const float S8 = 64f;
|
||||
public const float S9 = 96f;
|
||||
|
||||
// ── Touch targets ─────────────────────────────────────────────────────────
|
||||
// Minimum heights tuned for 1080x1920 reference. CanvasScaler keeps these
|
||||
// visually consistent across phone sizes.
|
||||
public const float MinTapHeight = 88f; // 44pt @ 2x
|
||||
public const float StandardBtn = 110f; // typical button height
|
||||
public const float HeroBtn = 140f; // primary CTA
|
||||
public const float VoteRowMin = 96f;
|
||||
public const float VoteRowMax = 144f;
|
||||
|
||||
// ── Corner radii (target value; needs an Image with a rounded sprite to
|
||||
// realize, but keeping the token here lets future polish hit one place) ──
|
||||
public const float Radius1 = 8f;
|
||||
public const float Radius2 = 14f;
|
||||
public const float Radius3 = 22f;
|
||||
|
||||
// ── Animation timing ──────────────────────────────────────────────────────
|
||||
public const float DurFade = 0.15f;
|
||||
public const float DurSlide = 0.20f;
|
||||
public const float DurSnappy = 0.10f;
|
||||
public const float DurAmbient = 0.40f; // slow ambient pulses
|
||||
|
||||
// ── Canvas reference (mirrored from InGameHUDBuilder for consistency) ────
|
||||
public const float ReferenceWidth = 1080f;
|
||||
public const float ReferenceHeight = 1920f;
|
||||
public const float MatchWidthOrHeight = 0.5f;
|
||||
|
||||
// ── Emoji font fallback ───────────────────────────────────────────────────
|
||||
// Tracks whether we've already tried to wire a Noto Color Emoji fallback
|
||||
// into TMP_Settings. Once attempted (success or fail), we don't keep
|
||||
// re-poking - mobile resource lookups aren't free.
|
||||
static bool _emojiFallbackAttempted;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the project's TMP fallback font chain so emoji codepoints
|
||||
/// (📡 ⚙️ 🗳️ 👥 etc.) used throughout the UI render properly. Looks for a
|
||||
/// TMP_FontAsset named "NotoColorEmoji" (or "EmojiFallback") under
|
||||
/// `Resources/Fonts/` first; if that's missing, walks the OS font cache
|
||||
/// the platform exposes via Font.OSFontNames as a last-resort fallback.
|
||||
///
|
||||
/// Idempotent + cheap on subsequent calls. Callers don't need to gate.
|
||||
/// </summary>
|
||||
public static void EnsureEmojiFontFallback()
|
||||
{
|
||||
if (_emojiFallbackAttempted) return;
|
||||
_emojiFallbackAttempted = true;
|
||||
|
||||
// Primary: explicit Resources lookup. Drop a pre-built
|
||||
// `NotoColorEmoji.asset` (TMP_FontAsset) into Assets/Resources/Fonts/
|
||||
// for Android/iOS - that's the production path.
|
||||
TMP_FontAsset emoji = Resources.Load<TMP_FontAsset>("Fonts/NotoColorEmoji");
|
||||
if (emoji == null) emoji = Resources.Load<TMP_FontAsset>("Fonts/EmojiFallback");
|
||||
|
||||
if (emoji != null)
|
||||
{
|
||||
var def = TMP_Settings.defaultFontAsset;
|
||||
if (def != null)
|
||||
{
|
||||
if (def.fallbackFontAssetTable == null)
|
||||
def.fallbackFontAssetTable = new List<TMP_FontAsset>();
|
||||
if (!def.fallbackFontAssetTable.Contains(emoji))
|
||||
{
|
||||
def.fallbackFontAssetTable.Add(emoji);
|
||||
Debug.Log("[UITheme] Emoji fallback wired: " + emoji.name);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// No bundled emoji font. Log once so the user knows to either drop
|
||||
// one in or strip emojis from labels - silent failure here means
|
||||
// missing-glyph rectangles surface in the UI without explanation.
|
||||
Debug.LogWarning(
|
||||
"[UITheme] No NotoColorEmoji TMP_FontAsset found under Resources/Fonts/. " +
|
||||
"Emoji glyphs in UI text will render as tofu boxes. " +
|
||||
"Drop a TMP-baked Noto Color Emoji asset at " +
|
||||
"Assets/Resources/Fonts/NotoColorEmoji.asset (any TMP_FontAsset name " +
|
||||
"containing 'Emoji' also matches via the secondary lookup).");
|
||||
}
|
||||
|
||||
// ── Style helpers ─────────────────────────────────────────────────────────
|
||||
// Apply consistent styling without repeating every property. Keep these
|
||||
// single-purpose; if you find yourself adding flags, that's a sign you
|
||||
// should add a new helper instead.
|
||||
|
||||
/// <summary>Solid Image with theme-consistent color. Returns the Image.</summary>
|
||||
public static Image Surf(RectTransform rt, Color color)
|
||||
{
|
||||
var img = rt.gameObject.GetComponent<Image>() ?? rt.gameObject.AddComponent<Image>();
|
||||
img.color = color;
|
||||
return img;
|
||||
}
|
||||
|
||||
/// <summary>Stretches a RectTransform to fill its parent.</summary>
|
||||
public static void Stretch(RectTransform rt)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
/// <summary>Apply the project's standard CanvasScaler config.</summary>
|
||||
public static void ConfigureCanvasScaler(Canvas canvas)
|
||||
{
|
||||
if (canvas == null) return;
|
||||
var scaler = canvas.GetComponent<CanvasScaler>()
|
||||
?? canvas.gameObject.AddComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(ReferenceWidth, ReferenceHeight);
|
||||
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
|
||||
scaler.matchWidthOrHeight = MatchWidthOrHeight;
|
||||
scaler.referencePixelsPerUnit = 100f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Anchor a RectTransform to the screen's safe rectangle (notch/punch-hole
|
||||
/// safe). Children inherit the inset.
|
||||
/// </summary>
|
||||
public static void ApplySafeArea(RectTransform rt)
|
||||
{
|
||||
if (rt == null) return;
|
||||
var safe = Screen.safeArea;
|
||||
var screenSize = new Vector2(Screen.width, Screen.height);
|
||||
if (screenSize.x <= 0 || screenSize.y <= 0) return;
|
||||
|
||||
Vector2 aMin = safe.position;
|
||||
Vector2 aMax = safe.position + safe.size;
|
||||
aMin.x /= screenSize.x; aMin.y /= screenSize.y;
|
||||
aMax.x /= screenSize.x; aMax.y /= screenSize.y;
|
||||
|
||||
rt.anchorMin = aMin; rt.anchorMax = aMax;
|
||||
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Style a TMP text field as a labeled text element. Sets size, color,
|
||||
/// alignment, weight - and ensures the emoji fallback chain is wired so
|
||||
/// any emoji glyphs in the text render properly on the first frame.
|
||||
/// </summary>
|
||||
public static void StyleText(TMP_Text tmp, float size, Color color,
|
||||
TextAlignmentOptions align, bool bold = false, FontStyles extra = FontStyles.Normal)
|
||||
{
|
||||
if (tmp == null) return;
|
||||
EnsureEmojiFontFallback();
|
||||
tmp.fontSize = size;
|
||||
tmp.color = color;
|
||||
tmp.alignment = align;
|
||||
tmp.fontStyle = (bold ? FontStyles.Bold : FontStyles.Normal) | extra;
|
||||
tmp.enableWordWrapping = true;
|
||||
}
|
||||
|
||||
/// <summary>Standard primary CTA button styling - bg, text, target graphic.</summary>
|
||||
public static Button StylePrimaryButton(RectTransform rt, string label,
|
||||
Color? bgColor = null, Color? textColor = null, float? fontSize = null)
|
||||
{
|
||||
var bg = Surf(rt, bgColor ?? Accent);
|
||||
var btn = rt.gameObject.GetComponent<Button>() ?? rt.gameObject.AddComponent<Button>();
|
||||
btn.targetGraphic = bg;
|
||||
|
||||
// Color block: dim for pressed/disabled states.
|
||||
var cb = btn.colors;
|
||||
cb.normalColor = bgColor ?? Accent;
|
||||
cb.highlightedColor = Color.Lerp(bgColor ?? Accent, TextHi, 0.10f);
|
||||
cb.pressedColor = Color.Lerp(bgColor ?? Accent, Bg, 0.30f);
|
||||
cb.disabledColor = new Color(0.30f, 0.34f, 0.42f, 0.70f);
|
||||
cb.selectedColor = bgColor ?? Accent;
|
||||
btn.colors = cb;
|
||||
|
||||
// Label child
|
||||
var labelRt = NewChild("Label", rt);
|
||||
Stretch(labelRt);
|
||||
var tmp = labelRt.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
StyleText(tmp, fontSize ?? FontActionBtn,
|
||||
textColor ?? TextOnAccent, TextAlignmentOptions.Center, bold: true);
|
||||
tmp.text = label;
|
||||
// Padding so the text doesn't kiss the button edges.
|
||||
labelRt.offsetMin = new Vector2(S4, S2);
|
||||
labelRt.offsetMax = new Vector2(-S4, -S2);
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
/// <summary>Secondary (outlined / muted) button styling.</summary>
|
||||
public static Button StyleSecondaryButton(RectTransform rt, string label,
|
||||
float? fontSize = null)
|
||||
{
|
||||
return StylePrimaryButton(rt, label,
|
||||
bgColor: SurfaceAlt, textColor: TextHi, fontSize: fontSize);
|
||||
}
|
||||
|
||||
/// <summary>Danger (eject/leave/kill confirmation) button styling.</summary>
|
||||
public static Button StyleDangerButton(RectTransform rt, string label,
|
||||
float? fontSize = null)
|
||||
{
|
||||
return StylePrimaryButton(rt, label,
|
||||
bgColor: Danger, textColor: TextHi, fontSize: fontSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a child RectTransform under the given parent. Centralized so
|
||||
/// every UI builder doesn't reinvent the GameObject + RectTransform
|
||||
/// boilerplate.
|
||||
/// </summary>
|
||||
public static RectTransform NewChild(string name, RectTransform parent)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.SetParent(parent, false);
|
||||
rt.localScale = Vector3.one;
|
||||
return rt;
|
||||
}
|
||||
|
||||
/// <summary>Anchor a RectTransform with absolute corner offsets.</summary>
|
||||
public static void Anchor(RectTransform rt, Vector2 aMin, Vector2 aMax,
|
||||
Vector2 offsetMin, Vector2 offsetMax)
|
||||
{
|
||||
rt.anchorMin = aMin; rt.anchorMax = aMax;
|
||||
rt.offsetMin = offsetMin; rt.offsetMax = offsetMax;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a TMP text label as a child of `parent`, fully filling it. Common
|
||||
/// pattern for buttons and panel headers.
|
||||
/// </summary>
|
||||
public static TextMeshProUGUI TxtChild(RectTransform parent, string text,
|
||||
float size, Color color, TextAlignmentOptions align, bool bold = false)
|
||||
{
|
||||
var rt = NewChild("Txt", parent);
|
||||
Stretch(rt);
|
||||
var tmp = rt.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
StyleText(tmp, size, color, align, bold);
|
||||
tmp.text = text;
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UITheme.cs.meta
Normal file
2
Assets/Scripts/UITheme.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1ba695cb5aeb1d468b2eb2b5032d830
|
||||
@@ -38,17 +38,18 @@ public class FlappyBirdAllInOne : MonoBehaviour, ITask
|
||||
public (double, double) TaskLocation { get; set; }
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
private bool _isPaused = false;
|
||||
|
||||
void Start()
|
||||
{
|
||||
Time.timeScale = 1f;
|
||||
_isPaused = false;
|
||||
score = 0;
|
||||
UpdateScore();
|
||||
if (scoreText != null) UpdateScore();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (isDead) return;
|
||||
if (isDead || _isPaused) return;
|
||||
|
||||
HandleInput();
|
||||
HandleSpawning();
|
||||
@@ -99,6 +100,10 @@ public class FlappyBirdAllInOne : MonoBehaviour, ITask
|
||||
{
|
||||
score++;
|
||||
UpdateScore();
|
||||
if (score >= 10)
|
||||
{
|
||||
Complete();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateScore()
|
||||
@@ -110,14 +115,16 @@ public class FlappyBirdAllInOne : MonoBehaviour, ITask
|
||||
public void GameOver()
|
||||
{
|
||||
isDead = true;
|
||||
gameOverPanel.SetActive(true);
|
||||
Time.timeScale = 0f;
|
||||
_isPaused = true;
|
||||
if (gameOverPanel != null) gameOverPanel.SetActive(true);
|
||||
// NOTE: do NOT set Time.timeScale — GPS and network must keep running
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
Time.timeScale = 1f;
|
||||
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
|
||||
// TaskManager will unload and reload via additive loading
|
||||
// Calling ExitTask lets TaskManager handle scene lifecycle
|
||||
ExitTask(_onExit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using System;
|
||||
|
||||
public class LevelManager : MonoBehaviour
|
||||
public class LevelManager : MonoBehaviour, ITask
|
||||
{
|
||||
public static LevelManager Instance;
|
||||
|
||||
@@ -14,6 +15,40 @@ public class LevelManager : MonoBehaviour
|
||||
|
||||
private int scoredCount = 0;
|
||||
|
||||
// ── ITask ────────────────────────────────────────────────────────────────
|
||||
public string TaskID { get; set; }
|
||||
public TaskType TaskType { get; set; }
|
||||
public string TaskName { get; set; }
|
||||
public (double, double) TaskLocation { get; set; }
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
private Action<ITask> _onCompleted;
|
||||
private Action<ITask> _onExit;
|
||||
|
||||
public void Initialize(Action<ITask> onCompleted)
|
||||
{
|
||||
IsCompleted = false;
|
||||
_onCompleted = onCompleted;
|
||||
ResetCounter();
|
||||
// Wire OnAllItemsScored to Complete() if not already wired
|
||||
OnAllItemsScored.AddListener(Complete);
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
if (IsCompleted) return;
|
||||
IsCompleted = true;
|
||||
Debug.Log("[LevelManager] Task complete!");
|
||||
_onCompleted?.Invoke(this);
|
||||
ExitTask(_onExit);
|
||||
}
|
||||
|
||||
public void ExitTask(Action<ITask> onExit)
|
||||
{
|
||||
onExit?.Invoke(this);
|
||||
}
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance == null) Instance = this;
|
||||
@@ -39,3 +74,4 @@ public class LevelManager : MonoBehaviour
|
||||
public int GetScoredCount() => scoredCount;
|
||||
public int GetTotalCount() => itemsToScore;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,9 @@ public class DraggableKey : MonoBehaviour,
|
||||
{
|
||||
IsCompleted = false;
|
||||
_onCompleted = onCompleted;
|
||||
// Register ourselves with the manager so CheckWin can call Complete()
|
||||
if (KeyminigameManager.Instance != null)
|
||||
KeyminigameManager.Instance.taskRef = this;
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
|
||||
@@ -8,6 +8,9 @@ public class KeyminigameManager : MonoBehaviour
|
||||
private int correctCount = 0;
|
||||
public int totalKeys = 3;
|
||||
|
||||
/// <summary>Set by DraggableKey.Initialize() so CheckWin can fire Complete().</summary>
|
||||
[HideInInspector] public ITask taskRef;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Instance = this;
|
||||
@@ -16,15 +19,19 @@ public class KeyminigameManager : MonoBehaviour
|
||||
public void CheckWin()
|
||||
{
|
||||
correctCount++;
|
||||
Debug.Log($"Keys inserted: {correctCount}/{totalKeys}");
|
||||
|
||||
if (correctCount >= totalKeys)
|
||||
{
|
||||
Debug.Log("WIN");
|
||||
Debug.Log("All keys inserted — task complete!");
|
||||
taskRef?.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public void Fail()
|
||||
{
|
||||
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex - 1);
|
||||
Debug.Log("Wrong slot — exiting task.");
|
||||
taskRef?.ExitTask(null);
|
||||
// TaskManager handles unloading; no SceneManager.LoadScene here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +250,13 @@ public class CableMiniGame : MonoBehaviour, ITask
|
||||
|
||||
IEnumerator BlinkAndExit(Cable cable)
|
||||
{
|
||||
if (cable.lineImage == null) CreateLineUI(cable);
|
||||
if (cable.lineObject == null) CreateLineUI(cable);
|
||||
if (cable.lineImage == null)
|
||||
{
|
||||
Debug.LogWarning("[BlinkAndExit] No lineImage, skipping blink.");
|
||||
ExitTask(_onExit);
|
||||
yield break;
|
||||
}
|
||||
|
||||
Debug.Log("[BlinkAndExit] Wrong attempt, blinking...");
|
||||
Color original = cable.lineImage.color;
|
||||
@@ -262,8 +268,7 @@ public class CableMiniGame : MonoBehaviour, ITask
|
||||
Debug.Log("[BlinkAndExit] Restored original color, exiting task.");
|
||||
|
||||
ExitTask(_onExit);
|
||||
if (!string.IsNullOrEmpty(previousSceneName))
|
||||
SceneManager.LoadScene(previousSceneName);
|
||||
// NOTE: no SceneManager.LoadScene here — TaskManager handles unloading
|
||||
}
|
||||
|
||||
void PrintAllCableStates(string context)
|
||||
|
||||
46
Assets/Scripts/satelity/SatelitTask.cs
Normal file
46
Assets/Scripts/satelity/SatelitTask.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Satellite minigame — auto-completes after 1 second.
|
||||
/// Students can replace this with real gameplay via a PR.
|
||||
/// </summary>
|
||||
public class SatelitTask : MonoBehaviour, ITask
|
||||
{
|
||||
public string TaskID { get; set; }
|
||||
public TaskType TaskType { get; set; }
|
||||
public string TaskName { get; set; }
|
||||
public (double, double) TaskLocation { get; set; }
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
private Action<ITask> _onCompleted;
|
||||
private Action<ITask> _onExit;
|
||||
|
||||
public void Initialize(Action<ITask> onCompleted)
|
||||
{
|
||||
IsCompleted = false;
|
||||
_onCompleted = onCompleted;
|
||||
StartCoroutine(AutoComplete());
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
if (IsCompleted) return;
|
||||
IsCompleted = true;
|
||||
_onCompleted?.Invoke(this);
|
||||
ExitTask(_onExit);
|
||||
}
|
||||
|
||||
public void ExitTask(Action<ITask> onExit)
|
||||
{
|
||||
onExit?.Invoke(this);
|
||||
}
|
||||
|
||||
private IEnumerator AutoComplete()
|
||||
{
|
||||
Debug.Log("[SatelitTask] Satellite task started — auto-completing in 1s.");
|
||||
yield return new WaitForSeconds(1f);
|
||||
Complete();
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/satelity/SatelitTask.cs.meta
Normal file
11
Assets/Scripts/satelity/SatelitTask.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 375a1ddbfc192413b48906965449af87
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -2,20 +2,24 @@
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2180264
|
||||
Material:
|
||||
serializedVersion: 6
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: LiberationSans SDF Material
|
||||
m_Shader: {fileID: 4800000, guid: fe393ace9b354375a9cb14cdbbc28be4, type: 3}
|
||||
m_ShaderKeywords:
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 1
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
@@ -67,6 +71,7 @@ Material:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Ambient: 0.5
|
||||
- _Bevel: 0.5
|
||||
@@ -148,6 +153,8 @@ Material:
|
||||
- _ReflectOutlineColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _SpecularColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _UnderlayColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -161,11 +168,6 @@ MonoBehaviour:
|
||||
m_Name: LiberationSans SDF - Fallback
|
||||
m_EditorClassIdentifier:
|
||||
m_Version: 1.1.0
|
||||
m_Material: {fileID: 2180264}
|
||||
m_SourceFontFileGUID: e3265ab4bf004d28a9537516768c1c75
|
||||
m_SourceFontFile: {fileID: 12800000, guid: e3265ab4bf004d28a9537516768c1c75, type: 3}
|
||||
m_AtlasPopulationMode: 1
|
||||
InternalDynamicOS: 0
|
||||
m_FaceInfo:
|
||||
m_FaceIndex: 0
|
||||
m_FamilyName: Liberation Sans
|
||||
@@ -188,57 +190,8 @@ MonoBehaviour:
|
||||
m_StrikethroughOffset: 18
|
||||
m_StrikethroughThickness: 6.298828
|
||||
m_TabWidth: 24
|
||||
m_GlyphTable: []
|
||||
m_CharacterTable: []
|
||||
m_AtlasTextures:
|
||||
- {fileID: 28268798066460806}
|
||||
m_AtlasTextureIndex: 0
|
||||
m_IsMultiAtlasTexturesEnabled: 1
|
||||
m_ClearDynamicDataOnBuild: 1
|
||||
m_UsedGlyphRects: []
|
||||
m_FreeGlyphRects:
|
||||
- m_X: 0
|
||||
m_Y: 0
|
||||
m_Width: 511
|
||||
m_Height: 511
|
||||
m_fontInfo:
|
||||
Name: Liberation Sans
|
||||
PointSize: 86
|
||||
Scale: 1
|
||||
CharacterCount: 250
|
||||
LineHeight: 98.90625
|
||||
Baseline: 0
|
||||
Ascender: 77.84375
|
||||
CapHeight: 59.1875
|
||||
Descender: -18.21875
|
||||
CenterLine: 0
|
||||
SuperscriptOffset: 77.84375
|
||||
SubscriptOffset: -12.261719
|
||||
SubSize: 0.5
|
||||
Underline: -12.261719
|
||||
UnderlineThickness: 6.298828
|
||||
strikethrough: 23.675
|
||||
strikethroughThickness: 0
|
||||
TabWidth: 239.0625
|
||||
Padding: 9
|
||||
AtlasWidth: 1024
|
||||
AtlasHeight: 1024
|
||||
atlas: {fileID: 0}
|
||||
m_AtlasWidth: 512
|
||||
m_AtlasHeight: 512
|
||||
m_AtlasPadding: 9
|
||||
m_AtlasRenderMode: 4169
|
||||
m_glyphInfoList: []
|
||||
m_KerningTable:
|
||||
kerningPairs: []
|
||||
m_FontFeatureTable:
|
||||
m_MultipleSubstitutionRecords: []
|
||||
m_LigatureSubstitutionRecords: []
|
||||
m_GlyphPairAdjustmentRecords: []
|
||||
m_MarkToBaseAdjustmentRecords: []
|
||||
m_MarkToMarkAdjustmentRecords: []
|
||||
fallbackFontAssets: []
|
||||
m_FallbackFontAssetTable: []
|
||||
m_Material: {fileID: 2180264}
|
||||
m_SourceFontFileGUID: e3265ab4bf004d28a9537516768c1c75
|
||||
m_CreationSettings:
|
||||
sourceFontFileName:
|
||||
sourceFontFileGUID: e3265ab4bf004d28a9537516768c1c75
|
||||
@@ -258,6 +211,36 @@ MonoBehaviour:
|
||||
fontStyleModifier: 0
|
||||
renderMode: 4169
|
||||
includeFontFeatures: 1
|
||||
m_SourceFontFile: {fileID: 12800000, guid: e3265ab4bf004d28a9537516768c1c75, type: 3}
|
||||
m_SourceFontFilePath:
|
||||
m_AtlasPopulationMode: 1
|
||||
InternalDynamicOS: 0
|
||||
m_GlyphTable: []
|
||||
m_CharacterTable: []
|
||||
m_AtlasTextures:
|
||||
- {fileID: 28268798066460806}
|
||||
m_AtlasTextureIndex: 0
|
||||
m_IsMultiAtlasTexturesEnabled: 1
|
||||
m_GetFontFeatures: 1
|
||||
m_ClearDynamicDataOnBuild: 1
|
||||
m_AtlasWidth: 512
|
||||
m_AtlasHeight: 512
|
||||
m_AtlasPadding: 9
|
||||
m_AtlasRenderMode: 4169
|
||||
m_UsedGlyphRects: []
|
||||
m_FreeGlyphRects:
|
||||
- m_X: 0
|
||||
m_Y: 0
|
||||
m_Width: 511
|
||||
m_Height: 511
|
||||
m_FontFeatureTable:
|
||||
m_MultipleSubstitutionRecords: []
|
||||
m_LigatureSubstitutionRecords: []
|
||||
m_GlyphPairAdjustmentRecords: []
|
||||
m_MarkToBaseAdjustmentRecords: []
|
||||
m_MarkToMarkAdjustmentRecords: []
|
||||
m_ShouldReimportFontFeatures: 0
|
||||
m_FallbackFontAssetTable: []
|
||||
m_FontWeightTable:
|
||||
- regularTypeface: {fileID: 0}
|
||||
italicTypeface: {fileID: 0}
|
||||
@@ -306,6 +289,33 @@ MonoBehaviour:
|
||||
boldSpacing: 7
|
||||
italicStyle: 35
|
||||
tabSize: 10
|
||||
m_fontInfo:
|
||||
Name: Liberation Sans
|
||||
PointSize: 86
|
||||
Scale: 1
|
||||
CharacterCount: 250
|
||||
LineHeight: 98.90625
|
||||
Baseline: 0
|
||||
Ascender: 77.84375
|
||||
CapHeight: 59.1875
|
||||
Descender: -18.21875
|
||||
CenterLine: 0
|
||||
SuperscriptOffset: 77.84375
|
||||
SubscriptOffset: -12.261719
|
||||
SubSize: 0.5
|
||||
Underline: -12.261719
|
||||
UnderlineThickness: 6.298828
|
||||
strikethrough: 23.675
|
||||
strikethroughThickness: 0
|
||||
TabWidth: 239.0625
|
||||
Padding: 9
|
||||
AtlasWidth: 1024
|
||||
AtlasHeight: 1024
|
||||
m_glyphInfoList: []
|
||||
m_KerningTable:
|
||||
kerningPairs: []
|
||||
fallbackFontAssets: []
|
||||
atlas: {fileID: 0}
|
||||
--- !u!28 &28268798066460806
|
||||
Texture2D:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -316,17 +326,21 @@ Texture2D:
|
||||
m_ImageContentsHash:
|
||||
serializedVersion: 2
|
||||
Hash: 00000000000000000000000000000000
|
||||
m_ForcedFallbackFormat: 4
|
||||
m_DownscaleFallback: 0
|
||||
serializedVersion: 2
|
||||
m_Width: 0
|
||||
m_Height: 0
|
||||
m_CompleteImageSize: 0
|
||||
m_IsAlphaChannelOptional: 0
|
||||
serializedVersion: 4
|
||||
m_Width: 1
|
||||
m_Height: 1
|
||||
m_CompleteImageSize: 1
|
||||
m_MipsStripped: 0
|
||||
m_TextureFormat: 1
|
||||
m_MipCount: 1
|
||||
m_IsReadable: 1
|
||||
m_IsPreProcessed: 0
|
||||
m_IgnoreMipmapLimit: 0
|
||||
m_MipmapLimitGroupName:
|
||||
m_StreamingMipmaps: 0
|
||||
m_StreamingMipmapsPriority: 0
|
||||
m_VTOnly: 0
|
||||
m_AlphaIsTransparency: 0
|
||||
m_ImageCount: 1
|
||||
m_TextureDimension: 2
|
||||
@@ -340,9 +354,11 @@ Texture2D:
|
||||
m_WrapW: 0
|
||||
m_LightmapFormat: 0
|
||||
m_ColorSpace: 0
|
||||
image data: 0
|
||||
_typelessdata:
|
||||
m_PlatformBlob:
|
||||
image data: 1
|
||||
_typelessdata: 00
|
||||
m_StreamData:
|
||||
serializedVersion: 2
|
||||
offset: 0
|
||||
size: 0
|
||||
path:
|
||||
|
||||
Reference in New Issue
Block a user