游戏服务端 - AOI九宫格算法

news2024/12/30 2:25:48

游戏服务端 - AOI九宫格算法

  下面简述内容,只针对平面上的简易场景。我们将平面上的场景分为一个个格子(Grid),场景管理所有的Grid。如下(假设场景的长宽均为20,每个格子宽高定义为1):

效果图

  其中可分为400个Grid,即如果一个场景每个Grid的宽高均为1(即每个单元unitSizeX为1,unitSizeY为1),那么一个场景需要同时管理400个Grid。

TIPS:每个Grid中可能有多个物体(角色),目前暂定场景中都是玩家对象,即为网络游戏,均为在线玩家角色(AOI对象)。

Grid对象

  作为服务端AOI算法,那么我们应当知道,在什么情况下应该通知附近的Grid哪个AOI对象消失,哪个AOI对象出现、哪个AOI对象状态发生改变(主要针对状态同步,当状态改变时时通知)。以及对于状态改变的玩家来说,在它自己的视野里,哪些Grid的AOI即将在视野内消失,哪些Grid的AOI即将在视野内出现。也就是说,这里有一个一对多和多对一的概念,对于其它Grid中的AOI,我们应该告知它们当前状态发生改变的AOI即将消失、即将出现、状态改变,也就是多对一,多个Grid中的AOI关注一个AOI。而对于当前状态发生改变的AOI,它自己视野内有哪些Grid中的AOI即将消失、出现,也就是一个AOI关注多个Grid中的AOI,一对多。这里指的状态改变包括从一个Grid移动到另外一个Grid、即使没有走出当前Grid,但是从停止到移动等这类改变均统称为状态改变。

  再给出示例之前,需要先知道如果获取一个AOI的视野范围?

bool _GetAOI(int32_t centerGridIndex,
    std::vector<int32_t>& outPut) {
    if (centerGridIndex == -1) {
        return false;
    }
    //视野方向:从右方逆时针旋转,共八个方向
    int32_t col = m_role->GetScene()->GetCol(); //获取列,这里为20
    int deltaIndexArr[8] = {
        1, -col + 1, -col, -col - 1, -1, col - 1, col, col + 1 };

    //获取实体 AOI
    outPut.clear();
    outPut.push_back(centerGridIndex);  //包括自身在内的九宫格
    for (int i = 0; i < 8; i++) {
        outPut.push_back(centerGridIndex + deltaIndexArr[i]);
    }
    return true;
}

  通过上述我们可以获取一个AOI的视野范围(包括自己所在Grid)。

  现假定一个AOI从Grid为21的格子向42的位置移动。那么该AOI旧的视野范围为0、1、2、20、21、22、40、41、42。新的视野范围为21、22、23、41、42、43、61、62、63。那么对于其它Grid中的AOI来说,我们应该通知它们(包括0、1、2、20、40格子内的AOI)当前AOI即将消失。同时通知(包括23、43、61、62、63格子内的AOI)当前AOI即将出现。针对当前AOI来说,它的视野范围也有变化,那就是Grid为0、1、2、20、40格子内的AOI即将消失,Grid为23、43、61、62、63格子内的AOI即将出现。那么对于Grid为21、22、41、42的格子,如果当前AOI没有发生其它的状态改变,那么是不用通知的。因为对于这些Grid中的AOI,当前AOI意味这不改变状态。

效果图

  下面给出通过目的地Grid,求三个队列状态(即将出现、即将消失、状态改变)的算法。

