【amazing cocos2d-x 3.0之十】使用touch事件来拖拽精灵

原创 2014年04月04日 21:46:20

本篇文章,你将学习到如下知识点:

(1)如何使用touch事件拖拽精灵的基本方法

(2)如何通过touch事件来滚动视图本身

(3)如何方便地计算坐标


1. 加载背景和图片

新建一个工程,名为“DragSprite”。打开HelloWorldScene.h,增加三个成员变量:

public:
   cocos2d::Sprite* background; //背景图片
   cocos2d::Sprite* selSprite; //当前选中的精灵
   cocos2d::Vector<cocos2d::Sprite*> movableSprites; //一个在处理touch事件时需要移动的精灵的数组

打开HelloWorldScene.ccp,把init方法的代码替换成如下:

bool HelloWorld::init()
{
    if ( !Layer::init() )
    {
        return false;
    }

    Size winSize = Director::getInstance()->getWinSize();

    Texture2D::setDefaultAlphaPixelFormat(Texture2D::PixelFormat::RGB565);
    background = Sprite::create("blue-shooting-stars.png");
    background->setAnchorPoint(Point(0, 0));
    this->addChild(background);

    Texture2D::setDefaultAlphaPixelFormat(Texture2D::PixelFormat::DEFAULT);

    std::string images[] = {"bird.png", "cat.png", "dog.png", "turtle.png"};
    int images_length = 4;
    for(int i =0; i < images_length; ++i)
    {
        std::string image = images[i];
        Sprite *sprite = Sprite::create(image);
        float offsetFraction = ((float)(i+1))/(images_length+1);
        sprite->setPosition(winSize.width*offsetFraction, winSize.height/2);
        this->addChild(sprite);

        movableSprites.pushBack(sprite);
    }

    selSprite = NULL;

    return true;
}

这段代码由两部分组成:


(1)加载背景

代码的第一部分加载了一张背景图片(blue-shooting-stars.png)。要注意的是,这里把图片的锚点(anchor point)设置为(0,0),即图片的左下角。之前我们也介绍过关于锚点的知识。这里我们针对具体的实例再说明一下。当你设置一个精灵的位置的时候,实际上,你设置的是这个精灵的锚点的位置。默认情况下,图片的锚点就是图片的中心。因此,通过把精灵锚点设置为左下角,而这个方法并没有设置背景的位置,即背景的位置默认情况下是(0,0),所以图片的左下角就位于屏幕的左下角。这个图片有1600个像素宽,那么超过的部分就在屏幕之外了。


还有一点需要注意,就是在加载图片之前,转换了一下像素格式。在默认情况下,cocos2d里面加载图片,它们是作为32位的图片加载进来的。这意味着每个像素占4个字节的内存空间。当你需要非常高质量的显示效果时,就很合适。但是,有时候你并不需要,因为以前的设备内存很有限,如果全部使用32的像素格式来加载图片的话,会造成内存消耗过多。当你加载大的图片的时候(比如背景图片),最佳实践是使用16位的像素格式来加载,也就是牺牲一点质量来减少内存开销。cocos2d里面有很多不同的像素格式,这里我们选择16位的像素格式,RGB565,因为背景一般不需要透明效果。(少了Alpha,RGBA就是有Alpha)


(2)加载图片

init方法的另外一部分,就是循环遍历一个图片数组,然后创建精灵并且计算精灵放置的坐标。这些精灵会按顺序排开,显示在屏幕上。同时把这些精灵的引用保存在movableSprites数组里面,后续会用到。


编译并运行,就可以看到一排可爱的小动物啦,在等待你touch。



2. 基于touch事件选取精灵,使用3.0新的事件监听

打开HelloWorldScene.cpp在init方法的最后添加如下代码:

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);

分析:首先我们创建了事件监听器listener,并绑定onTouchBegan,onTouchMoved,onTouchEnded函数。

接下来我们来实现这些函数

bool HelloWorld::onTouchBegan(Touch* touch, Event* event)
{
    Point touchLocation = this->convertTouchToNodeSpace(touch);
    this->selectSpriteForTouch(touchLocation);
    return true;
}

