This commit is contained in:
2026-10-07 05:28:36 +08:00
parent 1321ed6c01
commit 4ac6d8309c
8 changed files with 112 additions and 20 deletions
@@ -218,6 +218,22 @@ MonoBehaviour:
m_bufferingTime: 0.1
m_ClipProperties:
volume: 0.172
--- !u!114 &-8174235504841704556
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 15c38f6fa1940124db1ab7f6fe7268d1, type: 3}
m_Name: Signal Emitter
m_EditorClassIdentifier:
m_Time: 10.066666666666666
m_Retroactive: 0
m_EmitOnce: 0
m_Asset: {fileID: 11400000, guid: 1508e534c5cd88d459d245dd3557a1c6, type: 2}
--- !u!114 &-8072677713721982766
MonoBehaviour:
m_ObjectHideFlags: 1
@@ -1079,6 +1095,7 @@ MonoBehaviour:
m_Markers:
m_Objects:
- {fileID: 6527845954037689115}
- {fileID: -8174235504841704556}
--- !u!114 &-4838957258320222283
MonoBehaviour:
m_ObjectHideFlags: 1
@@ -19,6 +19,7 @@ using Assets.MyGame.Scripts.Net.APIResponses.GameConfig_SubClass;
using Assets.MyGame.Scripts.Net.WebResponses;
using cfg;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -173,6 +174,16 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
/// <summary>特殊 Boss</summary>
public Dictionary<string, MonsterContral> fishDatas_hashCodekey_TS_Boss = new Dictionary<string, MonsterContral>();
// ----------------------------------------------------------------
// 击杀消息幂等去重(fishId|killtime → 处理时刻)
// 服务端可能对同一次击杀下发多条 RoomKillNotice(掉落推送之外的重复/重发),
// 同 fishId+killtime 只处理第一条,防止重复克隆爆奖特效/重复出 bjEndUI;
// MultiDeath(星际巡航等多次死亡玩法)每段 killtime 不同,不受影响。
// ----------------------------------------------------------------
private readonly Dictionary<string, float> _handledKillKeys = new Dictionary<string, float>();
private readonly List<string> _expiredKillKeys = new List<string>();
private const float KillDedupKeepSeconds = 60f; // 键保鲜期(秒),超时清理防字典增长
#if UNITY_EDITOR
// ⭐ 编辑器调试 List:与上方字典同步,方便在 Inspector 查看当前场上活鱼
public List<MonsterContral> fishList_All = new List<MonsterContral>();
@@ -1961,15 +1972,40 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
// 第二条与击杀消息同 fishId/killtime/gold,但只有 drop、没有 param。
// Boss 鱼 DieFish 是延迟回收(字典未即时移除),第二条若按击杀再处理一次:
// ① not-self 模式下 _pending[key] 被覆盖 → 前一个"爆奖中"BJEndUI 失去引用、永久残留在座位上;
// ② 重复克隆爆奖特效、重复走 DieFish → 特效/音效/爆奖双份。
// ② 重复克隆爆奖特效、重复走 DieFish → 特效/音效/爆奖双份(两个 Event 实例各出一次 bjEndUI)。
// 客户端目前没有掉落弹窗逻辑(见 RoomKillNotice.drop 注释),直接跳过;将来做掉落 UI 时在此处理。
if (killFish.drop != null && killFish.param == null)
// ⚠️ param 是 JToken:JSON "param":null 会被反序列化成 JValue(Null)(Type==JTokenType.Null),
// 不能只判 C# null(与 BossKillParam.cs 的判空写法保持一致),否则第二条漏拦 → 重复爆奖。
if (killFish.drop != null
&& (killFish.param == null || killFish.param.Type == JTokenType.Null))
{
NetLog.Log($"[CreateFishManager] 掉落道具推送(跳过击杀处理):fishId={killFish.fishId} " +
$"prop={killFish.drop.propNumber}×{killFish.drop.count}");
return;
}
// ⭐ 击杀消息幂等去重(第二道锁):同 fishId+killtime+beishu+gold 已处理过 → 判为同一次击杀的
// 重复/重发消息,直接跳过,防止重复走击杀流程(重复克隆爆奖特效 → 两个 Event 实例各出一次
// bjEndUI = 重复显示爆奖标志)。
// ⚠️ 掉落推送(param 为空)已在上面被拦、不会记录键,避免"掉落先到"时误挡真正的击杀消息;
// MultiDeath(星际巡航等多次死亡玩法)同一秒内多段的 killtime 可能相同,但各段 beishu/gold
// 不同,键里带上二者后正常放行。
string killKey = killFish.fishId + "|" + killFish.killtime + "|" + killFish.beishu + "|" + killFish.gold;
if (_handledKillKeys.ContainsKey(killKey))
{
NetLog.Log($"[CreateFishManager] 重复击杀消息(跳过):key={killKey}");
return;
}
_handledKillKeys[killKey] = Time.unscaledTime;
if (_handledKillKeys.Count >= 64) // 顺手清理过期键,防字典增长
{
float nowTime = Time.unscaledTime;
_expiredKillKeys.Clear();
foreach (var kv in _handledKillKeys)
if (nowTime - kv.Value > KillDedupKeepSeconds) _expiredKillKeys.Add(kv.Key);
foreach (var k in _expiredKillKeys) _handledKillKeys.Remove(k);
}
NetLog.Log($"[CreateFishManager] 杀鱼延迟:{DateTimeOffset.Now.ToUnixTimeSeconds() - killFish.timespan}s");
SitPosData sitData = null; //击杀玩家座位数据
foreach (var item in currGameType.list_Sits)
@@ -79,6 +79,16 @@ public class FLZY_JL_Boss : MonsterControl_BossBase
base.DieFish(obj);
animator.speed = 0;
GetComponent<Animator>().enabled = true;
// ⭐ 他人击杀(含机器人)时立即回池:
// 金龙/青龙不像其他 Boss(PTS/GSL 等)在 DieFish 里直接 Id_DieFish_NotFX,
// 而是靠 FLZY_BOSS_AnimatorLeave 的动画状态退出回调回池;但上面 animator.speed=0
// 把状态机冻住后,状态永远走不到 Exit → 回调永不触发 →
// 第二个玩家视角里死龙会一直冻在屏幕中央,整个"爆奖中"期间乃至结束后都不消失。
// 自己击杀保持原表现(尸体定格当爆奖背景,与自己流程已验收的效果一致)。
if (!isLocalCoin)
CreateFishManager.Instance.Id_DieFish_NotFX(guid);
action_effect_DieAward = (o) =>
{
if (obj == null) return;
@@ -138,7 +138,9 @@ public class JinLongEvent : BJRedPacketEvent
// 全部完成
tMP_Score.text = maxScore.ToString();
isAddScore = false;
JiaFen?.Invoke();
// ⭐ 不在这里直接调 JiaFen:1.5 秒后 OnScoreEnd(基类统一收尾)里会调一次,
// 这里多调一次会导致击杀者座位金币加两遍、baoJiangEnd 帧同步连发两条
// (日志已证实:同 fishId 收到两条 baoJiangEnd)。
Invoke(nameof(die), 1f);
Invoke(nameof(OnScoreEnd), 1.5f);
}
@@ -1094,7 +1094,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &8926196532408561376
RectTransform:
m_ObjectHideFlags: 0
@@ -1111,9 +1111,9 @@ RectTransform:
- {fileID: 5724427392508043228}
m_Father: {fileID: 38992651813133378}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 86, y: -60}
m_SizeDelta: {x: 68, y: 62}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4112040777350681982
@@ -4320,7 +4320,7 @@ MonoBehaviour:
list_BiaoQingZi: []
mySitAnim: {fileID: 448370382151683116}
mySitAnimOffsetLeft: {x: 70, y: 0, z: 0}
mySitAnimOffsetRight: {x: -70, y: 0, z: 0}
mySitAnimOffsetRight: {x: 55, y: 0, z: 0}
toggle_Lock: {fileID: 1095565694137767186}
toggle_Three: {fileID: 557993769889242771}
toggle_Shuang: {fileID: 6788830007509759241}
@@ -4377,6 +4377,9 @@ MonoBehaviour:
goldColumnOffsetX: 80
goldColumnDeltaX: 40
goldColumnOffsetY: 0
goldColumnOffsetXRight: 200
goldColumnDeltaXRight: 40
goldColumnOffsetYRight: 0
btn_NukeList: {fileID: 8750404718084592857}
nukeListPanel: {fileID: 8718899482206626550}
nukeItemTemplate: {fileID: 8717863517958971103}
@@ -4432,6 +4435,9 @@ MonoBehaviour:
deltaX: 0
offsetX: 0
offsetY: 0
deltaXRight: 40
offsetXRight: 100
offsetYRight: 0
heightCap: 255
scoreDivisor: 2
barHeightToPanelRatio: 0.47
@@ -12532,7 +12538,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 0}
m_AnchorMax: {x: 1, y: 0}
m_AnchoredPosition: {x: -388, y: 59.886642}
m_AnchoredPosition: {x: -463.33, y: 59.886642}
m_SizeDelta: {x: 812.1321, y: 186.42029}
m_Pivot: {x: 0.4217527, y: 0.32124478}
--- !u!114 &7267664368300524434
@@ -17279,7 +17285,7 @@ PrefabInstance:
- target: {fileID: 224349892793438657, guid: 6f077aa14d1a8e34687a3af5f0f1eae5,
type: 3}
propertyPath: m_SizeDelta.x
value: 6
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224349892793438657, guid: 6f077aa14d1a8e34687a3af5f0f1eae5,
type: 3}
@@ -17309,7 +17315,7 @@ PrefabInstance:
- target: {fileID: 224448299783411175, guid: 6f077aa14d1a8e34687a3af5f0f1eae5,
type: 3}
propertyPath: m_SizeDelta.x
value: 6
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224448299783411175, guid: 6f077aa14d1a8e34687a3af5f0f1eae5,
type: 3}
@@ -59,13 +59,19 @@ public class GameMainPanel_XJMH : GameMainPanel_FishBase
public GoldColumnController goldColumnController;
private GoldColumnController _goldColumn;
[Header("金币柱位置(面板上实时调,每次出柱生效)")]
[Tooltip("首柱相对座位的 X 偏移(以此处为准,控制器组件上的同名字段会被覆盖)")]
[Header("金币柱位置(面板上实时调,每次出柱生效;左右座位各一套)")]
[Tooltip("左座位:首柱相对座位的 X 偏移(以此处为准,控制器组件上的同名字段会被覆盖)")]
public float goldColumnOffsetX = 150f;
[Tooltip("相邻金币柱的 X 间距")]
[Tooltip("左座位:相邻金币柱的 X 间距")]
public float goldColumnDeltaX = 40f;
[Tooltip("金币柱 Y(相对面板底边,0=贴着底边往上长)")]
[Tooltip("左座位:金币柱 Y(相对面板底边,0=贴着底边往上长)")]
public float goldColumnOffsetY = 0f;
[Tooltip("右座位:首柱相对座位的 X 偏移(左/右按座位在面板中线的左/右判定)")]
public float goldColumnOffsetXRight = 150f;
[Tooltip("右座位:相邻金币柱的 X 间距")]
public float goldColumnDeltaXRight = 40f;
[Tooltip("右座位:金币柱 Y(相对面板底边,0=贴着底边往上长)")]
public float goldColumnOffsetYRight = 0f;
// ==================== 核弹列表(按钮 + 缩放面板) ====================
[Header("核弹列表(按钮 + 缩放面板)")]
@@ -495,9 +501,13 @@ public class GameMainPanel_XJMH : GameMainPanel_FishBase
}
// ⭐ 位置参数以面板上的字段为准(每次出柱前同步,Play 模式下改完立即生效)
// 左右座位各一套:控制器内按座位在面板中线的左/右(sign)自动选对应那组
_goldColumn.offsetX = goldColumnOffsetX;
_goldColumn.deltaX = goldColumnDeltaX;
_goldColumn.offsetY = goldColumnOffsetY;
_goldColumn.offsetXRight = goldColumnOffsetXRight;
_goldColumn.deltaXRight = goldColumnDeltaXRight;
_goldColumn.offsetYRight = goldColumnOffsetYRight;
bool isSelf = currSitData != null && sit == currSitData;
_goldColumn.ShowOne(sit, earn, isSelf);
@@ -27,11 +27,15 @@ public class GoldColumnController : MonoBehaviour
public int maxColumns = 5;
[Tooltip("队伍长度超过此值时,队头不再额外停顿(快速滚动)")]
public int crowdThreshold = 3;
// ⚠️ 这三个位置参数由面板脚本 GameMainPanel_XJMH 每帧同步(goldColumnOffsetX/DeltaX/OffsetY),
// 请在面板的 GameMainPanel_XJMH 组件上调,不要在这里调(这里改了会被覆盖)。
// ⚠️ 这六个位置参数由面板脚本 GameMainPanel_XJMH 每次出柱前同步
// (无后缀=左座位、Right=右座位),请在面板的 GameMainPanel_XJMH 组件上调,不要在这里调(这里改了会被覆盖)。
// 左右判定:座位在面板中线左侧(sign<0)用无后缀那组,右侧(sign>0)用 Right 那组。
[HideInInspector] public float deltaX = 40f;
[HideInInspector] public float offsetX = 100f;
[HideInInspector] public float offsetY = 0f;
[HideInInspector] public float deltaXRight = 40f;
[HideInInspector] public float offsetXRight = 100f;
[HideInInspector] public float offsetYRight = 0f;
[Header("高度映射(沿用 PW 公式)")]
[Tooltip("高度封顶值:score/scoreDivisor 达到此值即顶格")]
@@ -243,8 +247,12 @@ public class GoldColumnController : MonoBehaviour
SetNum(item, ChangeUnit(pending.earn));
// 位置:X = 座位位置 + 方向 × (首柱外偏 + 序号间距);Y = offsetY(0=贴面板底边,柱子往上长)
float x = pending.basePos.x + pending.sign * (offsetX + item.index * deltaX);
float y = offsetY;
// ⭐ 左右座位各一套偏移:sign<0=座位在面板左 → 无后缀那组;sign>0=右 → Right 那组
float offX = pending.sign < 0f ? offsetX : offsetXRight;
float dltX = pending.sign < 0f ? deltaX : deltaXRight;
float offY = pending.sign < 0f ? offsetY : offsetYRight;
float x = pending.basePos.x + pending.sign * (offX + item.index * dltX);
float y = offY;
item.rt.anchoredPosition = new Vector2(x, y);
// 高度:默认沿用 PW 公式 score=earn/炮倍 → mul=score/scoreDivisor → 封顶归一化;
@@ -324,7 +332,10 @@ public class GoldColumnController : MonoBehaviour
{
var item = list[i];
item.index = i;
float targetX = item.basePos.x + item.sign * (offsetX + i * deltaX);
// ⭐ 与 SpawnOne 同规则:按该柱所在座位的左/右(sign)选对应偏移
float offX = item.sign < 0f ? offsetX : offsetXRight;
float dltX = item.sign < 0f ? deltaX : deltaXRight;
float targetX = item.basePos.x + item.sign * (offX + i * dltX);
item.moveTween?.Kill();
item.moveTween = item.rt.DOAnchorPosX(targetX, shiftDuration).SetEase(Ease.Linear);
}