//destGridIndex 目的地Grid,output为输入输出值
bool GetChangedAOIs(int32_t destGridIndex,
    std::vector<std::vector<int32_t>>& outPut) {
    outPut.clear();
    outPut.resize(3);
    std::vector<int32_t>& appearAOIArr    = 
        outPut[(int)AOI_STATUS::AOI_APPEAR];
    std::vector<int32_t>& disappearAOIArr = 
        outPut[(int)AOI_STATUS::AOI_DISAPPEAR];
    std::vector<int32_t>& unChangeAOIArr  = 
        outPut[(int)AOI_STATUS::AOI_UNCHANGE];

    int32_t oldGridIndex = m_centerGridIndex;
    //获取实体 旧AOI
    std::vector<int32_t> oldAOIArr;
    //获取实体 新AOI
    std::vector<int32_t> newAOIArr;
    if (oldGridIndex >= 0) {
        _GetAOI(oldGridIndex, oldAOIArr);
        _GetAOI(destGridIndex, newAOIArr);
    }

    std::sort(oldAOIArr.begin(), oldAOIArr.end());
    std::sort(newAOIArr.begin(), newAOIArr.end());

    //求对用的AOI区域
    int32_t indexOld = 0;
    int32_t indexNew = 0;
    while (indexOld < oldAOIArr.size() && indexNew < newAOIArr.size()) {
        //两个AOI的交集,即不变的AOI区域
        if (oldAOIArr[indexOld] == newAOIArr[indexNew]) {
            unChangeAOIArr.push_back(newAOIArr[indexNew]);
            ++indexOld;
            ++indexNew;
        }
        // oldAOI特有的区域,即需要通知的AOI,告知即将有AOI实体出现
        else if (oldAOIArr[indexOld] < newAOIArr[indexNew]) {
            appearAOIArr.push_back(newAOIArr[indexNew]);
            indexNew++;
        }
        else if (oldAOIArr[indexOld] > newAOIArr[indexNew]) {
            disappearAOIArr.push_back(oldAOIArr[indexOld]);
            indexOld++;
        }

        //将剩下部分加入对应队列中
        while (indexNew < newAOIArr.size()) {
            appearAOIArr.push_back(newAOIArr[indexNew]);
            ++indexNew;
        }
        while (indexOld < oldAOIArr.size()) {
            disappearAOIArr.push_back(oldAOIArr[indexOld]);
            ++indexOld;
        }
    }
    return true;
}

AOI对象

  在当前示例中,一个AOI对象实际上就是一个角色,那么如何通过角色坐标获取到它应当位于哪个Grid中呢?

int32_t index = (int)(pos.X / m_unitSizeX) + m_col * (int)(pos.Y / m_unitSizeY);

  pos表示角色的平面坐标。m_unitSizeX、m_unitSizeY对应上文已经阐述,即为1。m_col为列长(宽,20)。假如当前有一个角色的坐标为(2.8,1.9),那么根据上述公式为:index = (int)(2.8 / 1) + 20 * (1.9 / 1) = 22,即它位于第22个Grid中(根据常识也可验证,这么计算是正确的)。

  通过比较它即将前往的位置和当前位置,即可知是否改变Grid。

bool _IsChangeGridIndex(const struct POSITION& oldPos,
        const struct POSITION& newPos){
    int32_t oldGridIndex = _GetGridIndex(oldPos);
    int32_t newGridIndex = _GetGridIndex(newPos);
    return (oldGridIndex != newGridIndex);
}

完整实例

  实现完整实例如下(仅包含AOI相关代码,仅供参考):

Scene.h

//管理所有加入场景的角色(一个玩家对应一个角色)
class Scene {
public:
	Scene()  {}
	~Scene() {}
    
    bool Init(int32_t width, int32_t length);
    bool UnInit();
	
    //场景逻辑帧更新
    bool UpdateScene();         

    //角色加入场景
    bool AddRole(class Player* player, class Role* role);
    bool DelteRole(class Role* role);

    int32_t GetCol();
    int32_t GetRow();

    // AOI操作, 位置同步AOI
    // isStatusChange = true时,表示状态改变,即使不跨Grid也广播信息
    // isStatusChange = false时,表示状态不改变,这时跨Grid或者达到边缘时广播信息
    bool MoveSyncAOI(class Role* role, 
        const struct POSITION& newPos, bool isStatusChange);

    bool EnterGrid(class Role* role);
    bool ExitGrid(class Role* role);
private:
    //角色退出场景
    bool _OnDeleteRole(Topicype type, void* userData);

    //AOI操作 
    //查看是否改变grid,不变则返回false,改变则返回true
    bool _IsChangeGridIndex(const struct POSITION& oldPos,
        const struct POSITION& newPos);

    //获取坐标所在的Grid位置
    int32_t _GetGridIndex(const struct POSITION& pos);

    //进入一个Grid
    bool _EnterGrid(class Role* role, int32_t gridIndex);

    //从一个Grid退出
    bool _ExitGrid(class Role* role);

    //区域事件广播
    bool _OtherAOIAnnounce();

    //自身视野变化广播
    bool _SelfAOIAnnounce();
private:
    class RoleMgr*     m_roleMgr;       //角色管理器
    
    //AOI模块
    std::vector<Grid*> m_gridArrs;
    int32_t m_unitSizeX;
    int32_t m_unitSizeY;
    int32_t m_col;
    int32_t m_row;

    //对AOI区域队列进行广播,这里表示多个AOI区域对一个Role的观察,多对一
    std::vector<std::pair<
        std::vector<int32_t>, class Role*>> m_enterOtherAOISightArr;
    std::vector<std::pair<
        std::vector<int32_t>, int64_t>> m_exitOtherAOISightArr;
    std::vector<std::pair<
        std::vector<int32_t>, class Role*>> m_statusChangeAOISightArr;

