1
This commit is contained in:
@@ -90,10 +90,18 @@ public class EffectPool : Singleton<EffectPool>
|
||||
if (auto == null)
|
||||
auto = obj.AddComponent<EffectAutoReturn>();
|
||||
auto.prefabID = prefabID;
|
||||
auto.enabled = true;
|
||||
auto.Play();
|
||||
}
|
||||
// autoReturn=false: 动画/UI 驱动特效,不挂自动回收组件
|
||||
// 由动画事件(coinEvent/ScoreNumScrollEvent 等)调用 EffectPool.Instance.Return(gameObject) 回池
|
||||
// ⭐ 池复用来的对象上可能残留 EffectAutoReturn(预制体自带/上次按 autoReturn=true 取过),
|
||||
// 它会在 0.3s 后无条件回池 → 动画/UI 特效"刚出现就消失",这里显式禁用
|
||||
else
|
||||
{
|
||||
var stale = obj.GetComponent<EffectAutoReturn>();
|
||||
if (stale != null) stale.enabled = false;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -89,6 +89,28 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
|
||||
[Header("道具掉落(服务端驱动)")]
|
||||
[Tooltip("道具掉落预制体(挂 FishKillProp 脚本)。为空则不展示掉落特效")]
|
||||
public GameObject propDropPrefab;
|
||||
[Tooltip("单次掉落最多展示几个道具特效(服务端 count 可能很大,超出只按上限展示)")]
|
||||
public int propMaxFxCount = 10;
|
||||
[Tooltip("道具特效之间的生成间隔(秒),做出依次掉落的错落感")]
|
||||
public float propSpawnInterval = 0.06f;
|
||||
[Tooltip("每个道具相对掉落点的随机散开半径(像素),避免叠在同一位置")]
|
||||
public float propSpawnRadius = 60f;
|
||||
[Tooltip("道具出现后停留多久才飞向座位(秒),与金币的停留感一致")]
|
||||
public float propHoldTime = 0.3f;
|
||||
|
||||
/// <summary>刚死亡鱼的位置缓存:fishId → 位置/记录时刻。
|
||||
/// ⭐ 服务端把"击杀"和"掉落道具"拆成两条消息下发,掉落消息到达时鱼已从字典移除,
|
||||
/// 用此缓存还原掉落起点(否则会退化成座位位置 → 道具贴着座位飞 0 距离,看着就是"刚出现就消失")。</summary>
|
||||
private readonly Dictionary<string, DeathPosRecord> _recentFishDeathPos
|
||||
= new Dictionary<string, DeathPosRecord>();
|
||||
/// <summary>死亡位置缓存保留时长(秒)</summary>
|
||||
private const float DeathPosKeepSeconds = 10f;
|
||||
/// <summary>死亡位置缓存结构</summary>
|
||||
private struct DeathPosRecord
|
||||
{
|
||||
public Vector3 pos;
|
||||
public float time;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 生成系统运行时数据
|
||||
@@ -2173,6 +2195,10 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
|
||||
|
||||
if (!killedFound) return;
|
||||
|
||||
// ⭐ 记录死亡位置:掉落道具是服务端随后单独下发的一条消息,届时鱼已被回收,
|
||||
// 用这里的缓存还原道具掉落起点(否则道具会从座位位置冒出并立刻消失)
|
||||
RecordFishDeathPos(killFish.fishId, monster.transform.position);
|
||||
|
||||
// ⭐ 统一标记死亡:死亡后子弹不再命中、不再被锁定(含不调 base.DieFish 的 Boss 子类)
|
||||
monster.isDead = true;
|
||||
|
||||
@@ -2575,9 +2601,8 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
|
||||
|
||||
/// <summary>
|
||||
/// 处理服务端下发的道具掉落推送(RoomKillNotice.drop 不为空、param 为空的消息)。
|
||||
/// 与金币流程对称:EffectPool 取预制体→转 Canvas 坐标→InitProp 设置编号和座位终点→
|
||||
/// SetCoinCount→FlyToSeat 飞到座位回收。
|
||||
/// 道具图标按 /uploads/{propNumber}.png 从服务端下载(由 FishKillProp 内部完成)。
|
||||
/// 表现与金币一致:从鱼死亡处散开掉落 → 停留片刻 → 飞向击杀者座位 → 回收。
|
||||
/// 掉落数量 > 1 时按 propSpawnInterval 依次生成,各自随机散开一段距离。
|
||||
/// </summary>
|
||||
private void HandleDropProp(RoomKillNotice killFish)
|
||||
{
|
||||
@@ -2601,12 +2626,38 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
|
||||
return;
|
||||
}
|
||||
|
||||
// 找鱼位置(鱼可能已回收,找不到就用座位位置兜底)
|
||||
Vector3 dropPos = sitData.transform.position;
|
||||
// 掉落起点:优先当前活鱼位置 → 死亡位置缓存 → 座位上方向上偏移兜底
|
||||
Vector3 dropPos;
|
||||
if (fishDatas_hashCodekey.TryGetValue(killFish.fishId, out var monster) && monster != null)
|
||||
dropPos = monster.transform.position;
|
||||
else if (TryGetFishDeathPos(killFish.fishId, out var deathPos))
|
||||
dropPos = deathPos;
|
||||
else
|
||||
dropPos = sitData.transform.position + Vector3.up * 100f;
|
||||
|
||||
// 创建道具特效(和金币一样 autoReturn=false,由 FlyToSeat 手动回池)
|
||||
// 服务端下发的数量可能很大,视觉上只展示上限个,避免瞬间刷屏
|
||||
int fxCount = Mathf.Clamp(killFish.drop.count, 1, Mathf.Max(1, propMaxFxCount));
|
||||
|
||||
StartCoroutine(SpawnDropPropRoutine(killFish, sitData, dropPos, fxCount));
|
||||
}
|
||||
|
||||
/// <summary>依次生成多个道具特效,每个之间留出间隔,形成散开掉落的感觉。</summary>
|
||||
private IEnumerator SpawnDropPropRoutine(RoomKillNotice killFish, SitPosData sitData,
|
||||
Vector3 dropPos, int fxCount)
|
||||
{
|
||||
for (int i = 0; i < fxCount; i++)
|
||||
{
|
||||
if (currGameType == null) yield break;
|
||||
SpawnSingleDropProp(killFish, sitData, dropPos);
|
||||
if (i < fxCount - 1 && propSpawnInterval > 0f)
|
||||
yield return new WaitForSeconds(propSpawnInterval);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>生成单个道具特效:随机散开在掉落点周围,停留 propHoldTime 后飞向座位。</summary>
|
||||
private void SpawnSingleDropProp(RoomKillNotice killFish, SitPosData sitData, Vector3 dropPos)
|
||||
{
|
||||
// 创建道具(和金币一样 autoReturn=false,飞行结束后由 FlyToSeat 手动回池)
|
||||
var propObj = EffectPool.Instance.Get(
|
||||
propDropPrefab,
|
||||
dropPos, Quaternion.identity,
|
||||
@@ -2614,30 +2665,38 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
|
||||
autoReturn: false);
|
||||
if (propObj == null) return;
|
||||
|
||||
// 坐标转换(和金币一致:世界坐标转 UI Canvas 坐标)
|
||||
try
|
||||
{
|
||||
propObj.GetComponent<RectTransform>().anchoredPosition =
|
||||
CoordinateTransformation.WorldPos_ToCanvas(dropPos, GetUiCanvas());
|
||||
}
|
||||
catch
|
||||
{
|
||||
propObj.transform.position = dropPos;
|
||||
}
|
||||
|
||||
// 初始化道具:编号、数量、起点、终点(座位)、玩家信息
|
||||
var fishKillProp = propObj.GetComponent<FishKillProp>();
|
||||
if (fishKillProp == null)
|
||||
{
|
||||
// 预制体没挂 FishKillProp 脚本 → 回收并退出
|
||||
// 预制体没挂 FishKillProp 脚本 → 直接回收,避免残留
|
||||
EffectPool.Instance.Return(propObj);
|
||||
return;
|
||||
}
|
||||
|
||||
// 起始位置的随机散开(UI 像素空间,和金币一致走 anchoredPosition)
|
||||
Vector2 canvasPos;
|
||||
try
|
||||
{
|
||||
canvasPos = CoordinateTransformation.WorldPos_ToCanvas(dropPos, GetUiCanvas());
|
||||
}
|
||||
catch
|
||||
{
|
||||
canvasPos = propObj.transform.position;
|
||||
}
|
||||
if (propSpawnRadius > 0f)
|
||||
canvasPos += Random.insideUnitCircle * propSpawnRadius;
|
||||
|
||||
var rt = propObj.GetComponent<RectTransform>();
|
||||
if (rt != null)
|
||||
rt.anchoredPosition = canvasPos;
|
||||
else
|
||||
propObj.transform.position = dropPos;
|
||||
|
||||
// 初始化:编号、数量、起点、终点(击杀者座位)
|
||||
fishKillProp.InitProp(
|
||||
killFish.drop.propNumber,
|
||||
killFish.drop.count,
|
||||
dropPos,
|
||||
propObj.transform.position,
|
||||
sitData.transform.position,
|
||||
killFish.userId,
|
||||
sitData.posID,
|
||||
@@ -2648,13 +2707,52 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
|
||||
// TODO: 道具拾取后的业务逻辑(如刷新背包、弹获得面板等)
|
||||
};
|
||||
fishKillProp.SetCoinCount();
|
||||
fishKillProp.FlyToSeat();
|
||||
|
||||
// 停留片刻再飞向座位(金币也是先展示、再回收飞行,避免"刚出现就消失"的观感)
|
||||
fishKillProp.FlyToSeat(propHoldTime);
|
||||
|
||||
GameManager.LogFishFlow("DROP_PROP", killFish.fishId,
|
||||
$"prop={killFish.drop.propName}×{killFish.drop.count} 座位={sitData.posID} 击杀者={killFish.userId}",
|
||||
propObj.GetInstanceID());
|
||||
}
|
||||
|
||||
/// <summary>记录鱼死亡时的世界坐标(供随后到达的道具掉落消息还原掉落起点)。</summary>
|
||||
private void RecordFishDeathPos(string fishId, Vector3 pos)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fishId)) return;
|
||||
|
||||
// 顺手清理过期记录,防止字典无限增长
|
||||
if (_recentFishDeathPos.Count >= 64)
|
||||
{
|
||||
float now = Time.unscaledTime;
|
||||
var expired = new List<string>();
|
||||
foreach (var kv in _recentFishDeathPos)
|
||||
if (now - kv.Value.time > DeathPosKeepSeconds) expired.Add(kv.Key);
|
||||
foreach (var k in expired) _recentFishDeathPos.Remove(k);
|
||||
}
|
||||
|
||||
_recentFishDeathPos[fishId] = new DeathPosRecord
|
||||
{
|
||||
pos = pos,
|
||||
time = Time.unscaledTime
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>取出鱼死亡位置(超过保留时长视为失效)。</summary>
|
||||
private bool TryGetFishDeathPos(string fishId, out Vector3 pos)
|
||||
{
|
||||
pos = Vector3.zero;
|
||||
if (string.IsNullOrEmpty(fishId)) return false;
|
||||
if (!_recentFishDeathPos.TryGetValue(fishId, out var rec)) return false;
|
||||
if (Time.unscaledTime - rec.time > DeathPosKeepSeconds)
|
||||
{
|
||||
_recentFishDeathPos.Remove(fishId);
|
||||
return false;
|
||||
}
|
||||
pos = rec.pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,15 +6,37 @@ using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 鱼死亡掉落道具脚本(与 CoinScript 同级,均继承 BJBase)。
|
||||
/// 行为类似金币:原地掉落 → 飞向击杀者座位 → 回收。
|
||||
/// 行为类似金币:原地掉落并散开 → 停留片刻 → 飞向击杀者座位 → 回收。
|
||||
/// 掉落数据由服务端 RoomKillNotice.drop 下发(propNumber/propName/count),
|
||||
/// 道具图标按 /uploads/{propNumber}.png 从服务端下载。
|
||||
/// </summary>
|
||||
public class FishKillProp : BJBase
|
||||
{
|
||||
/// <summary>飞向座位耗时(秒),与金币 coinEvent 保持一致</summary>
|
||||
private const float FlyDuration = 0.5f;
|
||||
|
||||
/// <summary>道具图标(Inspector 绑定)</summary>
|
||||
public Image propIco;
|
||||
|
||||
/// <summary>当前飞行补间(池复用时需要先 Kill,防止旧回调回收新道具)</summary>
|
||||
private Tween _flyTween;
|
||||
/// <summary>本次生命周期是否已开始飞行,防止重复调用导致重复回收</summary>
|
||||
private bool _flying;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
// ⭐ 池复用重置:上一次的飞行补间若还在,其完成回调会把刚复用的新道具直接回池
|
||||
// 表现就是"道具刚出现就消失",这里清干净
|
||||
_flying = false;
|
||||
if (_flyTween != null && _flyTween.IsActive()) _flyTween.Kill(false);
|
||||
_flyTween = null;
|
||||
transform.DOKill(false);
|
||||
|
||||
// ⭐ 预制体/复用对象上若残留自动回收组件,会在 0.3s 后无条件回池,同样导致瞬间消失
|
||||
var auto = GetComponent<EffectAutoReturn>();
|
||||
if (auto != null) auto.enabled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写基类方法:道具不显示金币数字,而是下载并设置道具图标。
|
||||
/// 和 CoinScript.SetCoinCount() 对称,供外部统一调用。
|
||||
@@ -32,8 +54,8 @@ public class FishKillProp : BJBase
|
||||
/// 由 CreateFishManager 在收到服务端掉落推送时调用。
|
||||
/// </summary>
|
||||
/// <param name="propNumber">道具编号(用于下载图标 /uploads/编号.png)</param>
|
||||
/// <param name="propCount">掉落数量</param>
|
||||
/// <param name="startPoint">掉落起点(鱼死亡位置)</param>
|
||||
/// <param name="propCount">掉落数量(服务端下发总量,仅供展示/扩展)</param>
|
||||
/// <param name="startPoint">掉落起点(鱼死亡位置,已含随机散开偏移)</param>
|
||||
/// <param name="endPoint">飞行终点(击杀者座位位置)</param>
|
||||
/// <param name="userID">击杀者 ID</param>
|
||||
/// <param name="sitID">座位 ID</param>
|
||||
@@ -49,6 +71,10 @@ public class FishKillProp : BJBase
|
||||
DownloadPropIcon(propNumber);
|
||||
}
|
||||
|
||||
/// <summary>道具图标缓存(同一次掉落可能生成多个道具,避免重复下载同一张图)</summary>
|
||||
private static readonly System.Collections.Generic.Dictionary<string, Sprite> _iconCache
|
||||
= new System.Collections.Generic.Dictionary<string, Sprite>();
|
||||
|
||||
/// <summary>
|
||||
/// 从服务端下载道具图标并设置到 propIco。
|
||||
/// 失败时 propIco 保留预制体上配置好的默认图(不强制兜底重试)。
|
||||
@@ -57,36 +83,58 @@ public class FishKillProp : BJBase
|
||||
{
|
||||
if (string.IsNullOrEmpty(propNumber) || propIco == null) return;
|
||||
|
||||
// 命中缓存直接使用
|
||||
if (_iconCache.TryGetValue(propNumber, out var cached) && cached != null)
|
||||
{
|
||||
ApplyIcon(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
string url = APIManager.baseURL + "/uploads/" + propNumber + ".png";
|
||||
WebTool.DownImage(url, tex =>
|
||||
{
|
||||
// 回调时本物体可能已被销毁(飞到座位后回池/切场景),需判空
|
||||
if (this == null || propIco == null) return;
|
||||
if (tex != null)
|
||||
{
|
||||
propIco.sprite = Sprite.Create(tex,
|
||||
var sprite = Sprite.Create(tex,
|
||||
new Rect(0, 0, tex.width, tex.height),
|
||||
Vector2.one * 0.5f);
|
||||
propIco.SetNativeSize();
|
||||
propIco.gameObject.SetActive(true);
|
||||
_iconCache[propNumber] = sprite;
|
||||
}
|
||||
// 回调时本物体可能已被销毁(飞到座位后回池/切场景),需判空
|
||||
if (this == null || propIco == null) return;
|
||||
if (_iconCache.TryGetValue(propNumber, out var sprite2) && sprite2 != null)
|
||||
ApplyIcon(sprite2);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>把图标应用到 propIco。</summary>
|
||||
private void ApplyIcon(Sprite sprite)
|
||||
{
|
||||
propIco.sprite = sprite;
|
||||
propIco.SetNativeSize();
|
||||
propIco.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 飞到座位位置并回收(效果同 coinEvent.HuiShou)。
|
||||
/// 可由动画事件调用(预制体有掉落动画时),也可直接调用实现原地掉落后立即飞向座位。
|
||||
/// </summary>
|
||||
public void FlyToSeat()
|
||||
/// <param name="delay">起飞前停留时长(秒),让道具先展示一会儿再回收,观感与金币一致</param>
|
||||
public void FlyToSeat(float delay = 0f)
|
||||
{
|
||||
transform.DOMove(endPoint, 0.5f).OnComplete(() =>
|
||||
{
|
||||
// 拾取回调(由 CreateFishManager 设置 JiaFen)
|
||||
JiaFen?.Invoke(0);
|
||||
// 拾取音效(复用金币拾取音效)
|
||||
AudioManager.Instance.PlayerFX(61009);
|
||||
// 池化回收
|
||||
EffectPool.Instance.Return(gameObject);
|
||||
});
|
||||
if (_flying) return; // 防止重复调用导致重复回收
|
||||
_flying = true;
|
||||
|
||||
if (_flyTween != null && _flyTween.IsActive()) _flyTween.Kill(false);
|
||||
_flyTween = transform.DOMove(endPoint, FlyDuration)
|
||||
.SetDelay(Mathf.Max(0f, delay))
|
||||
.OnComplete(() =>
|
||||
{
|
||||
// 拾取回调(由 CreateFishManager 设置 JiaFen)
|
||||
JiaFen?.Invoke(0);
|
||||
// 拾取音效(复用金币拾取音效)
|
||||
AudioManager.Instance.PlayerFX(61009);
|
||||
// 池化回收
|
||||
EffectPool.Instance.Return(gameObject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ public class PoHuaiQiu_BOSS : MonsterControl_BossBase
|
||||
PlayAppearAnim();
|
||||
|
||||
// 音效:拆迁球出场(PW_WreckingBall_Show)
|
||||
AudioManager.Instance.PlayerFXByName("PW_WreckingBall_Show");
|
||||
//AudioManager.Instance.PlayerFXByName("PW_WreckingBall_Show");//泰语暂时删除
|
||||
|
||||
// 出现动画完成 → 切换移动动画 + 开始移动
|
||||
_appearCoroutine = StartCoroutine(AppearToMove());
|
||||
@@ -243,7 +243,7 @@ public class PoHuaiQiu_BOSS : MonsterControl_BossBase
|
||||
Debug.Log("[PoHuaiQiu] Phase 4: 攻击阶段开始");
|
||||
|
||||
// 音效:拆迁球攻击 + 机枪攻击(PW_WreckingBall_ToAttack / PW_MachineGunAttack)
|
||||
AudioManager.Instance.PlayerFXByName("PW_WreckingBall_ToAttack");
|
||||
//AudioManager.Instance.PlayerFXByName("PW_WreckingBall_ToAttack");//泰语暂时删除
|
||||
AudioManager.Instance.PlayerFXByName("PW_MachineGunAttack");
|
||||
|
||||
// 触发爆奖事件 Init(显示分数面板)
|
||||
|
||||
@@ -111,7 +111,7 @@ public class XingJiXunHang_BOSS : MonsterControl_BossBase
|
||||
ResetSpineToIdle();
|
||||
|
||||
// 音效:星舰出场(PW_StarShip_Show)
|
||||
AudioManager.Instance.PlayerFXByName("PW_StarShip_Show");
|
||||
//AudioManager.Instance.PlayerFXByName("PW_StarShip_Show"); //泰语声音暂时先不要
|
||||
}
|
||||
|
||||
/// <summary>复用/重生时把前端 spine 复位为正常待机(stand01,循环播放)</summary>
|
||||
|
||||
@@ -225,8 +225,10 @@ public class XingYunLunEvent : BJRedPacketEvent
|
||||
r1.z = -100f;
|
||||
|
||||
int totalCount = _targetSlots.Count;
|
||||
Debug.Log($"[XingYunLun][坐标转换] UIToWorld={r1} totalCount={totalCount} centerOffset={(totalCount - 1) / 2f * itemGapX}");
|
||||
modelBaseTarget = r1 - new Vector3((totalCount - 1) / 2f * itemGapX, 0f, 0f);
|
||||
// ⭐ 排列方向:左半区座位(0,2)从左到右,右半区座位(1,3)从右往左
|
||||
int layoutDir = (posID % 2 == 1) ? -1 : 1;
|
||||
Debug.Log($"[XingYunLun][坐标转换] UIToWorld={r1} totalCount={totalCount} posID={posID} layoutDir={layoutDir} centerOffset={(totalCount - 1) / 2f * itemGapX}");
|
||||
modelBaseTarget = r1 - new Vector3((totalCount - 1) / 2f * itemGapX * layoutDir, 0f, 0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -235,13 +237,16 @@ public class XingYunLunEvent : BJRedPacketEvent
|
||||
}
|
||||
|
||||
// Step 3: 启动顺序创建+旋转协程
|
||||
StartCoroutine(SequentialItemFlow(modelBaseTarget));
|
||||
int dir = (posID % 2 == 1) ? -1 : 1;
|
||||
StartCoroutine(SequentialItemFlow(modelBaseTarget, dir));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 顺序流程:创建第1个 item → 移动 → 旋转 → 创建第2个 → 旋转 → ... → 最后等2秒 → EndExplosion
|
||||
/// </summary>
|
||||
private IEnumerator SequentialItemFlow(Vector3 modelBaseTarget)
|
||||
/// <param name="modelBaseTarget">第1个 item 的目标位置(已按排列方向居中计算)</param>
|
||||
/// <param name="layoutDir">排列方向:1=从左到右,-1=从右往左</param>
|
||||
private IEnumerator SequentialItemFlow(Vector3 modelBaseTarget, int layoutDir)
|
||||
{
|
||||
ClearAllItems();
|
||||
|
||||
@@ -255,7 +260,7 @@ public class XingYunLunEvent : BJRedPacketEvent
|
||||
}
|
||||
|
||||
// 音效:幸运大转盘出场(PW_FortuneWheel_Show,转轮开始出现)
|
||||
AudioManager.Instance.PlayerFXByName("PW_FortuneWheel_Show");
|
||||
//AudioManager.Instance.PlayerFXByName("PW_FortuneWheel_Show");//泰语删除
|
||||
|
||||
// 计算所有 item 的倍数总和(给中间转轮的 BJEndLabel 用)
|
||||
int totalScoreSum = 0;
|
||||
@@ -294,7 +299,7 @@ public class XingYunLunEvent : BJRedPacketEvent
|
||||
foreach (var e in lunItem.objs_REffect) if (e != null) e.SetActive(false);
|
||||
}
|
||||
|
||||
Vector3 targetPos = modelBaseTarget + new Vector3(itemGapX * i, 0f, 0f);
|
||||
Vector3 targetPos = modelBaseTarget + new Vector3(itemGapX * i * layoutDir, 0f, 0f);
|
||||
originalPositions[clone] = targetPos;
|
||||
|
||||
// 第一个 item:移动到目标位置(有移动效果)
|
||||
@@ -325,7 +330,7 @@ public class XingYunLunEvent : BJRedPacketEvent
|
||||
{
|
||||
if (_items[j] == clone) continue;
|
||||
int dir = (j < i) ? -1 : 1;
|
||||
Vector3 shifted = originalPositions[_items[j]] + new Vector3(dir * shiftAmount, 0f, 0f);
|
||||
Vector3 shifted = originalPositions[_items[j]] + new Vector3(dir * layoutDir * shiftAmount, 0f, 0f);
|
||||
_items[j].transform.DOMove(shifted, 0.2f).SetEase(Ease.OutCubic);
|
||||
}
|
||||
yield return new WaitForSeconds(0.2f);
|
||||
@@ -529,7 +534,7 @@ public class XingYunLunEvent : BJRedPacketEvent
|
||||
isScoreEnd = true;
|
||||
|
||||
// 音效:幸运大转盘结算(PW_FortuneWheel_Settle)
|
||||
AudioManager.Instance.PlayerFXByName("PW_FortuneWheel_Settle");
|
||||
//AudioManager.Instance.PlayerFXByName("PW_FortuneWheel_Settle");//泰语删除
|
||||
|
||||
// 回加金币到玩家
|
||||
JiaFen?.Invoke();
|
||||
|
||||
@@ -2194,7 +2194,7 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
@@ -17489,7 +17489,7 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
@@ -20513,7 +20513,7 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
@@ -21492,7 +21492,7 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
@@ -22515,7 +22515,7 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
|
||||
@@ -1163,7 +1163,8 @@ public class UIHome_BY_ItemListPanels : SubUIBase
|
||||
APIRequestList.ChangeCannon(new ChangeCannonParam { number = numberStr }, res =>
|
||||
{
|
||||
Debug.Log($"[UIHome_BY_ItemListPanels] 切换炮台成功: #{turretNumber}");
|
||||
RefreshUserAndRebuild();
|
||||
// ⭐ 炮台/战神互斥:装上炮台后自动卸下已装备的战神,再统一刷新
|
||||
UnloadCounterpart(ItemPanelSelect.ZS, RefreshUserAndRebuild);
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -1179,12 +1180,85 @@ public class UIHome_BY_ItemListPanels : SubUIBase
|
||||
APIRequestList.ChangeGodWar(new ChangeGodWarParam { number = numberStr }, res =>
|
||||
{
|
||||
Debug.Log($"[UIHome_BY_ItemListPanels] 切换战神成功: #{turretNumber}");
|
||||
RefreshUserAndRebuild();
|
||||
// ⭐ 炮台/战神互斥:装上战神后自动卸下已装备的炮台,再统一刷新
|
||||
UnloadCounterpart(ItemPanelSelect.Turrect, RefreshUserAndRebuild);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ⭐ 炮台 ↔ 战神互斥:自动卸下"对方"那件已装备的东西。
|
||||
/// 装炮台 → 卸战神;装战神 → 卸炮台(走各自的 Unload 接口,传当前装备编号)。
|
||||
/// 没有装备对方就什么都不做直接继续。
|
||||
/// ⚠ 卸下被后端拒绝也不影响使用(游戏内炮台/战神显示本来就是二选一),
|
||||
/// 但 APIManager 只在 code==0 时才回调,被拒时回调永不触发 —— 所以这里必须有超时兜底,
|
||||
/// 否则"装上的那件"永远不会刷新,界面会一直停在旧数据。
|
||||
/// </summary>
|
||||
private void UnloadCounterpart(ItemPanelSelect counterpartType, Action onDone)
|
||||
{
|
||||
string number = GetEquippedNumber(counterpartType);
|
||||
if (string.IsNullOrEmpty(number) || number == "0")
|
||||
{
|
||||
onDone?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
// 只放行一次:正常回调 / 超时兜底谁先到用谁
|
||||
bool finished = false;
|
||||
Action finish = () =>
|
||||
{
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
onDone?.Invoke();
|
||||
};
|
||||
|
||||
// 兜底:卸下接口没回调(被拒/网络异常)时,到点照样往下走
|
||||
StartCoroutine(FinishAfterRealtime(unloadFallbackSeconds, finish));
|
||||
|
||||
if (counterpartType == ItemPanelSelect.ZS)
|
||||
{
|
||||
APIRequestList.UnloadGodWar(new UnloadGodWarParam { number = number }, _ =>
|
||||
{
|
||||
Debug.Log($"[UIHome_BY_ItemListPanels] 装备炮台 → 自动卸下战神 #{number}");
|
||||
finish();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
APIRequestList.UnloadCannon(new UnloadCannonParam { number = number }, _ =>
|
||||
{
|
||||
Debug.Log($"[UIHome_BY_ItemListPanels] 装备战神 → 自动卸下炮台 #{number}");
|
||||
finish();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>卸下接口无回调时的兜底等待秒数(短一点:被拒时界面也能很快恢复)</summary>
|
||||
private const float unloadFallbackSeconds = 0.6f;
|
||||
|
||||
private IEnumerator FinishAfterRealtime(float seconds, Action finish)
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(seconds);
|
||||
finish?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>按指定分类取"当前装备"的编号(与当前打开的 Tab 无关;没装备返回 null)</summary>
|
||||
private static string GetEquippedNumber(ItemPanelSelect type)
|
||||
{
|
||||
var user = GameManager.Instance?.user;
|
||||
if (user == null) return null;
|
||||
|
||||
BaseUserProp equipped = type switch
|
||||
{
|
||||
ItemPanelSelect.Turrect => user.cannonUsing,
|
||||
ItemPanelSelect.Wing => user.wingUsing,
|
||||
ItemPanelSelect.ZS => user.godWarUsing,
|
||||
_ => null,
|
||||
};
|
||||
return equipped?.number;
|
||||
}
|
||||
|
||||
/// <summary>API 成功后刷新 GameManager 用户数据并重建列表,游戏内则同步游戏装备。</summary>
|
||||
private void RefreshUserAndRebuild()
|
||||
{
|
||||
@@ -1202,6 +1276,13 @@ public class UIHome_BY_ItemListPanels : SubUIBase
|
||||
{
|
||||
case ItemPanelSelect.Turrect:
|
||||
Debug.Log($"[TurretPanel] LoadTurret: isInGame=true, localSit={SitPosData.localPlayerSit != null}, turretNum={_selectedTurretNumber}");
|
||||
// ⭐ 炮台/战神互斥:装炮台时战神已被自动卸下,座位上还挂着的战神实例要先清掉,
|
||||
// 否则 LoadTurret 会因为"当前有战神"而保持炮台隐藏、状态机入口也不还给炮台
|
||||
if (SitPosData.localPlayerSit != null && SitPosData.localPlayerSit.currentZhanShen != null)
|
||||
{
|
||||
SitPosData.localPlayerSit.ClearZhanShen();
|
||||
CreateFishManager.Instance?.currGameType?.SyncChangeZhanShen(0);
|
||||
}
|
||||
SitPosData.localPlayerSit?.LoadTurret(_selectedTurretNumber);
|
||||
CreateFishManager.Instance?.currGameType?.SyncChangeTurret(_selectedTurretNumber);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user