> 技术文档 > OpenRA开源红色警戒游戏RPG源码SpatiallyPartitioned.cs解读

OpenRA开源红色警戒游戏RPG源码SpatiallyPartitioned.cs解读

python编程示例系列
python编程示例系列二
python的Web神器Streamlit
如何应聘高薪职位
C#视觉应用开发问题系列
c#串口应用开发问题系列
microPython Python最小内核源码解析
NI-motion运动控制c语言示例代码解析
OpenRA开源红色警戒游戏RPG源码解读
在这里插入图片描述# SpatiallyPartitioned 类功能分析

主要功能

这个程序实现了一个空间分区数据结构(SpatiallyPartitioned),用于高效地存储和查询二维空间中的对象。它将二维空间划分为网格(bins),每个对象根据其边界矩形存储在相应的网格单元中。这种数据结构主要用于:

  1. 空间查询 - 快速找出特定位置或区域内的所有对象
  2. 碰撞检测 - 高效地确定哪些对象可能相交
  3. 大型游戏世界的优化 - 只处理视图或交互范围内的对象

这是游戏开发(如OpenRA这样的RTS游戏)中的常见技术,用于提高渲染和物理计算的性能。

代码注释

using System;using System.Collections;using System.Collections.Generic;using System.Runtime.InteropServices; // 用于CollectionsMarshal高性能操作namespace OpenRA.Primitives{ // 泛型空间分区数据结构,实现IDictionary接口,将类型T的对象映射到矩形区域 public sealed class SpatiallyPartitioned<T> : IDictionary<T, Rectangle> { // 用于添加项目到bin的委托函数 static readonly Action<Dictionary<T, Rectangle>, T, Rectangle> AddItem = (bin, item, bounds) => bin.Add(item, bounds); // 用于从bin移除项目的委托函数 static readonly Action<Dictionary<T, Rectangle>, T, Rectangle> RemoveItem = (bin, item, bounds) => bin.Remove(item); // 网格的行数、列数和每个网格单元的大小 readonly int rows, cols, binSize; // 存储所有网格单元的数组,每个单元是一个字典,映射对象到其边界 readonly Dictionary<T, Rectangle>[] itemBoundsBins; // 存储所有对象及其边界的主字典 readonly Dictionary<T, Rectangle> itemBounds = new(); // 构造函数,根据给定的宽度、高度和网格单元大小初始化空间分区 public SpatiallyPartitioned(int width, int height, int binSize) { this.binSize = binSize; // 计算行数和列数,使用向上取整确保覆盖整个区域 rows = Exts.IntegerDivisionRoundingAwayFromZero(height, binSize); cols = Exts.IntegerDivisionRoundingAwayFromZero(width, binSize); // 创建网格单元数组,每个单元是一个新的字典 itemBoundsBins = Exts.MakeArray(rows * cols, _ => new Dictionary<T, Rectangle>()); } // 验证矩形边界是否有效(不允许宽度或高度为0) static void ValidateBounds(T item, Rectangle bounds) { if (bounds.Width == 0 || bounds.Height == 0) throw new ArgumentException($\"Bounds of {item} are empty.\", nameof(bounds)); } // 添加项目及其边界到数据结构 public void Add(T item, Rectangle bounds) { ValidateBounds(item, bounds); // 先添加到主字典 itemBounds.Add(item, bounds); // 然后添加到所有相关的网格单元 MutateBins(item, bounds, AddItem); } // 索引器实现,允许通过item获取或设置边界 public Rectangle this[T item] { get => itemBounds[item]; set { ValidateBounds(item, value); // 使用CollectionsMarshal获取对字典值的引用,提高性能 // 注释说明这是安全的,因为在引用存活期间不会修改字典 ref var bounds = ref CollectionsMarshal.GetValueRefOrAddDefault(itemBounds, item, out var exists); // 如果项目已存在,先从旧位置移除 if (exists)  MutateBins(item, bounds, RemoveItem); // 然后添加到新位置 MutateBins(item, bounds = value, AddItem); } } // 从数据结构中移除项目 public bool Remove(T item) { // 尝试从主字典移除,并获取其边界 if (!itemBounds.Remove(item, out var bounds)) return false; // 从所有相关网格单元中移除 MutateBins(item, bounds, RemoveItem); return true; } // 获取指定行列的网格单元 Dictionary<T, Rectangle> BinAt(int row, int col) { return itemBoundsBins[row * cols + col]; } // 计算指定行列网格单元的矩形边界 Rectangle BinBounds(int row, int col) { return new Rectangle(col * binSize, row * binSize, binSize, binSize); } // 计算一个矩形覆盖的网格单元范围 void BoundsToBinRowsAndCols(Rectangle bounds, out int minRow, out int maxRow, out int minCol, out int maxCol) { // 处理矩形坐标可能不规范的情况(如右边界小于左边界) var top = Math.Min(bounds.Top, bounds.Bottom); var bottom = Math.Max(bounds.Top, bounds.Bottom); var left = Math.Min(bounds.Left, bounds.Right); var right = Math.Max(bounds.Left, bounds.Right); // 计算矩形覆盖的网格单元范围,确保在有效边界内 minRow = Math.Max(0, top / binSize); minCol = Math.Max(0, left / binSize); maxRow = Math.Min(rows, Exts.IntegerDivisionRoundingAwayFromZero(bottom, binSize)); maxCol = Math.Min(cols, Exts.IntegerDivisionRoundingAwayFromZero(right, binSize)); } // 对矩形覆盖的所有网格单元执行指定操作 void MutateBins(T item, Rectangle bounds, Action<Dictionary<T, Rectangle>, T, Rectangle> action) { // 计算矩形覆盖的网格单元范围 BoundsToBinRowsAndCols(bounds, out var minRow, out var maxRow, out var minCol, out var maxCol); // 对每个相关网格单元执行操作 for (var row = minRow; row < maxRow; row++) for (var col = minCol; col < maxCol; col++)  action(BinAt(row, col), item, bounds); } // 查询指定位置包含的所有对象 public IEnumerable<T> At(int2 location) { // 计算位置所在的网格单元,确保在有效范围内 var col = (location.X / binSize).Clamp(0, cols - 1); var row = (location.Y / binSize).Clamp(0, rows - 1); // 遍历该网格单元中的所有对象 foreach (var kvp in BinAt(row, col)) // 检查对象的边界是否包含该位置 if (kvp.Value.Contains(location))  yield return kvp.Key; } // 查询与指定矩形相交的所有对象 public IEnumerable<T> InBox(Rectangle box) { // 计算矩形覆盖的网格单元范围 BoundsToBinRowsAndCols(box, out var minRow, out var maxRow, out var minCol, out var maxCol); // 如果矩形覆盖多个网格单元,使用HashSet跟踪已返回的对象,避免重复 // 性能优化:如果只覆盖一个单元或无效范围,则不需要跟踪 var items = minRow >= maxRow || minCol >= maxCol ? null : new HashSet<T>(); // 遍历所有相关网格单元 for (var row = minRow; row < maxRow; row++) for (var col = minCol; col < maxCol; col++) {  var binBounds = BinBounds(row, col);  foreach (var kvp in BinAt(row, col))  { var item = kvp.Key; var bounds = kvp.Value; // 检查对象是否与查询矩形相交 // 并确保不重复返回同一对象(通过HashSet或判断对象完全包含在单元内) if (bounds.IntersectsWith(box) && (items == null || binBounds.Contains(bounds) || items.Add(item))) yield return item;  } } } // 清空数据结构 public void Clear() { itemBounds.Clear(); foreach (var bin in itemBoundsBins) bin.Clear(); } // 以下是IDictionary接口的实现,大多数直接委托给主字典 public ICollection<T> Keys => itemBounds.Keys; public ICollection<Rectangle> Values => itemBounds.Values; public int Count => itemBounds.Count; public bool ContainsKey(T item) => itemBounds.ContainsKey(item); public bool TryGetValue(T key, out Rectangle value) => itemBounds.TryGetValue(key, out value); public IEnumerator<KeyValuePair<T, Rectangle>> GetEnumerator() => itemBounds.GetEnumerator(); // 显式实现ICollection<KeyValuePair>接口的方法 bool ICollection<KeyValuePair<T, Rectangle>>.IsReadOnly => false; void ICollection<KeyValuePair<T, Rectangle>>.Add(KeyValuePair<T, Rectangle> item) => ((ICollection<KeyValuePair<T, Rectangle>>)itemBounds).Add(item); bool ICollection<KeyValuePair<T, Rectangle>>.Contains(KeyValuePair<T, Rectangle> item) => ((ICollection<KeyValuePair<T, Rectangle>>)itemBounds).Contains(item); void ICollection<KeyValuePair<T, Rectangle>>.CopyTo(KeyValuePair<T, Rectangle>[] array, int arrayIndex) => ((ICollection<KeyValuePair<T, Rectangle>>)itemBounds).CopyTo(array, arrayIndex); bool ICollection<KeyValuePair<T, Rectangle>>.Remove(KeyValuePair<T, Rectangle> item) => ((ICollection<KeyValuePair<T, Rectangle>>)itemBounds).Remove(item); IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)itemBounds).GetEnumerator(); }}

