2021년 4학년 1학기 기업연계프로젝트2 컴퓨터소프트웨어공학과 <원광투어팀> 팀장 : 송유진 팀원 : 김나영, 이경희, 한유진
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

489 lines
18 KiB

5 years ago
  1. using System.Collections.Generic;
  2. using Unity.FPS.Game;
  3. using UnityEngine;
  4. using UnityEngine.AI;
  5. using UnityEngine.Events;
  6. namespace Unity.FPS.AI
  7. {
  8. [RequireComponent(typeof(Health), typeof(Actor), typeof(NavMeshAgent))]
  9. public class EnemyController : MonoBehaviour
  10. {
  11. [System.Serializable]
  12. public struct RendererIndexData
  13. {
  14. public Renderer Renderer;
  15. public int MaterialIndex;
  16. public RendererIndexData(Renderer renderer, int index)
  17. {
  18. Renderer = renderer;
  19. MaterialIndex = index;
  20. }
  21. }
  22. [Header("Parameters")]
  23. [Tooltip("The Y height at which the enemy will be automatically killed (if it falls off of the level)")]
  24. public float SelfDestructYHeight = -20f;
  25. [Tooltip("The distance at which the enemy considers that it has reached its current path destination point")]
  26. public float PathReachingRadius = 2f;
  27. [Tooltip("The speed at which the enemy rotates")]
  28. public float OrientationSpeed = 10f;
  29. [Tooltip("Delay after death where the GameObject is destroyed (to allow for animation)")]
  30. public float DeathDuration = 0f;
  31. [Header("Weapons Parameters")] [Tooltip("Allow weapon swapping for this enemy")]
  32. public bool SwapToNextWeapon = false;
  33. [Tooltip("Time delay between a weapon swap and the next attack")]
  34. public float DelayAfterWeaponSwap = 0f;
  35. [Header("Eye color")] [Tooltip("Material for the eye color")]
  36. public Material EyeColorMaterial;
  37. [Tooltip("The default color of the bot's eye")] [ColorUsageAttribute(true, true)]
  38. public Color DefaultEyeColor;
  39. [Tooltip("The attack color of the bot's eye")] [ColorUsageAttribute(true, true)]
  40. public Color AttackEyeColor;
  41. [Header("Flash on hit")] [Tooltip("The material used for the body of the hoverbot")]
  42. public Material BodyMaterial;
  43. [Tooltip("The gradient representing the color of the flash on hit")] [GradientUsageAttribute(true)]
  44. public Gradient OnHitBodyGradient;
  45. [Tooltip("The duration of the flash on hit")]
  46. public float FlashOnHitDuration = 0.5f;
  47. [Header("Sounds")] [Tooltip("Sound played when recieving damages")]
  48. public AudioClip DamageTick;
  49. [Header("VFX")] [Tooltip("The VFX prefab spawned when the enemy dies")]
  50. public GameObject DeathVfx;
  51. [Tooltip("The point at which the death VFX is spawned")]
  52. public Transform DeathVfxSpawnPoint;
  53. [Header("Loot")] [Tooltip("The object this enemy can drop when dying")]
  54. public GameObject LootPrefab;
  55. [Tooltip("The chance the object has to drop")] [Range(0, 1)]
  56. public float DropRate = 1f;
  57. [Header("Debug Display")] [Tooltip("Color of the sphere gizmo representing the path reaching range")]
  58. public Color PathReachingRangeColor = Color.yellow;
  59. [Tooltip("Color of the sphere gizmo representing the attack range")]
  60. public Color AttackRangeColor = Color.red;
  61. [Tooltip("Color of the sphere gizmo representing the detection range")]
  62. public Color DetectionRangeColor = Color.blue;
  63. public UnityAction onAttack;
  64. public UnityAction onDetectedTarget;
  65. public UnityAction onLostTarget;
  66. public UnityAction onDamaged;
  67. List<RendererIndexData> m_BodyRenderers = new List<RendererIndexData>();
  68. MaterialPropertyBlock m_BodyFlashMaterialPropertyBlock;
  69. float m_LastTimeDamaged = float.NegativeInfinity;
  70. RendererIndexData m_EyeRendererData;
  71. MaterialPropertyBlock m_EyeColorMaterialPropertyBlock;
  72. public PatrolPath PatrolPath { get; set; }
  73. public GameObject KnownDetectedTarget => DetectionModule.KnownDetectedTarget;
  74. public bool IsTargetInAttackRange => DetectionModule.IsTargetInAttackRange;
  75. public bool IsSeeingTarget => DetectionModule.IsSeeingTarget;
  76. public bool HadKnownTarget => DetectionModule.HadKnownTarget;
  77. public NavMeshAgent NavMeshAgent { get; private set; }
  78. public DetectionModule DetectionModule { get; private set; }
  79. int m_PathDestinationNodeIndex;
  80. EnemyManager m_EnemyManager;
  81. ActorsManager m_ActorsManager;
  82. Health m_Health;
  83. Actor m_Actor;
  84. Collider[] m_SelfColliders;
  85. GameFlowManager m_GameFlowManager;
  86. bool m_WasDamagedThisFrame;
  87. float m_LastTimeWeaponSwapped = Mathf.NegativeInfinity;
  88. int m_CurrentWeaponIndex;
  89. WeaponController m_CurrentWeapon;
  90. WeaponController[] m_Weapons;
  91. NavigationModule m_NavigationModule;
  92. void Start()
  93. {
  94. m_EnemyManager = FindObjectOfType<EnemyManager>();
  95. DebugUtility.HandleErrorIfNullFindObject<EnemyManager, EnemyController>(m_EnemyManager, this);
  96. m_ActorsManager = FindObjectOfType<ActorsManager>();
  97. DebugUtility.HandleErrorIfNullFindObject<ActorsManager, EnemyController>(m_ActorsManager, this);
  98. m_EnemyManager.RegisterEnemy(this);
  99. m_Health = GetComponent<Health>();
  100. DebugUtility.HandleErrorIfNullGetComponent<Health, EnemyController>(m_Health, this, gameObject);
  101. m_Actor = GetComponent<Actor>();
  102. DebugUtility.HandleErrorIfNullGetComponent<Actor, EnemyController>(m_Actor, this, gameObject);
  103. NavMeshAgent = GetComponent<NavMeshAgent>();
  104. m_SelfColliders = GetComponentsInChildren<Collider>();
  105. m_GameFlowManager = FindObjectOfType<GameFlowManager>();
  106. DebugUtility.HandleErrorIfNullFindObject<GameFlowManager, EnemyController>(m_GameFlowManager, this);
  107. // Subscribe to damage & death actions
  108. m_Health.OnDie += OnDie;
  109. m_Health.OnDamaged += OnDamaged;
  110. // Find and initialize all weapons
  111. FindAndInitializeAllWeapons();
  112. var weapon = GetCurrentWeapon();
  113. weapon.ShowWeapon(true);
  114. var detectionModules = GetComponentsInChildren<DetectionModule>();
  115. DebugUtility.HandleErrorIfNoComponentFound<DetectionModule, EnemyController>(detectionModules.Length, this,
  116. gameObject);
  117. DebugUtility.HandleWarningIfDuplicateObjects<DetectionModule, EnemyController>(detectionModules.Length,
  118. this, gameObject);
  119. // Initialize detection module
  120. DetectionModule = detectionModules[0];
  121. DetectionModule.onDetectedTarget += OnDetectedTarget;
  122. DetectionModule.onLostTarget += OnLostTarget;
  123. onAttack += DetectionModule.OnAttack;
  124. var navigationModules = GetComponentsInChildren<NavigationModule>();
  125. DebugUtility.HandleWarningIfDuplicateObjects<DetectionModule, EnemyController>(detectionModules.Length,
  126. this, gameObject);
  127. // Override navmesh agent data
  128. if (navigationModules.Length > 0)
  129. {
  130. m_NavigationModule = navigationModules[0];
  131. NavMeshAgent.speed = m_NavigationModule.MoveSpeed;
  132. NavMeshAgent.angularSpeed = m_NavigationModule.AngularSpeed;
  133. NavMeshAgent.acceleration = m_NavigationModule.Acceleration;
  134. }
  135. foreach (var renderer in GetComponentsInChildren<Renderer>(true))
  136. {
  137. for (int i = 0; i < renderer.sharedMaterials.Length; i++)
  138. {
  139. if (renderer.sharedMaterials[i] == EyeColorMaterial)
  140. {
  141. m_EyeRendererData = new RendererIndexData(renderer, i);
  142. }
  143. if (renderer.sharedMaterials[i] == BodyMaterial)
  144. {
  145. m_BodyRenderers.Add(new RendererIndexData(renderer, i));
  146. }
  147. }
  148. }
  149. m_BodyFlashMaterialPropertyBlock = new MaterialPropertyBlock();
  150. // Check if we have an eye renderer for this enemy
  151. if (m_EyeRendererData.Renderer != null)
  152. {
  153. m_EyeColorMaterialPropertyBlock = new MaterialPropertyBlock();
  154. m_EyeColorMaterialPropertyBlock.SetColor("_EmissionColor", DefaultEyeColor);
  155. m_EyeRendererData.Renderer.SetPropertyBlock(m_EyeColorMaterialPropertyBlock,
  156. m_EyeRendererData.MaterialIndex);
  157. }
  158. }
  159. void Update()
  160. {
  161. EnsureIsWithinLevelBounds();
  162. DetectionModule.HandleTargetDetection(m_Actor, m_SelfColliders);
  163. Color currentColor = OnHitBodyGradient.Evaluate((Time.time - m_LastTimeDamaged) / FlashOnHitDuration);
  164. m_BodyFlashMaterialPropertyBlock.SetColor("_EmissionColor", currentColor);
  165. foreach (var data in m_BodyRenderers)
  166. {
  167. data.Renderer.SetPropertyBlock(m_BodyFlashMaterialPropertyBlock, data.MaterialIndex);
  168. }
  169. m_WasDamagedThisFrame = false;
  170. }
  171. void EnsureIsWithinLevelBounds()
  172. {
  173. // at every frame, this tests for conditions to kill the enemy
  174. if (transform.position.y < SelfDestructYHeight)
  175. {
  176. Destroy(gameObject);
  177. return;
  178. }
  179. }
  180. void OnLostTarget()
  181. {
  182. onLostTarget.Invoke();
  183. // Set the eye attack color and property block if the eye renderer is set
  184. if (m_EyeRendererData.Renderer != null)
  185. {
  186. m_EyeColorMaterialPropertyBlock.SetColor("_EmissionColor", DefaultEyeColor);
  187. m_EyeRendererData.Renderer.SetPropertyBlock(m_EyeColorMaterialPropertyBlock,
  188. m_EyeRendererData.MaterialIndex);
  189. }
  190. }
  191. void OnDetectedTarget()
  192. {
  193. onDetectedTarget.Invoke();
  194. // Set the eye default color and property block if the eye renderer is set
  195. if (m_EyeRendererData.Renderer != null)
  196. {
  197. m_EyeColorMaterialPropertyBlock.SetColor("_EmissionColor", AttackEyeColor);
  198. m_EyeRendererData.Renderer.SetPropertyBlock(m_EyeColorMaterialPropertyBlock,
  199. m_EyeRendererData.MaterialIndex);
  200. }
  201. }
  202. public void OrientTowards(Vector3 lookPosition)
  203. {
  204. Vector3 lookDirection = Vector3.ProjectOnPlane(lookPosition - transform.position, Vector3.up).normalized;
  205. if (lookDirection.sqrMagnitude != 0f)
  206. {
  207. Quaternion targetRotation = Quaternion.LookRotation(lookDirection);
  208. transform.rotation =
  209. Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * OrientationSpeed);
  210. }
  211. }
  212. bool IsPathValid()
  213. {
  214. return PatrolPath && PatrolPath.PathNodes.Count > 0;
  215. }
  216. public void ResetPathDestination()
  217. {
  218. m_PathDestinationNodeIndex = 0;
  219. }
  220. public void SetPathDestinationToClosestNode()
  221. {
  222. if (IsPathValid())
  223. {
  224. int closestPathNodeIndex = 0;
  225. for (int i = 0; i < PatrolPath.PathNodes.Count; i++)
  226. {
  227. float distanceToPathNode = PatrolPath.GetDistanceToNode(transform.position, i);
  228. if (distanceToPathNode < PatrolPath.GetDistanceToNode(transform.position, closestPathNodeIndex))
  229. {
  230. closestPathNodeIndex = i;
  231. }
  232. }
  233. m_PathDestinationNodeIndex = closestPathNodeIndex;
  234. }
  235. else
  236. {
  237. m_PathDestinationNodeIndex = 0;
  238. }
  239. }
  240. public Vector3 GetDestinationOnPath()
  241. {
  242. if (IsPathValid())
  243. {
  244. return PatrolPath.GetPositionOfPathNode(m_PathDestinationNodeIndex);
  245. }
  246. else
  247. {
  248. return transform.position;
  249. }
  250. }
  251. public void SetNavDestination(Vector3 destination)
  252. {
  253. if (NavMeshAgent)
  254. {
  255. NavMeshAgent.SetDestination(destination);
  256. }
  257. }
  258. public void UpdatePathDestination(bool inverseOrder = false)
  259. {
  260. if (IsPathValid())
  261. {
  262. // Check if reached the path destination
  263. if ((transform.position - GetDestinationOnPath()).magnitude <= PathReachingRadius)
  264. {
  265. // increment path destination index
  266. m_PathDestinationNodeIndex =
  267. inverseOrder ? (m_PathDestinationNodeIndex - 1) : (m_PathDestinationNodeIndex + 1);
  268. if (m_PathDestinationNodeIndex < 0)
  269. {
  270. m_PathDestinationNodeIndex += PatrolPath.PathNodes.Count;
  271. }
  272. if (m_PathDestinationNodeIndex >= PatrolPath.PathNodes.Count)
  273. {
  274. m_PathDestinationNodeIndex -= PatrolPath.PathNodes.Count;
  275. }
  276. }
  277. }
  278. }
  279. void OnDamaged(float damage, GameObject damageSource)
  280. {
  281. // test if the damage source is the player
  282. if (damageSource && !damageSource.GetComponent<EnemyController>())
  283. {
  284. // pursue the player
  285. DetectionModule.OnDamaged(damageSource);
  286. onDamaged?.Invoke();
  287. m_LastTimeDamaged = Time.time;
  288. // play the damage tick sound
  289. if (DamageTick && !m_WasDamagedThisFrame)
  290. AudioUtility.CreateSFX(DamageTick, transform.position, AudioUtility.AudioGroups.DamageTick, 0f);
  291. m_WasDamagedThisFrame = true;
  292. }
  293. }
  294. void OnDie()
  295. {
  296. // spawn a particle system when dying
  297. var vfx = Instantiate(DeathVfx, DeathVfxSpawnPoint.position, Quaternion.identity);
  298. Destroy(vfx, 5f);
  299. // tells the game flow manager to handle the enemy destuction
  300. m_EnemyManager.UnregisterEnemy(this);
  301. // loot an object
  302. if (TryDropItem())
  303. {
  304. Instantiate(LootPrefab, transform.position, Quaternion.identity);
  305. }
  306. // this will call the OnDestroy function
  307. Destroy(gameObject, DeathDuration);
  308. }
  309. void OnDrawGizmosSelected()
  310. {
  311. // Path reaching range
  312. Gizmos.color = PathReachingRangeColor;
  313. Gizmos.DrawWireSphere(transform.position, PathReachingRadius);
  314. if (DetectionModule != null)
  315. {
  316. // Detection range
  317. Gizmos.color = DetectionRangeColor;
  318. Gizmos.DrawWireSphere(transform.position, DetectionModule.DetectionRange);
  319. // Attack range
  320. Gizmos.color = AttackRangeColor;
  321. Gizmos.DrawWireSphere(transform.position, DetectionModule.AttackRange);
  322. }
  323. }
  324. public void OrientWeaponsTowards(Vector3 lookPosition)
  325. {
  326. for (int i = 0; i < m_Weapons.Length; i++)
  327. {
  328. // orient weapon towards player
  329. Vector3 weaponForward = (lookPosition - m_Weapons[i].WeaponRoot.transform.position).normalized;
  330. m_Weapons[i].transform.forward = weaponForward;
  331. }
  332. }
  333. public bool TryAtack(Vector3 enemyPosition)
  334. {
  335. if (m_GameFlowManager.GameIsEnding)
  336. return false;
  337. OrientWeaponsTowards(enemyPosition);
  338. if ((m_LastTimeWeaponSwapped + DelayAfterWeaponSwap) >= Time.time)
  339. return false;
  340. // Shoot the weapon
  341. bool didFire = GetCurrentWeapon().HandleShootInputs(false, true, false);
  342. if (didFire && onAttack != null)
  343. {
  344. onAttack.Invoke();
  345. if (SwapToNextWeapon && m_Weapons.Length > 1)
  346. {
  347. int nextWeaponIndex = (m_CurrentWeaponIndex + 1) % m_Weapons.Length;
  348. SetCurrentWeapon(nextWeaponIndex);
  349. }
  350. }
  351. return didFire;
  352. }
  353. public bool TryDropItem()
  354. {
  355. if (DropRate == 0 || LootPrefab == null)
  356. return false;
  357. else if (DropRate == 1)
  358. return true;
  359. else
  360. return (Random.value <= DropRate);
  361. }
  362. void FindAndInitializeAllWeapons()
  363. {
  364. // Check if we already found and initialized the weapons
  365. if (m_Weapons == null)
  366. {
  367. m_Weapons = GetComponentsInChildren<WeaponController>();
  368. DebugUtility.HandleErrorIfNoComponentFound<WeaponController, EnemyController>(m_Weapons.Length, this,
  369. gameObject);
  370. for (int i = 0; i < m_Weapons.Length; i++)
  371. {
  372. m_Weapons[i].Owner = gameObject;
  373. }
  374. }
  375. }
  376. public WeaponController GetCurrentWeapon()
  377. {
  378. FindAndInitializeAllWeapons();
  379. // Check if no weapon is currently selected
  380. if (m_CurrentWeapon == null)
  381. {
  382. // Set the first weapon of the weapons list as the current weapon
  383. SetCurrentWeapon(0);
  384. }
  385. DebugUtility.HandleErrorIfNullGetComponent<WeaponController, EnemyController>(m_CurrentWeapon, this,
  386. gameObject);
  387. return m_CurrentWeapon;
  388. }
  389. void SetCurrentWeapon(int index)
  390. {
  391. m_CurrentWeaponIndex = index;
  392. m_CurrentWeapon = m_Weapons[m_CurrentWeaponIndex];
  393. if (SwapToNextWeapon)
  394. {
  395. m_LastTimeWeaponSwapped = Time.time;
  396. }
  397. else
  398. {
  399. m_LastTimeWeaponSwapped = Mathf.NegativeInfinity;
  400. }
  401. }
  402. }
  403. }