职业选择

职业选择菜单按钮

为选择按钮添加脚本,并挂载对应UI对象。

alt

public class UI_ProfessionButton : MonoBehaviour
{
    [SerializeField] Button button; //按钮
    [SerializeField] Image icon;  //图标
    [SerializeField] Image selectFrame; //边框
    [SerializeField] AudioClip clickAudioClip; //按钮音效
    private static Color[] colors; //按钮颜色
    static UI_ProfessionButton()
    {
        colors = new Color[2];
        colors[0] = Color.white;
        colors[1] = new Color(0.964f, 0.882f, 0.611f);
    }
}

Color为四个按钮共有,由于color结构体占有内存较多,使用静态变量定义一份。

alt

    public void Init(UI_CreateCharacterWindow window, ProfessionType professionType)
    {
        button.onClick.AddListener(ButtonClick);
        this.window = window;
        this.professionType = professionType;
        //默认没有选中
        UnSelect();
    }
    private void Selecte()
    {
        icon.color = colors[1];
        nameText.color = colors[1];
        selectFrame.enabled = true;
    }
    /// <summary>
    /// 取消选中
    /// </summary>
    private void UnSelect()
    {
        icon.color = colors[0];
        nameText.color = colors[0];
        selectFrame.enabled = false;
    }

职业选择按钮初始化时添加按钮监听,默认没有选中,选中时文字变为金色,边框变亮。

    /// <summary>
    /// 鼠标点击事件
    /// </summary>
    private void ButtonClick()
    {
        //告诉窗口,当前按钮代表的职业被玩家选择了
        AudioManager.Instance.PlayOnShot(clickAudioClip, Vector3.zero, 1, false);
        window.SelectProfession_Button(this);
    }

按下按钮时,触发点击事件播放音效,并交由上层CreateCharacterWindow窗口进行职业切换的逻辑。

    //所有的职业按钮
    [SerializeField] private UI_ProfessionButton[] professionButtons;

    //当前选择的职业按钮
    private UI_ProfessionButton currentProfessionButton;

    public override void Init()
    {
        modelTouchImage.OnDrag(ModelTouchImageDrag, 6);
        //绑定角色预览
        characterPreviewTransform =Player_Controller.Instance.transform;

        //初始化职业按钮
        professionButtons[0].Init(this, ProfessionType.Warrior);
        professionButtons[1].Init(this, ProfessionType.Assassin);
        professionButtons[2].Init(this, ProfessionType.Archer);
        professionButtons[3].Init(this, ProfessionType.Tank);

        //默认选择一个职业(战士)
        SelectProfession_Button(professionButtons[0]);
    }

上层窗口持有四种职业对应的按钮,并进行初始化。

    /// <summary>
    /// 选择职业按钮
    /// </summary>
    public void SelectProfession_Button(UI_ProfessionButton newButton)
    {
        //相同啥也不干
        if (currentProfessionButton == newButton) return;

        //处理之前的职业按钮
        if (currentProfessionButton != null) currentProfessionButton.UnSelect();

        //处理新按钮
        newButton.Select();
        currentProfessionButton = newButton;
        SelectProfession(newButton.ProfessionType);

    }

    /// <summary>
    /// 选择职业
    /// </summary>
    private void SelectProfession(ProfessionType professionType)
    {
        //TODO:处理实际的职业切换逻辑
        Debug.Log(professionType.ToString());
    }

当下层按钮调用上层窗口职业切换逻辑时,首先取消原来按钮的选中状态,保存新按钮,并进行实际的职业切换逻辑(单独封一个方法)。

这里要注意的是每个按钮只负责鼠标事件的触发和音效生成,相应的职业切换逻辑需要交由上层窗口进行复杂功能的处理。

alt

测试结果如上,点击职业对应的Button,调用上层窗口逻辑切换职业,并在面板上输出当前职业类型(连续点击相同职业不会有新的输出)。

不同职业的动画

清空模型身上的动画控制器,取消Root Motion,自己写。

alt

为每个职业创建动画控制器,并在Res文件下找Idle动画拖进每个控制器内。

alt

切换职业动画

alt

新建一个创建角色场景管理器CreateCharacterScene,主要用于控制场景加载和事件监听。

public class CreateCharacterScene : LogicManagerBase<CreateCharacterScene>
{
    private void Start()
    {
        //显示角色选择窗口
        //UIManager.Instance.Show<UI_CreateCharacterWindow>();

    }
}

alt

新建一个角色创建者CharacterCreator,用来持有AnimatorController资源以及切换逻辑(不给角色控制器,避免带到下个场景)。

