lcy
13 小时以前 3d2d9d29a5ce766956abb44ceab3e445ce6ac0d9
602 坐骑优化-客户端
14个文件已添加
14个文件已修改
1101 ■■■■■ 已修改文件
Main/Config/ConfigManager.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/Configs/HorseIDConfig.cs 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/Configs/HorseIDConfig.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/Configs/HorseSkinConfig.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/PartialConfigs/HorseSkinConfig.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/ClientPack/CB2_NewFunction/CB204_tagCSHorseIDOP.cs 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/ClientPack/CB2_NewFunction/CB204_tagCSHorseIDOP.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/DTCFile/ServerPack/HA3_Function/DTCA305_tagSCHorseIDInfo.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/DTCFile/ServerPack/HA3_Function/DTCA305_tagSCHorseIDInfo.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/DataToCtl/PackageRegedit.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/ServerPack/HA3_Function/HA305_tagSCHorseIDInfo.cs 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/ServerPack/HA3_Function/HA305_tagSCHorseIDInfo.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Guild/GuildBossWin.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Horse/GetHorseWin.cs 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Horse/GetHorseWin.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Horse/HorseCarouselView.cs 229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Horse/HorseCarouselView.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Horse/HorseItem.cs 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Horse/HorseItem.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Horse/HorseManager.cs 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Horse/HorseRankUPWin.cs 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Horse/HorseSkinCell.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Horse/HorseSkinWin.cs 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Horse/HorseSuccessWin.cs 68 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Horse/HorseWin.cs 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Main/HomeWin.cs 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/OtherPlayerDetail/OtherPlayerDetailWin.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Tip/ConfirmCancel.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/ConfigManager.cs
@@ -79,6 +79,7 @@
            typeof(HeroQualityLVConfig),
            typeof(HeroSkinAttrConfig),
            typeof(HorseClassConfig),
            typeof(HorseIDConfig),
            typeof(HorseSkinConfig),
            typeof(ItemCompoundConfig),
            typeof(ItemConfig),
@@ -350,6 +351,8 @@
        ClearConfigDictionary<HeroSkinAttrConfig>();
        // 清空 HorseClassConfig 字典
        ClearConfigDictionary<HorseClassConfig>();
        // 清空 HorseIDConfig 字典
        ClearConfigDictionary<HorseIDConfig>();
        // 清空 HorseSkinConfig 字典
        ClearConfigDictionary<HorseSkinConfig>();
        // 清空 ItemCompoundConfig 字典
Main/Config/Configs/HorseIDConfig.cs
New file
@@ -0,0 +1,53 @@
//--------------------------------------------------------
//    [Author]:           YYL
//    [  Date ]:           Monday, April 13, 2026
//--------------------------------------------------------
using System.Collections.Generic;
using System;
using UnityEngine;
using LitJson;
public partial class HorseIDConfig : ConfigBase<int, HorseIDConfig>
{
    static HorseIDConfig()
    {
        // 访问过静态构造函数
        visit = true;
    }
    public int HorseID;
    public int UnlockWay;
    public int UnlockValue;
    public int UnlockNeedCnt;
    public int AttrID;
    public float AttrMultiValue;
    public override int LoadKey(string _key)
    {
        int key = GetKey(_key);
        return key;
    }
    public override void LoadConfig(string input)
    {
        try {
        string[] tables = input.Split('\t');
        int.TryParse(tables[0],out HorseID);
            int.TryParse(tables[1],out UnlockWay);
            int.TryParse(tables[2],out UnlockValue);
            int.TryParse(tables[3],out UnlockNeedCnt);
            int.TryParse(tables[4],out AttrID);
            float.TryParse(tables[5],out AttrMultiValue);
        }
        catch (Exception exception)
        {
            Debug.LogError(exception);
        }
    }
}
Main/Config/Configs/HorseIDConfig.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d3c2fe448c55f7c4a82cd8dc635934d6
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Config/Configs/HorseSkinConfig.cs
@@ -1,6 +1,6 @@
//--------------------------------------------------------
//    [Author]:           YYL
//    [  Date ]:           2025年11月23日
//    [  Date ]:           Monday, April 13, 2026
//--------------------------------------------------------
using System.Collections.Generic;
@@ -31,6 +31,9 @@
    public int[] Poses;
    public int heroFirst;
    public string getWay;
    public string Icon;
    public int showType;
    public int sortOrder;
    public override int LoadKey(string _key)
    {
@@ -119,6 +122,12 @@
            int.TryParse(tables[13],out heroFirst); 
            getWay = tables[14];
            Icon = tables[15];
            int.TryParse(tables[16],out showType);
            int.TryParse(tables[17],out sortOrder);
        }
        catch (Exception exception)
        {
Main/Config/PartialConfigs/HorseSkinConfig.cs
@@ -2,6 +2,8 @@
public partial class HorseSkinConfig : ConfigBase<int, HorseSkinConfig>
{
    public static List<int> itemsList = new List<int>(); //皮肤道具解锁/升星
    //坐骑ID,皮肤ID
    public static Dictionary<int, int> horseIDTohorseSkinIDDict = new();
    protected override void OnConfigParseCompleted()
    {
@@ -13,6 +15,10 @@
            }
        }
        if (UnlockWay == 3)
        {
            horseIDTohorseSkinIDDict[UnlockValue] = SkinID;
        }
    }
   
Main/Core/NetworkPackage/ClientPack/CB2_NewFunction/CB204_tagCSHorseIDOP.cs
New file
@@ -0,0 +1,20 @@
using UnityEngine;
using System.Collections;
// B2 04 坐骑ID操作 #tagCSHorseIDOP
public class CB204_tagCSHorseIDOP : GameNetPackBasic {
    public byte OPType;    // 操作 1-激活;2-佩戴;
    public byte HorseID;
    public CB204_tagCSHorseIDOP () {
        combineCmd = (ushort)0x03FE;
        _cmd = (ushort)0xB204;
    }
    public override void WriteToBytes () {
        WriteBytes (OPType, NetDataType.BYTE);
        WriteBytes (HorseID, NetDataType.BYTE);
    }
}
Main/Core/NetworkPackage/ClientPack/CB2_NewFunction/CB204_tagCSHorseIDOP.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 89a08c975b158c54eac484cba180a62b
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/NetworkPackage/DTCFile/ServerPack/HA3_Function/DTCA305_tagSCHorseIDInfo.cs
New file
@@ -0,0 +1,14 @@
using UnityEngine;
using System.Collections;
// A3 05 坐骑ID信息 #tagSCHorseIDInfo
public class DTCA305_tagSCHorseIDInfo : DtcBasic
{
    public override void Done(GameNetPackBasic vNetPack)
    {
        base.Done(vNetPack);
        HA305_tagSCHorseIDInfo vNetData = vNetPack as HA305_tagSCHorseIDInfo;
        HorseManager.Instance.UpdateHorseIDInfo(vNetData);
    }
}
Main/Core/NetworkPackage/DTCFile/ServerPack/HA3_Function/DTCA305_tagSCHorseIDInfo.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 24c3deaa0b65c2f45a4f0d7a0cfda6ee
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/NetworkPackage/DataToCtl/PackageRegedit.cs
@@ -162,6 +162,7 @@
        Register(typeof(HA924_tagSCQunyingMatchList), typeof(DTCA924_tagSCQunyingMatchList));
        Register(typeof(HA925_tagSCQunyingPlayerInfo), typeof(DTCA925_tagSCQunyingPlayerInfo));
        Register(typeof(HC010_tagSCCrossZoneInfo), typeof(DTCC010_tagSCCrossZoneInfo));
        Register(typeof(HA305_tagSCHorseIDInfo), typeof(DTCA305_tagSCHorseIDInfo));
    }
    //主工程注册封包
