EventListener for buttons - c++

I'm developing a 2D car game using Cocos2d-x and I have a problem.
I want to use Box2D to make collisions and movement managing easier, but before that I want to get my 4 buttons working.
The point is that I have something like this
RaceScene.cpp
bool Race::init() {
//...
auto forwardArrow = MenuItemImage::create("forward_arrow.png", "forward_arrow.png", CC_CALLBACK_1(Race::forward, this));
forwardArrow->setAnchorPoint(Vec2(0,0));
forwardArrow->setPosition(Vec2(origin.x + visibleSize.width - forwardArrow->getContentSize().width*1.2 ,
origin.y + forwardArrow->getContentSize().height));
//...
}
void Race::forward(Ref* pSender)
{
/*MessageBox("YOU PRESSED THE FORWARD BUTTON","Alert");*/
}
But I've noticed this is not the way because I can't neither press more than 1 button at a time nor change speed according to the time the button is pressed.
So I've googled and checked Cocos documentation, and everybody talks about that EventListeners but I don't find how to add it to my problem.
I don't need to handle screen touches, I need to handle EACH button touch.
Thanks.

So, after some hours of researching and trying, as well as receiving some help from Cocos2d-x forums, I got the solution.
RaceScene.cpp
bool Race::init() {
...
forwardArrow = Sprite::create("forward_arrow.png");
forwardArrow->setAnchorPoint(Vec2(0,0));
forwardArrow->setPosition(Vec2(origin.x + visibleSize.width - forwardArrow->getContentSize().width*1.2 ,
origin.y + forwardArrow->getContentSize().height));
this->addChild(forwardArrow);
...
auto listenerForwardButton = EventListenerTouchOneByOne::create();
listenerForwardButton->setEnabled(true);
listenerForwardButton->setSwallowTouches(true);
listenerForwardButton->onTouchBegan = CC_CALLBACK_2(Race::onTouchesForwardBegan, this);
// trigger when you let up
listenerForwardButton->onTouchEnded = CC_CALLBACK_2(Race::onTouchesForwardEnded, this);
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, forwardArrow);
}
bool Race::onTouchesForwardBegan(cocos2d::Touch* touch, cocos2d::Event* event)
{
Vec2 touchPoint = touch->getLocation();
Rect boundingBoxArrow = this->forwardArrow->getBoundingBox();
if (boundingBoxArrow.containsPoint(touchPoint)){
return true;
}
return false;
}
void Race::onTouchesForwardEnded(cocos2d::Touch* touch, cocos2d::Event* event)
{
cocos2d::log("You touched %f, %f", touch->getLocationInView().x, touch->getLocationInView().y);
MessageBox("YOU PRESSED THE FORWARD BUTTON","Alert");
auto forward = MoveBy::create(1,Vec2(-5,0));
mainSprite->runAction(forward);
}
RaceScene.h
class Race : public cocos2d::Layer {
public:
virtual bool init();
cocos2d::Sprite* forwardArrow;
...
private:
bool onTouchesForwardBegan(cocos2d::Touch* touch, cocos2d::Event* event);
void onTouchesForwardEnded(cocos2d::Touch* touch, cocos2d::Event* event);
...
};
Please note that you need a listener->onTouchBegan which CC_CALLBACK_X redirects to a bool function. When this one returns true, then the onTouchEnd function works, it doesn't when this one returns false.
Hope it helps someone.

Related

Unreal Engine 4: C++ Delegate not being called

