Arrow.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using DG.Tweening;
  4. /* 箭对象 */
  5. public class Arrow : MonoBehaviour
  6. {
  7. private Rigidbody newRigidbody;
  8. public Vector3 shootOutPosition;
  9. public float flyTime = 0;
  10. public bool isHit = false;
  11. public RaycastHit absoluteRay;
  12. public float offsetAngle;
  13. private float mySpeed = 0;
  14. public bool simulatedParabola = false; //模拟抛物线
  15. public ArmBow armBow;
  16. public static float speed = GameMgr.RealSizeToGameSize(60);
  17. void Awake()
  18. {
  19. GameMgr.ins.gameMode.PauseTimeCounting(this);
  20. }
  21. void Start()
  22. {
  23. newRigidbody = this.gameObject.AddComponent<Rigidbody>();
  24. mySpeed = speed;
  25. Billboard.ins?.SetArrowSpeed(speed);
  26. if (GameAssistUI.ins) {
  27. mySpeed *= GameAssistUI.ins.shootScaleValue;
  28. Billboard.ins?.SetArrowSpeedScale(GameAssistUI.ins.shootScaleValue);
  29. }
  30. Billboard.ins?.ShowSpeed();
  31. if (absoluteRay.transform && absoluteRay.transform.gameObject.name == "TargetBody") {
  32. //把瞄准点画成红圈,渲染在靶子上
  33. Transform redCircle = TargetBody.ins.transform.Find("RedCircle");
  34. redCircle.gameObject.SetActive(true);
  35. redCircle.transform.position = -redCircle.transform.forward * 0.001f + absoluteRay.point;
  36. }
  37. if (absoluteRay.transform && absoluteRay.transform.gameObject.name == "TargetBody") {
  38. SetUpBeforFly();
  39. }
  40. newRigidbody.velocity = this.transform.forward * mySpeed;
  41. newRigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
  42. newRigidbody.drag = 0;
  43. newRigidbody.angularDrag = 0;
  44. Transform cameraTF = this.transform.Find("Camera");
  45. cameraTF.gameObject.SetActive(true);
  46. cameraTF.gameObject.AddComponent<ArrowCamera>().arrow = this;
  47. this.activeEffectTrail(true);
  48. //是update运动,则禁用物理运动
  49. if (useUpdatePhysics) {
  50. this.GetComponent<BoxCollider>().enabled = false;
  51. Destroy(newRigidbody);
  52. }
  53. }
  54. /**在箭飞行前初始化&如果瞄准射线的落点在靶子上时,才调用该函数 */
  55. void SetUpBeforFly()
  56. {
  57. //看能否通过调整发射角让箭落在靶子的瞄准点上
  58. CalculateParabolaAngle(absoluteRay.point);
  59. if (!hasParabolaAngle) { //发射角无解,就提示用户
  60. if (AimLoadChecker.ins) AimLoadChecker.ins.ShowOutTip();
  61. return;
  62. }
  63. //来到这里,证明发射角有解,该解姑且叫它绝对角
  64. //因为Unity引擎的规则,向上时359~270度,向下是0~90度
  65. //为了方便数学运算,换算成 向上时0~90度,向下是0~-90度
  66. float baseAngleX = this.transform.eulerAngles.x;
  67. if (baseAngleX > 180) baseAngleX = baseAngleX - 360;
  68. baseAngleX *= -1;
  69. //绝对角
  70. float absoluteAngleX = parabolaAngleInRadian / Mathf.PI * 180;
  71. //客户要求绝对角跟原本角度相差不能超过 maxDeltaAngleX
  72. float deltaAngleX = absoluteAngleX - baseAngleX;
  73. float maxDeltaAngleX = 5;
  74. //最终的角还要加上偏移角度,偏移角也是客户要求加,单纯是为了增加误差,简单模式偏移角度为0
  75. float finalAngleX = this.offsetAngle;
  76. //如果绝对角跟原本角度相差超过 maxDeltaAngleX,就不能用绝对角了,直接在原本角度上加上最大差角
  77. if (Mathf.Abs(deltaAngleX) > maxDeltaAngleX) {
  78. finalAngleX += baseAngleX;
  79. finalAngleX += deltaAngleX > 0 ? maxDeltaAngleX : -maxDeltaAngleX;
  80. if (AimLoadChecker.ins) AimLoadChecker.ins.ShowOutTip();
  81. } else {//不超过就最好,直接用绝对角
  82. finalAngleX += absoluteAngleX;
  83. }
  84. //拆分两轴的速度
  85. float vy = Mathf.Sin(finalAngleX / 180 * Mathf.PI) * this.mySpeed;
  86. float vx = Mathf.Cos(finalAngleX / 180 * Mathf.PI) * this.mySpeed;
  87. //x轴距离
  88. float deltaX = Mathf.Sqrt(
  89. Mathf.Pow(absoluteRay.point.x - this.shootOutPosition.x, 2) +
  90. Mathf.Pow(absoluteRay.point.z - this.shootOutPosition.z, 2)
  91. );
  92. //x方向的耗时
  93. float tx = deltaX / vx;
  94. //最终y轴的位移
  95. float dy = vy * tx + 0.5f * Physics.gravity.y * tx * tx;
  96. float tbRadius = 0.62f;//靶子半径
  97. //之所以这样比较,是因为发射位置跟靶子中心是等高的,靶子半径是已知的
  98. if (Mathf.Abs(dy) < tbRadius) { //在靶子范围内,则用update运算物理
  99. finalPoint = absoluteRay.point;
  100. finalPoint.y = absoluteRay.transform.parent.position.y + dy;
  101. useUpdatePhysics = true;
  102. parabolaAngleInRadian = finalAngleX / 180 * Mathf.PI;
  103. RandomUseSideCamera();
  104. } else { //否则,调下角度就好,到时候用unity物理运算
  105. Vector3 eulerAngle = this.transform.eulerAngles;
  106. eulerAngle.x = -finalAngleX;
  107. this.transform.eulerAngles = eulerAngle;
  108. }
  109. }
  110. void RandomUseSideCamera() {
  111. //有几率进入侧面镜头观看
  112. float distance = TargetBody.ins.GetDistance();
  113. if (Mathf.RoundToInt(distance) >= 50) {
  114. if (Random.value < 0.5) simulatedParabola = true;
  115. }
  116. }
  117. /**发射角有无解 */
  118. bool hasParabolaAngle = false;
  119. /**发射角弧度解 */
  120. float parabolaAngleInRadian = 0;
  121. /**
  122. 求弓箭发射到指定坐标所需要的角度。
  123. 已知初速度大小V,重力g,起始坐标(a1,a2),目标坐标(b1,b2)。
  124. 解:
  125. 1、列出关系式
  126. Δx = b1 - a1;
  127. Δy = b2 - a2;
  128. Vx = V * cos(angle)
  129. Vy = V * sin(angle)
  130. Vy * t + 1/2 * g * t^2 = Δy
  131. Vx * t = Δx
  132. t = Δx / Vx
  133. 2、推导过程
  134. (V * sin(angle)) * Δx / (V * cos(angle)) + 1/2 * g * Δx^2 / (V^2*cos(angle)^2) = Δy
  135. tan(angle) * Δx + 1/2 * g * Δx^2 / (V^2*cos(angle)^2) = Δy
  136. tan(angle) * Δx + 1/2 * g * (Δx^2 / V^2) * (1 + tan(angle)^2) = Δy
  137. 3、根据求根公式得出结论
  138. a = 1/2 * g * Δx^2 / V^2
  139. b = Δx
  140. c = a - Δy
  141. d = tan(angle) = (-b ± (b^2 - 4*a*c)^0.5) / (2*a)
  142. angle = atan(d)
  143. 有无根的判别式,有根则b^2-4*a*c>=0
  144. */
  145. void CalculateParabolaAngle(Vector3 destination)
  146. {
  147. float deltaX = Vector2.Distance(
  148. new Vector2(destination.x, destination.z),
  149. new Vector2(this.transform.position.x, this.transform.position.z)
  150. );
  151. float deltaY = destination.y - this.transform.position.y;
  152. float a = 0.5f * Physics.gravity.y * Mathf.Pow(deltaX, 2) / Mathf.Pow(this.mySpeed, 2);
  153. float b = deltaX;
  154. float c = a - deltaY;
  155. hasParabolaAngle = Mathf.Pow(b, 2) - 4 * a * c >= 0;
  156. if (hasParabolaAngle) {
  157. float res1 = (-b + Mathf.Pow(Mathf.Pow(b, 2) - 4*a*c, 0.5f)) / (2 * a);
  158. float res2 = (-b - Mathf.Pow(Mathf.Pow(b, 2) - 4*a*c, 0.5f)) / (2 * a);
  159. parabolaAngleInRadian = Mathf.Min(Mathf.Atan(res1), Mathf.Atan(res2));
  160. }
  161. }
  162. void OnDestroy() {
  163. GameMgr.ins.gameMode.ResumeTimeCounting(this);
  164. if (this.newRigidbody)
  165. {
  166. Destroy(this.newRigidbody);
  167. }
  168. }
  169. void FixedUpdate()
  170. {
  171. if (newRigidbody) {
  172. transform.forward = newRigidbody.velocity.normalized;
  173. }
  174. if (!isHit && flyTime >= 0) {
  175. flyTime += Time.deltaTime;
  176. if (flyTime > 14) {
  177. Destroy(gameObject);
  178. GameMgr.ins.gameMode.HitTarget(0);
  179. AudioMgr.ins.PlayCheer(false);
  180. nextShoot();
  181. }
  182. this.UpdateRotate();
  183. }
  184. // this.UpdateShake();
  185. }
  186. void Update() {
  187. UpdateFlyWithoutPhysics();
  188. }
  189. bool useUpdatePhysics = false;
  190. float flyTimeWithoutPhysics = 0;
  191. Vector3 finalPoint = default;
  192. /**更新箭的飞行轨迹(非Unity物理运算)*/
  193. void UpdateFlyWithoutPhysics() {
  194. if (!useUpdatePhysics || isHit) return;
  195. flyTimeWithoutPhysics += Time.deltaTime;
  196. Vector3 destination = finalPoint;
  197. float deltaX = Mathf.Sqrt(
  198. Mathf.Pow(destination.x - shootOutPosition.x, 2) +
  199. Mathf.Pow(destination.z - shootOutPosition.z, 2)
  200. );
  201. float vx = Mathf.Cos(parabolaAngleInRadian) * mySpeed;
  202. float t = deltaX / vx;
  203. if (flyTimeWithoutPhysics < t) {
  204. t = flyTimeWithoutPhysics;
  205. }
  206. float vy = Mathf.Sin(parabolaAngleInRadian) * mySpeed + Physics.gravity.y * t;
  207. float dy = Mathf.Sin(parabolaAngleInRadian) * mySpeed * t + 0.5f * Physics.gravity.y * Mathf.Pow(t, 2);
  208. float dx = vx * t;
  209. Vector3 nextPosition = new Vector3(destination.x - shootOutPosition.x, 0, destination.z - shootOutPosition.z);
  210. nextPosition = nextPosition.normalized * dx;
  211. nextPosition.y = shootOutPosition.y + dy;
  212. this.transform.position = nextPosition;
  213. Vector3 eulerAngles = this.transform.eulerAngles;
  214. float angleX = Mathf.Atan(vy / vx) / Mathf.PI * 180;
  215. eulerAngles.x = -angleX;
  216. this.transform.eulerAngles = eulerAngles;
  217. //箭头到达目标点时,触发碰撞(之所以用以下,是因为箭的锚点在尾部,而箭的长度接近1.1)
  218. if (Vector3.Distance(nextPosition, destination) < 1.1f) {
  219. this.transform.SetParent(TargetBody.ins.transform);
  220. TargetBody.ins.Hit(this, destination);
  221. }
  222. }
  223. public void Hit() {
  224. if (GameDebug.ins) GameDebug.ins.ShowRes(absoluteRay.point, this.getHeadPosition());
  225. gameObject.GetComponent<BoxCollider>().enabled = false;
  226. if (newRigidbody) {
  227. newRigidbody.useGravity = false;
  228. newRigidbody.velocity = Vector3.zero;
  229. Destroy(newRigidbody);
  230. newRigidbody = null;
  231. }
  232. isHit = true;
  233. // this.Shake();
  234. this.activeEffectCyclone(false);
  235. this.activeEffectBomb(true);
  236. this.activeEffectTrail(false);
  237. //最新一箭击中后会发光标记
  238. ArrowLightSick.RecoveryAll();
  239. this.GetComponentInChildren<ArrowLightSick>().Hit();
  240. }
  241. public Vector3 getHeadPosition() {
  242. return this.transform.Find("Head").position;
  243. }
  244. /**箭矢旋转 */
  245. private Vector3 rotateV3 = new Vector3(0, 0, 1400);
  246. private void UpdateRotate() {
  247. this.transform.Find("Head").Rotate(rotateV3 * Time.deltaTime, Space.Self);
  248. }
  249. /**箭矢震动 */
  250. private bool shaking = false;
  251. private float shakeTime = 0;
  252. private float shakeTimeMax = 0.5f;
  253. private float shakeRange = 5;
  254. private int shakeDirection = 1;
  255. private Vector3 shakeAngles = new Vector3();
  256. private void Shake()
  257. {
  258. this.shaking = true;
  259. this.shakeTime = 0;
  260. this.shakeDirection = 1;
  261. }
  262. private void UpdateShake()
  263. {
  264. if (!this.shaking) {
  265. return;
  266. }
  267. Transform transform = this.transform.Find("Head").transform;
  268. float shakeRangeNow = (1 - this.shakeTime / this.shakeTimeMax) * this.shakeRange;
  269. if (shakeRangeNow <= 0) {//震动结束
  270. this.shaking = false;
  271. this.shakeAngles.x = 0;
  272. } else {
  273. this.shakeAngles.x = -this.shakeDirection * shakeRangeNow;
  274. this.shakeDirection *= -1;
  275. }
  276. transform.localEulerAngles = this.shakeAngles;
  277. this.shakeTime += Time.deltaTime;
  278. }
  279. //进入下一轮射击
  280. public bool hasDoneNextShoot = false;
  281. public void nextShoot() {
  282. if (hasDoneNextShoot) return;
  283. hasDoneNextShoot = true;
  284. GameMgr.ins.gameMode.ResumeTimeCounting(this);
  285. if (!GameMgr.ins.gameMode.DoNextShoot()) return;
  286. if (AimHandler.ins) AimHandler.ins.BanControlObjRotate(false);
  287. this.armBow.readyShoot();
  288. //把瞄准点画成红圈,渲染在靶子上(取消)
  289. Transform redCircle = TargetBody.ins.transform.Find("RedCircle");
  290. redCircle.gameObject.SetActive(false);
  291. //最新一箭击中后会发光标记(取消)
  292. ArrowLightSick.RecoveryAll();
  293. }
  294. void OnCollisionEnter(Collision collision) {
  295. if ((1 << collision.gameObject.layer) != LayerMask.GetMask("Target"))
  296. {
  297. this.Hit();
  298. GameMgr.ins.gameMode.HitTarget(0);
  299. AudioMgr.ins.PlayCheer(false);
  300. }
  301. this.transform.SetParent(collision.transform.parent);
  302. }
  303. public void activeEffectCyclone(bool value)
  304. {
  305. this.transform.Find("Head/EF_kuosanquan").gameObject.SetActive(value);
  306. if (!value) return;
  307. ParticleSystemRenderer ps = this.transform.Find("Head/EF_kuosanquan/kuosan").GetComponent<ParticleSystemRenderer>();
  308. ParticleSystemRenderer ps1 = this.transform.Find("Head/EF_kuosanquan/kuosan (1)").GetComponent<ParticleSystemRenderer>();
  309. DOTween.To(() => ps.minParticleSize, value => {
  310. ps.minParticleSize = value;
  311. ps.maxParticleSize = value;
  312. }, 0.4f, 0.6f);
  313. DOTween.To(() => ps1.minParticleSize, value => {
  314. ps1.minParticleSize = value;
  315. ps1.maxParticleSize = value;
  316. }, 0.8f, 0.6f);
  317. }
  318. void activeEffectBomb(bool value)
  319. {
  320. this.transform.Find("Head/EF_baodian").gameObject.SetActive(value);
  321. }
  322. void activeEffectTrail(bool value)
  323. {
  324. this.transform.Find("EF_tuowei").gameObject.SetActive(value);
  325. this.transform.Find("EF_tuowei/Trail").GetComponent<TrailRenderer>().time = 1.6f / mySpeed;
  326. }
  327. }