Main/Core/NetworkPackage/ServerPack/HA3_Function/HA305_tagSCHorseIDInfo.cs
New file
@@ -0,0 +1,19 @@
using UnityEngine;
using System.Collections;
// A3 05 坐骑ID信息 #tagSCHorseIDInfo
public class HA305_tagSCHorseIDInfo : GameNetPackBasic {
    public uint HorseIDState;    // 坐骑ID解锁状态,按二进制位运算判断坐骑ID是否已解锁
    public byte HorseID;    // 当前佩戴的坐骑ID
    public HA305_tagSCHorseIDInfo () {
        _cmd = (ushort)0xA305;
    }
    public override void ReadFromBytes (byte[] vBytes) {
        TransBytes (out HorseIDState, vBytes, NetDataType.DWORD);
        TransBytes (out HorseID, vBytes, NetDataType.BYTE);
    }
}
Main/Core/NetworkPackage/ServerPack/HA3_Function/HA305_tagSCHorseIDInfo.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 38ac993795b713545bc24a1fce151d67
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/Guild/GuildBossWin.cs
@@ -525,7 +525,7 @@
    void InitAttack()
    {
        var skinConfig = HorseSkinConfig.Get(HorseManager.Instance.GetUsingHorseSkinID(false));
        var skinConfig = HorseSkinConfig.Get(HorseManager.Instance.GetUsingHorseSkinID());
        heroModel.Create(skinConfig.SkinID, PhantasmPavilionManager.Instance.GetMyModelSkinID(), 1.2f);
        heroModel.GetHero().SetSpeed(heroAtkSpeed);
        for (int i = 0; i < hurtValues.Length; i++)
Main/System/Horse/GetHorseWin.cs
New file
@@ -0,0 +1,38 @@
using System;
using UnityEngine;
public class GetHorseWin : UIBase
{
    [SerializeField] ImageEx iconImg;
    [SerializeField] TextEx nameTxt;
    [SerializeField] TextEx attrTxt;
    [SerializeField] ButtonEx okBtn;
    protected override void InitComponent()
    {
        okBtn.AddListener(CloseWindow);
    }
    protected override void OnPreOpen()
    {
        int horseID = HorseManager.Instance.unLockHorseID;
        int skinID = HorseManager.Instance.unLockSkinID;
        var horseIDConfig = HorseIDConfig.Get(horseID);
        if (horseIDConfig == null)
            return;
        var horseSkinConfig = HorseSkinConfig.Get(skinID);
        if (horseSkinConfig == null)
            return;
        nameTxt.text = horseSkinConfig.Name;
        iconImg.overrideSprite = UILoader.LoadSprite("HorseIcon", horseSkinConfig.Icon);
        iconImg.SetNativeSize();
        attrTxt.text = StringUtility.Concat(PlayerPropertyConfig.Get(horseIDConfig.AttrID).ShowName, " ", "+", PlayerPropertyConfig.GetValueDescription(horseIDConfig.AttrID, HorseManager.Instance.GetNowAttrValue(horseIDConfig.AttrID)));
    }
}
Main/System/Horse/GetHorseWin.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3a845cc8515c95b4bafd63b59c6d7a0e
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/Horse/HorseCarouselView.cs
New file
@@ -0,0 +1,229 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;
using System;
using System.Linq;
public class HorseCarouselView : MonoBehaviour, IDragHandler, IEndDragHandler, IBeginDragHandler
{
    /// 当前选中索引变化时触发
    public event Action OnSelectedIndexChanged;
    [Header("UI References")]
    public RectTransform content;          // 存放Item的父节点
    public GameObject horseItemPrefab;     // 坐骑Item预制体
    [Header("Carousel Settings")]
    List<HorseIDConfig> horseConfigs;  // 坐骑配置列表
    public float itemSpacing = 150f;       // 每个坐骑的间距
    public float scaleMax = 1.3f;          // 居中时的最大缩放
    public float scaleMin = 0.8f;          // 边缘时的最小缩放
    private List<HorseItem> items = new List<HorseItem>();
    // 当前逻辑中心的偏移量 (浮点数,用于平滑滑动)
    private float currentCenterOffset = 0f;
    private float targetCenterOffset = 0f;
    private bool isDragging = false;
    private int currentSelectedIndex = 0;
    /// <summary>
    /// 当前选中的坐骑ID
    /// </summary>
    public int CurrentHorseId
    {
        get
        {
            if (horseConfigs != null && horseConfigs.Count > 0 && currentSelectedIndex >= 0)
            {
                int realIndex = (currentSelectedIndex % horseConfigs.Count + horseConfigs.Count) % horseConfigs.Count;
                return horseConfigs[realIndex].HorseID;
            }
            return 0;
        }
    }
    void Start()
    {
        // 初始化池子:为了保证左右滑动不穿帮,视野内显示5个,池子可以建7个 (5 + 左右各多1个缓冲)
        int poolSize = 7;
        for (int i = 0; i < poolSize; i++)
        {
            GameObject go = Instantiate(horseItemPrefab, content);
            HorseItem item = go.GetComponent<HorseItem>();
            items.Add(item);
        }
        // 动态获取所有坐骑配置
        horseConfigs = HorseIDConfig.GetValues();
        // 按HorseSkinConfig的sortOrder和SkinID排序
        horseConfigs = horseConfigs.OrderBy(c =>
        {
            int skinID = HorseSkinConfig.horseIDTohorseSkinIDDict.TryGetValue(c.HorseID, out var id) ? id : int.MaxValue;
            var skinConfig = HorseSkinConfig.Get(skinID);
            return skinConfig?.sortOrder ?? int.MaxValue;
        }).ThenBy(c =>
        {
            return HorseSkinConfig.horseIDTohorseSkinIDDict.TryGetValue(c.HorseID, out var skinID) ? skinID : int.MaxValue;
        }).ToList();
        // 获取当前骑乘坐骑的索引
        int currentHorseID = HorseManager.Instance.horseID;
        int index = horseConfigs.FindIndex(c => c.HorseID == currentHorseID);
        InitWithHorse(index >= 0 ? index : 0);
    }
    // 界面打开时调用,传入当前骑乘的坐骑ID或序号
    public void InitWithHorse(int horseIndex)
    {
        currentSelectedIndex = horseIndex;
        currentCenterOffset = horseIndex;
        targetCenterOffset = horseIndex;
        UpdateItems();
    }
    // 点击选中指定索引的坐骑
    public void SelectIndex(int index)
    {
        int oldIndex = currentSelectedIndex;
        currentSelectedIndex = index;
        targetCenterOffset = index;
        currentCenterOffset = index;
        UpdateItems();
        if (oldIndex != currentSelectedIndex)
        {
            OnSelectedIndexChanged?.Invoke();
        }
    }
    /// <summary>
    /// 切换到上一个坐骑
    /// </summary>
    public void SelectPrevious()
    {
        int newIndex = (currentSelectedIndex - 1 + horseConfigs.Count) % horseConfigs.Count;
        SelectIndex(newIndex);
    }
    /// <summary>
    /// 切换到下一个坐骑
    /// </summary>
    public void SelectNext()
    {
        int newIndex = (currentSelectedIndex + 1) % horseConfigs.Count;
        SelectIndex(newIndex);
    }
    /// <summary>
    /// 刷新所有HorseItem的显示(外部调用接口)
    /// </summary>
    public void RefreshAllItems()
    {
        if (horseConfigs != null && horseConfigs.Count > 0)
        {
            UpdateItems();
        }
    }
    void Update()
    {
        // 没在拖拽时,平滑吸附到目标位置
        if (!isDragging && Mathf.Abs(currentCenterOffset - targetCenterOffset) > 0.001f)
        {
            currentCenterOffset = Mathf.Lerp(currentCenterOffset, targetCenterOffset, Time.deltaTime * 10f);
            UpdateItems();
        }
    }
    // 核心刷新逻辑
    private void UpdateItems()
    {
        int poolSize = items.Count;
        // 计算当前中心位置左侧应该从哪个偏移量开始渲染
        int startOffset = Mathf.FloorToInt(currentCenterOffset) - (poolSize / 2);
        for (int i = 0; i < poolSize; i++)
        {
            // 1. 计算逻辑索引 (包含负数情况)
            int logicIndex = startOffset + i;
            // 2. 将逻辑索引映射到实际的坐骑序号 (核心:实现首尾相连的无限循环)
            // 比如 horseConfigs.Count=10,当 logicIndex=-1时,realIndex=9 (末位)
            int realIndex = (logicIndex % horseConfigs.Count + horseConfigs.Count) % horseConfigs.Count;
            // 3. 计算该 Item 应该在的 X 坐标位置
            // logicIndex - currentCenterOffset 表示该 item 距离中心的逻辑距离
            float distanceToCenter = logicIndex - currentCenterOffset;
            float posX = distanceToCenter * itemSpacing;
            // 4. 更新 UI 显示
            HorseItem itemUI = items[i];
            itemUI.rectTransform.anchoredPosition = new Vector2(posX, 0);
            // 5. 计算缩放与选中状态 (根据距离中心的绝对距离)
            float absDist = Mathf.Abs(distanceToCenter);
            // 距离小于1时开始放大,大于等于1时为最小缩放
            float scaleProgress = Mathf.Clamp01(1f - absDist);
            float currentScale = Mathf.Lerp(scaleMin, scaleMax, scaleProgress);
            itemUI.rectTransform.localScale = Vector3.one * currentScale;
            // 6. 数据与表现更新 (刷新头像、名字等)
            itemUI.UpdateData(horseConfigs[realIndex], realIndex);
            itemUI.onItemClick = SelectIndex;
        }
    }
    #region 拖拽与吸附逻辑
    public void OnBeginDrag(PointerEventData eventData)
    {
        isDragging = true;
    }
    public void OnDrag(PointerEventData eventData)
    {
        // 将鼠标/手指的滑动像素转化为逻辑偏移量
        float dragDeltaX = eventData.delta.x;
        // 阻尼系数,控制滑动速度
        currentCenterOffset -= dragDeltaX / itemSpacing;
        UpdateItems();
    }
    public void OnEndDrag(PointerEventData eventData)
    {
        isDragging = false;
        // 松手时,计算应该吸附到哪一个整数索引
        // 加入惯性判断:如果滑动速度够快,直接进一位或退一位
        float speedX = eventData.delta.x;
        if (speedX > 5f)
        {
            targetCenterOffset = Mathf.Floor(currentCenterOffset);
        }
        else if (speedX < -5f)
        {
            targetCenterOffset = Mathf.Ceil(currentCenterOffset);
        }
        else
        {
            // 否则就近四舍五入吸附
            targetCenterOffset = Mathf.Round(currentCenterOffset);
        }
        // 更新最终选中的真实索引
        int oldIndex = currentSelectedIndex;
        currentSelectedIndex = (Mathf.RoundToInt(targetCenterOffset) % horseConfigs.Count + horseConfigs.Count) % horseConfigs.Count;
        if (oldIndex != currentSelectedIndex)
        {
            OnSelectedIndexChanged?.Invoke();
        }
        Debug.Log("当前选中坐骑序号: " + currentSelectedIndex);
    }
    #endregion
}
Main/System/Horse/HorseCarouselView.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b1022553a62dd7d47afae33bd87625e3
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/Horse/HorseItem.cs
New file
@@ -0,0 +1,43 @@
using UnityEngine;
using UnityEngine.UI;
public class HorseItem : MonoBehaviour
{
    public RectTransform rectTransform;
    public Image iconImg;
    public Image attrNameBg;
    public TextEx attrName;
    public Image lockImg;
    public TextEx useTxt;
    public ButtonEx clickBtn;
    // 点击回调,传递该Item对应的realIndex
    public System.Action<int> onItemClick;
    public int realIndex;
    // 刷新数据
    public void UpdateData(HorseIDConfig config, int index)
    {
        if (config == null)
            return;
        if (!HorseSkinConfig.horseIDTohorseSkinIDDict.TryGetValue(config.HorseID, out int skinID))
            return;
        var horseSkinConfig = HorseSkinConfig.Get(skinID);
        if (horseSkinConfig == null)
            return;
        iconImg.overrideSprite = UILoader.LoadSprite("HorseIcon", horseSkinConfig.Icon);
        iconImg.SetNativeSize();
        bool hasArrtId = PlayerPropertyConfig.HasKey(config.AttrID);
        attrNameBg.SetActive(hasArrtId);
        attrName.text = hasArrtId ? PlayerPropertyConfig.Get(config.AttrID).Name : string.Empty;
        lockImg.SetActive(!HorseManager.Instance.IsHorseUnlocked(config.HorseID));
        useTxt.SetActive(config.HorseID == HorseManager.Instance.horseID);
        realIndex = index;
        clickBtn.onClick.RemoveAllListeners();
        clickBtn.onClick.AddListener(() => onItemClick?.Invoke(realIndex));
    }
}
Main/System/Horse/HorseItem.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e7860cf1649b57e479b5a50f0058a8c8
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/Horse/HorseManager.cs
@@ -1,11 +1,13 @@
using System;
using LitJson;
using System.Collections.Generic;
using System.Linq;
public class HorseManager : GameSystemManager<HorseManager>
{
    public int unLockSkinID;
    public int unLockHorseID;
    public int classLV;    //当前阶级,从0开始
    public int horseLV;    //当前阶等级,从1开始
    public int exp;    //当前阶等级经验,每级从0开始
@@ -35,6 +37,8 @@
    public int lvUPItemID;
    public int rankUPItemID;
    public int quickRankLV;
    public int defaultSkinID;
    public override void Init()
    {
@@ -52,12 +56,15 @@
        DTC0403_tagPlayerLoginLoadOK.playerLoginOkEvent -= OnPlayerLoginOk;
    }
    void ParseConfig()
    {
        var config = FuncConfigConfig.Get("HorseUpItem");
        lvUPItemID = int.Parse(config.Numerical1);
        rankUPItemID = int.Parse(config.Numerical2);
        quickRankLV = int.Parse(config.Numerical3);
        defaultSkinID = int.Parse(config.Numerical4);
    }
    void OnBeforePlayerDataInitialize()
@@ -66,6 +73,8 @@
        horseLV = 0;
        exp = 0;
        skinDic.Clear();
        horseIDState = 0;
        horseID = 0;
    }
    void OnPlayerLoginOk()
@@ -90,14 +99,15 @@
    }
    //获取当前使用坐骑皮肤ID
    public int GetUsingHorseSkinID(bool useDefault = false)
    public int GetUsingHorseSkinID(bool useDefault = true)
    {
        var id = (int)PlayerDatas.Instance.baseData.equipShowSwitch % 1000;
        if (id == 0)
        // 0 未幻化
        if (id == 0 || !HorseSkinConfig.HasKey(id))
        {
            if (useDefault)
            {
                return 1;
                return defaultSkinID;
            }
        }
        return id;
@@ -106,9 +116,29 @@
    //获取其他玩家使用坐骑皮肤ID
    public int GetOtherPlayerHorseSkinID(int value)
    {
        return value % 1000;
        int id = value % 1000;
        if (id == 0 || !HorseSkinConfig.HasKey(id))
            return defaultSkinID;
        return id;
    }
    //是否处于幻化状态
    public bool IsChangeState(int skinID)
    {
        if (skinID == 0) return false;
        if (!HorseSkinConfig.HasKey(skinID)) return false;
        var config = HorseSkinConfig.Get(skinID);
        if (config == null) return false;
        return config.showType == 1;
    }
    public bool IsHorseIDSkin(int skinID)
    {
        var config = HorseSkinConfig.Get(skinID);
        if (config == null) return false;
        return config.showType == 2;
    }
    public void UpdateHorseInfo(HA303_tagSCHorseClassInfo netPack)
    {
@@ -153,6 +183,87 @@
            UIManager.Instance.OpenWindow<HorseSkinGetWin>(netPack.HorseSkinList[0].HorseSkinID);
        }
    }
    public void SendHorseIDOP(int horseID, int opType)
    {
        CB204_tagCSHorseIDOP pack = new CB204_tagCSHorseIDOP()
        {
            HorseID = (byte)horseID,
            OPType = (byte)opType
        };
        GameNetSystem.Instance.SendInfo(pack);
    }
    //坐骑ID信息
    public uint horseIDState;
    public int horseID;
    public event Action OnUpdateHorseIDInfoEvent;
    /// <summary>新解锁坐骑事件,参数:新解锁的坐骑ID (0-31)</summary>
    public event Action<int> OnHorseUnlockedEvent;
    /// <summary>佩戴坐骑变化事件,参数:(旧ID, 新ID)</summary>
    public event Action<int, int> OnHorseChangedEvent;
    public void UpdateHorseIDInfo(HA305_tagSCHorseIDInfo netPack)
    {
        // 1. 检测新解锁的坐骑 (horseID 从 0 开始)
        uint diff = horseIDState ^ netPack.HorseIDState; // 差异位
        uint newUnlock = netPack.HorseIDState & diff;       // 新增解锁的位
        if (newUnlock != 0)
        {
            for (int i = 0; i < 32; i++)
            {
                if ((newUnlock & (1u << i)) != 0)
                {
                    OnHorseUnlockedEvent?.Invoke(i); // i 就是坐骑ID (0-31)
                }
            }
        }
        // 2. 检测佩戴ID变化
        if (horseID != netPack.HorseID)
        {
            byte oldID = (byte)horseID;
            horseID = netPack.HorseID;
            OnHorseChangedEvent?.Invoke(oldID, horseID);
        }
        // 更新状态
        horseIDState = netPack.HorseIDState;
        RefreshAttr();
        OnUpdateHorseIDInfoEvent?.Invoke();
    }
    /// <summary>
    /// 判断坐骑ID是否已解锁 (horseID 从 0 开始)
    /// </summary>
    /// <param name="horseID">坐骑ID (0-31)</param>
    public bool IsHorseUnlocked(int horseID)
    {
        var config = HorseIDConfig.Get(horseID);
        if (config == null) return false;
        if (config.UnlockWay == 1) return true;
        if (horseID < 0 || horseID > 31) return false;
        return (horseIDState & (1u << horseID)) != 0;
    }
    public int GetNowAttrValue(int attrID)
    {
        int totalValue = 0;
        // 从0阶累加到当前阶
        for (int lv = 0; lv <= classLV; lv++)
        {
            var config = HorseClassConfig.Get(lv);
            if (config.ClassSpecAttrIDList != null)
            {
                int index = Array.IndexOf(config.ClassSpecAttrIDList, attrID);
                if (index >= 0 && config.ClassSpecAttrValueList != null && index < config.ClassSpecAttrValueList.Length)
                {
                    totalValue += config.ClassSpecAttrValueList[index];
                }
            }
        }
        return totalValue;
    }
    #region 红点