    //更新自身的AOI,即AOI自身角度看哪些AOI消失、AOI出现,一对多
    std::vector<std::pair<
        class Role*, std::vector<int32_t>>> m_enterSelfAOISightArr;
    std::vector<std::pair<
        class Role*, std::vector<int32_t>>> m_exitSelfAOISightArr;
};

Scene.cpp

#include "Scene.h"

bool Scene::Init(int32_t width, int32_t length) {
    m_roleMgr   = (RoleMgr*)MALLOC(sizeof(RoleMgr));
    assert(NULL != m_roleMgr);
    new(m_roleMgr) RoleMgr();
    m_roleMgr->Init(this);
    
    //初始化AOI
    m_unitSizeX = 1;
    m_unitSizeY = 1;
    m_row = length / m_unitSizeY;
    m_col = width / m_unitSizeX;

    for (int i = 0; i < m_row* m_col; i++) {
        Grid* grid = (Grid*)MALLOC(sizeof(Grid));
        assert(NULL != grid);
        new(grid) Grid();
        grid->Init(i);
        m_gridArrs.push_back(grid);
    }

    m_enterOtherAOISightArr.clear();
    m_exitOtherAOISightArr.clear();
    m_statusChangeAOISightArr.clear();

    m_enterSelfAOISightArr.clear();
    m_exitSelfAOISightArr.clear();
    result = true;
Exit:
    return result;
}

bool Scene::UnInit() {
    if (m_roleMgr) {
        m_roleMgr->UnInit();
        Free(m_roleMgr);
        m_roleMgr = nullptr;
    }

    //释放AOI队列
    for (auto& grid : m_gridArrs) {
        grid->UnInit();
        Free(grid);
    }
    m_gridArrs.clear();
    m_enterOtherAOISightArr.clear();
    m_exitOtherAOISightArr.clear();
    m_statusChangeAOISightArr.clear();

    m_enterSelfAOISightArr.clear();
    m_exitSelfAOISightArr.clear();
    return true;
}

bool Scene::UpdateScene() {
    //移动同步
    m_roleMgr->UpdateRoles();

    //处理AOI队列
    _OtherAOIAnnounce();
    _SelfAOIAnnounce();
    return true;
}

bool Scene::AddRole(Player* player, Role* role) {
    //加入场景
    m_roleMgr->AddRole(player, role);

    //进入AOI
    bool nRet = EnterGrid(role);
    return nRet;
}


//事件系统通知
bool Scene::DeleteRole(class Role* role) {
    ExitGrid(role);

    //退出场景,删除role
    m_roleMgr->DeleteRole(role);
    return true;
}

int32_t Scene::GetCol() {
    return m_col;
}

int32_t Scene::GetRow() {
    return m_row;
}

bool Scene::MoveSyncAOI(Role* role, 
    const POSITION& newPos, bool isStatusChange) {
    bool nRet = _IsChangeGridIndex(role->GetRoleStatus()->POS, newPos);
    //位置合法且Grid改变,这时需要通知对该区域感兴趣的AOI
    if (newPos.X >= 0 && newPos.X < m_sceneMap.GetLength() &&
        newPos.Y >= 0 && newPos.Y < m_sceneMap.GetWidth()  && nRet) {
        int32_t newGridIndex = _GetGridIndex(newPos);
        if (newGridIndex >= 0 && newGridIndex < m_gridArrs.size()) {
            _ExitGrid(role);
            _EnterGrid(role, newGridIndex);

            //更新坐标
            //role->SetPos(newPos.X, newPos.Y);

            //获取即将出现、即将消息、不变的AOI区域
            std::vector<std::vector<int32_t>> aoiArrs;
            role->GetChangedAOIs(newGridIndex, aoiArrs);

            //加入即将出现队列
            m_enterOtherAOISightArr.push_back(
                std::make_pair(
                    aoiArrs[(int32_t)AOI_STATUS::AOI_APPEAR], role));

            //加入即将消失队列
            m_exitOtherAOISightArr.push_back(
                std::make_pair(
                    aoiArrs[(int32_t)AOI_STATUS::AOI_DISAPPEAR], 
                    role->GetRoleID()));

            //加入状态改变队列
            m_statusChangeAOISightArr.push_back(
                std::make_pair(
                    aoiArrs[(int32_t)AOI_STATUS::AOI_UNCHANGE], role));

            //更新自身的AOI,即AOI自身角度看哪些AOI消失、AOI出现,一对多
            m_enterSelfAOISightArr.push_back(
                std::make_pair(
                    role, aoiArrs[(int32_t)AOI_STATUS::AOI_APPEAR]));
            m_exitSelfAOISightArr.push_back(
                std::make_pair(
                    role, 
                    aoiArrs[(int32_t)AOI_STATUS::AOI_DISAPPEAR]));
            role->UpdateSelfAOI(newGridIndex);
        }
    }
    //Grid未改变,此时如果状态发生改变则加入状态改变队列中
    else { 
        //更新坐标
        //role->SetPos(newPos.X, newPos.Y);
        if (isStatusChange) {
            //加入状态改变队列
            m_statusChangeAOISightArr.push_back(
                std::make_pair(role->GetAOI(), role));
        }
    }
    return true;
}

