Hey,
I have been trying to learn cocos using VS and C++. Recently, I have encountered an issue while trying to run an animation when a key on the keyboard is pressed. The code:
Vector<SpriteFrame*> frames = getAnimation("player%d.png", 3);
_player = Sprite::createWithSpriteFrame(frames.front());
this->addChild(_player);
_player->setPosition(x, y);
animation = Animation::createWithSpriteFrames(frames, 1.0f / 3);
auto action = RepeatForever::create(Animate::create(animation));
auto eventListener = EventListenerKeyboard::create();
eventListener->onKeyPressed = [&](EventKeyboard::KeyCode keyCode, Event* event) {
_player->runAction(action);
};
After compiling this code and executing it, I get this error when I press a key:
Exception thrown: write access violation.
this was 0x10400A62.
And VS leads me to this line in CCRef.cpp file
void Ref::retain()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
++_referenceCount; // <---- this line
Though, if I put the runAction function outside the onKeyPressed lambda, (in the init function for example) everything works fine and the animation executes.
It’s probably something quite obvious, but since I am fairly new I hope to get some insight on what is exactly happening behind the scenes and why there is an error, how should I fix it.
Thanks.
Hi.
The lambda function know nothing about _player.
If you need access to _player try this:
_player->setName("player");
//and in the lamda function:
// target means "this" in this-> addChild(_player) in your code.
auto target = static_cast<Sprite*>(event->getCurrentTarget());
// now you can access player via target.
auto player = static_cast<Sprite*>(getChildByName("player"));
player->runAction(action);
If you want to know more about lambda function in cocos2d-x then check cpp-tests, newEventDispatcherTest.
Definitely heed admin13a’s advice.
I think your actual error is due to the action variable going out of scope and getting released before the onKeyPressed event is called.
Change your lambda to capture by value: onKeyPressed = [=]
Still no luck guys. Tried both solutions. My current code:
Vector<SpriteFrame*> frames = getAnimation("player%d.png", 3);
_player = Sprite::createWithSpriteFrame(frames.front());
this->addChild(_player);
_player->setPosition(x, y);
_player->setName("player");
animation = Animation::createWithSpriteFrames(frames, 1.0f / 3);
auto eventListener = EventListenerKeyboard::create();
eventListener->onKeyPressed = [=](EventKeyboard::KeyCode keyCode, Event* event) {
auto target = static_cast<Sprite*>(event->getCurrentTarget());
auto player = static_cast<Sprite*>(target->getChildByName("player"));
player->runAction(RepeatForever::create(Animate::create(animation)));
};
this->_eventDispatcher->addEventListenerWithSceneGraphPriority(eventListener, this);
Although, now I get a different error:
Exception thrown: read access violation.
_Parent_proxy was 0xDDDDDDDD.
And VS sends me to the “xutility” file’s code:
if (_Myproxy != _Parent_proxy)
{ // change parentage
_Lockit _Lock(_LOCK_DEBUG);
_Orphan_me();
_Mynextiter = _Parent_proxy->_Myfirstiter; // <-- this line
_Parent_proxy->_Myfirstiter = this;
_Myproxy = _Parent_proxy;
}
You don’t need [=] in lambda function. Use [] instead. (not deferent result)
I think this error means your parent (this) is not a “sprite” at all.
try cast it by Node*.
Tried using [] but then the compiler gives me an error saying “animation needs to be in the capture list”. I casted target to node and still doesn’t work… My current code:
Vector<SpriteFrame*> frames = getAnimation("player%d.png", 3);
_player = Sprite::createWithSpriteFrame(frames.front());
this->addChild(_player);
_player->setPosition(x, y);
_player->setName("player");
animation = Animation::createWithSpriteFrames(frames, 1.0f / 3);
auto eventListener = EventListenerKeyboard::create();
eventListener->onKeyPressed = [=](EventKeyboard::KeyCode keyCode, Event* event) {
// target means "this" in this-> addChild(_player) in your code.
auto target = static_cast<Node*>(event->getCurrentTarget());
// now you can access player via target.
auto player = static_cast<Sprite*>(target->getChildByName("player"));
player->runAction(RepeatForever::create(Animate::create(animation)));
};
Could there be a problem with the animation pointer?
Indeed, I tried a different action and it worked so now I know there’s a problem with the animation pointer, but still don’t know what it is. Here’s the full code for it:
Vector<SpriteFrame*> frames = getAnimation("player%d.png", 3);
animation = Animation::createWithSpriteFrames(frames, 1.0f / 3);
cocos2d::Vector<SpriteFrame*> CastleWalkScene::getAnimation(const char * format, int count)
{
auto spritecache = SpriteFrameCache::getInstance();
Vector<SpriteFrame*> animFrames;
char str[100];
for (int i = 1; i <= count; i++)
{
sprintf(str, format, i);
animFrames.pushBack(spritecache->getSpriteFrameByName(str));
}
return animFrames;
}
Could it be that animation goes out of scope in the lambda? Or something like that?
action will be released before onKeyPressed is called as stevetranby said, but I think the solution is to use RefPtr
...
RefPtr<RepeatForever> action = RepeatForever::create(Animate::create(animation));
auto eventListener = EventListenerKeyboard::create();
eventListener->onKeyPressed = [=](EventKeyboard::KeyCode keyCode, Event* event) {
_player->runAction(action->clone()); // Use clone to avoid adding the same action twice.
};
Is there a reason you’re creating your action outside of the keypress? It looks like you want a new action every time it fires. You’re probably better off storing the animation in the animation cache and then referencing it in the event callback.
AnimationCache::getInstance()->addAnimation(animation, "my_animation_key");
eventListener->onKeyPressed = [] (...) {
auto anim = AnimationCache::getInstance()->getAnimation("my_animation_key");
auto action = Animate::create(anim);
player->runAction(RepeatForever::create(action));
}
You can also just retain it as a class member. Then you can use by reference [&] since it doesn’t become invalid pointer before the lambda executes.
_action = MyAction::create(...); // _action is member of this' class
_action->retain(); // should release in this's Class destructor
eventListener->onKeyPressed = [&](...) { _player->runAction(_action); };
Thank you guys for the help!