using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; /* 箭对象 */ public class ArrowNew2 : MonoBehaviour { //飞行时间统计 [NonSerialized] public float flyTime = 0; //标识—是否击中了什么 [NonSerialized] public bool isHit = false; //箭的初速度(私用) [NonSerialized] public float mySpeed = 0; //箭的初速度(公用) public static float speed = GameMgr.RealSizeToGameSize(60); //箭射出时从弓传过来的参数 #region //手臂弓 [NonSerialized] public ArmBowDoublePlayer armBow; //射出时的起始坐标 [NonSerialized] public Vector3 shootOutPosition; //绝对射线 [NonSerialized] public RaycastHit absoluteRay; //制作误差偏移角(强行制造误差,为了增加游戏难度,该偏移角的大小根据难度而定) [NonSerialized] public float offsetAngle; //误差偏移后的欧拉角 [NonSerialized] public Vector3 finalAngleAfterOffset; #endregion //射线击中的靶子 TargetBodyNew rayHitTargetBody; //能够完美击中射线点 bool canPerfectHit; //能否使用侧面镜头 [NonSerialized] public bool canUseSideCamera; [NonSerialized] public ArrowCameraNew arrowCameraComp; public System.Action onDoNextShoot; public static HashSet arrowSet = new HashSet(); public int playerIndex; void Awake() { arrowSet.Add(this); //GameMgr.ins.gameMode.PauseTimeCounting(this); //箭模型平时属于ArmBow层,因为ArmBow层在飞行镜头中不被渲染,所以箭出去后要切换layer //this.transform.Find("Head/_hunse_jian").gameObject.layer = 0; } void OnDestroy() { arrowSet.Remove(this); //if (GameMgr.ins) GameMgr.ins.gameMode.ResumeTimeCounting(this); } void Start() { mySpeed = speed; if (playerIndex == 0) { mySpeed *= GameAssistUIDoublePlayer.ins.shootScaleValue; Billboard.ins?.SetArrowSpeed(speed); Billboard.ins?.SetArrowSpeedScale(GameAssistUIDoublePlayer.ins.shootScaleValue); Billboard.ins?.ShowSpeed(); } else if (playerIndex == 1) { mySpeed *= GameAssistUIDoublePlayer.ins.shootScaleValue2P; Billboard.ins?.Second_SetArrowSpeed(speed); Billboard.ins?.Second_SetArrowSpeedScale(GameAssistUIDoublePlayer.ins.shootScaleValue2P); Billboard.ins?.Second_ShowSpeed(); } Debug.Log("playerIndex:"+ playerIndex+",当前箭的速度是:" + mySpeed); if (absoluteRay.transform) { //记录射线有没有击中靶子 rayHitTargetBody = absoluteRay.transform.GetComponent(); //把瞄准点画成红圈,渲染在靶子上 if (rayHitTargetBody) { Transform redCircle = rayHitTargetBody.transform.Find("RedCircle"); redCircle.gameObject.SetActive(true); redCircle.transform.position = -redCircle.transform.forward * 0.001f + absoluteRay.point; } } SetUpBeforFly(); if (GlobalData.MyDeviceMode == DeviceMode.Archery) { //跟随摄像机 Transform cameraTF = this.transform.Find("Camera"); cameraTF.gameObject.SetActive(true); arrowCameraComp = cameraTF.gameObject.AddComponent().setInitData(this, playerIndex); } this.activeEffectTrail(true); } /**在箭飞行前初始化&如果瞄准射线的落点在靶子上时,才调用该函数 */ void SetUpBeforFly() { //基础角 float baseAngleX = FormatAngleX(this.transform.eulerAngles.x); //最大可调整的角度差 float maxDeltaAngleX = 5; //最终角 float finalAngleX = baseAngleX; //额外偏移误差角 float offsetAngleX = FormatAngleX(this.finalAngleAfterOffset.x) - baseAngleX; float offsetAngleY = FormatAngleY(this.finalAngleAfterOffset.y - this.transform.eulerAngles.y); if (DebugArrowOffsetAngle.ins) { offsetAngleX *= DebugArrowOffsetAngle.ins.GetOffsetScaleValue(); offsetAngleY *= DebugArrowOffsetAngle.ins.GetOffsetScaleValue(); } else { offsetAngleX *= this.armBow.shootOffsetAngleScale; offsetAngleY *= this.armBow.shootOffsetAngleScale; } DebugArrowOffsetAngle2.ins?.SetOffsetAngles(offsetAngleX, offsetAngleY); if (absoluteRay.transform) { bool plusOffsetAngleY = false; //看能否通过调整发射角让箭落在靶子的瞄准点上 CalculateParabolaAngle(absoluteRay.point); //瞄准的是不是Target层 bool isTargetLayer = IsTargetLayer(absoluteRay.transform.gameObject); Debug.Log("isTargetLayer:"+ isTargetLayer); //绝对发射角无解 if (!hasParabolaAngle) { if (isTargetLayer) AimLoadChecker.ins?.ShowOutTip(); } else { //来到这里,证明发射角有解,该解姑且叫它绝对角 float absoluteAngleX = parabolaAngleInRadian / Mathf.PI * 180; //客户要求绝对角跟原本角度相差不能超过 maxDeltaAngleX float deltaAngleX = absoluteAngleX - baseAngleX; //如果绝对角跟原本角度相差不超过maxDeltaAngleX if (Mathf.Abs(deltaAngleX) < maxDeltaAngleX) { finalAngleX = Mathf.Clamp(absoluteAngleX + offsetAngleX, -89, 89); plusOffsetAngleY = true; if (Math.Abs(offsetAngle) < 0.001) { canPerfectHit = true; } if (rayHitTargetBody && Mathf.RoundToInt(rayHitTargetBody.GetDistance()) >= 50 && UnityEngine.Random.value < 0.5) { canUseSideCamera = true; } } else { finalAngleX = Mathf.Clamp(baseAngleX + maxDeltaAngleX, -89, 89); if (isTargetLayer) AimLoadChecker.ins?.ShowOutTip(); } } finalPoint = absoluteRay.point; if (plusOffsetAngleY) { Vector3 myPos = this.transform.position; Vector3 pointer = finalPoint - myPos; pointer = Quaternion.AngleAxis(offsetAngleY, Vector3.up) * pointer; finalPoint = myPos + pointer; this.transform.LookAt(finalPoint); } } else { finalPoint = this.transform.position + this.transform.forward * 100; } parabolaAngleInRadian = finalAngleX / 180 * Mathf.PI; } /* 因为Unity引擎的规则,向上时359~270度,向下是0~90度 为了方便数学运算,换算成 向上时0~90度,向下是0~-90度 */ float FormatAngleX(float value) { if (value > 180) value = 360 - value; else value = -value; return value; } /**UnityY轴旋转只有0°~360°,两个Y轴旋转相减求得的夹角时会出现>180的情况,该函数就是为了解决这问题 */ float FormatAngleY(float value) { if (Mathf.Abs(value) > 180) { if (value < 0) { return 360f + value; } if (value > 0) { return value - 360f; } } return value; } bool IsTargetLayer(GameObject gameObject) { if (playerIndex == 0) { return (1 << gameObject.layer) == LayerMask.GetMask("Target1"); } else if (playerIndex == 1) { return (1 << gameObject.layer) == LayerMask.GetMask("Target2"); } else { return (1 << gameObject.layer) == LayerMask.GetMask("Target"); } } public Transform Head() { return this.transform.Find("Head"); } /**发射角有无解 */ bool hasParabolaAngle = false; /**发射角弧度解 */ float parabolaAngleInRadian = 0; /** 求弓箭发射到指定坐标所需要的角度。 已知初速度大小V,重力g,起始坐标(a1,a2),目标坐标(b1,b2)。 解: 1、列出关系式 Δx = b1 - a1; Δy = b2 - a2; Vx = V * cos(angle) Vy = V * sin(angle) Vy * t + 1/2 * g * t^2 = Δy Vx * t = Δx t = Δx / Vx 2、推导过程 (V * sin(angle)) * Δx / (V * cos(angle)) + 1/2 * g * Δx^2 / (V^2*cos(angle)^2) = Δy tan(angle) * Δx + 1/2 * g * Δx^2 / (V^2*cos(angle)^2) = Δy tan(angle) * Δx + 1/2 * g * (Δx^2 / V^2) * (1 + tan(angle)^2) = Δy 3、根据求根公式得出结论 a = 1/2 * g * Δx^2 / V^2 b = Δx c = a - Δy d = tan(angle) = (-b ± (b^2 - 4*a*c)^0.5) / (2*a) angle = atan(d) 有无根的判别式,有根则b^2-4*a*c>=0 */ void CalculateParabolaAngle(Vector3 destination) { float deltaX = Vector2.Distance( new Vector2(destination.x, destination.z), new Vector2(this.transform.position.x, this.transform.position.z) ); float deltaY = destination.y - this.transform.position.y; float a = 0.5f * Physics.gravity.y * Mathf.Pow(deltaX, 2) / Mathf.Pow(this.mySpeed, 2); float b = deltaX; float c = a - deltaY; hasParabolaAngle = Mathf.Pow(b, 2) - 4 * a * c >= 0; if (hasParabolaAngle) { float res1 = (-b + Mathf.Pow(Mathf.Pow(b, 2) - 4 * a * c, 0.5f)) / (2 * a); float res2 = (-b - Mathf.Pow(Mathf.Pow(b, 2) - 4 * a * c, 0.5f)) / (2 * a); parabolaAngleInRadian = Mathf.Min(Mathf.Atan(res1), Mathf.Atan(res2)); } } void FixedUpdate() { if (!isHit && flyTime >= 0) { flyTime += Time.deltaTime; if (flyTime > 14) { FlyTimeOut(); } // this.UpdateRotate(); } } public ArrowSync.SyncData outputSyncData; void Update() { UpdateFlyLogic(); } IEnumerator delayFlyTimeOut() { yield return new WaitForSeconds(0.8f);//gun_shoot 音频长度 FlyTimeOut(); } void FlyTimeOut() { Destroy(gameObject); GameController.ins.HitTarget(0); AudioMgr.ins.PlayCheer(false); nextShoot(); } float logicFlyTime = 0; Vector3 finalPoint; /**飞行帧逻辑(运动学) */ void UpdateFlyLogic() { if (isHit) return; logicFlyTime += Time.deltaTime; Vector3 destination = finalPoint; float vx = Mathf.Cos(parabolaAngleInRadian) * mySpeed; float t = logicFlyTime; float vy = Mathf.Sin(parabolaAngleInRadian) * mySpeed + Physics.gravity.y * t; float dy = Mathf.Sin(parabolaAngleInRadian) * mySpeed * t + 0.5f * Physics.gravity.y * Mathf.Pow(t, 2); float dx = vx * t; Vector3 nextPosition = new Vector3(destination.x - shootOutPosition.x, 0, destination.z - shootOutPosition.z); nextPosition = nextPosition.normalized * dx; nextPosition.y = dy; nextPosition = shootOutPosition + nextPosition; Vector3 oldPosition = this.transform.position; this.transform.position = nextPosition; Vector3 eulerAngles = this.transform.eulerAngles; float angleX = Mathf.Atan(vy / vx) / Mathf.PI * 180; eulerAngles.x = -angleX; this.transform.eulerAngles = eulerAngles; float deltaDistance = Vector3.Distance(oldPosition, nextPosition); Ray ray = new Ray(oldPosition, nextPosition - oldPosition); RaycastHit raycastHit; //检测对应的target1和target2层级 , 外围墙壁碰撞 ,default层 int _ignoreMask = playerIndex == 0 ? (1 << 6 | 1 << 19 | 1 << 0 ) : (1 << 7 | 1 << 19 | 1 << 0); //Debug.Log(playerIndex+"_ignoreMask:" + _ignoreMask); bool raycastResult = Physics.Raycast(ray, out raycastHit, deltaDistance, _ignoreMask); if (raycastResult) { this.transform.position = raycastHit.point; //if (ArrowTraceDebug.ins) ArrowTraceDebug.ins.OnArrowUpdate(this); OnHitAnyInFlyLogic(raycastHit); } else { // if (ArrowTraceDebug.ins) ArrowTraceDebug.ins.OnArrowUpdate(this); } } public class HitType { public static int None = 0; public static int TargetInRing = 1; //击中了靶子,且在得分环内 public static int TargetOutRing = 2; //击中了靶子,但不在得分环内 public static int NotTarget = 4; //击中非目标对象 public static int Animal = 5; //击中动物 } [NonSerialized] public int hitType = HitType.None; [NonSerialized] public Transform raycastHitTransform; //射线击中的目标变换 [NonSerialized] public string[] hitTargetAnimalInfo; //飞行逻辑中检测到碰撞 void OnHitAnyInFlyLogic(RaycastHit raycastHit) { this.Head().position = raycastHit.point; this.transform.SetParent(raycastHit.transform.parent); string targetName = raycastHit.transform.gameObject.name; this.raycastHitTransform = raycastHit.transform; if (targetName == "TargetBody") { Vector3 hitPoint = raycastHit.point; if (rayHitTargetBody && canPerfectHit) { hitPoint = absoluteRay.point; this.Head().position = hitPoint; } if (GlobalData.MyDeviceMode == DeviceMode.Archery) this.arrowCameraComp.arrowCameraTemplate?.beforeHit(); else nextShootFromType();//子弹直接下一轮 //this.arrowCameraComp.arrowCameraTemplate?.beforeHit(); raycastHit.transform.GetComponent().Hit(this, hitPoint, playerIndex); //nextShoot(); } else if (raycastHit.transform.GetComponent()) { //撞到空气墙当作超时处理 // FlyTimeOut(); if (GlobalData.MyDeviceMode == DeviceMode.Archery) FlyTimeOut(); else StartCoroutine(delayFlyTimeOut()); } else { //this.arrowCameraComp.arrowCameraTemplate?.beforeHit(); if (GlobalData.MyDeviceMode == DeviceMode.Archery) this.arrowCameraComp.arrowCameraTemplate?.beforeHit(); else nextShootFromType();//子弹直接下一轮 Hit(); GameController.ins.HitTarget(0); //击中其它东西时的音效 hitType = HitType.NotTarget; if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == "GameDouble" || UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == "InfraredGameDouble") { AudioMgr.ins.PlayCheer(false); } else { if (CommonConfig.StandaloneModeOrPlatformB) { AudioMgr.ins.PlayCheer(false); } else { AudioMgr.ins.PlayArrowEnter(); } } //nextShoot(); } } public void Hit() { isHit = true; if (GameDebug.ins) GameDebug.ins.ShowRes(absoluteRay.point, this.Head().position); //控制箭的特效显示 this.activeEffectCyclone(false); this.activeEffectBomb(true); this.activeEffectTrail(false); //最新一箭击中后会发光标记 ArrowLightSick.RecoveryAll(); this.GetComponentInChildren().Hit(); } //进入下一轮射击 [NonSerialized] public bool hasDoneNextShoot = false; public void nextShootFromType() { // Debug.Log("nextShootFromType:" + GlobalDataTemp.pkMatchType); if (GlobalDataTemp.pkMatchType == PKMatchType.LocalPK || GlobalDataTemp.pkMatchType == PKMatchType.OnlinePK) { StartCoroutine(delayNectShoot()); } else { nextShoot(); } } IEnumerator delayNectShoot() { yield return new WaitForSeconds(1.0f); nextShoot(); } public void nextShoot() { if (hasDoneNextShoot) return; hasDoneNextShoot = true; //GameMgr.ins.gameMode.ResumeTimeCounting(this); onDoNextShoot?.Invoke(); try { if (AimHandler.ins) AimHandler.ins.Ban9AxisCalculate(false); //把瞄准点画成红圈,渲染在靶子上(取消) if (rayHitTargetBody) { Transform redCircle = rayHitTargetBody.transform.Find("RedCircle"); redCircle.gameObject.SetActive(false); } //最新一箭击中后会发光标记(取消) ArrowLightSick.RecoveryAll(); } catch (System.Exception e) { Debug.LogError(e.Message + "\n" + e.StackTrace); } //if (!GameMgr.ins.gameMode.DoNextShoot()) return; this.armBow.readyShoot(); } //---------箭矢旋转-------- Vector3 rotateV3 = new Vector3(0, 0, 1400); void UpdateRotate() { this.Head().Rotate(rotateV3 * Time.deltaTime, Space.Self); } //---------箭矢特效--------- public void activeEffectCyclone(bool value) { this.transform.Find("Head/EF_kuosanquan").gameObject.SetActive(value); if (!value) return; ParticleSystemRenderer ps = this.transform.Find("Head/EF_kuosanquan/kuosan").GetComponent(); ParticleSystemRenderer ps1 = this.transform.Find("Head/EF_kuosanquan/kuosan (1)").GetComponent(); DOTween.To(() => ps.minParticleSize, value => { ps.minParticleSize = value; ps.maxParticleSize = value; }, 0.4f, 0.6f); DOTween.To(() => ps1.minParticleSize, value => { ps1.minParticleSize = value; ps1.maxParticleSize = value; }, 0.8f, 0.6f); } void activeEffectBomb(bool value) { this.transform.Find("Head/EF_baodian").gameObject.SetActive(value); } void activeEffectTrail(bool value) { //this.transform.Find("EF_tuowei").gameObject.SetActive(value); //this.transform.Find("EF_tuowei/Trail").GetComponent().time = 1.6f / mySpeed; if (GlobalData.MyDeviceMode == DeviceMode.Archery) { this.transform.Find("EF_tuowei").gameObject.SetActive(value); this.transform.Find("EF_tuowei/Trail").GetComponent().time = 1.6f / mySpeed; } else { StartCoroutine(showTrail(value)); } } IEnumerator showTrail(bool value) { this.transform.Find("EF_tuowei_bullet").gameObject.SetActive(value); //this.transform.Find("EF_tuowei_bullet/Trail").GetComponent().time = 0; yield return new WaitForEndOfFrame(); this.transform.Find("EF_tuowei_bullet/GB_flame_FX_1").gameObject.SetActive(true); //this.transform.Find("EF_tuowei_bullet/Trail").GetComponent().time = 0.02f; //this.transform.Find("EF_tuowei_bullet/Trail").GetComponent().time = 1.6f / mySpeed; } }