实现在游戏场景内根据DataManager中存的角色部位数据将角色模型还原出来。

在讲具体怎么还原之前先来回顾一下存档中心DataManager持有的数据关系。

alt

DataManager持有CustomCharacterData,保存了所有部位的数据,可以通过字典的Int key(对应枚举类型),获得每个部位下的具体数据,包括是哪一种,颜色,尺寸等信息,通过部位枚举类型的String值和对应的第几种配置Int值组合就构成了我们在AA中存储的具有特定命名规则的一个SO实例名,获得了这个配置的网格、材质信息,以此最终获得了完整的一个部位数据(存档存了每个部位类型的配置索引,颜色,大小,根据配置索引和部位类型找到配置文件存了同一部位下的每种类型的可使用职业,网格,材质信息。 )。

通过代码将以上思路完成。首先创建个GameSceneManager用于管理游戏场景里的全局逻辑。

// GameSceneManager类
Player_Controller.Instance.Init();

一行逻辑,我们希望通过角色模型身上的Controller去完成他自己的角色模型还原。

public class Player_Controller : SingletonMono<Player_Controller>
{
    [SerializeField] Player_View view;

    public void Init()
    {
        view.InitOnGame(DataManager.CustomCharacterData);
    }
}

PlayerController调用自己的View层去实现模型还原。

    public void Init(CustomCharacterData customCharacterData)
    {
        //让每一个部位的材质都实例化一份自己的材质球,互不干扰
        partSkinnedMeshRenderers[0].sharedMaterial = Instantiate(partMaterials[0]);//对应PBRMaskTint_HeadParts 1
        partSkinnedMeshRenderers[1].sharedMaterial = Instantiate(partMaterials[0]);//对应PBRMaskTint_HeadParts 1
        partSkinnedMeshRenderers[2].sharedMaterial = Instantiate(partMaterials[2]);//对应PBRMaskTint_BodyParts 1
        this.customCharacter = customCharacterData;
    }

    public void InitOnGame(CustomCharacterData customCharacterData)
    {
        Init(customCharacterData);
        //基于数据设置当前部位
        CharacterPartConfigBase faceConfig = ConfigTool.LoadCharacterPartConfig(CharacterParType.Face, customCharacterData.CustomPartDataDic.Dictionary[(int)CharacterParType.Face].Index);
        CharacterPartConfigBase clothConfig = ConfigTool.LoadCharacterPartConfig(CharacterParType.Cloth, customCharacterData.CustomPartDataDic.Dictionary[(int)CharacterParType.Cloth].Index);
        CharacterPartConfigBase hairConfig = ConfigTool.LoadCharacterPartConfig(CharacterParType.Hair, customCharacterData.CustomPartDataDic.Dictionary[(int)CharacterParType.Hair].Index);       

        SetPart(faceConfig, true);
        SetPart(hairConfig, true);
        SetPart(clothConfig, true);
    }