逻辑流程图

#mermaid-svg-hFAjglFlCBuKqPzi {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-hFAjglFlCBuKqPzi .error-icon{fill:#552222;}#mermaid-svg-hFAjglFlCBuKqPzi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hFAjglFlCBuKqPzi .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-hFAjglFlCBuKqPzi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hFAjglFlCBuKqPzi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hFAjglFlCBuKqPzi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hFAjglFlCBuKqPzi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hFAjglFlCBuKqPzi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hFAjglFlCBuKqPzi .marker.cross{stroke:#333333;}#mermaid-svg-hFAjglFlCBuKqPzi svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hFAjglFlCBuKqPzi .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hFAjglFlCBuKqPzi .cluster-label text{fill:#333;}#mermaid-svg-hFAjglFlCBuKqPzi .cluster-label span{color:#333;}#mermaid-svg-hFAjglFlCBuKqPzi .label text,#mermaid-svg-hFAjglFlCBuKqPzi span{fill:#333;color:#333;}#mermaid-svg-hFAjglFlCBuKqPzi .node rect,#mermaid-svg-hFAjglFlCBuKqPzi .node circle,#mermaid-svg-hFAjglFlCBuKqPzi .node ellipse,#mermaid-svg-hFAjglFlCBuKqPzi .node polygon,#mermaid-svg-hFAjglFlCBuKqPzi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hFAjglFlCBuKqPzi .node .label{text-align:center;}#mermaid-svg-hFAjglFlCBuKqPzi .node.clickable{cursor:pointer;}#mermaid-svg-hFAjglFlCBuKqPzi .arrowheadPath{fill:#333333;}#mermaid-svg-hFAjglFlCBuKqPzi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hFAjglFlCBuKqPzi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hFAjglFlCBuKqPzi .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-hFAjglFlCBuKqPzi .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-hFAjglFlCBuKqPzi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hFAjglFlCBuKqPzi .cluster text{fill:#333;}#mermaid-svg-hFAjglFlCBuKqPzi .cluster span{color:#333;}#mermaid-svg-hFAjglFlCBuKqPzi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hFAjglFlCBuKqPzi :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;} 无效 有效 无效 有效 初始化 SpatiallyPartitioned 创建网格结构 将空间划分为rows cols个网格 添加对象 验证边界有效性 抛出异常 添加到主字典 计算对象覆盖的网格单元 将对象添加到每个相关网格单元 移除对象 对象存在? 返回false 从主字典移除 从所有相关网格单元移除 返回true 更新对象边界 验证新边界有效性 抛出异常 对象已存在? 从旧位置移除 添加新对象 添加到新位置的网格单元 查询位置At 确定位置所在网格单元 遍历该网格单元中的对象 对象包含查询位置? 返回对象 查询区域InBox 计算区域覆盖的网格单元 覆盖多个单元? 创建HashSet跟踪已返回对象 不需要跟踪 遍历相关网格单元和对象 对象与查询区域相交? 跳过 已返回过该对象? 返回对象

