———-基于cocos2dx 3.9 + cocos studio 2.3.3.0 + VS2015测试
———转载自:https://blog.csdn.net/qq_32934429
一、关于世界摄像头,如果你不知道该如何设置,下面的几种方式可选其一:
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中,看到就明白了,第二个参数默认为true
-
void 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 test
-
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)
-
_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 position
-
if (_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);
-
}
## 标题 ##