【步兵 cocos2dx】四叉树碰撞算法

cocos2d-x 3.x 专栏收录该内容
12 篇文章 0 订阅

【步兵 cocos2dx】四叉树碰撞算法 by EOS.

四叉树碰撞网上例子不少,自己也观摩了一下,然后自己写了一个。
并不是想证明自己写的比别人好,不自己写一遍,总感觉自己不认识它。
写过之后感觉,深入了解,并且自己的代码才是最放心的~

原理部分我就跳过了,直接百度一大票。
阅读下文之前,最好去看一下原理,那样看起代码来会更快更清晰。

徒弟说有图效果更加,最好是看着酷炫的。
感觉好友道理,赶紧补上 ( ̄︶ ̄)
这里写图片描述

这里写图片描述

还有张动态碰撞的。可惜太大了,只支持2M以下的,悲剧了
想看的可以点链接 动态四叉碰撞图


先解说一下头文件

#include <list> 
#include "2d\CCNode.h"

namespace fxcollision
{
    typedef unsigned char litInt; //0-255绝对够用了
    const static litInt MAX_NODES = 5; //最大节点数
    const static litInt MAX_LEVEL = 4; //最多切几次
    const static litInt NO_IN_SUB = 4; //不在子结点上 
    static bool isRectInclude(const cocos2d::Rect& bigRect, const cocos2d::Rect& smallRect)
    {//是否是包含关系  --也可以不注意顺序 然后 大小互换递归一次,效率低点
        return bigRect.getMinX() < smallRect.getMinX() && bigRect.getMaxX() > smallRect.getMaxX()
            && bigRect.getMinY() < smallRect.getMinY() && bigRect.getMaxY() > smallRect.getMaxY();
    };
    class FXNode;
    class FXTree;

    class FXNode //继承方便,组合灵活--换平台改动小
    {
    public:
        FXNode(cocos2d::Node* node);
        ~FXNode();
        cocos2d::Rect getBox();
        void update();//动态优化--静态的不需要调用

    public:
        cocos2d::Node* ccNode;
        litInt index;
        FXTree* parentTree;//在哪个树下
    };

    class FXTree 
    {
    public:
        FXTree(litInt l, cocos2d::Rect r);
        ~FXTree();
        void clear();//递归清空
        void split();//切分
        bool hasSub() { return subTree[0] != NULL; };//是否有子树
        litInt getIndex(const cocos2d::Rect& nr);//过去象限
        void push(FXNode* node);//递归插入
        void remove(FXNode* node);//直接从nodeList移除
        void insert(FXNode* node);//直接放入nodeList
        void check(std::list<FXNode*>& retList, FXNode* node);//获取需检测的结点
        void crossCheck(std::list<FXNode*>& retList, FXNode* node);//跨象限检索

        static cocos2d::Node* sDrawNode;//调试画线用的
    public:
        litInt lv;
        cocos2d::Rect rect;
        cocos2d::Rect subRect[4];
        FXTree* subTree[4];
        std::list<FXNode*> nodeList;//频繁插入删除用链表
        FXTree* parent;//动态优化用的
    };
};

FXTree多么优雅的名字, 四(Four)叉(X)树(Tree) 然后在缩写一下FXTree。
噗·~其实我英语很渣的。。。


下面是源码

#include "FXTree.h"
#include "base\CCDirector.h"
#include "2d\CCDrawNode.h"
#include "2d\CCLabel.h"

using namespace std;
using namespace fxcollision;
using namespace cocos2d;

const static Color3B tagClr[4] = {
    //四象限颜色
    Color3B(255,255,0),
    Color3B(255,0,0),
    Color3B(0,255,0),
    Color3B(0,0,255),
};

Node* fxcollision::FXTree::sDrawNode = NULL;


/以下是FXTree/

FXTree::FXTree(litInt l, Rect r)
:   lv(l),
    rect(r)
{
    auto subW = rect.size.width / 2;
    auto subH = rect.size.height / 2;
    subRect[0] = Rect(rect.getMidX(), rect.getMidY(), subW, subH);
    subRect[1] = Rect(rect.getMinX(), rect.getMidY(), subW, subH);
    subRect[2] = Rect(rect.getMinX(), rect.getMinY(), subW, subH);
    subRect[3] = Rect(rect.getMidX(), rect.getMinY(), subW, subH);
    parent = subTree[0] = subTree[1] = subTree[2] = subTree[3] = NULL;
}

