职业选择
职业选择菜单按钮
为选择按钮添加脚本,并挂载对应UI对象。
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结构体占有内存较多,使用静态变量定义一份。
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());
}
当下层按钮调用上层窗口职业切换逻辑时,首先取消原来按钮的选中状态,保存新按钮,并进行实际的职业切换逻辑(单独封一个方法)。
这里要注意的是每个按钮只负责鼠标事件的触发和音效生成,相应的职业切换逻辑需要交由上层窗口进行复杂功能的处理。
测试结果如上,点击职业对应的Button,调用上层窗口逻辑切换职业,并在面板上输出当前职业类型(连续点击相同职业不会有新的输出)。
不同职业的动画
清空模型身上的动画控制器,取消Root Motion,自己写。
为每个职业创建动画控制器,并在Res文件下找Idle动画拖进每个控制器内。
切换职业动画
新建一个创建角色场景管理器CreateCharacterScene,主要用于控制场景加载和事件监听。
public class CreateCharacterScene : LogicManagerBase<CreateCharacterScene>
{
private void Start()
{
//显示角色选择窗口
//UIManager.Instance.Show<UI_CreateCharacterWindow>();
}
}
新建一个角色创建者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索引即可。
到目前为止,已经实现了按钮按下触发职业切换逻辑并切换待机动画,下一步需要更换每个职业的武器。
设置职业武器
为了避免AA在分包时把网格带有的模型一起加载,需要把武器上挂载的Mesh改成之前单独存好的不含有其他模型资源的Mesh。
清理角色root下挂载的武器种类,符合职业需求,使用独立出来的Mesh。
切换职业武器
逻辑层面做好武器显示/隐藏。
//不同职业的武器
[SerializeField] GameObject[] warriorWeapons;
[SerializeField] GameObject[] assassinWeapons;
[SerializeField] GameObject[] archerWeapons;
[SerializeField] GameObject[] tankWeapons;
//当前的武器
private GameObject[] currentWeapons;
存储好每个职业的武器(左右手可能都有武器),并记录当前武器,在面板上按职业拖拽赋值(战士单手剑,刺客双手剑,弓箭手左箭右弓,坦克左盾右剑)。
/// <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下,便于管理。
在此基础上,对角色拖拽旋转中获取玩家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);
}
最终效果。