bool Scene::EnterGrid(Role* role) {
    //AOI 进入场景
    //获取加入场景角色GridIndex
    int32_t gridIndex = _GetGridIndex(role->GetRoleStatus()->POS);
    bool nRet = _EnterGrid(role, gridIndex);
    if (nRet) {
        //更新实体自身的AOI
        role->UpdateSelfAOI(gridIndex);

        //加入即将出现队列
        const std::vector<int32_t>& gridArrs = role->GetAOI();
        m_enterOtherAOISightArr.push_back(
            std::make_pair(gridArrs, role));

        //更新自身AOI队列
        m_enterSelfAOISightArr.push_back(
            std::make_pair(role, gridArrs));
    }
    return nRet;
}

bool Scene::ExitGrid(Role* role) {
    //AOI 退出场景
    bool nRet = _ExitGrid(role);
    if (nRet) {
        //加入即将消失队列
        const std::vector<int32_t>& gridArrs = role->GetAOI();
        m_exitOtherAOISightArr.push_back(
            std::make_pair(gridArrs, role->GetRoleID()));

        //更新自身AOI
        role->UpdateSelfAOI(-1);
    }
    return nRet;
}

bool Scene::_IsChangeGridIndex(const POSITION& oldPos,
    const POSITION& newPos) {
    int32_t oldGridIndex = _GetGridIndex(oldPos);
    int32_t newGridIndex = _GetGridIndex(newPos);
    return (oldGridIndex != newGridIndex);
}

int32_t Scene::_GetGridIndex(const POSITION& pos) {
    int32_t index = (int)(pos.X / m_unitSizeX) + 
        m_col * (int)(pos.Y / m_unitSizeY);
    return index;
}

bool Scene::_EnterGrid(Role* role, int32_t gridIndex) {
    assert(role != nullptr);
    bool result = false;
    if (gridIndex >= 0 && gridIndex < m_gridArrs.size()) {
        result = m_gridArrs[gridIndex]->AddEntity(role);
    }
    return result;
}

bool Scene::_ExitGrid(Role* role) {
    assert(nullptr != role);
    bool result = false;
    int32_t gridIndex = _GetGridIndex(role->GetRoleStatus()->POS);
    if (gridIndex >= 0 && gridIndex < m_gridArrs.size()) {
        result = m_gridArrs[gridIndex]->RemoveEntity(role);
    }
    return result;
}

bool Scene::_OtherAOIAnnounce() {
    //进入视野
    for (auto& gridList : m_enterOtherAOISightArr) {
        //一个角色即将进入某些AOI的视野
        for (auto& gridIndex : gridList.first) {
            if (gridIndex >= 0 && gridIndex < m_gridArrs.size()) {
                m_gridArrs[gridIndex]->OtherEntityEnterAnnounce(
                    gridList.second);
            }
        }
    }
    m_enterOtherAOISightArr.clear();

    //离开视野
    for (auto& gridList : m_exitOtherAOISightArr) {
        //一个角色即将离开某些AOI的视野
        for (auto& gridIndex : gridList.first) {
            if (gridIndex >= 0 && gridIndex < m_gridArrs.size()) {
                m_gridArrs[gridIndex]->OtherEntityExitAnnounce(
                    gridList.second);
            }
        }
    }
    m_exitOtherAOISightArr.clear();

    //状态改变
    for (auto& gridList : m_statusChangeAOISightArr) {
        for (auto& gridIndex : gridList.first) {
            if (gridIndex >= 0 && gridIndex < m_gridArrs.size()) {
                m_gridArrs[gridIndex]->EntityStatusChangeAnnounce(
                    gridList.second);
            }
        }
    }
    m_statusChangeAOISightArr.clear();
    return true;
}

