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.

559 lines
22 KiB

5 years ago
  1. using System.Collections.Generic;
  2. using Unity.FPS.Game;
  3. using UnityEngine;
  4. using UnityEngine.Events;
  5. namespace Unity.FPS.Gameplay
  6. {
  7. [RequireComponent(typeof(PlayerInputHandler))]
  8. public class PlayerWeaponsManager : MonoBehaviour
  9. {
  10. public enum WeaponSwitchState
  11. {
  12. Up,
  13. Down,
  14. PutDownPrevious,
  15. PutUpNew,
  16. }
  17. [Tooltip("List of weapon the player will start with")]
  18. public List<WeaponController> StartingWeapons = new List<WeaponController>();
  19. [Header("References")] [Tooltip("Secondary camera used to avoid seeing weapon go throw geometries")]
  20. public Camera WeaponCamera;
  21. [Tooltip("Parent transform where all weapon will be added in the hierarchy")]
  22. public Transform WeaponParentSocket;
  23. [Tooltip("Position for weapons when active but not actively aiming")]
  24. public Transform DefaultWeaponPosition;
  25. [Tooltip("Position for weapons when aiming")]
  26. public Transform AimingWeaponPosition;
  27. [Tooltip("Position for innactive weapons")]
  28. public Transform DownWeaponPosition;
  29. [Header("Weapon Bob")]
  30. [Tooltip("Frequency at which the weapon will move around in the screen when the player is in movement")]
  31. public float BobFrequency = 10f;
  32. [Tooltip("How fast the weapon bob is applied, the bigger value the fastest")]
  33. public float BobSharpness = 10f;
  34. [Tooltip("Distance the weapon bobs when not aiming")]
  35. public float DefaultBobAmount = 0.05f;
  36. [Tooltip("Distance the weapon bobs when aiming")]
  37. public float AimingBobAmount = 0.02f;
  38. [Header("Weapon Recoil")]
  39. [Tooltip("This will affect how fast the recoil moves the weapon, the bigger the value, the fastest")]
  40. public float RecoilSharpness = 50f;
  41. [Tooltip("Maximum distance the recoil can affect the weapon")]
  42. public float MaxRecoilDistance = 0.5f;
  43. [Tooltip("How fast the weapon goes back to it's original position after the recoil is finished")]
  44. public float RecoilRestitutionSharpness = 10f;
  45. [Header("Misc")] [Tooltip("Speed at which the aiming animatoin is played")]
  46. public float AimingAnimationSpeed = 10f;
  47. [Tooltip("Field of view when not aiming")]
  48. public float DefaultFov = 60f;
  49. [Tooltip("Portion of the regular FOV to apply to the weapon camera")]
  50. public float WeaponFovMultiplier = 1f;
  51. [Tooltip("Delay before switching weapon a second time, to avoid recieving multiple inputs from mouse wheel")]
  52. public float WeaponSwitchDelay = 1f;
  53. [Tooltip("Layer to set FPS weapon gameObjects to")]
  54. public LayerMask FpsWeaponLayer;
  55. public bool IsAiming { get; private set; }
  56. public bool IsPointingAtEnemy { get; private set; }
  57. public int ActiveWeaponIndex { get; private set; }
  58. public UnityAction<WeaponController> OnSwitchedToWeapon;
  59. public UnityAction<WeaponController, int> OnAddedWeapon;
  60. public UnityAction<WeaponController, int> OnRemovedWeapon;
  61. WeaponController[] m_WeaponSlots = new WeaponController[9]; // 9 available weapon slots
  62. PlayerInputHandler m_InputHandler;
  63. PlayerCharacterController m_PlayerCharacterController;
  64. float m_WeaponBobFactor;
  65. Vector3 m_LastCharacterPosition;
  66. Vector3 m_WeaponMainLocalPosition;
  67. Vector3 m_WeaponBobLocalPosition;
  68. Vector3 m_WeaponRecoilLocalPosition;
  69. Vector3 m_AccumulatedRecoil;
  70. float m_TimeStartedWeaponSwitch;
  71. WeaponSwitchState m_WeaponSwitchState;
  72. int m_WeaponSwitchNewWeaponIndex;
  73. void Start()
  74. {
  75. ActiveWeaponIndex = -1;
  76. m_WeaponSwitchState = WeaponSwitchState.Down;
  77. m_InputHandler = GetComponent<PlayerInputHandler>();
  78. DebugUtility.HandleErrorIfNullGetComponent<PlayerInputHandler, PlayerWeaponsManager>(m_InputHandler, this,
  79. gameObject);
  80. m_PlayerCharacterController = GetComponent<PlayerCharacterController>();
  81. DebugUtility.HandleErrorIfNullGetComponent<PlayerCharacterController, PlayerWeaponsManager>(
  82. m_PlayerCharacterController, this, gameObject);
  83. SetFov(DefaultFov);
  84. OnSwitchedToWeapon += OnWeaponSwitched;
  85. // Add starting weapons
  86. foreach (var weapon in StartingWeapons)
  87. {
  88. AddWeapon(weapon);
  89. }
  90. SwitchWeapon(true);
  91. }
  92. void Update()
  93. {
  94. // shoot handling
  95. WeaponController activeWeapon = GetActiveWeapon();
  96. if (activeWeapon != null && activeWeapon.IsReloading)
  97. return;
  98. if (activeWeapon != null && m_WeaponSwitchState == WeaponSwitchState.Up)
  99. {
  100. if (!activeWeapon.AutomaticReload && m_InputHandler.GetReloadButtonDown() && activeWeapon.CurrentAmmoRatio < 1.0f)
  101. {
  102. IsAiming = false;
  103. activeWeapon.StartReloadAnimation();
  104. return;
  105. }
  106. // handle aiming down sights
  107. IsAiming = m_InputHandler.GetAimInputHeld();
  108. // handle shooting
  109. bool hasFired = activeWeapon.HandleShootInputs(
  110. m_InputHandler.GetFireInputDown(),
  111. m_InputHandler.GetFireInputHeld(),
  112. m_InputHandler.GetFireInputReleased());
  113. // Handle accumulating recoil
  114. if (hasFired)
  115. {
  116. m_AccumulatedRecoil += Vector3.back * activeWeapon.RecoilForce;
  117. m_AccumulatedRecoil = Vector3.ClampMagnitude(m_AccumulatedRecoil, MaxRecoilDistance);
  118. }
  119. }
  120. // weapon switch handling
  121. if (!IsAiming &&
  122. (activeWeapon == null || !activeWeapon.IsCharging) &&
  123. (m_WeaponSwitchState == WeaponSwitchState.Up || m_WeaponSwitchState == WeaponSwitchState.Down))
  124. {
  125. int switchWeaponInput = m_InputHandler.GetSwitchWeaponInput();
  126. if (switchWeaponInput != 0)
  127. {
  128. bool switchUp = switchWeaponInput > 0;
  129. SwitchWeapon(switchUp);
  130. }
  131. else
  132. {
  133. switchWeaponInput = m_InputHandler.GetSelectWeaponInput();
  134. if (switchWeaponInput != 0)
  135. {
  136. if (GetWeaponAtSlotIndex(switchWeaponInput - 1) != null)
  137. SwitchToWeaponIndex(switchWeaponInput - 1);
  138. }
  139. }
  140. }
  141. // Pointing at enemy handling
  142. IsPointingAtEnemy = false;
  143. if (activeWeapon)
  144. {
  145. if (Physics.Raycast(WeaponCamera.transform.position, WeaponCamera.transform.forward, out RaycastHit hit,
  146. 1000, -1, QueryTriggerInteraction.Ignore))
  147. {
  148. if (hit.collider.GetComponentInParent<Health>() != null)
  149. {
  150. IsPointingAtEnemy = true;
  151. }
  152. }
  153. }
  154. }
  155. // Update various animated features in LateUpdate because it needs to override the animated arm position
  156. void LateUpdate()
  157. {
  158. UpdateWeaponAiming();
  159. UpdateWeaponBob();
  160. UpdateWeaponRecoil();
  161. UpdateWeaponSwitching();
  162. // Set final weapon socket position based on all the combined animation influences
  163. WeaponParentSocket.localPosition =
  164. m_WeaponMainLocalPosition + m_WeaponBobLocalPosition + m_WeaponRecoilLocalPosition;
  165. }
  166. // Sets the FOV of the main camera and the weapon camera simultaneously
  167. public void SetFov(float fov)
  168. {
  169. m_PlayerCharacterController.PlayerCamera.fieldOfView = fov;
  170. WeaponCamera.fieldOfView = fov * WeaponFovMultiplier;
  171. }
  172. // Iterate on all weapon slots to find the next valid weapon to switch to
  173. public void SwitchWeapon(bool ascendingOrder)
  174. {
  175. int newWeaponIndex = -1;
  176. int closestSlotDistance = m_WeaponSlots.Length;
  177. for (int i = 0; i < m_WeaponSlots.Length; i++)
  178. {
  179. // If the weapon at this slot is valid, calculate its "distance" from the active slot index (either in ascending or descending order)
  180. // and select it if it's the closest distance yet
  181. if (i != ActiveWeaponIndex && GetWeaponAtSlotIndex(i) != null)
  182. {
  183. int distanceToActiveIndex = GetDistanceBetweenWeaponSlots(ActiveWeaponIndex, i, ascendingOrder);
  184. if (distanceToActiveIndex < closestSlotDistance)
  185. {
  186. closestSlotDistance = distanceToActiveIndex;
  187. newWeaponIndex = i;
  188. }
  189. }
  190. }
  191. // Handle switching to the new weapon index
  192. SwitchToWeaponIndex(newWeaponIndex);
  193. }
  194. // Switches to the given weapon index in weapon slots if the new index is a valid weapon that is different from our current one
  195. public void SwitchToWeaponIndex(int newWeaponIndex, bool force = false)
  196. {
  197. if (force || (newWeaponIndex != ActiveWeaponIndex && newWeaponIndex >= 0))
  198. {
  199. // Store data related to weapon switching animation
  200. m_WeaponSwitchNewWeaponIndex = newWeaponIndex;
  201. m_TimeStartedWeaponSwitch = Time.time;
  202. // Handle case of switching to a valid weapon for the first time (simply put it up without putting anything down first)
  203. if (GetActiveWeapon() == null)
  204. {
  205. m_WeaponMainLocalPosition = DownWeaponPosition.localPosition;
  206. m_WeaponSwitchState = WeaponSwitchState.PutUpNew;
  207. ActiveWeaponIndex = m_WeaponSwitchNewWeaponIndex;
  208. WeaponController newWeapon = GetWeaponAtSlotIndex(m_WeaponSwitchNewWeaponIndex);
  209. if (OnSwitchedToWeapon != null)
  210. {
  211. OnSwitchedToWeapon.Invoke(newWeapon);
  212. }
  213. }
  214. // otherwise, remember we are putting down our current weapon for switching to the next one
  215. else
  216. {
  217. m_WeaponSwitchState = WeaponSwitchState.PutDownPrevious;
  218. }
  219. }
  220. }
  221. public WeaponController HasWeapon(WeaponController weaponPrefab)
  222. {
  223. // Checks if we already have a weapon coming from the specified prefab
  224. for (var index = 0; index < m_WeaponSlots.Length; index++)
  225. {
  226. var w = m_WeaponSlots[index];
  227. if (w != null && w.SourcePrefab == weaponPrefab.gameObject)
  228. {
  229. return w;
  230. }
  231. }
  232. return null;
  233. }
  234. // Updates weapon position and camera FoV for the aiming transition
  235. void UpdateWeaponAiming()
  236. {
  237. if (m_WeaponSwitchState == WeaponSwitchState.Up)
  238. {
  239. WeaponController activeWeapon = GetActiveWeapon();
  240. if (IsAiming && activeWeapon)
  241. {
  242. m_WeaponMainLocalPosition = Vector3.Lerp(m_WeaponMainLocalPosition,
  243. AimingWeaponPosition.localPosition + activeWeapon.AimOffset,
  244. AimingAnimationSpeed * Time.deltaTime);
  245. SetFov(Mathf.Lerp(m_PlayerCharacterController.PlayerCamera.fieldOfView,
  246. activeWeapon.AimZoomRatio * DefaultFov, AimingAnimationSpeed * Time.deltaTime));
  247. }
  248. else
  249. {
  250. m_WeaponMainLocalPosition = Vector3.Lerp(m_WeaponMainLocalPosition,
  251. DefaultWeaponPosition.localPosition, AimingAnimationSpeed * Time.deltaTime);
  252. SetFov(Mathf.Lerp(m_PlayerCharacterController.PlayerCamera.fieldOfView, DefaultFov,
  253. AimingAnimationSpeed * Time.deltaTime));
  254. }
  255. }
  256. }
  257. // Updates the weapon bob animation based on character speed
  258. void UpdateWeaponBob()
  259. {
  260. if (Time.deltaTime > 0f)
  261. {
  262. Vector3 playerCharacterVelocity =
  263. (m_PlayerCharacterController.transform.position - m_LastCharacterPosition) / Time.deltaTime;
  264. // calculate a smoothed weapon bob amount based on how close to our max grounded movement velocity we are
  265. float characterMovementFactor = 0f;
  266. if (m_PlayerCharacterController.IsGrounded)
  267. {
  268. characterMovementFactor =
  269. Mathf.Clamp01(playerCharacterVelocity.magnitude /
  270. (m_PlayerCharacterController.MaxSpeedOnGround *
  271. m_PlayerCharacterController.SprintSpeedModifier));
  272. }
  273. m_WeaponBobFactor =
  274. Mathf.Lerp(m_WeaponBobFactor, characterMovementFactor, BobSharpness * Time.deltaTime);
  275. // Calculate vertical and horizontal weapon bob values based on a sine function
  276. float bobAmount = IsAiming ? AimingBobAmount : DefaultBobAmount;
  277. float frequency = BobFrequency;
  278. float hBobValue = Mathf.Sin(Time.time * frequency) * bobAmount * m_WeaponBobFactor;
  279. float vBobValue = ((Mathf.Sin(Time.time * frequency * 2f) * 0.5f) + 0.5f) * bobAmount *
  280. m_WeaponBobFactor;
  281. // Apply weapon bob
  282. m_WeaponBobLocalPosition.x = hBobValue;
  283. m_WeaponBobLocalPosition.y = Mathf.Abs(vBobValue);
  284. m_LastCharacterPosition = m_PlayerCharacterController.transform.position;
  285. }
  286. }
  287. // Updates the weapon recoil animation
  288. void UpdateWeaponRecoil()
  289. {
  290. // if the accumulated recoil is further away from the current position, make the current position move towards the recoil target
  291. if (m_WeaponRecoilLocalPosition.z >= m_AccumulatedRecoil.z * 0.99f)
  292. {
  293. m_WeaponRecoilLocalPosition = Vector3.Lerp(m_WeaponRecoilLocalPosition, m_AccumulatedRecoil,
  294. RecoilSharpness * Time.deltaTime);
  295. }
  296. // otherwise, move recoil position to make it recover towards its resting pose
  297. else
  298. {
  299. m_WeaponRecoilLocalPosition = Vector3.Lerp(m_WeaponRecoilLocalPosition, Vector3.zero,
  300. RecoilRestitutionSharpness * Time.deltaTime);
  301. m_AccumulatedRecoil = m_WeaponRecoilLocalPosition;
  302. }
  303. }
  304. // Updates the animated transition of switching weapons
  305. void UpdateWeaponSwitching()
  306. {
  307. // Calculate the time ratio (0 to 1) since weapon switch was triggered
  308. float switchingTimeFactor = 0f;
  309. if (WeaponSwitchDelay == 0f)
  310. {
  311. switchingTimeFactor = 1f;
  312. }
  313. else
  314. {
  315. switchingTimeFactor = Mathf.Clamp01((Time.time - m_TimeStartedWeaponSwitch) / WeaponSwitchDelay);
  316. }
  317. // Handle transiting to new switch state
  318. if (switchingTimeFactor >= 1f)
  319. {
  320. if (m_WeaponSwitchState == WeaponSwitchState.PutDownPrevious)
  321. {
  322. // Deactivate old weapon
  323. WeaponController oldWeapon = GetWeaponAtSlotIndex(ActiveWeaponIndex);
  324. if (oldWeapon != null)
  325. {
  326. oldWeapon.ShowWeapon(false);
  327. }
  328. ActiveWeaponIndex = m_WeaponSwitchNewWeaponIndex;
  329. switchingTimeFactor = 0f;
  330. // Activate new weapon
  331. WeaponController newWeapon = GetWeaponAtSlotIndex(ActiveWeaponIndex);
  332. if (OnSwitchedToWeapon != null)
  333. {
  334. OnSwitchedToWeapon.Invoke(newWeapon);
  335. }
  336. if (newWeapon)
  337. {
  338. m_TimeStartedWeaponSwitch = Time.time;
  339. m_WeaponSwitchState = WeaponSwitchState.PutUpNew;
  340. }
  341. else
  342. {
  343. // if new weapon is null, don't follow through with putting weapon back up
  344. m_WeaponSwitchState = WeaponSwitchState.Down;
  345. }
  346. }
  347. else if (m_WeaponSwitchState == WeaponSwitchState.PutUpNew)
  348. {
  349. m_WeaponSwitchState = WeaponSwitchState.Up;
  350. }
  351. }
  352. // Handle moving the weapon socket position for the animated weapon switching
  353. if (m_WeaponSwitchState == WeaponSwitchState.PutDownPrevious)
  354. {
  355. m_WeaponMainLocalPosition = Vector3.Lerp(DefaultWeaponPosition.localPosition,
  356. DownWeaponPosition.localPosition, switchingTimeFactor);
  357. }
  358. else if (m_WeaponSwitchState == WeaponSwitchState.PutUpNew)
  359. {
  360. m_WeaponMainLocalPosition = Vector3.Lerp(DownWeaponPosition.localPosition,
  361. DefaultWeaponPosition.localPosition, switchingTimeFactor);
  362. }
  363. }
  364. // Adds a weapon to our inventory
  365. public bool AddWeapon(WeaponController weaponPrefab)
  366. {
  367. // if we already hold this weapon type (a weapon coming from the same source prefab), don't add the weapon
  368. if (HasWeapon(weaponPrefab) != null)
  369. {
  370. return false;
  371. }
  372. // search our weapon slots for the first free one, assign the weapon to it, and return true if we found one. Return false otherwise
  373. for (int i = 0; i < m_WeaponSlots.Length; i++)
  374. {
  375. // only add the weapon if the slot is free
  376. if (m_WeaponSlots[i] == null)
  377. {
  378. // spawn the weapon prefab as child of the weapon socket
  379. WeaponController weaponInstance = Instantiate(weaponPrefab, WeaponParentSocket);
  380. weaponInstance.transform.localPosition = Vector3.zero;
  381. weaponInstance.transform.localRotation = Quaternion.identity;
  382. // Set owner to this gameObject so the weapon can alter projectile/damage logic accordingly
  383. weaponInstance.Owner = gameObject;
  384. weaponInstance.SourcePrefab = weaponPrefab.gameObject;
  385. weaponInstance.ShowWeapon(false);
  386. // Assign the first person layer to the weapon
  387. int layerIndex =
  388. Mathf.RoundToInt(Mathf.Log(FpsWeaponLayer.value,
  389. 2)); // This function converts a layermask to a layer index
  390. foreach (Transform t in weaponInstance.gameObject.GetComponentsInChildren<Transform>(true))
  391. {
  392. t.gameObject.layer = layerIndex;
  393. }
  394. m_WeaponSlots[i] = weaponInstance;
  395. if (OnAddedWeapon != null)
  396. {
  397. OnAddedWeapon.Invoke(weaponInstance, i);
  398. }
  399. return true;
  400. }
  401. }
  402. // Handle auto-switching to weapon if no weapons currently
  403. if (GetActiveWeapon() == null)
  404. {
  405. SwitchWeapon(true);
  406. }
  407. return false;
  408. }
  409. public bool RemoveWeapon(WeaponController weaponInstance)
  410. {
  411. // Look through our slots for that weapon
  412. for (int i = 0; i < m_WeaponSlots.Length; i++)
  413. {
  414. // when weapon found, remove it
  415. if (m_WeaponSlots[i] == weaponInstance)
  416. {
  417. m_WeaponSlots[i] = null;
  418. if (OnRemovedWeapon != null)
  419. {
  420. OnRemovedWeapon.Invoke(weaponInstance, i);
  421. }
  422. Destroy(weaponInstance.gameObject);
  423. // Handle case of removing active weapon (switch to next weapon)
  424. if (i == ActiveWeaponIndex)
  425. {
  426. SwitchWeapon(true);
  427. }
  428. return true;
  429. }
  430. }
  431. return false;
  432. }
  433. public WeaponController GetActiveWeapon()
  434. {
  435. return GetWeaponAtSlotIndex(ActiveWeaponIndex);
  436. }
  437. public WeaponController GetWeaponAtSlotIndex(int index)
  438. {
  439. // find the active weapon in our weapon slots based on our active weapon index
  440. if (index >= 0 &&
  441. index < m_WeaponSlots.Length)
  442. {
  443. return m_WeaponSlots[index];
  444. }
  445. // if we didn't find a valid active weapon in our weapon slots, return null
  446. return null;
  447. }
  448. // Calculates the "distance" between two weapon slot indexes
  449. // For example: if we had 5 weapon slots, the distance between slots #2 and #4 would be 2 in ascending order, and 3 in descending order
  450. int GetDistanceBetweenWeaponSlots(int fromSlotIndex, int toSlotIndex, bool ascendingOrder)
  451. {
  452. int distanceBetweenSlots = 0;
  453. if (ascendingOrder)
  454. {
  455. distanceBetweenSlots = toSlotIndex - fromSlotIndex;
  456. }
  457. else
  458. {
  459. distanceBetweenSlots = -1 * (toSlotIndex - fromSlotIndex);
  460. }
  461. if (distanceBetweenSlots < 0)
  462. {
  463. distanceBetweenSlots = m_WeaponSlots.Length + distanceBetweenSlots;
  464. }
  465. return distanceBetweenSlots;
  466. }
  467. void OnWeaponSwitched(WeaponController newWeapon)
  468. {
  469. if (newWeapon != null)
  470. {
  471. newWeapon.ShowWeapon(true);
  472. }
  473. }
  474. }
  475. }