onKeyPressed animation exception, help

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!