Arrow.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using DG.Tweening;
  6. using UnityEngine.SceneManagement;
  7. /* 箭对象 */
  8. public class Arrow : MonoBehaviour
  9. {
  10. //飞行时间统计
  11. [NonSerialized] public float flyTime = 0;
  12. //标识—是否击中了什么
  13. [NonSerialized] public bool isHit = false;
  14. //箭的初速度(私用)
  15. [NonSerialized] public float mySpeed = 0;
  16. //箭的初速度(公用)
  17. public static float speed = GameMgr.RealSizeToGameSize(60);
  18. //箭射出时从弓传过来的参数
  19. #region
  20. //手臂弓
  21. [NonSerialized] public ArmBow armBow;
  22. //射出时的起始坐标
  23. [NonSerialized] public Vector3 shootOutPosition;
  24. //绝对射线
  25. [NonSerialized] public RaycastHit absoluteRay;
  26. //默认层级的射线对象
  27. [NonSerialized] public RaycastHit absoluteRayDef;
  28. //制作误差偏移角(强行制造误差,为了增加游戏难度,该偏移角的大小根据难度而定)
  29. [NonSerialized] public float offsetAngle;
  30. //误差偏移后的欧拉角
  31. [NonSerialized] public Vector3 finalAngleAfterOffset;
  32. #endregion
  33. //射线击中的靶子
  34. TargetBody rayHitTargetBody;
  35. //能够完美击中射线点
  36. bool canPerfectHit;
  37. //能否使用侧面镜头
  38. [NonSerialized] public bool canUseSideCamera;
  39. [NonSerialized] public bool canUserLockCamera;
  40. [NonSerialized] public ArrowCamera arrowCameraComp;
  41. public System.Action onDoNextShoot;
  42. public static HashSet<Arrow> arrowSet = new HashSet<Arrow>();
  43. private bool isInGameScene = false;
  44. private float currentSlowFactor = 1;
  45. void Awake()
  46. {
  47. arrowSet.Add(this);
  48. GameMgr.ins.gameMode.PauseTimeCounting(this);
  49. //箭模型平时属于ArmBow层,因为ArmBow层在飞行镜头中不被渲染,所以箭出去后要切换layer
  50. this.transform.Find("Head/_hunse_jian").gameObject.layer = 0;
  51. if (SceneManager.GetActiveScene().name == "Game" && GlobalData.MyDeviceMode == DeviceMode.Archery)
  52. {
  53. //经典射箭
  54. isInGameScene = true;
  55. currentSlowFactor = ArmBow.ins.slowFactor;
  56. }
  57. else {
  58. isInGameScene = false;
  59. }
  60. }
  61. void OnDestroy()
  62. {
  63. arrowSet.Remove(this);
  64. if (GameMgr.ins) GameMgr.ins.gameMode.ResumeTimeCounting(this);
  65. }
  66. void Start()
  67. {
  68. //speed = 23;
  69. mySpeed = speed;
  70. Billboard.ins?.SetArrowSpeed(speed);
  71. if (GameAssistUI.ins && GlobalData.MyDeviceMode == DeviceMode.Archery)
  72. {
  73. mySpeed *= GameAssistUI.ins.shootScaleValue;
  74. Billboard.ins?.SetArrowSpeedScale(GameAssistUI.ins.shootScaleValue);
  75. }
  76. Debug.Log($"Speed:{speed},mySpeed:{mySpeed},shootScaleValue :{GameAssistUI.ins.shootScaleValue}");
  77. Billboard.ins?.ShowSpeed();
  78. if (GlobalData.pkMatchType == PKMatchType.OnlinePK)
  79. {
  80. if (GameMgr.gameType == 9)
  81. {
  82. ((PKGameMode_OnlinePK)GameMgr.ins.gameMode).shootSpeedWillSend = Billboard.ins.GetShootSpeedText();
  83. }
  84. }
  85. if (absoluteRay.transform)
  86. {
  87. //记录射线有没有击中靶子
  88. rayHitTargetBody = absoluteRay.transform.GetComponent<TargetBody>();
  89. //把瞄准点画成红圈,渲染在靶子上
  90. if (rayHitTargetBody)
  91. {
  92. //如果是目标是靶子,就是启动靶子摄像头
  93. canUserLockCamera = true;
  94. //string typeStr = GlobalData.MyDeviceMode == DeviceMode.Archery? "RedCircle":"BulletCircle";
  95. Transform redCircle = rayHitTargetBody.transform.Find("RedCircle");
  96. redCircle.gameObject.SetActive(true);
  97. redCircle.transform.position = -redCircle.transform.forward * 0.001f + absoluteRay.point;
  98. //渲染弹坑
  99. if (GlobalData.MyDeviceMode == DeviceMode.Gun)
  100. {
  101. Transform crater = transform.Find("Crater");
  102. crater.rotation = redCircle.rotation;
  103. }
  104. }
  105. }
  106. //新的靶子场景不用这个SetUpBeforFly
  107. if (isInGameScene)
  108. {
  109. this.InitFlyLogic_Parabola();
  110. }
  111. else
  112. {
  113. SetUpBeforFly();
  114. }
  115. if (GlobalData.MyDeviceMode == DeviceMode.Archery)
  116. {
  117. Transform cameraTF = this.transform.Find("Camera");
  118. cameraTF.gameObject.SetActive(true);
  119. arrowCameraComp = cameraTF.gameObject.AddComponent<ArrowCamera>();
  120. arrowCameraComp.arrow = this;
  121. }
  122. this.activeEffectTrail(true);
  123. }
  124. /**在箭飞行前初始化&如果瞄准射线的落点在靶子上时,才调用该函数 */
  125. void SetUpBeforFly()
  126. {
  127. //基础角
  128. float baseAngleX = FormatAngleX(this.transform.eulerAngles.x);
  129. //最大可调整的角度差
  130. float maxDeltaAngleX = 5;
  131. //最终角
  132. float finalAngleX = baseAngleX;
  133. //额外偏移误差角
  134. float offsetAngleX = FormatAngleX(this.finalAngleAfterOffset.x) - baseAngleX;
  135. float offsetAngleY = FormatAngleY(this.finalAngleAfterOffset.y - this.transform.eulerAngles.y);
  136. if (DebugArrowOffsetAngle.ins)
  137. {
  138. offsetAngleX *= DebugArrowOffsetAngle.ins.GetOffsetScaleValue();
  139. offsetAngleY *= DebugArrowOffsetAngle.ins.GetOffsetScaleValue();
  140. }
  141. else
  142. {
  143. offsetAngleX *= this.armBow.shootOffsetAngleScale;
  144. offsetAngleY *= this.armBow.shootOffsetAngleScale;
  145. }
  146. DebugArrowOffsetAngle2.ins?.SetOffsetAngles(offsetAngleX, offsetAngleY);
  147. if (absoluteRay.transform)
  148. {
  149. bool plusOffsetAngleY = false;
  150. Debug.Log($"射出位置:{shootOutPosition} 目标位置:{absoluteRay.point} 名字: {absoluteRay.collider.name}");
  151. //看能否通过调整发射角让箭落在靶子的瞄准点上
  152. CalculateParabolaAngle(absoluteRay.point);
  153. //瞄准的是不是Target层
  154. bool isTargetLayer = IsTargetLayer(absoluteRay.transform.gameObject);
  155. //绝对发射角无解
  156. if (!hasParabolaAngle)
  157. {
  158. if (isTargetLayer) AimLoadChecker.ins?.ShowOutTip();
  159. }
  160. else
  161. {
  162. //来到这里,证明发射角有解,该解姑且叫它绝对角
  163. float absoluteAngleX = parabolaAngleInRadian / Mathf.PI * 180;
  164. //客户要求绝对角跟原本角度相差不能超过 maxDeltaAngleX
  165. float deltaAngleX = absoluteAngleX - baseAngleX;
  166. //如果绝对角跟原本角度相差不超过maxDeltaAngleX
  167. if (Mathf.Abs(deltaAngleX) < maxDeltaAngleX)
  168. {
  169. finalAngleX = Mathf.Clamp(absoluteAngleX + offsetAngleX, -89, 89);
  170. plusOffsetAngleY = true;
  171. if (Math.Abs(offsetAngle) < 0.001)
  172. {
  173. canPerfectHit = true;
  174. }
  175. if (rayHitTargetBody && Mathf.RoundToInt(rayHitTargetBody.GetDistance()) >= 50 && UnityEngine.Random.value < 0.5)
  176. {
  177. canUseSideCamera = true;
  178. }
  179. }
  180. else
  181. {
  182. finalAngleX = Mathf.Clamp(baseAngleX + maxDeltaAngleX, -89, 89);
  183. if (isTargetLayer) AimLoadChecker.ins?.ShowOutTip();
  184. }
  185. }
  186. finalPoint = absoluteRay.point;
  187. if (plusOffsetAngleY)
  188. {
  189. Vector3 myPos = this.transform.position;
  190. Vector3 pointer = finalPoint - myPos;
  191. pointer = Quaternion.AngleAxis(offsetAngleY, Vector3.up) * pointer;
  192. finalPoint = myPos + pointer;
  193. this.transform.LookAt(finalPoint);
  194. }
  195. }
  196. else
  197. {
  198. //以前默认的计算
  199. finalPoint = this.transform.position + this.transform.forward * 100;
  200. }
  201. parabolaAngleInRadian = finalAngleX / 180 * Mathf.PI;
  202. }
  203. /*
  204. 因为Unity引擎的规则,向上时359~270度,向下是0~90度
  205. 为了方便数学运算,换算成 向上时0~90度,向下是0~-90度
  206. */
  207. float FormatAngleX(float value)
  208. {
  209. if (value > 180) value = 360 - value;
  210. else value = -value;
  211. return value;
  212. }
  213. /**UnityY轴旋转只有0°~360°,两个Y轴旋转相减求得的夹角时会出现>180的情况,该函数就是为了解决这问题 */
  214. float FormatAngleY(float value)
  215. {
  216. if (Mathf.Abs(value) > 180)
  217. {
  218. if (value < 0)
  219. {
  220. return 360f + value;
  221. }
  222. if (value > 0)
  223. {
  224. return value - 360f;
  225. }
  226. }
  227. return value;
  228. }
  229. bool IsTargetLayer(GameObject gameObject)
  230. {
  231. return (1 << gameObject.layer) == LayerMask.GetMask("Target");
  232. }
  233. public Transform Head()
  234. {
  235. return this.transform.Find("Head");
  236. }
  237. /**发射角有无解 */
  238. bool hasParabolaAngle = false;
  239. /**发射角弧度解 */
  240. float parabolaAngleInRadian = 0;
  241. /**
  242. 求弓箭发射到指定坐标所需要的角度。
  243. 已知初速度大小V,重力g,起始坐标(a1,a2),目标坐标(b1,b2)。
  244. 解:
  245. 1、列出关系式
  246. Δx = b1 - a1;
  247. Δy = b2 - a2;
  248. Vx = V * cos(angle)
  249. Vy = V * sin(angle)
  250. Vy * t + 1/2 * g * t^2 = Δy
  251. Vx * t = Δx
  252. t = Δx / Vx
  253. 2、推导过程
  254. (V * sin(angle)) * Δx / (V * cos(angle)) + 1/2 * g * Δx^2 / (V^2*cos(angle)^2) = Δy
  255. tan(angle) * Δx + 1/2 * g * Δx^2 / (V^2*cos(angle)^2) = Δy
  256. tan(angle) * Δx + 1/2 * g * (Δx^2 / V^2) * (1 + tan(angle)^2) = Δy
  257. 3、根据求根公式得出结论
  258. a = 1/2 * g * Δx^2 / V^2
  259. b = Δx
  260. c = a - Δy
  261. d = tan(angle) = (-b ± (b^2 - 4*a*c)^0.5) / (2*a)
  262. angle = atan(d)
  263. 有无根的判别式,有根则b^2-4*a*c>=0
  264. */
  265. void CalculateParabolaAngle(Vector3 destination)
  266. {
  267. float deltaX = Vector2.Distance(
  268. new Vector2(destination.x, destination.z),
  269. new Vector2(this.transform.position.x, this.transform.position.z)
  270. );
  271. float deltaY = destination.y - this.transform.position.y;
  272. float a = 0.5f * Physics.gravity.y * Mathf.Pow(deltaX, 2) / Mathf.Pow(this.mySpeed, 2);
  273. float b = deltaX;
  274. float c = a - deltaY;
  275. hasParabolaAngle = Mathf.Pow(b, 2) - 4 * a * c >= 0;
  276. //Debug.Log("是否有弧度解:" + hasParabolaAngle);
  277. if (hasParabolaAngle)
  278. {
  279. float res1 = (-b + Mathf.Pow(Mathf.Pow(b, 2) - 4 * a * c, 0.5f)) / (2 * a);
  280. float res2 = (-b - Mathf.Pow(Mathf.Pow(b, 2) - 4 * a * c, 0.5f)) / (2 * a);
  281. //parabolaAngleInRadian = Mathf.Min(Mathf.Atan(res1), Mathf.Atan(res2));
  282. // 两个解分别代表低抛角和高抛角
  283. float angle1 = Mathf.Atan(res1); // 弧度
  284. float angle2 = Mathf.Atan(res2);
  285. // 这里决定是取低角度还是高角度
  286. parabolaAngleInRadian = Mathf.Min(angle1, angle2);
  287. //parabolaAngleInRadian = Mathf.Max(angle1, angle2); // 高抛解
  288. }
  289. }
  290. void FixedUpdate()
  291. {
  292. if (!isHit && flyTime >= 0)
  293. {
  294. flyTime += Time.deltaTime;
  295. if (flyTime > 14)
  296. {
  297. Debug.Log("flyTime:"+ flyTime);
  298. FlyTimeOut();
  299. }
  300. // this.UpdateRotate();
  301. }
  302. }
  303. public ArrowSync.SyncData outputSyncData;
  304. void Update()
  305. {
  306. if (isInGameScene)
  307. UpdateFlyLogic_Parabola();
  308. else
  309. UpdateFlyLogic();
  310. if (GlobalData.pkMatchType == PKMatchType.OnlinePK)
  311. {
  312. if (outputSyncData == null) outputSyncData = new ArrowSync.SyncData();
  313. outputSyncData.SetData(this);
  314. }
  315. }
  316. IEnumerator delayFlyTimeOut() {
  317. yield return new WaitForSeconds(0.8f);//gun_shoot 音频长度
  318. FlyTimeOut();
  319. }
  320. void FlyTimeOut()
  321. {
  322. if (arrowCameraComp != null)
  323. {
  324. var targetLock = arrowCameraComp.arrowCameraTemplate as ArrowCameraTemplate_targetLock;
  325. if (targetLock != null && !targetLock.IsDeleting)
  326. {
  327. // 删除弓箭相机,因为Lock目标靶的情况下 会脱离弓箭
  328. Destroy(arrowCameraComp.gameObject);
  329. }
  330. }
  331. Destroy(gameObject);
  332. GameMgr.ins.gameMode.HitTarget(0);
  333. AudioMgr.ins.PlayCheer(false);
  334. nextShoot();
  335. }
  336. float logicFlyTime = 0;
  337. Vector3 finalPoint;
  338. //飞行速度可在过程中变化,比如快到目标时突然加速
  339. public AnimationCurve speedCurve = AnimationCurve.Linear(0, 1, 1, 1.5f);
  340. /**飞行帧逻辑(运动学) */
  341. void UpdateFlyLogic()
  342. {
  343. if (isHit) return;
  344. logicFlyTime += Time.deltaTime;
  345. Vector3 destination = finalPoint;
  346. float vx = Mathf.Cos(parabolaAngleInRadian) * mySpeed;
  347. float t = logicFlyTime;
  348. float vy = Mathf.Sin(parabolaAngleInRadian) * mySpeed + Physics.gravity.y * t;
  349. float dy = Mathf.Sin(parabolaAngleInRadian) * mySpeed * t + 0.5f * Physics.gravity.y * Mathf.Pow(t, 2);
  350. float dx = vx * t;
  351. Vector3 nextPosition = new Vector3(destination.x - shootOutPosition.x, 0, destination.z - shootOutPosition.z);
  352. nextPosition = nextPosition.normalized * dx;
  353. nextPosition.y = dy;
  354. nextPosition = shootOutPosition + nextPosition;
  355. Vector3 oldPosition = this.transform.position;
  356. this.transform.position = nextPosition;
  357. Vector3 eulerAngles = this.transform.eulerAngles;
  358. float angleX = Mathf.Atan(vy / vx) / Mathf.PI * 180;
  359. eulerAngles.x = -angleX;
  360. this.transform.eulerAngles = eulerAngles;
  361. float deltaDistance = Vector3.Distance(oldPosition, nextPosition);
  362. Ray ray = new Ray(oldPosition, nextPosition - oldPosition);
  363. RaycastHit raycastHit;
  364. bool raycastResult = Physics.Raycast(ray, out raycastHit, deltaDistance);
  365. if (raycastResult)
  366. {
  367. this.transform.position = raycastHit.point;
  368. if (ArrowTraceDebug.ins) ArrowTraceDebug.ins.OnArrowUpdate(this);
  369. OnHitAnyInFlyLogic(raycastHit);
  370. }
  371. else
  372. {
  373. if (ArrowTraceDebug.ins) ArrowTraceDebug.ins.OnArrowUpdate(this);
  374. }
  375. }
  376. #region 新飞行帧逻辑 (非运动学)
  377. /** 经典射击里面的 新飞行帧逻辑(非运动学) */
  378. public float minFlyDuration = 0.2f; // 最短飞行时间,受slowFactor影响
  379. public float maxFlyDuration = 2.0f; // 最长飞行时间,受slowFactor影响
  380. public float maxAirTime = 3f; // 最大允许飞行时间,超过这个时间即加上重力
  381. public float minHeight = 0.3f; // 最近时的最小弧度
  382. public float maxHeightFactor = 1f; // 最远时的最大弧度倍数
  383. public float flyDuration; // 自动计算后的飞行时长
  384. public float maxHeight; // 自动计算后的抛物线高度
  385. private float flyTimer = 0f;
  386. public float arrowLength = 1.7f; // 箭长度
  387. private bool isFalling = false;
  388. /// <summary>
  389. /// 初始化飞行参数(在发射时调用一次)
  390. /// </summary>
  391. public void InitFlyLogic_Parabola()
  392. {
  393. //获取终点
  394. if (absoluteRay.transform)
  395. {
  396. if (Math.Abs(offsetAngle) < 0.001)
  397. {
  398. canPerfectHit = true;
  399. }
  400. if (rayHitTargetBody && Mathf.RoundToInt(rayHitTargetBody.GetDistance()) >= 50 && UnityEngine.Random.value < 0.5)
  401. {
  402. canUseSideCamera = true;
  403. }
  404. finalPoint = absoluteRay.point;
  405. }
  406. else
  407. {
  408. if (absoluteRayDef.transform)
  409. {
  410. finalPoint = absoluteRayDef.point;
  411. }
  412. else
  413. {
  414. //如果没有给个以前默认的计算
  415. finalPoint = this.transform.position + this.transform.forward * 100;
  416. }
  417. }
  418. // 目标距离
  419. float dist = Vector3.Distance(shootOutPosition, finalPoint);
  420. // 自动计算飞行时长
  421. //flyDuration = Mathf.Clamp(dist / mySpeed, minFlyDuration, maxFlyDuration);
  422. //Debug.Log("dist / mySpeed:" + (dist / mySpeed));
  423. // 超长飞行时间 → 把终点调整到靶子前面,让箭自然落地
  424. float flyTime = dist / mySpeed;
  425. if (flyTime <= maxFlyDuration)
  426. {
  427. // 正常情况
  428. flyDuration = flyTime;
  429. }
  430. else
  431. {
  432. // 超过最大飞行时间 → 缩短水平距离
  433. flyDuration = maxFlyDuration;
  434. // 缩短比例 = maxFlyDuration / flyTime
  435. float ratio = maxFlyDuration / flyTime;
  436. // 新终点 = 起点 + ratio * (原终点 - 起点)
  437. this.finalPoint = shootOutPosition + (finalPoint - shootOutPosition) * ratio;
  438. this.isFalling = true;
  439. //
  440. currentSlowFactor = ArmBow.ins.notSlowFactor;
  441. //这里飞不到的时候给个false,反正弓箭最后跳回瞄准点
  442. //OnHitAnyInFlyLogic 之后
  443. canPerfectHit = false;
  444. //给个加速卡提示?
  445. AimLoadChecker.ins?.ShowOutTip();
  446. }
  447. // 自动计算抛物线弧度(远的高一点,近的低一点)
  448. maxHeight = Mathf.Lerp(minHeight, maxHeightFactor, dist / 70f); // 假设70米是最大距离
  449. flyTimer = 0f;
  450. isHit = false;
  451. // 起始位置
  452. transform.position = shootOutPosition;
  453. }
  454. /// <summary>
  455. /// 带抛物线的飞行逻辑
  456. /// </summary>
  457. void UpdateFlyLogic_Parabola()
  458. {
  459. if (isHit) return;
  460. // 时间推进(整体慢速因子)
  461. flyTimer += Time.deltaTime * currentSlowFactor;
  462. // 线性进度
  463. float rawProgress = Mathf.Clamp01(flyTimer / flyDuration);
  464. // 曲线控制速度分布(0~1)
  465. float curveValue = ArmBow.ins.customCurveArrow.Evaluate(rawProgress);
  466. // 最终进度 = 曲线值(保证能跑到 1)
  467. float progress = Mathf.Clamp01(curveValue);
  468. // --- 水平位置插值 ---
  469. Vector3 horizontalPos = Vector3.Lerp(shootOutPosition, finalPoint, progress);
  470. // --- 抛物线高度 ---
  471. float parabola = 4 * progress * (1 - progress); // 标准0~1抛物线
  472. float heightOffset = parabola * maxHeight;
  473. Vector3 nextPos = new Vector3(
  474. horizontalPos.x,
  475. Mathf.Lerp(shootOutPosition.y, finalPoint.y, progress) + heightOffset,
  476. horizontalPos.z
  477. );
  478. // 移动
  479. Vector3 oldPos = transform.position;
  480. // --- 最终位置 ---
  481. if (this.isFalling && progress == 1)
  482. {
  483. //这个状态到了,加个持续飞行,知道碰撞到停止?
  484. // 当前状态:到达缩短终点,但需要持续飞行
  485. Vector3 forwardMove = transform.forward * mySpeed *2 * Time.deltaTime * currentSlowFactor;
  486. nextPos = transform.position + forwardMove;
  487. }
  488. transform.position = nextPos;
  489. // 箭头朝向(指向移动方向)
  490. Vector3 moveDir = (nextPos - oldPos).normalized;
  491. if (moveDir.sqrMagnitude > 0.0001f)
  492. transform.forward = moveDir;
  493. // 箭尖位置(保证碰撞从箭尖检测)
  494. Vector3 tipOffset = transform.forward * arrowLength * 0.5f;
  495. Vector3 oldTip = oldPos + tipOffset;
  496. Vector3 newTip = nextPos + tipOffset;
  497. // 轨迹调试线
  498. Debug.DrawLine(shootOutPosition, nextPos, Color.cyan, 2f);
  499. Debug.DrawLine(oldTip, newTip, Color.green, 0.1f);
  500. // 碰撞检测
  501. if (Physics.Linecast(oldTip, newTip, out RaycastHit hit))
  502. {
  503. transform.position = hit.point - tipOffset; // 调整箭模型,使箭尖贴在命中点
  504. Debug.DrawLine(oldTip, hit.point, Color.red, 2f);
  505. if (ArrowTraceDebug.ins) ArrowTraceDebug.ins.OnArrowUpdate(this);
  506. OnHitAnyInFlyLogic(hit);
  507. this.isFalling = false;
  508. }
  509. else
  510. {
  511. if (ArrowTraceDebug.ins) ArrowTraceDebug.ins.OnArrowUpdate(this);
  512. }
  513. }
  514. #endregion
  515. public class HitType
  516. {
  517. public static int None = 0;
  518. public static int TargetInRing = 1; //击中了靶子,且在得分环内
  519. public static int TargetOutRing = 2; //击中了靶子,但不在得分环内
  520. public static int NotTarget = 4; //击中非目标对象
  521. public static int Animal = 5; //击中动物
  522. }
  523. [NonSerialized] public int hitType = HitType.None;
  524. [NonSerialized] public Transform raycastHitTransform; //射线击中的目标变换
  525. [NonSerialized] public string[] hitTargetAnimalInfo;
  526. //飞行逻辑中检测到碰撞
  527. void OnHitAnyInFlyLogic(RaycastHit raycastHit)
  528. {
  529. string targetName = raycastHit.transform.gameObject.name;
  530. this.Head().position = raycastHit.point;
  531. //命中靶子的情况下不移动这个parent,不然弓箭位置旋转会被影响
  532. //其他情况照常
  533. if (targetName != "TargetBody") {
  534. this.transform.SetParent(raycastHit.transform.parent);
  535. }
  536. this.raycastHitTransform = raycastHit.transform;
  537. if (targetName == "TargetBody")
  538. {
  539. Vector3 hitPoint = raycastHit.point;
  540. if (rayHitTargetBody && canPerfectHit)
  541. {
  542. hitPoint = absoluteRay.point;
  543. this.Head().position = hitPoint;
  544. }
  545. if (GlobalData.MyDeviceMode == DeviceMode.Archery)
  546. this.arrowCameraComp.arrowCameraTemplate?.beforeHit();
  547. else
  548. nextShootFromType();//子弹直接下一轮
  549. raycastHit.transform.GetComponent<TargetBody>().Hit(this, hitPoint);
  550. }
  551. else if (targetName.StartsWith("TargetAnimalPart"))
  552. {
  553. hitType = HitType.Animal;
  554. Vector3 hitPoint = raycastHit.point;
  555. string partName = targetName.Split(new char[] { '_' })[1];
  556. if (GlobalData.MyDeviceMode == DeviceMode.Archery)
  557. this.arrowCameraComp.arrowCameraTemplate?.beforeHit();
  558. else
  559. nextShootFromType();//子弹直接下一轮
  560. TargetAnimal targetAnimal = raycastHit.transform.GetComponentInParent<TargetAnimal>();
  561. targetAnimal.OnHit(this, hitPoint, partName);
  562. //箭击中的音效
  563. AudioMgr.ins.PlayArrowEnter();
  564. //记录击中的部位和动物ID
  565. hitTargetAnimalInfo = new string[] {
  566. targetAnimal.GetOnlineID().ToString(),
  567. targetAnimal.targetAnimalParts.IndexOf(raycastHitTransform).ToString(),
  568. SyncDataUtil.Vec3ToStr(transform.localPosition),
  569. SyncDataUtil.QuatToStr(transform.localRotation),
  570. SyncDataUtil.Vec3ToStr(transform.position),
  571. SyncDataUtil.QuatToStr(transform.rotation)
  572. };
  573. }
  574. else if (raycastHit.transform.GetComponent<TargetOutBound>())
  575. { //撞到空气墙当作超时处理
  576. if (GlobalData.MyDeviceMode == DeviceMode.Archery)
  577. FlyTimeOut();
  578. else
  579. StartCoroutine(delayFlyTimeOut());
  580. }
  581. else
  582. {
  583. if (GlobalData.MyDeviceMode == DeviceMode.Archery)
  584. this.arrowCameraComp.arrowCameraTemplate?.beforeHit();
  585. else
  586. nextShootFromType();//子弹直接下一轮
  587. Hit();
  588. GameMgr.ins.gameMode.HitTarget(0);
  589. //击中其它东西时的音效
  590. hitType = HitType.NotTarget;
  591. if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == "Game")
  592. {
  593. AudioMgr.ins.PlayCheer(false);
  594. }
  595. else
  596. {
  597. if (CommonConfig.StandaloneModeOrPlatformB) {
  598. AudioMgr.ins.PlayCheer(false);
  599. } else {
  600. AudioMgr.ins.PlayArrowEnter();
  601. }
  602. }
  603. }
  604. }
  605. public void Hit()
  606. {
  607. isHit = true;
  608. if (GameDebug.ins) GameDebug.ins.ShowRes(absoluteRay.point, this.Head().position);
  609. //控制箭的特效显示
  610. this.activeEffectCyclone(false);
  611. this.activeEffectBomb(true);
  612. this.activeEffectTrail(false);
  613. //最新一箭击中后会发光标记
  614. ArrowLightSick.RecoveryAll();
  615. this.GetComponentInChildren<ArrowLightSick>().Hit();
  616. }
  617. //进入下一轮射击
  618. [NonSerialized] public bool hasDoneNextShoot = false;
  619. public void nextShootFromType() {
  620. // Debug.Log("nextShootFromType:" + GlobalDataTemp.pkMatchType);
  621. if (GlobalDataTemp.pkMatchType == PKMatchType.LocalPK || GlobalDataTemp.pkMatchType == PKMatchType.OnlinePK)
  622. {
  623. StartCoroutine(delayNectShoot());
  624. }
  625. else {
  626. nextShoot();
  627. }
  628. }
  629. IEnumerator delayNectShoot() {
  630. yield return new WaitForSeconds(1.0f);
  631. nextShoot();
  632. }
  633. public void nextShoot()
  634. {
  635. if (hasDoneNextShoot) return;
  636. hasDoneNextShoot = true;
  637. GameMgr.ins.gameMode.ResumeTimeCounting(this);
  638. onDoNextShoot?.Invoke();
  639. try
  640. {
  641. //把瞄准点画成红圈,渲染在靶子上(取消)
  642. if (rayHitTargetBody)
  643. {
  644. //string typeStr = GlobalData.MyDeviceMode == DeviceMode.Archery ? "RedCircle" : "BulletCircle";
  645. Transform redCircle = rayHitTargetBody.transform.Find("RedCircle");
  646. redCircle.gameObject.SetActive(false);
  647. }
  648. //最新一箭击中后会发光标记(取消)
  649. ArrowLightSick.RecoveryAll();
  650. }
  651. catch (System.Exception e) { Debug.LogError(e.Message + "\n" + e.StackTrace); }
  652. if (!GameMgr.ins.gameMode.DoNextShoot()) return;
  653. this.armBow.readyShoot();
  654. }
  655. //---------箭矢旋转--------
  656. Vector3 rotateV3 = new Vector3(0, 0, 1400);
  657. void UpdateRotate()
  658. {
  659. this.Head().Rotate(rotateV3 * Time.deltaTime, Space.Self);
  660. }
  661. //---------箭矢特效---------
  662. public void activeEffectCyclone(bool value)
  663. {
  664. this.transform.Find("Head/EF_kuosanquan").gameObject.SetActive(value);
  665. if (!value) return;
  666. ParticleSystemRenderer ps = this.transform.Find("Head/EF_kuosanquan/kuosan").GetComponent<ParticleSystemRenderer>();
  667. ParticleSystemRenderer ps1 = this.transform.Find("Head/EF_kuosanquan/kuosan (1)").GetComponent<ParticleSystemRenderer>();
  668. DOTween.To(() => ps.minParticleSize, value =>
  669. {
  670. ps.minParticleSize = value;
  671. ps.maxParticleSize = value;
  672. }, 0.4f, 0.6f);
  673. DOTween.To(() => ps1.minParticleSize, value =>
  674. {
  675. ps1.minParticleSize = value;
  676. ps1.maxParticleSize = value;
  677. }, 0.8f, 0.6f);
  678. }
  679. void activeEffectBomb(bool value)
  680. {
  681. this.transform.Find("Head/EF_baodian").gameObject.SetActive(value);
  682. }
  683. void activeEffectTrail(bool value)
  684. {
  685. if (GlobalData.MyDeviceMode == DeviceMode.Archery)
  686. {
  687. Transform guadiantuowei = this.transform.Find("guadiantuowei");
  688. if (guadiantuowei == null)
  689. {
  690. this.transform.Find("EF_tuowei").gameObject.SetActive(value);
  691. this.transform.Find("EF_tuowei/Trail").GetComponent<TrailRenderer>().time = 1.6f / mySpeed;
  692. }
  693. else {
  694. guadiantuowei.gameObject.SetActive(value);
  695. }
  696. }
  697. else {
  698. StartCoroutine(showTrail(value));
  699. }
  700. }
  701. IEnumerator showTrail(bool value) {
  702. this.transform.Find("EF_tuowei_bullet").gameObject.SetActive(value);
  703. //this.transform.Find("EF_tuowei_bullet/Trail").GetComponent<TrailRenderer>().time = 0;
  704. yield return new WaitForEndOfFrame();
  705. this.transform.Find("EF_tuowei_bullet/GB_flame_FX_1").gameObject.SetActive(true);
  706. //this.transform.Find("EF_tuowei_bullet/Trail").GetComponent<TrailRenderer>().time = 0.02f;
  707. //this.transform.Find("EF_tuowei_bullet/Trail").GetComponent<TrailRenderer>().time = 1.6f / mySpeed;
  708. }
  709. }