cocos2dx : schedule 计时,调用崩溃

本文介绍了在使用cocos2dx时遇到的schedule计时调用导致程序崩溃的问题。问题源于在错误的对象上下文中调用了schedule函数。解决方案包括从包含成员的正确类中启动schedule,或者使用scheduler的scheduleSelector方法手动设置this指针。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这个问题还不知道怎么用标题来说明


问题如下:

有一个Scene如下:

class FirstScene : public cocos2d::CCLayer
{
public:
    virtual bool init();  
    static cocos2d::CCScene* scene();
    CREATE_FUNC(FirstScene);

    void start(float f);
    CCNode *anim;
};
里面有一个start函数,是一个定时器回调。有一个节点,比如是一个动画什么的。start函数如下:

void start(float f)
{
    anim->doSomething();
}

现在我要做的事情,是要让这个节点anim过段时间之后,执行start函数。有时候惯性思维(以前用wiengine),就直接这么写了

anim->schedule(SEL_SCHEDULE(start));
然后一运行,就崩了,崩的还很诡异,说anim是空指针


原因如下:

schedule函数,在ccnode里面的定义

void CCNode::schedule(SEL_SCHEDULE selector)
{
    this->schedule(selector, 0.0f, kCCRepeatForever, 0.0f);
}
然后会调用到

void CCNode::schedule(SEL_SCHEDULE selector, float interval, unsigned int repeat, float delay)
{
    CCAssert( selector, "Argument must be non-nil");
    CCAssert( interval >=0, "Argument must be positive");

    m_pScheduler->scheduleSelector(selector, this, interval , repeat, delay, !m_bRunning);
}
发现问题没有,请注意第一个参数和第二个参数。第一个参数就是执行的函数指针。第二个参数,他直接把自己(this)作为m_pScheduler的参数丢进去了。最后在触发回调之后,他的调用就会成为

(m_pTarget->*m_pfnSelector)(m_fElapsed);
这里的m_pTarget就成了this,由于我们之前是用anim启动schedule,所以这个this就成了anim。那么在anim对象里面去找一个叫anim的变量,就找不到了。

解决办法:
1、从包含你要操作的成员的类启动schedule。

  2、如果这个类没有schedule函数,或者说她不是node的子类,用scheduler的scheduleSelector启动,因为这个可以手动设置this指针,把this设置成第一点说的。

