This commit is contained in:
2026-07-30 23:54:35 +08:00
parent fafc5c8bb7
commit 5ad1ab1d54
6 changed files with 148 additions and 43 deletions
@@ -66,6 +66,19 @@ namespace Assets.MyGame.Scripts.Net.WebResponses
public List<string> bulletIds;
public SyncGameData extra;
public bool isBot;
/// <summary>服务端权威:每发子弹扣费明细(bulletId + gold)</summary>
public List<BulletGold> bullets;
/// <summary>服务端时间戳</summary>
public long timestamp;
}
/// <summary>
/// 单发子弹扣费明细:服务端在 FireCannon 广播里返回的权威扣费金额
/// </summary>
[Serializable]
public class BulletGold
{
public string bulletId;
public long gold;
}
/// <summary>
/// 同步游戏数据
@@ -445,6 +445,9 @@ namespace Assets.MyGame.Scripts.Net
case "SyncPlayerGold":
WebSocketReponseList.SyncPlayerGold(msg);
break;
case "ChangeGunMultipleNotice":
WebSocketReponseList.ChangeGunMultipleNotice(msg);
break;
case "GetRoomInfoResult":
WebSocketReponseList.GetRoomInfoResult(msg);
@@ -579,14 +582,9 @@ namespace Assets.MyGame.Scripts.Net
}
break;
case "ChangeGunMultipleNotice":
{
var data = JsonConvert.DeserializeObject<WebSocketResponseBase<Dictionary<string, object>>>(msg).arguments[0];
// { userId, multiple }
if (data != null)
Debug.Log($"[FishGameHub] 炮倍变更: userId={data["userId"]}, multiple={data["multiple"]}");
}
break;
//case "ChangeGunMultipleNotice":
// WebSocketReponseList.ChangeGunMultipleNotice(msg);
// break;
case "UsePropNotice":
{
@@ -220,6 +220,22 @@ namespace Assets.MyGame.Scripts.Net
}
}
/// <summary>
/// 炮倍变更通知:服务端确认玩家炮倍切换后广播给房间内所有玩家。
/// 客户端收到后才应用新炮倍到 consumeGoldNum 和 UI,避免开火扣费不一致。
/// </summary>
public static void ChangeGunMultipleNotice(string msg)
{
var data = JsonConvert.DeserializeObject<WebSocketResponseBase<ChangeGunMultipleNotice>>(msg).arguments[0];
var panel = UIManager.Instance.Get<GameMainPanel_FishBase>(UIPanel.MainGamePanelDefault)
?? UIManager.Instance.Get<GameMainPanel_FishBase>(UIPanel.MainGamePanelFLZY)
?? UIManager.Instance.Get<GameMainPanel_FishBase>(UIPanel.MainGamePanelYYSC)
?? UIManager.Instance.Get<GameMainPanel_FishBase>(UIPanel.MainGamePanelXJMH)
?? UIManager.Instance.Get<GameMainPanel_FishBase>(UIPanel.MainGamePanelHHWK);
if (panel == null) return;
panel.ApplyCannonMultiple(data.userId, data.multiple);
}
/// <summary>
/// 后端推送 SyncPlayerGold 时同步本地玩家金币(底层值 + UI 显示)。
/// 之前只更新 goldNum 不刷新 tMP_GoldNum.text,导致 UI 显示和后端 authoritative 值不同步,
+18 -9
View File
@@ -597,15 +597,18 @@ public class SitPosData : MonoBehaviour
// ==================== 金币消耗 ====================
public bool ConsumeGold(int mul = 1)
public bool ConsumeGold(int mul = 1, long fireConsumeGold = 0)
{
long temp = goldNum - consumeGoldNum * mul;
// fireConsumeGold>0: 用服务端返回的该发子弹权威扣费金额
// fireConsumeGold=0: 回退到本地 consumeGoldNum * mul
long useConsume = fireConsumeGold > 0 ? fireConsumeGold : consumeGoldNum * mul;
long temp = goldNum - useConsume;
if (temp < 0) return false;
long oldGold = goldNum;
goldNum = temp;
tMP_GoldNum.text = goldNum.ToString();
// ⭐ 记录金币流水:已扣钱(开火消耗,本地已执行)
GameManager.LogGoldFlowAppliedCost("Fire", $"开火消耗 原有金币={oldGold} 消耗金币=-{consumeGoldNum * mul} 剩余金币={goldNum} 座位={posID} 房间={GameManager.Instance.gameData?.currRoomData?.roomId}");
GameManager.LogGoldFlowAppliedCost("Fire", $"开火消耗 原有金币={oldGold} 消耗金币=-{useConsume} 剩余金币={goldNum} 座位={posID} 房间={GameManager.Instance.gameData?.currRoomData?.roomId}");
return true;
}
@@ -631,9 +634,12 @@ public class SitPosData : MonoBehaviour
indexConsume = 0;
AudioManager.Instance.PlayerFX(62011);
consumeGoldNum = CreateFishManager.Instance.currGameType.paoBeiList[indexConsume];
tMP_TurretConsumeNum.text = consumeGoldNum.ToString();
CreateFishManager.Instance.currGameType?.SyncCannonMultiple((int)consumeGoldNum);
// ⭐ 不立即修改 consumeGoldNum 和 UI,等服务端 ChangeGunMultipleNotice 确认后再应用
// 避免:客户端先切换炮倍 → 开火按新炮倍扣费 → 服务端还没确认按旧炮倍扣费 → 差值
int newConsume = CreateFishManager.Instance.currGameType.paoBeiList[indexConsume];
string roomId = GameManager.Instance.gameData?.currRoomData?.roomId;
if (!string.IsNullOrEmpty(roomId))
WebSocketSendList.ChangeGunMultiple(roomId, user, newConsume);
}
public void ChangeTurretMinusConsumeNum()
@@ -644,9 +650,12 @@ public class SitPosData : MonoBehaviour
indexConsume = CreateFishManager.Instance.currGameType.paoBeiList.Count - 1;
AudioManager.Instance.PlayerFX(62010);
consumeGoldNum = CreateFishManager.Instance.currGameType.paoBeiList[indexConsume];
tMP_TurretConsumeNum.text = consumeGoldNum.ToString();
CreateFishManager.Instance.currGameType?.SyncCannonMultiple((int)consumeGoldNum);
// ⭐ 不立即修改 consumeGoldNum 和 UI,等服务端 ChangeGunMultipleNotice 确认后再应用
// 避免:客户端先切换炮倍 → 开火按新炮倍扣费 → 服务端还没确认按旧炮倍扣费 → 差值
int newConsume = CreateFishManager.Instance.currGameType.paoBeiList[indexConsume];
string roomId = GameManager.Instance.gameData?.currRoomData?.roomId;
if (!string.IsNullOrEmpty(roomId))
WebSocketSendList.ChangeGunMultiple(roomId, user, newConsume);
}
// ==================== 锁定系统 ====================
@@ -44,23 +44,50 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 38ccfa89023796c4a94893b7e9b4914c, type: 3}
m_Name:
m_EditorClassIdentifier:
tMP_CoinCount: {fileID: 0}
gold: {fileID: 0}
silver: {fileID: 0}
endPoint: {x: 0, y: 0, z: 0}
isBoss: 0
coinCount: 0
userID:
sitID: 0
startPoint: {x: 0, y: 0, z: 0}
radius: 0
count: 0
bjEndUI: {fileID: 0}
useScorePanel: 1
tMP_Score: {fileID: 0}
scorePanelPrefab: {fileID: 0}
currScore: 0
maxScore: 0
scoreCount: 0
posID: 0
paoBei: 0
time: 0.5
baoJiangEffectPrefab: {fileID: 0}
fishScore: 0
bJbeishu: 0
showDelay: 3
moveDuration: 0.5
moveEase: 6
startOffset: {x: 0, y: 0, z: -100}
bjEndUIOffsetY: 150
coinFlyDuration: 0.5
coinArrivalInterval: 0.1
isSelf: 0
autoFinishOnScoreFull: 1
totalBJCount: 0
currBJCount: 0
isEffEnd: 0
isScoreStart: 0
isScoreEnd: 0
isGoldEffectStart: 0
baoJiangMode: 0
endUIType: 1
endUIEntries: []
monster: {fileID: 0}
startPoint: {x: 0, y: 0, z: 0}
coinCount: 0
isLocalCoin: 0
baoJiangEffectPrefab: {fileID: 136624701376015062, guid: 8d6e13708cafa5d448d52c8fd7f00f1a,
type: 3}
parentTransform: {fileID: 0}
offsetPosition: {x: 0, y: 2, z: 0}
effectType: 5
onTimeObjs: []
hitTimes: []
time: 0.5
dieEffectTesyNum: 0
minBJCount: 10
maxBJCount: 15
lightningMaterial: {fileID: 2100000, guid: a023113e8eb8bf245b8ba10c3e9788d1, type: 2}
lightningColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
lightningWidth: 0.08
@@ -69,7 +96,6 @@ MonoBehaviour:
explosionPrefab: {fileID: 1709254077376921, guid: 08be47e4b1d3e1c4a895269c2c08653c,
type: 3}
explosionRadius: 10
startPoint: {x: 0, y: 0, z: 0}
lightningCount: 5
lightningRange: 5
lightningWait: 0.5
@@ -1371,10 +1371,18 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
SyncFire(angle, isSanShe, isDouble, isLock);
}
private void FireBullet(SitPosData sit, float angle, bool isSanShe, bool isDouble, bool isLock, List<string> bullitIDs,
List<string> fireTargetMonsterIDs = null)
List<string> fireTargetMonsterIDs = null, List<long> bulletGolds = null)
{
Vector2 startPos = sit.GetBulletStartPos();
// ⭐ 按索引取该发子弹的服务端权威扣费金额(bullets[i].gold),没有则回退到0(ConsumeGold内部用consumeGoldNum)
long GetBulletGold(int i)
{
if (bulletGolds != null && i < bulletGolds.Count)
return bulletGolds[i];
return 0;
}
// ⭐ 三发 + 锁定:每发子弹优先瞄准对应目标,目标不足或失效则按默认角度发射
// 必须保证三发子弹都创建,否则金币消耗和后端对不上
if (isSanShe && isLock)
@@ -1406,24 +1414,24 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
Vector3 targetWorldPos = targetHitPos.position;
Vector2 dir = ((Vector2)targetWorldPos - startPos).normalized;
float targetAngle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90f;
CreateBullet(sit, startPos, targetAngle, isDouble, true, bulletId, targetMonster, targetHitPos);
CreateBullet(sit, startPos, targetAngle, isDouble, true, bulletId, targetMonster, targetHitPos, GetBulletGold(i));
}
else
{
// 目标不足或失效 → 按默认角度发射(不锁定)
CreateBullet(sit, startPos, defaultAngles[i], isDouble, isLock, bulletId);
CreateBullet(sit, startPos, defaultAngles[i], isDouble, isLock, bulletId, null, null, GetBulletGold(i));
}
}
}
else if (isSanShe)
{
CreateBullet(sit, startPos, angle - 20, isDouble, isLock, bullitIDs[0]);
CreateBullet(sit, startPos, angle, isDouble, isLock, bullitIDs[1]);
CreateBullet(sit, startPos, angle + 20, isDouble, isLock, bullitIDs[2]);
CreateBullet(sit, startPos, angle - 20, isDouble, isLock, bullitIDs[0], null, null, GetBulletGold(0));
CreateBullet(sit, startPos, angle, isDouble, isLock, bullitIDs[1], null, null, GetBulletGold(1));
CreateBullet(sit, startPos, angle + 20, isDouble, isLock, bullitIDs[2], null, null, GetBulletGold(2));
}
else
{
CreateBullet(sit, startPos, angle, isDouble, isLock, bullitIDs[0]);
CreateBullet(sit, startPos, angle, isDouble, isLock, bullitIDs[0], null, null, GetBulletGold(0));
}
if (sit.posID==currSitData.posID)
{
@@ -2001,7 +2009,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
}
private BullitMove CreateBullet(SitPosData sit, Vector2 pos, float angle, bool isDouble, bool isLock, string id,
MonsterContral lockTarget = null, Transform lockHitPos = null)
MonsterContral lockTarget = null, Transform lockHitPos = null, long fireConsumeGold = 0)
{
// ⭐ 远程座位炮台未加载时补加载
if (sit.currentTurret == null)
@@ -2025,7 +2033,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
return null;
}
if (!sit.ConsumeGold(isDouble ? 2 : 1))
if (!sit.ConsumeGold(isDouble ? 2 : 1, fireConsumeGold))
return null;
// ⭐ 从对象池取子弹(池空时内部会 Instantiate)
@@ -2211,6 +2219,24 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
// 但如果服务器不回广播给开火者,本地玩家需要在 SyncFire 里直接 FireBullet)
// 实际设计:本地玩家也通过广播回来创建子弹,所以不跳过
// ⭐ 从服务端权威数据提取每发子弹扣费金额
// 1. 优先用 bullets 字段(type:3 调用返回值,包含每发子弹的扣费金额)
// 2. fallback 用 sync.gold(RoomFireNotice 广播里的炮倍,服务端权威)
List<long> bulletGolds = null;
if (sync.bullets != null && sync.bullets.Count > 0)
{
bulletGolds = new List<long>(sync.bullets.Count);
foreach (var bg in sync.bullets)
bulletGolds.Add(bg.gold);
}
else if (sync.gold > 0 && sync.bulletIds != null && sync.bulletIds.Count > 0)
{
// RoomFireNotice 没有 bullets 字段,用 sync.gold(服务端权威炮倍)作为每发扣费金额
bulletGolds = new List<long>(sync.bulletIds.Count);
for (int i = 0; i < sync.bulletIds.Count; i++)
bulletGolds.Add(sync.gold);
}
// extra 为空:从 botLastFireData 或默认参数还原(bot 或真人玩家都可能走到这里)
if (sync.extra == null || sync.extra.frameData == null)
{
@@ -2226,17 +2252,18 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
if (isBot && botLastFireData.TryGetValue(sync.userId, out var fireData))
{
FireBullet(sit, angle, fireData.isSanShe, fireData.twoRTruuect,
fireData.isSuoDing, sync.bulletIds, fireData.monsterIDs);
fireData.isSuoDing, sync.bulletIds, fireData.monsterIDs, bulletGolds);
}
else
{
FireBullet(sit, angle, false, false, false, sync.bulletIds, null);
FireBullet(sit, angle, false, false, false, sync.bulletIds, null, bulletGolds);
}
return;
}
var data = FrameDataParser.Parse<FrameData_FireBullet>(sync.extra.frameData);
FireBullet(list_Sits[sync.extra.sitPos], data.angle, data.isSanShe, data.twoRTruuect, data.isSuoDing, sync.bulletIds, data.monsterIDs);
var targetSit = list_Sits[sync.extra.sitPos];
FireBullet(targetSit, data.angle, data.isSanShe, data.twoRTruuect, data.isSuoDing, sync.bulletIds, data.monsterIDs, bulletGolds);
}
/// <summary>
@@ -2333,6 +2360,22 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
sit.tMP_TurretConsumeNum.text = data.cannonMultiple.ToString();
}
/// <summary>
/// 应用服务端确认的炮倍变更(ChangeGunMultipleNotice)。
/// 本地玩家切换炮倍后,等服务端广播此通知才更新 consumeGoldNum 和 UI,
/// 确保开火扣费时本地炮倍与服务端一致。
/// </summary>
public void ApplyCannonMultiple(string userId, int multiple)
{
var sit = list_Sits.Find(s => s.user == userId);
if (sit == null) return;
sit.consumeGoldNum = multiple;
sit.SyncIndexByConsume(multiple);
if (sit.tMP_TurretConsumeNum != null)
sit.tMP_TurretConsumeNum.text = multiple.ToString();
}
/// <summary>
/// 爆奖结束帧同步(baoJiangEnd):
/// 其他玩家爆奖流程完成,从 pending 取出对应 not-self 爆奖,