I've been working on converting some blueprint logic over to C++. One of the things I have is a button. The button can be pressed in VR and has a delegate that is called to notify any registered functions that the button press occurred. Here is how the delegate is declared in the AButtonItem.h class.
#pragma once
#include "BaseItem.h"
#include "ButtonItem.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FButtonItemPressedSignatrue);
UCLASS()
class AButtonItem : public ABaseItem
{
GENERATED_BODY()
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Touch)
float myMaxButtonPress;
public:
UPROPERTY(EditAnywhere, Category = Callback)
FButtonItemPressedSignatrue ButtonItem_OnPressed;
};
The delegate's broadcast function is then being called when the button is pressed like so:
ButtonItem_OnPressed.Broadcast();
(This function should defiantly be called because I have a debug statement that prints right before the call. Its also important to note this was all working when it was blueprint logic.)
Here is where I try to register with the delegate and how I declared the function that will be called:
WeaponMaker.h:
UFUNCTION()
void OnNextBladeButtonPressed();
WeaponMaker.cpp:
void AWeaponMaker::BeginPlay()
{
Super::BeginPlay();
TArray<USceneComponent*> weaponMakerComponents;
this->GetRootComponent()->GetChildrenComponents(true, weaponMakerComponents);
for (int componentIndex = 0; componentIndex < weaponMakerComponents.Num(); componentIndex++)
{
if (weaponMakerComponents[componentIndex]->GetName().Equals("NextBladeButton") == true)
{
myNextBladeButton = (AButtonItem*)weaponMakerComponents[componentIndex];
break;
}
}
if (myNextBladeButton != NULL)
{
myNextBladeButton->ButtonItem_OnPressed.AddDynamic(this, &AWeaponMaker::OnNextBladeButtonPressed);
}
}
I put a breakpoint and a print statement in the function OnNextBladeButtonPressed so I should immediately know when it works but its never happening. I also re-created the blueprint itself from scratch but still no luck. Sometimes on compile I get a crash due to the InvocationList being invalid but I haven't found much info on that issue either. Bottom line is, OnNextBladeButtonPressed is not getting called when it should be.
Edit: Here is where I call the broadcast function in my AButtonItem code. It seems to be getting called since i see the UE_LOG output in the console:
void AButtonItem::Tick(float deltaTime)
{
FTransform buttonWorldTransform;
FVector buttonLocalSpacePos;
FVector ownerLocalSpacePos;
FVector localDiff;
float buttonPressAmount;
if (myHasStarted == true)
{
Super::Tick(deltaTime);
if (myButtonComponent != NULL)
{
if (myPrimaryHand != NULL)
{
//Get the world space location of the button.
buttonWorldTransform = myButtonComponent->GetComponentTransform();
//Convert the location of the button and the location of the hand to local space.
buttonLocalSpacePos = buttonWorldTransform.InverseTransformPosition(myInitialOverlapPosition);
ownerLocalSpacePos = buttonWorldTransform.InverseTransformPosition(myPrimaryHand->GetControllerLocation() + (myPrimaryHand->GetControllerRotation().Vector() * myPrimaryHand->GetReachDistance()));
//Vector distance between button and hand in local space.
localDiff = ownerLocalSpacePos - buttonLocalSpacePos;
//Only interested in the z value difference.
buttonPressAmount = FMath::Clamp(FMath::Abs(localDiff.Z), 0.0f, myMaxButtonPress);
localDiff.Set(0.0f, 0.0f, buttonPressAmount);
//Set the new relative position of button based on the hand and the start button position.
myButtonComponent->SetRelativeLocation(myButtonInitialPosition - localDiff);
//UE_LOG(LogTemp, Error, TEXT("buttonPressAmount:%f"), buttonPressAmount);
if (buttonPressAmount >= myMaxButtonPress)
{
if (myHasBeenTouchedOnce == false)
{
//Fire button pressed delegate
if (ButtonItem_OnPressed.IsBound() == true)
{
ButtonItem_OnPressed.Broadcast();
AsyncTask(ENamedThreads::GameThread, [=]()
{
ButtonItem_OnPressed.Broadcast();
});
}
myHasBeenTouchedOnce = true;
myButtonComponent->SetScalarParameterValueOnMaterials("State", 1.0f);
Super::VibrateTouchingHands(EVibrationType::VE_TOUCH);
}
}
}
else
{
//Slowly reset the button position back to the initial position when not being touched.
FVector newPosition = FMath::VInterpTo(myButtonComponent->GetRelativeTransform().GetLocation(), myButtonInitialPosition, deltaTime, 10.0f);
myButtonComponent->SetRelativeLocation(newPosition);
}
}
}
}
First of all:
UPROPERTY(EditAnywhere, Category = Callback)
FButtonItemPressedSignatrue ButtonItem_OnPressed;
This should be:
UPROPERTY(BlueprintAssignable, Category = Callback)
FButtonItemPressedSignatrue ButtonItem_OnPressed;
For convenience.
Secondly the tick function may be called before begin play is executed for a number of reasons. Your even't won't be broadcasted if the game hasn't begin play yet. So to avoid just add a check in your tick function.
if(bHasBegunPlay)
{
// .. your logics ...
}
Sometimes on compile I get a crash due to the InvocationList being invalid but I haven't found much info on that issue either. Bottom line is, OnNextBladeButtonPressed is not getting called when it should be.
I don't see any issue in the code from the question. At my glance, the issue could be in different location. I would suspect that AWeaponMaker had been deleted at moment of broadcasting.

