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

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

———转载自:https://blog.csdn.net/qq_32934429

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

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


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

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


   
   
  1. //update camera's position
  2. _camera->setPosition3D(_orc->getPosition3D() + camera_offset);

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


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


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


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


   
   
  1. // MARK: Camera 位于cocos/2d/CCNode.cpp中,看到就明白了,第二个参数默认为true
  2. void Node::setCameraMask( unsigned short mask, bool applyChildren)
  3. {
  4. _cameraMask = mask;
  5. if (applyChildren)
  6. {
  7. for ( const auto& child : _children)
  8. {
  9. child->setCameraMask(mask, applyChildren);
  10. }
  11. }
  12. }

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


   
   
  1. void HelloWorld::onTouchesMoved( const std:: vector<Touch*>& touchs, Event * event)
  2. {
  3. //log("call onTouchesMoved");
  4. //推进角度
  5. /*if (!touchs.empty()) {
  6. Point touch = touchs[0]->getPreviousLocation() - touchs[0]->getLocation();
  7. _distanceZ -= touch.y * 0.1f;
  8. updateCameraTransform();
  9. }*/
  10. //自由角度
  11. if (touchs.size() == 1) {
  12. Point newPos = touchs[ 0]->getPreviousLocation() - touchs[ 0]->getLocation();
  13. Vec3 cameraPos = _camera->getPosition3D();
  14. cameraPos.z -= newPos.y;
  15. cameraPos.x += newPos.x;
  16. _camera->setPosition3D(cameraPos);
  17. }
  18. }


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

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

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


   
   
  1. if ( 0) {
  2. auto sbg = Sprite::create( "model/plane.png");
  3. //sbg->setCameraMask(2);
  4. sbg->setRotation3D(Vec3( 90, 0, 0)); //水平旋转90,放在世界中心位置
  5. sbg->setPosition3D(_center);
  6. _layer3D->addChild(sbg, -1); //放于底层
  7. }
暂时没有深入,后续会详细测试,说实话我也是个入门摸索阶段,基本上你很难在网上找到一些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图形,一般来说,你可以通过下面的方式初始化:


   
   
  1. //terrain test
  2. Terrain:: 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)


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

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


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

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


   
   
  1. void HelloWorld::onTouchesEnded( const std:: vector<Touch*>& touches, cocos2d::Event *event)
  2. {
  3. for ( auto &item : touches)
  4. {
  5. auto touch = item;
  6. auto location = touch->getLocationInView(); //获取点击的屏幕坐标
  7. if (_camera)
  8. {
  9. if (_orc)
  10. {
  11. //log("onTouchesEnded...");
  12. Vec3 nearP(location.x, location.y, -1.0f), farP(location.x, location.y, 1.0f);
  13. auto size = Director::getInstance()->getWinSize();
  14. //_camera->unprojectGL(size, &nearP, &nearP);
  15. //_camera->unprojectGL(size, &farP, &farP);
  16. //将屏幕坐标转为世界坐标,这里要不要指定大小,需要根据实际情况而定
  17. nearP = _camera->unproject(nearP);
  18. farP = _camera->unproject(farP);
  19. Vec3 dir(farP - nearP);
  20. if ( 1) { //注意这里,如果你采用了地形贴图的方式,则走if语句
  21. dir.normalize();
  22. bool isInTerrain = _terrain->getIntersectionPoint(Ray(nearP, dir),_targetPos);
  23. return;
  24. }
  25. float dist = 0.0f;
  26. float ndd = Vec3::dot(Vec3( 0, 1, 0), dir);
  27. if (ndd == 0)
  28. dist = 0.0f;
  29. float ndo = Vec3::dot(Vec3( 0, 1, 0), nearP);
  30. dist = ( 0 - ndo) / ndd;
  31. Vec3 p = nearP + dist * dir;
  32. //防越界
  33. if (p.x > 190)
  34. p.x = 190;
  35. if (p.x < -190)
  36. p.x = -190;
  37. if (p.z > 190)
  38. p.z = 190;
  39. if (p.z < -190)
  40. p.z = -190;
  41. //p.z = -p.z;
  42. _targetPos = p;
  43. //log("move3D %f-%f-%f", _targetPos.x, _targetPos.y, _targetPos.z);
  44. //_terrain->getIntersectionPoint();
  45. }
  46. }
  47. }
  48. }
上面方法的最终目的就是将我们点击的屏幕坐标,转化为世界中的坐标,然后,我们通过schedule方式更新:


   
   
  1. void HelloWorld::update( float fDelta)
  2. {
  3. updateState(fDelta); //不断更新物体状态
  4. if (isState(_curState, State_Move))
  5. {
  6. move3D(fDelta); //移动物体
  7. if (isState(_curState, State_Rotate)) //如果需要,则旋转物体
  8. {
  9. Vec3 curPos = _orc->getPosition3D();
  10. Vec3 newFaceDir = _targetPos - curPos;
  11. newFaceDir.y = 0;
  12. newFaceDir.normalize();
  13. turn(newFaceDir); //旋转
  14. }
  15. }
  16. //update camera
  17. _camera->setPosition3D(_orc->getPosition3D() + camera_offset); //上面有介绍
  18. //set Y
  19. _orc->setPositionY(_terrain->getHeight(_orc->getPositionX(), _orc->getPositionZ())); //额,下面有介绍
  20. }

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

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

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


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


   
   
  1. void HelloWorld::move3D( float elapsedTime)
  2. {
  3. Vec3 curPos = _orc->getPosition3D();
  4. Vec3 newFaceDir = _targetPos - curPos;
  5. newFaceDir.y = 0.0f;
  6. newFaceDir.normalize();
  7. //位移向量*速度*时间
  8. Vec3 offset = newFaceDir * 50.0f * elapsedTime;
  9. curPos += offset;
  10. _orc->setPosition3D(curPos);
  11. offset.x = offset.x;
  12. offset.z = offset.z;
  13. //pass the newest orc position
  14. if (_state) {
  15. _state->setUniformVec3( "u_target_pos", _orc->getPosition3D());
  16. }
  17. //log("move3D %f-%f-%f",newFaceDir.x,newFaceDir.y,newFaceDir.z);
  18. }
最后是旋转,代码来源cocos2dx示例:


   
   
  1. //旋转3D精灵
  2. void HelloWorld::turn(Vec3 dir) {
  3. Vec3 axis;
  4. float angle = -1 * acos(dir.dot(Vec3( 0, 0, -1)));
  5. dir.cross(dir, Vec3( 0, 0, -1), &axis);
  6. Quaternion q2;
  7. q2.createFromAxisAngle(Vec3( 0, 1, 0), ( float)-M_PI, &q2);
  8. Quaternion headingQ;
  9. headingQ.createFromAxisAngle(axis, angle, &headingQ);
  10. _orc->setRotationQuat(headingQ*q2);
  11. }
## 标题 ##











  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值