第十一章 地图动作与地图事件(Map Action and Map Event)
我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制。
我们这一章来完成地图上的操作,地图的操作将全部由MapAction
控制。
文章目录
四 地图菜单(Map Menu)
就像上一节我们说的,游戏菜单分为主菜单和角色菜单。主菜单主要是提供一些公共的功能,例如所有角色的列表;而角色菜单提供一些角色独有的功能,例如移动、状态和攻击等。
你可以按照自己的喜好布局,并发送点击事件,让MapAction
接收事件。不过我这里使用一个SubUIButtonLayoutGroup
来控制它们,所有的回调直接让MapAction
控制。
而这需要一个UI面板UIMapMenuPanel
:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace DR.Book.SRPG_Dev.UI
{
using DR.Book.SRPG_Dev.Framework;
using DR.Book.SRPG_Dev.Models;
public class UIMapMenuPanel : UIBase
{
private SubUIButtonLayoutGroup m_ButtonLayoutGroup;
// TODO
}
}
- 图 11.1 Map Menu
1 菜单文本(Menu Text)
按钮文本可以直接写在Unity的UI中,不过为了更好的控制,我将它们放入了TextInfoConfig
中,这样就需要它们的标识,称为MenuTextID
,它是一个枚举类型,而值是在TextInfoConfig
中的id
。
/// <summary>
/// id in TextInfoConfig
/// </summary>
public enum MenuTextID
{
// Main
Unit = 100,
Item = 101,
Data = 102,
Skill = 103,
Config = 104,
Save = 105,
TurnEnd = 106,
// Common
Close = 110,
// Player
Move = 120,
Holding = 121,
Talk = 122,
Attack = 123,
Status = 124
}
而我们的TextInfoConfig.txt
中:
//id text
0 测试对话0
1 测试对话1:Hello World!Hello 是你好的意思。
2 测试对话2:Bottom Hello World!
5 测试对话5
8 测试对话8
100 作战单位
101 物品
102 数据
103 技能
104 设置
105 存档
106 回合结束
110 关闭
120 移动
121 待机
122 对话
123 攻击
124 状态
有了这些之后,我们在打开菜单时,只需要传入需要打开的值就可以了。
再添加一些帮助方法:
/// <summary>
/// 获取所有主菜单的按钮文本
/// </summary>
/// <returns></returns>
public static HashSet<MenuTextID> GetDefaultMainMenuTextIds()
{
return new HashSet<MenuTextID>()
{
MenuTextID.Unit,
MenuTextID.Item,
MenuTextID.Data,
MenuTextID.Skill,
MenuTextID.Config,
MenuTextID.Save,
MenuTextID.TurnEnd,
MenuTextID.Close
};
}
/// <summary>
/// 获取所有角色菜单按钮文本
/// </summary>
/// <returns></returns>
public static HashSet<MenuTextID> GetDefaultUnitMenuTextIds()
{
return new HashSet<MenuTextID>()
{
MenuTextID.Move,
MenuTextID.Attack,
MenuTextID.Holding,
MenuTextID.Talk,
MenuTextID.Status,
MenuTextID.Close
};
}
2 初始化UI面板与点击事件(Initialize UIMapMenuPanel and Click Event)
我们每一个按钮需要回调一个MenuTextID
给MapAction
,这其实相当于每一个按钮都做了同样的事。
private Action<MenuTextID> m_OnItemClickAction;
private void Button_onClick(MenuTextID menuTextID)
{
UIManager.views.CloseView();
if (m_OnItemClickAction != null)
{
m_OnItemClickAction(menuTextID);
m_OnItemClickAction = null;
}
}
而其它按钮只需要调用它就可以了,建立它们:
private void ButtonUnit_onClick()
{
Button_onClick(MenuTextID.Unit);
}
// 省略其它按钮
我们在打开菜单时,需要按照MenuTextID
来打开需要的按钮,这就需要我们保存每个按钮的信息。
还要保存每一个方法,再点击时再触发这些方法,我们用文本来作为标识。
private readonly Dictionary<MenuTextID, SubUIButtonLayoutGroup.ItemOption> m_MenuOptions
= new Dictionary<MenuTextID, SubUIButtonLayoutGroup.ItemOption>();
private readonly Dictionary<string, Action> m_ButtonClickActions
= new Dictionary<string, Action>();
我们在初始化时,需要设置按钮的文本:
/// <summary>
/// 初始化按钮,
/// </summary>
/// <param name="textId"></param>
/// <param name="action"></param>
private void InitButtonOption(MenuTextID textId, Action action)
{
TextInfoConfig config = TextInfoConfig.Get<TextInfoConfig>();
TextInfo info = config[textId.ToInteger()];
SubUIButtonLayoutGroup.ItemOption option = new SubUIButtonLayoutGroup.ItemOption(info.text);
m_MenuOptions[textId] = option;
m_ButtonClickActions[info.text] = action;
}
/// <summary>
/// 初始化所有按钮
/// </summary>
private void InitButtonOptions()
{
m_MenuOptions.Clear();
m_ButtonClickActions.Clear();
InitButtonOption(MenuTextID.Close, ButtonClose_onClick);
InitButtonOption(MenuTextID.Unit, ButtonUnit_onClick);
InitButtonOption(MenuTextID.Item, ButtonItem_onClick);
InitButtonOption(MenuTextID.Data, ButtonData_onClick);
InitButtonOption(MenuTextID.Skill, ButtonSkill_onClick);
InitButtonOption(MenuTextID.Config, ButtonConfig_onClick);
InitButtonOption(MenuTextID.Save, ButtonSave_onClick);
InitButtonOption(MenuTextID.TurnEnd, ButtonTurnEnd_onClick);
InitButtonOption(MenuTextID.Move, ButtonMove_onClick);
InitButtonOption(MenuTextID.Holding, ButtonHolding_onClick);
InitButtonOption(MenuTextID.Talk, ButtonTalk_onClick);
InitButtonOption(MenuTextID.Attack, ButtonAttack_onClick);
InitButtonOption(MenuTextID.Status, ButtonStatus_onClick);
}
最终我们的初始化方法:
protected override IEnumerator OnLoadingView()
{
InitButtonOptions();
m_ButtonLayoutGroup = this.FindChildComponent<SubUIButtonLayoutGroup>("ButtonLayoutGroup");
m_ButtonLayoutGroup.onItemClick.AddListener(ButtonLayoutGroup_onItemClick);
yield break;
}
private void ButtonLayoutGroup_onItemClick(GameObject buttonGameObject, int index, string message)
{
Text text = buttonGameObject.GetComponentInChildren<Text>();
Action action;
if (!m_ButtonClickActions.TryGetValue(text.text, out action))
{
Debug.LogError("Button Click Action was not found.");
Button_onClick(MenuTextID.Close);
return;
}
action();
}
3 在UI面板中打开菜单(Open Menu in UIMapMenuPanel)
在这里,要注意的是 打开UI面板并不等于打开菜单 。这是由于我们并不是把按钮全部显示。
所以我们在打开面板的同时隐藏菜单:
protected override void OnOpen(params object[] args)
{
m_ButtonLayoutGroup.Display(false);
}
我们单独建立一个打开菜单的方法:
/// <summary>
/// 打开菜单
/// </summary>
/// <param name="textIds"></param>
/// <param name="onItemClick"></param>
/// <param name="left"></param>
public void OpenMenu(IEnumerable<MenuTextID> textIds, Action<MenuTextID> onItemClick)
{
if (onItemClick == null)
{
Debug.LogError("OpenMapMenu error: `action` is null.");
UIManager.views.CloseView(viewName);
return;
}
m_OnItemClickAction = onItemClick;
m_ButtonLayoutGroup.itemOptions.Clear();
// 设置需要打开的按钮
if (textIds != null)
{
foreach (MenuTextID item in textIds)
{
m_ButtonLayoutGroup.itemOptions.Add(m_MenuOptions[item]);
}
}
// 没有按钮显示,直接关闭
if (m_ButtonLayoutGroup.itemOptions.Count == 0)
{
UIManager.views.CloseView(viewName);
onItemClick(MenuTextID.Close);
return;
}
// 打开菜单
m_ButtonLayoutGroup.Display(true);
}
4 打开地图菜单(Open Map Menu)
在MapAction
中,我们已经建立了打开菜单的方法protected void ShowMapMenu(bool sub)
,在这里我们将填充它。
步骤为:
-
1 打开UI面板;
-
2 设置需要显示的按钮;
-
3 打开菜单。
基于以上,建立方法:
/// <summary>
/// 显示地图菜单
/// </summary>
protected void ShowMapMenu(bool sub)
{
UIMapMenuPanel panel = UIManager.views.OpenView<UIMapMenuPanel>(UINames.k_UIMapMenuPanel, true);
HashSet<UIMapMenuPanel.MenuTextID> showButtons;
// 如果点击的不是角色,那么显示主菜单
if (selectedUnit == null)
{
// TODO 设置需要打开的主菜单按钮
}
else
{
// TODO 设置需要打开的角色按钮
}
panel.OpenMenu(showButtons, MapMenu_OnButtonClick);
}
/// <summary>
/// 菜单按钮点击事件
/// </summary>
/// <param name="menuTextID"></param>
private void MapMenu_OnButtonClick(UIMapMenuPanel.MenuTextID menuTextID)
{
switch (menuTextID)
{
case UIMapMenuPanel.MenuTextID.Unit:
// TODO
break;
// 省略其它case
}
}
4.1 主菜单(Main Menu)
我们默认主菜单就是全部打开。而当本回合有角色行动过了,那么不显示“存档”按钮。
关于角色单位,我们将按照阵营保存:
protected readonly Dictionary<AttitudeTowards, List<MapClass>> m_UnitDict
= new Dictionary<AttitudeTowards, List<MapClass>>();
主菜单:
if (selectedUnit == null)
{
showButtons = UIMapMenuPanel.GetDefaultMainMenuTextIds();
// 如果有角色行动过了,就不能存档
foreach (MapClass mapClass in m_UnitDict[AttitudeTowards.Player])
{
if (mapClass.role.holding)
{
showButtons.Remove(UIMapMenuPanel.MenuTextID.Save);
break;
}
}
}
4.2 角色单位菜单(Unit Menu)
我们在这里规定:
- 玩家
- 移动之前,不能做其它动作(即,除了移动和状态外,其它不显示);
- 移动之后,显示动作按钮(即,除了移动和状态外,其它显示);
- NPC
- 显示移动;
- 显示状态。
填充方法:
// 如果点击的是角色,那么显示角色菜单
else
{
showButtons = UIMapMenuPanel.GetDefaultUnitMenuTextIds();
// 如果是玩家
if (selectedUnit.role.attitudeTowards == AttitudeTowards.Player)
{
// 如果是移动后的菜单
if (sub)
{
showButtons.Remove(UIMapMenuPanel.MenuTextID.Move);
showButtons.Remove(UIMapMenuPanel.MenuTextID.Status);
// 如果物品栏没有武器,或所有武器不可用
bool canAtk = false;
for (int i = 0; i < selectedUnit.role.items.Length; i++)
{
Item item = selectedUnit.role.items[i];
if (item == null || item.itemType != ItemType.Weapon)
{
continue;
}
// 有武器,但职业不能装备此武器
WeaponUniqueInfo info = item.uniqueInfo as WeaponUniqueInfo;
//if (info.weaponType == WeaponType.Staff)
//{
// continue;
//}
if (info.level > selectedUnit.role.cls.info.availableWeapons[info.weaponType])
{
continue;
}
canAtk = true;
break;
}
if (!canAtk)
{
showButtons.Remove(UIMapMenuPanel.MenuTextID.Attack);
}
// TODO 检测Talk
showButtons.Remove(UIMapMenuPanel.MenuTextID.Talk);
}
// 移动之前的菜单
else
{
// 移动之前不能待机
showButtons.Remove(UIMapMenuPanel.MenuTextID.Holding);
// 如果目标已经待机了,不可移动
if (selectedUnit.role.holding)
{
showButtons.Remove(UIMapMenuPanel.MenuTextID.Move);
}
// 移动之前不能攻击与谈话
showButtons.Remove(UIMapMenuPanel.MenuTextID.Attack);
showButtons.Remove(UIMapMenuPanel.MenuTextID.Talk);
}
}
// 如果不是玩家,只能显示移动范围与查看状态
else
{
showButtons.Remove(UIMapMenuPanel.MenuTextID.Holding);
showButtons.Remove(UIMapMenuPanel.MenuTextID.Talk);
showButtons.Remove(UIMapMenuPanel.MenuTextID.Attack);
}
}
4.3 点击事件(Click Event)
我们来填充点击事件的方法MapMenu_OnButtonClick
。
我们在这里先添加三个UIMapMenuPanel.MenuTextID.Close
、UIMapMenuPanel.MenuTextID.Move
和UIMapMenuPanel.MenuTextID.Attack
。
-
关闭按钮
UIMapMenuPanel.MenuTextID.Close
:关闭,相当于我们点击了右键,直接重置就可以了:
case UIMapMenuPanel.MenuTextID.Close: ResetSelected(); break;
-
移动按钮
UIMapMenuPanel.MenuTextID.Move
:这里,我们要显示我们的移动范围:
case UIMapMenuPanel.MenuTextID.Move: if (map.SearchAndShowMoveRange(selectedUnit, true)) { mapStatus = MapStatus.MoveCursor; DisplayMouseCursor(true); } else { mapStatus = MapStatus.Normal; error = "MapAction -> Search Move Range error"; status = ActionStatus.Error; } break;
-
攻击按钮
UIMapMenuPanel.MenuTextID.Attack
:这里,我们应该打开选择武器面板,但我们先忽略它,直接显示默认武器的攻击范围。
case UIMapMenuPanel.MenuTextID.Attack: //// TODO (这里应显示选择武器面板,需修修改) WeaponUniqueInfo weaponInfo = selectedUnit.role.equipedWeapon.uniqueInfo; List<CellData> atkCells = map.SearchAttackRange(movingEndCell, weaponInfo.minRange, weaponInfo.maxRange, true); map.ShowRangeCursors(atkCells, MapCursor.CursorType.Attack); mapStatus = MapStatus.AttackCursor; DisplayMouseCursor(true); break;
到这里,我们就完成了菜单的部分编写。