What does the return bool mean in onTouchBegan() of cocos2dx

I find following code will only work with onTouchBegan() and not onTouchMoved() and onTouchEnded()
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = [=](Touch *touch, Event *event) {
CCLOG("on touch begain at (%f,%f)", touch->getLocation().x, touch->getLocation().y);
return false; // this will make following two events couldn't be fired.
};
listener->onTouchMoved = [=](Touch *touch, Event *event) {
CCLOG("on touch moved at (%f, %f)", touch->getLocation().x, touch->getLocation().y);
};
listener->onTouchEnded = [=](Touch *touch, Event * event) {
CCLOG("on touch ended at (%f,%f)", touch->getLocation().x, touch->getLocation().y);
};
_eventDispatcher->addEventListenerWithFixedPriority(listener, 1);
The reason why this happens is that the onTouchBegan assigned to a lambda function that returns false after changing this to true, onTouchMoved and onTouchEnded will be triggered as expected.
I searched around could find any explanation about what does this return flags intended to do? could some one help to explain that?
The documentation does not tell this, but the programmers-guide does:
// trigger when you push down
listener1->onTouchBegan = [](Touch* touch, Event* event)
{
// your code
return true; // if you are consuming it
};
So with the bool you can tell the system if you want to handle the touch event or not.
Also: you don't need any captures in your lambdas so better use [] instead of [=]

How to use execute stored animation in Cocos2d?

I am fairly new to Cocos2d-x v3 and Recently I was attempting to use the listener key press functionality in order to get my Sprite to move with the animations I have created for it. All the code Compiles with no error, but when the game runs if I press the specified Key in the switch case the window is suspend and it brings me to the action.h header file and highlights the "void setTarget" method of the class where it states error "this was a nullptr" perhaps I forgot to initialize a variable somewhere?
My header looks like so:
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
using namespace cocos2d;
class HelloWorld : public cocos2d::Layer
{
private:
Sprite* sarah;
Animate* walking;
Action* action;
public:
static cocos2d::Scene* createScene();
virtual bool init();
void menuCloseCallback(cocos2d::Ref* pSender);
CREATE_FUNC(HelloWorld);
void onKeyPressed(EventKeyboard::KeyCode keyCode, Event *eventer);
void onKeyReleased(EventKeyboard::KeyCode keyCode, Event *eventer);
Sprite* GetSprite();
Animate* GetAnimation();
}
#endif // __HELLOWORLD_SCENE_H__
and the part in my cpp that is causing me problems looks like so:
void HelloWorld::onKeyPressed(EventKeyboard::KeyCode keyCode, Event * event){
auto action1 = event->getCurrentTarget()->getActionByTag(1);
auto node = event->getCurrentTarget();
switch (keyCode){
case EventKeyboard::KeyCode::KEY_LEFT_ARROW:
action1->setTarget(node);
node->runAction(action1);
default:
break;
}
}
void HelloWorld::onKeyReleased(EventKeyboard::KeyCode keyCode,Event *event) {
auto action1 = event->getCurrentTarget()->getActionByTag(1);
auto node = event->getCurrentTarget();
Vec2 loc = event->getCurrentTarget()->getPosition();
switch (keyCode){
case EventKeyboard::KeyCode::KEY_UP_ARROW:
action1->getTarget()->stopActionByTag(1);
node->setPosition(--loc.x, --loc.y);
default:
break;
}
}
sarah = Sprite::create("standing.png");
sarah->setAnchorPoint(Vec2(0, 0));
sarah->setPosition(100, 100);
Vector<SpriteFrame*> walkingframeskleft;
walkingframeskleft.reserve(3);
walkingframeskleft.pushBack(SpriteFrame::create("walk2.png", Rect(0, 0, 65, 81)));
walkingframeskleft.pushBack(SpriteFrame::create("walk3.png", Rect(0, 0, 65, 81)));
walkingframeskleft.pushBack(SpriteFrame::create("walk4.png", Rect(0, 0, 65, 81)));
Animation* walkinganimation = Animation::createWithSpriteFrames(walkingframeskleft, .1f);
walking = Animate::create(walkinganimation);
action = RepeatForever::create(walking);
action->setTag(1);
this->addChild(sarah);
auto listener = EventListenerKeyboard::create();
listener->onKeyPressed = CC_CALLBACK_2(HelloWorld::onKeyPressed,this);
listener->onKeyReleased = CC_CALLBACK_2(HelloWorld::onKeyReleased,this);
this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, sarah);
return true;
}
in my switch cases i only have the left arrow set up since i just wanted to test if a key would work to begin with
As far as I can tell, your error is caused by the fact that your action is null at the moment when you try to run it.
That is because:
After you create your action with this line:
action = RepeatForever::create(walking);
The action gets added to the autorelease pool. So, if you don't use it immediately(run it on a sprite) action->release(); will be called on it which will delete it. So, when you are creating actions that you will want to use later(or reuse) in your code, be sure to manually retain them after creation and release them when you are sure you won't use them again:
action = RepeatForever::create(walking);
action->retain();
This way your action won't be released(deleted) until you call release() yourself.