void HelloWorld::onTouchMoved(Touch* touch, Event* event)
{
}

void HelloWorld::onTouchEnded(Touch* touch, Event* event)
{
}

void HelloWorld::selectSpriteForTouch(Point touchLocation)
{
    Sprite * newSprite = NULL;
    for (Sprite *sprite : movableSprites)
    {
        if ( sprite->getBoundingBox().containsPoint(touchLocation) )
        {
            newSprite = sprite;
            break;
        }
    }
    if (newSprite != selSprite && NULL != newSprite)
    {
        if (NULL != selSprite)
        {
            selSprite->stopAllActions();
            selSprite->runAction(RotateTo::create(0.1, 0));
        }
        RotateBy * rotLeft = RotateBy::create(0.1, -14.0);
        RotateBy * rotCenter = RotateBy::create(0.1, 0.0);
        RotateBy * rotRight = RotateBy::create(0.1, 14.0);
        Sequence * rotSeq = Sequence::create(rotLeft, rotCenter, rotRight, rotCenter, NULL);
        newSprite->runAction(RepeatForever::create(rotSeq));
        selSprite = newSprite;
    }
}

这里的selectSpriteForTouch方法,遍历movableSprites数组中的所有精灵,查找第一个精灵位置为touch点位置相交的精灵。使用getBoundingBox函数和containsPoint,前一个函数可以返回精灵的边界矩形,后一个函数判断点击的点是否包含在矩形中。


如果找到一个匹配的精灵,那么就让这个精灵执行一些动画,这样用户就知道哪个精灵被选中了。如果动画还没执行完,又选中另一个精灵了,那么就终端前一个精灵的动画。这里的动画效果,使用了一系列的Action来实现的。


最后,onTouchBegan方法基于用户的touch事件调用上面的方法。注意,这里把touch坐标点转换成了结点坐标系。为了实现这个目的,通过调用Node的一个辅助函数,convertTouchToNodeSpace。这个方法做了以下两件事情:

(1)得到touch视图(也就是屏幕)的touch点位置为OpenGL坐标点(使用getLocation方法)

(2)转换OpenGL坐标系为指定结点的坐标系(使用convertToNodeSpace方法)

这是一个非常常用的转换方法,所以提供这个方法可以节约很多事件。


编译并运行,并且用手触摸这些动物。当你点中一个精灵的时候,它就会以一种非常可爱的方式旋转,表明它被你选中了。



3. 基于touch事件移动精灵和背景层

是时候让小动物移动了!基本的思想就是实现onTouchMoved回调函数,然后计算本次touch点到上一次touch点之间的距离。如果一个动物被选中,将按照计算出来的touch偏移量来移动它。如果动物没有被选中,那么就移动整个层,因此用户可以从左至右滚动层。


在编写代码之前,我们先花点时间来讨论一下如何滚动一个层。

先看下面的两张图片:



如我们上面所将的,背景的锚点在左下角(0,0),那么背景的其他部分就在屏幕之外。黑色框框表示当前可见的区域,也就是屏幕范围。

因此,当你将图片往右边滚动100个像素时,你就可以把整个cocos2d-x层往左移动100个像素。如第二张图所示。

要注意的是,你不能移太多,因为你把层往右移动太多的话,左边就是空白的了。


添加如下代码:

void HelloWorld::onTouchMoved(Touch* touch, Event* event)
{
    Point touchLocation = this->convertTouchToNodeSpace(touch);

    Point oldTouchLocation = touch->getPreviousLocation();
    oldTouchLocation = this->convertToNodeSpace(oldTouchLocation);

    Point translation = touchLocation - oldTouchLocation;
    this->panForTranslation(translation);
}

