返回顶部
首页 > 资讯 > 后端开发 > GO >Go语言实现AOI区域视野管理流程详解
  • 112
分享到

Go语言实现AOI区域视野管理流程详解

Go AOI区域视野管理Go AOI视野管理 2023-03-06 11:03:42 112人浏览 薄情痞子
摘要

目录一.定义管理器接口二.定义区域tower三.AOI的具体方法实现1.进入实现2.离开区域3.移动4.同步优化的思路一般是: 第一个是尽量降低向客户端同步对象的数量,第二个是尽量降

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

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

// AOI 管理器
type AOIManager interface {
	GetWidth() int
	GetHeight() int
	OnEnter(obj scene.GameObject, enterPos *geom.Vector2d) bool
	OnLeave(obj scene.GameObject) bool
	OnMove(obj scene.GameObject, movePos *geom.Vector2d) bool
	OnSync()
}

一.定义管理器接口

1. 进入区域

2. 离开区域

3. 在区域移动

4. 同步信息

具体实现:

type TowerAOIManager struct {
	minX, maxX, minY, maxY float64 // 单位 m
	towerRange             float64 // 格子大小
	towers                 [][]tower
	xTowerNum, 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) + 1
	m.xTowerNum = numXSlots
	numYSlots := int((m.maxY-m.minY)/m.towerRange) + 1
	m.yTowerNum = numYSlots
	m.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       int64
	context       *TowerSyncContext
	mapid2Obj     map[uint32]scene.GameObject // obj容器
	mapId2Watcher map[uint32]scene.GameObject // 观察集合
}
func (t *tower) init(key int64) {
	t.towerId = key
	t.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()] = obj
	if 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 tower
		for 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 log
			return
		}
	}
	t.mapId2Watcher[obj.GetId()] = obj
	// now obj can see all objs under this tower
	for 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 log
		return
	}
	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. 流量浪费.有客户端不需要的对象被同步过来了.我们知道它是基于格子来管理地图对象的.那么就会无法保证九宫区域一定刚好是视野范围.肯定是大于视野区域这样才保证同步对象正确.(如果是俯视角那种 ,视野就会是一个 梯形范围.)

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

到此这篇关于Go语言实现AOI区域视野管理流程详解的文章就介绍到这了,更多相关Go AOI区域视野管理内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

您可能感兴趣的文档:

--结束END--

本文标题: Go语言实现AOI区域视野管理流程详解