Moving player using keyboard listeners

I've got a player class, having MoveUp, MoveLeft and MoveRight functions.
At MainScene.cpp (my only scene so far), I've got a listener
auto keyboardListener = EventListenerKeyboard::create();
keyboardListener->onKeyPressed = CC_CALLBACK_2(MainScene::keyPressed, this);
keyboardListener->onKeyReleased = CC_CALLBACK_2(MainScene::keyReleased, this);
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(keyboardListener, this);
Also, I've got a pointer player, defined by
this->player = rootNode->getChildByName<Player*>("Player1");
Also, I've got a function keyPressed and keyReleased in MainScene.
In keyPressed function, I was scheduling functions with if's:
if (keyCode == cocos2d::EventKeyboard::KeyCode::KEY_D) { schedule(schedule_selector(MainScene::MoveRight)); }
But there was a problem, when I tried to do the same thing with objects instead of sprites and with functions located in separated class, not within the same file.
If I try to run code like this in MainScene::keyPressed:
if (keyCode == cocos2d::EventKeyboard::KeyCode::KEY_W) { this->player->MoveLeft(5); }
, player moves only once per key press (I want it to move till I release that key), and if I try to schedule it or do something like this, it doesn't work or there are errors.
I tried to make something with CallFunc and CCCallFunc, but nothing seems to work.
Could you please help me? :)
Why not create a method startMoving() and stopMoving() in Player class?
Something like this:
void Player::init(){
scheduleUpdate()
}
void Play::startMoving(){
isMoving = true;
}
void Player::stopMoving(){
isMoving = false;
}
void Player::update(float delta){
if(isMoving){
//move player here
sprite->setPositionX(sprite->getPositionX() + speed * delta);
}
}
and then call them from keyPressed/keyReleased?

CCEventListener.cpp - Assertion failed when trying to implement touch functionality

I'm fiddling around with Cocos2D-X and trying to implement touch functionality as per this wiki entry. However, when I try to run my code, it launches and pops up a dialog saying "Assertion failed!" giving a reason "CCEventListenerTouch.cpp, Line:89, Expression: false".
Here's my code related to this:
bool MainLayer::init()
{
Sprite* tile = Sprite::create("tile.png");
this->addChild(tile);
auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);
listener->onTouchMoved = [](Touch* touch, Event* event)
{
tile->setPosition(tile->getPosition() + touch->getDelta());
};
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, tile);
return true;
}
If I uncomment the line before the returnstatement, it works without crashing, but the touches are not working.
It seems that you actually have to implement all the onTouch* callbacks. I added these two and it started working.
listener->onTouchBegan = [&](Touch* touch, Event* event)
{
return true;
};
listener->onTouchEnded = [=](Touch* touch, Event* event)
{
};