@@ -203,9 +314,13 @@
        }
        //升星/解锁红点
        foreach (var skin in HorseSkinConfig.GetValues())
        {
            if (skin.showType != 1)
                continue;
            if (IsSkinActive(skin.SkinID))
            {
                if (skin.StarMax > 0)
@@ -272,7 +387,8 @@
    public bool IsShowTheHorseRedImg(int skinID)
    {
        var skin = HorseSkinConfig.Get(skinID);
        if (skin.showType != 1)
            return false;
        if (IsSkinActive(skin.SkinID))
        {
            if (skin.StarMax > 0)
@@ -342,6 +458,7 @@
    {
        specialAttrDic.Clear();
        attrDic.Clear();
        for (int lv = 0; lv <= classLV; lv++)
        {
            //按阶加成
@@ -385,6 +502,7 @@
                }
            }
        }
    }
    //刷新皮肤属性
@@ -431,6 +549,53 @@
        }
    }
    public Dictionary<int, long> GetMergedAttrDic()
    {
        Dictionary<int, long> resultDic = new Dictionary<int, long>();
        // 合并 specialAttrDic
        foreach (var kvp in specialAttrDic)
        {
            resultDic[kvp.Key] = kvp.Value;
        }
        HorseIDConfig horseIDConfig = HorseIDConfig.Get(horseID);
        int horseIDAttrID = horseIDConfig.AttrID;
        float attrMultiValue = horseIDConfig.AttrMultiValue;
        var keys = resultDic.Keys.ToList();
        foreach (var key in keys)
        {
            if (key == horseIDAttrID)
            {
                resultDic[key] = (long)(resultDic[key] * attrMultiValue);
            }
        }
        // 合并 attrDic
        foreach (var kvp in attrDic)
        {
            if (resultDic.ContainsKey(kvp.Key))
                resultDic[kvp.Key] += kvp.Value;
            else
                resultDic[kvp.Key] = kvp.Value;
        }
        // 合并 skinAttrDic
        foreach (var kvp in skinAttrDic)
        {
            if (resultDic.ContainsKey(kvp.Key))
                resultDic[kvp.Key] += kvp.Value;
            else
                resultDic[kvp.Key] = kvp.Value;
        }
        return resultDic;
    }
    public long GetAttrValue(int attrID)
    {
        attrDic.TryGetValue(attrID, out long value);
@@ -458,7 +623,15 @@
    public List<int> sortSkinList = new List<int>();
    public void SortHorseSkinList()
    {
        sortSkinList = HorseSkinConfig.GetKeys();
        sortSkinList.Clear();
        var temp = HorseSkinConfig.GetValues();
        foreach (var config in temp)
        {
            if (config.showType == 1)
            {
                sortSkinList.Add(config.SkinID);
            }
        }
        sortSkinList.Sort((a, b) =>
        {
            var isActiveA = IsSkinActive(a);
@@ -469,9 +642,19 @@
            }
            var skinA = HorseSkinConfig.Get(a);
            var skinB = HorseSkinConfig.Get(b);
            // isActive 相同时,先按 sortOrder 排序
            if (skinA.sortOrder != skinB.sortOrder)
            {
                return skinA.sortOrder - skinB.sortOrder;
            }
            // sortOrder 相同时,再按 SkinID 排序
            return skinA.SkinID - skinB.SkinID;
        });
    }
    public Dictionary<int, long> GetAttrBySkinID(HorseSkinConfig config)
    {
@@ -524,7 +707,8 @@
    }
}
public class HorseSkin {
public class HorseSkin
{
    public int State;        //是否已激活
    public int EndTime;        //到期时间戳,0为永久
    public int Star;        //星级
Main/System/Horse/HorseRankUPWin.cs
@@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@@ -11,14 +12,17 @@
{
    [SerializeField] Text curLVText;
    [SerializeField] Text nextLVText;
    [SerializeField] Text[] attrNameTexts;
    [SerializeField] Text[] attrValueTexts;
    [SerializeField] Text[] nextAttrValueTexts;
    [SerializeField] Transform[] specialAttrRect;
    [SerializeField] Text[] specialAttrNameTexts;
    [SerializeField] Text[] specialAttrValueTexts;
    [SerializeField] Text[] specialNextAttrValueTexts;
    [SerializeField] Transform riderAttrRect;
    [SerializeField] Text riderAttrNameText;
    [SerializeField] Text riderAttrValueText;
    [SerializeField] Text riderNextAttrValueText;
    [SerializeField] Text noRiderText;
    [SerializeField] Text costText;
    [SerializeField] Image costItemImg;
@@ -50,18 +54,6 @@
        var keys = HorseManager.Instance.attrDic.Keys.ToList();
        keys.Sort();
        for (int i = 0; i < attrNameTexts.Length; i++)
        {
            if (i < keys.Count)
            {
                var curValue = HorseManager.Instance.attrDic[keys[i]];
                attrNameTexts[i].text = PlayerPropertyConfig.Get(keys[i]).Name;
                attrValueTexts[i].text = PlayerPropertyConfig.GetValueDescription(keys[i], curValue, 2);
                nextAttrValueTexts[i].text = PlayerPropertyConfig.GetValueDescription(keys[i],
                    curValue + nextConfig.ClassAttrValueList[i] + nextConfig.PerLVAttrValueList[i], 2);
            }
        }
        var nextKeys = HorseManager.Instance.specialAttrDic.Keys.ToList();
        nextKeys.Sort();
@@ -92,6 +84,38 @@
        costText.text = UIHelper.ShowUseItem(PackType.Item, HorseManager.Instance.rankUPItemID, HorseClassConfig.Get(HorseManager.Instance.classLV).ClassUPItemCnt);
        costItemImg.SetItemSprite(HorseManager.Instance.rankUPItemID);
        int nowhorseID = HorseManager.Instance.horseID;
        HorseSkinConfig.horseIDTohorseSkinIDDict.TryGetValue(nowhorseID, out int horseSkinID);
        var horseIdSkinConfig = HorseSkinConfig.Get(horseSkinID);
        var horseIDConfig = HorseIDConfig.Get(nowhorseID);
        if (horseIdSkinConfig == null || horseIDConfig == null) return;
        int attrID = horseIDConfig.AttrID;
        int attrValue = HorseManager.Instance.GetNowAttrValue(attrID);
        // 检查属性是否存在
        var propConfig = PlayerPropertyConfig.Get(attrID);
        if (propConfig == null)
        {
            // 没有属性
            noRiderText.SetActive(true);
            riderAttrRect.SetActive(false);
        }
        else
        {
            // 有属性
            noRiderText.SetActive(false);
            riderAttrRect.SetActive(true);
            riderAttrNameText.text = propConfig.Name;
            riderAttrValueText.text = PlayerPropertyConfig.GetValueDescription(attrID, (long)(attrValue * horseIDConfig.AttrMultiValue), 2);
            // 下一阶的属性值
            int index = Array.IndexOf(nextConfig.ClassSpecAttrIDList, attrID);
            if (index < 0 || index > nextConfig.ClassSpecAttrIDList.Length)
                return;
            long nextAttrValue = (long)((attrValue + nextConfig.ClassSpecAttrValueList[index]) * horseIDConfig.AttrMultiValue);
            riderNextAttrValueText.text = PlayerPropertyConfig.GetValueDescription(attrID, nextAttrValue, 2);
        }
    }
    //升阶
Main/System/Horse/HorseSkinCell.cs
@@ -31,7 +31,7 @@
    {
        skinID = _skinID;
        horseModel.Create(skinID, 0, 0.6f);
        emptyImg.SetActive(skinID == 0);
        emptyImg.SetActive(skinID == 999);
        usingObj.SetActive(HorseManager.Instance.GetUsingHorseSkinID() == skinID);
        bool isShowRed = HorseManager.Instance.IsShowTheHorseRedImg(skinID);
        redObj.SetActive(isShowRed);
Main/System/Horse/HorseSkinWin.cs
@@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
@@ -22,8 +23,7 @@
    [SerializeField] Image lvUpItemImg;
    [SerializeField] Button changeSkinBtn;
    [SerializeField] Text timeText;
    [SerializeField] Transform inSkinMarkObj;   //幻化中
    [SerializeField] Button removeBtn;  //解除幻化
    [SerializeField] Button unlockBtn;
    [SerializeField] Text unlockCostText;
    [SerializeField] Image unlockItemImg;
@@ -34,11 +34,15 @@
        lvUpBtn.AddListener(OnLvUpBtnClick);
        changeSkinBtn.AddListener(OnChangeSkinBtnClick);
        unlockBtn.AddListener(OnUnlockBtnClick);
        removeBtn.AddListener(OnRemoveBtnClick);
    }
    protected override void OnPreOpen()
    {
        HorseManager.Instance.selectSkinID = HorseManager.Instance.GetUsingHorseSkinID();
        HorseManager.Instance.SortHorseSkinList();
        int usingHorseSkinID = HorseManager.Instance.GetUsingHorseSkinID();
        HorseManager.Instance.selectSkinID = usingHorseSkinID == 0 || !HorseManager.Instance.sortSkinList.Contains(usingHorseSkinID) ? HorseManager.Instance.sortSkinList[0] : usingHorseSkinID;
        HorseManager.Instance.SortHorseSkinList();
        HorseManager.Instance.OnSkinUpdateEvent += OnSkinUpdateEvent;
        HorseManager.Instance.OnSelectEvent += OnSelectEvent;
@@ -140,12 +144,12 @@
            if (HorseManager.Instance.selectSkinID == HorseManager.Instance.GetUsingHorseSkinID())
            {
                changeSkinBtn.SetActive(false);
                inSkinMarkObj.SetActive(true);
                removeBtn.SetActive(true);
            }
            else
            {
                changeSkinBtn.SetActive(true);
                inSkinMarkObj.SetActive(false);
                removeBtn.SetActive(false);
            }
            ShowTime();
@@ -214,8 +218,10 @@
    void OnSeeAttrBtnClick()
    {
        AttributeManager.Instance.OpenSimpleAttributeWin(HorseManager.Instance.skinAttrDic, "AttributeTitle04");
        Dictionary<int, long> totalAttrDic = HorseManager.Instance.GetMergedAttrDic();
        AttributeManager.Instance.OpenSimpleAttributeWin(totalAttrDic, "AttributeTitle04");
    }
    string GetSkinAttrSring(HorseSkinConfig config)
@@ -280,4 +286,8 @@
        }
    }
    
