cocos2dx 学习笔记之摄像头与3D精灵的移动

来源:互联网 发布:网络兼职英语教师招聘 编辑:程序博客网 时间:2024/05/17 04:18

----------基于cocos2dx 3.9 + cocos studio 2.3.3.0 + VS2015测试

一、关于世界摄像头,如果你不知道该如何设置,下面的几种方式可选其一:

  1 第三人称视角(多数2D~3D游戏中,摄像头跟随主角形式),初始化时,斜向下45°取景,此时不要调用lookat:

auto camera = Camera::createPerspective(60.0, (float)winSize.width / winSize.height, 1, 1000);_camera->setRotation3D(Vec3(-45, 0, 0));

在场景的更新中,不断更新位置(跟随人物),放置在人物的斜上方(一般来说高度由Y决定,前后距离由Z决定):

//update camera's position_camera->setPosition3D(_orc->getPosition3D() + camera_offset);

2 如果是第一人称视角,则需要将camera挂在主角身上:初始化之后挂载到人物上

auto _camera = Camera::createPerspective(60, (float)winSize.width / winSize.height, 1, 500);_camera->setPosition3D(Vec3(0, 1, 0));_camera->lookAt(Vec3(0, 0, 1));_orc->addChild(_camera);
之后再场景切换中,不断设置摄像机的位置以及观察点:

_camera->setPosition3D(Vec3(0, 1, 0));_camera->lookAt(_orc->getPosition3D() + Vec3(0, 0, 1), Vec3(0.0f, 1.0f, 0.0f));//第二个参数可省略,此为默认值,观察物体正前方
最后,camera有个setCameraFlag属性值,在3D世界中,所有需要被观察的物体都需要通过setCameraMask的mask值与flag做与操作,如果不为0,则表示受此摄像机观察,如果你不想写的这么麻烦,或者怕漏掉,可以直接在所有3D元素的基层layer中最后调用一次就好,一般如下:

_layer3D = Layer::create();addChild(_layer3D);//初始化摄像机 //初始化UI//初始化世界对象...//通过_layer3D->addChild将所有node添加到地图//最后_layer3D->setCameraMask(2);//具体的值根据实际情况而定
=============================分割线=============================

// MARK: Camera 位于cocos/2d/CCNode.cpp中,看到就明白了,第二个参数默认为truevoid Node::setCameraMask(unsigned short mask, bool applyChildren){    _cameraMask = mask;    if (applyChildren)    {        for (const auto& child : _children)        {            child->setCameraMask(mask, applyChildren);        }    }}

如果你想通过前后左右来移动镜头:

