l OpenGL坐标系
Cocos2dx以OpenGL&OpenGL ES为基础,支持OpenGL坐标系。该坐标系原点在屏幕左下角,x轴向右,y轴向上。
l 屏幕坐标系
屏幕坐标系使用的是不同的坐标系统,原点在屏幕左上角,x轴向右,y轴向下。触摸屏事件传入的位置信息使用的是该坐标系,在cocos2dx中对触摸事件做出响应前,需要首先把触摸点转化到OpenGL坐标系。这一点在后面的触屏信息中会详细介绍,可以使用CCDirector的convertToGL方法来完成这一转化。
l 世界坐标系
世界坐标系也叫作绝对坐标系,是游戏开发中建立的概念。“世界”即是游戏世界,它建立了描述其他坐标系所需要的参考标准。我们能够用世界坐标系来描述其他坐标系的位置。它是cocos2dx中一个比较大的概念。
l 本地坐标系
本地坐标系是和特定节点相关联的坐标系。每个节点都有独立的坐标系。当节点移动或改变方向时,和该节点关联的坐标系(它的子节点)将随之移动或改变方向。这一切都是相对的,相对于基准的,只有在节点坐标系中才有意义。Node类的设置位置使用的就是父节点的节点坐标系,它和OpenGL坐标系的方向是一致的,原点在父节点的左下角。如果父节点是场景树中的顶层节点,那么它使用的节点坐标系就和世界坐标系重合了 。
Cocos2dx中的元素是父子关系的层级结构。通过Node设置位置使用的是相对其父节点的本地坐标系,而非世界坐标系。最后在绘制屏幕的时候,Cocos2dx会把这些元素的本地节点坐标映射成世界坐标系坐标。世界坐标系和OpenGL坐标系方向一致。
下面看一个例子:
首先我们添加两个测试精灵(宽:27,高:40)到场景里面:
auto *sprite1 = Sprite::create("test.png");
sprite1->setPosition(ccp(20, 40));
sprite1->setAnchorPoint(ccp(0, 0));
this->addChild(sprite1);
auto *sprite2 = Sprite::create("test.png");
sprite2->setPosition(ccp(-15, -30));
sprite2->setAnchorPoint(ccp(1, 1));
this->addChild(sprite2);
现在我们来看看坐标系转换,同样地,我们先写点测试代码:
Point p1 = sprite1->convertToNodeSpace(sprite2->getPosition());
Point p2 = sprite1->convertToWorldSpace(sprite2->getPosition());
Point p3 = sprite1->convertToNodeSpaceAR(sprite2->getPosition());
Point p4 = sprite1->convertToWorldSpaceAR(sprite2->getPosition());
接着,再打印出各点的x,y值:
log("p1:%f,%f", p1.x, p1.y);
log("p2:%f,%f", p2.x, p2.y);
log("p3:%f,%f", p3.x, p3.y);
log("p4:%f,%f", p4.x, p4.y);
坐标系如下图:
可知sprite1、sprite2在锚点(0,0)处的本地坐标为(20,40)、(-42,-70);
p1为sprite2相对于sprite1(0,0锚点)本地坐标系,(-15-20,-30-40)=(-35,-70)
p2为sprite2相对于sprite1(0,0锚点)在上图坐标系中的位置,
(-15+20,-30+40)=(5,10)
p3为sprite2相对于sprite1锚点本地坐标系,(-15-20,-30-40)=(-35,-70)
p2为sprite2相对于sprite1锚点在上图坐标系中的位置,(-15+20,-30+40)=(5,10)
现在我们来看看坐标系转换,同样地,我们先写点测试代码:
Point p5 = sprite2->convertToNodeSpace(sprite1->getPosition());
Point p6 = sprite2->convertToWorldSpace(sprite1->getPosition());
Point p7 = sprite2->convertToNodeSpaceAR(sprite1->getPosition());
Point p8 = sprite2->convertToWorldSpaceAR(sprite1->getPosition());
接着,再打印出各点的x,y值:
log("p1:%f,%f", p5.x, p5.y);
log("p2:%f,%f", p6.x, p6.y);
log("p3:%f,%f", p7.x, p7.y);
log("p4:%f,%f", p8.x, p8.y);
可知:
P5(20-(-42), 40-(-70))=(62, 110)
P6(20+(-42), 40+(-70))=(22, 30)
P7(20-(-15), 40-(-30))=(35, 70)
P8(20+(-15), 40+(-30))=(5, 10)
下面为sprite2->convertToNodeSpace(sprite1->getPosition())在Cocos2dx中代码具体流程:
Vec2 Node::convertToNodeSpace(const Vec2& worldPoint) const
{
Mat4 tmp =getWorldToNodeTransform();/* 获取sprite2的世界坐标转化矩阵Tmp = <img src="https://img-blog.csdn.net/20141011143016009" alt="" /> */
Vec3vec3(worldPoint.x, worldPoint.y, 0);
Vec3 ret;
tmp.transformPoint(vec3,&ret);
/*将sprite1转化为相对于sprite2本地坐标系的坐标 ret = <img src="https://img-blog.csdn.net/20141011143126583" alt="" /> */
return Vec2(ret.x,ret.y);
}
在convertToNodeSpace方法里使用到了OpenGL中矩阵变换知识,不谅解这部分的同学可以查看一下关于矩阵运算和OpenGL矩阵变换;
Mat4 Node::getWorldToNodeTransform() const
{
returngetNodeToWorldTransform().getInversed();
}
getWorldToNodeTransform方法实现是先获取本地坐标系到世界坐标系的转换矩阵,然后将矩阵反转,即得到世界坐标系到本地坐标系的转换矩阵。
getWorldToNodeTransform返回 反转矩阵->
Mat4Node::getNodeToWorldTransform() const
{
Mat4 t = this->getNodeToParentTransform();
/*获取本节点的空间坐标系转换成父节点的空间坐标系的变换矩阵,sprite基于父节点的变换矩阵为 <img src="https://img-blog.csdn.net/20141011143000281" alt="" />*/
for (Node *p = _parent; p != nullptr; p =p->getParent())
{
t = p->getNodeToParentTransform() *t;
}<pre name="code" class="cpp"><p> //不停的获取本节点的空间坐标系转换成父节点的空间坐标系的变换矩阵和当前变换矩阵相乘,直至追溯到本节点的空间坐标系转换成Scene的空间坐标系的变换矩阵</p><p> return t; //<img src="https://img-blog.csdn.net/20141011143000281" style="font-family: Arial, Helvetica, sans-serif;" alt="" /></p><p>}</p>
在getNodeToWorldTransform()获取sprite2节点的空间坐标系转换成Scene的空间坐标系的变换矩阵。由于Scene坐标系和世界坐标系重合,故sprite2节点的空间坐标系转换成世界坐标系的变换矩阵。
constMat4& Node::getNodeToParentTransform() const
{
if (_transformDirty) {
float x = _position.x;//sprite坐标位置
float y = _position.y;
float z = _positionZ;//Coso2dx为2D游戏引擎,一般_positionZ=0
if (_ignoreAnchorPointForPosition) {//Scene使用
x += _anchorPointInPoints.x;
y += _anchorPointInPoints.y;
}
float cx = 1, sx = 0, cy = 1, sy = 0;
if (_rotationZ_X || _rotationZ_Y){ //旋转处理
float radiansX =-CC_DEGREES_TO_RADIANS(_rotationZ_X);
float radiansY = -CC_DEGREES_TO_RADIANS(_rotationZ_Y);
cx = cosf(radiansX);
sx = sinf(radiansX);
cy = cosf(radiansY);
sy = sinf(radiansY);
}
bool needsSkewMatrix = ( _skewX ||_skewY );
Vec2 anchorPoint;
anchorPoint.x = _anchorPointInPoints.x* _scaleX;//放大缩小
anchorPoint.y = _anchorPointInPoints.y* _scaleY;
if (! needsSkewMatrix && !_anchorPointInPoints.equals(Vec2::ZERO)){
<span style="white-space:pre"> </span> //计算sprite2在描点(0,0)坐标
x += cy * -anchorPoint.x + -sx *-anchorPoint.y;
y += sy * -anchorPoint.x + cx * -anchorPoint.y;
}
float mat[] = {
cy * _scaleX, sy * _scaleX, 0, 0,
-sx * _scaleY, cx * _scaleY, 0, 0,
0, 0, _scaleZ, 0,
x, y, z, 1 };
/*mat= <img src="https://img-blog.csdn.net/20141011143000281" alt="" />*/
_transform.set(mat);
if(!_ignoreAnchorPointForPosition) {
//将矩阵转换成相对于设置锚点位置坐标矩阵,这么做事为下面旋转做铺垫,毕竟旋转是以锚点为中心
_transform.translate(anchorPoint.x,anchorPoint.y, 0);
}
if(_rotationY) { //延Y轴旋转
Mat4 rotY;
Mat4::createRotationY(CC_DEGREES_TO_RADIANS(_rotationY),&rotY);
_transform = _transform * rotY;
}
if(_rotationX) {//延X轴旋转
Mat4 rotX;
Mat4::createRotationX(CC_DEGREES_TO_RADIANS(_rotationX),
&rotX);
_transform = _transform * rotX;
}
if(!_ignoreAnchorPointForPosition) {
<span style="white-space:pre"> </span> //将矩阵转换成相对于锚点(0,0)位置坐标矩阵
_transform.translate(-anchorPoint.x, -anchorPoint.y, 0);
}
if (needsSkewMatrix) { //斜切处理
Mat4 skewMatrix(1,(float)tanf(CC_DEGREES_TO_RADIANS(_skewY)),0, 0,(float)tanf(CC_DEGREES_TO_RADIANS(_skewX)),1, 0, 0,
0, 0, 1,0,
0, 0, 0,1);
_transform = _transform *skewMatrix;
if(!_anchorPointInPoints.equals(Vec2::ZERO)) {
_transform.m[12]+= _transform.m[0] *-_anchorPointInPoints.x + _transform.m[4] *-_anchorPointInPoints.y;
_transform.m[13] += _transform.m[1] * -_anchorPointInPoints.x + _transform.m[5]* -_anchorPointInPoints.y;
}
}
if (_useAdditionalTransform) {//没用过,不知道作用,使用
<span style="white-space:pre"> </span> //setAdditionalTransform设置_additionalTransform后就会执行
_transform = _transform *_additionalTransform;
}
_transformDirty = false;
}
return _transform;
}
其他的转换过程和convertToNodeSpace类似,这里就不一一列举了。