cocos2d-x-2.2.3-CCLayer源码学习(三)

| 分类 cocos2d-x  | 标签 CCLayerColor  CCLayer  KeypadTest  CCKeypadDelegate 

本文主要讲解的是关于CCLayer在TestCPP中的例子,关于CCLayer源码解析,参见cocos2d-x-2.2.3-CCLayer源码学习(一)

下面我们还是先学习学习KeypadTest这个例子

//重点方法其实就只有一句setKeypadEnabled(true);还是先看看这方法怎么实现的把,跳转到CCLayer中

void CCLayer::setKeypadEnabled(bool enabled)
{
	if (enabled != m_bKeypadEnabled)//先判断当前CCLayer的m_bKeypadEnabled是否一样,默认m_bKeypadEnabled为false
	{
    	m_bKeypadEnabled = enabled;

    	if (m_bRunning)
    	{
        	CCDirector* pDirector = CCDirector::sharedDirector();
        	if (enabled)
        	{
            	pDirector->getKeypadDispatcher()->addDelegate(this);//把CCKeypadDelegate加入到CCKeypadDispatcher中统一管理。由于CCLayer实现了CCKeypadDelegate接口
        	}
        	else
        	{
            	pDirector->getKeypadDispatcher()->removeDelegate(this);//把CCKeypadDelegate从CCKeypadDispatcher中移除            	}
    	}
	}
}

其实看到这个我们就应该想到CCTouchDelegete,是的,keypad和touch处理流程是一样的,先把delegate统一放到Handler,再把handler加入到dispach中的CCArray中,最后统一在一个方法中处理消息,再把接受到的消息分发出去。

下面我们还是看看CCKeypadDelegate这个接口:

class CC_DLL CCKeypadDelegate
{
public:
	
	virtual void keyBackClicked() {}//点击返回键时候触发

	virtual void keyMenuClicked() {};//点击menu键的时候触发
};

在KeypadTest例子中,只要用户点击返回键或者menu键,都会回调相应的方法。

下面我们接着看CCKeypadDispatcher,看看是怎么统一管理CCKeypadDelegate的

有个枚举类ccKeypadMSGType,来区分点击事件
typedef enum {
// the back key clicked msg
kTypeBackClicked = 1,
kTypeMenuClicked,
} ccKeypadMSGType;

CCArray* m_pDelegates;//保存delegates的指针
bool m_bLocked;//是否被锁住,默认false
bool m_bToAdd;//是否添加,默认false
bool m_bToRemove;//是否移除,默认false
struct _ccCArray *m_pHandlersToAdd;//即将要加入到m_pDelegates的delegates,事先加入到m_pHandlersToAdd,当m_bLocked为true的时候
struct _ccCArray *m_pHandlersToRemove;//即将要移除到m_pDelegates的delegates,事先加入到m_pHandlersToRemove指针中,当m_bLocked为true的时候

typedef struct _ccCArray {
unsigned int num, max;//num表示arr指向的数组所含有的成员个数,该数组的空间
void** arr;//指向数组的指针
} ccCArray;//ccCArray是一个结构体,后面内容会详细介绍他。

下面看重点方法讲解
void CCKeypadDispatcher::addDelegate(CCKeypadDelegate* pDelegate)//在CCLayer中直接调用方法,站在最前线的战士
{
	if (!pDelegate)//如果为null,返回
	{
    	return;
	}

	if (! m_bLocked)//如果没有被锁住,则直接添加到m_pDelegates中,在dispatchKeypadMSG方法中有把锁判断是否锁定
	{
    	forceAddDelegate(pDelegate);//该方法才是真正的添加CCKeypadDelegate的家伙
	}
	else
	{
    	ccCArrayAppendValue(m_pHandlersToAdd, pDelegate);//把pDelegate添加到m_pHandlersToAdd中,该方法是ccCArray.h中的方法
    	m_bToAdd = true;//表明有东西添加到m_pHandlersToAdd了,标志作用
	}
}
还是先来看ccCArrayAppendValue方法把。
void ccCArrayAppendValue(ccCArray *arr, void* value)//有两个参数,m_pHandlersToAdd, pDelegate
{
	arr->arr[arr->num] = value;//把pDelegate指针保存到数组中,通过_ccCArray结构体的void** arr来索引
	arr->num++;//数组长度+1
	// double the capacity for the next append action
	// if the num >= max
	if (arr->num >= arr->max)//如果数组长度大于等于最大容量,那就扩容啊
	{
    	ccCArrayDoubleCapacity(arr);
	}
}

下面欣赏forceAddDelegate方法
void CCKeypadDispatcher::forceAddDelegate(CCKeypadDelegate* pDelegate)
{
	CCKeypadHandler* pHandler = CCKeypadHandler::handlerWithDelegate(pDelegate);//pDelegate竟然是存放在CCKeypadHandler里面的

	if (pHandler)
	{
    	m_pDelegates->addObject(pHandler);//m_pDelegates添加的是CCKeypadHandler,而不是pDelegate,为啥需要这样做呢???
	}
}
下面看看CCKeypadHandler的源码,看了半天发觉其实CCKeypadHandler没干什么事情,纯粹是多余的,感觉其实是作者是为了配套CCTouchHandler才这样搞的
CCKeypadHandler* CCKeypadHandler::handlerWithDelegate(CCKeypadDelegate *pDelegate)
{
	CCKeypadHandler* pHandler = new CCKeypadHandler;

	if (pHandler)
	{
    	if (pHandler->initWithDelegate(pDelegate))//CCKeypadHandler持有一个CCKeypadDelegate,赋值给m_pDelegate
    	{
        	pHandler->autorelease();
    	}
    	else
    	{
        	CC_SAFE_RELEASE_NULL(pHandler);//宏定义pHandler设为null
        	#define CC_SAFE_RELEASE_NULL(p)        do { if(p) { (p)->release(); (p) = 0; } } while(0)
    	}
	}

	return pHandler;
}

下面还是看看CCKeypadDispatcher怎么分发消息的把。

bool CCKeypadDispatcher::dispatchKeypadMSG(ccKeypadMSGType nMsgType)
{
	CCKeypadHandler*  pHandler = NULL;
	CCKeypadDelegate* pDelegate = NULL;

	m_bLocked = true;//当分发消息的时候,把开关关了

	if (m_pDelegates->count() > 0)//这个数组>0,说明存放了CCKeypadHandler
	{
    	CCObject* pObj = NULL;
    	CCARRAY_FOREACH(m_pDelegates, pObj)//循环迭代
    	{
        	CC_BREAK_IF(!pObj);

        	pHandler = (CCKeypadHandler*)pObj;
        	pDelegate = pHandler->getDelegate();//看到没pHandler就是多余的

        	switch (nMsgType)//根据消息类型,分发
        	{
        	case kTypeBackClicked:
            	pDelegate->keyBackClicked();//回调keyBackClicked()方法
            	break;
        	case kTypeMenuClicked:
            	pDelegate->keyMenuClicked();//回调kTypeMenuClicked()方法
            	break;
        	default:
            	break;
        	}
    	}
	}

	m_bLocked = false;//消息分发完毕,解锁了
	if (m_bToRemove)//判断是否有东西需要移除,因为锁被锁住,临时存放在m_pHandlersToRemove中
	{
    	m_bToRemove = false;//立马设置为false
    	for (unsigned int i = 0; i < m_pHandlersToRemove->num; ++i)//
    	{
        	forceRemoveDelegate((CCKeypadDelegate*)m_pHandlersToRemove->arr[i]);//把存放在数组中的CCKeypadDelegate*取出来,m_pHandlersToRemove->arr[i]移除
    	}
    	ccCArrayRemoveAllValues(m_pHandlersToRemove);//清空m_pHandlersToRemove,仅仅只需要arr->num = 0;就可以
	}

	if (m_bToAdd))//判断是否有东西需要添加,因为锁被锁住,临时存放在m_pHandlersToAdd中
	{
    	m_bToAdd = false;//立马设置为false
    	for (unsigned int i = 0; i < m_pHandlersToAdd->num; ++i)
    	{
        	forceAddDelegate((CCKeypadDelegate*)m_pHandlersToAdd->arr[i]);//把存放在数组中的CCKeypadDelegate*取出来,m_pHandlersToAdd->arr[i]添加

    	}
    	ccCArrayRemoveAllValues(m_pHandlersToAdd);//清空m_pHandlersToAdd,仅仅只需要arr->num = 0;就可以

	}

	return true;
}