bool Scene::_SelfAOIAnnounce() {
    //进入视野
    for (auto& gridList : m_enterSelfAOISightArr) {
        //一个角色自身AOI的视野即将出现哪些Grid
        for (auto& gridIndex : gridList.second) {
            if (gridIndex >= 0 && gridIndex < m_gridArrs.size()) {
                m_gridArrs[gridIndex]->NotifyEntityEnter(
                    gridList.first);
            }
        }
    }
    m_enterSelfAOISightArr.clear();

    //离开视野
    for (auto& gridList : m_exitSelfAOISightArr) {
        //一个角色自身AOI的视野即将消失哪些Grid
        for (auto& gridIndex : gridList.second) {
            if (gridIndex >= 0 && gridIndex < m_gridArrs.size()) {
                m_gridArrs[gridIndex]->NotifyEntityExit(
                    gridList.first);
            }
        }
    }
    m_exitSelfAOISightArr.clear();
    return true;
}

Grid.h

#pragma once
#include <unordered_set>
#include <assert.h>
#include "../Role.h"

class Grid {
public:
	Grid()  {}
	~Grid() {}

    bool Init(int32_t gridIndex);
    bool UnInit();

    bool AddEntity(GAME_SERVICE::Role* role);
    bool RemoveEntity(GAME_SERVICE::Role* role);

    //多对一
    bool OtherEntityEnterAnnounce(GAME_SERVICE::Role* role);
    bool OtherEntityExitAnnounce(int64_t roleID);
    bool EntityStatusChangeAnnounce(GAME_SERVICE::Role* role);

    //一对多
    bool NotifyEntityEnter(GAME_SERVICE::Role* role);
    bool NotifyEntityExit(GAME_SERVICE::Role*  role);

private:
    bool _EnterOrStatusChangeAnnounce(GAME_SERVICE::Role* role);

private:
    int32_t m_gridIndex;
    std::unordered_set<GAME_SERVICE::Role*> m_entitySet;        //该grid上存在的实体
};

Grid.cpp

#include "Grid.h"

bool Grid::Init(int32_t gridIndex) {
    assert(gridIndex >= 0);
    m_gridIndex = gridIndex;
    m_entitySet.clear();
    return true;
}

bool Grid::UnInit() {
    m_entitySet.clear();
    return true;
}

bool Grid::AddEntity(GAME_SERVICE::Role* role) {
    assert(role != nullptr);
    auto nRet = m_entitySet.insert(role);
    return nRet.second;
}

bool Grid::RemoveEntity(GAME_SERVICE::Role* role) {
    assert(role != nullptr);
    m_entitySet.erase(role);
    return true;
}

bool Grid::OtherEntityEnterAnnounce(GAME_SERVICE::Role* role) {
    return _EnterOrStatusChangeAnnounce(role);
}

bool Grid::OtherEntityExitAnnounce(int64_t roleID) {
    TCCamp::ExitSceneAnnounce exitAnnounce;
    exitAnnounce.add_roleids(roleID);
    for (auto& other : m_entitySet) {
        other->SendMsg(
            TCCamp::SERVER_CMD::SERVER_ROLE_EXIT_SCENE_ANNOUNCE, 
            &exitAnnounce);
    }
    return true;
}

bool Grid::EntityStatusChangeAnnounce(GAME_SERVICE::Role* role) {
    return _EnterOrStatusChangeAnnounce(role);
}

bool Grid::NotifyEntityEnter(GAME_SERVICE::Role* role) {
    TCCamp::MoveSyncAnnounce    moveAnnounce;
    TCCamp::PBRoleMoveSyncData* pbRoleMoveSyncData = nullptr;
    for (auto& other : m_entitySet) {
        if (other->GetRoleID() != role->GetRoleID()) {
            pbRoleMoveSyncData = moveAnnounce.add_datas();
            pbRoleMoveSyncData->set_roleid(other->GetRoleID());
            other->RoleStatusConvertToPBObj(
                pbRoleMoveSyncData->mutable_status());
        }
    }
    role->SendMsg(
        TCCamp::SERVER_CMD::SERVER_ROLE_MOVE_SYNC_ANNOUNCE,
        &moveAnnounce);
    return true;
}

bool Grid::NotifyEntityExit(GAME_SERVICE::Role* role) {
    TCCamp::ExitSceneAnnounce exitAnnounce;
    for (auto& other : m_entitySet) {
        if (other->GetRoleID() != role->GetRoleID()) {
            exitAnnounce.add_roleids(other->GetRoleID());
        }
    }
    role->SendMsg(
        TCCamp::SERVER_CMD::SERVER_ROLE_EXIT_SCENE_ANNOUNCE,
        &exitAnnounce);
    return true;
}