特殊语法与技巧分析

  1. 泛型与接口实现

    • 类定义为SpatiallyPartitioned,允许存储任何类型的对象
    • 实现IDictionary接口,提供标准字典操作
  2. 委托与Lambda表达式

    static readonly Action<Dictionary<T, Rectangle>, T, Rectangle> AddItem = (bin, item, bounds) => bin.Add(item, bounds);
    • 使用委托存储常用操作,简化代码并提高可维护性
    • Lambda表达式提供简洁的函数定义
  3. 引用传递与性能优化

    ref var bounds = ref CollectionsMarshal.GetValueRefOrAddDefault(itemBounds, item, out var exists);
    • 使用CollectionsMarshal直接获取字典值的引用,避免复制
    • 通过ref关键字传递引用,提高性能
  4. 空间划分算法

    • 将二维空间划分为网格,每个对象可能存在于多个网格单元中
    • 使用整数除法和边界检查确保网格覆盖正确
  5. 迭代器与延迟计算

    public IEnumerable<T> At(int2 location){ // ... foreach (var kvp in BinAt(row, col)) if (kvp.Value.Contains(location)) yield return kvp.Key;}
    • 使用yield return创建迭代器,实现延迟计算
    • 只在需要时才计算结果,提高效率
  6. 条件三元运算符

    var items = minRow >= maxRow || minCol >= maxCol ? null : new HashSet<T>();
    • 根据条件决定是否创建HashSet,避免不必要的对象创建
  7. 显式接口实现

    bool ICollection<KeyValuePair<T, Rectangle>>.IsReadOnly => false;
    • 使用显式接口实现隐藏不常用的接口方法
    • 保持公共API简洁清晰
  8. 性能优化技巧

    • 避免重复计算(如预先计算网格边界)
    • 仅在必要时使用HashSet跟踪已处理对象
    • 使用引用而非值复制
    • 委托常用操作减少代码重复
  9. 边界条件处理

    var top = Math.Min(bounds.Top, bounds.Bottom);var bottom = Math.Max(bounds.Top, bounds.Bottom);
    • 处理矩形坐标可能不规范的情况,增强健壮性
  10. 表达式体成员

    public int Count => itemBounds.Count;
    • 使用C#6.0引入的表达式体成员简化属性和方法定义

总体而言,这是一个设计良好、性能优化的空间分区数据结构实现,适用于游戏开发中的高效空间查询和碰撞检测。

Python展示如何实现二维空间物体的各种物理约束
c#视觉应用开发中如何在C#中进行图像色彩平衡?
python如何创建内存视图
c++ qt如何进行对象序列化与反序列化
python如何快速创建命令行接口(CLI)
microPython的源码解析之 objmodule.c
DALL-E如何使用
车载系统软件工程师如何处理车载系统的兼容性测试和验证
智能农业设备软件工程师如何处理设备的远程诊断和修复
c#视觉应用开发中如何使用AForge.NET进行图像处理?
python生成伪随机数序列库randomstate
C#进行串口应用开发如何设置串口的超时时间
车载系统软件工程师如何处理车载系统的电磁兼容性(EMC)
智能农业设备软件工程师如何实现农业设备的能量回收系统
矩阵运算思维如何理解
python的shutil库如何使用
python如何使用halcon识别二维码
Python开源的字体解析库FreeType
量化交易系统中+如何处理系统故障和异常情况?
microPython的源码解析之 gc.c
量化交易系统中+如何处理数据的延迟和丢包?
无服务器计算平台
python使用原始套接字的ICMP ping实现库AsyncPing
python 如何将传统关系数据库的数据导入 Hadoop
python的unittest库如何使用功能
C#进行串口应用开发如何设置串口的数据位、停止位和校验位
量化交易系统中+如何处理策略的回撤和停止?
C#进行串口应用开发如何处理串口通信因为线路问题导致的数据错乱
microPython的源码解析之 builtinimport.c
车载系统软件工程师如何实现车载系统的车辆健康监测
车载系统软件工程师如何处理车载系统的用户体验优化
量化交易系统中如何处理机器学习模型的训练和部署?
python如何将图可视化
python的生成器和迭代器
量化交易系统中+如何进行策略的回测(backtesting)?
智能农业设备软件工程师如何实现农业设备的电池管理
智能农业设备软件工程师如何处理设备的网络连接和通信
c#委托和事件
python的click库如何使用
智能农业设备软件工程师如何实现农业设备的故障预测和预防
c#视觉应用开发中如何在C#中进行图像序列处理?
python的PySFML 库如何安装使用以及功能和用途
python如何简单处理zip存档文件
D-Link Australia利用Python控制固件更新
指数和对数
量化交易系统中+如何确保数据的安全性和隐私保护?
Python的双下方法(dunder methods)
NI-Motion如何控制一个运动控制器执行一个螺旋形移动路径 的C语言代码实力
Python端到端的测试的生态系统库pyATS
智能农业设备软件工程师如何集成多种农业传感器(如土壤湿度温度pH值传感器)
Python 用于协作机器人
车载系统软件工程师如何处理车载系统的系统性能监控
python数学量子计算库toqito
量化交易系统中+如何处理历史数据的准确性和一致性?
Python pygame库开发的射击小游戏(二娃大战外星人)完整示例.
python给游戏增加音效
C#进行串口应用开发如何避免串口通信因线程死锁问题导致的程序卡死
c#视觉应用开发中如何在C#中进行图像光照校正?
如何应聘数据标注员 ,年薪10万以上
C#进行串口应用开发如何检测电脑是否有串口
python的WPS-SDK-Python库提供哪些功能
Union Investment如何利用Python和机器学习(ML)技术来改进其投资流程
microPython的源码解析之 asmxtensa.c
Python的opencv库使用行人检测
python的Panda3D库如何安装使用以及用途
microPython的源码解析之 nativeglue.c
Python如何测网速
linux下模拟鼠标键盘的工具xdotool
python的overrides库
python web应用开发神器 入门二十
python的ast库的使用
Python如何处理开放神经网络交换格式
qt如何操作Excel文件
用莎士比亚风格写程序
几种常用的开源协议
量化交易系统中+如何进行策略的优化和参数调优?
c#视觉应用开发中如何在C#中进行图像立体匹配?
python 如何写入文本文件?
microPython的源码解析之 scope.c
智能农业设备软件工程师如何实现农业设备的高级数据分析和可视化
车载系统软件工程师如何实现车载系统的语音命令和自然语言处理
python蓝牙设备通信的功能的库python-lightblue
c#视觉应用开发中如何在C#中进行目标检测?
智能农业设备软件工程师如何集成和管理农业设备的系统日志
microPython的源码解析之 modio.c
智能农业设备软件工程师如何实现农业设备的用户权限管理
开源的AI算法可以进行物体识别,动物识别
智能农业设备软件工程师如何实现农业设备的自动化控制系统
Python的opencv库进行图像分割
python的NLTK库如何使用
车载系统软件工程师如何处理车载系统的冷启动和热启动优化
NI-Motion运动控制混合直线移动和圆弧移动c语言示例
python web应用开发神器 入门十九
车载系统软件工程师如何处理车载系统的多语言支持
车载系统软件工程师如何处理车载系统的传感器校准和同步
python如何使用Windows API 和 COM 接口
microPython的源码解析之 repl.c
车载系统软件工程师如何处理车载系统的电磁干扰(EMI)
R语言和python语言的区别在什么地方,各自的应用场景是什么
智能农业设备软件工程师如何处理设备的传输延迟和数据一致性