SRPG游戏开发(三十)第七章 寻路与地图对象 - 八 优化地图(Optimize MapGraph)

返回总目录第七章 寻路与地图对象(Pathfinding and Map Object)这一章主要进行寻路与地图对象的部分工作。第七章 寻路与地图对象(Pathfinding and Map Object)八 优化地图(Optimize MapGraph)1 优化格子数据(Optimize CellData)1.1 打开与关闭开关(Switch On or Off)...
摘要由CSDN通过智能技术生成

返回总目录

第七章 寻路与地图对象(Pathfinding and Map Object)

这一章主要进行寻路与地图对象的部分工作。



八 优化地图(Optimize MapGraph)

这一节我们来优化地图相关的代码。


1 优化格子数据(Optimize CellData)

当我们显示移动范围后,再次点击地图角色将移动到指定位置,在我们之前的EditorTestPathFinding中,我们是保存了移动范围,然后判断点击的格子是否在集合中;而有没有Tile我们使用了一个bool变量保存;在这里我们可以做一些文章。

其实可以想到,格子有几种状态:

  • 有没有Tile

  • 有没有移动范围网格

  • 有没有攻击范围网格

  • 有没有地图对象

这些属性我们时常需要进行判断,每一个我们都单独会写变量保存状态,这里我们可以整合变量,使用一个二进制来保存它们,每一位表示一个开关。

首先,建立一个二进制Enum

using System;

namespace DR.Book.SRPG_Dev.Maps
{
    /// <summary>
    /// 格子状态
    /// </summary>
    [Serializable, Flags]
    public enum CellStatus : byte
    {
        /// <summary>
        /// 没有任何东西, 0000 0000
        /// </summary>
        None = 0,

        /// <summary>
        /// 有TerrainTile, 0000 0001
        /// </summary>
        TerrainTile = 0x01,

        /// <summary>
        /// 移动光标, 0000 0010
        /// </summary>
        MoveCursor = 0x02,

        /// <summary>
        /// 攻击光标, 0000 0100
        /// </summary>
        AttackCursor = 0x04,

        /// <summary>
        /// 地图对象, 0000 1000
        /// </summary>
        MapObject = 0x08

        // 如果有其它需求,在这里添加其余4个开关属性

        /// <summary>
        /// 全部8个开关, 1111 1111
        /// </summary>
        All = byte.MaxValue
    }
}

这样你会看到,我们有8个开关的位置,而byte只占1个字节,即1个字节保存了8个“bool”变量。

如果你需要更多的开关位置,请继承相应数字的无符号类型(有符号类型有负数参与,你需要更深入的了解二进制补码知识与计算),例如你需要32个开关,你可以:

using System;

[Serializable, Flags]
public enum YourSwitch : UInt32
{
    // your switches

    /// <summary>
    /// 1111 1111 1111 1111 1111 1111 1111 1111
    /// </summary>
    All = UInt32.MaxValue   
}

然后,在CellData中添加这个变量:

private CellStatus m_Status = CellStatus.None;

最后,我们来说说如何打开和关闭它们。

1.1 打开与关闭开关(Switch On or Off)

之前我们已经使用过二进制的Enum(Direction),但没有过多详细介绍过它。这里我们将介绍一下二进制的位运算。

我们的基本类型byte,1字节有8位,取值范围为[0, 255],即二进制的区间[0000 0000, 1111 1111],每一位都可表示成一个开关。

想要更了解进制之间的转换与计算方式,可以访问我的另一篇文章编程基础 - 进制转换(Binary Conversion)

每一位上的二进制,1都代表打开(存在),0都代表关闭(不存在)。我们的开关m_Status默认为CellStatus.None即全部是关闭的,二进制位 0000 0000

  • 打开开关:

    要打开其中某一个开关,根据位运算规则,我们需要或(|)操作这个位置的值;举例来说,如果此格子有移动光标,从右数第2位表示此开关,只需要或(|)上0000 0010即可打开开关,即0000 0000 | 0000 0010 = 0000 0010

  • 关闭开关:

    我们先假设Tile、移动光标与攻击光标都存在,即0000 0111(游戏中不会出现这种状态,移动光标与攻击光标是不能同时出现的)。

    我们希望能够关闭移动光标,即希望结果为0000 0101,而移动光标为0000 0010;发现4种位运算都不能满足我们的情况,不过很容易看到0000 0111 - 0000 0010 = 0000 0101可以达到我们的效果。不过这产生了一个新的问题,当我们的开关已经是关闭状态了,就得不到我们的需求。

    例如我们希望关闭MapObject,即0000 0111 - 0000 1000 = 1111 1111,我的天啊,开关居然全部都打开了。这可不是我们需要的,我们希望开关都能保持不变,即依然是0000 0111

    要解决这个问题有两种方式:

    • 其一,我们可通过观察,相减的结果再次位与(&)开关原始状态即可得到结果,即(0000 0111 - 0000 0010) & 0000 0111 = 0000 0101,关闭MapObject(0000 0111 - 0000 1000) & 0000 0111 = 0000 0111。但这种运算使用了减法,在某些语言中(比如C#)是不支持byte运算的,需要类型转换,这有些麻烦,所以我们采用第二种方式。

    • 其二,我们需要将它关闭,其它开关打开,即取反(~)得到1111 1101,然后再与开关做位与(&)运算,即0000 0111 & 1111 1101 = 0000 0101;再次试验关闭MapObject,即0000 0111 & (~ 0000 1000) = 0000 0111。这样就解决了关闭问题。

基于以上分析,我们的代码为:

        /// <summary>
        /// 设置状态开关
        /// </summary>
        /// <param name="status"></param>
        /// <param name="isOn"></param>
        public void SwitchStatus(CellStatus status, bool isOn)
        {
            if (isOn)
            {
                m_Status |= status;
            }
            else
            {
                m_Status &= ~status;
            }
        }

这样,我们就可以控制是否打开开关与关闭开关了,拥有了设置开关属性,我们还要能够利用开关,即知道开关的状态。

1.2 检查开关(Check Switch)

要知道开关是否被打开,只需要其对应的二进制数字是否为1即可。这只需要一个位与(&)运算即可。

例如,假设原始状态为0000 0111;我们希望知道攻击光标(0000 0010)是否存在,只需要做位与运算࿰

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值