    void OnRemoveBtnClick()
    {
        HorseManager.Instance.SendSkinOP(2, 0);
    }
}
Main/System/Horse/HorseSuccessWin.cs
@@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@@ -11,9 +12,6 @@
{
    [SerializeField] Text curLVText;
    [SerializeField] Text nextLVText;
    [SerializeField] Text[] attrNameTexts;
    [SerializeField] Text[] attrValueTexts;
    [SerializeField] Text[] nextAttrValueTexts;
    [SerializeField] Transform[] specialAttrRect;
    [SerializeField] Text[] specialAttrNameTexts;
@@ -21,6 +19,11 @@
    [SerializeField] Text[] specialNextAttrValueTexts;
    [SerializeField] Button okBtn;
    [SerializeField] Transform riderAttrRect;
    [SerializeField] Text riderAttrNameText;
    [SerializeField] Text riderAttrValueText;
    [SerializeField] Text riderNextAttrValueText;
    [SerializeField] Text noRiderText;
    protected override void InitComponent()
@@ -49,17 +52,6 @@
        var keys = HorseManager.Instance.attrDic.Keys.ToList();
        keys.Sort();
        for (int i = 0; i < attrNameTexts.Length; i++)
        {
            if (i < keys.Count)
            {
                var curValue = HorseManager.Instance.attrDic[keys[i]];
                attrNameTexts[i].text = PlayerPropertyConfig.Get(keys[i]).Name;
                attrValueTexts[i].text = PlayerPropertyConfig.GetValueDescription(keys[i],
                    curValue - nextConfig.ClassAttrValueList[i] - nextConfig.PerLVAttrValueList[i], 2);
                nextAttrValueTexts[i].text = PlayerPropertyConfig.GetValueDescription(keys[i], curValue, 2);
            }
        }
        var nextKeys = HorseManager.Instance.specialAttrDic.Keys.ToList();
        nextKeys.Sort();
@@ -88,8 +80,56 @@
                specialAttrRect[i].SetActive(false);
            }
        }
        int nowhorseID = HorseManager.Instance.horseID;
        HorseSkinConfig.horseIDTohorseSkinIDDict.TryGetValue(nowhorseID, out int horseSkinID);
        var horseIdSkinConfig = HorseSkinConfig.Get(horseSkinID);
        var horseIDConfig = HorseIDConfig.Get(nowhorseID);
        if (horseIdSkinConfig == null || horseIDConfig == null) return;
        int attrID = horseIDConfig.AttrID;
        // 【注意】因为已经进阶成功,此处获取的是进阶【后】的最新总值
        int newAttrValue = HorseManager.Instance.GetNowAttrValue(attrID);
        // 检查属性是否存在
        var propConfig = PlayerPropertyConfig.Get(attrID);
        if (propConfig == null)
        {
            // 没有属性
            noRiderText.SetActive(true);
            riderAttrRect.SetActive(false);
        }
        else
        {
            // 有属性
            noRiderText.SetActive(false);
            riderAttrRect.SetActive(true);
            riderAttrNameText.text = propConfig.Name;
            // 计算进阶前的旧值
            int oldAttrValue = newAttrValue; // 默认如果没有增量,旧值等于新值
            // nextConfig 变量在这里其实代表的是“刚达到的新阶级配置”
            if (nextConfig.ClassSpecAttrIDList != null)
            {
                // 在 ID 列表中寻找对应属性的索引
                int index = Array.IndexOf(nextConfig.ClassSpecAttrIDList, attrID);
                // 确保找到了,且索引没有越界(避免直接return导致UI没刷新)
                if (index >= 0 && nextConfig.ClassSpecAttrValueList != null && index < nextConfig.ClassSpecAttrValueList.Length)
                {
                    // 旧值 = 最新总值 - 本次进阶增加的数值
                    oldAttrValue = newAttrValue - nextConfig.ClassSpecAttrValueList[index];
                }
    }
            float multi = horseIDConfig.AttrMultiValue;
            // 左边显示升阶前的旧值
            riderAttrValueText.text = PlayerPropertyConfig.GetValueDescription(attrID, (long)(oldAttrValue * multi), 2);
            // 右边显示升阶后的新值
            riderNextAttrValueText.text = PlayerPropertyConfig.GetValueDescription(attrID, (long)(newAttrValue * multi), 2);
        }
    }
}
Main/System/Horse/HorseWin.cs
@@ -13,7 +13,6 @@
    [SerializeField] HorseController modelImg;
    [SerializeField] UIEffectPlayer lvUPEffect;
    [SerializeField] Text nameText;
    [SerializeField] Text specialAttrText;
    [SerializeField] Button skinBtn;
    [SerializeField] Text[] attrNameTexts;
    [SerializeField] Text[] attrValueTexts;