好了CCKeypadDelegate消息分发大体流程讲完了,还有一个疑问,随把消息传递给dispatchKeypadMSG啊???据说只有android才有实体键,所以android上是这么传递的,在TouchesJni.cpp中

#define KEYCODE_BACK 0x04
#define KEYCODE_MENU 0x52

JNIEXPORT jboolean JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeKeyDown(JNIEnv * env, jobject thiz, jint keyCode) {
    CCDirector* pDirector = CCDirector::sharedDirector();
    switch (keyCode) {//keyCode是android上按键的编码,在nativeKeyDown方法中,他是直接把keyCode传递过来的,如果我们还有特殊需求,也可以添加其他事件
        case KEYCODE_BACK:
              if (pDirector->getKeypadDispatcher()->dispatchKeypadMSG(kTypeBackClicked);//终于看到dispatchKeypadMSG了,传递了ccKeypadMSGType中的kTypeBackClicked
                return JNI_TRUE;
            break;
        case KEYCODE_MENU:
            if (pDirector->getKeypadDispatcher()->dispatchKeypadMSG(kTypeMenuClicked))////终于看到dispatchKeypadMSG了,传递了ccKeypadMSGType中的kTypeMenuClicked
                return JNI_TRUE;
            break;
        default:
            return JNI_FALSE;
    }
    return JNI_FALSE;
}

上面就是完整的按键回调流程,没有具体讲KeypadTest,但也差不多了!

下面就看看AccelerometerTest把。看看Accelerometer的流程是啥样的?下面仅仅只针对于Android( ⊙ o ⊙ )啊!

setAccelerometerEnabled(true);//在onEnter()仅仅只需要设置这样就可以。

//接着就可以到didAccelerate方法中接受了。该方法是CCAccelerometerDelegate中的类,CCLayer继承于CCAccelerometerDelegate中
void AccelerometerTest::didAccelerate(CCAcceleration* pAccelerationValue)
{

CCDirector* pDir = CCDirector::sharedDirector();

/*FIXME: Testing on the Nexus S sometimes m_pBall is NULL */
if ( m_pBall == NULL ) {
    return;
}

CCSize ballSize  = m_pBall->getContentSize();//获取球球的大小

CCPoint ptNow  = m_pBall->getPosition();//获取球球的位置,由于该球直接添加到CCLayer的,所以不需要转换成世界坐标系
CCPoint ptTemp = pDir->convertToUI(ptNow);//直接转成UI坐标系:左上为原点,x轴向右,y轴向下

ptTemp.x += pAccelerationValue->x * 9.81f;//pAccelerationValue->x * 9.81f才是加速度
ptTemp.y -= pAccelerationValue->y * 9.81f;//注意是-啊

CCPoint ptNext = pDir->convertToGL(ptTemp);//转换成世界坐标系
FIX_POS(ptNext.x, (VisibleRect::left().x+ballSize.width / 2.0), (VisibleRect::right().x - ballSize.width / 2.0));
FIX_POS(ptNext.y, (VisibleRect::bottom().y+ballSize.height / 2.0), (VisibleRect::top().y - ballSize.height / 2.0));
m_pBall->setPosition(ptNext);//设置坐标
}

//FIX_POS确保球运动不超出屏幕
#define FIX_POS(_pos, _min, _max) \ 
if (_pos < _min)        \
_pos = _min;        \
else if (_pos > _max)   \
_pos = _max;        \

关于坐标转换的,参考下这:cocos2dx进阶学习之坐标转换

void CCLayer::setAccelerometerEnabled(bool enabled)
{
if (enabled != m_bAccelerometerEnabled)
{
    m_bAccelerometerEnabled = enabled;

    if (m_bRunning)
    {
        CCDirector* pDirector = CCDirector::sharedDirector();
        if (enabled)
        {
            pDirector->getAccelerometer()->setDelegate(this);//设置代理类,CCLayer继承于CCAccelerometerDelegate
        }
        else
        {
            pDirector->getAccelerometer()->setDelegate(NULL);//注意此处直接传的是NULL,在下面看看为啥传NULL啊
        }
    }
}
}

class CCAcceleration
{
public:
double x;//X轴加速度
double y;//Y轴加速度
double z;//Z轴加速度

double timestamp;//时间戳
};

class CC_DLL CCAccelerometerDelegate//代理类,CCLayer继承于她
{
public:
virtual void didAccelerate(CCAcceleration* pAccelerationValue) 	{CC_UNUSED_PARAM(pAccelerationValue);}
};

下面我们就看看CCAccelerometer类

CCAccelerometerDelegate* m_pAccelDelegate;//有他,那肯定就有setDelegate方法,否则怎么赋值啊。
CCAcceleration m_obAccelerationValue;//这个变量就是为了存储加速度以及时间戳的

//好了,终于看到setDelegate了,从CCLayer一直追到这
void CCAccelerometer::setDelegate(CCAccelerometerDelegate* pDelegate) 
{
    m_pAccelDelegate = pDelegate;

    if (pDelegate)//看setDelegate设置为NULL的作用了没,为了判断运行不同的方法
    {        
        enableAccelerometerJNI();
    }
    else
    {
        disableAccelerometerJNI();
    }
}

下面先看enableAccelerometerJNI方法。 
void enableAccelerometerJNI() {
JniMethodInfo t;

if (JniHelper::getStaticMethodInfo(t, CLASS_NAME, "enableAccelerometer", "()V")) {
    t.env->CallStaticVoidMethod(t.classID, t.methodID);//CLASS_NAME "org/cocos2dx/lib/Cocos2dxHelper",会运行类名为org/cocos2dx/lib/Cocos2dxHelper下的enableAccelerometer方法。
    t.env->DeleteLocalRef(t.classID);
}
}

下面继续看enableAccelerometer方法
public static void enableAccelerometer() {
	Cocos2dxHelper.sAccelerometerEnabled = true;
	Cocos2dxHelper.sCocos2dxAccelerometer.enable();//继续查看
}
public void enable() {
	this.mSensorManager.registerListener(this, this.mAccelerometer, SensorManager.SENSOR_DELAY_GAME);//注册了一个监听器,其实这都是android的知识了啊。this代表Cocos2dxAccelerometer,他实现了SensorEventListener接口,他会回调public void onSensorChanged(final SensorEvent pSensorEvent) 方法。
}

//在onSensorChanged(final SensorEvent pSensorEvent)回调方法中

@Override
public void onSensorChanged(final SensorEvent pSensorEvent) {
	if (pSensorEvent.sensor.getType() != Sensor.TYPE_ACCELEROMETER) {
		return;
	}

	float x = pSensorEvent.values[0];//获取相应方向的加速度
	float y = pSensorEvent.values[1];
	final float z = pSensorEvent.values[2];

	final int orientation = this.mContext.getResources().getConfiguration().orientation;

	if ((orientation == Configuration.ORIENTATION_LANDSCAPE) && (this.mNaturalOrientation != Surface.ROTATION_0)) {//判断旋转方向,横屏
		final float tmp = x;
		x = -y;
		y = tmp;
	} else if ((orientation == Configuration.ORIENTATION_PORTRAIT) && (this.mNaturalOrientation != Surface.ROTATION_0)) {//竖屏
		final float tmp = x;
		x = y;
		y = -tmp;
	}		
	
	Cocos2dxGLSurfaceView.queueAccelerometer(x,y,z,pSensorEvent.timestamp);//把加速度的值传递到queueAccelerometer
}
    //定位到queueAccelerometer
    public static void queueAccelerometer(final float x, final float y, final float z, final long timestamp) {	
   mCocos2dxGLSurfaceView.queueEvent(new Runnable() {//queueEvent可以实现主线程和渲染线程之间的交互
	@Override
	    public void run() {
		    Cocos2dxAccelerometer.onSensorChanged(x, y, z, timestamp);//我们发现该方法是个native方法,在C++文件中,我们找到了具体实现
	}
   });
 //下面就是具体实现
JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxAccelerometer_onSensorChanged(JNIEnv*  env, jobject thiz, jfloat x, jfloat y, jfloat z, jlong timeStamp) {
    CCDirector* pDirector = CCDirector::sharedDirector();
    pDirector->getAccelerometer()->update(x, y, z, timeStamp);//我们发现又回到了CCAccelerometer,而这次是update方法,一个轮回啊。
}

//下面前往CCAccelerometer的update方法。
void CCAccelerometer::update(float x, float y, float z, long sensorTimeStamp) 
{
    if (m_pAccelDelegate)
    {
        m_obAccelerationValue.x = -((double)x / TG3_GRAVITY_EARTH);//TG3_GRAVITY_EARTH 9.80665f为重力加速度,感觉是为了和其他平台相适应,所以除以重力加速度
        m_obAccelerationValue.y = -((double)y / TG3_GRAVITY_EARTH);
        m_obAccelerationValue.z = -((double)z / TG3_GRAVITY_EARTH);
        m_obAccelerationValue.timestamp = (double)sensorTimeStamp;

        m_pAccelDelegate->didAccelerate(&m_obAccelerationValue);//看到回调了啊,就是上文,例子中处理逻辑的啊
    }    
}

好了,关于CCAccelerometer的逻辑也讲的差不多了,通过CCAccelerometer设置setDelegate,又通过他接受返回数据。

青春是一场大雨,即使感冒了,还盼回头再淋一次...image...微笑永远是一个人身上最好看的东西...


PREVIOUS     NEXT