浅谈精灵容器和“精灵”中添加的精灵碰撞问题。


      在oop的思想趋势下,越来越要求将自定义的“精灵”比如怪物或者monster写成精灵的子类,一方面可以添加众多属性,另一方面方便精灵中添加精灵。
      所谓精灵中添加精灵,如下:
===========================
class Player:public Sprite{…};

bool player::playinit(){

    if(!Sprite::init())

        return false;

    Size size=Director::getInstance()->getWinSize();

//精灵中添加两个精灵的对象,作为player的child。

    Sprite*sp=Sprite::create("4.0.png");

    sp->setPosition(size.width/2,size.height/2);

    this->addChild(sp,1,tag_p1);

    Sprite*sp2=Sprite::create(“5.0.png");

    sp2->setPosition(size.width/2,size.height/2);

    this->addChild(sp2,1,tag_p1);

    return true;

}

=========================

    精灵中添加精灵有什么好处,来看两个例子:

例一,精灵有血条,精灵也有自己的运动,我们当然希望精灵的运动和血条是不干涉的,也就是血条的加减血和精灵的运动是两个不同的精灵,各自负责自己的运动,但是又有一些运动希望他们同步,比如,一起走,一起跳,等等;

例二,英雄的运动和英雄的皮肤分开,英雄升级换一套皮肤,然后运动相同;比如有10级,10套皮肤,英雄的动作由20张图片合成,如果不分离,我们可能需要10*20=200(每种皮肤一套动作)张图片,但是分离,我们只需要20+10张(一套动作,套上10套皮肤)。

    先来看两个初始化精灵的常用函数,

通过精灵图片创建:

==============================

SpriteSprite::create(const std::string& filename)

{

    Sprite *sprite = new (std::nothrowSprite();

    if (sprite && sprite->initWithFile(filename))  //注意init

    {

        sprite->autorelease();

        return sprite;

    }

    CC_SAFE_DELETE(sprite);

    return nullptr;

}

bool Sprite::initWithFile(const std::string& filename)

{

    CCASSERT(filename.size()>0"Invalid filename for sprite");


    Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);

    if (texture)

    {

        Rect rect = Rect::ZERO;

        rect.size = texture->getContentSize();

        return initWithTexture(texture, rect);

    }

    return false;

}


直接创建:

SpriteSprite::create()

{

    Sprite *sprite = new (std::nothrowSprite();

    if (sprite && sprite->init())  //注意init

    {

        sprite->autorelease();

        return sprite;

    }

    CC_SAFE_DELETE(sprite);

    return nullptr;

}


bool Sprite::init(void)

{

    return initWithTexture(nullptrRect::ZERO );

}         
====================================
比较两种创建方式,对于create,只有 init(), initWithFile(filename), 一句有别,但是他们产生的精灵确完全不同:
他们的init都返回一个   return initWithTexture(texture, rect); Texture是啥,纹理,openGl将png图片的像素变成精灵, 当你用默认时 return initWithTexture(nullptr, Rect::ZERO );nullptr,没有纹理,Rect为0,说明了什么,
一个空精灵-这就是所谓的精灵的容器。(其实任何方式创建的精灵都可以视为一个精灵的容器,但是不带参的这种方式,更像一个空容器)。
这个精灵是没有任何载体的。
     这是一个好消息,没有任何载体,意味着你可以自由发挥,你可以添加任意个精灵在里面,并且各自负责各自的运动。各自child精灵的运动在精灵内部完成。然后对于整个精灵的运动,在Layer中完成,oop思想的极致体现啊。
     这时,你在player的init中添加精灵。
============================

 Sprite*sp1=Sprite::create("1483.0.png");

    sp1->setAnchorPoint(ccp(0.5,1));

    sp1->setPosition(this->getContentSize().width,this->getContentSize().height);

    addChild(sp1);

============================

       并给他一个动作,如果这是一个血条,他会动态加减血;
可以在play的update中直接动作
============================

   auto sp=(Player*)this->getChildByTag(tag_hp);

    int add=p->hp_n+k;

    if (add>10) {

        add=10;

    }

    float sca=(float)add/10;

    CCLOG("-----%f",sca);

      sp-> setScaleY (sca);
也可以在layer的update中去更改

  auto p=(Player*)this->getChildByTag(tag_player);

    auto sp=(Player*)p->getChildByTag(tag_hp);

    int add=p->hp_n+k;

    if (add>10) {

        add=10;

    }

    float sca=(float)add/10;

    CCLOG("-----%f",sca);

    sp->setScaleY(sca);

============================

这是player的child的孩子自己的动作,他独立出去了,很好,
然后整体的运动,
============================
  auto sp=(Player*)this->getChildByTag(tag_player);

sp->runAction(Sequence::create(JumpTo::create(1.0,ccp(200,100),3001),CallFunc::create(this,callfunc_selector(Player::gowolk)),CallFunc::create(this,callfunc_selector(Player::set_speed)),NULL));

============================

这将发生什么,你操作的是精灵的容器,那么这个精灵中的所有子精灵,都将执行相同的运动,这就实现了案例一的要求。


现在来说说实现共同运动的几种方式:

一,直接通过initwithfile创建出实体玩家,在玩家这个精灵上,添加血条精灵,这是一个比较好用的方法,你可以很好的实现碰撞。共同运动;但是,架构不好,因为你有一个主精灵this,还有一堆child,你的主精灵是个实在的实体。

二,通过不带参的creat创建空精灵,在适当位置initwithfile,这和方法一相同。也好实现碰撞

三,在player中实现每个精灵的共同运动,比如按键,所有精灵一起跳,

 auto vec=this->get children(),他返回一个Vector(Node*),你add child都在这里面,然后每个jumpTo。

for(auto sp:vec){ sp->runaction(Jumpto::create(100,ccp(100,100)))}

这也能实现共同运动,但这是比较垃圾的方法。

四,创建空精灵,并且不实例化,就操作这个”不存在“的精灵。

但是对于碰撞,和坐标,该如何操作。这就是难点,也是本文讨论的重点所在。


比如我们创建一个精灵,这个精灵里面有一堆精灵,这个精灵是用方法四创建的,如下

============================

playerplayer::createplayer(){

        player *pRet = new(std::nothrowplayer();

             if (pRet && pRet->init())

        {

            pRet->autorelease();

            return pRet;

        }

        else

        {

            delete pRet;

            pRet = NULL;

            return NULL;

        }

}

bool player::playinit(){

    if(!Sprite::init())

        return false;

    score=0;

    Size size=Director::getInstance()->getWinSize();

//只列举一个

    Sprite*sp=Sprite::create("4.0.png");

    sp->setPosition(568,160);

    this->addChild(sp,1,tag_p1);

...

    return true;

}

============================

当我们在主Layer中添加精灵
============================

   player*p=player::createplayer();

    p->setPosition(0,0);

    p->playinit();

    addChild(p,1,tag_player);

============================

这时setPosition为(0,0),精灵的坐标设置了两次 sp->setPosition(568,160);  p->setPosition(0,0);

这样,对于layer来说,这个player中的sp1的位置就是(0+568,0+160),这和坐标转换converToWorldspace函数的意思有点类似,然后我们操作精灵移动,改变player*p的位置,很好,添加的所有子精灵都跟着移动了,但是

这个“但是”很重要,碰撞失效了。当我们精灵的位置为0,0时还好,可以碰撞,但是一旦移动了,或者player的setposition不是0,0.碰撞压根就不存在(其实是存在的,只是不合效果)。

这是什么原因:

============================

        auto pla=(player*)this->getChildByTag(tag_player);

        Sprite* sp=(Sprite*)pla->getChildByTag(tag_p1);        

        if(i->boundingBox().intersectsRect(sp->boundingBox())){…}

//sp是pla内部的子精灵,是pla的孩子节点

============================

是sp没取到,还是这样跨级取不合理,还是boundingbox这个函数实效了?

通过CCLOG sp的精灵坐标,pla的坐标以及,sp的contentSize,可以发现一切没有问题。

问题出在intersectsRect。

操作sp的移动,直接在layer中sp->setposition,但是,我们这不是一个精灵,而是一组,当然你也可以一个个setposition,那么写精灵组合的意义就没有了;所以,我们在Layer中操作pla的移动,所有的pla中的child都会跟着移动。

这里将引进一个概念,模型坐标,当pla的坐标变化了,精灵的位置也变了,但是sp的坐标一直没变,而intersecrsRect用的判断坐标恰恰是你的sp坐标,一个相对你精灵组的模型坐标,所以,无论你如何改变你的pla坐标,你的sp坐标没变,虽然你的位置变了,但是碰撞点的判断的位置一直没变(568,160),一直在根据你的sp在player中setposition的那个点在判断。

问题找到,如何解决。

这里总结了4套方案:

方案一,将精灵通过createwithfile的方式创建,再在这个精灵内添加一些装饰精灵,一主多辅的方案,此方案较为简单,也不容易出错,boundingbox也是根据你的file的精灵来的,位置上精灵和模型统一,底层的碰撞函数可用,不存在模型坐标问题。

方案二,创建精灵空容器player,里面有个精灵,sp1,将碰撞写在自己的成员函数里,这样的话,碰撞的对象就需要转换成你的player模型坐标,更加容易出错,不推荐。

方案三,将你的精灵容器位置设置为(0,0),移动时候将player 的所有自精灵同时移动,也可以达到效果,但这是个糟糕的做法,第一,这么做,还不如layer,完全没有用到精灵的特性,第二,精灵容器child越多,代码量越多,当然循环也可以搞定,但是内存优化方面却不能及;

方案四:就是上面所说的,player就是空的,里面有很多精灵,运动通过操作player完成,但是碰撞有问题,其实该方案比较符合精灵容器的观点,解决碰撞问题的方案,就是重写碰撞函数。

如下:sp坐标+pla坐标+图片半径,(当然,也可以用Poin p=pla->converToWorldSpace(sp->getposition),p代替sp坐标+pla坐标)

   if (i->getPositionY()<sp->getPositionY()+pla->getPositionY()+sp->getContentSize().height/2

            &&i->getPositionY()>sp->getPositionY()+pla->getPositionY()-sp->getContentSize().height/2

            &&i->getPositionX()<sp->getPositionX()+pla->getPositionX()+sp->getContentSize().width/2

            &&i->getPositionX()>sp->getPositionX()+pla->getPositionX()-sp->getContentSize().width/2) 


或者改写底层的intersecrsRect函数

bool GameLayer::myintersecrsRect(Sprite* sp1,Sprite* sp2){

    Rect Rect1=sp1->boundingBox();

    Rect Rect2=sp2->boundingBox();

    auto position1=sp1->getParent()->getPosition();

    auto position2=sp2->getParent()->getPosition();

    

    return !(Rect1.getMaxX()+position1.x<Rect2.getMinX()+position2.x ||

             Rect2.getMaxX()+position2.x<Rect1.getMinX()+position1.x ||

             Rect1.getMaxY()+position1.y<Rect2.getMinY()+position2.y ||

             Rect2.getMaxY()+position2.y<Rect1.getMinY()+position1.y);

}

当然,这只适合两级的精灵容器,三级的就要另写了,不过,一个精灵容器写到三级,真的还不如写成layer了。
仅以此文,献给使用精灵"容器"出错的同学,分享心得,交流学习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值