C++ 技术中心

   :: 首页 :: 联系 ::  :: 管理
  160 Posts :: 0 Stories :: 87 Comments :: 0 Trackbacks

公告

郑重声明:本BLOG所发表的原创文章,作者保留一切权利。必须经过作者本人同意后方可转载,并注名作者(天空)和出处(CppBlog.com)。作者Email:coder@luckcoder.com

留言簿(27)

搜索

  •  

最新随笔

最新评论

评论排行榜

AOI主要有九宫格、灯塔和十字链表的算法实现。本文阐述十字链表的实现和尝试。

1. 基本原理

根据二维地图,将其分成x轴和y轴两个链表。如果是三维地图,则还需要维护多一个z轴的链表。将对象的坐标值按照大小相应的排列在相应的坐标轴上面。

2. 基本接口

对对象的操作主要有以下三个接口:

  • add:对象进入地图;
  • leave:对象离开地图;
  • move:对象在地图内移动。

2. 算法实现

既然是链表,很自然地想到用线性表来实现。因为存在向前和向后找的情况,所以使用双链表实现。其实实现也是非常简单,就是两个双链表(这里以二维地图举例)。那么我们的节点需要四个指针,分布为x轴的前后指针,y轴的前后指针。

// 双链表(对象)
class DoubleNode
{
public:
    DoubleNode(string key, int x, int y)
    {
        this->key = key;
        this->x = x;
        this->y = y;
        xPrev = xNext = NULL;
    };
    
    DoubleNode * xPrev;
    DoubleNode * xNext;
    
    DoubleNode * yPrev;
    DoubleNode * yNext;
    
    string key;  // 只是一个关键字
    int x; // 位置(x坐标)
    int y; // 位置(y坐标)

private:

};

下面是地图场景信息和接口。这里的实现比较粗略,是带头尾的的双链表,暂时不考虑空间占用的问题。类Scene有分别有一个头尾指针,初始化的时候会为其赋值,主要用DoubleNode类的指针来存储x轴和y轴的头尾。初始化的时候,将_head的next指针指向尾_tail;将_tail的prev指针指向_head
// 地图/场景
class Scene
{
public:
    Scene()
    {
        this->_head = new DoubleNode("[head]", 0, 0); // 带头尾的双链表(可优化去掉头尾)
        this->_tail = new DoubleNode("[tail]", 0, 0);
        _head->xNext = _tail;
        _head->yNext = _tail;
        _tail->xPrev = _head;
        _tail->yPrev = _head;
    };

    // 对象加入(新增)
    DoubleNode * Add(string name, int x, int y);

    // 对象离开(删除)
    void Leave(DoubleNode * node);

    // 对象移动
    void Move(DoubleNode * node, int x, int y);

    // 获取范围内的AOI (参数为查找范围)
    void PrintAOI(DoubleNode * node, int xAreaLen, int yAreaLen);

private:
    DoubleNode * _head;
    DoubleNode * _tail;
};

2.1. add(进入地图)

DoubleNode对象插入到十字链表中。x轴和y轴分别处理,处理方法基本一致。其实就是双链表的数据插入操作,需要从头开始遍历线性表,对比相应轴上的值的大小,插入到合适的位置。

void _add(DoubleNode * node)
{
    // x轴处理
    DoubleNode * cur = _head->xNext;
    while(cur != NULL)
    {
        if((cur->x > node->x) || cur==_tail) // 插入数据
        {
            node->xNext = cur;
            node->xPrev = cur->xPrev;
            cur->xPrev->xNext = node;
            cur->xPrev = node;
            break;
        }
        cur = cur->xNext;
    }

    // y轴处理
    cur = _head->yNext;
    while(cur != NULL)
    {
        if((cur->y > node->y) || cur==_tail) // 插入数据
        {
            node->yNext = cur;
            node->yPrev = cur->yPrev;
            cur->yPrev->yNext = node;
            cur->yPrev = node;
            break;
        }
        cur = cur->yNext;
    }
}