bool Grid::_EnterOrStatusChangeAnnounce(GAME_SERVICE::Role* role) {
    TCCamp::MoveSyncAnnounce    moveAnnounce;
    TCCamp::PBRoleMoveSyncData* pbRoleMoveSyncData = nullptr;
    pbRoleMoveSyncData = moveAnnounce.add_datas();
    pbRoleMoveSyncData->set_roleid(role->GetRoleID());
    role->RoleStatusConvertToPBObj(
        pbRoleMoveSyncData->mutable_status());
    for (auto& other : m_entitySet) {
        other->SendMsg(
            TCCamp::SERVER_CMD::SERVER_ROLE_MOVE_SYNC_ANNOUNCE, 
            &moveAnnounce);
    }
    return true;
}

AOI.h

#pragma once

//对当前区域感兴趣的格子(Grid),该区域对它们来说的状态
enum class AOI_STATUS {
    AOI_APPEAR,
    AOI_DISAPPEAR,
    AOI_UNCHANGE,
};

class AOI {
public:
	AOI()  {}
	~AOI() {}
	
    bool Init(class Role* role);
    bool UnInit();

    //更新实体AOI(通知场景 更新实体能看到的其它实体信息)
    bool UpdateAOI(int32_t destGridIndex);

    //获取当前AOI区域
    const std::vector<int32_t>& GetAOI();

    // 获取实体,包括即将出现、即将消失、状态不变的AOI
    // TIPS:(不保证gridIndex合法,后续使用需判断合法性)
    bool GetChangedAOIs(int32_t destGridIndex,
        std::vector<std::vector<int32_t>>& outPut);
private:
    // 获取以当前场景的centerGridIndex为中心的AOI区域
    // TIPS:(不保证gridIndex合法,后续使用需判断合法性)
    bool _GetAOI(int32_t centerGridIndex, std::vector<int32_t>& outPut);

private:
    int32_t m_centerGridIndex;
    Role*   m_role;

    std::vector<int32_t> m_aoiGridIndexArr;
};

AOI.cpp

#include "AOI.h"
#include "../Scene.h"
#include "../Role.h"

bool AOI::Init(Role* role) {
    assert(nullptr != role);
    m_centerGridIndex = -1;
    m_role = role;
    m_aoiGridIndexArr.clear();
    return true;
}

bool AOI::UnInit() {
    m_aoiGridIndexArr.clear();
    m_centerGridIndex = -1;
    m_role = nullptr;
    return true;
}

bool AOI::UpdateAOI(int32_t destGridIndex) {
    // 更新aoi
    m_centerGridIndex = destGridIndex;
    m_aoiGridIndexArr.clear();
    _GetAOI(m_centerGridIndex, m_aoiGridIndexArr);
    return true;
}

const std::vector<int32_t>& AOI::GetAOI() {
    return m_aoiGridIndexArr;
}

bool AOI::GetChangedAOIs(int32_t destGridIndex,
    std::vector<std::vector<int32_t>>& outPut) {
    bool result = false;
    outPut.clear();
    outPut.resize(3);
    std::vector<int32_t>& appearAOIArr    = 
        outPut[(int)AOI_STATUS::AOI_APPEAR];
    std::vector<int32_t>& disappearAOIArr = 
        outPut[(int)AOI_STATUS::AOI_DISAPPEAR];
    std::vector<int32_t>& unChangeAOIArr  = 
        outPut[(int)AOI_STATUS::AOI_UNCHANGE];

    int32_t oldGridIndex = m_centerGridIndex;
    //获取实体 旧AOI
    std::vector<int32_t> oldAOIArr;
    //获取实体 新AOI
    std::vector<int32_t> newAOIArr;
    if (oldGridIndex >= 0) {
        _GetAOI(oldGridIndex, oldAOIArr);
        _GetAOI(destGridIndex, newAOIArr);
    }

    std::sort(oldAOIArr.begin(), oldAOIArr.end());
    std::sort(newAOIArr.begin(), newAOIArr.end());

    //求对用的AOI区域
    int32_t indexOld = 0;
    int32_t indexNew = 0;
    while (indexOld < oldAOIArr.size() && indexNew < newAOIArr.size()) {
        //两个AOI的交集,即不变的AOI区域
        if (oldAOIArr[indexOld] == newAOIArr[indexNew]) {
            unChangeAOIArr.push_back(newAOIArr[indexNew]);
            ++indexOld;
            ++indexNew;
        }
        // oldAOI特有的区域,即需要通知的AOI,告知即将有AOI实体出现
        else if (oldAOIArr[indexOld] < newAOIArr[indexNew]) {
            appearAOIArr.push_back(newAOIArr[indexNew]);
            indexNew++;
        }
        else if (oldAOIArr[indexOld] > newAOIArr[indexNew]) {
            disappearAOIArr.push_back(oldAOIArr[indexOld]);
            indexOld++;
        }

        //将剩下部分加入对应队列中
        while (indexNew < newAOIArr.size()) {
            appearAOIArr.push_back(newAOIArr[indexNew]);
            ++indexNew;
        }
        while (indexOld < oldAOIArr.size()) {
            disappearAOIArr.push_back(oldAOIArr[indexOld]);
            ++indexOld;
        }
    }
    return true;
}

