0
This commit is contained in:
@@ -19,6 +19,8 @@ public class ConstEventString
|
||||
// SyncGameData_RequestSyncFish 已废弃:鱼同步改由服务端 OnFishSpawn(type=sync) 下发
|
||||
/// <summary>爆奖结束帧同步(baoJiangEnd):某玩家爆奖流程完成,其他客户端显示最终 BJEndUI 并加分</summary>
|
||||
public const string BaoJiangEnd = "BaoJiangEnd";
|
||||
/// <summary>钻头蟹/激光蟹释放帧同步(zuanTouRelease):击杀者点击(或超时)发射,所有客户端(含击杀者回广播)统一时刻发射钻头/激光</summary>
|
||||
public const string SyncGameData_ZuanTouRelease = "SyncGameData_ZuanTouRelease";
|
||||
|
||||
// ================================================================
|
||||
// 服务端 → 客户端事件 (新协议)
|
||||
|
||||
@@ -59,6 +59,8 @@ namespace Assets.MyGame.Scripts.Net.WebResponses
|
||||
changeZhanShen=13,
|
||||
/// <summary>表情包:某玩家发送表情(spine动画/文字),广播给其他客户端在对应座位显示</summary>
|
||||
biaoQing=14,
|
||||
/// <summary>钻头蟹/激光蟹释放:击杀者点击(或超时)发射钻头/激光,广播给房间所有客户端(含发送者回广播),各端统一时刻发射</summary>
|
||||
zuanTouRelease=15,
|
||||
}
|
||||
[Serializable]
|
||||
public class FireCannonSendData
|
||||
@@ -210,6 +212,22 @@ namespace Assets.MyGame.Scripts.Net.WebResponses
|
||||
public string content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 钻头蟹/激光蟹释放帧数据(frameType=zuanTouRelease)。
|
||||
/// 击杀者点击(或超时)时由击杀者客户端发送(机器人击杀由房主代发),
|
||||
/// 服务器广播给房间所有人(含发送者回广播),各客户端按 fishId 匹配本地爆奖事件统一发射。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class FrameData_ZuanTouRelease : FrameData
|
||||
{
|
||||
/// <summary>击杀者 userId</summary>
|
||||
public string userId;
|
||||
/// <summary>死亡的特殊鱼 ID(各端用它匹配本地正在等待释放的爆奖事件实例)</summary>
|
||||
public string fishId;
|
||||
/// <summary>发射时的炮台源角度(下方座位坐标系,接收端按该座位在本端显示位置用 SeatMirror 还原)</summary>
|
||||
public float angle;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// OnFishSpawn 事件数据结构 (服务端出鱼,破坏性替代 createFish 帧同步)
|
||||
// ================================================================
|
||||
|
||||
@@ -383,6 +383,10 @@ namespace Assets.MyGame.Scripts.Net
|
||||
case FrameDataType.baoJiangEnd:
|
||||
EventCenter.Instance.EventTrigger<SyncGameData>(ConstEventString.BaoJiangEnd, syncGameData);
|
||||
break;
|
||||
// ⭐ 钻头蟹/激光蟹释放:击杀者点击(或超时)广播,各端(含击杀者回广播)统一时刻发射钻头/激光
|
||||
case FrameDataType.zuanTouRelease:
|
||||
EventCenter.Instance.EventTrigger<SyncGameData>(ConstEventString.SyncGameData_ZuanTouRelease, syncGameData);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -310,7 +310,8 @@ public partial class WebSocketSendList
|
||||
{
|
||||
case FrameDataType.fireBullet:
|
||||
case FrameDataType.rotateTurrect:
|
||||
// ⭐ 开火/旋转实时发送,和开炮一样立即广播,保证炮台旋转流畅
|
||||
case FrameDataType.zuanTouRelease:
|
||||
// ⭐ 开火/旋转/钻头释放实时发送,和开炮一样立即广播,保证各端同时刻触发
|
||||
SendImmediate(BuildJson(sgd));
|
||||
return;
|
||||
|
||||
|
||||
@@ -190,8 +190,8 @@ public static class BaoJiangNotSelfHandler
|
||||
Debug.Log($"[BaoJiangNotSelfHandler] 房主已代发 baoJiangEnd 帧同步:key={key} beishu={beishu} score={score}");
|
||||
}
|
||||
|
||||
/// <summary>判断当前本地客户端是否为房主(用于决定是否代发机器人帧同步)</summary>
|
||||
private static bool IsLocalRoomOwner()
|
||||
/// <summary>判断当前本地客户端是否为房主(用于决定是否代发机器人帧同步;钻头蟹/激光蟹释放代发也复用)</summary>
|
||||
public static bool IsLocalRoomOwner()
|
||||
{
|
||||
return SitPosData.roomOwnerSit != null
|
||||
&& GameManager.Instance != null
|
||||
|
||||
@@ -2,6 +2,8 @@ using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 特殊鱼:激光蟹 死亡玩法(继承 ZuanTouXie_DieEvent,只覆写发射部分)。
|
||||
/// ⭐ 多人释放触发与钻头蟹同源:击杀者点击(或超时) → zuanTouRelease 帧同步 →
|
||||
/// 服务器广播全房间(含击杀者回广播) → 各端统一时刻启动激光(机器人击杀由房主代发)。
|
||||
/// 和钻头蟹完全同构,区别仅在"点击发射":
|
||||
/// 钻头蟹 → 发射钻头子弹(SpecialBulletMove:反弹/碰鱼爆炸)
|
||||
/// 激光蟹 → 生成 3D 世界横穿屏幕的激光(SpecialLaserMove:蓝→紫两形态,扫到就杀)
|
||||
@@ -35,9 +37,11 @@ public class JiGuangXie_DieEvent : ZuanTouXie_DieEvent
|
||||
/// <summary>
|
||||
/// 覆写发射:直接使用【炮台预制体上配置好的 SpecialLaserMove】组件(特效引用在炮台里拖好),
|
||||
/// 调它的 Init 启动激光(hit → 主激光 → 切换形态,扫到就杀)。
|
||||
/// 激光方向由炮台自身旋转决定(各端炮台角度经 rotateTurrect 帧同步,基本一致),
|
||||
/// 参数 angle 仅供签名统一,激光不使用。
|
||||
/// 返回 holder:激光跑完 onFinished 时销毁,父类 while(drill != null) 检测到走结算。
|
||||
/// </summary>
|
||||
protected override GameObject FireDrill(bool isKiller)
|
||||
protected override GameObject FireDrill(bool isKiller, float angle)
|
||||
{
|
||||
if (_sit == null) return null;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Assets.MyGame.Scripts.Net.WebResponses;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
@@ -10,15 +12,19 @@ using UnityEngine.InputSystem;
|
||||
/// 1. 死亡 → 普通炮台被钻头效果覆盖:关闭 turretParent,
|
||||
/// 在 tSYPTParent 下生成钻头炮台(localPos=0,不改默认旋转),
|
||||
/// isDrillActive=true 禁用普通开火(期间点击/拖动只瞄准);
|
||||
/// 2. 点击屏幕 → 按普通子弹的创建方式发射钻头
|
||||
/// (位置=GetBulletStartWorldPos,旋转=Euler(0,0,当前瞄准角));
|
||||
/// 2. ⭐多人帧同步触发:击杀者点击(或超时 maxWaitTime) → 发 zuanTouRelease 帧同步 →
|
||||
/// 服务器广播给房间所有人(含击杀者回广播),所有端收到帧才发射(角度用帧里带的,按座位镜像还原)
|
||||
/// → 自己和别人界面同一时刻发射;机器人击杀由房主代发(otherReleaseDelay 延迟);
|
||||
/// 长时间没收到帧(击杀者掉线)则各端兜底本地发射,避免卡死;
|
||||
/// 3. 收到帧同步 → 发射钻头(按普通子弹的创建方式
|
||||
/// 位置=GetBulletStartWorldPos,旋转=Euler(0,0,帧同步角度));
|
||||
/// 发射后【立刻】恢复普通炮台(turretParent=true)——爆奖与炮台无关;
|
||||
/// 3. 钻头飞行三状态(表现=钻头子对象,Inspector 配在钻头预制体上):
|
||||
/// 4. 钻头飞行三状态(表现=钻头子对象,Inspector 配在钻头预制体上):
|
||||
/// 状态1 反弹杀鱼:撞墙反弹 peng 次,碰鱼回调杀小鱼(Id_DieFish_FX 帧同步),显示 state1Fx;
|
||||
/// 状态2 乱转纯反弹:再反弹 peng2 次,碰鱼穿过不杀,显示 state2Fx 并驱动持续自转(spin2Speed);
|
||||
/// 状态3 收尾:飞向世界原点(0,0) → 到达后最终爆炸,显示 state3Fx;
|
||||
/// 最终爆炸炸不炸鱼按 bigPrizeFishMode 判断(KillFish=炸范围小鱼,NoFish/CoinNoFish=只爆不炸);
|
||||
/// 4. 爆炸后(无特效则画范围圈圈) 圈圈淡出完成(或兜底) -> ShowEndUI:加分 + 显示 BJEndUI -> 结束爆奖回池。
|
||||
/// 5. 爆炸后(无特效则画范围圈圈) 圈圈淡出完成(或兜底) -> ShowEndUI:加分 + 显示 BJEndUI -> 结束爆奖回池。
|
||||
/// </summary>
|
||||
public class ZuanTouXie_DieEvent : DieEffect_TSY
|
||||
{
|
||||
@@ -47,14 +53,19 @@ public class ZuanTouXie_DieEvent : DieEffect_TSY
|
||||
[Header("====== 时序 ======")]
|
||||
[Tooltip("等待击杀者点击释放的最长时间(秒),超时自动释放")]
|
||||
public float maxWaitTime = 10f;
|
||||
[Tooltip("非击杀者端自动释放延迟(秒,纯表现)")]
|
||||
[Tooltip("机器人击杀时房主代发释放帧的延迟(秒);也是无鱼guid防御回退时他人端的等待")]
|
||||
public float otherReleaseDelay = 1.5f;
|
||||
[Tooltip("等释放帧同步的网络余量(秒):超过 maxWaitTime+该时长仍未收到帧(击杀者掉线没发帧)则本地兜底发射")]
|
||||
public float releaseNetMargin = 3f;
|
||||
[Tooltip("爆炸后到显示结算的延迟(秒)")]
|
||||
public float endDelay = 0.5f;
|
||||
|
||||
protected SitPosData _sit; // 击杀者座位
|
||||
protected GameObject _paoTai; // 生成的钻头炮台
|
||||
protected bool _endShown; // 结算(BJEndUI)只显示一次
|
||||
private string _fishId; // 宿主鱼 guid(释放帧同步的匹配键,Ws_DieFish 注入 monster)
|
||||
private bool _releaseReceived; // 已收到释放帧同步(服务器回广播)
|
||||
private float _releaseAngle; // 帧同步带来的发射角(已按本端座位镜像还原成世界角度)
|
||||
|
||||
/// <summary>
|
||||
/// 恢复普通炮台的时机:false=发射后立刻恢复(钻头蟹);true=发射物跑完才恢复(激光蟹,炮台持续到激光结束)。
|
||||
@@ -94,6 +105,64 @@ public class ZuanTouXie_DieEvent : DieEffect_TSY
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_routineStarted = false;
|
||||
_releaseReceived = false; // 对象池复用:释放帧状态复位
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// ⭐ 释放帧同步(多人一致性):fishId → 正在等待释放的事件实例
|
||||
// 击杀者端点击/超时 → 发 zuanTouRelease 帧 → 服务器广播全房间(含击杀者回广播)
|
||||
// → 各端按 fishId 找到本地实例 → 统一时刻、统一角度发射钻头/激光
|
||||
// ================================================================
|
||||
private static readonly Dictionary<string, ZuanTouXie_DieEvent> _waitingByFishId
|
||||
= new Dictionary<string, ZuanTouXie_DieEvent>();
|
||||
private static bool _releaseListenerBound;
|
||||
|
||||
/// <summary>绑定释放帧同步监听(进程内只绑一次;EventCenter 是纯 C# 单例不随场景销毁)</summary>
|
||||
private static void BindReleaseListener()
|
||||
{
|
||||
if (_releaseListenerBound) return;
|
||||
_releaseListenerBound = true;
|
||||
EventCenter.Instance.AddEventListener<SyncGameData>(
|
||||
ConstEventString.SyncGameData_ZuanTouRelease, OnZuanTouReleaseFrame);
|
||||
}
|
||||
|
||||
/// <summary>收到 zuanTouRelease 帧同步(所有端都会收到,含击杀者自己的回广播):
|
||||
/// 按 fishId 找到等待中的实例 → 记录角度并放行发射</summary>
|
||||
private static void OnZuanTouReleaseFrame(SyncGameData sync)
|
||||
{
|
||||
var data = FrameDataParser.Parse<FrameData_ZuanTouRelease>(sync.frameData);
|
||||
if (data == null || string.IsNullOrEmpty(data.fishId)) return;
|
||||
ZuanTouXie_DieEvent evt;
|
||||
if (_waitingByFishId.TryGetValue(data.fishId, out evt) && evt != null)
|
||||
evt.OnReleaseFrame(data.angle);
|
||||
}
|
||||
|
||||
/// <summary>实例侧收到释放帧:源角度按击杀者座位在本端的显示位置还原成世界角度,放行 MainRoutine 等待</summary>
|
||||
private void OnReleaseFrame(float sourceAngle)
|
||||
{
|
||||
_releaseAngle = SeatMirror.SourceAngleToWorld(sourceAngle, _sit != null && _sit.IsFlipped);
|
||||
_releaseReceived = true;
|
||||
Debug.Log($"[钻头蟹] 收到释放帧同步:fishId={_fishId} 源角度={sourceAngle} 本端世界角度={_releaseAngle}");
|
||||
}
|
||||
|
||||
/// <summary>发送 zuanTouRelease 帧同步:服务器广播给房间所有人(含自己回广播),各端收到后统一发射。
|
||||
/// 角度转"源角度"(发送方下方座位系)发送,接收端按击杀者座位在本端的显示位置还原。</summary>
|
||||
private void SendReleaseFrame(float worldAngle)
|
||||
{
|
||||
SyncGameData sgd = new SyncGameData
|
||||
{
|
||||
sitPos = SeatMirror.ToServer(_sit.posID),
|
||||
frameType = FrameDataType.zuanTouRelease,
|
||||
time = BeijingTimeManager.Instance.CurrentTimestampMs,
|
||||
frameData = new FrameData_ZuanTouRelease
|
||||
{
|
||||
userId = _sit.user,
|
||||
fishId = _fishId,
|
||||
angle = SeatMirror.WorldAngleToSource(worldAngle, _sit.IsFlipped),
|
||||
}
|
||||
};
|
||||
WebSocketSendList.SyncGameData(sgd);
|
||||
Debug.Log($"[钻头蟹] 已发送释放帧同步:fishId={_fishId} userId={_sit.user} 世界角度={worldAngle}");
|
||||
}
|
||||
|
||||
protected virtual IEnumerator MainRoutine()
|
||||
@@ -117,6 +186,14 @@ public class ZuanTouXie_DieEvent : DieEffect_TSY
|
||||
|
||||
if (paoBei <= 0 && _sit.consumeGoldNum > 0) paoBei = (int)_sit.consumeGoldNum;
|
||||
|
||||
// ⭐ 释放帧同步注册:fishId=宿主鱼 guid(Ws_DieFish 注入 monster),收到 zuanTouRelease 帧时按它找到本实例
|
||||
_fishId = monster != null ? monster.guid : null;
|
||||
if (!string.IsNullOrEmpty(_fishId))
|
||||
{
|
||||
BindReleaseListener();
|
||||
_waitingByFishId[_fishId] = this;
|
||||
}
|
||||
|
||||
// ===== 1. 炮台被钻头覆盖 → 生成钻头炮台 → 禁普通开火 =====
|
||||
// ⭐ 炮台/战神二选一:隐藏的是"当前显示的那个"——战神出战(currentZhanShen!=null)时隐藏战神
|
||||
// (turretParent 本来就是隐藏状态),否则隐藏普通炮台 turretParent
|
||||
@@ -134,8 +211,17 @@ public class ZuanTouXie_DieEvent : DieEffect_TSY
|
||||
}
|
||||
_sit.isDrillActive = true;
|
||||
|
||||
// ===== 2. 等待点击释放钻头(击杀者点击;他人/超时自动释放)=====
|
||||
// ===== 2. 等待释放钻头(⭐ 帧同步统一触发,多人界面一致)=====
|
||||
// 击杀者:点击(或超时 maxWaitTime) → 发 zuanTouRelease 帧同步;
|
||||
// 服务器广播给房间所有人(含击杀者回广播) → 所有端收到帧才发射 → 自己和别人界面同一时刻发射。
|
||||
// 机器人击杀:机器人不会点击,由房主延迟 otherReleaseDelay 代发(同 baoJiangEnd 房主代发模式)。
|
||||
// 其他真人玩家:不发帧,只等帧同步。
|
||||
// 兜底:等帧超过 maxWaitTime + releaseNetMargin 仍未收到(击杀者掉线没发帧) → 本地按当前瞄准角直接发射,避免卡死。
|
||||
bool isKiller = _sit.user == GameManager.Instance.user.id;
|
||||
float fireAngle;
|
||||
if (string.IsNullOrEmpty(_fishId))
|
||||
{
|
||||
// 防御:拿不到宿主鱼 guid(理论不会发生)→ 退回本地触发
|
||||
if (isKiller)
|
||||
{
|
||||
float end = Time.time + maxWaitTime;
|
||||
@@ -146,9 +232,41 @@ public class ZuanTouXie_DieEvent : DieEffect_TSY
|
||||
{
|
||||
yield return new WaitForSeconds(otherReleaseDelay);
|
||||
}
|
||||
fireAngle = _sit.currentAngle;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isKiller)
|
||||
{
|
||||
float end = Time.time + maxWaitTime;
|
||||
while (Time.time < end && !PressedThisFrame())
|
||||
yield return null;
|
||||
SendReleaseFrame(_sit.currentAngle); // 点击或超时 → 广播(含自己回广播)
|
||||
}
|
||||
else if ((_sit.isBot || (_sit.user != null && _sit.user.StartsWith("bot_")))
|
||||
&& BaoJiangNotSelfHandler.IsLocalRoomOwner())
|
||||
{
|
||||
// 机器人击杀:房主代发释放帧(机器人端无人点击)
|
||||
yield return new WaitForSeconds(otherReleaseDelay);
|
||||
SendReleaseFrame(_sit.currentAngle);
|
||||
}
|
||||
|
||||
// ===== 3. 发射钻头(普通子弹的创建方式)=====
|
||||
GameObject drill = FireDrill(isKiller);
|
||||
// 所有端(含击杀者,走服务器回广播)统一等帧再发射,角度用帧里带的保证方向一致
|
||||
float fallbackEnd = Time.time + maxWaitTime + releaseNetMargin;
|
||||
while (!_releaseReceived && Time.time < fallbackEnd)
|
||||
yield return null;
|
||||
if (!_releaseReceived)
|
||||
Debug.LogWarning($"[钻头蟹] ⚠未收到释放帧同步(击杀者可能掉线),本地兜底发射 fishId={_fishId}");
|
||||
fireAngle = _releaseReceived ? _releaseAngle : _sit.currentAngle;
|
||||
|
||||
// 已到发射时机,注销等待(防御:确认注册的还是本实例才删,避免误删后来覆盖注册的其他实例)
|
||||
ZuanTouXie_DieEvent registered;
|
||||
if (_waitingByFishId.TryGetValue(_fishId, out registered) && registered == this)
|
||||
_waitingByFishId.Remove(_fishId);
|
||||
}
|
||||
|
||||
// ===== 3. 发射钻头(普通子弹的创建方式;角度统一来自帧同步)=====
|
||||
GameObject drill = FireDrill(isKiller, fireAngle);
|
||||
|
||||
// ===== 4. 恢复普通炮台时机:钻头蟹=发射后立刻恢复(爆奖与炮台无关)=====
|
||||
if (!restoreTurretAfterProjectile)
|
||||
@@ -248,10 +366,10 @@ public class ZuanTouXie_DieEvent : DieEffect_TSY
|
||||
/// <summary>
|
||||
/// 按普通子弹方式发射钻头:位置=炮台子弹起点(3D 世界坐标);
|
||||
/// 旋转=用预制体默认旋转实例化(保留默认 X 轴姿态),之后只改 Z 轴;
|
||||
/// 前进方向由当前瞄准角单独算出传给 Init。
|
||||
/// 前进方向由发射角单独算出传给 Init(角度来自释放帧同步,各端一致)。
|
||||
/// 子类(激光蟹)可覆写此方法改为激活激光节点+扫鱼,返回临时对象供 while(drill!=null) 等待。
|
||||
/// </summary>
|
||||
protected virtual GameObject FireDrill(bool isKiller)
|
||||
protected virtual GameObject FireDrill(bool isKiller, float angle)
|
||||
{
|
||||
if (bulletPrefab == null) return null;
|
||||
|
||||
@@ -264,8 +382,8 @@ public class ZuanTouXie_DieEvent : DieEffect_TSY
|
||||
// 旋转:预制体默认旋转(保留默认 X 轴,不重置),Z 轴由 SpecialBulletMove 按 heading 设置
|
||||
GameObject drill = Instantiate(bulletPrefab, muzzle, bulletPrefab.transform.rotation);
|
||||
|
||||
// 前进方向:当前瞄准角 → 方向(同普通子弹的 angle 语义)
|
||||
Vector2 heading = Quaternion.Euler(0, 0, _sit.currentAngle) * Vector2.up;
|
||||
// 前进方向:释放帧同步带来的角度 → 方向(同普通子弹的 angle 语义)
|
||||
Vector2 heading = Quaternion.Euler(0, 0, angle) * Vector2.up;
|
||||
|
||||
// 无物理组件时补触发器,保证碰墙/碰鱼检测生效
|
||||
if (drill.GetComponent<Rigidbody2D>() == null)
|
||||
@@ -328,6 +446,9 @@ public class ZuanTouXie_DieEvent : DieEffect_TSY
|
||||
private void OnDestroy()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
// 释放帧同步:注销等待(事件被外部销毁时防止悬空引用)
|
||||
if (_fishId != null && _waitingByFishId.ContainsValue(this))
|
||||
_waitingByFishId.Remove(_fishId);
|
||||
// 兜底:事件被外部销毁时恢复座位状态(显示统一交给 RestoreTurretVisual 按当前装备恢复)
|
||||
if (_sit != null)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user