假设可视范围为x轴2以内,y轴2以内,则运行:

  1. 分别插入以下数据a(1,5)、f(6,6)、c(3,1)、b(2,2)、e(5,3),然后插入d(3,3),按照x轴和y轴打印其双链表结果;
  2. 插入d(3,3)数据,求其可视AOI范围(如图,除了f(6,6),其它对象都在d的可视范围内)。

控制台结果(前8行):

步骤1结果图示:

步骤2结果图示:

2.2. leave(离开地图)和move(移动)

其实都是双链表的基本操作,断掉其相应的指针就好了。按理,是需要

move和leave操作如图,move是将d(3,3)移动到(4,4),然后再打印其AOI范围。

控制台结果:

移动后AOI范围图示:


3. 完整代码实例
// ConsoleApplication2.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"


#include "stdafx.h"
#include "stdio.h"
#include <iostream>
#include <string>

using namespace std;

// 双链表(对象)
class DoubleNode
{
public:
    DoubleNode(string key, int x, int y)
    {
        this->key = key;
        this->x = x;
        this->y = y;
        xPrev = xNext = NULL;
    };

    DoubleNode * xPrev;
    DoubleNode * xNext;

    DoubleNode * yPrev;
    DoubleNode * yNext;

    string key;
    int x; // 位置(x坐标)
    int y; // 位置(y坐标)

private:

};




// 地图/场景
class Scene
{
public:

    Scene()
    {
        this->_head = new DoubleNode("[head]", 0, 0); // 带头尾的双链表(可优化去掉头尾)
        this->_tail = new DoubleNode("[tail]", 0, 0);
        _head->xNext = _tail;
        _head->yNext = _tail;
        _tail->xPrev = _head;
        _tail->yPrev = _head;
    };

    // 对象加入(新增)
    DoubleNode * Add(string name, int x, int y)
    {

        DoubleNode * node = new DoubleNode(name, x, y);
        _add(node);
        return node;
    };

    // 对象离开(删除)
    void Leave(DoubleNode * node)
    {
        node->xPrev->xNext = node->xNext;
        node->xNext->xPrev = node->xPrev;
        node->yPrev->yNext = node->yNext;
        node->yNext->yPrev = node->yPrev;

        node->xPrev = NULL;
        node->xNext = NULL;
        node->yPrev = NULL;
        node->yNext = NULL;
    };

    // 对象移动
    void Move(DoubleNode * node, int x, int y)
    {
        Leave(node);
        node->x = x;
        node->y = y;
        _add(node);
    };

    // 获取范围内的AOI (参数为查找范围)
    void PrintAOI(DoubleNode * node, int xAreaLen, int yAreaLen)
    {
        cout << "Cur is: " << node->key << "(" << node->x << "," << node->y << ")" << endl;
        cout << "Print AOI:" << endl;

        // 往后找
        DoubleNode * cur = node->xNext;
        while (cur != _tail)
        {
            if ((cur->x - node->x) > xAreaLen)
            {
                break;
            }
            else
            {
                int inteval = 0;
                inteval = node->y - cur->y;
                if (inteval >= -yAreaLen && inteval <= yAreaLen)
                {
                    cout << "\t" << cur->key << "(" << cur->x << "," << cur->y << ")" << endl;
                }
            }
            cur = cur->xNext;
        }

        // 往前找
        cur = node->xPrev;
        while (cur != _head)
        {
            if ((node->x - cur->x) > xAreaLen)
            {
                break;
            }
            else
            {
                int inteval = 0;
                inteval = node->y - cur->y;
                if (inteval >= -yAreaLen && inteval <= yAreaLen)
                {
                    cout << "\t" << cur->key << "(" << cur->x << "," << cur->y << ")" << endl;
                }
            }
            cur = cur->xPrev;
        }
    };

    // 调试代码
    void PrintLink()  // 打印链表(从头开始)
    {
        // 打印x轴链表
        DoubleNode * cur = _head->xNext;
        while (cur != _tail)
        {
            cout << (cur->key) << "(" << (cur->x) << "," << (cur->y) << ") -> ";
            cur = cur->xNext;
        }
        cout << "end" << endl;

        // 打印y轴链表
        cur = _head->yNext;
        while (cur != _tail)
        {
            cout << (cur->key) << "(" << (cur->x) << "," << (cur->y) << ") -> ";
            cur = cur->yNext;
        }
        cout << "end" << endl;
    };

private:
    DoubleNode * _head;
    DoubleNode * _tail;