bool AOI::_GetAOI(int32_t centerGridIndex,
    std::vector<int32_t>& outPut) {
    if (centerGridIndex == -1) {
        return false;
    }
    //视野方向:从右方逆时针旋转,共八个方向
    int32_t col = m_role->GetScene()->GetCol();
    int deltaIndexArr[8] = {
        1, -col + 1, -col, -col - 1, -1, col - 1, col, col + 1 };

    //获取实体 AOI
    outPut.clear();
    outPut.push_back(centerGridIndex);  //包括自身在内的九宫格
    for (int i = 0; i < 8; i++) {
        outPut.push_back(centerGridIndex + deltaIndexArr[i]);
    }
    return true;
}

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

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

相关文章

电脑怎么查看是固态硬盘还是机械硬盘

前言 前两天有粉丝问我&#xff0c;买电脑的时候有的参数看不懂&#xff0c;比如固态硬盘和机械硬盘区分&#xff0c;他听商家说给他配置的电脑是512G固态硬盘&#xff0c;但是又不知道从哪里看到底是不是固态硬盘&#xff0c;怕以次充好。 今天我就跟大家详细介绍一下硬盘到…

五、path路径模块和url模块

上一篇内容讲到的fs文件系统模块是官方提供的内置模块&#xff0c;本篇path路径模块也是Node.js官方提供的内置模块&#xff0c;也是核心模块&#xff0c;用来处理路径&#xff0c;path模块用来满足用户对路径的处理需求。在上一篇内容就涉及到路径拼接的问题&#xff0c;来一个…

1 数据结构 绪论(时间空间复杂度)

文章链接是我的掘金博客&#xff0c;大家有兴趣可以去我的博客上看 博客地址&#xff1a;数据结构专栏 1 数据结构 绪论&#xff08;时间空间复杂度&#xff09;考纲要求 &#x1f495;1 术语&#xff08;逻辑结构&存储结构&#xff09;1.1 数据结构的形式定义&#xff08;…

【图像分割】遗传算法优化K聚类图像分割【含Matlab源码 1605期】

⛄一、遗传算法优化K聚类简介 文中提出基于优化遗传算法的模糊聚类图像分割算法, 是在上述对遗传算法进行了优化的基础上形成的。不仅根据个体适应度大小和变化快慢自适应调节变异率和交叉率, 提高计算准确性和效率, 另外, 在遗传算法迭代计算中加入基于曲线二阶导数的约束条件…

JavaWeb框架(三):JavaWeb项目实战 基于Servlet 实现系统登录注册功能

MVC实战项目 仓储管理系统需求&#xff1a;实现基本的登录和注册功能MVC实战项目&#xff1a;登录和注册登录功能实现注册功能实现总结Redis章节复习已经过去&#xff0c;新的章节JavaWeb开始了&#xff0c;这个章节中将会回顾JavaWeb实战项目 公司管理系统部分功能 代码会同步…

「地表最强」C++核心编程(四)类和对象--继承

环境&#xff1a; 编译器&#xff1a;CLion2021.3&#xff1b;操作系统&#xff1a;macOS Ventura 13.0.1 文章目录一、继承的基本语法二、继承方式2.1 public继承2.2 protected继承2.3 private继承2.4 继承规则三、继承中的对象模型四、继承中的构造和析构顺序五、继承同名成员…

PyQt5利用Qt designer(QT设计师)使用tab widget和stacked widget实现多页面切换

PyQt5 Qt designer QT设计师 使用tab widget和stacked widget实现多页面切换一、使用Qt designer(QT设计师)进行多页面切换ui设计二、实现tab widget多页面切换三、实现stacked widget多页面切换四、生成代码五、运行效果一、使用Qt designer(QT设计师)进行多页面切换ui设计 本…

Go 实现线性查找算法和二分查找算法

耐心和持久胜过激烈和狂热。 哈喽大家好&#xff0c;我是陈明勇&#xff0c;今天分享的内容使用 Go 实现线性查找算法和二分查找算法。如果本文对你有帮助&#xff0c;不妨点个赞&#xff0c;如果你是 Go 语言初学者&#xff0c;不妨点个关注&#xff0c;一起成长一起进步&…

