游戏服务器算法-AOI九宫格python实现

news2025/7/19 11:32:05

 

将空间按照一定的方法进行分割,例如根据AOI范围的大小将整个游戏世界切分为固定大小的格子。当游戏物体位于场景的时候,根据坐标将它放入特定的格子中。

例如玩家1在位置7中,如果游戏内的AOI的范围为1个格子。当我们需要获取这个玩家周围的AOI对象时,只需要遍历7周围9个里面的对象即可。

 

实体的事件:

  • 进入场景enter:进入一个格子,取出周围9格的对象,向它们发送Enter(我)事件,同时向我发送Enter(对象)事件。
  • 离开场景leave:取出周围9格的对象,向它们发送Leave(我)事件。
  • 移动move:
    • 如果没跨格子,直接取9格的对象,向它们发送移动事件。
    • 如果跨过格子,计算{OldGrid-NewGrid},向它们发送Leave(我)事件,向我发送Leave(对象)事件;计算{NewGrid-OldGrid}集合,向它们发送Enter(我)事件,向我发送Enter(对象事件;计算{NewGrid*OldGrid}集合,向他们发送Move(我)事件。
       

空间分割在计算AOI对象时,只需要遍历周围几个空间格子即可,大大提高了计算效率。

但是该方法也有缺点,格子数和空间大小成为正比,空间越大,所需要的内存空间也越大。

如果玩家数里远远小于空间的格子数,使用这种方法来计算AOI可能比全部遍历效率还差。

实现

实体Entity:有三个事件,enter.leave,move事件

class Entity(object):
	# 场景实体
	def __init__(self, eid, x, y):
		self.id = eid  # 角色ID
		self.x = x
		self.y = y

		self.last_x = -1  # 是用来参与判断实体是否有进入或者离开 AOI
		self.last_y = -1

	def __str__(self):
		return "<{0}, {1}-{2}>".format(self.id, self.x, self.y)

	def enter(self, eobj):
		print("{0} enter {1} view".format(eobj, self))

	def leave(self, eobj):
		print("{0} leave {1} view".format(eobj, self))
		pass

	def move(self, eobj):
		print("{0} move in {1} view".format(eobj, self))

 

格子:场景根据大小分成若干格子,每个格子管理一个区域

角色所在的位置,由格子管理

class Grid(object):
	# 格子
	def __init__(self, gid, min_x, max_x, min_y, max_y):
		self.gid = gid  # 格子ID
		self.min_x = min_x  # 格子坐标x范围
		self.max_x = max_x

		self.min_y = min_y  # 格子坐标y范围
		self.max_y = max_y

		self.players = set([])  # 角色ID

比如此图,分成36个格子

场景:控制生成格子数量,管理格子和实体对象

class Scene(object):
	# 场景,由多个格子组成
	def __init__(self, min_x, max_x, cnts_x, min_y, max_y, cnts_y):
		self.min_x = min_x
		self.max_x = max_x
		self.cnts_x = cnts_x  # X轴方向格子的数量

		self.min_y = min_y
		self.max_y = max_y
		self.cnts_y = cnts_y  # y轴方向格子的数量

		self.grids = {}  # 管理格子对象
		
		self.map_entity = {}  # 管理实体对象

完整代码:

python版本:2.7

# -*- coding: utf-8 -*-

class Entity(object):
	# 场景实体
	def __init__(self, eid, x, y):
		self.id = eid  # 角色ID
		self.x = x
		self.y = y

		self.last_x = -1  # 是用来参与判断实体是否有进入或者离开 AOI
		self.last_y = -1

	def __str__(self):
		return "<{0}, {1}-{2}>".format(self.id, self.x, self.y)

	def enter(self, eobj):
		print("{0} enter {1} view".format(eobj, self))

	def leave(self, eobj):
		print("{0} leave {1} view".format(eobj, self))
		pass

	def move(self, eobj):
		print("{0} move in {1} view".format(eobj, self))


class Grid(object):
	# 格子
	def __init__(self, gid, min_x, max_x, min_y, max_y):
		self.gid = gid
		self.min_x = min_x
		self.max_x = max_x

		self.min_y = min_y
		self.max_y = max_y

		self.players = set([])  # 角色ID

	def __str__(self):
		return "<{0}, {1}-{2}: {3}>,".format(self.gid, self.min_x, self.min_y, str(self.players))

	def add(self, eid):
		self.players.add(eid)

	def remove(self, eid):
		self.players.remove(eid)

	def get_player_ids(self):
		return list(self.players)


class Scene(object):
	# 场景,由多个格子组成
	def __init__(self, min_x, max_x, cnts_x, min_y, max_y, cnts_y):
		self.min_x = min_x
		self.max_x = max_x
		self.cnts_x = cnts_x  # X轴方向格子的数量

		self.min_y = min_y
		self.max_y = max_y
		self.cnts_y = cnts_y  # y轴方向格子的数量

		self.grids = {}
		
		self.map_entity = {}  # 实体对象
		self.init_grid()

	def __str__(self):
		res = ""
		for y in xrange(self.cnts_y):
			for x in xrange(self.cnts_x):
				gid = y * self.cnts_x + x
				res += str(self.grids[gid])
			res += "\n"
		return res

	def init_grid(self):
		# 生成格子
		for y in xrange(self.cnts_y):
			for x in xrange(self.cnts_x):
				gid = y * self.cnts_x + x

				min_x = self.min_x + x * self.grid_width()
				max_x = self.min_x + (x + 1) * self.grid_width()
				min_y = self.min_y + y * self.grid_height()
				max_y = self.min_y + (y + 1) * self.grid_height()

				self.grids[gid] = Grid(gid, min_x, max_x, min_y, max_y)

	def grid_width(self):
		# 每个格子在x轴方向的宽度
		return (self.max_x - self.min_x) / self.cnts_x

	def grid_height(self):
		# 得到每个格子在Y轴方向高度
		return (self.max_y - self.min_y) / self.cnts_y

	def get_surround_grids_by_gid(self, gid, include_self=False):
		# 周边的格子对象
		surrounds = []
		grid = self.grids[gid]
		y, x = divmod(grid.gid, self.cnts_x)
		for y_i, x_j in ((-1, 1), (-1, 0), (-1, -1), (0, -1), (0, 1), (1, 1), (1, 0), (1, -1)):
			cal_y = y + y_i
			cal_x = x + x_j
			if cal_x < 0 or cal_x >= self.cnts_x:
				continue
			if cal_y < 0 or cal_y >= self.cnts_y:
				continue

			cal_gid = cal_y * self.cnts_x + cal_x
			surrounds.append(self.grids[cal_gid])
		return surrounds

	def add_eid_grid(self, eid, gid):
		self.grids[gid].add(eid)

	def remove_eid_grid(self, eid, gid):
		self.grids[gid].remove(eid)

	def get_eids_by_gid(self, gid):
		return self.grids[gid].get_player_ids()

	def get_gid_by_pos(self, x, y):
		# 通过,x, y得到对应格子ID
		idx = (x - self.min_x) / self.grid_width()
		idy = (y - self.min_y) / self.grid_height()

		gid = idy * self.cnts_x + idx
		return gid

	def get_surround_eids_by_pos(self, x, y, include_self=False):
		# 根据一个坐标 得到 周边九宫格之内的全部的 玩家ID集合
		gid = self.get_gid_by_pos(x, y)
		grids = self.get_surround_grids_by_gid(gid)
		eids = []
		for grid in grids:
			eids.extend(grid.get_player_ids())
		if include_self:
			eids.extend(self.grids[gid].get_player_ids())
		return eids

	def add_to_grid_by_pos(self, eid, x, y):
		# 通过坐标 将eid 加入到一个格子中
		gid = self.get_gid_by_pos(x, y)
		grid = self.grids[gid]
		grid.add(eid)
		return gid

	def remove_to_grid_by_pos(self, eid, x, y):
		# 通过坐标 将eid remove到一个格子中
		gid = self.get_gid_by_pos(x, y)
		grid = self.grids[gid]
		grid.remove(eid)

	def update_pos(self, update_eid, x, y):
		if update_eid not in self.map_entity:
			# 首次进入
			eobj = Entity(update_eid, x, y)
			self.map_entity[update_eid] = eobj
			grip_id = self.add_to_grid_by_pos(update_eid, x, y)
			eids = self.get_surround_eids_by_pos(x, y)
			for eid in eids:
				ob = self.map_entity[eid]
				ob.enter(eobj)
		else:
			eobj = self.map_entity[update_eid]
			eobj.last_x = eobj.x
			eobj.last_y = eobj.y
			eobj.x = x
			eobj.y = y

			# 格子内移动
			old_gid = self.get_gid_by_pos(eobj.last_x, eobj.last_y)
			new_gid = self.get_gid_by_pos(eobj.x, eobj.y)
			if old_gid == new_gid:
				eids = self.get_surround_eids_by_pos(x, y, True)
				for eid in eids:
					self.map_entity[eid].move(eobj)
			else:
				# 移动格子
				self.remove_eid_grid(update_eid, old_gid)
				self.add_eid_grid(update_eid, new_gid)
				old_surround_gids = self.get_surround_grids_by_gid(old_gid)
				new_surround_gids = self.get_surround_grids_by_gid(new_gid)

				# 新格子事件处理
				for grid in [grid for grid in new_surround_gids if grid not in old_surround_gids]:
					for eid in grid.get_player_ids():
						self.map_entity[eid].enter(eobj)

				# 老格子事件处理
				for grid in [grid for grid in old_surround_gids if grid not in new_surround_gids]:
					for eid in grid.get_player_ids():
						self.map_entity[eid].leave(eobj)

				for grid in [grid for grid in old_surround_gids if grid in new_surround_gids]:
					for eid in grid.get_player_ids():
						self.map_entity[eid].move(eobj)

def test():
	scene = Scene(0, 100, 4, 0, 100, 4)
	scene.update_pos(1, 0, 0)
	scene.update_pos(2, 50, 20)
	scene.update_pos(3, 99, 99)
	print(scene)

	print("<25-1> sorround: {0}".format(scene.get_surround_eids_by_pos(25, 1, True)))
	print("<50-50> sorround: {0}".format(scene.get_surround_eids_by_pos(50, 50, True)))
	scene.update_pos(3, 25, 1)
	scene.update_pos(3, 99, 99)


test()


 

运行结果:一个场景分成16个格子,大小为100 * 100

<0, 0-0: set([1])>,<1, 25-0: set([])>,<2, 50-0: set([2])>,<3, 75-0: set([])>,
<4, 0-25: set([])>,<5, 25-25: set([])>,<6, 50-25: set([])>,<7, 75-25: set([])>,
<8, 0-50: set([])>,<9, 25-50: set([])>,<10, 50-50: set([])>,<11, 75-50: set([])>,
<12, 0-75: set([])>,<13, 25-75: set([])>,<14, 50-75: set([])>,<15, 75-75: set([3])>,

<25-1> sorround: [1, 2]
<50-50> sorround: [3]
<3, 25-1> enter <1, 0-0> view
<3, 25-1> enter <2, 50-20> view
<3, 99-99> leave <1, 0-0> view
<3, 99-99> leave <2, 50-20> view

 深入探索AOI算法:深入探索AOI算法 - 知乎

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第九章 - 多表查询(join,left join 等),合并查询(union union all),子查询

第九章 - 多表查询&#xff08;join&#xff0c;left join 等&#xff09;&#xff0c;合并查询&#xff08;union & union all&#xff09;&#xff0c;子查询交叉链接&#xff08;笛卡尔积&#xff09;内连接查询外连接查询左链接&#xff1a; left join右链接&#xff1…

python3遍历目录的三种方法浅谈

日期&#xff1a;2023年2月22日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xf…

JavaScript中怎么实现链表?

JavaScript中怎么实现链表&#xff1f; 学习数据结构的的链表和树时&#xff0c;会遇到节点&#xff08;node&#xff09;这个词&#xff0c;节点是处理数据结构的链表和树的基础。节点是一种数据元素&#xff0c;包括两个部分&#xff1a;一个是实际需要用到的数据&#xff1b…

MATLAB | 如何用MATLAB绘制这样有气泡感的网络图

今天给大家带来一款用来绘制有气泡感的网络图的工具函数&#xff0c;绘制效果如下&#xff1a; 花里胡哨的&#xff0c;气泡大小代表流入流出数据量综合&#xff0c;不同颜色的气泡代表属于不同类&#xff0c;两个气泡之间有连线代表有数据流动&#xff0c;连线透明度代表流动数…

木鱼cms 审计小结

MuYuCMS基于Thinkphp开发的一套轻量级开源内容管理系统,专注为公司企业、个人站长提供快速建站提供解决方案。‍环境搭建我们利用 phpstudy 来搭建环境&#xff0c;选择 Apache2.4.39 MySQL5.7.26 php5.6.9 &#xff0c;同时利用 PhpStorm 来实现对项目的调试‍漏洞复现分析‍…

经过深思熟虑后的接口测试自动化的总结与思考

序近期看到阿里云性能测试 PTS 接口测试开启免费公测&#xff0c;本着以和大家交流如何实现高效的接口测试为出发点&#xff0c;本文包含了我在接口测试领域的一些方法和心得&#xff0c;希望大家一起讨论和分享&#xff0c;内容包括但不仅限于&#xff1a;服务端接口测试介绍接…

中央一号文件首提“即时零售”,县域掀起消费业态新风潮

经过几年的探索&#xff0c;即时零售已经逐步走向成熟&#xff0c;并开始向三四线城市以及乡镇城市渗透。 过去一年&#xff0c;京东、美团、阿里争先布局即时零售市场&#xff0c;完善即时配送网络、培养用户消费习惯&#xff0c;即时零售订单迎来了骤增。2022年下半年&#…

【字节面试】Fail-fast知识点相关知识点

字节面试&#xff0c;问到的一个小知识点&#xff0c;这里做一下总结&#xff0c;其实小编之前有一篇文章&#xff0c;已经对此有过涉及&#xff0c;不过这里知识专项针对于问题&#xff0c;把这个知识点拎出来说一下。 1.问题 什么是Fail-fast机制&#xff1f; Hashmap是否拥…

斯坦福大学团队提出AI生成文本检测器DetectGPT,通过文本对数概率的曲率进行可解释判断

原文链接&#xff1a;https://www.techbeat.net/article-info?id4583 作者&#xff1a;seven_ 随着以ChatGPT等大型语言模型&#xff08;large language models&#xff0c;LLMs&#xff09;的爆火&#xff0c;学界和工业界目前已经开始重视这些模型的安全性&#xff0c;由于C…

DSP28系列 CCS 开发问题总结及解决办法

文章目录 问题汇总 1. CCS编译器的Project菜单栏工程导入选项丢失&#xff0c;怎么解决&#xff01; 1.1启动CCS后发现导入工程菜单栏丢失&#xff0c;无法导入工程文件。 1.2方法一 工程选项的导入工程文件丢失&#xff0c;如果要重新获得相应的选项&#xff0c;就需要删除当前…

Java必备小知识点2——输入输出与变量

变量变量的实质假如将内存比喻成一座房子&#xff0c;存入内存就相当于住进房子。但是如果一个房间给了应人&#xff0c;他却不住&#xff0c;那么房间实际上是空着的&#xff0c;又属于个人&#xff0c;别人无法住进来&#xff0c;就会造成浪费。那么将内存比喻成酒店&#xf…

Cocoa-autoLayout

什么是autoLayout autoLayout的重新布局是通过 constraints 各个view实现自动布局&#xff0c;autoLayout重新调整布局view是在runtime中 autolayout without constraints 也就是通过stack view实现不引入复杂的constraints的情况下 还能使用autoLayout 什么是stack view …

SPDK NVMe-oF Target

SPDK NVMe-oF TargetNVMe协议制定了本机高速访问PCIe SSD的规范&#xff0c;相对于SATA、SAS、AHCI等协议&#xff0c;NVMe协议在带宽、延迟、IOps等方面占据了极大的优势&#xff0c;但是在价格上目前相对来讲还是比较贵的。不过不可否认的是&#xff0c;配置PCIe SSD的服务器…

软件供应链受威胁下的应对方法——供应链安全管理平台的五大工具能力

背景如今&#xff0c;软件供应链安全问题已经成为一个全球性的难题。根据数据统计&#xff0c;2017年全球遭受网络攻击的公司比例已经达到了93&#xff05;&#xff0c;其中很大一部分是由于软件供应链安全问题导致的。而在中国&#xff0c;据统计&#xff0c;2019年全国共发生…

FlinkSQL行级权限解决方案及源码

FlinkSQL的行级权限解决方案及源码&#xff0c;支持面向用户级别的行级数据访问控制&#xff0c;即特定用户只能访问授权过的行&#xff0c;隐藏未授权的行数据。此方案是实时领域Flink的解决方案&#xff0c;类似离线数仓Hive中Ranger Row-level Filter方案。 源码地址: https…

不可变集合、Stream、异常、日志框架

创建不可变集合 什么是不可变集合&#xff1f;不可变集合&#xff0c;就是不可被修改的集合。集合的数据项在创建的时候提供&#xff0c;并且在整个生命周期中都不可改变。否则报错。为什么要创建不可变集合&#xff1f;如果某个数据不能被修改&#xff0c;把它防御性地拷贝到…

交叉编译 SQLite

交叉编译 SQLite 概述 SQLite 是一个 C 语言库&#xff0c;它实现了一个小型、快速、自包含、高可靠性、功能齐全的 SQL 数据库引擎。SQLite 是世界上使用最多的数据库引擎。SQLite 内置于所有手机和大多数计算机中&#xff0c;并捆绑在人们每天使用的无数其他应用程序中。 S…

java迷宫回溯找最短路径问题(多策略)

1、首先我们先明确迷宫&#xff0c;并创建如上图&#xff0c;1为墙&#xff0c;中间的1为挡板墙&#xff0c;由图我们可以看出这是一个二维数组/*** 创建地图* return*/public static int [][] createMap(){int [][] mapNew new int[8][7];//1表示墙&#xff0c;不能走for (in…

Kaggle系列之识别狗的品种类别(深度残差网络模型ResNet-34)

我们来到这个比赛页面&#xff1a;https://www.kaggle.com/competitions/dog-breed-identification这个数据集的目标是Determine the breed of a dog in an image(确定图像中狗的品种)我们先下载数据集解压之后来看下(当然不手动解压&#xff0c;也可以使用)&#xff0c;这里我…

超全 Vue3新特性总结

Vue3 应用-技术分享与交流 新特性篇 Vue3 组合式 API VS Vue2 选项式 API 选项式 API 面临的问题: 我们在处理业务逻辑时&#xff0c;需要在 data computed method watch 中分别进行代码编写&#xff0c;碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问…