    void _add(DoubleNode * node)
    {
        // x轴处理
        DoubleNode * cur = _head->xNext;
        while (cur != NULL)
        {
            if ((cur->x > node->x) || cur == _tail) // 插入数据
            {
                node->xNext = cur;
                node->xPrev = cur->xPrev;
                cur->xPrev->xNext = node;
                cur->xPrev = node;
                break;
            }
            cur = cur->xNext;
        }

        // y轴处理
        cur = _head->yNext;
        while (cur != NULL)
        {
            if ((cur->y > node->y) || cur == _tail) // 插入数据
            {
                node->yNext = cur;
                node->yPrev = cur->yPrev;
                cur->yPrev->yNext = node;
                cur->yPrev = node;
                break;
            }
            cur = cur->yNext;
        }
    }
};

// --------------------------------------------

int _tmain(int argc, _TCHAR* argv[])
{
    Scene scene = Scene();
    // 增加
    scene.Add("a", 1, 5);
    scene.Add("f", 6, 6);
    scene.Add("c", 3, 1);
    scene.Add("b", 2, 2);
    scene.Add("e", 5, 3);
    DoubleNode * node = scene.Add("d", 3, 3);

    scene.PrintLink();
    scene.PrintAOI(node, 2, 2);

    // 移动
    cout << endl << "[MOVE]" << endl;
    scene.Move(node, 4, 4);
    scene.PrintLink();

    scene.PrintAOI(node, 2, 2);

    // 删除
    cout << endl << "[LEAVE]" << endl;
    scene.Leave(node);
    scene.PrintLink();

}

算法的大概思想如下.每个场景维护两个链表,分别为X轴和Y轴的坐标按序排列好的链表,也就是比如在X轴链表上,越在前的对象,X坐标越小,Y轴链表同理.这样,每次需要更新状态的时候,只需要在这个链表上向前或者向后遍历结点就知道该通知谁了.

这里假设有三个API:Add(向场景中添加一个对象),Leave(某对象离开场景),Move(某对象在场景中移动).

来一个一个看.

Add:根据新增对象的X,Y坐标,依次遍历X,Y轴坐标链表,这里有两个目的,一个是获得这个新增对象的坐标在X,Y轴坐标的位置,另一方面获得该通知哪些结点.通知的范围,每个对象可以自己定制自己的通知范围.但是为了简单起见,在这里我们假设每个结点X,Y坐标相差1,而通知的范围是2.比如原有的X轴坐标为:
a->b->c->d->e->f->g->h
假设新增一个对象z,它最终所在的位置是c和d之间,根据前面的假设,它只需要通知b,c,e,f四个结点就好了.所以,新增一个结点的时候,并不需要遍历完链表的.
但是这里还需要注意的是,一个结点,必须X,Y坐标同时都在通知范围内才可以进入通知集合.

Leave:在添加了对象之后,对象身上挂了两个结点,分别存放在X,Y轴坐标链表上的位置,那么在这个对象要离开场景时,也只需要向前向后探索一定的范围,就可以得到需要通知该对象要离开的集合了.

Move:移动操作比较麻烦,但是也可以比较漂亮的解决.在更新位置之前,同样根据前面的算法得到要通知的结点集合,称为old_set;更新位置之后,再获取另一个通知集合,称为new_set;然后,old_set和new_set的交集,称为move_set,在此集合中的结点,在移动前后都在通知范围,因此要向这个集合的结点发送该对象移动的消息;而old_set-move_set的集合,在移动之后已经离开了视野,因此要向它们发送该对象离开的消息;最后,new_set-move_set是移动之后才看见该结点的结点集合,这个集合的结点,要发送该结点进入场景的消息.

posted on 2017-03-07 15:09 C++技术中心 阅读(1739) 评论(0)  编辑 收藏 引用 所属分类: 游戏开发

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理