/// <summary>
/// 角色创建者
/// </summary>
public class CharacterCreator : SingletonMono<CharacterCreator>
{
    [SerializeField] Animator animator;

    //不同职业的预览动画
    [SerializeField] RuntimeAnimatorController[] animatorControllers;

    //TODO:不同职业的武器

    /// <summary>
    /// 设置职业
    /// </summary>
    public void SetProfession(ProfessionType professionType)
    {
        animator.runtimeAnimatorController = animatorControllers[(int)professionType];
        //TODO:切换武器
    }
}

//UI_CreateCharacterWindow类
/// <summary>
/// 选择职业
/// </summary>
private void SelectProfession(ProfessionType professionType)
{
    //处理实际的职业切换逻辑
    CharacterCreator.Instance.SetProfession(professionType);

    //TODO:检查当前已经应用的部位是否有不支持这个职业的情况

}

持有角色的Animator和创建的四种职业待机动画,当切换职业按钮触发时,调用上层窗口进行职业切换逻辑,其中就包括动画的切换,持有的职业待机动画控制器顺序与枚举顺序一致,直接转换成int索引即可。

alt

到目前为止,已经实现了按钮按下触发职业切换逻辑并切换待机动画,下一步需要更换每个职业的武器。

alt

设置职业武器

为了避免AA在分包时把网格带有的模型一起加载,需要把武器上挂载的Mesh改成之前单独存好的不含有其他模型资源的Mesh。

alt

清理角色root下挂载的武器种类,符合职业需求,使用独立出来的Mesh。

alt

切换职业武器

逻辑层面做好武器显示/隐藏。

    //不同职业的武器
    [SerializeField] GameObject[] warriorWeapons;
    [SerializeField] GameObject[] assassinWeapons;
    [SerializeField] GameObject[] archerWeapons;
    [SerializeField] GameObject[] tankWeapons;
    //当前的武器
    private GameObject[] currentWeapons;

存储好每个职业的武器(左右手可能都有武器),并记录当前武器,在面板上按职业拖拽赋值(战士单手剑,刺客双手剑,弓箭手左箭右弓,坦克左盾右剑)。

alt

    /// <summary>
    /// 设置职业
    /// </summary>
    public void SetProfession(ProfessionType professionType)
    {   
        //设置预览动画
        animator.runtimeAnimatorController = animatorControllers[(int)professionType];
        //设置武器
        //隐藏当前武器
        if (currentWeapons != null)
        {   
            for (int i = 0; i < currentWeapons.Length; i++)
            {
                currentWeapons[i].SetActive(false);
            }
        }

        //设置职业确定当前的武器数组
        switch (professionType)
        {
            case ProfessionType.Warrior:
                currentWeapons = warriorWeapons;
                break;
            case ProfessionType.Assassin:
                currentWeapons = assassinWeapons;
                break;
            case ProfessionType.Archer:
                currentWeapons = archerWeapons;
                break;
            case ProfessionType.Tank:
                currentWeapons = tankWeapons;
                break;
            default:
                break;
        }

        //显示当前的武器
        for (int i = 0; i < currentWeapons.Length; i++)
        {
            currentWeapons[i].SetActive(true);
        }

    }

根据当前武器数组显示/关闭武器对象,先全部关闭,根据职业设置好当前武器数组后再打开即可。 在这里对场景结构进行调整,为CharacterCreator单独创建一个空物体用于管理角色控制逻辑,CreateCharacterSceneManger用于管理场景,GameRoot管理框架级逻辑,分三层,将Player对象拖拽到CharacterCreator下,便于管理。

alt

在此基础上,对角色拖拽旋转中获取玩家Transform的逻辑进行优化,由于UI窗口是动态加载的没办法拖拽玩家对象,原来是通过获取玩家上的PlayerController单例来得到Transform,需要额外挂载脚本,简单起见所以委托CharacterCreator进行逻辑处理,其可以通过拖拽获取玩家对象(实际上给场景中任何一个单例都行,只要窗口能找到)。

		//对比,UI_CreateCharacterWindow类
        CharacterCreator.Instance.RotateCharacter(new Vector3(0, -offset * Time.deltaTime * 60f, 0));
        // characterPreviewTransform =Player_Controller.Instance.transform;
        //characterPreviewTransform.Rotate(new Vector3(0, -offset * Time.deltaTime * 60f, 0));

		//CharacterCreator类
        [SerializeField] Transform characterTransform;
		public void RotateCharacter(Vector3 rot)
    	{
        	characterTransform.Rotate(rot);
    	}

最终效果。

alt