游戏中的AOI
在游戏开发中,AOI通常指Area of Interest,即“兴趣区域”或“关注区域”。它是网络游戏(特别是大型多人在线游戏,MMORPG)中一个关键的优化概念,用于管理玩家和游戏对象之间的交互范围。
AOI的核心含义
AOI 是一种通过限制玩家感知范围来优化游戏性能的技术。
- 玩家感知范围:玩家只能看到或与一定范围内的对象互动,超出这个范围的对象不会被同步到客户端。
- 兴趣区域:为每个玩家或游戏对象定义的范围,用于决定哪些其他对象需要同步。
AOI 的主要作用
- 减少网络流量:
- 玩家不需要接收所有其他玩家或 NPC 的状态更新,而只需接收在其 AOI 范围内的对象。
- 减少服务器与客户端之间的数据传输。
- 优化服务器性能:
- 服务器只需处理每个玩家或对象在其兴趣区域内的逻辑,大大降低计算复杂度。
- 提升客户端表现:
- 客户端只需要渲染和处理当前兴趣区域内的对象,减少性能压力。
AOI的实现方式
AOI通常通过以下方式实现:
- 基于圆形或矩形范围的 AOI
- 定义一个固定半径的圆或矩形,范围内的对象是当前兴趣对象。
- 优点:实现简单,适合中小型地图。
- 缺点:范围固定,在密集区域可能导致负载过高。
- 基于网格划分的AOI
- 将地图划分为多个网格,玩家或对象只关注自己所在网格及周围网格的内容。
- 优点:适合大型地图,且可以动态调整网格大小。
- 缺点:复杂度较高,边界处对象的处理需要特别注意。
- 基于兴趣列表的AOI
- 每个玩家维护一个兴趣列表,动态更新列表中关注的对象。
- 优点:可以更细粒度地控制感知范围。
- 缺点:需要额外的管理逻辑。
常用案例:
在Unity或者其他引擎中,可以通过以下方式实现AOI:
- Physics Overlap Checks:使用物理引擎的重叠检测(如 Physics.OverlapSphere)来获取范围内的对象。
- Spatial Partitioning:利用空间分区技术(如四叉树、八叉树)高效查询范围内的对象。
具体案例:
为了更细致的了解AOI,我这边用Unity引擎写一个简单的客户端demo作为例子展示。
这个例子是基于球形范围内的AOI,将不渲染超出范围外的模型,减轻客户端渲染压力。这里采用Unity自带的CullingGroup技术,不了解CullingGroup的同学可以点击文末的链接进行学习,这里不作详细扩展。
简单来说,会以主角(黄色立方体)为圆心,观察在“5米”范围内或者在摄像机视野范围内,是否有NPC(红色立方体),有就渲染,超出范围就不会渲染。
效果如图所示,在NPC超出主角5米范围后,将隐藏模型显示,详细代码放在下面,有兴趣的一同学习。
CullingControl
负责裁剪事件派发,当NPC超出主角“5米”范围后或者超出摄像机视野范围外,会进行通知。
using System.Collections.Generic;
using UnityEngine;
namespace CullingGroup.Scripts
{
public class CullingControl : MonoBehaviour
{
private static CullingControl _instance;
public static CullingControl Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType(typeof(CullingControl), true) as CullingControl;
_instance.Init();
DontDestroyOnLoad(_instance);
}
return _instance;
}
}
private static int MAX_SIZE = 1024;
private static float[] CULLING_DISTANCE = new float[] {1f, 5f};
private static Vector3 NO_RENDER_DIS = Vector3.one * Mathf.Infinity; // 假如移除该角色,对应bound的position设置为超远距离,避免渲染
private BoundingSphere[] bounds = new BoundingSphere[MAX_SIZE];
private UnityEngine.CullingGroup cullingGroup;
private Dictionary<int, IPlayer> index2Players = new Dictionary<int, IPlayer>();
private Dictionary<IPlayer, int> player2Index = new Dictionary<IPlayer, int>();
private void Init()
{
cullingGroup = new UnityEngine.CullingGroup();
cullingGroup.targetCamera = Camera.main;
cullingGroup.SetBoundingDistances(CULLING_DISTANCE);
cullingGroup.SetBoundingSpheres(bounds);
cullingGroup.SetBoundingSphereCount(index2Players.Count);
cullingGroup.onStateChanged = OnStateChanged;
}
private void OnDestroy()
{
cullingGroup.Dispose();
cullingGroup = null;
}
private void OnStateChanged(CullingGroupEvent e)
{
int index = e.index;
if (index2Players.TryGetValue(index, out var player))
{
player.ChangeModel(e);
}
}
public void SetReferencePoint(IPlayer player)
{
if (player != null && player.isMain)
{
cullingGroup.SetDistanceReferencePoint(player.transform);
}
else
{
cullingGroup.SetDistanceReferencePoint(null);
}
}
public void Add(IPlayer player)
{
int newIndex = -1;
for (int i = 0; i < MAX_SIZE; i++)
{
if (!index2Players.ContainsKey(i))
{
newIndex = i;
break;
}
}
if (newIndex == -1)
{
Debug.LogError("[CullingControl] 超出同屏最大显示模型数量");
return;
}
index2Players.Add(newIndex, player);
player2Index.Add(player, newIndex);
bounds[newIndex].radius = player.radius;
bounds[newIndex].position = player.pos;
cullingGroup.SetBoundingSphereCount(index2Players.Count);
}
public void Remove(IPlayer player)
{
if (player2Index.ContainsKey(player))
{
int index = player2Index[player];
player2Index.Remove(player);
index2Players.Remove(index);
bounds[index].position = NO_RENDER_DIS;
}
}
private void LateUpdate()
{
foreach (var kvp in index2Players)
{
bounds[kvp.Key].position = kvp.Value.transform.position;
}
}
}
}
Player
using UnityEngine;
namespace CullingGroup.Scripts
{
public interface IPlayer
{
Transform transform { get; }
Vector3 pos { get; }
float radius { get; }
bool isMain { get; }
void ChangeModel(CullingGroupEvent e);
}
}
using UnityEngine;
namespace CullingGroup.Scripts
{
public class Player : MonoBehaviour, IPlayer
{
public bool isMain => mainPlayer;
public float radius => playerRadius;
public Vector3 pos => transform.position;
public bool mainPlayer;
public float playerRadius = 0.5f;
public GameObject model;
private void OnEnable()
{
CullingControl.Instance.Add(this);
CullingControl.Instance.SetReferencePoint(this);
model = transform.Find("Model").gameObject;
}
private void OnDisable()
{
CullingControl.Instance.Remove(this);
}
public void ChangeModel(CullingGroupEvent e)
{
model.SetActive(e.isVisible);
}
}
}
本文作者:陈侠云
本文链接:https://www.cnblogs.com/chenxiayun/p/18656273
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步