FXTree::~FXTree()
{
    nodeList.clear();
}

void FXTree::clear()
{
    nodeList.clear();
    if (hasSub()) 
    {
        //列举 比 for 稍微微微微微微微微微微 快一点~
        subTree[0]->clear();
        subTree[1]->clear();
        subTree[2]->clear();
        subTree[3]->clear();
    }
}

void FXTree::split()
{
    auto subW = rect.size.width/2;
    auto subH = rect.size.height/2;
    subTree[0] = new FXTree(lv + 1, subRect[0]);
    subTree[1] = new FXTree(lv + 1, subRect[1]);
    subTree[2] = new FXTree(lv + 1, subRect[2]);
    subTree[3] = new FXTree(lv + 1, subRect[3]);
    subTree[0]->parent = subTree[1]->parent = subTree[2]->parent = subTree[3]->parent = this;

    auto drawNode = (DrawNode*)sDrawNode;
    drawNode->drawSegment(Vec2(rect.getMinX(), rect.getMidY()), Vec2(rect.getMaxX(), rect.getMidY()), 1, Color4F::RED);
    drawNode->drawSegment(Vec2(rect.getMidX(), rect.getMinY()), Vec2(rect.getMidX(), rect.getMaxY()), 1, Color4F::RED);
}

litInt FXTree::getIndex(const Rect& nr)
{
    auto idx = NO_IN_SUB;

    for (int i = 0; i < 4; ++i)
    {
        if (isRectInclude(subRect[i], nr))
        {
            idx = i;
            break;
        }
    }

    return idx;
}

void FXTree::push(FXNode* node)
{
    auto nodeBox = node->getBox();

    //如果存在子树,就向下传递
    auto idx = getIndex(nodeBox);
    if (idx != NO_IN_SUB && hasSub())
    {
        node->index = idx;
        subTree[idx]->push(node);
        if (node->ccNode->getColor() == Color3B::WHITE) {
            node->ccNode->setColor(tagClr[idx]);
            node->ccNode->setOpacity(255 - (lv + 1) * 50);
        }
        return;
    }

    nodeList.push_back(node);
    node->parentTree = this;
    node->ccNode->setColor(Color3B::WHITE);
    node->ccNode->setOpacity(255 - lv * 50);

    if (!hasSub() && nodeList.size() > MAX_NODES && lv < MAX_LEVEL)
    {
        split();
        nodeList.remove_if([this](FXNode* node_lm)->bool {
            auto idx_lm = getIndex(node_lm->getBox());
            if (idx_lm != NO_IN_SUB)
            {
                node_lm->index = idx_lm;
                subTree[idx_lm]->push(node_lm);
                node_lm->ccNode->setColor(tagClr[idx_lm]);
                node_lm->ccNode->setOpacity(255 - (lv + 1) * 50);
                return true;
            }
            return false;
        });
    }
}

void FXTree::remove(FXNode* node)
{
    nodeList.remove(node);
}

void FXTree::insert(FXNode* node)
{
    nodeList.push_back(node);
}


void FXTree::check(std::list<FXNode*>& retList, FXNode* node)
{
    auto nodeBox = node->getBox();
    auto idx = getIndex(nodeBox);

    //子结点递归检查
    if (idx != NO_IN_SUB && subTree[idx])
    {
        subTree[idx]->check(retList, node);
    }
    else
    {
        //跨象限检索
        crossCheck(retList, node);
    }

    //本身的全部都要检查 -- 可能是超大物体跨N象限 待优化
    for_each(nodeList.begin(), nodeList.end(), [=, &retList](FXNode* node_lm) {
        retList.push_back(node_lm);
    });
}

void FXTree::crossCheck(std::list<FXNode*>& retList, FXNode* node)
{
    if (!hasSub()) return;

    auto nodeBox = node->getBox();

    for (int i = 0; i < 4; ++i)
    {
        if (subTree[i]->rect.intersectsRect(nodeBox))
        {
            for_each(subTree[i]->nodeList.begin(), subTree[i]->nodeList.end(), [=, &retList](FXNode* node_lm) {
                retList.push_back(node_lm);
            });
            subTree[i]->crossCheck(retList, node);
        }
    }
}


/以下是FXNode/

