游戏开发日志

我来为您逐行详细讲解这个 `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个格子单位避免闪烁

  • 转换为网格坐标

  1. **检查可移动对象**:
  • 调用可移动对象检查方法
  1. **检查网格可见性**:
  • 遍历视野范围内的所有网格

  • 如果网格存在数据,标记为可见

  1. **更新对象视野状态**:
  • 遍历上一帧可见的网格

  • 对每个网格内的对象进行碰撞检测

  • 根据是否在视野内调用相应的回调

  • 移除不再可见的网格

获取视野内对象

```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() 是一个方法的声明,其中的 protectedoverride 是两个关键修饰符,分别表示 访问权限方法重写。以下是详细解释:

1. protected(访问修饰符)

  • 作用 :表示该方法只能在 当前类派生类(子类) 中访问,外部类无法直接调用。

  • 对比其他修饰符

    • public:完全公开。

    • private:仅当前类可访问。

    • internal:同一程序集内可访问。

      cs 复制代码
      class BaseClass
      {
          protected void ProtectedMethod() { } // 仅基类和子类可调用
      }

      2. override(方法重写)

    • 作用 :表示该方法 重写(覆盖)了父类的虚方法(virtual)或抽象方法(abstract

    • 前提条件

      1. 父类中必须存在标记为 virtualabstract 的同名方法。

      2. 方法签名(名称、参数、返回类型)必须完全一致。

        cs 复制代码
        class ParentClass
        {
            public virtual void OnInit() { } // 父类虚方法
        }
        
        class ChildClass : ParentClass
        {
            protected override void OnInit() 
            { 
                base.OnInit(); // 可选:调用父类逻辑
                // 子类新增逻辑
            }
        }

        3. 为什么需要 protected override

      3. 封装性protected 确保方法只在继承体系内使用,避免外部误调用。

      4. 多态性override 允许子类自定义父类行为,实现多态(同一方法在不同子类中有不同实现)。

4. 常见应用场景

  • 框架/游戏生命周期方法

    如 Unity 的 OnInit()ASP.NET Core 的 OnInitialize(),通常由框架基类定义虚方法,子类重写以注入自定义逻辑。

  • 抽象类/接口实现

    若父类是抽象类(abstract),子类必须重写抽象方法。

5. 注意事项

  • new vs override

    若子类方法用 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 是一个外部传入的变量,通常是一个枚举值,代表需要查找的目标页面类型。

相关推荐
快乐肚皮22 分钟前
ZooKeeper学习专栏(六):集群模式部署与解析
分布式·学习·zookeeper
Shining059629 分钟前
Datawhale AI 夏令营-心理健康Agent开发学习-Task1
人工智能·学习·其他·agent
茯苓gao38 分钟前
小孙学变频学习笔记(十)异步电动机的低频带载能力
笔记·嵌入式硬件·学习
序属秋秋秋1 小时前
《C++初阶之STL》【string类:详解 + 实现】
开发语言·c++·笔记·学习·stl
迪迦不喝可乐1 小时前
MySQL 学习二 MVCC
数据库·学习·mysql
Zaly.2 小时前
【Vue进阶学习笔记】组件通信专题精讲
vue.js·笔记·学习
香蕉可乐荷包蛋2 小时前
OpenCV学习(二)-二维、三维识别
人工智能·opencv·学习
张一西10 小时前
docker 容器学习
学习·docker·容器
艾莉丝努力练剑12 小时前
【LeetCode&数据结构】栈的应用——有效的括号问题详解
c语言·开发语言·数据结构·学习·链表