Character.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. using TMPro;
  2. using UnityEngine;
  3. public enum ActionDecisionState
  4. {
  5. NoAction,
  6. ActionSelected,
  7. ActionExecuted,
  8. EnemyAction
  9. }
  10. [System.Serializable]
  11. public class CharacterActionData
  12. {
  13. public ActionDecisionState state = ActionDecisionState.NoAction;
  14. public Vector3 targetPosition;
  15. public GameObject targetEnemy;
  16. public bool hasTarget => targetEnemy != null || targetPosition != Vector3.zero;
  17. public void Reset()
  18. {
  19. state = ActionDecisionState.NoAction;
  20. targetPosition = Vector3.zero;
  21. targetEnemy = null;
  22. }
  23. public void SetMoveTarget(Vector3 position)
  24. {
  25. targetPosition = position;
  26. targetEnemy = null;
  27. state = ActionDecisionState.ActionSelected;
  28. }
  29. public void SetAttackTarget(GameObject enemy)
  30. {
  31. targetEnemy = enemy;
  32. targetPosition = Vector3.zero;
  33. state = ActionDecisionState.ActionSelected;
  34. }
  35. }
  36. public abstract class Character : MonoBehaviour
  37. {
  38. private string characterName;
  39. private int maxHealth;
  40. private int currentHealth;
  41. private int attack;
  42. private int constitution;
  43. private int dexterity;
  44. private int wisdom;
  45. private Sprite characterSprite;
  46. private int level;
  47. private int experiencePoints;
  48. private int experienceToLevelUp;
  49. private int initModifier;
  50. private int damageModifier;
  51. private int spellModifier;
  52. private int movementSpeed;
  53. private int armorClass;
  54. private bool isDead = false;
  55. private bool isTargetable = true;
  56. private Bank bank;
  57. private Inventory inventory;
  58. private TextMeshProUGUI nameText;
  59. // Health bar components
  60. private UnityEngine.UI.Slider healthSlider;
  61. private Canvas healthBarCanvas;
  62. // Add action system components
  63. [Header("Action System")]
  64. public CharacterActionData actionData = new CharacterActionData();
  65. [Header("Visual Feedback Settings")]
  66. public Color noActionColor = Color.yellow;
  67. public Color actionSelectedColor = Color.green;
  68. public Color normalColor = Color.white;
  69. public float outlineWidth = 0.1f;
  70. // Visual feedback components
  71. private Renderer characterRenderer;
  72. private Material characterMaterial;
  73. private Color originalColor;
  74. private GameObject outlineObject;
  75. public string CharacterName
  76. {
  77. get => characterName; set
  78. {
  79. characterName = value;
  80. UpdateNameDisplay();
  81. }
  82. }
  83. public int MaxHealth { get => maxHealth; set => maxHealth = value; }
  84. public int CurrentHealth { get => currentHealth; set => currentHealth = value; }
  85. public int Attack { get => attack; set => attack = value; }
  86. public int Constitution { get => constitution; set => constitution = value; }
  87. public int Dexterity { get => dexterity; set => dexterity = value; }
  88. public int Wisdom { get => wisdom; set => wisdom = value; }
  89. public Sprite CharacterSprite { get => characterSprite; set => characterSprite = value; }
  90. public int Level { get => level; set => level = value; }
  91. public int ExperiencePoints { get => experiencePoints; set => experiencePoints = value; }
  92. public int ExperienceToLevelUp { get => experienceToLevelUp; set => experienceToLevelUp = value; }
  93. public int InitModifier { get => initModifier; set => initModifier = value; }
  94. public int DamageModifier { get => damageModifier; set => damageModifier = value; }
  95. public int SpellModifier { get => spellModifier; set => spellModifier = value; }
  96. public int MovementSpeed { get => movementSpeed; set => movementSpeed = value; }
  97. public int ArmorClass { get => armorClass; set => armorClass = value; }
  98. public bool IsDead { get => isDead; }
  99. public bool IsTargetable { get => isTargetable; }
  100. protected Weapon spawnedWeapon;
  101. public Weapon Weapon { get => spawnedWeapon; set => spawnedWeapon = value; }
  102. public abstract Character Spawn(int count);
  103. public abstract Weapon GetWeapon();
  104. public Character()
  105. {
  106. }
  107. void Start()
  108. {
  109. InitializeStats();
  110. // SpawnAndEquipWeapon();
  111. InitializeActionSystem();
  112. Debug.Log($"Character Start() completed for {CharacterName}");
  113. healthSlider = GetComponentInChildren<UnityEngine.UI.Slider>();
  114. }
  115. protected virtual void InitializeStats()
  116. {
  117. // Override in subclasses to set character stats
  118. }
  119. public Weapon GetEquippedWeapon()
  120. {
  121. return spawnedWeapon;
  122. }
  123. public virtual void SpawnAndEquipWeapon()
  124. {
  125. Debug.Log($"SpawnAndEquipWeapon called for {CharacterName}");
  126. Weapon weaponPrefab = GetWeapon();
  127. Debug.Log($"GetWeapon returned: {(weaponPrefab != null ? weaponPrefab.name : "null")}");
  128. if (weaponPrefab != null)
  129. {
  130. GameObject weaponInstance = Instantiate(weaponPrefab.gameObject);
  131. weaponInstance.transform.SetParent(this.transform, false);
  132. Debug.Log($"Instantiated weapon: {weaponInstance.name}");
  133. spawnedWeapon = weaponInstance.GetComponent<Weapon>();
  134. Debug.Log($"spawnedWeapon component: {(spawnedWeapon != null ? spawnedWeapon.GetType().Name : "null")}");
  135. if (spawnedWeapon != null)
  136. {
  137. // Set the wielder reference directly to avoid timing issues
  138. spawnedWeapon.SetWielder(this);
  139. Debug.Log($"{CharacterName} equipped {spawnedWeapon.weaponName}");
  140. // Position the weapon appropriately (you may want to customize this)
  141. PositionWeapon(weaponInstance);
  142. }
  143. else
  144. {
  145. Debug.LogError($"Weapon prefab for {CharacterName} doesn't have a Weapon component!");
  146. Destroy(weaponInstance);
  147. }
  148. }
  149. else
  150. {
  151. Debug.Log($"{CharacterName} no weapon prefab, trying direct creation...");
  152. CreateDirectWeapon();
  153. }
  154. }
  155. protected virtual Weapon CreateDirectWeapon()
  156. {
  157. Debug.LogWarning($"{CharacterName} - CreateDirectWeapon not implemented in subclass!");
  158. return null;
  159. }
  160. // Virtual method to position the weapon - can be overridden by specific character types
  161. protected virtual void PositionWeapon(GameObject weaponInstance)
  162. {
  163. // Default positioning - you can customize this based on your character setup
  164. weaponInstance.transform.localPosition = Vector3.zero;
  165. weaponInstance.transform.localRotation = Quaternion.identity;
  166. weaponInstance.transform.localScale = Vector3.one; // Ensure proper scale
  167. // Optional: Look for specific attachment points
  168. Transform weaponAttachPoint = transform.Find("WeaponAttachPoint");
  169. if (weaponAttachPoint != null)
  170. {
  171. weaponInstance.transform.SetParent(weaponAttachPoint, false);
  172. weaponInstance.transform.localPosition = Vector3.zero;
  173. weaponInstance.transform.localRotation = Quaternion.identity;
  174. Debug.Log($"Weapon attached to {weaponAttachPoint.name}");
  175. }
  176. else
  177. {
  178. // If no specific attach point, just position relative to character
  179. weaponInstance.transform.localPosition = new Vector3(0.5f, 0, 0); // Slightly to the right
  180. Debug.Log($"Weapon positioned at default location relative to {CharacterName}");
  181. }
  182. }
  183. void Awake()
  184. {
  185. nameText = GetComponentInChildren<TextMeshProUGUI>();
  186. if (nameText == null)
  187. {
  188. Debug.LogWarning($"No TextMeshProUGUI component found in children of {gameObject.name}");
  189. }
  190. // Initialize visual feedback system
  191. InitializeVisualFeedback();
  192. UpdateNameDisplay();
  193. }
  194. // Initialize visual feedback components
  195. private void InitializeVisualFeedback()
  196. {
  197. characterRenderer = GetComponent<Renderer>();
  198. if (characterRenderer != null)
  199. {
  200. // Create a unique material instance to avoid modifying shared materials
  201. characterMaterial = new Material(characterRenderer.material);
  202. characterRenderer.material = characterMaterial;
  203. originalColor = characterMaterial.color;
  204. }
  205. else
  206. {
  207. Debug.LogWarning($"No Renderer found on {gameObject.name}. Visual feedback will not work.");
  208. }
  209. // Ensure we have a collider for mouse detection
  210. Collider col = GetComponent<Collider>();
  211. if (col == null)
  212. {
  213. col = gameObject.AddComponent<CapsuleCollider>();
  214. Debug.Log($"Added CapsuleCollider to {gameObject.name} for mouse detection");
  215. }
  216. }
  217. private void UpdateNameDisplay()
  218. {
  219. if (nameText != null && !string.IsNullOrEmpty(characterName))
  220. {
  221. nameText.text = characterName;
  222. }
  223. }
  224. public void RefreshNameDisplay()
  225. {
  226. UpdateNameDisplay();
  227. }
  228. public virtual float GetAttackRange()
  229. {
  230. return spawnedWeapon != null ? spawnedWeapon.Range : 1.0f; // Default range if no weapon is equipped
  231. }
  232. private void InitializeActionSystem()
  233. {
  234. actionData.Reset();
  235. SetVisualState(ActionDecisionState.NoAction);
  236. }
  237. public void SetVisualState(ActionDecisionState state)
  238. {
  239. if (characterRenderer == null) return;
  240. switch (state)
  241. {
  242. case ActionDecisionState.NoAction:
  243. ShowNoActionState();
  244. break;
  245. case ActionDecisionState.ActionSelected:
  246. ShowActionSelectedState();
  247. break;
  248. default:
  249. ShowNormalState();
  250. break;
  251. }
  252. }
  253. private void ShowNoActionState()
  254. {
  255. if (characterMaterial != null)
  256. characterMaterial.color = noActionColor;
  257. CreateOutline();
  258. }
  259. private void ShowActionSelectedState()
  260. {
  261. if (characterMaterial != null)
  262. characterMaterial.color = actionSelectedColor;
  263. RemoveOutline();
  264. }
  265. private void ShowNormalState()
  266. {
  267. if (characterMaterial != null)
  268. characterMaterial.color = originalColor;
  269. RemoveOutline();
  270. }
  271. private void CreateOutline()
  272. {
  273. if (outlineObject != null) return;
  274. outlineObject = new GameObject("Outline");
  275. outlineObject.transform.SetParent(transform);
  276. outlineObject.transform.localPosition = Vector3.zero;
  277. outlineObject.transform.localScale = Vector3.one * (1f + outlineWidth);
  278. // For 3D characters, we'll create a simple colored outline using a MeshRenderer
  279. MeshFilter meshFilter = outlineObject.AddComponent<MeshFilter>();
  280. MeshRenderer outlineRenderer = outlineObject.AddComponent<MeshRenderer>();
  281. // Copy the mesh from the character
  282. MeshFilter characterMesh = GetComponent<MeshFilter>();
  283. if (characterMesh != null)
  284. {
  285. meshFilter.mesh = characterMesh.mesh;
  286. }
  287. // Create a simple black material for the outline
  288. Material outlineMaterial = new Material(Shader.Find("Standard"));
  289. outlineMaterial.color = Color.black;
  290. outlineRenderer.material = outlineMaterial;
  291. // Set rendering order to appear behind the character
  292. outlineRenderer.sortingOrder = -1;
  293. }
  294. private void RemoveOutline()
  295. {
  296. if (outlineObject != null)
  297. {
  298. DestroyImmediate(outlineObject);
  299. outlineObject = null;
  300. }
  301. }
  302. public void ExecuteAction()
  303. {
  304. Debug.Log($"=== ExecuteAction called for {CharacterName} ===");
  305. Debug.Log($"Action state: {actionData.state}");
  306. Debug.Log($"Has target enemy: {actionData.targetEnemy != null}");
  307. Debug.Log($"Target enemy name: {(actionData.targetEnemy != null ? actionData.targetEnemy.name : "none")}");
  308. if (actionData.state != ActionDecisionState.ActionSelected)
  309. {
  310. Debug.Log($"Action not selected, returning. State: {actionData.state}");
  311. return;
  312. }
  313. if (actionData.targetEnemy != null)
  314. {
  315. Debug.Log($"{CharacterName} executing attack on {actionData.targetEnemy.name}");
  316. // Get equipped weapon and perform attack
  317. Debug.Log($"Getting equipped weapon for {CharacterName}...");
  318. Debug.Log($"spawnedWeapon is: {(spawnedWeapon != null ? spawnedWeapon.GetType().Name : "null")}");
  319. Weapon equippedWeapon = GetEquippedWeapon();
  320. Debug.Log($"GetEquippedWeapon returned: {(equippedWeapon != null ? equippedWeapon.GetType().Name : "null")}");
  321. if (equippedWeapon != null)
  322. {
  323. Debug.Log($"Using weapon: {equippedWeapon.weaponName}");
  324. equippedWeapon.Attack(actionData.targetEnemy);
  325. Character targetCharacter = actionData.targetEnemy.GetComponent<Character>();
  326. if (targetCharacter != null && targetCharacter.IsDead)
  327. {
  328. actionData.state = ActionDecisionState.ActionExecuted;
  329. SetVisualState(actionData.state);
  330. }
  331. }
  332. else
  333. {
  334. Debug.LogError($"{CharacterName} has no weapon equipped for attack!");
  335. Debug.LogError($"spawnedWeapon field is: {(spawnedWeapon != null ? "not null" : "null")}");
  336. Debug.LogError($"Weapon property is: {(Weapon != null ? "not null" : "null")}");
  337. }
  338. }
  339. else if (actionData.targetPosition != Vector3.zero)
  340. {
  341. Debug.Log($"{CharacterName} moves to {actionData.targetPosition}");
  342. // Add your movement logic here
  343. actionData.state = ActionDecisionState.ActionExecuted;
  344. SetVisualState(actionData.state);
  345. }
  346. }
  347. public bool HasActionSelected()
  348. {
  349. return actionData.state == ActionDecisionState.ActionSelected;
  350. }
  351. public bool IsActionComplete()
  352. {
  353. // If action is already executed or no action, it's complete
  354. if (actionData.state != ActionDecisionState.ActionSelected) return true;
  355. if (actionData.targetEnemy != null)
  356. {
  357. // Check if we're within attack range of the target
  358. float distance = Vector3.Distance(transform.position, actionData.targetEnemy.transform.position);
  359. float attackRange = GetAttackRange();
  360. return distance <= attackRange;
  361. }
  362. else if (actionData.targetPosition != Vector3.zero)
  363. {
  364. // Check if we've reached the movement target using horizontal distance
  365. Vector3 currentPos = transform.position;
  366. Vector3 targetPos = actionData.targetPosition;
  367. Vector2 currentPos2D = new Vector2(currentPos.x, currentPos.z);
  368. Vector2 targetPos2D = new Vector2(targetPos.x, targetPos.z);
  369. float horizontalDistance = Vector2.Distance(currentPos2D, targetPos2D);
  370. return horizontalDistance <= 1.2f; // Arrival threshold
  371. }
  372. return true; // No target means completed
  373. }
  374. public void ResetAction()
  375. {
  376. actionData.Reset();
  377. SetVisualState(actionData.state);
  378. }
  379. public void TakeDamage(int damage)
  380. {
  381. if (isDead) return;
  382. currentHealth -= damage;
  383. Debug.Log($"{CharacterName} takes {damage} damage. Health: {currentHealth}/{maxHealth}");
  384. // Update health bar
  385. UpdateHealthBar();
  386. if (currentHealth <= 0)
  387. {
  388. Die();
  389. }
  390. }
  391. // Debug method to test attacks manually
  392. public void DebugAttack(GameObject target)
  393. {
  394. Weapon equippedWeapon = GetEquippedWeapon();
  395. if (equippedWeapon != null)
  396. {
  397. Debug.Log($"DEBUG: {CharacterName} manually attacking {target.name}");
  398. equippedWeapon.Attack(target);
  399. }
  400. else
  401. {
  402. Debug.LogError($"DEBUG: {CharacterName} has no weapon equipped!");
  403. }
  404. }
  405. private void Die()
  406. {
  407. isDead = true;
  408. isTargetable = false;
  409. currentHealth = 0;
  410. gameObject.SetActive(false); // Disable the character GameObject
  411. characterRenderer.enabled = false;
  412. // Make character gray
  413. if (characterMaterial != null)
  414. {
  415. characterMaterial.color = Color.gray;
  416. }
  417. // Update health bar to show death
  418. UpdateHealthBar();
  419. Debug.Log($"{CharacterName} has died!");
  420. }
  421. private void UpdateHealthBar()
  422. {
  423. if (healthSlider != null)
  424. {
  425. healthSlider.value = currentHealth;
  426. healthSlider.maxValue = maxHealth;
  427. // Change color based on health percentage
  428. UnityEngine.UI.Image fillImage = healthSlider.fillRect.GetComponent<UnityEngine.UI.Image>();
  429. if (fillImage != null)
  430. {
  431. float healthPercent = (float)currentHealth / maxHealth;
  432. if (healthPercent > 0.6f)
  433. fillImage.color = Color.green;
  434. else if (healthPercent > 0.3f)
  435. fillImage.color = Color.yellow;
  436. else
  437. fillImage.color = Color.red;
  438. }
  439. Debug.Log($"Updated health bar for {CharacterName}: {currentHealth}/{maxHealth}");
  440. }
  441. }
  442. }