@@ -29,8 +28,20 @@
    [SerializeField] Text needUPText;
    [SerializeField] Button rankUpBtn;
    [SerializeField] TextEx[] specialAttrNameTexts;
    [SerializeField] TextEx[] specialAttrValueTexts;
    [SerializeField] Button lastHorseBtn;
    [SerializeField] Button nextHorseBtn;
    [SerializeField] HorseCarouselView carouselView;
    [SerializeField] ImageEx attrImg;
    [SerializeField] TextEx attrTxt;
    [SerializeField] TextEx noAttrTxt;
    [SerializeField] ButtonEx unlockBtn;
    [SerializeField] TextEx moneyTxt;
    [SerializeField] ImageEx moneyImg;
    [SerializeField] ButtonEx useBtn;
    [SerializeField] TextEx usingTxt;
    int beforeLV;
    protected override void InitComponent()
@@ -43,6 +54,64 @@
        lvupBtn.onPress.AddListener(HorseUpgrade);
        quickUpToggle.onValueChanged.AddListener((bool value) => { OnToggle(value); });
        rankUpBtn.AddListener(HorseRankUpgrade);
        lastHorseBtn.AddListener(OnLastHorse);
        nextHorseBtn.AddListener(OnNextHorse);
        useBtn.AddListener(() =>
        {
            HorseManager.Instance.SendHorseIDOP(carouselView.CurrentHorseId, 2);
            if (!HorseSkinConfig.horseIDTohorseSkinIDDict.TryGetValue(carouselView.CurrentHorseId, out var skinID))
                return;
            bool isChangeState = HorseManager.Instance.IsChangeState(HorseManager.Instance.GetUsingHorseSkinID(false));
            if (!isChangeState)
            {
                HorseManager.Instance.SendSkinOP(2, skinID);
            }
        });
        unlockBtn.AddListener(() =>
        {
            if (!HorseSkinConfig.horseIDTohorseSkinIDDict.TryGetValue(carouselView.CurrentHorseId, out var skinID))
                return;
            var skinConfig = HorseSkinConfig.Get(skinID);
            if (skinConfig == null)
                return;
            var horseConfig = HorseIDConfig.Get(carouselView.CurrentHorseId);
            if (horseConfig == null)
                return;
            ConfirmCancel.MoneyIconToggleConfirmByType(
                ToggleCheckType.Horse,
                horseConfig.UnlockNeedCnt,
                horseConfig.UnlockValue,
                Language.Get("Horse20", UIHelper.GetIconNameWithMoneyType(horseConfig.UnlockValue), horseConfig.UnlockNeedCnt),
                () =>
                {
                    if (!UIHelper.CheckMoneyCount(horseConfig.UnlockValue, horseConfig.UnlockNeedCnt, 2))
                    {
                        return;
                    }
                    HorseManager.Instance.unLockHorseID = carouselView.CurrentHorseId;
                    HorseManager.Instance.SendHorseIDOP(carouselView.CurrentHorseId, 1);
                    HorseManager.Instance.unLockSkinID = skinID;
                    bool isChangeState = HorseManager.Instance.IsChangeState(HorseManager.Instance.GetUsingHorseSkinID(false));
                    if (!isChangeState)
                    {
                        HorseManager.Instance.SendSkinOP(2, skinID);
                    }
                });
        });
    }
    void OnLastHorse()
    {
        carouselView?.SelectPrevious();
    }
    void OnNextHorse()
    {
        carouselView?.SelectNext();
    }
    protected override void OnPreOpen()
