Go 实现 AOI 区域视野管理

news/2024/5/19 4:04:21/文章来源:https://blog.csdn.net/qq_16998379/article/details/129288348

        在游戏中,场景里存在大量的物体.如果我们把所有物体的变化都广播给玩家.那客户端很难承受这么大的压力.因此我们肯定会做优化.把不必要的信息过滤掉.如只关心玩家视野所看到的.减轻客户端的压力,给玩家更流畅的体验.

        优化的思路一般是: 第一个是尽量降低向客户端同步对象的数量,第二个是尽量降低单个对象向客户端同步的数据.

       "九宫格"是最常见的视野管理算法了.它的优点在于原理和实现都非常简单.

        

// AOI 管理器
type AOIManager interface {GetWidth() intGetHeight() intOnEnter(obj scene.GameObject, enterPos *geom.Vector2d) boolOnLeave(obj scene.GameObject) boolOnMove(obj scene.GameObject, movePos *geom.Vector2d) boolOnSync()
}

一 . 定义管理器接口:

1. 进入区域  2. 离开区域  3. 在区域移动 4. 同步信息

具体实现:

type TowerAOIManager struct {minX, maxX, minY, maxY float64 // 单位 mtowerRange             float64 // 格子大小towers                 [][]towerxTowerNum, yTowerNum   int
}

划分格子: 按照实际情况出发,规定格子大小 towerRange. (一般 九个格子的范围需大于屏幕看到的视野范围) 这样才能保证客户端场景物体的生成和消失在玩家屏幕外.不会突然出现.

// 构造结构
func NewTowerAOIManager(minX, maxX, minY, maxY float64, towerRange float64) AOIManager {mgr := &TowerAOIManager{minX: minX, maxX: maxX, minY: minY, maxY: maxY, towerRange: towerRange}mgr.init()return mgr
}func (m *TowerAOIManager) init() {numXSlots := int((m.maxX-m.minX)/m.towerRange) + 1m.xTowerNum = numXSlotsnumYSlots := int((m.maxY-m.minY)/m.towerRange) + 1m.yTowerNum = numYSlotsm.towers = make([][]tower, numXSlots)for i := 0; i < numXSlots; i++ {m.towers[i] = make([]tower, numYSlots)for j := 0; j < numYSlots; j++ {key := NewKey(int64(i), int64(j))m.towers[i][j].init(int64(key))}}}

二 . 定义区域 tower : 