在上一阶段换装界面,我们已经在Player_View里实现了Init方法,用于实例化材质并初始化角色数据,在这之后会有自定义角色窗口去根据数据完成角色的初始化。在游戏场景内这一逻辑需要我们自己再做一次,调用Init方法完成同样的数据初始化逻辑,通过ConfigTool根据当前部位类型和对应的部位种类索引拿到配置SO,再根据配置SO内的网格、材质和角色数据内的颜色信息完成角色模型的还原,传入true表示刷新角色模型。

	//Player_View类
    public void SetPart(CharacterPartConfigBase characterPartConfig, bool updateCharacterView = true)
    {

        if (characterPartDic.TryGetValue((int)characterPartConfig.CharacterParType, out CharacterPartConfigBase currentConfigBase))
        {

            //释放旧配置的资源
            ResManager.Release<CharacterPartConfigBase>(currentConfigBase);
            characterPartDic[(int)characterPartConfig.CharacterParType] = characterPartConfig;

        }
        else
        {
            //这个部位之前是空的,不存在资源释放问题
            characterPartDic.Add((int)characterPartConfig.CharacterParType, characterPartConfig);
        }

        //不更新资源
        if (!updateCharacterView) return;

        switch (characterPartConfig.CharacterParType)
        {

            case CharacterParType.Face:
                FaceConfig faceConfig = characterPartConfig as FaceConfig;
                partSkinnedMeshRenderers[0].sharedMesh = characterPartConfig.Mesh1;
                CustomCharacterPartData facePartData = customCharacter.CustomPartDataDic.Dictionary[(int)CharacterParType.Face];
                SetSize(CharacterParType.Face, customCharacter.CustomPartDataDic.Dictionary[(int)CharacterParType.Face].Size);
                SetHeight(CharacterParType.Face, customCharacter.CustomPartDataDic.Dictionary[(int)CharacterParType.Face].Height);
                break;

            case CharacterParType.Hair:
                HairConfig hairConfig = characterPartConfig as HairConfig;
                partSkinnedMeshRenderers[1].sharedMesh = hairConfig.Mesh1;
                SetColor1(CharacterParType.Hair, customCharacter.CustomPartDataDic.Dictionary[(int)CharacterParType.Hair].Color1.ConverToUnityColor());
                break;

            case CharacterParType.Cloth:
                ClothConfig clothConfig = characterPartConfig as ClothConfig;
                partSkinnedMeshRenderers[2].sharedMesh = clothConfig.Mesh1;
                SetColor1(CharacterParType.Cloth, customCharacter.CustomPartDataDic.Dictionary[(int)CharacterParType.Cloth].Color1.ConverToUnityColor());
                SetColor2(CharacterParType.Cloth, customCharacter.CustomPartDataDic.Dictionary[(int)CharacterParType.Cloth].Color2.ConverToUnityColor());
                break;

        }
    }

由于现在测试必须从Menu场景进来才有存档的数据,方便测试对GameSceneManager进行改造,在当前场景有GameRoot的情况下,可以直接从当前场景进行默认角色创建的测试(每次创建新存档会覆盖本地文件,所以实际上只有从未勾选IsCreateArchive的情况下读到的才是上次保存的存档数据)。

public class GameSceneManager : LogicManagerBase<GameSceneManager>
{
    #region 测试逻辑
    public bool IsTest;
    public bool IsCreateArchive;
    #endregion
    private void Start()
    {
        #region 测试逻辑
        if (IsTest)
        {
            if (IsCreateArchive)
            {
                DataManager.CreateArchive();
            }
            else
            {
                DataManager.LoadCurrentArchive();
            }
        }
        #endregion
        //初始化角色
        Player_Controller.Instance.Init();

    }
}

alt

测试结果:

alt

修个小BUG,从自定义角色场景切换到游戏场景的时候原来持有的Config没释放,可以看到每一份配置文件都有两份AA资源(AudioPlay受对象池管理可以复用有多份正常,UI类勾选了可以缓存也可能被重用不释放正常)。

原因是换场景之后原来的Player_View没了上面的characterPartDic也空了,新场景没办法通过判断characterPartDic里面有没有数据释放原来的AA资源,得在窗口关闭的时候手动释放

alt

	//Player_View类
    public void ReleaseCharacterConfig()
    {   
        // 0-FaceConfig 1-HairConfig 3-ClothConfig
        for (int i = 0; i < 8; i++)
        {          
            if (characterPartDic.TryGetValue(i, out CharacterPartConfigBase currentConfigBase))
            {
                //释放所有剩余配置的资源
                ResManager.Release<CharacterPartConfigBase>(currentConfigBase);

            }
        }
    }

alt

修正后AA正常持有一份配置。

alt

补充,上面这种修改方法实际是让characterPartDic的生命周期跟着UI窗口走,在自定义角色场景里这是没问题的,但更好的办法是让他生命周期就跟着Player_View类自己,使用OnDestroy。

    private void OnDestroy()
    {   
        //释放资源
        foreach (var item in characterPartDic)
        {
            ResManager.Release<CharacterPartConfigBase>(item.Value);
        }
    }