@@ -53,6 +122,30 @@
        HorseManager.Instance.OnHorseUpdateEvent += OnHorseUpdateEvent;
        PlayerDatas.Instance.playerDataRefreshEvent += PlayerDataRefresh;
        PackManager.Instance.RefreshItemEvent += OnRefreshItemEvent;
        HorseManager.Instance.OnUpdateHorseIDInfoEvent += OnUpdateHorseIDInfoEvent;
        carouselView.OnSelectedIndexChanged += OnSelectedIndexChanged;
        HorseManager.Instance.OnHorseUnlockedEvent += OnHorseUnlockedEvent;
        // 每次打开时,恢复到当前骑乘的坐骑
        if (carouselView != null)
        {
            int currentHorseID = HorseManager.Instance.horseID;
            // 动态获取所有坐骑配置
            var horseConfigs = HorseIDConfig.GetValues();
            // 按HorseSkinConfig的sortOrder和SkinID排序
            horseConfigs = horseConfigs.OrderBy(c =>
            {
                int skinID = HorseSkinConfig.horseIDTohorseSkinIDDict.TryGetValue(c.HorseID, out var id) ? id : int.MaxValue;
                var skinConfig = HorseSkinConfig.Get(skinID);
                return skinConfig?.sortOrder ?? int.MaxValue;
            }).ThenBy(c =>
            {
                return HorseSkinConfig.horseIDTohorseSkinIDDict.TryGetValue(c.HorseID, out var skinID) ? skinID : int.MaxValue;
            }).ToList();
            int index = horseConfigs.FindIndex(c => c.HorseID == currentHorseID);
            carouselView.InitWithHorse(index >= 0 ? index : 0);
        }
        Display();
    }
