370 lines
14 KiB
C#
370 lines
14 KiB
C#
using Assets.MyGame.Scripts.Net;
|
||
using Assets.MyGame.Scripts.Net.API;
|
||
using Assets.MyGame.Scripts.Net.WebResponses;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.Text;
|
||
using UnityEngine;
|
||
|
||
public class BullitMove : MonoBehaviour
|
||
{
|
||
public string userid;
|
||
public float moveSpeed = 5;
|
||
public float desTime = 10f;
|
||
public string playerID;
|
||
public GameObject effectPrefab;
|
||
public GameObject diePrefab;
|
||
float currShowTime = 0f;
|
||
Vector3 vector3_startPosition;
|
||
public bool isTriggerFirst = true;
|
||
MonsterContral target;
|
||
public Transform targetTra;
|
||
public string bulletID;
|
||
/// <summary>该发子弹的实际扣费炮倍(双倍炮=2倍炮倍)。击中结算时作为 KillFish 的 paoBei 上报,
|
||
/// 后端按它算奖励 → 双倍炮奖励随之×2。0=未记录(回退发射者座位炮倍)</summary>
|
||
public long fireGold = 0;
|
||
|
||
// 转向参数
|
||
public float maxTurnSpeed = 15f; // 最大转向速度(度/秒)
|
||
public float minTurnSpeed = 2f; // 最小转向速度
|
||
public float distanceForMaxTurn = 1f; // 在此距离内达到最大转向速度
|
||
float hitDistance = 2f; // 命中判定距离
|
||
|
||
// ⭐ 三合一抛物线(正常炮台散射+锁定时,上下两颗子弹的弧线飞行)
|
||
// 出膛偏移0 → 中段鼓出到 parabolaAmplitude(弧线最高点)→ 命中前归零(三发收敛到同一条鱼)
|
||
Vector2 parabolaAxis = Vector2.right; // 横向鼓出方向(垂直于炮口→目标连线)
|
||
float parabolaAmplitude = 0f; // 最高点横向鼓出幅度(<=0 关闭抛物线)
|
||
float parabolaInitialDistance = 1f; // 开火时到目标的距离(进度基准)
|
||
float parabolaPrevLateral = 0f; // 上一帧横向偏移(子弹是增量移动,偏移按帧差叠加)
|
||
float parabolaBaseSpeed = 0f; // 降速前的原始速度(清理时恢复,防池化复用残留慢速)
|
||
|
||
// ----------------------------------------------------------------
|
||
// ⭐ 渲染层级:sortingOrder 优先于 z 深度排序,
|
||
// 2D 鱼/飞机设置了 sortingOrder 后会把默认 0 的子弹压在下面,
|
||
// 所以子弹需要显式设置更高的 sortingOrder 保证显示在飞机和鱼的上方
|
||
// ----------------------------------------------------------------
|
||
[Header("渲染层级")]
|
||
[Tooltip("子弹sortingOrder层级值,需大于场上鱼/飞机的最大层级,保证子弹显示在最上层")]
|
||
public int sortOrder = 100;
|
||
|
||
/// <summary>子弹渲染器缓存(设置sortingOrder用,Awake 后只取一次)</summary>
|
||
private Renderer[] _cachedRenderers;
|
||
|
||
/// <summary>应用sortingOrder到子弹所有渲染器(多渲染器依次 +i 递增,保持内部遮挡关系)</summary>
|
||
private void ApplySortOrder()
|
||
{
|
||
if (_cachedRenderers == null)
|
||
_cachedRenderers = GetComponentsInChildren<Renderer>(true);
|
||
|
||
for (int i = 0; i < _cachedRenderers.Length; i++)
|
||
{
|
||
var r = _cachedRenderers[i];
|
||
if (r != null)
|
||
r.sortingOrder = sortOrder + i;
|
||
}
|
||
}
|
||
|
||
void Awake()
|
||
{
|
||
Init();
|
||
}
|
||
|
||
bool isLock = false;
|
||
public void Lock(MonsterContral monsterContral, Transform endTra)
|
||
{
|
||
foreach (var item in GetComponents<Collider2D>())
|
||
{
|
||
item.enabled = false;
|
||
}
|
||
isLock = true;
|
||
target = monsterContral;
|
||
targetTra = endTra;
|
||
}
|
||
|
||
public void NotLock()
|
||
{
|
||
foreach (var item in GetComponents<Collider2D>())
|
||
{
|
||
item.enabled = true;
|
||
}
|
||
isLock = false;
|
||
target = null;
|
||
targetTra = null;
|
||
// 解锁后重置运动基准:锁定期间子弹是追踪移动的,位置早已偏离起点
|
||
// 若不重置,非锁定模式的 vector3_startPosition + up*currShowTime*speed 会让子弹瞬移
|
||
vector3_startPosition = transform.position;
|
||
vector3_startPosition.z = -400;
|
||
currShowTime = 0f;
|
||
// 解锁同时清除抛物线,防止直线飞行时残留横向偏移
|
||
ClearParabola();
|
||
}
|
||
|
||
/// <summary>
|
||
/// ⭐ 三合一抛物线:子弹在追踪目标的轨迹上叠加横向正弦偏移——
|
||
/// 出膛偏移0 → 中段鼓出到 amplitude(弧线最高点)→ 命中前归零(和另外两发收敛到同一条鱼)。
|
||
/// lateralAxis=横向鼓出方向(垂直于炮口→目标连线),totalDistance=开火时到目标的距离。
|
||
/// bulletSpeed>0 时给这发子弹降速:子弹默认速度太快(如100)时全程飞行仅零点几秒,弧线根本看不清。
|
||
/// </summary>
|
||
public void SetParabola(Vector2 lateralAxis, float amplitude, float totalDistance, float bulletSpeed = 0f)
|
||
{
|
||
parabolaAxis = lateralAxis.sqrMagnitude > 0.0001f ? lateralAxis.normalized : Vector2.right;
|
||
parabolaAmplitude = amplitude;
|
||
parabolaInitialDistance = Mathf.Max(0.01f, totalDistance);
|
||
parabolaPrevLateral = 0f;
|
||
// ⭐ 降速拉长飞行时间,弧线才看得清;记录原始速度,清理时恢复
|
||
if (bulletSpeed > 0f)
|
||
{
|
||
if (parabolaBaseSpeed <= 0f)
|
||
parabolaBaseSpeed = moveSpeed;
|
||
moveSpeed = bulletSpeed;
|
||
}
|
||
}
|
||
|
||
/// <summary>清除抛物线状态(解锁/回池时调用):关偏移 + 恢复原速度</summary>
|
||
void ClearParabola()
|
||
{
|
||
parabolaAmplitude = 0f;
|
||
parabolaPrevLateral = 0f;
|
||
if (parabolaBaseSpeed > 0f)
|
||
{
|
||
moveSpeed = parabolaBaseSpeed;
|
||
parabolaBaseSpeed = 0f;
|
||
}
|
||
}
|
||
|
||
public void Init()
|
||
{
|
||
vector3_startPosition = transform.position;
|
||
vector3_startPosition.z = -400;
|
||
transform.position = vector3_startPosition;
|
||
|
||
// ⭐ 应用渲染层级(池化复用时 Init 会重新调用,Inspector 改了值也能生效)
|
||
ApplySortOrder();
|
||
}
|
||
|
||
/// <summary>把"回池重置"钩子注入框架层 BulletPool(框架层不能反向引用 BullitMove)</summary>
|
||
static BullitMove()
|
||
{
|
||
BulletPool.ResetBulletState = go =>
|
||
{
|
||
var bm = go != null ? go.GetComponent<BullitMove>() : null;
|
||
if (bm != null) bm.ResetState();
|
||
};
|
||
}
|
||
|
||
/// <summary>对象池回收前调用:重置所有运行时状态,避免复用时残留旧数据</summary>
|
||
public void ResetState()
|
||
{
|
||
userid = null;
|
||
playerID = null;
|
||
bulletID = null;
|
||
currShowTime = 0f;
|
||
isTriggerFirst = true;
|
||
target = null;
|
||
targetTra = null;
|
||
// 解除锁定 + 恢复碰撞器
|
||
NotLock();
|
||
// 重置起始位置(z=-400 是原 Awake→Init 的默认值)
|
||
vector3_startPosition = transform.position;
|
||
vector3_startPosition.z = -400;
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
// 子弹生命周期:超时自动销毁(默认10秒)
|
||
currShowTime += Time.deltaTime;
|
||
if (currShowTime >= desTime)
|
||
{
|
||
BulletPool.Instance.Return(gameObject);
|
||
return;
|
||
}
|
||
|
||
if (isLock)
|
||
{
|
||
// 目标无效或已死亡 → 解除锁定
|
||
if (target == null || target.isDead || targetTra == null || !target.gameObject.activeInHierarchy || !targetTra.gameObject.activeInHierarchy)
|
||
{
|
||
NotLock();
|
||
return;
|
||
}
|
||
|
||
Vector3 myPos = transform.position;
|
||
myPos.z = 0;
|
||
Vector3 targetPos = targetTra.position;
|
||
targetPos.z = 0;
|
||
|
||
float distance = Vector3.Distance(myPos, targetPos);
|
||
|
||
// 命中判定
|
||
if (distance <= hitDistance)
|
||
{
|
||
AttackFish(target);
|
||
return;
|
||
}
|
||
|
||
// 计算指向目标的方向
|
||
Vector3 targetDir = (targetPos - myPos).normalized;
|
||
|
||
// 根据距离动态计算转向速度:距离越近转向越快
|
||
float t = Mathf.Clamp01(1f - (distance / distanceForMaxTurn));
|
||
float turnSpeed = Mathf.Lerp(minTurnSpeed, maxTurnSpeed, t);
|
||
// 使用 RotateTowards 平滑转向(角度/秒)
|
||
float currentAngle = Vector2.SignedAngle(transform.up, targetDir);
|
||
float newAngle = Mathf.MoveTowardsAngle(currentAngle, 0f, turnSpeed * Time.deltaTime);
|
||
transform.up = Quaternion.Euler(0, 0, newAngle) * transform.up;
|
||
|
||
// 沿当前方向移动
|
||
transform.position += transform.up * moveSpeed * Time.deltaTime;
|
||
|
||
// ⭐ 三合一抛物线:在追踪轨迹上叠加横向正弦偏移
|
||
// 进度按剩余距离算(dist=初始距离→0,dist=命中距离→1),命中前偏移正好归零,三发收敛到目标身上
|
||
if (parabolaAmplitude > 0f)
|
||
{
|
||
float range = parabolaInitialDistance - hitDistance;
|
||
float progress = range > 0.1f
|
||
? Mathf.Clamp01(1f - (distance - hitDistance) / range)
|
||
: 1f;
|
||
float lateralNow = parabolaAmplitude * Mathf.Sin(progress * Mathf.PI);
|
||
transform.position += (Vector3)parabolaAxis * (lateralNow - parabolaPrevLateral);
|
||
parabolaPrevLateral = lateralNow;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 非锁定模式:保持原有直线 + 反弹逻辑(不变)
|
||
Vector3 targetPoint = transform.up.normalized * currShowTime * moveSpeed;
|
||
targetPoint.z = 0;
|
||
transform.position = vector3_startPosition + targetPoint;
|
||
}
|
||
}
|
||
|
||
private void OnCollisionEnter2D(Collision2D collision)
|
||
{
|
||
if (isTriggerFirst)
|
||
{
|
||
if (collision.transform.tag == "Fish")
|
||
{
|
||
// ⭐ 已死亡的鱼(死亡特效播放窗口期)不可被命中,子弹穿透继续飞
|
||
var hitMonster = collision.transform.GetComponentInParent<MonsterContral>();
|
||
if (hitMonster != null && hitMonster.isDead) return;
|
||
|
||
isTriggerFirst = false;
|
||
if (effectPrefab != null)
|
||
{
|
||
EffectPool.Instance.Get(effectPrefab, transform.position, Quaternion.identity, transform.parent);
|
||
}
|
||
|
||
if (userid == GameManager.Instance.user.id)
|
||
{
|
||
hitMonster.monsterData.charactersHealth.OnHurt(10, CharacterHurtType.none);
|
||
Destroy(hitMonster.gameObject);
|
||
}
|
||
BulletPool.Instance.Return(gameObject);
|
||
}
|
||
}
|
||
}
|
||
|
||
public static Quaternion RotationFromVector2(Vector2 dir)
|
||
{
|
||
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
|
||
return Quaternion.Euler(0, 0, angle);
|
||
}
|
||
|
||
Vector2 GetReflectionAngle(Vector2 velocity, Vector2 normal)
|
||
{
|
||
return Vector2.Reflect(velocity.normalized, normal.normalized);
|
||
}
|
||
|
||
public void OnTriggerEnter2D(Collider2D collision)
|
||
{
|
||
if (isTriggerFirst)
|
||
{
|
||
if (collision.tag == "Fish")
|
||
{
|
||
// ⭐ 已死亡的鱼(死亡特效播放窗口期)不可被命中,子弹穿透继续飞
|
||
var monster = collision.transform.GetComponentInParent<MonsterContral>();
|
||
if (monster != null && monster.isDead) return;
|
||
|
||
isTriggerFirst = false;
|
||
AttackFish(monster);
|
||
}
|
||
else if (collision.tag == "Wall")
|
||
{
|
||
Vector3 v3 = transform.eulerAngles;
|
||
switch (collision.name)
|
||
{
|
||
case "up":
|
||
v3.z = -v3.z + 180;
|
||
break;
|
||
case "down":
|
||
v3.z = -v3.z + 180;
|
||
break;
|
||
case "left":
|
||
v3.z = -v3.z;
|
||
break;
|
||
case "right":
|
||
v3.z = -v3.z;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
vector3_startPosition = transform.position;
|
||
currShowTime = 0;
|
||
transform.eulerAngles = v3;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void AttackFish(MonsterContral monster)
|
||
{
|
||
// ⭐ 兜底:已死亡的鱼(死亡特效播放窗口期)不可被命中,子弹回池不结算
|
||
if (monster == null || monster.isDead)
|
||
{
|
||
BulletPool.Instance.Return(gameObject);
|
||
return;
|
||
}
|
||
|
||
if (effectPrefab != null)
|
||
{
|
||
EffectPool.Instance.Get(effectPrefab, transform.position, Quaternion.identity, transform.parent);
|
||
}
|
||
|
||
if (userid == GameManager.Instance.user.id)
|
||
{
|
||
// 真人玩家击杀
|
||
// ⭐ paoBei 用该发实际扣费炮倍(双倍炮=2倍),后端按它结算 → 奖励×2;
|
||
// 未记录时回退当前座位炮倍(兼容旧逻辑)
|
||
long paoBei = fireGold > 0 ? fireGold
|
||
: CreateFishManager.Instance.currGameType.currSitData.consumeGoldNum;
|
||
var v2 = GameManager.Instance.gameData.currRoomData;
|
||
WebSocketSendList.KillFish(v2.roomId, userid, monster.monsterData.fishTypeConfig.fishNumber, monster.guid, (int)paoBei, bulletID, "63215f0afa3f5b25");
|
||
}
|
||
else if (!string.IsNullOrEmpty(userid) && userid.StartsWith("bot_"))
|
||
{
|
||
// 机器人击杀:只有房主客户端发送 BotKillFish
|
||
if (SitPosData.roomOwnerSit != null && SitPosData.roomOwnerSit.user == GameManager.Instance.user.id)
|
||
{
|
||
var v2 = GameManager.Instance.gameData.currRoomData;
|
||
// 从 bot 座位获取炮倍
|
||
long gold = 100;
|
||
var panel = CreateFishManager.Instance.currGameType;
|
||
if (panel != null && panel.list_Sits != null)
|
||
{
|
||
var sit = panel.list_Sits.Find(s => s.user == userid);
|
||
if (sit != null) gold = sit.consumeGoldNum;
|
||
}
|
||
WebSocketSendList.BotKillFish(
|
||
v2.roomId,
|
||
userid,
|
||
monster.monsterData.fishTypeConfig.fishNumber,
|
||
monster.guid,
|
||
gold,
|
||
bulletID
|
||
);
|
||
}
|
||
}
|
||
|
||
monster.OnHurt();
|
||
BulletPool.Instance.Return(gameObject);
|
||
}
|
||
} |