FXNode::FXNode(Node* node)
:ccNode(node),
index(NO_IN_SUB),
parentTree(NULL)
{
    ccNode->setOnExitCallback([this]() {
        if(parentTree)
            parentTree->remove(this);
        delete this;
    });
}

FXNode::~FXNode()
{
}


Rect FXNode::getBox()
{
    return ccNode->getBoundingBox();
}

void FXNode::update()
{
    auto nodeBox = ccNode->getBoundingBox();

    //向下--进入子树
    auto subIdx = parentTree->getIndex(nodeBox);
    if (subIdx != NO_IN_SUB && parentTree->subTree[subIdx])
    {
        parentTree->remove(this);
        parentTree->push(this);
        return;
    }

    //向上--返回父树
    if (parentTree->parent == NULL) return;
    auto newIndex = parentTree->parent->getIndex(nodeBox);
    if (newIndex == index) return;

    auto papa = parentTree->parent;
    if (newIndex != NO_IN_SUB)
    {
        papa->subTree[index]->remove(this);
        papa->subTree[newIndex]->insert(this);
        ccNode->setColor(tagClr[newIndex]);
        index = newIndex;
    }
    else
    {
        parentTree->remove(this);
        if (papa->parent == NULL) 
            papa->push(this);
        else
            papa->parent->push(this);//最多跨三阶
    }
}

可能注释不多,其实 我代码里都没这么多注释的=、=,现加了点。

我对自己代码还蛮有自信的说=。= (PS:注释写得好不如代码写的妙~)


使用Demo

//画线
auto r = cocos2d::DrawNode::create();
r->setName("DrawNode");
this->addChild(r, 9999);
FXTree::sDrawNode = r;

//获取精灵
auto getOne = [=](float x, float y)->Sprite* {
    auto sprite = Sprite::create("HelloWorld.png", Rect(0, 0, 20, 20));
    sprite->setPosition(Vec2(x, y));
    this->addChild(sprite, 0);
    return sprite;
};

//树根 =、=
FXTree* root = new FXTree(0, Rect(0, 0, 800, 800));

//按下键盘添加
auto keyListener = EventListenerKeyboard::create();
keyListener->onKeyPressed = [=](EventKeyboard::KeyCode keyCode, Event* event) {
    log("Key %d pressed.", keyCode);  
    for (int i = 0; i < 10; i++)
    {
        auto sprite = getOne(20 + rand_0_1() * 760, 20 + rand_0_1() * 760);
        auto fxNode = new FXNode(sprite);
        root->push(fxNode);//创建插入

        sprite->schedule([=](float dt) {
            auto pos = Vec2(-10 + rand_0_1() * 20, -10 + rand_0_1() * 20);
            if (sprite->getPositionX() + pos.x < 0 || sprite->getPositionX() + pos.x > 800)
                pos.x *= -1;
            if (sprite->getPositionY() + pos.y < 0 || sprite->getPositionY() + pos.y > 800)
                pos.y *= -1;
            sprite->setPosition(sprite->getPosition() + pos);
            fxNode->update();//动态优化
        }, "x");
    }
};
keyListener->onKeyReleased = [](EventKeyboard::KeyCode keyCode, Event* event) { log("Key %d released.", keyCode); };
_eventDispatcher->addEventListenerWithSceneGraphPriority(keyListener, this);

static std::list<Node*> spArr;
this->schedule([=](float dt) {
    if (!spArr.empty())
    {
        for_each(spArr.begin(), spArr.end(), [=](Node* sp) {
            sp->setScale(1);
        });
        spArr.clear();
    }
    std::list<FXNode*> retList;
    root->check(retList, fxNodeMove);//fxNodeMove是随便创建的一个,跟着鼠标走的
    if (!retList.empty())
    {
        auto box = moves->getBoundingBox();//减少临时变量
        for_each(retList.begin(), retList.end(), [=](FXNode* node) {
            if (box.intersectsRect(node->getBox()))
            {
                node->ccNode->setScale(2);
                spArr.push_back(node->ccNode);
            }
        });
    }
}, "x");

这是1VN模式,NVN一个道理,使用部分竟然贴了这么多。啧啧,不开心~


题外话

还有可优化空间,优化之前要保证算法的正确性和可读性=。=
具体优化可能会实际使用再说,先这样,有哪里些的不好的地方欢迎指正~

See Again~
之前
真爱无价,欢迎打赏~
赞赏码

  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值