@@ -62,6 +155,28 @@
        HorseManager.Instance.OnHorseUpdateEvent -= OnHorseUpdateEvent;
        PlayerDatas.Instance.playerDataRefreshEvent -= PlayerDataRefresh;
        PackManager.Instance.RefreshItemEvent -= OnRefreshItemEvent;
        HorseManager.Instance.OnUpdateHorseIDInfoEvent -= OnUpdateHorseIDInfoEvent;
        carouselView.OnSelectedIndexChanged -= OnSelectedIndexChanged;
        HorseManager.Instance.OnHorseUnlockedEvent -= OnHorseUnlockedEvent;
    }
    private void OnHorseUnlockedEvent(int horseID)
    {
        if (!UIManager.Instance.IsOpened<GetHorseWin>())
        {
            UIManager.Instance.OpenWindow<GetHorseWin>();
        }
    }
    private void OnUpdateHorseIDInfoEvent()
    {
        Display();
        carouselView.RefreshAllItems();
    }
    private void OnSelectedIndexChanged()
    {
        DisplayModel();
    }
    void OnHorseUpdateEvent()
@@ -112,12 +227,69 @@
        UIManager.Instance.OpenWindow<HorseRankUPWin>();
    }
    void DisplayModel()
    {
        int nowSkinID = HorseManager.Instance.GetUsingHorseSkinID(false);
        int nowhorseID = HorseManager.Instance.horseID;
        int showHorseID = carouselView.CurrentHorseId;
        HorseSkinConfig.horseIDTohorseSkinIDDict.TryGetValue(showHorseID, out int horseSkinID);
        var horseIdSkinConfig = HorseSkinConfig.Get(horseSkinID);
        bool isChangeState = HorseManager.Instance.IsChangeState(nowSkinID);
        modelImg.Create(isChangeState ? nowSkinID : horseSkinID, PhantasmPavilionManager.Instance.GetMyModelSkinID());
        nameText.text = isChangeState ? HorseSkinConfig.Get(nowSkinID).Name : horseIdSkinConfig.Name;
        HorseIDConfig horseIDConfig = HorseIDConfig.Get(showHorseID);
        float attrMultiValue = horseIDConfig.AttrMultiValue;
        var attrConfig = PlayerPropertyConfig.Get(horseIDConfig.AttrID);
        bool hasAttr = attrConfig != null;
        attrImg.SetActive(hasAttr);
        noAttrTxt.SetActive(!hasAttr);
        if (hasAttr)
        {
            attrTxt.text = StringUtility.Concat(attrConfig.ShowName, " ", "+", PlayerPropertyConfig.GetValueDescription(horseIDConfig.AttrID, GetAttrValue(horseIDConfig.AttrID, attrMultiValue)));
        }
        useBtn.SetActive(carouselView.CurrentHorseId != nowhorseID && HorseManager.Instance.IsHorseUnlocked(carouselView.CurrentHorseId));
        usingTxt.SetActive(carouselView.CurrentHorseId == nowhorseID);
        bool isUnlock = HorseManager.Instance.IsHorseUnlocked(carouselView.CurrentHorseId);
        unlockBtn.SetActive(!isUnlock);
        if (!isUnlock)
        {
            moneyImg.SetIconWithMoneyType(horseIDConfig.UnlockValue);
            moneyTxt.text = horseIDConfig.UnlockNeedCnt.ToString();
        }
    }
    public int GetAttrValue(int attrID, float attrMultiValue)
    {
        int totalValue = 0;
        int currentClassLV = HorseManager.Instance.classLV;
        // 从0阶累加到当前阶
        for (int lv = 0; lv <= currentClassLV; lv++)
        {
            var config = HorseClassConfig.Get(lv);
            if (config.ClassSpecAttrIDList != null)
            {
                int index = Array.IndexOf(config.ClassSpecAttrIDList, attrID);
                if (index >= 0 && config.ClassSpecAttrValueList != null && index < config.ClassSpecAttrValueList.Length)
                {
                    totalValue += config.ClassSpecAttrValueList[index];
                }
            }
        }
        return totalValue * (int)attrMultiValue;
    }
    void Display()
    {
        var skinConfig = HorseSkinConfig.Get(HorseManager.Instance.GetUsingHorseSkinID());
        modelImg.Create(skinConfig.SkinID, PhantasmPavilionManager.Instance.GetMyModelSkinID());
        nameText.text = skinConfig.Name;
        specialAttrText.text = GetSpecialAttr();
        DisplayModel();
        DisplaySpecialAttr();
        var config = HorseClassConfig.Get(HorseManager.Instance.classLV);
        lvText.text = Language.Get("Horse8", HorseManager.Instance.classLV, HorseManager.Instance.horseLV);
@@ -194,17 +366,27 @@
        }
    }
    string GetSpecialAttr()
    public void DisplaySpecialAttr()
    {
        if (HorseManager.Instance.specialAttrDic.Count == 0)
            return "";
        var specialAttrDic = HorseManager.Instance.specialAttrDic;
        var attrs = specialAttrDic.Keys.ToList();
        int arrtCnt = attrs.Count;
        List<string> attrList = new List<string>();
        foreach (var attrID in HorseManager.Instance.specialAttrDic.Keys)
        for (int i = 0; i < specialAttrNameTexts.Length; i++)
        {
            attrList.Add(UIHelper.AppendColor(TextColType.itemchuanqi, PlayerPropertyConfig.GetFullDescription(attrID, HorseManager.Instance.specialAttrDic[attrID])));
            if (i < arrtCnt)
            {
                specialAttrNameTexts[i].SetActive(true);
                specialAttrValueTexts[i].SetActive(true);
                specialAttrNameTexts[i].text = PlayerPropertyConfig.Get(attrs[i]).Name;
                specialAttrValueTexts[i].text = StringUtility.Concat("+", PlayerPropertyConfig.GetValueDescription(attrs[i], specialAttrDic.ContainsKey(attrs[i]) ? specialAttrDic[attrs[i]] : 0, 2));
        }
        return Language.Get("L1100", Language.Get("herocard55"), string.Join(Language.Get("L1112"), attrList));
            else
            {
                specialAttrNameTexts[i].SetActive(false);
                specialAttrValueTexts[i].SetActive(false);
            }
        }
    }
    void OnToggle(bool value)