void HelloWorld::onTouchesMoved(const std::vector<Touch*>& touchs, Event * event){//log("call onTouchesMoved");//推进角度/*if (!touchs.empty()) {Point touch = touchs[0]->getPreviousLocation() - touchs[0]->getLocation();_distanceZ -= touch.y * 0.1f;updateCameraTransform();}*///自由角度if (touchs.size() == 1) {Point newPos = touchs[0]->getPreviousLocation() - touchs[0]->getLocation();Vec3 cameraPos = _camera->getPosition3D();cameraPos.z -= newPos.y;cameraPos.x += newPos.x;_camera->setPosition3D(cameraPos);}}


二、关于3D精灵触摸的简单交互:目标在一个3D地图中,人物朝我们点击的位置移动

首先,我们需要一个地图界面,在3D中,如何方式我们想要的效果呢?

1 用2D贴图实现效果,测试后,发现图片模糊严重,可能和我们取得素材有关系,下面是一种可能的实现方式:

if (0) {    auto sbg = Sprite::create("model/plane.png");    //sbg->setCameraMask(2);    sbg->setRotation3D(Vec3(90, 0, 0));//水平旋转90,放在世界中心位置    sbg->setPosition3D(_center);    _layer3D->addChild(sbg, -1);//放于底层}
暂时没有深入,后续会详细测试,说实话我也是个入门摸索阶段,基本上你很难在网上找到一些cocos2d介绍新特性的文章,这也是写这篇文章的目的,下面是采用3D里的地形方式实现,即Terrain:

2 如果你想大概知道这是个啥玩意,可以先看下面这篇文章:

http://edu.the9.com/a/xingyexinxi/Cocos2d_x/2015/0506/271.html

我们直接打开官方的文档,能看到三个构造函数(最复杂的如下):

 TerrainData (const char *heightMapsrc, const char *alphamap, const DetailMap &detail1, const DetailMap &detail2, const DetailMap &detail3, const DetailMap&detail4, const Size &chunksize=Size(32, 32), float mapHeight=2, float mapScale=0.1)
构造函数,该构造函数构造一个地形,有4个detailmaps,1个Alpha地图 

   第一个参数:高度图(如果你和我一样没用过PS 3DX一些图形工具的画,还是先百度吧)一种特殊的灰度图(黑白),表现世界中的地形(越亮的位置,表示地形相对越高)(这里,如果你用第一个构造形式,会用你指定的texture平铺满高度图,实际效果你可以想象~~~一片那啥,我看实际效果好像是拉伸的形式,反正效果很差,如果你只是想平铺,下面会有一种更好的形式)

  第二个参数:alpha贴图,你如果想知道,可以看看上面的链接;你打开test中cpptest的资源文件夹下的alphamap.png,可以看到它是一个127*127 由r g b a通道组成的png图片,在结合4个detailmaps的参数,你是不是明白了什么呢。实际上,alpha贴图上每一个块(块的大小默认是32px)的元素表示了 r g b a 所占的权重比,如果你的r g b a设置为同一个图片,同时将倒数第二个参数mapHeight设为 0.000001啥的,就是上面说的平铺了

第三到六个参数:具体的 r g b a图形,一般来说,你可以通过下面的方式初始化:

//terrain testTerrain::DetailMap r("model/Grass1.jpg"), g("model/Grass1.jpg"), b("model/Grass1.jpg"), a("model/Grass1.jpg");
第七个参数是每个块的大小,默认为32

第八个参数是地形上最大的高度

第九个是地形的缩放值,举个例子,现在我们假定我们的世界大小是 640x 640,但是高度图大小为 257x257,如果想铺满我们的世界:

(mapScale = 640/ 256),普及来说,如果是W * H呢,mapScale = Math.max(W , H)/  (高度图的大小 - 1)

_terrain = Terrain::create(data, Terrain::CrackFixedType::SKIRT);//裂缝修补方式_terrain->setMaxDetailMapAmount(4);//最大细节层数//_terrain->setDrawWire(false);//_terrain->setSkirtHeightRatio(3);_terrain->setLODDistance(64, 128, 192);//裂缝距离_layer3D->addChild(_terrain);

上面有点啰嗦,如果你看晕了可以跳着看~扯得有点远了,回归主题,现在我们有了一个3D的草皮了~下面加对象~很简单:

_orc = Sprite3D::create("model/mod_niutou.c3b");_orc->setScale(.1f);//大小矫正_orc->setRotation3D(Vec3(0, 180, 0));//方向矫正_orc->setPosition3D(Vec3(0, 0, 10));_targetPos = _orc->getPosition3D();//纪录物体位置_layer3D->addChild(_orc);

现在我们有了地图和物体,就剩下移动拉~,触发点击我们只监听touchesEnd

void HelloWorld::onTouchesEnded(const std::vector<Touch*>& touches, cocos2d::Event *event){for (auto &item : touches){auto touch = item;auto location = touch->getLocationInView();//获取点击的屏幕坐标if (_camera){if (_orc){//log("onTouchesEnded...");Vec3 nearP(location.x, location.y, -1.0f), farP(location.x, location.y, 1.0f);auto size = Director::getInstance()->getWinSize();//_camera->unprojectGL(size, &nearP, &nearP);//_camera->unprojectGL(size, &farP, &farP);//将屏幕坐标转为世界坐标,这里要不要指定大小,需要根据实际情况而定nearP = _camera->unproject(nearP);farP = _camera->unproject(farP);Vec3 dir(farP - nearP);if (1) {//注意这里,如果你采用了地形贴图的方式,则走if语句    dir.normalize();    bool isInTerrain = _terrain->getIntersectionPoint(Ray(nearP, dir),_targetPos);    return;}float dist = 0.0f;float ndd = Vec3::dot(Vec3(0, 1, 0), dir);if (ndd == 0)dist = 0.0f;float ndo = Vec3::dot(Vec3(0, 1, 0), nearP);dist = (0 - ndo) / ndd;Vec3 p = nearP + dist *  dir;                                //防越界if (p.x > 190)p.x = 190;if (p.x < -190)p.x = -190;if (p.z > 190)p.z = 190;if (p.z < -190)p.z = -190;//p.z = -p.z;_targetPos = p;//log("move3D %f-%f-%f", _targetPos.x, _targetPos.y, _targetPos.z);//_terrain->getIntersectionPoint();}}}}
上面方法的最终目的就是将我们点击的屏幕坐标,转化为世界中的坐标,然后,我们通过schedule方式更新:

void HelloWorld::update(float fDelta){updateState(fDelta);//不断更新物体状态if (isState(_curState, State_Move)){move3D(fDelta);//移动物体if (isState(_curState, State_Rotate))//如果需要,则旋转物体{Vec3 curPos = _orc->getPosition3D();Vec3 newFaceDir = _targetPos - curPos;newFaceDir.y = 0;newFaceDir.normalize();turn(newFaceDir);//旋转}}//update camera_camera->setPosition3D(_orc->getPosition3D() + camera_offset);//上面有介绍//set Y _orc->setPositionY(_terrain->getHeight(_orc->getPositionX(), _orc->getPositionZ()));//额,下面有介绍}

首先我们需要不断的更新物体状态,比如在物体移动时,可能我们又点了一下,那么状态就会有更新(其实主要是旋转的状态,位移只需要重新设置终点即可)

这里要说明的是上面最后一段代码,设置了物体的Y,注意这里我们使用了地形,那么地形肯定就有高和低了,物体的Y随着地形需要做动态的变化,SO~terrain通过当前对象位于世界的x与z坐标值计算出了相应的地形高度

下面是状态更新的代码,代码来源cocos2dx示例:

void HelloWorld::updateState(float elapsedTime){Vec3 curPos = _orc->getPosition3D();//当前位置Vec3 curFaceDir;_orc->getNodeToWorldTransform().getForwardVector(&curFaceDir);//当前朝向的向量curFaceDir = -curFaceDir;curFaceDir.normalize();Vec3 newFaceDir = _targetPos - curPos;//根据新的位置获取其向量newFaceDir.y = 0.0f;newFaceDir.normalize();float cosAngle = std::fabs(Vec3::dot(curFaceDir, newFaceDir) - 1.0f);//计算二个向量的点积,获取偏转角度?我没看明白。。。。float dist = curPos.distanceSquared(_targetPos);//二点间距离if (dist <= 4.0f)//如果距离过小,则只考虑是否需要旋转{if (cosAngle <= 0.01f)_curState = State_Idle;else_curState = State_Rotate;}else//移动的同时考虑是否旋转{if (cosAngle>0.01f)_curState = State_Rotate | State_Move;else_curState = State_Move;}}
下面是移动,很简单,就不多说了,注意他的移动不是通过action而是一步一步的:

void HelloWorld::move3D(float elapsedTime){Vec3 curPos = _orc->getPosition3D();Vec3 newFaceDir = _targetPos - curPos;newFaceDir.y = 0.0f;newFaceDir.normalize();//位移向量*速度*时间Vec3 offset = newFaceDir * 50.0f * elapsedTime;curPos += offset;_orc->setPosition3D(curPos);offset.x = offset.x;offset.z = offset.z;//pass the newest orc positionif (_state) {_state->setUniformVec3("u_target_pos", _orc->getPosition3D());}//log("move3D %f-%f-%f",newFaceDir.x,newFaceDir.y,newFaceDir.z);}
最后是旋转,代码来源cocos2dx示例:

//旋转3D精灵void HelloWorld::turn(Vec3 dir) {Vec3 axis;float angle = -1 * acos(dir.dot(Vec3(0, 0, -1)));dir.cross(dir, Vec3(0, 0, -1), &axis);Quaternion q2;q2.createFromAxisAngle(Vec3(0, 1, 0), (float)-M_PI, &q2);Quaternion headingQ;headingQ.createFromAxisAngle(axis, angle, &headingQ);_orc->setRotationQuat(headingQ*q2);}
先就这样了,后面写的比较急,因为很多我也是都示例代码,测试实现的~期待后续更新吧~











s
0 0
原创粉丝点击