This commit is contained in:
2026-09-26 15:35:40 +08:00
parent b777f0f6aa
commit cbaf4afd95
47 changed files with 454 additions and 60 deletions
+5
View File
@@ -887,6 +887,8 @@ MonoBehaviour:
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -894,6 +896,9 @@ MonoBehaviour:
depthOffset: 560
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &1470420886364660
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -193,6 +193,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -200,6 +203,9 @@ MonoBehaviour:
depthOffset: 570
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &233454476363136655
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -94,6 +94,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -101,6 +104,9 @@ MonoBehaviour:
depthOffset: 510
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &23241636797705634
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -3247,6 +3247,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -3254,6 +3257,9 @@ MonoBehaviour:
depthOffset: 460
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &1529740580748378
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -133,6 +133,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -140,6 +143,9 @@ MonoBehaviour:
depthOffset: 450
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &1064317485118747
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -94,6 +94,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -101,6 +104,9 @@ MonoBehaviour:
depthOffset: 440
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &91986277059817027
GameObject:
m_ObjectHideFlags: 0
+5
View File
@@ -95,6 +95,8 @@ MonoBehaviour:
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -102,6 +104,9 @@ MonoBehaviour:
depthOffset: 600
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &126286391056136836
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -94,6 +94,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -101,6 +104,9 @@ MonoBehaviour:
depthOffset: 520
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &260146002311135624
GameObject:
m_ObjectHideFlags: 0
+3 -3
View File
@@ -93,10 +93,10 @@ MonoBehaviour:
- {fileID: 5085328304069200089}
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: -120
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 1
useRouteFlipX: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
+6
View File
@@ -3732,6 +3732,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -3739,6 +3742,9 @@ MonoBehaviour:
depthOffset: 430
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &1785410037514682
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -5519,6 +5519,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -5526,6 +5529,9 @@ MonoBehaviour:
depthOffset: 470
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &1918114390380129
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -95,6 +95,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -102,6 +105,9 @@ MonoBehaviour:
depthOffset: 490
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!58 &-6866065024429339140
CircleCollider2D:
m_ObjectHideFlags: 0
+6
View File
@@ -95,6 +95,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -102,6 +105,9 @@ MonoBehaviour:
depthOffset: 540
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!58 &-1529010931143686574
CircleCollider2D:
m_ObjectHideFlags: 0
+7 -1
View File
@@ -15753,13 +15753,19 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 30
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
ischild0: 1
depthOffset: 0
depthOffset: 370
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &1594108707966477
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -5106,6 +5106,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -5113,6 +5116,9 @@ MonoBehaviour:
depthOffset: 410
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &1500587202456742
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -94,6 +94,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 30
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -101,6 +104,9 @@ MonoBehaviour:
depthOffset: 480
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &41597890750949400
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -332,6 +332,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -339,6 +342,9 @@ MonoBehaviour:
depthOffset: 530
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &1361146795606657
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -9011,6 +9011,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -9018,6 +9021,9 @@ MonoBehaviour:
depthOffset: 360
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &1819357568620622
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -5436,6 +5436,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -5443,6 +5446,9 @@ MonoBehaviour:
depthOffset: 390
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &1491235563968188
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -162,6 +162,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -169,6 +172,9 @@ MonoBehaviour:
depthOffset: 420
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &1088853801920561
GameObject:
m_ObjectHideFlags: 0
+6
View File
@@ -93,6 +93,9 @@ MonoBehaviour:
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -100,6 +103,9 @@ MonoBehaviour:
depthOffset: 580
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &65381062315250812
GameObject:
m_ObjectHideFlags: 0
+8 -1
View File
@@ -172,10 +172,14 @@ MonoBehaviour:
yzID:
colliderList: []
lockPoint: {fileID: 7919990334340192164}
bingDongEffect: {fileID: 4067042857930265640}
bingDongEffect:
- {fileID: 0}
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -183,6 +187,9 @@ MonoBehaviour:
depthOffset: 560
is2DFish: 0
sortOrder2D: 0
drawMoveLine: 0
drawLineSegments: 60
drawLineColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
--- !u!1 &173797174121316484
GameObject:
m_ObjectHideFlags: 0
@@ -130,6 +130,8 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
[Tooltip("按 M 键生成的鱼编号(配置 fishNumber)")]
public string debugSpawnFishNumber = "11043";
public bool isleft = true;
[Tooltip("按M键批量模式:激活=本地在所有普通鱼路线上各生成一条(不走服务端);不激活=走服务端出一条")]
public bool debugSpawnAllLines = false;
private Dictionary<string, CharacterStateType> frozenFishStates = new Dictionary<string, CharacterStateType>();
private Dictionary<string, bool> frozenFishIsMoves = new Dictionary<string, bool>();
@@ -333,7 +335,23 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
Debug.LogWarning("[CreateFishManager] 当前未在房间中(roomId 为空),无法出鱼");
return;
}
APIManager.TempSpawnFish(roomId, debugSpawnFishNumber,1,30, isleft?1:2);
// ⭐ debugSpawnAllLines 激活:本地在所有普通鱼路线("曲线"+id)上各生成一条(不走服务端);
// 不激活:走服务端 AdminApi 出一条(现状)
if (debugSpawnAllLines)
{
int spawned = 0;
foreach (var key in MonsterMoveLine.dic_BezierCurveDatas.Keys)
{
if (key == null || !key.StartsWith("曲线")) continue;
if (!int.TryParse(key.Substring(2), out int rid)) continue;
if (ManualSpawn(debugSpawnFishNumber, rid, 30)) spawned++;
}
Debug.Log($"[CreateFishManager] 调试批量出鱼(所有路线): fishNumber={debugSpawnFishNumber}, 共生成{spawned}条");
}
else
{
APIManager.TempSpawnFish(roomId, debugSpawnFishNumber,1,30, isleft?1:2);
}
}
}
@@ -2078,28 +2096,30 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
}
// 特殊鱼:金币飞向炮台(池化,动画事件播完手动回池)
// ⭐ Get 不设置位置:DieEffect_TSY(ZDX 炸弹蟹等)爆奖完全用预制体里配置好的位置(含 y 轴);
// 只有 BJBase 金币特效按鱼死亡位置摆放(startPoint 依赖)
var award = EffectPool.Instance.Get(
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAward,
monster.transform.position, Quaternion.identity, currGameType.subPanelParent.parent,
currGameType.subPanelParent.parent,
autoReturn: false);
try
{
award.GetComponent<RectTransform>().anchoredPosition =
CoordinateTransformation.WorldPos_ToCanvas(
monster.transform.position,
GetUiCanvas());
}
catch
{
award.transform.position = monster.transform.position;
}
var coinScript = award.GetComponent<BJBase>();
if (coinScript != null)
{
// 普通爆奖(BJBase 体系)
// 普通爆奖(BJBase 体系):摆到鱼死亡位置(画布坐标),startPoint 依赖
try
{
award.GetComponent<RectTransform>().anchoredPosition =
CoordinateTransformation.WorldPos_ToCanvas(
monster.transform.position,
GetUiCanvas());
}
catch
{
award.transform.position = monster.transform.position;
}
coinScript.startPoint = award.transform.position;
coinScript.Init(killFish.gold, sitData.transform.position, killFish.userId, sitData.posID, killFish.param);
coinScript.isLocalCoin = sitData.user == GameManager.Instance.user.id;
@@ -90,9 +90,17 @@ namespace Assets.MyGame.Scripts.Net
case "BeginGame":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<ChessGameRoom>>(msg.ToString()).arguments[0];
roomMainPanel.subUIManager.CloseAll();
roomMainPanel.StartGame(msg);
roomMainPanel.UpdateUserData(va);
if (roomMainPanel != null && roomMainPanel.IsChessGameActive && va.state == 2)
{
// 对局中收到 BeginGame = 断线重连/取消挂起的房间同步:只刷新玩家数据,不重置对局
roomMainPanel.UpdateUserData(va);
}
else
{
roomMainPanel.subUIManager.CloseAll();
roomMainPanel.StartGame(msg);
roomMainPanel.UpdateUserData(va);
}
}
break;
@@ -174,6 +182,12 @@ namespace Assets.MyGame.Scripts.Net
{
roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQDefeated);
}
// 服务端已结算:本地对局同步置结束 + 停表(防后台方回前台后对局还在跑)
if (roomMainPanel != null)
{
roomMainPanel.StopWaitCountdown();
roomMainPanel.MarkChessGameOverByServer();
}
// 结算后按结果实时更新房间双方金币显示
roomMainPanel.ApplySettlementGold(va);
}
@@ -222,7 +236,16 @@ namespace Assets.MyGame.Scripts.Net
}
break;
case "Disconnect":
roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQWin);
{
// 对方断线 { userId, disconnectTime, timeoutSeconds }:只提示等待重连,
// 服务端超时未重连会自动判负推 GameEnd(心跳里的 CheckTimeout 也会触发检查)
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<Dictionary<string, object>>>(msg).arguments[0];
string disUserId = va["userId"]?.ToString();
int disTimeout = 60;
if (va.ContainsKey("timeoutSeconds") && va["timeoutSeconds"] != null)
int.TryParse(va["timeoutSeconds"].ToString(), out disTimeout);
if (roomMainPanel != null) roomMainPanel.OnOpponentDisconnected(disUserId, disTimeout);
}
break;
case "GameError":
{
@@ -251,6 +274,7 @@ namespace Assets.MyGame.Scripts.Net
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<Dictionary<string, object>>>(msg).arguments[0];
Debug.Log($"[Chess] 重连: userId={va["userId"]}, userName={va["userName"]}");
if (roomMainPanel != null) roomMainPanel.OnOpponentReconnected(va["userId"]?.ToString());
}
break;
@@ -272,6 +296,8 @@ namespace Assets.MyGame.Scripts.Net
roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQWin);
else
roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQDefeated);
// 服务端已结算:本地对局同步置结束(防后台方回前台后对局还在跑)
if (roomMainPanel != null) roomMainPanel.MarkChessGameOverByServer();
// 结算后按结果实时更新房间双方金币显示
roomMainPanel.ApplySettlementGold(winUserId);
}
@@ -316,9 +342,9 @@ namespace Assets.MyGame.Scripts.Net
case "PlayerAfkNotice":
{
// 玩家挂起通知 { userId }(房间组推送;挂起仅表现提示,不影响服务端操作校验)
// 表现层(座位挂起标记)由 UI 对话接入
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<PlayerAfkNoticeData>>(msg).arguments[0];
Debug.Log($"[Chess] 玩家挂起: userId={va?.userId}");
if (roomMainPanel != null) roomMainPanel.OnPlayerAfkNotice(va?.userId);
}
break;
@@ -327,6 +353,7 @@ namespace Assets.MyGame.Scripts.Net
// 玩家取消挂起通知 { userId }(房间组推送;发起者本人另收 BeginGame 恢复房间同步)
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<PlayerAfkNoticeData>>(msg).arguments[0];
Debug.Log($"[Chess] 玩家取消挂起: userId={va?.userId}");
if (roomMainPanel != null) roomMainPanel.OnPlayerCancelAfkNotice(va?.userId);
}
break;
@@ -26,7 +26,7 @@ Transform:
m_GameObject: {fileID: 7036353727337932811}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 4000, z: 0}
m_LocalPosition: {x: 5000, y: 4000, z: 0}
m_LocalScale: {x: 5, y: 5, z: 5}
m_ConstrainProportionsScale: 1
m_Children:
@@ -52,7 +52,6 @@ MonoBehaviour:
fishScore: 0
bJbeishu: 0
maxScore: 0
startOffset: {x: 0, y: 0, z: -100}
isSelf: 0
isScoreEnd: 0
useScorePanel: 1
@@ -229,3 +228,4 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
subCamera: {fileID: 0}
isFishCamera: 0
@@ -648,7 +648,7 @@ MonoBehaviour:
rotationPY: 70
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -689,7 +689,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -693,7 +693,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -389,7 +389,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -421,7 +421,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -903,7 +903,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -206,7 +206,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 1
@@ -212,7 +212,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 1
@@ -366,7 +366,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 1
@@ -206,7 +206,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 1
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 1
@@ -212,7 +212,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 1
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 1
@@ -285,19 +285,19 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 4703437598541787, guid: ee1e72c133ca6124485715190eaa1e3a, type: 3}
propertyPath: m_LocalRotation.w
value: 0.7071068
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 4703437598541787, guid: ee1e72c133ca6124485715190eaa1e3a, type: 3}
propertyPath: m_LocalRotation.x
value: -0.7071068
value: -0.5
objectReference: {fileID: 0}
- target: {fileID: 4703437598541787, guid: ee1e72c133ca6124485715190eaa1e3a, type: 3}
propertyPath: m_LocalRotation.y
value: 0
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 4703437598541787, guid: ee1e72c133ca6124485715190eaa1e3a, type: 3}
propertyPath: m_LocalRotation.z
value: 0
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 4703437598541787, guid: ee1e72c133ca6124485715190eaa1e3a, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
@@ -305,7 +305,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 4703437598541787, guid: ee1e72c133ca6124485715190eaa1e3a, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: -0
value: 90
objectReference: {fileID: 0}
- target: {fileID: 4703437598541787, guid: ee1e72c133ca6124485715190eaa1e3a, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
@@ -206,7 +206,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 1
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 1
@@ -212,7 +212,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 1
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 1
@@ -284,7 +284,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -206,7 +206,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 1
@@ -206,7 +206,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 1
@@ -345,7 +345,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -501,7 +501,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -727,7 +727,7 @@ MonoBehaviour:
rotationPY: 90
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 1
useRouteFlipX: 0
baseMmove: 0
isRotate: 1
isNot1: 0
@@ -241,4 +241,16 @@ public class ChessGameManager
winnerCamp = winner;
OnGameOver?.Invoke(winner);
}
/// <summary>
/// 外部强制结束对局(挂起判负 / 服务端结算同步)。
/// silent=true 只置结束状态、不触发 OnGameOver(调用方自行上报和弹面板,避免重复)
/// </summary>
public void ForceGameOver(ChessCamp winner, bool silent = false)
{
if (isGameOver) return;
isGameOver = true;
winnerCamp = winner;
if (!silent) OnGameOver?.Invoke(winner);
}
}
@@ -225,6 +225,7 @@ public class ChineseChessMainPanel : GameMainPanel_Base
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<ChessGameRoom>>(msg.ToString());
ChessGameRoom chessGameRoom = va.arguments[0];
roomData = chessGameRoom; // 记录房间数据(开象棋对局时要取阵营)
StartHeartbeat(); // 房间心跳(更新活跃时间 + 断线超时检查)
UpdateUserData(chessGameRoom);
switch (chessGameRoom.users.Count)
@@ -252,7 +253,7 @@ public class ChineseChessMainPanel : GameMainPanel_Base
if (isRoomOwner)
{
btn_StartGame.gameObject.SetActive(true);
//btn_StartGame.gameObject.SetActive(true);
btn_StartGame.onClick.RemoveAllListeners();
btn_StartGame.onClick.AddListener(() =>
{
@@ -474,6 +475,11 @@ public class ChineseChessMainPanel : GameMainPanel_Base
{
if (roomData == null || roomData.users == null) return;
// 新一局开始:清挂起状态
isMyAfk = false;
isOpponentAfk = false;
StopAfkMonitor();
// 我的阵营:RedisGameUser.type 1 黑方 2 红方
var me = roomData.users.Find(u => u.userId == GameManager.Instance.user.BaseInfo.number);
ChessCamp myCamp = me != null ? (ChessCamp)me.type : ChessCamp.Red;
@@ -624,7 +630,207 @@ public class ChineseChessMainPanel : GameMainPanel_Base
#endregion
/// <summary>发送象棋帧同步数据(SyncGameData 通道)</summary>
#region 玩家挂起(退到后台)/ 断线 / 房间心跳
/// <summary>挂起判负秒数:挂起最多 120s 超时判输;双方都挂起时,先挂起的判输</summary>
private const float AfkTimeoutSeconds = 120f;
/// <summary>挂起判负毫秒数(北京时间戳比较用)</summary>
private const long AfkTimeoutMs = (long)(AfkTimeoutSeconds * 1000f);
/// <summary>当前北京时间(毫秒,服务端同步)</summary>
private static long NowMs => BeijingTimeManager.Instance.CurrentTimestampMs;
private bool isMyAfk; // 我是否挂起中(退到后台)
private long myAfkStartTimeMs; // 我挂起开始时间(北京时间毫秒)
private bool isOpponentAfk; // 对方是否挂起中
private long opponentAfkStartTimeMs; // 对方挂起开始时间(北京时间毫秒)
private Coroutine afkMonitorCoroutine;
private Coroutine heartbeatCoroutine;
/// <summary>是否处于对局中(挂起/断线/重连处理只对进行中的对局生效)</summary>
public bool IsChessGameActive => chessGameManager != null && !chessGameManager.isGameOver;
private string MyChessUserId => GameManager.Instance.user.BaseInfo.number;
private string OpponentChessUserId
{
get
{
if (roomData?.users == null) return null;
var op = roomData.users.Find(u => u.userId != MyChessUserId);
return op?.userId;
}
}
/// <summary>
/// 应用切后台/回前台(iOS/Android 都会触发 OnApplicationPause):
/// 退到后台 = 挂起(发 SetPlayerAfk);回前台 = 取消挂起(发 CancelPlayerAfk,本人会收到 BeginGame 恢复同步)
/// </summary>
private void OnApplicationPause(bool pause)
{
if (roomData == null || !IsChessGameActive) return; // 只对进行中的对局处理
if (pause)
{
isMyAfk = true;
myAfkStartTimeMs = NowMs;
StopWaitCountdown(); // 后台期间停走棋倒计时,回前台重新计
WebSocketSendList.SetPlayerAfkChineseChess(roomData.roomId, MyChessUserId);
}
else if (isMyAfk)
{
isMyAfk = false;
WebSocketSendList.CancelPlayerAfkChineseChess(roomData.roomId, MyChessUserId);
// ===== 挂起判定(北京时间,回前台必判,弹对应结果面板):
// 谁挂起满 120s 谁输;双方都超时 → 先挂起的输 =====
long now = NowMs;
bool iTimedOut = now - myAfkStartTimeMs >= AfkTimeoutMs;
bool oppTimedOut = isOpponentAfk && now - opponentAfkStartTimeMs >= AfkTimeoutMs;
ChessCamp oppCamp = chessGameManager.myCamp == ChessCamp.Red ? ChessCamp.Black : ChessCamp.Red;
if (oppTimedOut && (!iTimedOut || opponentAfkStartTimeMs <= myAfkStartTimeMs))
{
JudgeAfkLose(chessGameManager.myCamp); // 对方超时(或双方都超时但对方先挂起)→ 对方输
return;
}
if (iTimedOut)
{
JudgeAfkLose(oppCamp); // 我挂起超时 → 我输(本地立即弹失败面板)
return;
}
// 都没超时 → 继续对局(对方挂起监控继续跑)
// 恢复走棋倒计时
OnChessTurnChanged(chessGameManager.currentTurn);
}
}
/// <summary>PlayerAfkNotice:对方退到后台 → 提示 + 开始 120s 判负监控</summary>
public void OnPlayerAfkNotice(string userId)
{
if (!IsChessGameActive || userId != OpponentChessUserId) return;
isOpponentAfk = true;
opponentAfkStartTimeMs = NowMs;
if (chessBoard != null) chessBoard.ShowToast($"对方已挂起,{AfkTimeoutSeconds:0}秒内未恢复将判负");
StartAfkMonitor();
}
/// <summary>PlayerCancelAfkNotice:对方回前台 → 清除挂起,继续对局</summary>
public void OnPlayerCancelAfkNotice(string userId)
{
if (userId != OpponentChessUserId) return;
isOpponentAfk = false;
StopAfkMonitor();
if (chessBoard != null) chessBoard.ShowToast("对方已恢复");
}
/// <summary>对方挂起监控:挂起满 120s → 对方判输(我在前台时运行;我挂起时冻结,回前台再判)</summary>
private IEnumerator AfkMonitorRoutine()
{
while (IsChessGameActive && isOpponentAfk)
{
if (NowMs - opponentAfkStartTimeMs >= AfkTimeoutMs)
{
JudgeAfkLose(chessGameManager.myCamp); // 对方挂起超时 → 对方输
yield break;
}
yield return new WaitForSeconds(1f);
}
}
private void StartAfkMonitor()
{
StopAfkMonitor();
if (IsChessGameActive && isActiveAndEnabled)
afkMonitorCoroutine = StartCoroutine(AfkMonitorRoutine());
}
private void StopAfkMonitor()
{
if (afkMonitorCoroutine != null)
{
StopCoroutine(afkMonitorCoroutine);
afkMonitorCoroutine = null;
}
}
/// <summary>
/// 挂起判负结算:由判定方直接上报 EndGame(对方可能还在后台收不到,不能只靠赢家上报)
/// </summary>
private void JudgeAfkLose(ChessCamp winnerCamp)
{
if (!IsChessGameActive) return;
StopAfkMonitor();
isOpponentAfk = false;
isMyAfk = false;
StopWaitCountdown();
bool iWin = winnerCamp == chessGameManager.myCamp;
string winnerId = iWin ? MyChessUserId : OpponentChessUserId;
if (roomData != null && !string.IsNullOrEmpty(winnerId))
WebSocketSendList.EndGameChineseChess(roomData.roomId, winnerId); // EndGame 第二参 = 胜者 userId
// 本地对局置结束(静默:不走 OnChessGameOver 的重复上报,上面已上报)
chessGameManager.ForceGameOver(winnerCamp, silent: true);
subUIManager.ShowAsync(iWin ? UIPanel.ZGXQWin : UIPanel.ZGXQDefeated);
}
/// <summary>Disconnect:对方断线 → 停倒计时 + 提示(服务端超时未重连自动判负推 GameEnd)</summary>
public void OnOpponentDisconnected(string userId, int timeoutSeconds)
{
if (!IsChessGameActive || userId != OpponentChessUserId) return;
StopWaitCountdown();
if (chessBoard != null)
chessBoard.ShowToast($"对方断线,{Mathf.Max(1, timeoutSeconds)}秒内未重连将判负");
}
/// <summary>Reconnect:对方重连 → 恢复走棋倒计时</summary>
public void OnOpponentReconnected(string userId)
{
if (!IsChessGameActive || userId != OpponentChessUserId) return;
if (chessBoard != null) chessBoard.ShowToast("对方已重连");
OnChessTurnChanged(chessGameManager.currentTurn);
}
/// <summary>服务端结算通知(EndGameSuccess/GameEnd)→ 本地对局同步置结束,防止后台方回前台后对局还在跑</summary>
public void MarkChessGameOverByServer()
{
if (chessGameManager != null && !chessGameManager.isGameOver)
chessGameManager.ForceGameOver(chessGameManager.myCamp, silent: true);
}
/// <summary>房间心跳:更新房间活跃时间 + 触发断线超时检查(协议 2.11 / 2.12,每 30s)</summary>
private IEnumerator HeartbeatRoutine()
{
var wait = new WaitForSeconds(30f);
while (true)
{
yield return wait;
if (roomData != null && !string.IsNullOrEmpty(roomData.roomId))
{
WebSocketSendList.HeartbeatChineseChess(roomData.roomId);
WebSocketSendList.CheckTimeoutChineseChess(roomData.roomId);
}
}
}
private void StartHeartbeat()
{
StopHeartbeat();
if (isActiveAndEnabled) heartbeatCoroutine = StartCoroutine(HeartbeatRoutine());
}
private void StopHeartbeat()
{
if (heartbeatCoroutine != null)
{
StopCoroutine(heartbeatCoroutine);
heartbeatCoroutine = null;
}
}
#endregion
private void SendChessNetData(ChessNetData data)
{
if (roomData == null || chessGameManager == null || data == null) return;
@@ -692,6 +898,10 @@ public class ChineseChessMainPanel : GameMainPanel_Base
chessGameManager = null;
}
StopWaitCountdown(); // 停走棋倒计时
StopAfkMonitor(); // 停挂起监控
StopHeartbeat(); // 停房间心跳
isMyAfk = false;
isOpponentAfk = false;
if (chessBoard != null) chessBoard.Clear();
if (autoChessBoardRect != null)
{
@@ -35,14 +35,14 @@ public class UIChineseChessRoomExplain : SubUIBase
WebSocketSendList.GameReadyChineseChess(v.roomId,GameManager.Instance.user.BaseInfo.number);
uIManager.Close(UIPanel.ZGXQGameExplain);
});
btn_Close.onClick.RemoveAllListeners();
btn_Close.onClick.AddListener(() =>
{
uIManager.CloseAll();
//btn_Close.onClick.RemoveAllListeners();
//btn_Close.onClick.AddListener(() =>
//{
// uIManager.CloseAll();
WebSocketSendList.ExitRoomChineseChess(v.roomId, GameManager.Instance.user.BaseInfo.number);
// WebSocketSendList.ExitRoomChineseChess(v.roomId, GameManager.Instance.user.BaseInfo.number);
});
//});
}
}