Files
5.24BuYu/Assets/MyGame/Scripts/Items/Bullits/BullitMove.cs
T
2026-10-15 03:57:20 +08:00

370 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}