检查VScocos2dx4.0代码问题,并修改错误 #include "Thorn.h" #include "HelloWorldScene.h" // StaticThorn StaticThorn* StaticThorn::create(const cocos2d::Size& size, const cocos2d::Vec2& position) { StaticThorn* thorn = new (std::nothrow) StaticThorn(); if (thorn && thorn->initWithFile("Thorn.png")) { thorn->autorelease(); thorn->setPosition(position); thorn->setContentSize(size); thorn->setupPhysics(); return thorn; } CC_SAFE_DELETE(thorn); return nullptr; } void StaticThorn::setupPhysics() { damageBody = cocos2d::PhysicsBody::createBox(this->getContentSize(), cocos2d::PhysicsMaterial(0.0f, 0.0f, 1.0f)); damageBody->setDynamic(false); damageBody->setCategoryBitmask(0x08); damageBody->setCollisionBitmask(0x01); damageBody->setContactTestBitmask(0x01); damageBody->setTag(THORN_DAMAGE_TAG); this->setPhysicsBody(damageBody); triggerBody = cocos2d::PhysicsBody::createBox(this->getContentSize(), cocos2d::PhysicsMaterial(0.0f, 0.0f, 1.0f)); triggerBody->setDynamic(false); triggerBody->setCategoryBitmask(0x10); triggerBody->setCollisionBitmask(0x01); triggerBody->setContactTestBitmask(0x01); triggerBody->setTag(THORN_TRIGGER_TAG); this->addChild(cocos2d::Node::create()); this->getChildByTag(0)->setPhysicsBody(triggerBody); } void StaticThorn::onTrigger() { // Do nothing for static thorn } // MovingThorn MovingThorn* MovingThorn::create(const cocos2d::Size& size, const cocos2d::Vec2& position) { MovingThorn* thorn = new (std::nothrow) MovingThorn(); if (thorn && thorn->initWithFile("Thorn.png")) { thorn->autorelease(); thorn->setPosition(position); thorn->setContentSize(size); thorn->setupPhysics(); return thorn; } CC_SAFE_DELETE(thorn); return nullptr; } void MovingThorn::setupPhysics() { Thorn::setupPhysics(); } void MovingThorn::onTrigger() { auto moveAction = cocos2d::MoveBy::create(1.0f, cocos2d::Vec2(0, 100)); auto removeAction = cocos2d::RemoveSelf::create(); auto sequence = cocos2d::Sequence::create(moveAction, removeAction, nullptr); this->runAction(sequence); } // InvisibleThorn InvisibleThorn* InvisibleThorn::create(const cocos2d::Size& size, const cocos2d::Vec2& position) { InvisibleThorn* thorn = new (std::nothrow) InvisibleThorn(); if (thorn && thorn->initWithFile("Thorn.png")) { thorn->autorelease(); thorn->setPosition(position); thorn->setContentSize(size); thorn->setOpacity(0); thorn->setupPhysics(); return thorn; } CC_SAFE_DELETE(thorn); return nullptr; } void InvisibleThorn::setupPhysics() { Thorn::setupPhysics(); } void InvisibleThorn::onTrigger() { auto fadeIn = cocos2d::FadeIn::create(0.5f); auto moveAction = cocos2d::MoveBy::create(1.0f, cocos2d::Vec2(0, 100)); auto removeAction = cocos2d::RemoveSelf::create(); auto sequence = cocos2d::Sequence::create(fadeIn, moveAction, removeAction, nullptr); this->runAction(sequence); } // StretchingThorn StretchingThorn* StretchingThorn::create(const cocos2d::Size& size, const cocos2d::Vec2& position, float stretchLength) { StretchingThorn* thorn = new (std::nothrow) StretchingThorn(); if (thorn && thorn->initWithFile("Thorn.png")) { thorn->autorelease(); thorn->setPosition(position); thorn->setContentSize(size); thorn->stretchLength = stretchLength; thorn->setupPhysics(); return thorn; } CC_SAFE_DELETE(thorn); return nullptr; } void StretchingThorn::setupPhysics() { Thorn::setupPhysics(); } void StretchingThorn::onTrigger() { auto stretchAction = cocos2d::ScaleBy::create(1.0f, 1.0f, stretchLength / this->getContentSize().height); auto removeAction = cocos2d::RemoveSelf::create(); auto sequence = cocos2d::Sequence::create(stretchAction, removeAction, nullptr); this->runAction(sequence); } #ifndef THORN_H #define THORN_H #include "cocos2d.h" class Thorn : public cocos2d::Sprite { public: static const int THORN_DAMAGE_TAG = 400; static const int THORN_TRIGGER_TAG = 500; virtual void setupPhysics() = 0; // 纯虚函数 virtual void onTrigger() = 0; // 纯虚函数 protected: cocos2d::PhysicsBody* damageBody; cocos2d::PhysicsBody* triggerBody; }; class StaticThorn : public Thorn { public: static StaticThorn* create(const cocos2d::Size& size, const cocos2d::Vec2& position); virtual void setupPhysics() override; virtual void onTrigger() override; }; class MovingThorn : public Thorn { public: static MovingThorn* create(const cocos2d::Size& size, const cocos2d::Vec2& position); virtual void setupPhysics() override; virtual void onTrigger() override; }; class InvisibleThorn : public Thorn { public: static InvisibleThorn* create(const cocos2d::Size& size, const cocos2d::Vec2& position); virtual void setupPhysics() override; virtual void onTrigger() override; }; class StretchingThorn : public Thorn { public: static StretchingThorn* create(const cocos2d::Size& size, const cocos2d::Vec2& position, float stretchLength); virtual void setupPhysics() override; virtual void onTrigger() override; private: float stretchLength; }; #endif // THORN_H
最新发布
06-05
<think>我们正在处理一个Cocos2d-x4.0项目中的Thorn类及其派生类的代码。首先,我们需要检查代码中可能存在的问题,并修正错误。观察提供的代码,我们可以发现几个问题:1.在StaticThorn::setupPhysics中,我们创建了两个物理体(damageBody和triggerBody),但是我们将damageBody设置为当前节点的物理体,而将triggerBody添加到了一个新创建的节点(作为子节点)上。然而,在创建子节点时,我们使用了`this->addChild(cocos2d::Node::create());`,然后立即使用`this->getChildByTag(0)`获取这个节点。这里有两个问题:-新节点没有设置标签(tag),所以`getChildByTag(0)`将返回nullptr,因为默认的tag是0,而tag=0的节点可能有多个(cocos2d-x允许),但通常我们不应该依赖tag=0。而且这里我们根本没有设置tag,所以使用tag=0来获取节点是不正确的。-实际上,我们并不需要为triggerBody创建一个新的节点。在Cocos2d-x中,一个节点只能有一个PhysicsBody。因此,我们为triggerBody创建了一个子节点,并将triggerBody附加到这个子节点上。这是正确的做法,因为这样可以避免与当前节点的物理体冲突。但是,我们创建子节点的方式可能导致无法正确获取该节点。2.在StaticThorn::setupPhysics中,我们为triggerBody设置了和damageBody相同的碰撞掩码(collisionBitmask和contactTestBitmask),这可能导致两个物理体同时参与碰撞,但也许这是设计意图?我们需要根据游戏设计决定。3.在MovingThorn和InvisibleThorn的setupPhysics方法中,它们调用了`Thorn::setupPhysics()`,但Thorn类中的setupPhysics是纯虚函数,没有实现。所以,我们可能需要在Thorn基类中实现setupPhysics吗?但是代码中Thorn类被定义为抽象基类,所以不能实例化。而派生类都需要实现setupPhysics。然而,我们注意到MovingThorn和InvisibleThorn的setupPhysics方法体是空的,仅仅调用了基类的setupPhysics,但基类并没有实现。这会导致链接错误。4.在StretchingThorn::onTrigger中,我们使用ScaleBy来改变节点的缩放,但是缩放会同时影响节点的内容大小和碰撞体吗?我们没有更新物理体的大小,所以碰撞体不会跟着缩放后的节点更新。这样就会导致物理体与可视内容不一致。5.在InvisibleThorn的create函数中,我们设置了`thorn->setOpacity(0);`使其不可见,这没问题。但在onTrigger中,我们执行了一个fadeIn动作,使其变为可见。然而,我们并没有在动作之后更新其物理体(例如,如果我们需要在可见后重新设置物理体,但这里没有)。6.在MovingThorn::onTrigger中,我们执行了一个移动然后删除的动作。同样,节点移动了,但是物理体的位置是否同步更新?在Cocos2d-x中,当节点移动时,物理体的位置会自动更新(如果物理体是附加在节点上的)。但是注意,在StaticThorn中,我们有两个物理体:一个是节点自身的(damageBody),另一个是子节点的(triggerBody)。当我们移动MovingThorn节点时,子节点会随之移动,所以triggerBody的位置也会更新。但是,对于InvisibleThorn和StretchingThorn,我们需要注意。7.在StretchingThorn中,我们执行了缩放动作,但同样,子节点(如果有的话)也会随之缩放?并且我们并没有更新物理体的大小。所以,在缩放后,物理体的大小并没有改变,这样就会导致错误的碰撞检测。8.头文件中,Thorn类中声明了`damageBody`和`triggerBody`为protected,但是派生类中都在使用。然而,在Thorn类中,我们并没有在析构函数中释放这些物理体?在Cocos2d-x中,物理体是Ref的子类,所以当节点被释放时,附加在节点上的物理体会被自动释放。但是,对于triggerBody,它是附加在子节点上的,子节点也会随着父节点的释放而释放,所以应该没问题。不过,我们需要注意,在Thorn的析构函数中,我们不需要手动删除这些物理体。9.在StaticThorn::setupPhysics中,我们为triggerBody创建了一个子节点,并将triggerBody设置到该子节点上。然而,在MovingThorn、InvisibleThorn和StretchingThorn中,它们都没有重新实现setupPhysics(除了MovingThorn调用了基类的setupPhysics,但这个基类是Thorn,而Thorn的setupPhysics是纯虚函数,所以实际上这些派生类都重了setupPhysics,但MovingThorn和InvisibleThorn又调用了基类的,而基类没有实现,所以会出错)。所以,我们需要在每个派生类中重复StaticThorn中关于triggerBody的创建代码吗?显然,这样代码重复了。10.考虑到代码重复,更好的方式是在基类Thorn中实现一个公共的setupPhysics方法,然后由派生类调用。但是Thorn类中我们声明了setupPhysics为纯虚函数,所以不能直接实现。我们可以将其改为非纯虚函数,并提供一个实现,然后派生类可以选择是否重。或者,我们保留纯虚函数,但将triggerBody的创建代码提取到一个辅助函数中。然而,从现有代码看,每个派生类都重了setupPhysics,只有StaticThorn实现了,其他派生类都是空的(除了调用基类,但基类没有实现)。11.在StaticThorn::setupPhysics中,我们创建了triggerBody并将其附加到一个新节点上,然后这个节点被添加为当前节点的子节点。这个新节点的位置是(0,0),相对于父节点(即Thorn节点)。这样,triggerBody的位置就是当前Thorn节点的位置。这可能是设计意图,即两个物理体重叠在一起。但是,对于其他类型的Thorn,我们可能也希望有同样的triggerBody机制。所以,我们应该将triggerBody的创建和设置放在基类中,或者每个派生类都做同样的事情?目前,除了StaticThorn,其他派生类都没有设置triggerBody。12.在StaticThorn中,我们创建了两个物理体,一个是damageBody(用于伤害)作为节点的物理体,另一个是triggerBody(用于触发)作为子节点的物理体。但是,其他派生类(如MovingThorn)在setupPhysics中调用了`Thorn::setupPhysics()`,而Thorn::setupPhysics是纯虚函数,所以会引发错误。实际上,其他派生类并没有设置triggerBody。因此,这些派生类只有damageBody(由基类中的setupPhysics设置?但基类没有实现,所以实际上根本没有设置)。13.另外,在Thorn基类中,我们没有构造函数初始化两个指针为nullptr,这可能会导致访问野指针。14.在MovingThorn的create函数中,它调用initWithFile("Thorn.png"),而InvisibleThorn和StaticThorn也一样。但是StretchingThorn也是用同样的图片。这可能是一个问题,因为StretchingThorn可能会被拉伸,图片可能会变形。基于以上分析,我们需要进行如下修改:首先,解决基类Thorn的setupPhysics问题。由于Thorn类中的setupPhysics是纯虚函数,而派生类中只有StaticThorn实现了,其他派生类虽然实现了,但是并没有正确的物理体设置。因此,我们可以将setupPhysics在基类中实现,但并不是纯虚的?或者,我们重新设计。但是,观察发现每个派生类都可能有不同的物理体设置需求。然而,从代码来看,除了StaticThorn,其他派生类在setupPhysics中都是空的(除了调用基类,但基类没有实现,所以会报错)。所以,我们需要移除其他派生类中对基类setupPhysics的调用,或者为基类提供一个默认的setupPhysics实现。然而,从设计上,每个Thorn都应该有两个物理体(damageBody和triggerBody)吗?从StaticThorn来看是这样。所以,其他派生类也应该有相同的设置。因此,我们应该将StaticThorn中的setupPhysics的实现移到基类Thorn中,然后由每个派生类来调用基类的setupPhysics,并且可以选择额外设置自己的内容。修改Thorn类,将setupPhysics改为虚函数,并在基类中提供实现。同时,每个派生类的setupPhysics中,首先调用基类的setupPhysics(即Thorn::setupPhysics())来设置两个物理体,然后进行自己的特殊设置。但是,StaticThorn的setupPhysics函数中创建了两个物理体,并分别设置了不同的CategoryBitmask(0x08和0x10),其他物理体设置也不同。对于其他Thorn,我们希望物理体的基本设置是一样的吗?从设计上看,应该是。所以,我们可以在基类中提供一个公共的物理体设置。所以,我们修改Thorn类:1.在Thorn类中实现setupPhysics函数,创建两个物理体(damageBody和triggerBody),设置好相应的属性,并添加triggerBody到子节点。2.将setupPhysics改为虚函数,但不是纯虚函数,这样派生类可以选择是否重,如果不重则使用基类的实现(但通常我们可能需要重?),或者派生类先调用基类的setupPhysics,然后进行自己的设置。但是,每个派生类可能对物理体的设置有不同的要求,例如不同的位掩码?所以,我们可以提供两个保护型的辅助函数,分别用于创建damageBody和triggerBody,这样派生类可以重这些函数来改变物理体的属性。但是,由于时间关系,我们简单地先将StaticThorn中的setupPhysics移到基类Thorn中,并让其他派生类调用基类的setupPhysics(即Thorn::setupPhysics)。修改后,Thorn::setupPhysics的实现与StaticThorn::setupPhysics相同。然后,我们在StaticThorn的setupPhysics中,只需要调用基类的setupPhysics(尽管StaticThorn现在不需要额外的设置,但为了统一,我们也调用基类的)。这样,所有派生类的setupPhysics都一致了。但是,注意:在StaticThorn的create函数中,我们调用了thorn->setupPhysics()。如果我们让Thorn基类实现了setupPhysics,那么StaticThorn::setupPhysics(重的函数)应该调用基类的函数,而基类的函数已经创建了两个物理体。这样StaticThorn就不需要再自己的setupPhysics了?可以删除StaticThorn中的setupPhysics函数?但我们需要在基类中实现。因此,我们重新设计:-在Thorn类中实现setupPhysics函数(去纯虚化),实现与StaticThorn中相同的两个物理体创建。-然后,移除StaticThorn中的setupPhysics函数(因为它和基类一样)。-其他派生类(MovingThorn,InvisibleThorn,StretchingThorn)的setupPhysics函数,我们修改为调用基类的setupPhysics,然后可以添加自己的额外设置(例如,如果需要的话)。但是,MovingThorn、InvisibleThorn和StretchingThorn在setupPhysics中并没有自己的额外设置,所以我们可以直接移除这些函数(使用基类的)。但要注意,基类的setupPhysics是在每个派生类创建时调用的(在create函数中会调用setupPhysics)。另外,关于triggerBody的创建,我们在基类的setupPhysics中创建子节点的方式需要修正:我们给新创建的子节点设置一个tag(例如,一个常量),以确保我们能够正确获取,但我们没有。其实,我们可以将其存储为成员变量,或者不需要获取它(因为我们不需要操作这个子节点,物理体会自动工作)。所以,我们不需要获取这个子节点,但我们需要确保它被添加到了父节点上。因此,在基类的setupPhysics中,我们只需要创建子节点并添加即可,不需要用tag获取。我们修改Thorn::setupPhysics(基类中的实现)如下:```cppvoidThorn::setupPhysics(){//创建damageBodydamageBody=cocos2d::PhysicsBody::createBox(this->getContentSize(),cocos2d::PhysicsMaterial(0.0f,0.0f,1.0f));damageBody->setDynamic(false);damageBody->setCategoryBitmask(0x08);//伤害体的类别damageBody->setCollisionBitmask(0x01);//与哪些类别的物体会碰撞damageBody->setContactTestBitmask(0x01);//与哪些类别的物体会触发接触事件damageBody->setTag(THORN_DAMAGE_TAG);this->setPhysicsBody(damageBody);//创建triggerBodytriggerBody=cocos2d::PhysicsBody::createBox(this->getContentSize(),cocos2d::PhysicsMaterial(0.0f,0.0f,1.0f));triggerBody->setDynamic(false);triggerBody->setCategoryBitmask(0x10);//触发体的类别triggerBody->setCollisionBitmask(0x01);//与哪些类别碰撞triggerBody->setContactTestBitmask(0x01);//与哪些类别接触测试triggerBody->setTag(THORN_TRIGGER_TAG);//创建子节点用于放置triggerBodyautotriggerNode=cocos2d::Node::create();triggerNode->setPhysicsBody(triggerBody);this->addChild(triggerNode);//添加子节点,位置为(0,0),即与父节点相同位置}```这样,triggerNode会被添加为当前节点的子节点,默认位置为(0,0)。这样,triggerBody的位置就和当前节点相同。然后,我们修改派生类:-移除StaticThorn的setupPhysics函数(因为基类已经实现了)。-修改MovingThorn、InvisibleThorn、StretchingThorn的setupPhysics函数为:```cppvoidMovingThorn::setupPhysics(){Thorn::setupPhysics();//调用基类设置//这里可以添加MovingThorn特有的设置}```实际上,这里不需要额外设置,所以我们可以删除这些派生类的setupPhysics函数(如果我们不再需要额外设置),这样它们就会继承基类的setupPhysics。或者,保留它们并调用基类的方法。但是,我们注意到MovingThorn、InvisibleThorn、StretchingThorn的setupPhysics函数在代码中已经存在,所以我们可以直接修改它们:对于MovingThorn:```cppvoidMovingThorn::setupPhysics(){Thorn::setupPhysics();//调用基类}```同理,修改其他派生类。15.对于StretchingThorn的onTrigger,在缩放后,物理体不会自动更新大小。我们需要在缩放后更新物理体的尺寸?但是,物理体是创建在基类setupPhysics中的,而基类中创建的物理体使用的是初始大小。当节点缩放时,子节点也会缩放(包括triggerNode,它上面有triggerBody),所以triggerBody也会随之缩放?但是,物理体缩放并不像节点那样直接。实际上,物理体的大小在创建时就固定了,不会随节点的缩放而更新。因此,我们需要在节点缩放后,重新设置物理体的大小。16.为了解决缩放后物理体不更新的问题,我们有几种选择:a)不使用缩放,而是直接改变节点的大小(setContentSize),然后更新物理体的大小。但这样会影响物理体的形状,需要重新创建物理体?或者我们可以使用setShape来更新形状,但是cocos2d-x的PhysicsBody似乎没有提供直接更新形状的方法,只能通过重新创建。b)在缩放动作进行的同时,创建一个自定义的动作,在每一帧更新物理体的大小。这很复杂,也不推荐。c)设计改变:不要使用缩放来改变刺的大小,而是通过改变节点的高度,然后重新创建物理体?但是这样消耗较大。考虑到刺的拉伸是触发后的一次性动作,我们可以这样处理:在完成拉伸动作后,删除原来的物理体,然后创建新的物理体。但是这样可能会在拉伸过程中出现碰撞检测的问题。另一种方法是,在动作结束后,我们更新物理体的大小(此时节点的大小已经改变)。我们可以将triggerBody和damageBody都删除,然后按照新的尺寸重新创建并附加。但是,这可能会导致在重新创建之前的瞬间发生碰撞检测的遗漏。17.在MovingThorn的onTrigger中,节点移动后会删除自身。这个移动动作是1秒,然后删除。同样,在移动过程中,物理体是否跟随?是的,因为物理体是附加在节点上的。所以移动没有问题。18.对于InvisibleThorn,在变为可见之后,它又移动了100像素然后删除。在移动过程中,物理体也会跟着移动,所以没有位置不同步的问题。19.但是,在InvisibleThorn中,我们将其设为不可见(setOpacity(0)),在触发时淡入。在淡入过程中,它的物理体一直存在吗?是的,物理体不会受Opacity影响。所以,即使不可见,它也会触发碰撞。因此,这可能是设计意图:作为陷阱,不可见但仍然有碰撞。所以这样设计没有问题。20.回到StretchingThorn的拉伸问题,我们需要在动作结束后更新物理体吗?或者,我们改变方法:使用cocos2d::ResizeBy动作(但cocos2d::Sprite没有提供ResizeBy动作)?所以,我们只能使用缩放。那么,我们必须在每次缩放后更新物理体的大小?但缩放动作是连续的,每一帧都在变化,我们不能每一帧都重新创建物理体(效率低)。所以,可能的设计是:在触发拉伸后,我们忽略物理体的精确匹配?或者,我们允许刺在拉伸过程中穿透一些物体(因为这是一个陷阱),或者我们重新设计拉伸刺,不要让它与物体相互作用?这需要看游戏设计。由于时间关系,我们暂时不处理这个问题。但是,我们需要知道存在这个物理体不同步的问题。21.另外,在StretchingThorn的onTrigger中,我们删除了自身(removeSelf),所以它在拉伸后会被移除。这样,即使物理体没有同步,也只是在短暂的时间里不同步,然后就被移除。可能影响不大。基于以上,我们进行代码修改:步骤:1.在基类Thorn中实现setupPhysics函数(将StaticThorn中的代码移入),并修改为创建子节点。2.移除StaticThorn中的setupPhysics函数(因为它不再需要)。3.修改其他派生类(MovingThorn,InvisibleThorn,StretchingThorn)的setupPhysics函数,使其调用基类的setupPhysics。4.修复头文件:在Thorn类中将setupPhysics从纯虚函数改为虚函数,并添加实现。另外,确保每个类的create函数中调用的setupPhysics是派生类重后的版本(通过虚函数机制,会调用到基类的setupPhysics,因为我们没有在派生类中重,所以会调用基类;或者派生类重调用基类)。具体代码修改如下:注意:由于我们没有StaticThorn的setupPhysics函数了,所以StaticThorn不需要重setupPhysics。在thorn.h中:```cppclassThorn:publiccocos2d::Sprite{public:staticconstintTHORN_DAMAGE_TAG=400;staticconstintTHORN_TRIGGER_TAG=500;virtualvoidsetupPhysics();//改为虚函数,不再是纯虚函数virtualvoidonTrigger()=0;//纯虚函数protected:cocos2d::PhysicsBody*damageBody;cocos2d::PhysicsBody*triggerBody;};```然后,在thorn.cpp中实现Thorn::setupPhysics(内容来自原来的StaticThorn::setupPhysics,但稍作修改)。我们注意到,在StaticThorn中,我们定义了两个物理体,但在其他派生类中可能也需要。所以,将Thorn::setupPhysics实现为上面的版本(修正子节点创建问题)。由于代码较长,我们提供修改后的Thorn::setupPhysics(在Thorn.cpp中):```cppvoidThorn::setupPhysics(){//创建damageBodydamageBody=cocos2d::PhysicsBody::createBox(this->getContentSize(),cocos2d::PhysicsMaterial(0.0f,0.0f,1.0f));damageBody->setDynamic(false);damageBody->setCategoryBitmask(0x08);damageBody->setCollisionBitmask(0x01);damageBody->setContactTestBitmask(0x01);damageBody->setTag(THORN_DAMAGE_TAG);this->setPhysicsBody(damageBody);//创建triggerBodytriggerBody=cocos2d::PhysicsBody::createBox(this->getContentSize(),cocos2d::PhysicsMaterial(0.0f,0.0f,1.0f));triggerBody->setDynamic(false);triggerBody->setCategoryBitmask(0x10);triggerBody->setCollisionBitmask(0x01);triggerBody->setContactTestBitmask(0x01);triggerBody->setTag(THORN_TRIGGER_TAG);autotriggerNode=cocos2d::Node::create();triggerNode->setPhysicsBody(triggerBody);this->addChild(triggerNode);//triggerNode被添加,位置为(0,0)}```然后,StaticThorn类中移除setupPhysics函数(声明和实现)。在头文件中:```cppclassStaticThorn:publicThorn{public:staticStaticThorn*create(constcocos2d::Size&size,constcocos2d::Vec2&position);//不需要重setupPhysics,使用基类的virtualvoidonTrigger()override;};```对于MovingThorn、InvisibleThorn、StretchingThorn,我们保留它们的setupPhysics函数,但修改为调用基类:```cppvoidMovingThorn::setupPhysics(){Thorn::setupPhysics();//调用基类}```同时,这些派生类需要包含Thorn.h,并且Thorn.cpp需要被正确编译。另外,在StaticThorn中,我们不需要再声明setupPhysics函数(因为我们不需要重),所以头文件中去掉声明。在StaticThorn的.cpp中,我们移除StaticThorn::setupPhysics的实现。这样修改后,所有Thorn的派生类都拥有两个物理体:一个作为自身,一个作为触发(通过子节点),并且触发体位于与主节点相同的位置。最后,关于StretchingThorn的缩放问题,我们暂时不做处理(在问题中已知,但是需要设计权衡)。我们可以通过重新设计StretchingThorn的行为,或者使用其他方式来实现拉伸并同步物理体。但是,这里给出一个简单的解决方案:在StretchingThorn::onTrigger中,我们不立即删除自身,而是在动作结束后再删除。同时,在动作结束后,我们可以更新物理体的大小(重新创建)。但是这样会导致在动作过程中物理体没有拉伸。所以,我们可以在StretchingThorn中重基类的setContentSize函数?这样每次缩放时调用父类的setContentSize,同时更新物理体。但是,cocos2d::ScaleBy不会改变节点的ContentSize,而是会改变getScaleY()。所以,我们需要在缩放后获取新的尺寸,然后更新物理体?这样不可行。因此,我们考虑在StretchingThorn::onTrigger中,不要使用ScaleBy,而是使用改变ContentSize(通过自定义动作)的方式。这样,我们可以使用Tween来改变ContentSize,并在每一帧更新物理体。但是,自定义动作比较复杂。由于这是代码检查,我们暂不深入,而是标记这是一个问题。总结:我们主要解决了物理体创建的问题,移除了代码重复,修复了基类纯虚函数没有实现导致的链接错误,以及修正了triggerBody的添加方式。现在,修改后的代码(部分)如下:在Thorn.h中:```cpp#ifndefTHORN_H#defineTHORN_H#include"cocos2d.h"classThorn:publiccocos2d::Sprite{public:staticconstintTHORN_DAMAGE_TAG=400;staticconstintTHORN_TRIGGER_TAG=500;virtualvoidsetupPhysics();//虚函数,有默认实现virtualvoidonTrigger()=0;//纯虚函数protected:cocos2d::PhysicsBody*damageBody;cocos2d::PhysicsBody*triggerBody;};classStaticThorn:publicThorn{public:staticStaticThorn*create(constcocos2d::Size&size,constcocos2d::Vec2&position);virtualvoidonTrigger()override;};//其他派生类声明类似,移除setupPhysics函数,除非需要重#endif//THORN_H```在Thorn.cpp中(部分):```cpp#include"Thorn.h"voidThorn::setupPhysics(){//创建damageBody和triggerBody,并添加子节点//...具体代码见上面}//StaticThorn的onTrigger实现voidStaticThorn::onTrigger(){//Donothing}//其他派生类,如MovingThorn等,在它们自己的文件中(如果分开)://在MovingThorn.cpp中voidMovingThorn::setupPhysics(){Thorn::setupPhysics();//调用基类}voidMovingThorn::onTrigger(){//移动然后删除automoveAction=cocos2d::MoveBy::create(1.0f,cocos2d::Vec2(0,100));autoremoveAction=cocos2d::RemoveSelf::create();autosequence=cocos2d::Sequence::create(moveAction,removeAction,nullptr);this->runAction(sequence);}```注意:在InvisibleThorn的create函数中,我们调用了thorn->setOpacity(0);,然后才调用setupPhysics。这没问题,因为物理体的设置不依赖于opacity。最后,由于头文件中StaticThorn移除了setupPhysics,其他派生类如果在头文件中声明了setupPhysics,我们也需要移除,除非它们需要重。以MovingThorn为例:修改前:```cppclassMovingThorn:publicThorn{public:staticMovingThorn*create(constcocos2d::Size&size,constcocos2d::Vec2&position);virtualvoidsetupPhysics()override;virtualvoidonTrigger()override;};```修改后(如果我们不再需要重setupPhysics,则移除该函数的声明):```cppclassMovingThorn:publicThorn{public:staticMovingThorn*create(constcocos2d::Size&size,constcocos2d::Vec2&position);virtualvoidonTrigger()override;};```然后,在MovingThorn.cpp中,我们删除setupPhysics函数的实现。但是,如果我们在基类Thorn::setupPhysics中创建物理体,那么MovingThorn会自动调用基类的setupPhysics(通过create函数中调用setupPhysics,因为MovingThorn没有重,所以调用基类)。如果派生类不需要特殊处理setupPhysics,我们不必在派生类中重setupPhysics。所以,对于MovingThorn,我们完全移除setupPhysics函数(声明和实现)。但是,在提供的原始代码中,MovingThorn、InvisibleThorn、StretchingThorn都重了setupPhysics。现在,如果我们不需要,就可以移除这些重。因此,我们移除这些派生类的setupPhysics函数:-MovingThorn:移除头文件中的声明和实现文件中的定义。-InvisibleThorn:同样移除。-StretchingThorn:同样移除。然后,在StretchingThorn类中,我们只需要重onTrigger。所以其头文件为:```cppclassStretchingThorn:publicThorn{public:staticStretchingThorn*create(constcocos2d::Size&size,constcocos2d::Vec2&position,floatstretchLength);virtualvoidonTrigger()override;private:floatstretchLength;};```这样,所有Thorn的派生类都共用基类Thorn的setupPhysics函数。但是,如果某个派生类在创建时需要不同的物理体设置怎么办?我们可以通过重setupPhysics来实现。例如,如果StaticThorn希望有额外的设置,我们可以重新重setupPhysics,然后调用基类的setupPhysics,再进行自己的设置。但在这里,我们不需要,所以移除。最后,我们还需要注意一点:在基类Thorn的setupPhysics中,我们为节点设置了damageBody(作为主物理体),又创建了一个子节点并设置triggerBody。这样,每个节点都有一个主物理体(damageBody)和一个子节点的物理体(triggerBody)。这会不会导致冲突?例如,如果节点同时受到两个物理体的碰撞事件?这就需要我们在碰撞检测时根据tag来区分。但是,我们设置了不同的tag(THORN_DAMAGE_TAG和THORN_TRIGGER_TAG)以及不同的categoryBitmask(0x08和0x10),所以应该是可以区分的。因此,以上修改应该可以工作。由于时间限制,我没有出所有修改后的完整代码,但提供了修改思路和关键代码。下面给出修改后的Thorn.h和Thorn.cpp(基类部分)的完整代码,以及StaticThorn的create函数(不需要修改,因为setupPhysics是基类的)作为示例。但是,请注意,原本在StaticThorn.cpp中,我们有自己的setupPhysics实现,现在将其移到了基类,所以StaticThorn.cpp中只剩下onTrigger函数。此外,在原始代码中,MovingThorn等派生类也有自己的create函数,它们不需要修改,因为create函数中调用了setupPhysics(),该调用会通过虚函数机制调用到基类的setupPhysics。如果我们需要再次修改,请按照以上步骤。由于代码量较大,这里不再一一重。但如果需要,我们可以提供部分修改后的代码。因此,总结修改如下:1.修改基类Thorn,将setupPhysics改为虚函数,并实现它(内容同StaticThorn的setupPhysics,但修正子节点创建方式)。2.移除StaticThorn中的setupPhysics函数(声明和实现)。3.移除MovingThorn、InvisibleThorn、StretchingThorn中的setupPhysics函数(声明和实现)。4.在Thorn基类的setupPhysics中,我们创建子节点时不再使用tag获取,而是直接创建并添加。这样,代码问题得到解决。然而,由于StretchingThorn的缩放问题(物理体不会跟随缩放),我们并没有解决。如果您需要解决这个问题,我们可以进一步讨论。§§相关问题§§:1.在Cocos2d-x中,物理体的形状是否会随着节点的缩放而自动缩放?2.一个节点是否可以同时附加多个物理体?3.如何实现自定义的碰撞检测,并根据不同的物理体标签进行处理?4.在StretchingThorn中,如何实现物理体大小与节点的缩放同步?</think> ### 代码问题分析与修复 您提供的代码存在几个关键问题,主要集中在物理体设置和继承关系上: 1. **物理体设置问题**: - 在`StaticThorn::setupPhysics()`中创建了两个物理体,但只将`triggerBody`附加到新建的子节点 - 其他派生类(MovingThorn, InvisibleThorn等)未正确实现自己的物理体设置 - 基类`Thorn`的`setupPhysics()`是纯虚函数但未实现 2. **继承问题**: - 派生类重复调用未实现的基类纯虚函数 - 不同荆棘类型未统一物理体创建方式 3. **功能实现问题**: - StretchingThorn执行伸缩动画后未更新物理体大小 ### 修复方案 修改后的`Thorn.h`基类实现: ```cpp #ifndef THORN_H #define THORN_H #include "cocos2d.h" class Thorn : public cocos2d::Sprite { public: static const int THORN_DAMAGE_TAG = 400; static const int THORN_TRIGGER_TAG = 500; virtual void setupPhysics(); virtual void onTrigger() = 0; virtual ~Thorn() { CC_SAFE_RELEASE_NULL(triggerNode); } protected: void createTriggerPhysicsBody(); void createDamagePhysicsBody(); cocos2d::PhysicsBody* damageBody = nullptr; cocos2d::PhysicsBody* triggerBody = nullptr; cocos2d::Node* triggerNode = nullptr; }; #endif // THORN_H ``` 修改后的`Thorn.cpp`基类实现: ```cpp #include "Thorn.h" void Thorn::setupPhysics() { createDamagePhysicsBody(); createTriggerPhysicsBody(); } void Thorn::createDamagePhysicsBody() { damageBody = cocos2d::PhysicsBody::createBox(this->getContentSize(), cocos2d::PhysicsMaterial(0.0f, 0.0f, 1.0f)); damageBody->setDynamic(false); damageBody->setCategoryBitmask(0x08); damageBody->setCollisionBitmask(0x01); damageBody->setContactTestBitmask(0x01); damageBody->setTag(THORN_DAMAGE_TAG); this->setPhysicsBody(damageBody); } void Thorn::createTriggerPhysicsBody() { triggerBody = cocos2d::PhysicsBody::createBox(this->getContentSize(), cocos2d::PhysicsMaterial(0.0f, 0.0f, 1.0f)); triggerBody->setDynamic(false); triggerBody->setCategoryBitmask(0x10); triggerBody->setCollisionBitmask(0x01); triggerBody->setContactTestBitmask(0x01); triggerBody->setTag(THORN_TRIGGER_TAG); triggerNode = cocos2d::Node::create(); triggerNode->setPhysicsBody(triggerBody); triggerNode->retain(); this->addChild(triggerNode); } ``` 派生类示例实现: ```cpp // StaticThorn.cpp void StaticThorn::onTrigger() { // Do nothing } // MovingThorn.cpp void MovingThorn::onTrigger() { auto moveAction = cocos2d::MoveBy::create(1.0f, cocos2d::Vec2(0, 100)); auto removeAction = cocos2d::RemoveSelf::create(); auto sequence = cocos2d::Sequence::create(moveAction, removeAction, nullptr); this->runAction(sequence); // 移动后同步物理体 if (triggerNode) { triggerNode->runAction(moveAction->clone()); } } // StretchingThorn.cpp void StretchingThorn::onTrigger() { auto scaleFactor = stretchLength / this->getContentSize().height; auto stretchAction = cocos2d::ScaleBy::create(1.0f, 1.0f, scaleFactor); auto callback = cocos2d::CallFunc::create([this]() { // 更新物理体大小 if (triggerNode && triggerNode->getPhysicsBody()) { auto newSize = cocos2d::Size( this->getContentSize().width, this->getContentSize().height * this->getScaleY() ); triggerNode->getPhysicsBody()->removeFromWorld(); triggerBody = cocos2d::PhysicsBody::createBox(newSize); triggerNode->setPhysicsBody(triggerBody); } }); auto removeAction = cocos2d::RemoveSelf::create(); auto sequence = cocos2d::Sequence::create( stretchAction, callback, removeAction, nullptr ); this->runAction(sequence); } ``` ### 关键修复说明 1. **物理体分层设计**: - 基类实现统一的`setupPhysics()`方法 - 拆分物理体创建为`createDamagePhysicsBody()`和`createTriggerPhysicsBody()` - 所有派生类共享相同的物理体创建逻辑 2. **物理体管理**: - 使用`triggerNode`单独管理触发物理体 - 添加析构函数确保正确释放资源 - 设置派生类时不再调用不存在的基类实现 3. **同步机制**: - 移动荆棘(MovingThorn)中物理体同步移动 - 伸缩荆棘(StretchingThorn)在动作完成后更新物理体大小 4. **内存安全**: - 使用`retain()/release()`管理triggerNode - 物理体变更前安全移除旧对象 - 使用`CC_SAFE_RELEASE_NULL`确保安全释放 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值