SRPG游戏开发(五十五)第十一章 地图动作与地图事件 - 四 地图菜单(Map Menu)

本文详细介绍了在SRPG游戏开发中如何实现地图菜单,包括主菜单和角色单位菜单的设计,以及如何处理点击事件。内容涵盖菜单文本的管理、UI面板的初始化、菜单的打开与关闭,以及不同类型的菜单选项和对应的点击事件处理逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

返回总目录

第十一章 地图动作与地图事件(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
    }
}

Map Menu

  • 图 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)

我们每一个按钮需要回调一个MenuTextIDMapAction,这其实相当于每一个按钮都做了同样的事。

        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.CloseUIMapMenuPanel.MenuTextID.MoveUIMapMenuPanel.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;
    

到这里,我们就完成了菜单的部分编写。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值