@@ -235,7 +417,7 @@
        switch (type)
        {
            case PlayerDataType.EquipShowSwitch:
                var skinConfig = HorseSkinConfig.Get(HorseManager.Instance.GetUsingHorseSkinID());
                var skinConfig = HorseSkinConfig.Get(HorseManager.Instance.GetUsingHorseSkinID(true));
                modelImg.Create(skinConfig.SkinID, PhantasmPavilionManager.Instance.GetMyModelSkinID());
                nameText.text = skinConfig.Name;
                break;
Main/System/Main/HomeWin.cs
@@ -877,8 +877,21 @@
        {
            horseBGImg.SetActive(true);
            //equipShowSwitch;//当前配置的坐骑外观ID存储在(最大支持 1~999)
            var skinConfig = HorseSkinConfig.Get(HorseManager.Instance.GetUsingHorseSkinID(true));
            var skinConfig = HorseSkinConfig.Get(HorseManager.Instance.GetUsingHorseSkinID());
            if (skinConfig.SkinID == 999)
            {
                int nowhorseID = HorseManager.Instance.horseID;
                HorseSkinConfig.horseIDTohorseSkinIDDict.TryGetValue(nowhorseID, out int horseSkinID);
                var horseIdSkinConfig = HorseSkinConfig.Get(horseSkinID);
                horseImg.Create(horseIdSkinConfig.SkinID, 0, 0.6f);
            }
            else
            {
            horseImg.Create(skinConfig.SkinID, 0, 0.6f);
            }
            horseLVText.text = Language.Get("Horse8", HorseManager.Instance.classLV, HorseManager.Instance.horseLV);
        }
        else
Main/System/OtherPlayerDetail/OtherPlayerDetailWin.cs
@@ -191,7 +191,16 @@
        if (horseData != null)
        {
            txtHorseLV.text = Language.Get("Horse8", horseData.ClassLV, horseData.LV);
            horseController.Create(horseSkinID <= 0 ? 1 : horseSkinID, scale: 0.6f);
            if (horseSkinID == 999)
            {
                horseController.Create(HorseManager.Instance.defaultSkinID, scale: 0.6f);
            }
            else
            {
                horseController.Create(horseSkinID, scale: 0.6f);
            }
        }
    }
Main/System/Tip/ConfirmCancel.cs
@@ -358,6 +358,7 @@
    GoldRush = 2,   //淘金 
    BoneField = 3,   //白骨盈野
    FuncPreset = 4, //功能(流派)预设
    Horse = 5,//坐骑购买
}