雪花算法原理

SnowFlake算法生成id的结果是一个64bit大小的整数&#xff0c;它的结构如下图&#xff1a;1bit&#xff0c;不用&#xff0c;因为二进制中最高位是符号位&#xff0c;1表示负数&#xff0c;0表示正数。生成的id一般都是用整数&#xff0c;所以最高位固定为0。41bit时间戳&#…

热门技术中的应用:云计算中的网络-第27讲-云中的网络QoS:邻居疯狂下电影,我该怎么办?

在小区里面,是不是经常有住户不自觉就霸占公共通道,如果你找他理论,他的话就像一个相声《楼道曲》说的一样:“公用公用,你用我用,大家都用,我为什么不能用?”。 除此之外,你租房子的时候,有没有碰到这样的情况:本来合租共享WiFi,一个人狂下小电影,从而你网都上不…

编程15年40岁程序员的我终于在压力下被迫转行了

本人今年40岁多了&#xff0c;中山大学计算机小硕&#xff0c;已经从事it工作15年多&#xff0c;最后一次工作是2017年&#xff0c;创业&#xff0c;互联网教育方向&#xff0c;2020年失败关闭公司。 创业失败后&#xff0c;在家沉淀了几个月&#xff0c;然后决定再次找工作。…

如何在UnrealEngine虚幻引擎中进行版本管理

项目团队中的分工协作必不可少&#xff0c;在UE项目中进行版本控制非常必要。UE支持使用Perforce和SVN进行版本管理&#xff0c;此处选用自己比较熟悉的SVN。 1.使用SVN进行源码管理 通过编辑器偏好设置窗口&#xff08;编辑&#xff08;Edit&#xff09;> 编辑器偏好设置&…

9. Spring注解开发

1. 注解开发定义Bean对象 目的&#xff1a;xml配置Bean对象有些繁琐&#xff0c;使用注解简化Bean对象的定义 1.1 在applicationContext.xml中开启Spring注解包扫描 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.spr…

33 CPP类多态-如何析构派生类

33 CPP类多态-如何析构派生类 派生类的析构函数在执行完后&#xff0c;会自动执行基类的析构函数&#xff0c;这是C编译器强制的规定。 这时候基类的内存模型&#xff1a;AA表示的就是Person类 将基类的析构函数设置为虚函数后。 基类的虚函数表中多了一个函数&#xff0c;但是…

性能测试(二)—— 常用测试工具、JMeter环境搭建、JMeter功能概述

目录 一、常用性能测试工具 1. 主流性能测试工具 1.1 LoadRunner 1.2 JMeter 1.3 LoadRunner 与 JMeter对比 二、JMeter环境搭建 1. 安装JDK 1.1 JDK下载 1.2 JDK配置环境变量 2. 安装JMeter 2.1 下载 2.2 安装 2.3 Jmeter环境配置 2.4 启动验证 三、JMeter功能…

[附源码]计算机毕业设计Python的中点游戏分享网站(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

反应式编程框架设计:如何使得程序调用不阻塞等待

前言&#xff1a; 程序在高并发的情况下&#xff0c;程序容易崩溃。主要的原因是&#xff1a;在高并发的情况下&#xff0c;有大量用户请求需要程序计算处理&#xff0c;而目前的处理方式是&#xff0c;为每个用户请求分配一个线程&#xff0c;当程序内部因为访问数据库等原因…

软件测试:Java VS Python,刚开始应该选择哪门语言进行入门呢?

前言 当你学完软件测试基本理论&#xff0c;掌握业务测试流程&#xff0c;功能测试可以搞定&#xff0c;数据库和linux玩得也很溜时&#xff0c;接下来想进一步进阶&#xff0c;那么学习一门编程语言必不可少。 同时&#xff0c;学习一门编程语言也是你成为自动化测试工程师乃…

如何正确使用JMeter性能测试?紧扣面试实际要求

前段时间专门挑了一段时间在准备面试。经过两次面试后&#xff0c;有一些比较深刻的认识。对于企业要求来说&#xff0c;除了对专业理论知识考究之外&#xff0c;对测试工具这块也是看重的。 一、使用JMeter测试快速入门 1、线程组是什么 进程&#xff1a; 一个正在执行的程序…

图解设计模式: 有趣的工厂模式

工厂模式 Factory Method 在工厂模式中 父子类的关系就像是生产工厂中模具一样, 由父类负责指定实例生成的方式 子类来决定生成具体的类. 具体的处理全部交给子类负责&#xff0c;目的就是为了将生产实例的框架和负责实例生产类解耦 示例程序 从下面这段示例来看看工厂模式到…