Point HelloWorld::boundLayerPos(Point newPos)
{
    Size winSize = Director::getInstance()->getWinSize();
    Point retval = newPos;

    /* if(retval.x > 0)
          retval.x = 0;
       if(retval.x < -background->getContentSize().width+winSize.width)
          retval.x = -background->getContentSize().width+winSize.width;  */
    retval.x = MIN(retval.x, 0);
    retval.x = MAX(retval.x, -background->getContentSize().width+winSize.width);

    retval.y = this->getPosition().y;
    return retval;
}

void HelloWorld::panForTranslation(Point translation)
{
    if (selSprite)
    {
        Point newPos = selSprite->getPosition() + translation;
        selSprite->setPosition(newPos);
    }
    else
    {
        Point newPos = this->getPosition() + translation;
        this->setPosition( this->boundLayerPos(newPos) );
    }
}

方法(boundLayerPos),用来确保你在滚动层的时候不会超出背景图片的边界。你传递一个目标点坐标,然后相应地修改x值,保证不会超出边界。如果你不是很理解的话,可以拿出纸和笔,结合上面给出的图片,自己动手画一画。那两句MAX和MIN代码等价于注释的代码。


方法(panForTranslation)基于传入的目标点位置移动精灵(如果有精灵被选中就移动之,否则移动整个层)。具体的做法就是设置精灵或者层的位置。


方法(onTouchMoved)是一个回调函数,它在你拖动屏幕上的手指时调用。像之前一样,把touch坐标转换成局部node坐标。因为没有一个辅助方法可以把前一个层的touch坐标转换成node坐标,因此,我们需要手工地调用那2个方法来执行这个操作。


然后,计算touch偏移量,通过把当前的点坐标减去上一个点坐标,然后调用panForTranslation方法。


编译并运行,现在,你可以通过拖拽来移动精灵和层啦!






cocos3.X拖动精灵移动

1、 创建一个精灵sprite; 2、一个触摸事件 listener ,设置listener的onTouchBegan,onTouchMoved,onTouchEnded; 3、将sprite 和...

Cocos2d-js中实现惯性滑动效果

Cocos2d-js中实现惯性滑动效果 [摘要:甚么是惯性滑动后果呢?我们常常正在脚机上看到一种后果,拖动手机屏幕滑动,把脚摊开后,屏幕仍会由于惯性而滑动一段间隔,那便是一种惯性滑动后果。目前的...

Cocos2d-x 2.0 百例精讲:如何让一个精灵跟随触点移动

红孩儿Cocos2d-x学习园地教学资料由 [红孩儿游戏编程教学组] 组织编写.版权所有,盗文必究! 如何让一个精灵跟随触点移动                        ...

cocos2d-x 3.0 如何使用拖拽精灵

引言 程序截图:   许多读者想要一个关于如何在cocos2d-x里面使用touch事件来拖拽精灵(sprite)的教程。既然你们这么要求,我就满足你们啦!   在这个教程中...

CocosCreator学习5:实现物体拖动

今天来学习一下如何实现物体拖动。 通过CocosCreator内置的cc.Node.EventType.MOUSE_MOVE鼠标事件实现,其返回参数为鼠标的坐标值delta.x,delta.y。将节...

cocos2dx中地图拖拽(任意方向拖拽+固定方向拖拽)

在游戏开发中,经常会碰见需要使用地图拖拽,例如通关关卡地图,可以允许用户拖拽选择进入哪个关卡,那么在cocos2dx中如何来实现呢?请见如下方案。 我们先创建一个精灵: auto s_map= Spr...

cocos2dx鼠标拖动与释放

最近在学习cocos2dx,发现原来2.0版本以前是没有鼠标监听器的,既然

如何使用Tomcat配置本地服务器

首先登录tomcat官网进行下载,我是win7 64位的: 将压缩包解压,我解压在C盘,然后配置Tomcat环境变量: 以及在Class_path中添加: %CATALINA_H...

php绝对路径与相对路径之间关系的的深入研究

前一阵子老是受php开发中,文件互相引入require()相对位置关系的困扰,为了彻底弄清它们的关系,笔者做了个实验。...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:【amazing cocos2d-x 3.0之十】使用touch事件来拖拽精灵
举报原因:
原因补充:

(最多只允许输入30个字)