type tower struct {towerId       int64context       *TowerSyncContextmapId2Obj     map[uint32]scene.GameObject // obj容器mapId2Watcher map[uint32]scene.GameObject // 观察集合
}
func (t *tower) init(key int64) {t.towerId = keyt.context = NewTowerSyncContext() // 同步信息t.mapId2Obj = make(map[uint32]scene.GameObject)t.mapId2Watcher = make(map[uint32]scene.GameObject)
}
func (t *tower) AddObj(obj scene.GameObject, fromOtherTower scene.AOITower, bExclude bool) {obj.SetAOITower(t)t.mapId2Obj[obj.GetId()] = objif fromOtherTower == nil {for watcherId, watcher := range t.mapId2Watcher {if bExclude && watcherId == obj.GetId() {continue}watcher.OnEnterAOI(obj)}} else {// obj moved from other tower to this towerfor watcherId, watcher := range fromOtherTower.GetWatchers() {if watcherId == obj.GetId() {continue}if _, ok := t.mapId2Watcher[watcherId]; ok {continue}watcher.OnLeaveAOI(obj)}for watcherId, watcher := range t.mapId2Watcher {if watcherId == obj.GetId() {continue}if _, ok := fromOtherTower.GetWatchers()[watcherId]; ok {continue}watcher.OnEnterAOI(obj)}}
}func (t *tower) RemoveObj(obj scene.GameObject, notifyWatchers bool) {obj.SetAOITower(nil)delete(t.mapId2Obj, obj.GetId())if notifyWatchers {for watcherId, watcher := range t.mapId2Watcher {if watcherId == obj.GetId() {continue}watcher.OnLeaveAOI(obj)}}
}func (t *tower) addWatcher(obj scene.GameObject, bExclude bool) {if bExclude {if _, ok := t.mapId2Watcher[obj.GetId()]; ok {// todo logreturn}}t.mapId2Watcher[obj.GetId()] = obj// now obj can see all objs under this towerfor neighborId, neighbor := range t.mapId2Obj {if neighborId == obj.GetId() {continue}obj.OnEnterAOI(neighbor)}
}func (t *tower) removeWatcher(obj scene.GameObject) {if _, ok := t.mapId2Watcher[obj.GetId()]; !ok {// todo logreturn}delete(t.mapId2Watcher, obj.GetId())for neighborId, neighbor := range t.mapId2Obj {if neighborId == obj.GetId() {continue}obj.OnLeaveAOI(neighbor)}
}func (t *tower) GetWatchers() map[uint32]scene.GameObject {return t.mapId2Watcher
}func (t *tower) GetObjs() map[uint32]scene.GameObject {return t.mapId2Obj
}func (t *tower) GetTowerId() int64 {return t.towerId
}func (t *tower) AddSyncData(mod uint16, cmd uint16, msg protoreflect.ProtoMessage) {t.context.AddSyncData(mod, cmd, msg)
}func (t *tower) Broadcast() {if len(t.context.fights) == 0 {return}// 广播协议....   t.context.ClearContext()
}

三. AOI 的具体方法实现

我们在回过头来继续说 mgr 的方法.

1.  进入实现: 

前提:

GameObject : 一切场景物体的基础接口 

type GameObject interface {}

Vector2d : X,Y 坐标

type Vector2d struct {x, y, w float64
}

具体实现: 

如果是从上一个区域内离开,则先走 离开上一个区域,然后计算当前进入位置坐标对应的九宫区域,

然后把obj 加入到各个区域内

func (m *TowerAOIManager) OnEnter(obj scene.GameObject, enterPos *geom.Vector2d) bool {if obj.GetAOITower() != nil {m.OnLeave(obj) // 离开上一个区域}obj.SetPosition(enterPos) // 设置当前位置// obj 视野范围内的所有区域m.visitWatchedTowers(enterPos, obj.GetViewRange(), func(tower *tower) {tower.addWatcher(obj, false)})t := m.getTowerXY(enterPos)// 当前位置所在的区域t.AddObj(obj, nil, false)return true
}func (m *TowerAOIManager) getTowerXY(xyPos *geom.Vector2d) *tower {xi, yi := m.transXY(xyPos.GetX(), xyPos.GetY())return &m.towers[xi][yi]
}

关键的方法:

        计算obj当前位置中,视野内能被观察到的所有区域.

func (m *TowerAOIManager) visitWatchedTowers(xyPos *geom.Vector2d, aoiDistance float64, f func(*tower)) {ximin, ximax, yimin, yimax := m.getWatchedTowers(xyPos.GetX(), xyPos.GetY(), aoiDistance)for xi := ximin; xi <= ximax; xi++ {for yi := yimin; yi <= yimax; yi++ {tower := &m.towers[xi][yi]f(tower)}}
}func (aoiman *TowerAOIManager) getWatchedTowers(x, y float64, aoiDistance float64) (int, int, int, int) {ximin, yimin := aoiman.transXY(x-aoiDistance, y-aoiDistance)ximax, yimax := aoiman.transXY(x+aoiDistance, y+aoiDistance)return ximin, ximax, yimin, yimax
}func (m *TowerAOIManager) transXY(x, y float64) (int, int) {xi := int((x - m.minX) / m.towerRange)yi := int((y - m.minY) / m.towerRange)return m.normalizeXi(xi), m.normalizeYi(yi)
}func (m *TowerAOIManager) normalizeXi(xi int) int {if xi < 0 {xi = 0} else if xi >= m.xTowerNum {xi = m.xTowerNum - 1}return xi
}func (m *TowerAOIManager) normalizeYi(yi int) int {if yi < 0 {yi = 0} else if yi >= m.yTowerNum {yi = m.yTowerNum - 1}return yi
}

2. 离开区域:

        

func (m *TowerAOIManager) OnLeave(obj scene.GameObject) bool {obj.GetAOITower().RemoveObj(obj, true) // 离开当前区域// 查找视野内所有区域,然后从关注列表中移除m.visitWatchedTowers(obj.GetPosition(), obj.GetViewRange(), func(tower *tower) {tower.removeWatcher(obj)})return true
}

3. 移动

       每帧移动坐标点 movePos

func (m *TowerAOIManager) OnMove(obj scene.GameObject, movePos *geom.Vector2d) bool {oldX, oldY := obj.GetPosition().GetX(), obj.GetPosition().GetY()obj.SetPosition(movePos) //设置当前坐标t0 := obj.GetAOITower()t1 := m.getTowerXY(movePos)// 判断移动是否跨区域了if t0.GetTowerId() != t1.GetTowerId() {t0.RemoveObj(obj, false)t1.AddObj(obj, t0, true)}// 计算前后变化的区域,进行移除和添加关注列表oximin, oximax, oyimin, oyimax := m.getWatchedTowers(oldX, oldY, obj.GetViewRange())ximin, ximax, yimin, yimax := m.getWatchedTowers(movePos.GetX(), movePos.GetY(), obj.GetViewRange())for xi := oximin; xi <= oximax; xi++ {for yi := oyimin; yi <= oyimax; yi++ {if xi >= ximin && xi <= ximax && yi >= yimin && yi <= yimax {continue}tower := &m.towers[xi][yi]tower.removeWatcher(obj)}}for xi := ximin; xi <= ximax; xi++ {for yi := yimin; yi <= yimax; yi++ {if xi >= oximin && xi <= oximax && yi >= oyimin && yi <= oyimax {continue}tower := &m.towers[xi][yi]tower.addWatcher(obj, true)}}return true
}

 4 . 同步

       每帧同步所有区域变化的物体对象

func (m *TowerAOIManager) OnSync() {for i := 0; i < m.xTowerNum; i++ {for j := 0; j < m.yTowerNum; j++ {m.towers[i][j].Broadcast()}}
}

 简单的实现了 AOI 区域变化管理,当然后面还需要优化,我们知道"九宫格" 算法的缺点:

1 . 当玩家跨越格子的时候,比如说从A点到B点.瞬间会有新增格子,那其中的对象就会进入视野,与此同时,就会有消失的格子,那其中的对象就要消失视野.这个瞬间就会出现一个流量激增点,它可能会导致客户端卡顿等问题.

2. 流量浪费.有客户端不需要的对象被同步过来了.我们知道它是基于格子来管理地图对象的.那么就会无法保证九宫区域一定刚好是视野范围.肯定是大于视野区域这样才保证同步对象正确.(如果是俯视角那种 ,视野就会是一个 梯形范围.)

 或者你可以在服务端中,根据客户端梯形视野在作一遍初筛.

        如果你有更好的优化方案,欢迎留言交流!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_76433.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么FPGA在深度学习领域有着得天独厚的优势?

01 FPGA在深度学习领域有哪些优势 FPGA&#xff08;Field-Programmable Gate Array&#xff09;是一种灵活的可编程硬件设备&#xff0c;它在深度学习应用领域中具有许多优势。 首先&#xff0c;FPGA具有非常高的并行性。在深度学习中&#xff0c;许多计算都可以并行化&…

“数字档案室测评”相关参考依据梳理

数字档案室建设无疑比数字档案馆建设应用范围更为广泛&#xff0c;涉及的单位类型和专业领域也更多。这一点从国家档案局的机构设置上也可以看出端倪&#xff1a; 国家档案局两个内设业务指导司中&#xff0c;档案馆(室)业务指导司主要针对档案馆和机关档案室&#xff0c;而经济…

施工机械设备群远程在线监控管理系统

一、项目背景 在加强基础设施建设等一系列政策的牵引下&#xff0c;我国工程机械设备市场连续保持强劲增长势头&#xff0c;伴随国内中高端机械设备公司业务的不断扩展&#xff0c;施工大型机械设备的应用率不断提高&#xff0c;铁路施工特别是架桥机作业过程被认为是一个极易出…

Windows搭建机器学习环境

一、环境介绍Anaconda&#xff1a;Anaconda offers the easiest way to perform Python/R data science and machine learning on a single machine. Start working with thousands of open-source packages and libraries today.Anaconda包括Conda、Python以及一大堆安装好的工…

边缘计算开源项目解读——kubeedge mappers实现

0 背景 本文重点解读kubeedge项目中的mapper模块。该模块位于kubeedge的edgecore的南向边缘侧&#xff0c;主要对接入kubeedge的终端设备&#xff0c;进行协议的适配和转换&#xff0c;使其可以和边缘设备通信&#xff0c;转换后的协议是我们前面描述的mqtt协议&#xff0c;当然…

快速上手配置firewalld

firewalld使用firewall-cmd命令配置策略。 查看当前firewalld当前服务运行状态 firewall-cmd --state firewalld防火墙状态还用使用如下命令查看状态 systemctl status firewalld 查看所有打开运行的端口 firewall-cmd --zonepublic --list-ports 查看区域信息情况 firewall…

04 Android基础--RelativeLayout

04 Android基础--RelativeLayout什么是RelativeLayout&#xff1f;RelativeLayout的常见用法&#xff1a;什么是RelativeLayout&#xff1f; 相对布局&#xff08;RelativeLayout&#xff09;是一种根据父容器和兄弟控件作为参照来确定控件位置的布局方式。 根据父容器定位 在相…

maven高级知识。

目录 一、分模块开发 1、分模块开发设计 2、依赖管理 二、继承和聚合 1、聚合 2、继承 三、属性 1、基本介绍 2、版本管理 四、多环境配置与应用 1、多环境开发 2、跳过测试 五、私服 1、私服安装 2、私服仓库分类 一、分模块开发 1、分模块开发设计 ▶ 示意图 …

南卡Neo骨传导运动耳机发布,重塑骨传导耳机舒适听感新体验!

近日&#xff0c;在骨传导耳机领域中最专业的南卡发布了今年全新系列——NEO&#xff0c;如果说南卡Runner Pro4的音质是偏向于节奏性&#xff0c;那么这款南卡NEO是更加偏向于沉稳性节奏&#xff0c;能够轻松征服轻运动场景&#xff0c;此系列在舒适度以及音质上&#xff0c;更…

【GNN笔记】GNN图论文相关笔记公开汇总清单

图神经网络相关论文笔记清单持续待更....&#x1f600; &#x1f427;&#x1f427;&#x1f427;&#x1f427;~【异构图笔记】1. 异构图笔记部分示例2. 异构图笔记清单罗列【图-注意力机制笔记】1. 图-注意力机制笔记部分示例2. 图-注意力机制笔记清单罗列这是一份有关我自己…

经典蓝牙Sniff Mode

文章目录IntroductionApplicationSniff Sub-ratingReferenceIntroduction Sniff mode为两个已连接的经典蓝牙设备提供了有效的降低功耗的方法。我们知道&#xff0c;当没有数据需要传输的时候&#xff0c;两个已连接的蓝牙设备之间也需要每两个slots完成一次POLL packet - NUL…

新版本GPU加速的tensorflow库的配置方法

本文介绍在Anaconda环境中&#xff0c;配置可以用GPU运行的Python新版tensorflow库的方法。 在上一篇文章Anaconda配置Python新版本tensorflow库&#xff08;CPU、GPU通用&#xff09;的方法&#xff08;https://blog.csdn.net/zhebushibiaoshifu/article/details/129285815&am…

Torch同时训练多个模型

20230302 引言 在进行具体的研究时&#xff0c;利用Torch进行编程&#xff0c;考虑到是不是能够同时训练两个模型呢&#xff1f;&#xff01;而且利用其中一个模型的输出来辅助另外一个模型进行学习。这一点&#xff0c;在我看来应该是很简单的&#xff0c;例如GAN网络同时训…

HOT100--(5)最长回文子串

点击查看题目详情 中心扩散法 思路&#xff1a; 遍历字符串&#xff0c;以每个字符为中心点向两边扩散&#xff0c;如果遇到不一样的就跳出循环。以此类推&#xff0c;最后截取最大回文串返回。 细节 字符个数不一定都是奇数。当个数是偶数的是时候&#xff0c;我们可以“忽…

Canal数据同步配置

文章目录Canal数据同步配置0.canal工作原理1.**检查binlog功能是否有开启**2.如果显示状态为OFF表示该功能未开启&#xff0c;开启binlog功能3.**在mysql里面添加以下的相关用户和权限**4.下载安装Canal服务5.修改配置文件6.进入bin目录下启动7.idea中配置Canal数据同步配置 c…

Java接口专题

基本介绍 接口给出一些没有实现的方法&#xff0c;封装到一起&#xff0c;到某个类使用时再根据具体情况把这些方法写出来。 注意&#xff1a;在jdk7之前&#xff0c;接口里所有的方法都是抽象方法。在jdk8之后接口中可以有静态方法&#xff0c;默认方法 interface 接口名{/…

MySQL 数据库创建不了外键约束

在数据库的表里面创建不了外键约束❓❓❓ 没错&#xff0c;以我名侦探 q 的分析&#xff08;狗屁&#xff01;&#xff01;&#xff01;&#xff09;&#xff0c;真相只有一个❗❗❗ 那就是&#xff1a;你表的存储引擎非 InnoDB&#xff0c;外键约束只有存储引擎是 InnoDB 才…

flutter window安装过程

这里写自定义目录标题#下载相关官网地址&#xff1a;https://flutter.cn/docs/get-started/install/windows 根据官网下载相关包flutter_windows_3.7.5-stable.zip 解压到c盘&#xff0c;在path配置相关解压路径(c:\flutter)。 执行 where flutter dart &#xff0c;发现没有提…

APP测试面试题汇总(基础篇、进阶篇)

一、基础篇1、请介绍一下&#xff0c;APP测试流程&#xff1f;APP测试流程与web测试流程类似&#xff0c;分为如下七个阶段&#xff1a;1.根据需求说明书编写测试计划&#xff1b;2.制定测试方案&#xff0c;主要是测试任务、测试人员和测试时间的分配&#xff1b;3.测试准备&a…

离散事件动态系统

文章目录离散事件动态系统ppt离散事件系统建模离散事件动态系统的基本组成元素离散事件动态系统仿真具体建模petri建模实例离散事件动态系统 ppt ppt 仿真建模步骤 离散事件系统建模 from&#xff1a;离散事件系统建模 离散事件动态系统的基本组成元素 &#xff08;1&am…