这篇文章主要介绍“Go语言如何实现AOI区域视野管理”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Go语言如何实现AOI区域视野管理”文章能帮助大家解决问题。优化的思路一般是: 第一个是尽量降低向客
这篇文章主要介绍“Go语言如何实现AOI区域视野管理”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Go语言如何实现AOI区域视野管理”文章能帮助大家解决问题。
优化的思路一般是: 第一个是尽量降低向客户端同步对象的数量,第二个是尽量降低单个对象向客户端同步的数据.
"九宫格"是最常见的视野管理算法了.它的优点在于原理和实现都非常简单.
// 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()}
进入区域
离开区域
在区域移动
同步信息
具体实现:
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))}}}
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()}
我们在回过头来继续说 mgr 的方法.
前提:
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}
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}
每帧移动坐标点 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}
每帧同步所有区域变化的物体对象
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点.瞬间会有新增格子,那其中的对象就会进入视野,与此同时,就会有消失的格子,那其中的对象就要消失视野.这个瞬间就会出现一个流量激增点,它可能会导致客户端卡顿等问题.
流量浪费.有客户端不需要的对象被同步过来了.我们知道它是基于格子来管理地图对象的.那么就会无法保证九宫区域一定刚好是视野范围.肯定是大于视野区域这样才保证同步对象正确.(如果是俯视角那种 ,视野就会是一个 梯形范围.)
或者你可以在服务端中,根据客户端梯形视野在作一遍初筛.
关于“Go语言如何实现AOI区域视野管理”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网精选频道,小编每天都会为大家更新不同的知识点。
--结束END--
本文标题: Go语言如何实现AOI区域视野管理
本文链接: https://lsjlt.com/news/350882.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-05-24
2024-05-24
2024-05-24
2024-05-24
2024-05-24
2024-05-24
2024-05-24
2024-05-24
2024-05-24
2024-05-24
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0