I have a problem with cocos2d-x touch handler. I have two classes. Each class has one scene and one layer.I have sprites on both layers, and they handle in the same function. The problem is when I make the transition from layer A to layer B and click on the sprite, so touch handler gets sprite from layer A, but I need sprite from layer B, obviously that I click on layer B. As far as I understood, the problem is in setPriority? Could you please help me solve this problem? Thanks
CardItem is my sprite
void CardItem::addEvents()
{
auto listener = cocos2d::EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);
listener->onTouchBegan = [&](cocos2d::Touch* touch, cocos2d::Event* event)
{
cocos2d::Vec2 p = touch->getLocation();
cocos2d::Rect rect = this->getBoundingBox();
if(rect.containsPoint(p))
{
return true; // to indicate that we have consumed it.
}
return false; // we did not consume this event, pass thru.
};
listener->onTouchEnded = [=](cocos2d::Touch* touch, cocos2d::Event* event)
{
//I have here some code
};
cocos2d::Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(listener, 30);
}
void CardItem::touchEvent(cocos2d::Touch* touch, cocos2d::Vec2 _point)
{
auto *pNode = this->getParent(); // here is "pNode" always gets layer A
}
that's how I change scenes beetween layer A and B
void GameBoardLayer::showGameBoardComputer()
{
pGameBoardComputerScene = GameBoardComputerScene::create();
auto pGameBoardComputerLayer = pGameBoardComputerScene->getLayer();
Director::getInstance()->pushScene(pGameBoardComputerScene);
}
void GameBoardComputerLayer::gameBoardComputerItemBackCallback(Ref* pSender)
{
Director::getInstance()->popScene();
}
so when I click on sprite no matter what layer it is, it always clicks on layer A through layer B
Related
I write desktop application that works with map,
and I want to react on pan and long press events.
It is possible to use QGestureEvent on Qt/Linux/X11 with ordinary mouse?
I took Qt gesture example, it works on tablet,
but not reaction on press left mouse button and move (I expect that application recognizes it as tap or swipe event).
Then I added to Qt gesture example app.setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents, true); at main and such code to imagewidget.cpp:
void ImageWidget::mousePressEvent(QMouseEvent *e)
{
e->ignore();
}
void ImageWidget::mouseReleaseEvent(QMouseEvent *e)
{
e->ignore();
}
void ImageWidget::mouseMoveEvent(QMouseEvent *e)
{
e->ignore();
}
this code still works on tablet, but again no reaction on mouse on Linux/X11.
Any way to enable qgesture on linux/x11, should I write my own gesture recognition
for mouse?
The official way to make gestures out of mouse events in Qt is deriving from the QGestureRecognizer class, which allows to listen to relevant mouse events, set gesture properties accordingly, then trigger the gesture (or cancel it).
Here follows an example for pan gestures only, just to give an idea of what has to be done.
Have a QGestureRecognizer subclass like this:
#include <QGestureRecognizer>
#include <QPointF>
class PanGestureRecognizer : public QGestureRecognizer
{
QPointF startpoint;
bool panning;
public:
PanGestureRecognizer() : panning(false){}
QGesture *create(QObject *target);
Result recognize(QGesture *state, QObject *watched, QEvent *event);
};
The create method has been overridden to return a new instance of our gesture of interest:
QGesture *PanGestureRecognizer::create(QObject *target)
{
return new QPanGesture();
}
The recognize method override is the core of our recognizer class, where events are passed in, gesture properties set, gesture events triggered:
QGestureRecognizer::Result PanGestureRecognizer::recognize(QGesture *state, QObject *, QEvent *event)
{
QMouseEvent * mouse = dynamic_cast<QMouseEvent*>(event);
if(mouse != 0)
{
if(mouse->type() == QMouseEvent::MouseButtonPress)
{
QPanGesture * gesture = dynamic_cast<QPanGesture*>(state);
if(gesture != 0)
{
panning = true;
startpoint = mouse->pos();
gesture->setLastOffset(QPointF());
gesture->setOffset(QPointF());
return TriggerGesture;
}
}
if(panning && (mouse->type() == QMouseEvent::MouseMove))
{
QPanGesture * gesture = dynamic_cast<QPanGesture*>(state);
if(gesture != 0)
{
gesture->setLastOffset(gesture->offset());
gesture->setOffset(mouse->pos() - startpoint);
return TriggerGesture;
}
}
if(mouse->type() == QMouseEvent::MouseButtonRelease)
{
QPanGesture * gesture = dynamic_cast<QPanGesture*>(state);
if(gesture != 0)
{
QPointF endpoint = mouse->pos();
if(startpoint == endpoint)
{
return CancelGesture;
}
panning = false;
gesture->setLastOffset(gesture->offset());
gesture->setOffset(mouse->pos() - startpoint);
return FinishGesture;
}
}
if(mouse->type() == QMouseEvent::MouseButtonDblClick)
{
panning = false;
return CancelGesture;
}
return Ignore;
}
}
Basically, we track mouse events, updating a couple of properties of our own (panning and startpoint) and the passed in gesture properties as well. For each mouse event type, we also return a QGestureRecognizer::Result . All other events are discarded (the method returns Ignore).
This code can be tested with the Image Gestures Example, though: just add the class to the project and this line in the ImageWidget constructor:
QGestureRecognizer::registerRecognizer(new PanGestureRecognizer());
This should let the user grab the picture and move it around, using a mouse.
Look into this image widget gestures example. (search for mouseDoubleClickEvent)
http://doc.qt.io/qt-5/qtwidgets-gestures-imagegestures-imagewidget-cpp.html
Based on that you need to reimplement the required mouse events.
MyWidget::MyWidget() {
---
---
}
bool MyWidget::event(QEvent *ev)
{
---
---
}
void MyWidget::mouseReleaseEvent(QMouseEvent *event)
{
}
void MyWidget::mouseMoveEvent(QMouseEvent *event)
{
}
And declare those two functions in header
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
i want to detect which sprite has been touched.
if I do :
auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);
listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this);
listener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener->clone(), mySprite);
and then in my touch method I do:
bool HelloWorld::onTouchBegan(Touch* touch, Event* event)
auto spriteBlock = static_cast<Block*>(event->getCurrentTarget());
the sprite is detected fine.
the problem is I have like 20 sprites on the layer and I need to be able to detect them all
do I need to set
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener->clone(), mySprite);
for each sprite?
No, you do not need add eventListener to all sprites.
You need an event listener for the parent node of the sprites.
Try this:
bool HelloWorld::onTouchBegan(Touch *touch, Event *event) {
Node *parentNode = event->getCurrentTarget();
Vector<Node *> children = parentNode->getChildren();
Point touchPosition = parentNode->convertTouchToNodeSpace(touch);
for (auto iter = children.rbegin(); iter != children.rend(); ++iter) {
Node *childNode = *iter;
if (childNode->getBoundingBox().containsPoint(touchPosition)) {
//childNode is the touched Node
//do the stuff here
return true;
}
}
return false;
}
It is iterate in reverse order because you will touch the sprite with the highest z-index in place (if they overlapping).
Hope, this help.
Yes You have to add an eventlistener to each sprite
and to detect the touches on each
you have to add this in the touch began function
bool OwlStoryScene0::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *event) {
cocos2d::Touch *touched = (Touch *) touch;
Point location = touched->getLocationInView();
location = Director::getInstance()->convertToGL(location);
auto target = static_cast<Sprite*>(event->getCurrentTarget());
Point locationInNode = target->convertToNodeSpace(touch->getLocation());
Size s = target->getContentSize();
Rect rect = Rect(0, 0, s.width, s.height);
if (rect.containsPoint(locationInNode) && target == SPRITE) {
//DoSomething
}
else if (rect.containsPoint(locationInNode) && target == SPRITE) {
//DoSomething
}
return false;
}
Hope this works for you
I am stuck, I need help, I can't post all files here because this is a project with almost 20 files of c++. But it basically all boils down to movements on a screen.
-The class "MyClass" is the screen, it has a constructor which calls the functions. the screen has a small-box at the bottom. when you press the mouse down,up, or move it, it sends a signal or 'Event" and one of the three functions below is called;
-function-mouseMotion, it should move the small-box to where your mouse is pointing when you are moving the mouse, sort of drag. this function should be active whenever you press down your mouse, say whenever mouse is down
-function-mouseButtonDown, when u press your mouse down, the small-box should move from the bottom of screen to where your cursor is.
-function-mouseButtonUp, when you unclick your mouse, this function will be called and all the movements of small box should stop.
Right now as the code is, whenever I bring the ouse to the screen, the small box immediately jumps and starts following the mouse. But our professor wants us to change the functions such that when I introduce the moue on the screen, nothing should happen. When I click down the mouse, the box should then come to where the mouse is. If I click and hold the mouse down as I move it, the box should keep following the mouse. Finally, if i unclick the mouse, the box needs to remain where i unclicked it from.
What alograithm would you use, am new to c++
using namespace WALY;
class MyClass : public Frame {
private:
void(*switchAction) (void);
Frame* box;
static void mouseButtonDown (Frame* f, const Event* e);
static void mouseButtonUp (Frame* f, const Event* e);
static void mouseMotion (Frame* f, const Event* e);
public:
MyClass (Frame* parent, void (*switchFunc) (void));
void activate (void);
void deactivate (void);
};
MyClass::MyClass(Frame *parent, void (*switchFunc)(void)) : Frame (parent), switchAction (switchFunc)
{
box = new Frame (this, 250, 700); // creating the box on the screen
box->setAlign (ALIGN_C);
Rect boxRect;
boxRect.x = boxRect.y = 0;
boxRect.w = 80;
boxRect.h = 50;
box->setScrollRect (boxRect);
box->useSolidBackground (0x808080);
setCallbackFunc (MOUSE_DOWN, mouseButtonDown); // registering functions into wally library
setCallbackFunc (MOUSE_UP, mouseButtonUp);
setCallbackFunc (MOUSE_MOTION, mouseMotion);
deactivate ();
}
void MyClass::activate(void)
{
setActive(true);
setVisible(true);
}
void MyClass::deactivate(void)
{
setVisible(false);
setActive(false);
}
void
MyClass::mouseMotion (Frame* f, const Event* e)
{
MyClass* wywtcyc = (MyClass*)f;
wywtcyc->box->setX (e->motion.x);
wywtcyc->box->setY (e->motion.y);
}
void MyClass::mouseButtonDown (Frame* f, const Event* e)
{
// (e->button.x)
}
void MyClass::mouseButtonUp (Frame* f, const Event* e)
{
}
The logic could be like below:
Declare variables:
mouseIsDown = False;
released = False;
In mouseMotion:
if (mouseIsDown && ! released) {
// your current code
}
In mouseButtonDown:
if (mouseIsDown) {
released = True;
} else {
mouseIsDown = True;
released = False;
}
In mouseButtonUp:
if (mouseIsDown) {
mouseIsDown = False;
released = True;
}
I've created a label and used setPixmap to attach a .png image to the label. I've also setWindowFlags to disable the title bar and create a frameless window. Because I've disabled those, it also disables the ability to drag anything around, so I want to create mouseevents (unless there's a better method) to position my label anywhere on the screen exactly like dragging the frame of a window. How would I do that? An example and brief explanation would be greatly appreciated.
reimplement the QMouseEvents you need .... like
void MyLabel::mousePressEvent(QMouseEvent* e)
{
m_moveDatWidget = true;
// so when the mousebutton got pressed, you set something to
// tell your code to move the widget ... consider maybe you
// want to move it only on right button pressed ...
}
void MyLabel::mouseReleaseEvent(QMouseEvent* e)
{
m_moveDatWidget = false;
// after releasing do not forget to reset the movement variable
}
void MyLabel::mouseMoveEvent(QMouseEvent* e)
{
// when in 'moving state' ...
if (m_moveDatWidget)
{
// move your widget around using QWidget::move(qreal,qreal)
}
}
this is only a really basic implementation but it should do well if you calculate the desired movement correct :)
I would implement the label dragging by mouse in the following way:
class Label : public QLabel
{
public:
// Implement the constructor(s)
protected:
void Label::mouseMoveEvent(QMouseEvent* event)
{
if (!m_offset.isNull()) {
move(event->globalPos() - m_offset);
}
QLabel::mouseMoveEvent(event);
}
void Label::mousePressEvent(QMouseEvent* event)
{
// Get the mouse offset in the label's coordinates system.
m_offset = event->globalPos() - pos();
QLabel::mousePressEvent(event);
}
void Notifier::mouseReleaseEvent(QMouseEvent* event)
{
m_offset = QPoint();
QLabel::mouseReleaseEvent(event);
}
private:
// The mouse pointer offset from the top left corner of the label.
QPoint m_offset;
};
UPDATE i changed to more simpler example
Ok , now i really puzzled i simplified the class as as i was reading on the net the suggestion was to extend CCNode better then CCSprite
and keep it as member of the CCNode
so here is the very simpleminded example based on the Hello cpp.
the problem remine the same , when touching any of the Gem instances i get printed the last Gem added , why ??
i expect that each Touchwill give me the correct instance that have bean touched ( i print id and name )
Im using cocos2d-2.1rc0-x-2.1.3 c++ , and i have something strange i created 10 CCSprites.
i have class that extend CCSprite and CCTargetedTouchDelegate like this :
Gem.cpp
#include "Gem.h"
Gem::Gem()
{
;
}
Gem::~Gem()
{
;
}
void Gem::onEnter()
{
CCDirector* pDirector = CCDirector::sharedDirector();
pDirector->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
CCNode::onEnter();
}
void Gem::onExit()
{
CCDirector* pDirector = CCDirector::sharedDirector();
pDirector->getTouchDispatcher()->removeDelegate(this);
CCNode::onExit();
}
bool Gem::ccTouchBegan(CCTouch* touch, CCEvent* event)
{
CCPoint touchPoint = touch->getLocation();
CCLOG("Gem Touched! ImageName:%s |GemId:%s x:%f ,y:%f myspriteX:%f, myspriteY:%f nodePosX:%f nodePosY:%f",this->getImageName().c_str(),this->getGemId().c_str(),touchPoint.x,touchPoint.y,getGemSprite()->getPositionX(),getGemSprite()->getPositionY(),this->getPositionX(),this->getPositionY());
return true;
}
void Gem::ccTouchMoved(CCTouch* touch, CCEvent* event)
{
CCPoint touchPoint = touch->getLocation();
}
void Gem::ccTouchEnded(CCTouch* touch, CCEvent* event)
{
}
Gem.h
class Gem :public CCNode , public CCTargetedTouchDelegate
{
public:
Gem();
virtual ~Gem();
CC_SYNTHESIZE(std::string,imageName,ImageName)
CC_SYNTHESIZE(std::string,gemId,GemId)
CC_SYNTHESIZE(CCPoint,gemPos,GemPos)
CC_SYNTHESIZE(CCSprite*,gemSprite,GemSprite)
virtual void onEnter();
virtual void onExit();
virtual bool ccTouchBegan(CCTouch* touch, CCEvent* event);
virtual void ccTouchMoved(CCTouch* touch, CCEvent* event);
virtual void ccTouchEnded(CCTouch* touch, CCEvent* event);
};
The helloWorldScene.cpp init() method
bool HelloWorld::init()
{
bool bRet = false;
//////////////////////////////////////////////////////////////////////////
// super init first
//////////////////////////////////////////////////////////////////////////
if(!CCLayer::init())
return false;
CCSize m_winSize;
CCSize visibleSize;
CCPoint origin;
m_winSize = CCDirector::sharedDirector()->getWinSize();
visibleSize = CCDirector::sharedDirector()->getVisibleSize();
origin = CCDirector::sharedDirector()->getVisibleOrigin();
CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("sprites.plist","sprites.png");
CCSpriteBatchNode* gameBatchNode = CCSpriteBatchNode::create("sprites.png"/*,200*/);
CCSprite *bg= CCSprite::create("grideFinal.png");
//bg->setAnchorPoint(ccp(0,0));
bg->setPosition(ccp(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
this->addChild(bg,1);
Gem* gem1 = new Gem();
gem1->retain();
gem1->setGemId("gem1");
gem1->setImageName("img_1");
gem1->setGemSprite(CCSprite::createWithSpriteFrameName("gem1.png"));
Gem* gem2 = new Gem();
gem2->retain();
gem2->setGemId("gem2");
gem2->setImageName("img_2");
gem2->setGemSprite(CCSprite::createWithSpriteFrameName("gem2.png"));
gem1->setAnchorPoint(ccp(0,0));
gem2->setAnchorPoint(ccp(0,0));
gem1->setPosition(ccp(0,0));
gem2->setPosition(ccp(gem1->getGemSprite()->getContentSize().width,40));
gem1->getGemSprite()->setAnchorPoint(ccp(0,0));
gem2->getGemSprite()->setAnchorPoint(ccp(0,0));
gem1->getGemSprite()->setPosition(ccp(0,0));
gem2->getGemSprite()->setPosition(ccp(gem1->getGemSprite()->getContentSize().width,40));
//gameBatchNode->addChild(gem1->getGemSprite(),4,44);
//gameBatchNode->addChild(gem2->getGemSprite(),4,45);
this->addChild(gameBatchNode);
bg->addChild(gem1->getGemSprite(),50);
bg->addChild(gem2->getGemSprite(),50);
bg->addChild(gem1,50);
bg->addChild(gem2,50);
bRet = true;
return bRet;
}
Every thing is woring fine except when i touch the screen and trigger Gem::ccTouchBegan method. its always gives me the last CCSprite ( and i have like 50 on the screen
why is that ? what im missing here ?
Because every Gem instance extend CCTargetedTouchDelegate and register touch dispatcher, only the highest or latest added will be triggered.
So what the correct way is implement CCTargetedTouchDelegate in HelloWorld class, and when touch occur, check which Gem is touched by touch point.
Here is a method used for checking if touch is in some node:
bool Gem::isTouchInside(CCTouch* pTouch)
{
CCPoint touchLocation = pTouch->getLocation();
CCRect rect =
CCRectApplyAffineTransform(CCRectMake(0 ,
0 ,
this->getContentSize().width,
this->getContentSize().height),
this->nodeToWorldTransform());
return rect.containsPoint(touchLocation);
}