|
|
@@ -1,28 +1,49 @@
|
|
|
-using System.Collections.Generic;
|
|
|
+using System;
|
|
|
using UnityEngine;
|
|
|
using DG.Tweening;
|
|
|
/* 箭对象 */
|
|
|
public class Arrow : MonoBehaviour
|
|
|
-{
|
|
|
- private Rigidbody newRigidbody;
|
|
|
- public Vector3 shootOutPosition;
|
|
|
- public float flyTime = 0;
|
|
|
- public bool isHit = false;
|
|
|
- public RaycastHit absoluteRay;
|
|
|
- public float offsetAngle;
|
|
|
+{
|
|
|
+ //飞行时间统计
|
|
|
+ [NonSerialized] public float flyTime = 0;
|
|
|
+ //标识—是否击中了什么
|
|
|
+ [NonSerialized] public bool isHit = false;
|
|
|
+ //箭的初速度(私用)
|
|
|
private float mySpeed = 0;
|
|
|
- public bool simulatedParabola = false; //模拟抛物线
|
|
|
- public ArmBow armBow;
|
|
|
+ //箭的初速度(公用)
|
|
|
public static float speed = GameMgr.RealSizeToGameSize(60);
|
|
|
|
|
|
+ //箭射出时从弓传过来的参数
|
|
|
+ #region
|
|
|
+ //手臂弓
|
|
|
+ [NonSerialized] public ArmBow armBow;
|
|
|
+ //射出时的起始坐标
|
|
|
+ [NonSerialized] public Vector3 shootOutPosition;
|
|
|
+ //绝对射线
|
|
|
+ [NonSerialized] public RaycastHit absoluteRay;
|
|
|
+ //制作误差偏移角(强行制造误差,为了增加游戏难度,该偏移角的大小根据难度而定)
|
|
|
+ [NonSerialized] public float offsetAngle;
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ //射线击中的靶子
|
|
|
+ TargetBody rayHitTargetBody;
|
|
|
+ //能够完美击中射线点
|
|
|
+ bool canPerfectHit;
|
|
|
+ //能否使用侧面镜头
|
|
|
+ [NonSerialized] public bool canUseSideCamera;
|
|
|
+
|
|
|
void Awake()
|
|
|
{
|
|
|
GameMgr.ins.gameMode.PauseTimeCounting(this);
|
|
|
}
|
|
|
|
|
|
+ void OnDestroy() {
|
|
|
+ GameMgr.ins.gameMode.ResumeTimeCounting(this);
|
|
|
+ }
|
|
|
+
|
|
|
void Start()
|
|
|
{
|
|
|
- newRigidbody = this.gameObject.AddComponent<Rigidbody>();
|
|
|
+ Destroy(gameObject.GetComponent<BoxCollider>());
|
|
|
|
|
|
mySpeed = speed;
|
|
|
Billboard.ins?.SetArrowSpeed(speed);
|
|
|
@@ -32,108 +53,90 @@ public class Arrow : MonoBehaviour
|
|
|
}
|
|
|
Billboard.ins?.ShowSpeed();
|
|
|
|
|
|
- if (absoluteRay.transform && absoluteRay.transform.gameObject.name == "TargetBody") {
|
|
|
+ if (absoluteRay.transform) {
|
|
|
+ TargetBody targetBody = absoluteRay.transform.GetComponent<TargetBody>();
|
|
|
+ rayHitTargetBody = targetBody??null;
|
|
|
//把瞄准点画成红圈,渲染在靶子上
|
|
|
- Transform redCircle = TargetBody.ins.transform.Find("RedCircle");
|
|
|
- redCircle.gameObject.SetActive(true);
|
|
|
- redCircle.transform.position = -redCircle.transform.forward * 0.001f + absoluteRay.point;
|
|
|
- }
|
|
|
- if (absoluteRay.transform && absoluteRay.transform.gameObject.name == "TargetBody") {
|
|
|
- SetUpBeforFly();
|
|
|
+ if (rayHitTargetBody) {
|
|
|
+ Transform redCircle = rayHitTargetBody.transform.Find("RedCircle");
|
|
|
+ redCircle.gameObject.SetActive(true);
|
|
|
+ redCircle.transform.position = -redCircle.transform.forward * 0.001f + absoluteRay.point;
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- newRigidbody.velocity = this.transform.forward * mySpeed;
|
|
|
- newRigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
|
|
|
- newRigidbody.drag = 0;
|
|
|
- newRigidbody.angularDrag = 0;
|
|
|
+ SetUpBeforFly();
|
|
|
|
|
|
Transform cameraTF = this.transform.Find("Camera");
|
|
|
cameraTF.gameObject.SetActive(true);
|
|
|
cameraTF.gameObject.AddComponent<ArrowCamera>().arrow = this;
|
|
|
|
|
|
this.activeEffectTrail(true);
|
|
|
-
|
|
|
- //是update运动,则禁用物理运动
|
|
|
- if (useUpdatePhysics) {
|
|
|
- this.GetComponent<BoxCollider>().enabled = false;
|
|
|
- Destroy(newRigidbody);
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
/**在箭飞行前初始化&如果瞄准射线的落点在靶子上时,才调用该函数 */
|
|
|
void SetUpBeforFly()
|
|
|
{
|
|
|
- //看能否通过调整发射角让箭落在靶子的瞄准点上
|
|
|
- CalculateParabolaAngle(absoluteRay.point);
|
|
|
-
|
|
|
- if (!hasParabolaAngle) { //发射角无解,就提示用户
|
|
|
- if (AimLoadChecker.ins) AimLoadChecker.ins.ShowOutTip();
|
|
|
- return;
|
|
|
- }
|
|
|
- //来到这里,证明发射角有解,该解姑且叫它绝对角
|
|
|
-
|
|
|
- //因为Unity引擎的规则,向上时359~270度,向下是0~90度
|
|
|
- //为了方便数学运算,换算成 向上时0~90度,向下是0~-90度
|
|
|
- float baseAngleX = this.transform.eulerAngles.x;
|
|
|
- if (baseAngleX > 180) baseAngleX = baseAngleX - 360;
|
|
|
- baseAngleX *= -1;
|
|
|
-
|
|
|
- //绝对角
|
|
|
- float absoluteAngleX = parabolaAngleInRadian / Mathf.PI * 180;
|
|
|
-
|
|
|
- //客户要求绝对角跟原本角度相差不能超过 maxDeltaAngleX
|
|
|
- float deltaAngleX = absoluteAngleX - baseAngleX;
|
|
|
+ //基础角
|
|
|
+ float baseAngleX = FormatAngleX(this.transform.eulerAngles.x);
|
|
|
+ //最大可调整的角度差
|
|
|
float maxDeltaAngleX = 5;
|
|
|
-
|
|
|
- //最终的角还要加上偏移角度,偏移角也是客户要求加,单纯是为了增加误差,简单模式偏移角度为0
|
|
|
- float finalAngleX = this.offsetAngle;
|
|
|
- //如果绝对角跟原本角度相差超过 maxDeltaAngleX,就不能用绝对角了,直接在原本角度上加上最大差角
|
|
|
- if (Mathf.Abs(deltaAngleX) > maxDeltaAngleX) {
|
|
|
- finalAngleX += baseAngleX;
|
|
|
- finalAngleX += deltaAngleX > 0 ? maxDeltaAngleX : -maxDeltaAngleX;
|
|
|
- if (AimLoadChecker.ins) AimLoadChecker.ins.ShowOutTip();
|
|
|
- } else {//不超过就最好,直接用绝对角
|
|
|
- finalAngleX += absoluteAngleX;
|
|
|
+ //最终角
|
|
|
+ float finalAngleX;
|
|
|
+
|
|
|
+ if (absoluteRay.transform) {
|
|
|
+ //看能否通过调整发射角让箭落在靶子的瞄准点上
|
|
|
+ CalculateParabolaAngle(absoluteRay.point);
|
|
|
+ //瞄准的是不是Target层
|
|
|
+ bool isTargetLayer = IsTargetLayer(absoluteRay.transform.gameObject);
|
|
|
+ //绝对发射角无解
|
|
|
+ if (!hasParabolaAngle) {
|
|
|
+ finalAngleX = Mathf.Clamp(baseAngleX + maxDeltaAngleX + this.offsetAngle, -89, 89);
|
|
|
+ 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 + offsetAngle, -89, 89);
|
|
|
+ if (Math.Abs(offsetAngle) < 0.01) {
|
|
|
+ canPerfectHit = true;
|
|
|
+ }
|
|
|
+ if (rayHitTargetBody && Mathf.RoundToInt(rayHitTargetBody.GetDistance()) >= 50 && UnityEngine.Random.value < 0.5) {
|
|
|
+ canUseSideCamera = true;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ finalAngleX = Mathf.Clamp(baseAngleX + maxDeltaAngleX + this.offsetAngle, -89, 89);
|
|
|
+ if (isTargetLayer) AimLoadChecker.ins?.ShowOutTip();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ finalPoint = absoluteRay.point;
|
|
|
+ } else {
|
|
|
+ finalAngleX = baseAngleX;
|
|
|
+ finalPoint = this.transform.position + this.transform.forward * 100;
|
|
|
}
|
|
|
|
|
|
- //拆分两轴的速度
|
|
|
- float vy = Mathf.Sin(finalAngleX / 180 * Mathf.PI) * this.mySpeed;
|
|
|
- float vx = Mathf.Cos(finalAngleX / 180 * Mathf.PI) * this.mySpeed;
|
|
|
- //x轴距离
|
|
|
- float deltaX = Mathf.Sqrt(
|
|
|
- Mathf.Pow(absoluteRay.point.x - this.shootOutPosition.x, 2) +
|
|
|
- Mathf.Pow(absoluteRay.point.z - this.shootOutPosition.z, 2)
|
|
|
- );
|
|
|
- //x方向的耗时
|
|
|
- float tx = deltaX / vx;
|
|
|
- //最终y轴的位移
|
|
|
- float dy = vy * tx + 0.5f * Physics.gravity.y * tx * tx;
|
|
|
+ parabolaAngleInRadian = finalAngleX / 180 * Mathf.PI;
|
|
|
+ }
|
|
|
|
|
|
- float tbRadius = 0.62f;//靶子半径
|
|
|
- //之所以这样比较,是因为发射位置跟靶子中心是等高的,靶子半径是已知的
|
|
|
- if (Mathf.Abs(dy) < tbRadius) { //在靶子范围内,则用update运算物理
|
|
|
- finalPoint = absoluteRay.point;
|
|
|
- finalPoint.y = absoluteRay.transform.parent.position.y + dy;
|
|
|
- useUpdatePhysics = true;
|
|
|
- parabolaAngleInRadian = finalAngleX / 180 * Mathf.PI;
|
|
|
- RandomUseSideCamera();
|
|
|
- } else { //否则,调下角度就好,到时候用unity物理运算
|
|
|
- Vector3 eulerAngle = this.transform.eulerAngles;
|
|
|
- eulerAngle.x = -finalAngleX;
|
|
|
- this.transform.eulerAngles = eulerAngle;
|
|
|
- }
|
|
|
+ /*
|
|
|
+ 因为Unity引擎的规则,向上时359~270度,向下是0~90度
|
|
|
+ 为了方便数学运算,换算成 向上时0~90度,向下是0~-90度
|
|
|
+ */
|
|
|
+ float FormatAngleX(float value)
|
|
|
+ {
|
|
|
+ if (value > 180) value = 360 - value;
|
|
|
+ else value = -value;
|
|
|
+ return value;
|
|
|
}
|
|
|
|
|
|
- void RandomUseSideCamera() {
|
|
|
- //有几率进入侧面镜头观看
|
|
|
- float distance = TargetBody.ins.GetDistance();
|
|
|
- if (Mathf.RoundToInt(distance) >= 50) {
|
|
|
- if (Random.value < 0.5) simulatedParabola = true;
|
|
|
- }
|
|
|
+ bool IsTargetLayer(GameObject gameObject) {
|
|
|
+ return (1 << gameObject.layer) == LayerMask.GetMask("Target");
|
|
|
}
|
|
|
|
|
|
/**发射角有无解 */
|
|
|
- bool hasParabolaAngle = false;
|
|
|
+ [NonSerialized] public bool hasParabolaAngle = false;
|
|
|
/**发射角弧度解 */
|
|
|
float parabolaAngleInRadian = 0;
|
|
|
/**
|
|
|
@@ -178,82 +181,82 @@ public class Arrow : MonoBehaviour
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- void OnDestroy() {
|
|
|
- GameMgr.ins.gameMode.ResumeTimeCounting(this);
|
|
|
- if (this.newRigidbody)
|
|
|
- {
|
|
|
- Destroy(this.newRigidbody);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
void FixedUpdate()
|
|
|
{
|
|
|
- if (newRigidbody) {
|
|
|
- transform.forward = newRigidbody.velocity.normalized;
|
|
|
- }
|
|
|
if (!isHit && flyTime >= 0) {
|
|
|
flyTime += Time.deltaTime;
|
|
|
if (flyTime > 14) {
|
|
|
- Destroy(gameObject);
|
|
|
- GameMgr.ins.gameMode.HitTarget(0);
|
|
|
- AudioMgr.ins.PlayCheer(false);
|
|
|
- nextShoot();
|
|
|
+ FlyTimeOut();
|
|
|
}
|
|
|
this.UpdateRotate();
|
|
|
}
|
|
|
- // this.UpdateShake();
|
|
|
}
|
|
|
|
|
|
void Update() {
|
|
|
UpdateFlyWithoutPhysics();
|
|
|
}
|
|
|
|
|
|
+ void FlyTimeOut() {
|
|
|
+ Destroy(gameObject);
|
|
|
+ GameMgr.ins.gameMode.HitTarget(0);
|
|
|
+ AudioMgr.ins.PlayCheer(false);
|
|
|
+ nextShoot();
|
|
|
+ }
|
|
|
+
|
|
|
bool useUpdatePhysics = false;
|
|
|
float flyTimeWithoutPhysics = 0;
|
|
|
Vector3 finalPoint = default;
|
|
|
/**更新箭的飞行轨迹(非Unity物理运算)*/
|
|
|
void UpdateFlyWithoutPhysics() {
|
|
|
- if (!useUpdatePhysics || isHit) return;
|
|
|
+ if (isHit) return;
|
|
|
flyTimeWithoutPhysics += Time.deltaTime;
|
|
|
Vector3 destination = finalPoint;
|
|
|
- float deltaX = Mathf.Sqrt(
|
|
|
- Mathf.Pow(destination.x - shootOutPosition.x, 2) +
|
|
|
- Mathf.Pow(destination.z - shootOutPosition.z, 2)
|
|
|
- );
|
|
|
float vx = Mathf.Cos(parabolaAngleInRadian) * mySpeed;
|
|
|
- float t = deltaX / vx;
|
|
|
- if (flyTimeWithoutPhysics < t) {
|
|
|
- t = flyTimeWithoutPhysics;
|
|
|
- }
|
|
|
+ float t = flyTimeWithoutPhysics;
|
|
|
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 = shootOutPosition.y + dy;
|
|
|
+ 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;
|
|
|
- //箭头到达目标点时,触发碰撞(之所以用以下,是因为箭的锚点在尾部,而箭的长度接近1.1)
|
|
|
- if (Vector3.Distance(nextPosition, destination) < 1.1f) {
|
|
|
- this.transform.SetParent(TargetBody.ins.transform);
|
|
|
- TargetBody.ins.Hit(this, destination);
|
|
|
+
|
|
|
+ float deltaDistance = Vector3.Distance(oldPosition, nextPosition);
|
|
|
+ Ray ray = new Ray(oldPosition, nextPosition - oldPosition);
|
|
|
+ RaycastHit raycastHit;
|
|
|
+ bool raycastResult = Physics.Raycast(ray, out raycastHit, deltaDistance);
|
|
|
+ if (raycastResult) {
|
|
|
+ this.transform.Find("Head").position = raycastHit.point;
|
|
|
+ this.transform.SetParent(raycastHit.transform.parent);
|
|
|
+ string targetName = raycastHit.transform.gameObject.name;
|
|
|
+ if (targetName == "TargetBody") {
|
|
|
+ Vector3 hitPoint = raycastHit.point;
|
|
|
+ if (rayHitTargetBody && canPerfectHit) {
|
|
|
+ hitPoint = absoluteRay.point;
|
|
|
+ this.transform.Find("Head").position = hitPoint;
|
|
|
+ }
|
|
|
+ raycastHit.transform.GetComponent<TargetBody>().Hit(this, hitPoint);
|
|
|
+ } else if (raycastHit.transform.GetComponent<TargetOutBound>()) {
|
|
|
+ FlyTimeOut();
|
|
|
+ } else {
|
|
|
+ Hit();
|
|
|
+ GameMgr.ins.gameMode.HitTarget(0);
|
|
|
+ AudioMgr.ins.PlayCheer(false);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void Hit() {
|
|
|
- if (GameDebug.ins) GameDebug.ins.ShowRes(absoluteRay.point, this.getHeadPosition());
|
|
|
- gameObject.GetComponent<BoxCollider>().enabled = false;
|
|
|
- if (newRigidbody) {
|
|
|
- newRigidbody.useGravity = false;
|
|
|
- newRigidbody.velocity = Vector3.zero;
|
|
|
- Destroy(newRigidbody);
|
|
|
- newRigidbody = null;
|
|
|
- }
|
|
|
isHit = true;
|
|
|
- // this.Shake();
|
|
|
+
|
|
|
+ GameDebug.ins?.ShowRes(absoluteRay.point, this.transform.Find("Head").position);
|
|
|
+
|
|
|
+ //控制箭的特效显示
|
|
|
this.activeEffectCyclone(false);
|
|
|
this.activeEffectBomb(true);
|
|
|
this.activeEffectTrail(false);
|
|
|
@@ -263,49 +266,6 @@ public class Arrow : MonoBehaviour
|
|
|
this.GetComponentInChildren<ArrowLightSick>().Hit();
|
|
|
}
|
|
|
|
|
|
- public Vector3 getHeadPosition() {
|
|
|
- return this.transform.Find("Head").position;
|
|
|
- }
|
|
|
-
|
|
|
- /**箭矢旋转 */
|
|
|
- private Vector3 rotateV3 = new Vector3(0, 0, 1400);
|
|
|
- private void UpdateRotate() {
|
|
|
- this.transform.Find("Head").Rotate(rotateV3 * Time.deltaTime, Space.Self);
|
|
|
- }
|
|
|
-
|
|
|
- /**箭矢震动 */
|
|
|
- private bool shaking = false;
|
|
|
- private float shakeTime = 0;
|
|
|
- private float shakeTimeMax = 0.5f;
|
|
|
- private float shakeRange = 5;
|
|
|
- private int shakeDirection = 1;
|
|
|
- private Vector3 shakeAngles = new Vector3();
|
|
|
-
|
|
|
- private void Shake()
|
|
|
- {
|
|
|
- this.shaking = true;
|
|
|
- this.shakeTime = 0;
|
|
|
- this.shakeDirection = 1;
|
|
|
- }
|
|
|
-
|
|
|
- private void UpdateShake()
|
|
|
- {
|
|
|
- if (!this.shaking) {
|
|
|
- return;
|
|
|
- }
|
|
|
- Transform transform = this.transform.Find("Head").transform;
|
|
|
- float shakeRangeNow = (1 - this.shakeTime / this.shakeTimeMax) * this.shakeRange;
|
|
|
- if (shakeRangeNow <= 0) {//震动结束
|
|
|
- this.shaking = false;
|
|
|
- this.shakeAngles.x = 0;
|
|
|
- } else {
|
|
|
- this.shakeAngles.x = -this.shakeDirection * shakeRangeNow;
|
|
|
- this.shakeDirection *= -1;
|
|
|
- }
|
|
|
- transform.localEulerAngles = this.shakeAngles;
|
|
|
- this.shakeTime += Time.deltaTime;
|
|
|
- }
|
|
|
-
|
|
|
//进入下一轮射击
|
|
|
public bool hasDoneNextShoot = false;
|
|
|
public void nextShoot() {
|
|
|
@@ -317,22 +277,22 @@ public class Arrow : MonoBehaviour
|
|
|
this.armBow.readyShoot();
|
|
|
|
|
|
//把瞄准点画成红圈,渲染在靶子上(取消)
|
|
|
- Transform redCircle = TargetBody.ins.transform.Find("RedCircle");
|
|
|
- redCircle.gameObject.SetActive(false);
|
|
|
+ if (rayHitTargetBody) {
|
|
|
+ Transform redCircle = rayHitTargetBody.transform.Find("RedCircle");
|
|
|
+ redCircle.gameObject.SetActive(false);
|
|
|
+ }
|
|
|
//最新一箭击中后会发光标记(取消)
|
|
|
ArrowLightSick.RecoveryAll();
|
|
|
}
|
|
|
|
|
|
- void OnCollisionEnter(Collision collision) {
|
|
|
- if ((1 << collision.gameObject.layer) != LayerMask.GetMask("Target"))
|
|
|
- {
|
|
|
- this.Hit();
|
|
|
- GameMgr.ins.gameMode.HitTarget(0);
|
|
|
- AudioMgr.ins.PlayCheer(false);
|
|
|
- }
|
|
|
- this.transform.SetParent(collision.transform.parent);
|
|
|
+ //---------箭矢旋转--------
|
|
|
+ Vector3 rotateV3 = new Vector3(0, 0, 1400);
|
|
|
+ void UpdateRotate() {
|
|
|
+ this.transform.Find("Head").Rotate(rotateV3 * Time.deltaTime, Space.Self);
|
|
|
}
|
|
|
|
|
|
+ //---------箭矢特效---------
|
|
|
+
|
|
|
public void activeEffectCyclone(bool value)
|
|
|
{
|
|
|
this.transform.Find("Head/EF_kuosanquan").gameObject.SetActive(value);
|