第七章 寻路与地图对象(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)是否存在,只需要做位与运算,即0000 0111 & 0000 0010 = 0000 0010;类似的我们希望知道地图对象(0000 1000)是否存在,即0000 0111 & 0000 1000 = 0000 0000;你会发现它们的区别,即存在的结果与输入的结果相同,而不存在的结果为0。
public bool CheckStatus(CellStatus status)
{
return (m_Status & status) == status;
}
这样我们确实可以检测了,但我们还希望能够知道是否有开关开启,比如我们希望知道是否有光标在格子上,这样检测就不可以了,但我们发现,只有关闭时才为0,主要有开启的就会大于0;例如,假设原始状态为0000 0101,即0000 0101 & (0000 0100 | 0000 0010) = 0000 0100 > 0,

本文详细介绍了在SRPG游戏开发中优化地图和寻路的过程,包括优化格子数据,如使用二进制开关保存状态,以及修改寻路和光标显示的代码。此外,还讨论了优化测试代码,修复对象池的错误,以提高游戏性能。
最低0.47元/天 解锁文章
第七章 寻路与地图对象 - 八 优化地图(Optimize MapGraph)&spm=1001.2101.3001.5002&articleId=82666493&d=1&t=3&u=4e92d4fa193145069b203003b67f29c4)
4352

被折叠的 条评论
为什么被折叠?



