我来为您逐行详细讲解这个 `ViewMgr.cs` 文件。这是一个Unity游戏中的视野管理系统,用于优化游戏性能。
## 文件结构概览
这个文件主要包含以下几个部分:
1. 数据结构和接口定义
2. 视野管理器 `ViewMgr` 类
3. 工具类 `ViewTools`
让我逐行为您讲解:
## 1. 命名空间和引用
```1:7:Scripts/WeiLai/Mgr/ViewMgr.cs
using System.Collections.Generic;
using UnityEngine;
using WeiLai.Common;
using WeiLai.GameWorld.Camera;
using WeiLai.GameWorld;
using UnityEngine.Rendering;
```
这些是必要的引用:
- `System.Collections.Generic` - 用于List和Dictionary
- `UnityEngine` - Unity核心功能
- `WeiLai.Common` - 项目通用功能
- `WeiLai.GameWorld.Camera` - 相机管理
- `WeiLai.GameWorld` - 游戏世界相关
- `UnityEngine.Rendering` - 渲染相关
## 2. 数据结构定义
### GridRangeInt 类
```9:25:Scripts/WeiLai/Mgr/ViewMgr.cs
public class GridRangeInt : IRecycle
{
public int minPosX;
public int minPosY;
public int maxPosX;
public int maxPosY;
public void Recycle()
{
}
public void Fetch()
{
minPosX = 0;
minPosY = 0;
maxPosX = 0;
maxPosY = 0;
}
}
```
这个类表示整数网格范围:
- 继承 `IRecycle` 接口,支持对象池回收
- 存储网格的最小和最大X、Y坐标
- `Recycle()` - 回收对象(这里为空实现)
- `Fetch()` - 从对象池获取时重置数据
### GridRangeFloat 类
```27:43:Scripts/WeiLai/Mgr/ViewMgr.cs
public class GridRangeFloat : IRecycle
{
public float minPosX;
public float minPosY;
public float maxPosX;
public float maxPosY;
public void Recycle()
{
}
public void Fetch()
{
minPosX = 0;
minPosY = 0;
maxPosX = 0;
maxPosY = 0;
}
}
```
与 `GridRangeInt` 类似,但使用浮点数存储世界坐标范围。
### SkinnerItem 类
```45:70:Scripts/WeiLai/Mgr/ViewMgr.cs
public class SkinnerItem : IRecycle
{
public IRecycleSkinner m_Skinner;
public GridRangeInt m_RangeInt;
public GridRangeFloat m_RangeFloat;
public void Recycle()
{
m_Skinner = null;
if (m_RangeInt != null)
{
m_RangeInt.Recycle();
m_RangeInt = null;
}
if (m_RangeFloat != null)
{
m_RangeFloat.Recycle();
m_RangeFloat = null;
}
}
public void Fetch()
{
m_Skinner = null;
m_RangeFloat = null;
m_RangeInt = null;
}
}
```
这个类包装了皮肤对象和其范围信息:
- `m_Skinner` - 实现了 `IRecycleSkinner` 接口的对象
- `m_RangeInt` - 整数网格范围
- `m_RangeFloat` - 浮点数世界坐标范围
- `Recycle()` - 回收时清理所有引用
- `Fetch()` - 获取时重置所有引用
## 3. 接口定义
### IRecycleSkinnerMoveable 接口
```72:85:Scripts/WeiLai/Mgr/ViewMgr.cs
public interface IRecycleSkinnerMoveable
{
/// <summary>
/// 进入摄像机视野
/// </summary>
public void InCameraView();
/// <summary>
/// 退出摄像机视野
/// </summary>
public void OutCameraView();
/// <summary>
/// 获取位置
/// </summary>
public Vector3 GetPosition();
}
```
可移动对象的接口:
- `InCameraView()` - 进入视野时的回调
- `OutCameraView()` - 退出视野时的回调
- `GetPosition()` - 获取当前位置
### IRecycleSkinner 接口
```87:97:Scripts/WeiLai/Mgr/ViewMgr.cs
public interface IRecycleSkinner
{
/// <summary>
/// 进入摄像机视野
/// </summary>
public void InCameraView();
/// <summary>
/// 退出摄像机视野
/// </summary>
public void OutCameraView();
}
```
静态对象的接口,比可移动对象少了位置获取方法。
## 4. ViewItem 类
```99:105:Scripts/WeiLai/Mgr/ViewMgr.cs
/// <summary>
/// 存放格子数据的,这个就不缓存了,整个游戏初始化以后一直保留
/// </summary>
public class ViewItem
{
public bool m_Visible;
public List<SkinnerItem> m_SkinnerList;
}
```
表示一个网格单元:
- `m_Visible` - 是否在视野内
- `m_SkinnerList` - 该网格内的所有皮肤对象列表
## 5. ViewMgr 主类
### 成员变量
```107:130:Scripts/WeiLai/Mgr/ViewMgr.cs
public class ViewMgr : Singleton<ViewMgr>
{
/// <summary>
/// 视野相机
/// </summary>
private Camera m_ViewCamera;
/// <summary>
/// 所有的视野对象
/// </summary>
private Dictionary<int, ViewItem> m_ItemMap = new Dictionary<int, ViewItem>();
/// <summary>
/// 上一次对象
/// </summary>
private List<ViewItem> m_LastViewItems = new List<ViewItem>();
/// <summary>
/// 怪物listcache
/// </summary>
private List<Monster> m_MonsterList = new List<Monster>();
private List<SceneGridItem> m_SceneGridList = new List<SceneGridItem>();
/// <summary>
/// 皮肤和对应皮肤管理对象的缓存容器
/// </summary>
private Dictionary<IRecycleSkinner, SkinnerItem>
m_CacheSkinner = new Dictionary<IRecycleSkinner, SkinnerItem>();
private List<IRecycleSkinnerMoveable> m_Moveables = new List<IRecycleSkinnerMoveable>();
```
- `m_ViewCamera` - 视野相机引用
- `m_ItemMap` - 所有网格的字典,key是网格ID
- `m_LastViewItems` - 上一帧在视野内的网格列表
- `m_MonsterList` - 视野内怪物的缓存列表
- `m_SceneGridList` - 视野内场景网格的缓存列表
- `m_CacheSkinner` - 皮肤对象到SkinnerItem的映射
- `m_Moveables` - 可移动对象列表
### 初始化方法
```132:135:Scripts/WeiLai/Mgr/ViewMgr.cs
public void InitData()
{
m_ItemMap.Clear();
}
```
清空所有网格数据。
### 可移动对象管理
```137:147:Scripts/WeiLai/Mgr/ViewMgr.cs
public void AddMoveableItem(IRecycleSkinnerMoveable skinner)
{
return;
m_Moveables.Add(skinner);
}
public void RemoveMoveableItem(IRecycleSkinnerMoveable skinner)
{
return;
m_Moveables.Remove(skinner);
}
```
注意这里直接返回了,说明可移动对象功能被暂时禁用。
### 添加视野对象
```149:155:Scripts/WeiLai/Mgr/ViewMgr.cs
public void AddViewItem(IRecycleSkinner skinner, float posX, float posY)
{
return;
AddViewItem(skinner, ViewTools.GetGridRange(posX, posY), ViewTools.GetGridRangeFloat(posX, posY));
}
```
这个重载方法也被禁用了,原本应该调用下面的完整版本。
```157:200:Scripts/WeiLai/Mgr/ViewMgr.cs
public void AddViewItem(IRecycleSkinner skinner, GridRangeInt gridRange, GridRangeFloat gridRangeFloat)
{
return;
if (m_CacheSkinner.ContainsKey(skinner))
{
MyLogger.Error("why add again?");
return;
}
SkinnerItem skinnerItem = CacheObjectMgr.Inst.Fetch<SkinnerItem>();
skinnerItem.m_Skinner = skinner;
skinnerItem.m_RangeInt = gridRange;
skinnerItem.m_RangeFloat = gridRangeFloat;
m_CacheSkinner[skinner] = skinnerItem;
var minX = gridRange.minPosX;
var minY = gridRange.minPosY;
var maxX = gridRange.maxPosX;
var maxY = gridRange.maxPosY;
//如果对象占用的网格足够大,那就放多个格子上,检测的时候,只要有一个格子在视野内也就显示出来
for (int i = minX; i <= maxX; i++)
{
for (int j = minY; j <= maxY; j++)
{
var key = ViewTools.BlockPosToId(i, j);
#if UNITY_EDITOR
if (key < 0)
{
UnityEngine.Debug.LogError("key < 0");
}
#endif
if (!m_ItemMap.TryGetValue(key, out var item))
{
item = new ViewItem();
item.m_Visible = true;
item.m_SkinnerList = new List<SkinnerItem>();
m_ItemMap.Add(key, item);
}
item.m_SkinnerList.Add(skinnerItem);
}
}
}
```
这个方法也被禁用了,原本的逻辑是:
1. 检查是否重复添加
2. 从对象池获取SkinnerItem并设置数据
3. 将对象添加到所有覆盖的网格中
4. 如果网格不存在则创建新的
### 移除视野对象
```202:230:Scripts/WeiLai/Mgr/ViewMgr.cs
public void RemoveViewItem(IRecycleSkinner skinner)
{
return;
if (!m_CacheSkinner.TryGetValue(skinner, out var skinnerItem))
{
return;
}
m_CacheSkinner.Remove(skinner);
if (skinnerItem != null)
{
var minX = skinnerItem.m_RangeInt.minPosX;
var minY = skinnerItem.m_RangeInt.minPosY;
var maxX = skinnerItem.m_RangeInt.maxPosX;
var maxY = skinnerItem.m_RangeInt.maxPosY;
//如果对象占用的网格足够大,那就放多个格子上,检测的时候,只要有一个格子在视野内也就显示出来
for (int i = minX; i <= maxX; i++)
{
for (int j = minY; j <= maxY; j++)
{
var key = ViewTools.BlockPosToId(i, j);
if (m_ItemMap.TryGetValue(key, out var item))
{
skinnerItem.Recycle();
item.m_SkinnerList.Remove(skinnerItem);
}
}
}
}
else
{
MyLogger.Error("skinner not found");
}
}
```
这个方法也被禁用了,原本的逻辑是:
1. 从缓存中获取SkinnerItem
2. 从所有覆盖的网格中移除该对象
3. 回收SkinnerItem
### 视野检查
```232:242:Scripts/WeiLai/Mgr/ViewMgr.cs
public void CheckSkinnerInView()
{
if (m_ItemMap.Count > 0)
{
//假设所有格子不可见
foreach (var viewItem in m_LastViewItems)
{
viewItem.m_Visible = false;
}
_CheckGridItemInView();
}
}
```
主要的视野检查方法:
1. 先将所有上一帧可见的网格标记为不可见
2. 调用内部方法进行详细检查
### 添加视野内项目
```244:252:Scripts/WeiLai/Mgr/ViewMgr.cs
private void _AddItemInView(ViewItem item)
{
//设置格子成可见
item.m_Visible = true;
if (!m_LastViewItems.Contains(item))
{
//保证,只添加一次
m_LastViewItems.Add(item);
}
}
```
将网格标记为可见并添加到当前视野列表。
### 检查可移动对象
```254:268:Scripts/WeiLai/Mgr/ViewMgr.cs
private void _CheckMoveableInView(float minX, float minY, float maxX, float maxY)
{
for (int i = 0; i < m_Moveables.Count; i++)
{
var skinner = m_Moveables[i];
var pos = skinner.GetPosition();
if (pos.x > minX && pos.x < maxX && pos.y > minY &&
pos.y < maxY)
{
skinner.InCameraView();
}
else
{
skinner.OutCameraView();
}
}
}
```
检查可移动对象是否在视野范围内:
1. 遍历所有可移动对象
2. 获取其位置
3. 判断是否在视野范围内
4. 调用相应的回调方法
### 核心视野检查逻辑
```270:320:Scripts/WeiLai/Mgr/ViewMgr.cs
private void _CheckGridItemInView()
{
var ctrl = CameraMgr.Inst.m_CameraMoveCtrl;
//扩大1个格子单位,以免出现刚进格子的时候,因加载延迟导致的对象闪烁的效果
int minX = Mathf.FloorToInt(ctrl.m_MinPosX / CameraMgr.m_ViewWidth) - 1;
int minY = Mathf.FloorToInt(ctrl.m_MinPosY / CameraMgr.m_ViewHeight) - 1;
int maxX = Mathf.CeilToInt(ctrl.m_MaxPosX / CameraMgr.m_ViewWidth) + 1;
int maxY = Mathf.CeilToInt(ctrl.m_MaxPosY / CameraMgr.m_ViewHeight) + 1;
_CheckMoveableInView(ctrl.m_MinPosX - 2, ctrl.m_MinPosY, ctrl.m_MaxPosX + 2, ctrl.m_MaxPosY);
//检查在视野内的范围内是否有格子数据,一般没数据就没有格子
for (int i = minX; i <= maxX; i++)
{
for (int j = minY; j <= maxY; j++)
{
var key = ViewTools.BlockPosToId(i, j);
if (m_ItemMap.TryGetValue(key, out var item))
{
_AddItemInView(item);
}
}
}
//更新格子内的对象视野
var minPosx = ctrl.m_MinPosX - 2;
var minPosy = ctrl.m_MinPosY - 2;
var maxPosx = ctrl.m_MaxPosX + 2;
var maxPosy = ctrl.m_MaxPosY + 2;
for (int i = m_LastViewItems.Count - 1; i >= 0; i--)
{
var item = m_LastViewItems[i];
if (item.m_Visible)
{
for (int j = item.m_SkinnerList.Count - 1; j >= 0; j--)
{
//格子部分在视野内,那么就会有的对象在视野范围内,有的对象不在视野范围内
var skinner = item.m_SkinnerList[j];
if (ViewTools.CheckCollision(skinner.m_RangeFloat.minPosX,
skinner.m_RangeFloat.minPosY,
skinner.m_RangeFloat.maxPosX,
skinner.m_RangeFloat.maxPosY
, minPosx, minPosy, maxPosx, maxPosy))
{
skinner.m_Skinner.InCameraView();
}
else
{
skinner.m_Skinner.OutCameraView();
}
}
}
else
{
//整个格子都不在视野内了,那么格子内的对象也都不在视野范围内
for (int j = item.m_SkinnerList.Count - 1; j >= 0; j--)
{
var skinner = item.m_SkinnerList[j];
skinner.m_Skinner.OutCameraView();
}
//不再视野内的移除掉,只保留在视野内的
m_LastViewItems.RemoveAt(i);
}
}
}
```
这是最核心的视野检查逻辑:
1. **计算视野范围**:
- 获取相机控制器的视野范围
- 扩大1个格子单位避免闪烁
- 转换为网格坐标
2. **检查可移动对象**:
- 调用可移动对象检查方法
3. **检查网格可见性**:
- 遍历视野范围内的所有网格
- 如果网格存在数据,标记为可见
4. **更新对象视野状态**:
- 遍历上一帧可见的网格
- 对每个网格内的对象进行碰撞检测
- 根据是否在视野内调用相应的回调
- 移除不再可见的网格
### 获取视野内对象
```322:340:Scripts/WeiLai/Mgr/ViewMgr.cs
public List<Monster> GetMonstersInView()
{
if (SceneMgr.Inst.m_CurScene.GetIsCheckView())
{
m_MonsterList.Clear();
for (int i = 0; i < m_LastViewItems.Count; i++)
{
var viewItem = m_LastViewItems[i];
for (int j = 0; j < viewItem.m_SkinnerList.Count; j++)
{
var skinnerItem = viewItem.m_SkinnerList[j];
if (skinnerItem.m_Skinner is Monster monster)
{
m_MonsterList.Add(monster);
}
}
}
}
//else
//{
//如果不做视野管理,就直接返回m_MonsterList,其怪物在AddViewItem的时候就添加进去且一直存在,直到RemoveViewItem才移除
//}
return m_MonsterList;
}
```
获取视野内的怪物列表:
1. 检查当前场景是否启用视野检查
2. 清空缓存列表
3. 遍历所有可见网格
4. 提取其中的Monster对象
5. 返回列表
```342:356:Scripts/WeiLai/Mgr/ViewMgr.cs
/// <summary>
/// 获取视野内的格子
/// </summary>
/// <returns></returns>
public List<SceneGridItem> GetGridItemInView()
{
m_SceneGridList.Clear();
foreach (var viewItem in m_LastViewItems)
{
foreach (var skinnerItem in viewItem.m_SkinnerList)
{
if (skinnerItem.m_Skinner is SceneGridItem sceneGridItemData)
{
m_SceneGridList.Add(sceneGridItemData);
}
}
}
return m_SceneGridList;
}
```
获取视野内的场景网格列表,逻辑与获取怪物类似。
### 清理方法
```358:378:Scripts/WeiLai/Mgr/ViewMgr.cs
protected override void OnUnInit()
{
if(m_CacheSkinner.Count > 0)
{
//MyLogger.Error("why has item?");
}
foreach (var kvViewItem in m_CacheSkinner)
{
kvViewItem.Value.Recycle();
}
m_CacheSkinner.Clear();
m_LastViewItems.Clear();
m_ItemMap.Clear();
m_MonsterList.Clear();
base.OnUnInit();
}
```
清理所有数据:
1. 回收所有SkinnerItem
2. 清空所有集合
3. 调用基类清理方法
## 6. ViewTools 工具类
### 静态变量
```382:384:Scripts/WeiLai/Mgr/ViewMgr.cs
public static class ViewTools
{
public static Vector2 m_GridSize = new(3.8f, 3.0f);
private static Vector2 m_HalfGridSize = m_GridSize / 2.0f;
```
定义网格大小和半网格大小。
### 网格坐标转换
```386:390:Scripts/WeiLai/Mgr/ViewMgr.cs
public static int BlockPosToId(int x, int y)
{
return (x + 1000) | ((y + 1000) << 16);
}
```
将网格坐标转换为唯一ID:
- 加1000是为了处理负数坐标
- 使用位运算将X和Y坐标组合成一个整数
```392:396:Scripts/WeiLai/Mgr/ViewMgr.cs
public static void IdToBlockPos(int id, out int x, out int y)
{
x = id & 0xFFFF - 1000;
y = (id >> 16) - 1000;
}
```
将ID转换回网格坐标:
- 使用位运算分离X和Y坐标
- 减去1000恢复原始坐标
### 获取网格大小
```398:402:Scripts/WeiLai/Mgr/ViewMgr.cs
public static Vector3 GetGridSize()
{
//配置的是每个100个像素等于1个unity单位
return m_GridSize;
}
```
返回网格大小,注释说明100像素=1Unity单位。
### 获取世界坐标范围
```408:418:Scripts/WeiLai/Mgr/ViewMgr.cs
/// <summary>
/// 获取世界坐标的范围
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static GridRangeFloat GetGridRangeFloat(float x, float y)
{
var ret = CacheObjectMgr.Inst.Fetch<GridRangeFloat>();
ret.minPosX = x - m_HalfGridSize.x;
ret.minPosY = y - m_HalfGridSize.y;
ret.maxPosX = x + m_HalfGridSize.x;
ret.maxPosY = y + m_HalfGridSize.y;
return ret;
}
```
根据中心点计算世界坐标范围:
1. 从对象池获取GridRangeFloat
2. 计算以中心点为中心的矩形范围
3. 返回范围对象
### 获取网格范围
```424:434:Scripts/WeiLai/Mgr/ViewMgr.cs
/// <summary>
/// 获取占据格子的范围
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static GridRangeInt GetGridRange(float x, float y)
{
var ret = CacheObjectMgr.Inst.Fetch<GridRangeInt>();
ret.minPosX = Mathf.FloorToInt((x - m_HalfGridSize.x) / CameraMgr.m_ViewWidth);
ret.maxPosX = Mathf.FloorToInt((x + m_HalfGridSize.x) / CameraMgr.m_ViewWidth);
ret.minPosY = Mathf.FloorToInt((y - m_HalfGridSize.y) / CameraMgr.m_ViewHeight);
ret.maxPosY = Mathf.FloorToInt((y + m_HalfGridSize.y) / CameraMgr.m_ViewHeight);
return ret;
}
```
将世界坐标转换为网格坐标范围:
1. 从对象池获取GridRangeInt
2. 将世界坐标范围除以网格大小
3. 使用FloorToInt确保覆盖所有相关网格
### 碰撞检测
```436:460:Scripts/WeiLai/Mgr/ViewMgr.cs
public static bool CheckCollision(
float rect1MinX, float rect1MinY, float rect1MaxX, float rect1MaxY,
float rect2MinX, float rect2MinY, float rect2MaxX, float rect2MaxY)
{
// 检查x轴是否有重叠
if (rect1MaxX < rect2MinX)
{
return false;
}
if (rect2MaxX < rect1MinX)
{
return false;
}
if (rect1MaxY < rect2MinY)
{
return false;
}
if (rect2MaxY < rect1MinY)
{
return false;
}
return true;
}
```
检查两个矩形是否重叠:
1. 检查X轴是否有重叠
2. 检查Y轴是否有重叠
3. 如果任一轴没有重叠,返回false
4. 否则返回true
## 总结
这个视野管理系统的主要特点:
1. **性能优化**:通过网格化视野管理,只渲染视野内的对象
2. **对象池**:使用对象池减少内存分配
3. **双重范围**:同时维护整数网格范围和浮点数世界坐标范围
4. **回调机制**:通过接口回调通知对象进入/退出视野
5. **缓存机制**:缓存视野内的怪物和场景网格列表
目前大部分功能被禁用了(直接return),可能是为了调试或性能测试。
在C#中,protected override void OnInit()
是一个方法的声明,其中的 protected
和 override
是两个关键修饰符,分别表示 访问权限 和 方法重写。以下是详细解释:
1.
protected
(访问修饰符)
作用:表示该方法只能在 当前类 或 派生类(子类) 中访问,外部类无法直接调用。
对比其他修饰符:
public
:完全公开。
private
:仅当前类可访问。
internal
:同一程序集内可访问。
class BaseClass { protected void ProtectedMethod() { } // 仅基类和子类可调用 }
2.override
(方法重写)作用:表示该方法 重写(覆盖)了父类的虚方法(
virtual
)或抽象方法(abstract
)。前提条件:
父类中必须存在标记为
virtual
或abstract
的同名方法。方法签名(名称、参数、返回类型)必须完全一致。
class ParentClass { public virtual void OnInit() { } // 父类虚方法 } class ChildClass : ParentClass { protected override void OnInit() { base.OnInit(); // 可选:调用父类逻辑 // 子类新增逻辑 } }
3. 为什么需要
protected override
?封装性:
protected
确保方法只在继承体系内使用,避免外部误调用。多态性:
override
允许子类自定义父类行为,实现多态(同一方法在不同子类中有不同实现)。4. 常见应用场景
框架/游戏生命周期方法:
如 Unity 的OnInit()
、ASP.NET Core 的OnInitialize()
,通常由框架基类定义虚方法,子类重写以注入自定义逻辑。抽象类/接口实现:
若父类是抽象类(abstract
),子类必须重写抽象方法。5. 注意事项
new
vsoverride
:
若子类方法用new
修饰,会隐藏父类方法(而非重写),多态调用时仍执行父类逻辑。密封方法:
若父类方法标记为sealed override
,子类不能再重写。
protected override void OnInit()
的含义:
这是一个受保护的方法,重写了父类的虚方法或抽象方法,允许子类在继承关系中扩展或修改其行为。典型用途:自定义初始化逻辑、实现多态、响应生命周期事件。
List<T> 简介
List<T> 是 .NET 框架中泛型集合类,位于 System.Collections.Generic 命名空间下。
它提供了动态数组的功能,可以存储任意类型的对象,并且在运行时可以根据需要动态调整大小。
PageInfo 类型
PageInfo 是一个自定义类型,通常用于表示 UI 页面的相关信息(例如页面名称、资源路径、是否已加载等)。
该类型可能包含一些属性或方法来管理页面的状态或数据。
UIMgr
循环遍历 m_Caches 列表
使用一个从后往前(倒序)的 for 循环来遍历 m_Caches 列表。
m_Caches 是一个存储 PageInfo 对象的列表,通常用于缓存已经加载过的 UI 页面信息。
倒序遍历的原因可能是为了在删除元素时不影响前面的索引,避免跳过某些元素。
获取当前迭代的页面对象
var page = m_Caches[i];:从 m_Caches 列表中取出索引为 i 的页面对象,并将其赋值给局部变量 page。
这里的 var 关键字表示隐式类型推断,编译器会根据右侧的表达式自动推断出 page 的类型为 PageInfo。
判断页面类型是否匹配
if (page.m_PageType == ePageType):检查当前页面对象的 m_PageType 属性是否与传入的 ePageType 匹配。
m_PageType 是 PageInfo 类中的一个字段或属性,表示该页面的类型。
ePageType 是一个外部传入的变量,通常是一个枚举值,代表需要查找的目标页面类型。