TeamPerceptionVisualizer.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. /// <summary>
  4. /// Visualization component that displays a semi-transparent purple circle around the team marker
  5. /// to represent the team's collective perception range. The radius is based on the highest
  6. /// perception value among team members.
  7. /// </summary>
  8. public class TeamPerceptionVisualizer : MonoBehaviour
  9. {
  10. [Header("Perception Circle Settings")]
  11. [SerializeField] private Material perceptionCircleMaterial;
  12. [SerializeField] private Color perceptionColor = new Color(0.5f, 0f, 1f, 0.5f); // 50% transparent purple
  13. [SerializeField] private float perceptionMultiplier = 1.0f; // Converts perception value to world units
  14. [SerializeField] private int circleSegments = 64; // Circle resolution
  15. [SerializeField] private float heightOffset = 0.1f; // How high above ground to place the circle
  16. [Header("Dynamic Updates")]
  17. [SerializeField] private bool updatePerceptionInRealTime = true;
  18. [SerializeField] private float updateInterval = 1f; // How often to check for perception changes (in seconds)
  19. [Header("Debug")]
  20. [SerializeField] private bool showDebugInfo = true;
  21. // Private variables
  22. private GameObject perceptionCircleObject;
  23. private LineRenderer circleRenderer;
  24. private SimpleTeamPlacement teamPlacement;
  25. private GameStateManager gameStateManager;
  26. private MainTeamSelectScript teamSelectScript;
  27. private int lastKnownMaxPerception = -1;
  28. private float lastUpdateTime = 0f;
  29. public static TeamPerceptionVisualizer Instance { get; private set; }
  30. void Awake()
  31. {
  32. if (Instance == null)
  33. {
  34. Instance = this;
  35. }
  36. else
  37. {
  38. Debug.LogWarning("Multiple TeamPerceptionVisualizer instances found. Destroying this one.");
  39. Destroy(gameObject);
  40. return;
  41. }
  42. }
  43. void Start()
  44. {
  45. // Find required components
  46. teamPlacement = FindFirstObjectByType<SimpleTeamPlacement>();
  47. gameStateManager = FindFirstObjectByType<GameStateManager>();
  48. teamSelectScript = FindFirstObjectByType<MainTeamSelectScript>();
  49. if (teamPlacement == null)
  50. {
  51. Debug.LogError("TeamPerceptionVisualizer: SimpleTeamPlacement component not found!");
  52. return;
  53. }
  54. // Create the perception circle
  55. CreatePerceptionCircle();
  56. // Initial position update with slight delay to ensure team marker is loaded
  57. StartCoroutine(DelayedInitialUpdate());
  58. }
  59. /// <summary>
  60. /// Performs initial updates with a small delay to ensure all components are loaded
  61. /// </summary>
  62. private System.Collections.IEnumerator DelayedInitialUpdate()
  63. {
  64. yield return new WaitForEndOfFrame();
  65. yield return new WaitForSeconds(0.1f); // Small delay
  66. UpdateCirclePosition();
  67. UpdatePerceptionVisualization();
  68. }
  69. void Update()
  70. {
  71. // Always update position to follow team marker
  72. UpdateCirclePosition();
  73. if (!updatePerceptionInRealTime || Time.time - lastUpdateTime < updateInterval)
  74. return;
  75. UpdatePerceptionVisualization();
  76. lastUpdateTime = Time.time;
  77. }
  78. /// <summary>
  79. /// Creates the visual circle GameObject and LineRenderer component
  80. /// </summary>
  81. private void CreatePerceptionCircle()
  82. {
  83. // Create the circle GameObject
  84. perceptionCircleObject = new GameObject("PerceptionCircle");
  85. perceptionCircleObject.transform.SetParent(this.transform);
  86. // Add and configure LineRenderer
  87. circleRenderer = perceptionCircleObject.AddComponent<LineRenderer>();
  88. circleRenderer.useWorldSpace = false;
  89. circleRenderer.loop = true;
  90. circleRenderer.startWidth = 0.3f;
  91. circleRenderer.endWidth = 0.3f;
  92. circleRenderer.positionCount = circleSegments;
  93. circleRenderer.startColor = perceptionColor;
  94. circleRenderer.endColor = perceptionColor;
  95. // Set material if provided, otherwise create a simple one
  96. if (perceptionCircleMaterial != null)
  97. {
  98. circleRenderer.material = perceptionCircleMaterial;
  99. }
  100. else
  101. {
  102. // Create a simple material with transparency support
  103. Material simpleMaterial = new Material(Shader.Find("Sprites/Default"));
  104. simpleMaterial.color = perceptionColor;
  105. circleRenderer.material = simpleMaterial;
  106. }
  107. // Initially hide the circle until we have valid data
  108. perceptionCircleObject.SetActive(false);
  109. }
  110. /// <summary>
  111. /// Updates the circle position to follow the team marker
  112. /// </summary>
  113. private void UpdateCirclePosition()
  114. {
  115. if (perceptionCircleObject == null || teamPlacement == null)
  116. return;
  117. Vector3 teamPosition = GetTeamMarkerPosition();
  118. if (teamPosition != Vector3.zero)
  119. {
  120. Vector3 newPosition = new Vector3(
  121. teamPosition.x,
  122. teamPosition.y + heightOffset,
  123. teamPosition.z
  124. );
  125. // Only move if the position actually changed (to avoid spam)
  126. if (Vector3.Distance(perceptionCircleObject.transform.position, newPosition) > 0.1f)
  127. {
  128. perceptionCircleObject.transform.position = newPosition;
  129. if (showDebugInfo)
  130. {
  131. Debug.Log($"TeamPerceptionVisualizer: Updated circle position to {newPosition}");
  132. }
  133. }
  134. }
  135. else if (showDebugInfo)
  136. {
  137. Debug.LogWarning("TeamPerceptionVisualizer: Could not find team marker position");
  138. }
  139. }
  140. /// <summary>
  141. /// Updates the perception visualization based on current team data
  142. /// </summary>
  143. public void UpdatePerceptionVisualization()
  144. {
  145. if (circleRenderer == null)
  146. return;
  147. int maxPerception = GetHighestTeamPerception();
  148. // If no valid perception data, hide the circle
  149. if (maxPerception <= 0)
  150. {
  151. if (perceptionCircleObject.activeSelf)
  152. {
  153. perceptionCircleObject.SetActive(false);
  154. if (showDebugInfo)
  155. Debug.Log("TeamPerceptionVisualizer: Hiding perception circle - no valid team data");
  156. }
  157. return;
  158. }
  159. // If perception hasn't changed, no need to update
  160. if (maxPerception == lastKnownMaxPerception && perceptionCircleObject.activeSelf)
  161. return;
  162. lastKnownMaxPerception = maxPerception;
  163. // Calculate radius based on perception
  164. float radius = maxPerception * perceptionMultiplier;
  165. // Generate circle points
  166. Vector3[] points = new Vector3[circleSegments];
  167. for (int i = 0; i < circleSegments; i++)
  168. {
  169. float angle = (float)i / circleSegments * 2f * Mathf.PI;
  170. points[i] = new Vector3(
  171. Mathf.Cos(angle) * radius,
  172. 0f,
  173. Mathf.Sin(angle) * radius
  174. );
  175. }
  176. // Apply points to LineRenderer
  177. circleRenderer.positionCount = circleSegments;
  178. circleRenderer.SetPositions(points);
  179. // Show the circle
  180. if (!perceptionCircleObject.activeSelf)
  181. {
  182. perceptionCircleObject.SetActive(true);
  183. }
  184. if (showDebugInfo)
  185. {
  186. Debug.Log($"TeamPerceptionVisualizer: Updated perception circle - Max Perception: {maxPerception}, Radius: {radius:F1}");
  187. }
  188. }
  189. /// <summary>
  190. /// Gets the highest perception value among all team members
  191. /// </summary>
  192. /// <returns>The highest perception value, or 0 if no team data available</returns>
  193. private int GetHighestTeamPerception()
  194. {
  195. int maxPerception = 0;
  196. List<TeamCharacter> teamMembers = GetCurrentTeamMembers();
  197. if (teamMembers == null || teamMembers.Count == 0)
  198. return 0;
  199. foreach (TeamCharacter character in teamMembers)
  200. {
  201. if (character != null)
  202. {
  203. int characterPerception = character.FinalPerception; // Use final perception (includes equipment bonuses)
  204. if (characterPerception > maxPerception)
  205. {
  206. maxPerception = characterPerception;
  207. }
  208. }
  209. }
  210. return maxPerception;
  211. }
  212. /// <summary>
  213. /// Gets the current team members from available sources
  214. /// </summary>
  215. /// <returns>List of team members or null if not available</returns>
  216. private List<TeamCharacter> GetCurrentTeamMembers()
  217. {
  218. // Try to get team from MainTeamSelectScript first (if in current scene)
  219. if (teamSelectScript != null)
  220. {
  221. var configuredCharacters = teamSelectScript.GetConfiguredCharacters();
  222. if (configuredCharacters != null && configuredCharacters.Count > 0)
  223. {
  224. if (showDebugInfo)
  225. Debug.Log($"TeamPerceptionVisualizer: Found {configuredCharacters.Count} team members from MainTeamSelectScript");
  226. return configuredCharacters;
  227. }
  228. }
  229. // Fall back to GameStateManager saved data
  230. if (gameStateManager != null && gameStateManager.savedTeam != null)
  231. {
  232. var savedTeamList = new List<TeamCharacter>();
  233. foreach (var character in gameStateManager.savedTeam)
  234. {
  235. if (character != null)
  236. savedTeamList.Add(character);
  237. }
  238. if (savedTeamList.Count > 0)
  239. {
  240. if (showDebugInfo)
  241. Debug.Log($"TeamPerceptionVisualizer: Found {savedTeamList.Count} team members from GameStateManager");
  242. return savedTeamList;
  243. }
  244. }
  245. // Final fallback: Load directly from PlayerPrefs (same as TeamOverviewController)
  246. var playerPrefTeam = new List<TeamCharacter>();
  247. for (int i = 0; i < 4; i++)
  248. {
  249. string prefix = $"Character{i}_";
  250. if (PlayerPrefs.HasKey(prefix + "Exists") && PlayerPrefs.GetInt(prefix + "Exists") == 1)
  251. {
  252. var character = new TeamCharacter();
  253. character.name = PlayerPrefs.GetString(prefix + "Name", "");
  254. character.isMale = PlayerPrefs.GetInt(prefix + "IsMale", 1) == 1;
  255. character.strength = PlayerPrefs.GetInt(prefix + "Strength", 10);
  256. character.dexterity = PlayerPrefs.GetInt(prefix + "Dexterity", 10);
  257. character.constitution = PlayerPrefs.GetInt(prefix + "Constitution", 10);
  258. character.wisdom = PlayerPrefs.GetInt(prefix + "Wisdom", 10);
  259. character.perception = PlayerPrefs.GetInt(prefix + "Perception", 10);
  260. character.gold = PlayerPrefs.GetInt(prefix + "Gold", 25);
  261. playerPrefTeam.Add(character);
  262. }
  263. }
  264. if (playerPrefTeam.Count > 0)
  265. {
  266. if (showDebugInfo)
  267. Debug.Log($"TeamPerceptionVisualizer: Found {playerPrefTeam.Count} team members from PlayerPrefs");
  268. return playerPrefTeam;
  269. }
  270. if (showDebugInfo)
  271. Debug.Log("TeamPerceptionVisualizer: No team members found in any source");
  272. return null;
  273. }
  274. /// <summary>
  275. /// Gets the world position of the team marker
  276. /// </summary>
  277. /// <returns>World position of the team marker, or Vector3.zero if not found</returns>
  278. private Vector3 GetTeamMarkerPosition()
  279. {
  280. if (teamPlacement != null && teamPlacement.TeamMarkerInstance != null)
  281. {
  282. return teamPlacement.TeamMarkerWorldPosition;
  283. }
  284. // Fallback: try to find the marker by name or tag
  285. var teamMarker = GameObject.FindGameObjectWithTag("Player"); // The marker uses "Player" tag
  286. if (teamMarker != null && teamMarker.name == "TeamMarker")
  287. {
  288. return teamMarker.transform.position;
  289. }
  290. // Alternative: look for any GameObject with "TeamMarker" in the name
  291. var foundMarker = GameObject.Find("TeamMarker");
  292. if (foundMarker != null)
  293. {
  294. return foundMarker.transform.position;
  295. }
  296. // If we can't find the marker, return zero
  297. return Vector3.zero;
  298. }
  299. /// <summary>
  300. /// Call this method when team composition or equipment changes
  301. /// </summary>
  302. public void ForceUpdatePerception()
  303. {
  304. lastKnownMaxPerception = -1; // Force update on next check
  305. UpdatePerceptionVisualization();
  306. }
  307. /// <summary>
  308. /// Enables or disables the perception visualization
  309. /// </summary>
  310. /// <param name="enabled">Whether to show the perception circle</param>
  311. public void SetPerceptionVisualizationEnabled(bool enabled)
  312. {
  313. if (perceptionCircleObject != null)
  314. {
  315. perceptionCircleObject.SetActive(enabled && GetHighestTeamPerception() > 0);
  316. }
  317. }
  318. /// <summary>
  319. /// Updates the circle color
  320. /// </summary>
  321. /// <param name="newColor">New color for the perception circle</param>
  322. public void SetPerceptionColor(Color newColor)
  323. {
  324. perceptionColor = newColor;
  325. if (circleRenderer != null)
  326. {
  327. circleRenderer.startColor = newColor;
  328. circleRenderer.endColor = newColor;
  329. if (circleRenderer.material != null)
  330. {
  331. circleRenderer.material.color = newColor;
  332. }
  333. }
  334. }
  335. /// <summary>
  336. /// Updates the perception multiplier (how large the circle is relative to perception value)
  337. /// </summary>
  338. /// <param name="multiplier">New multiplier value</param>
  339. public void SetPerceptionMultiplier(float multiplier)
  340. {
  341. perceptionMultiplier = multiplier;
  342. ForceUpdatePerception(); // Update circle with new size
  343. }
  344. void OnDestroy()
  345. {
  346. if (Instance == this)
  347. {
  348. Instance = null;
  349. }
  350. }
  351. /// <summary>
  352. /// Debug method to show current perception info
  353. /// </summary>
  354. [ContextMenu("Show Perception Debug Info")]
  355. public void ShowPerceptionDebugInfo()
  356. {
  357. var teamMembers = GetCurrentTeamMembers();
  358. if (teamMembers == null || teamMembers.Count == 0)
  359. {
  360. Debug.Log("No team members found");
  361. return;
  362. }
  363. Debug.Log("=== Team Perception Debug Info ===");
  364. Debug.Log($"Team Members Count: {teamMembers.Count}");
  365. int maxPerception = 0;
  366. string bestCharacter = "";
  367. foreach (var character in teamMembers)
  368. {
  369. if (character != null)
  370. {
  371. int perception = character.FinalPerception;
  372. Debug.Log($"- {character.name}: Perception {perception} (Base: {character.perception}, Modifier: {character.perceptionModifier})");
  373. if (perception > maxPerception)
  374. {
  375. maxPerception = perception;
  376. bestCharacter = character.name;
  377. }
  378. }
  379. }
  380. Debug.Log($"Highest Perception: {maxPerception} ({bestCharacter})");
  381. Debug.Log($"Circle Radius: {maxPerception * perceptionMultiplier:F1} units");
  382. Debug.Log("=== End Debug Info ===");
  383. }
  384. /// <summary>
  385. /// Test method to force immediate visualization update
  386. /// </summary>
  387. [ContextMenu("Force Update Visualization Now")]
  388. public void ForceUpdateVisualizationNow()
  389. {
  390. Debug.Log("=== Forcing Perception Visualization Update ===");
  391. ForceUpdatePerception();
  392. var teamMembers = GetCurrentTeamMembers();
  393. if (teamMembers != null && teamMembers.Count > 0)
  394. {
  395. int maxPerception = GetHighestTeamPerception();
  396. Debug.Log($"Found {teamMembers.Count} team members, highest perception: {maxPerception}");
  397. if (perceptionCircleObject != null)
  398. {
  399. Debug.Log($"Circle active: {perceptionCircleObject.activeSelf}");
  400. Debug.Log($"Circle position: {perceptionCircleObject.transform.position}");
  401. }
  402. else
  403. {
  404. Debug.LogWarning("Perception circle object is null!");
  405. }
  406. }
  407. else
  408. {
  409. Debug.LogWarning("No team members found for visualization!");
  410. }
  411. Debug.Log("=== End Force Update ===");
  412. }
  413. }