本文链接: https://lsjlt.com/news/198645.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Go语言实现AOI区域视野管理流程详解
    目录一.定义管理器接口二.定义区域tower三.AOI的具体方法实现1.进入实现2.离开区域3.移动4.同步优化的思路一般是: 第一个是尽量降低向客户端同步对象的数量,第二个是尽量降...
    99+
    2023-03-06
    Go AOI区域视野管理 Go AOI视野管理
  • Go语言如何实现AOI区域视野管理
    这篇文章主要介绍“Go语言如何实现AOI区域视野管理”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Go语言如何实现AOI区域视野管理”文章能帮助大家解决问题。优化的思路一般是: 第一个是尽量降低向客...
    99+
    2023-07-05
  • GO语言怎么实现协程池管理
    本篇内容介绍了“GO语言怎么实现协程池管理”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!使用channel实现协程池通过 Channel 实...
    99+
    2023-06-20
  • Go语言实战学习之流程控制详解
    目录1. 前言2. if分支3. for及for-range循环4. switch-case-fallthrough分支5. goto6. break和continue7. 跳出嵌套...
    99+
    2024-04-02
  • c语言实现学生管理系统详解
    目录infor.htest.cinfor.c总结 该学生管理系统的实现是通过分文件的方式来写的,infor.h 文件为头文件,源文件infor.c实现源文件tes...
    99+
    2024-04-02
  • Java实现学生管理系统详解流程
    目录学生管理系统项目介绍JavaSwing功能展示使用说明遇到的问题JavaWeb功能展示使用说明遇到的问题最后学生管理系统 前言:这个是大二做的课设(还是学生管理系统&hellip...
    99+
    2024-04-02
  • Go语言编译器实现原理与编译过程详解
    标题:Go语言编译器实现原理与编译过程详解 在计算机编程领域中,编译器是一种非常重要的工具,它负责将我们编写的高级语言代码转换为目标机器能够执行的机器码。Go语言作为一种快速、高效的编...
    99+
    2024-03-11
    编译器 go语言 实现
  • Go语言开发实现智能物流管理系统的最佳实践
    Go语言开发实现智能物流管理系统的最佳实践摘要:随着物流业的快速发展和互联网技术的不断创新,智能物流管理系统已经成为了提高物流效率和降低物流成本的重要手段。本文将介绍如何使用Go语言开发一套高效、稳定和可扩展的智能物流管理系统,并分享一些最...
    99+
    2023-11-20
    最佳实践 智能物流 开发实现
  • Go语言TCP从原理到代码实现详解
    目录引言TCP介绍特点图解代码实现1. 连接1.1 服务端1.2 客户端2. 通信2.1 服务端2.2 客户端3. 回复3.1 服务端3.2 客户端引言 基于net包的小应用 完整代...
    99+
    2024-04-02
  • GO语言中Chan实现原理的示例详解
    目录GO 中 Chan 实现原理分享chan 是什么GO 中 Chan 的底层数据结构咱们来画个图看看dataqsiz 对应的环形队列是啥样的写 sendq和 读 recvq 等待队...
    99+
    2023-02-24
    GO语言Chan实现原理 GO语言Chan原理 GO语言Chan
  • GO语言中defer实现原理的示例详解
    目录GO 中 defer的实现原理defer 是什么defer 实现原理GO 中 defer 的规则第一点咱们来写个小DEMO第三点也来一个DEMO总结GO 中 defer的实现原理...
    99+
    2023-02-24
    GO语言defer实现原理 GO语言defer原理 GO defer
  • Go语言编程指南:单链表实现详解
    Go语言编程指南:单链表实现详解 在Go语言中,单链表是一种常见的数据结构,用于存储一系列元素并按顺序访问。本文将详细介绍单链表的实现原理,并给出具体的Go语言代码示例。 单链表的定义...
    99+
    2024-04-02
  • Vue element实现权限管理业务流程详解
    目录展开渲染标签编辑权限对话框内树形组件编辑权限展示所有权限 添加 编辑 删除 角色 都与上一篇 用户类似 只是接口不同 我们只关注其他不一样的: 展开渲染标签编辑权限 el-tab...
    99+
    2024-04-02
  • C语言分别实现栈和队列详解流程
    目录什么是栈栈的结构图示栈的实现创建栈的结构体初始化栈入栈出栈获取栈顶元素获取栈中有效元素个数检测栈是否为空栈的销毁什么是队列?队列的实现创建队列结构体初始化队列队尾入队列队头出队列...
    99+
    2024-04-02
  • 详解用C语言实现三子棋游戏流程
    目录三子棋游戏简介一、分析及实现1.棋盘2.落子3.判断输赢二、程序演示三、完整代码1.main.c2.game.c3.game.h总结三子棋游戏简介 这是一个简单的三子棋小游戏,...
    99+
    2024-04-02
  • C语言 小游戏打砖块实现流程详解
    始祖是美国英宝格公司(en:Atari Games,ja:アタリ (ゲーム))于1976年推出的街机游戏“Breakout”(en:Breakout),由该公司在1972年发行的“...
    99+
    2024-04-02
  • go语言编程实现递归函数示例详解
    目录前言函数中的 return递归的问题总结前言 本篇文章主要是记录一下在 GScript 中实现递归调用时所遇到的坑,类似的问题在中文互联网上我几乎没有找到相关的内容,所以还是很有...
    99+
    2024-04-02
  • Springboot详解实现食品仓库管理系统流程
    目录一,项目简介二,环境介绍三,系统展示3.1 系统功能模块设计3.1.1 登录模块3.1.2 客户管理模块3.1.3 供应商管理功能3.1.4 商品管理模块3.1.5 商品进货管理...
    99+
    2024-04-02
  • 详解Go语言中泛型的实现原理与使用
    目录前言问题解决方法类型约束重获类型安全泛型使用场景性能虚拟方法表单态化Go 的实现结论前言 原文:A gentle introduction to generics in G...
    99+
    2022-06-07
    详解go语言 GO 泛型 go语言
  • Spring基于Aop实现事务管理流程详细讲解
    目录aop在spring事务中的运用基于纯注解实现spring事务管理基于xml文件实现Spring事务管理aop在spring事务中的运用 在Spring中,AOP和事务管理是两个...
    99+
    2023-05-20
    Spring Aop事务管理 Spring实现事务管理
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作