Cocos2dx-3.x 中CCCamera相机类详解及源码分析

Cocos2d-x 3.3版本中加入了相机这个类,该类在3D游戏中是必不可少的,在3D立体游戏中,往往需要视野角度的变化,通过相机的变换才能观察和体验整个游戏世界。
CCCamera类基本使用
在游戏中一般有两种类型的相机:一种是透视相机,它在3D游戏中十分常见;另一种是正交相机,它没有透视相机的近大远小的效果而是相机内任何位置的物体大小比例都是一样的。

上图是透视相机的原理图,一般来说,我们通过以下代码创建:

_camera = Camera::createPerspective(60,(GLfloat)s.width/s.height,1,1000);

上面的代码就是创建了一个透视投影的相机,下面我来说明下参数的意义:第一个参数是FOV,即视场角(field of view),它可以理解为你的视线左右能看多宽(以角度计)第二个就是上述所有的宽高比,最后两个是相机的近裁面和远裁面,这个也很好理解,距离相机比近裁面还要近的,比远裁面还要远的,都不会被渲染到。


上图是正交相机的原理图,一般来说,我们通过以下代码创建:

_camera = Camera::createOrthographic(s.width,s.height,1000);

上面的代码就是创建了一个正交投影的相机,下面我来说明下参数的意义:第一个参数是相机的宽度,第二个就是相机的高度,最后两个是相机的近裁面和远裁面,这个也很好理解,距离相机比近裁面还要近的,比远裁面还要远的,都不会被渲染到。这个和透视相机是一样的。

接下来,我们需要对相机设置一个标记位(FLAG),这样可以让相机与其他的相机区分开来–在一些游戏的应用中,通常不仅仅只有一个相机,如果有多个相机的话,那么我们要标记一个物体,到底是要被哪一个相机所”看到”,这时候,我们就需要设置它的CameraMask来与相机的Flag对应:

_layer3D->setCameraMask(2);
_camera->setCameraFlag(CameraFlag::USER1);

这样_layer3D就能被_camera看到。
注意到Camera中有个_cameraFlag属性,为枚举类型,定义如下:

enum class CameraFlag {
    DEFAULT = 1,USER1 = 1 << 1,USER2 = 1 << 2,USER3 = 1 << 3,USER4 = 1 << 4,USER5 = 1 << 5,USER6 = 1 << 6,USER7 = 1 << 7,USER8 = 1 << 8,};

Node中有个_cameraMask的属性,当相机的_cameraFlag & _cameraMask为true时,该Node可以被该相机看到。所以在上述相机的创建代码中,camera的CameraFlag设置为CameraFlag::USER1,并且该layer的CameraMask为2,则表示该layer只能被CameraFlag::USER1相机看到。如果你设置的精灵的cameraMask是3的话,它也是能被cameraFlag为CameraFlag::USER1和CameraFlag::USER2的相机看到的。我们还要注意如果你的精灵是在layer设置cameraMask之后添加的,它是不会被看到的,还需要手动再设置精灵的cameraMask。不要以为这样就可以了,最后我们还要把相机添加到场景中,不然我们还是看不到效果的,一定要记住呀,下图就是把相机加到场景中的代码:

_layer3D->addChild(_camera);

这样,通过设置相机的位置,角度等参数就能实现不同视角观察游戏世界。

CCCamera类源码分析

下面是CCCamera.h文件内容:

#ifndef _CCCAMERA_H__
#define _CCCAMERA_H__

#include "2d/CCNode.h"
#include "3d/CCFrustum.h"
#include "renderer/CCQuadCommand.h"
#include "renderer/CCCustomCommand.h"
#include "renderer/CCFrameBuffer.h"

NS_CC_BEGIN

class Scene;
class CameraBackgroundBrush;

/** 相机标识,每个Node中有个_cameraMask的属性,当相机的_cameraFlag & _cameraMask为true时,该Node可以被该相机看到。 */

enum class CameraFlag
{
    DEFAULT = 1,};
/** 定义一个相机类,该类继承于Node。 */
class CC_DLL Camera :public Node
{
    /** 友元类有场景类,导演类以及事件分发类。 */
    friend class Scene;
    friend class Director;
    friend class EventDispatcher;
public:
    ;/** 枚举类标记:透视相机和正交相机。 */
    enum class Type
    {
        PERSPECTIVE = 1,ORTHOGRAPHIC = 2
    };
public:

    ;/** 创建一个透视相机。 参数: fieldOfView 透视相机的可视角度 (一般是在40-60度之间). aspectRatio 相机的长宽比(通常会使用视窗的宽度除以视窗的高度)。 nearPlane 近平面的距离。 farPlane 远平面的距离。 */
    static Camera* createPerspective(float fieldOfView,float aspectRatio,float nearPlane,float farPlane);
    /** 创建一个正交相机。 参数: zoomX 沿x轴的正交投影的缩放因子(正交投影的宽度)。 zoomY 沿y轴的正交投影的缩放因子(正交投影的高度)。 nearPlane 近平面的距离。 farPlane 远平面的距离。 */
    static Camera* createOrthographic(float zoomX,float zoomY,float farPlane);

    /** 创建默认的相机,相机的类型取决于Director::getProjection,默认的相机深度是0 */
    static Camera* create();

    /** 获取相机类型。 */
    Camera::Type getType() const { return _type; }

    /** 获取和设置相机标识。类型为枚举类和无符号短整型。 */
    CameraFlag getCameraFlag() const { return (CameraFlag)_cameraFlag; }
    void setCameraFlag(CameraFlag flag) { _cameraFlag = (unsigned short)flag; }

    /** 使相机看着目标 参数: target 目标的位置 up 相机向上的向量,通常这是Y轴 */
    virtual void lookAt(const Vec3& target,const Vec3& up = Vec3::UNIT_Y);

    /** 获取相机的投影矩阵。 返回: 相机投影矩阵。 */
    const Mat4& getProjectionMatrix() const;
    /** 获取相机的视图矩阵。 返回: 相机视图矩阵。 */
    const Mat4& getViewMatrix() const;

    /** 得到视图投影矩阵。 */
    const Mat4& getViewProjectionMatrix() const;

    /* 把指定坐标点从世界坐标转换为屏幕坐标。 原点在GL屏幕坐标系的左下角。 参数: src 世界的位置。 返回: 屏幕的位置。 */
    Vec2 project(const Vec3& src) const;

    /* 把指定坐标点从3D世界坐标转换为屏幕坐标。 原点在GL屏幕坐标系的左下角。 参数: src 3D世界的位置。 返回: GL屏幕空间的位置。 */
    Vec2 projectGL(const Vec3& src) const;

    /** 把指定坐标点从屏幕坐标转换为世界坐标。 原点在GL屏幕坐标系的左下角。 参数: src 屏幕的位置。 返回: 世界的位置。 */
    Vec3 unproject(const Vec3& src) const;

    /** 把指定坐标点从屏幕坐标转换为3D世界坐标。 原点在GL屏幕坐标系的左下角。 参数 src GL屏幕空间的位置。 返回 3D世界的位置。 */
    Vec3 unprojectGL(const Vec3& src) const;

    /** 把指定坐标点从屏幕坐标转换为世界坐标。 原点在GL屏幕坐标系的左下角。 参数 size 使用的视窗大小。 src 屏幕的位置。 dst 世界的位置。 */
    void unproject(const Size& size,const Vec3* src,Vec3* dst) const;

    /** 把指定坐标点从屏幕坐标转换为3D世界坐标。 原点在GL屏幕坐标系的左下角。 参数: size 使用的窗口大小。 src GL屏幕空间的位置。 dst 3D世界的位置。 */
    void unprojectGL(const Size& size,Vec3* dst) const;

    /** aabb在视椎体内是否可见 */
    bool isVisibleInFrustum(const AABB* aabb) const;

    /** 获取朝向相机的物体深度。 */
    float getDepthInView(const Mat4& transform) const;

    /** 设置深度,相比深度小的,深度较大的相机会绘制在顶端,标识是CameraFlag::DEFAULT的相机深度是0,用户定义的相机深度默认为-1 */
    void setDepth(int8_t depth);

    /** 获取深度,相比深度小的,深度较大的相机会绘制在顶端,标识是CameraFlag::DEFAULT的相机深度是0,用户定义的相机深度默认为-1 */
    int8_t getDepth() const { return _depth; }

    /** 获取渲染顺序。 */
    int getRenderOrder() const;

    /** 获取视椎体远平面。 */
    float getFarPlane() const { return _farPlane; }

    /** 获取视椎体近平面。 */
    float getNearPlane() const { return _nearPlane; }

    //复写
    virtual void onEnter() override;
    virtual void onExit() override;

    /** 获取绘制的相机,绘制的相机会在Scene::render中设置。 */
    static const Camera* getVisitingCamera() { return _visitingCamera; }

    /** 获取到当前运行场景的默认相机。 */
    static Camera* getDefaultCamera();
    /** 在相机渲染所属的场景前,需要对背景进行清除。它以默认的深度值清除缓存,可以通过setBackgroundBrush 函数获取深度值。 */
    void clearBackground();
    /** 应用帧缓冲,渲染目标和视图。 */
    void apply();
    /** 设置帧缓冲,从中可以获取到一些需要渲染的目标。 */
    void setFrameBufferObject(experimental::FrameBuffer* fbo);
    /** 设置相机视口。 */
    void setViewport(const experimental::Viewport& vp) { _viewport = vp; }

    /** 视图矩阵是否在上一帧被更新。 */
    bool isViewProjectionUpdated() const {return _viewProjectionUpdated;}

    /** 设置背景刷,通过CameraBackgroundBrush 查看更多详情。 */
    void setBackgroundBrush(CameraBackgroundBrush* clearBrush);

    /** 获取背景刷。 */
    CameraBackgroundBrush* getBackgroundBrush() const { return _clearBrush; }
    /** 遍历所有子节点,并且循环递归得发送它们的渲染指令。 参数: renderer 指定一个渲染器 parentTransform 父节点放射变换矩阵 parentFlags 渲染器标签 重载 Node . */
    virtual void visit(Renderer* renderer,const Mat4 &parentTransform,uint32_t parentFlags) override;

CC_CONSTRUCTOR_ACCESS:
    Camera();
    ~Camera();

    /** 设置场景,这个方法不应该手动调用 . */
    void setScene(Scene* scene);

    /** 对投影矩阵设置额外的矩阵,在WP8平台使用时,调用的时候它会乘以投影矩阵 */
    void setAdditionalProjection(const Mat4& mat);

    /** 初始化相机。包括透视相机和正交相机。 */
    bool initDefault();
    bool initPerspective(float fieldOfView,float farPlane);
    bool initOrthographic(float zoomX,float farPlane);
    void applyFrameBufferObject();
    void applyViewport();
protected:

    Scene* _scene; //相机所属的场景。
    Mat4 _projection;
    mutable Mat4 _view;
    mutable Mat4 _viewInv;
    mutable Mat4 _viewProjection;
    Vec3 _up;
    Camera::Type _type;
    float _fieldOfView;
    float _zoom[2];
    float _aspectRatio;
    float _nearPlane;
    float _farPlane;
    mutable bool  _viewProjectionDirty;
    bool _viewProjectionUpdated; //视图矩阵是否在上一帧中被更新。
    unsigned short _cameraFlag; // 相机标识
    mutable Frustum _frustum;   // 相机投影平面
    mutable bool _frustumDirty;
    int8_t  _depth;                 //相机深度
    static Camera* _visitingCamera;

    CameraBackgroundBrush* _clearBrush; 

    experimental::Viewport _viewport;

    experimental::FrameBuffer* _fbo;
protected:
    static experimental::Viewport _defaultViewport;
public:
    static const experimental::Viewport& getDefaultViewport() { return _defaultViewport; }
    static void setDefaultViewport(const experimental::Viewport& vp) { _defaultViewport = vp; }
};

NS_CC_END

#endif// __CCCAMERA_H_

好的,我们大致了解了相机类中有哪些方法,接下来我们看具体实现。

下面是CCCamera.cpp的内容:

#include "2d/CCCamera.h"
#include "2d/CCCameraBackgroundBrush.h"
#include "base/CCDirector.h"
#include "platform/CCGLView.h"
#include "2d/CCScene.h"
#include "renderer/CCRenderer.h"
#include "renderer/CCQuadCommand.h"
#include "renderer/CCGLProgramCache.h"
#include "renderer/ccGLStateCache.h"
#include "renderer/CCFrameBuffer.h"
#include "renderer/CCRenderState.h"

NS_CC_BEGIN


Camera* Camera::_visitingCamera = nullptr;
experimental::Viewport Camera::_defaultViewport;

Camera* Camera::getDefaultCamera()
{
    //获取当前场景,并返回默认相机。
    auto scene = Director::getInstance()->getRunningScene();
    if(scene)
    {
        return scene->getDefaultCamera();
    }

    return nullptr;
}

Camera* Camera::create()
{
    //创建一个相机,根据投影类型初始化相机为透视相机或者正交相机。
    Camera* camera = new (std::nothrow) Camera();
    camera->initDefault();
    //设置回收释放及相机深度。
    camera->autorelease();
    camera->setDepth(0.f);

    return camera;
}

Camera* Camera::createPerspective(float fieldOfView,float farPlane)
{
    auto ret = new (std::nothrow) Camera();
    if (ret)
    {
        //初始化透视相机。
        ret->initPerspective(fieldOfView,aspectRatio,nearPlane,farPlane);
        ret->autorelease();
        return ret;
    }
    CC_SAFE_DELETE(ret);
    return nullptr;
}

Camera* Camera::createOrthographic(float zoomX,float farPlane)
{
    auto ret = new (std::nothrow) Camera();
    if (ret)
    {
        //初始化正交相机。
        ret->initOrthographic(zoomX,zoomY,farPlane);
        ret->autorelease();
        return ret;
    }
    CC_SAFE_DELETE(ret);
    return nullptr;
}
//构造函数,初始化参数列表。
Camera::Camera()
: _scene(nullptr),_viewProjectionDirty(true),_cameraFlag(1),_frustumDirty(true),_depth(-1),_fbo(nullptr)
{
    _frustum.setClipZ(true);
    _clearBrush = CameraBackgroundBrush::createDepthBrush(1.f);
    _clearBrush->retain();
}

Camera::~Camera()
{
    //释放帧缓存和背景刷。
    CC_SAFE_RELEASE_NULL(_fbo);
    CC_SAFE_RELEASE(_clearBrush);
}

const Mat4& Camera::getProjectionMatrix() const
{
    return _projection;
}
const Mat4& Camera::getViewMatrix() const
{
    Mat4 viewInv(getNodeToWorldTransform());
    static int count = sizeof(float) * 16;
    if (memcmp(viewInv.m,_viewInv.m,count) != 0)
    {
        _viewProjectionDirty = true;
        _frustumDirty = true;
        _viewInv = viewInv;
        _view = viewInv.getInversed();
    }
    return _view;
}
void Camera::lookAt(const Vec3& lookAtPos,const Vec3& up)
{
    //camera->lookAt必须在camera->setPostion3D之后,因为其在运行过程中调用了getPosition3D()

    //定义y方向的归一化向量。
    Vec3 upv = up;
    upv.normalize();
    //计算x、y、z、方向上的向量。
    Vec3 zaxis;
    Vec3::subtract(this->getPosition3D(),lookAtPos,&zaxis);
    zaxis.normalize();

    Vec3 xaxis;
    Vec3::cross(upv,zaxis,&xaxis);
    xaxis.normalize();

    Vec3 yaxis;
    Vec3::cross(zaxis,xaxis,&yaxis);
    yaxis.normalize();
    //将上面计算的向量值构造旋转矩阵。
    Mat4  rotation;
    rotation.m[0] = xaxis.x;
    rotation.m[1] = xaxis.y;
    rotation.m[2] = xaxis.z;
    rotation.m[3] = 0;

    rotation.m[4] = yaxis.x;
    rotation.m[5] = yaxis.y;
    rotation.m[6] = yaxis.z;
    rotation.m[7] = 0;
    rotation.m[8] = zaxis.x;
    rotation.m[9] = zaxis.y;
    rotation.m[10] = zaxis.z;
    rotation.m[11] = 0;
    /* 定义四元数,将旋转矩阵转换为四元数。 通过四元数来设置3D空间中的旋转角度。要保证四元数是经过归一化的。 */
    Quaternion  quaternion;
    Quaternion::createFromRotationMatrix(rotation,&quaternion);
    quaternion.normalize();
    setRotationQuat(quaternion);
}

const Mat4& Camera::getViewProjectionMatrix() const
{
    //获取视图矩阵
    getViewMatrix();
    if (_viewProjectionDirty)
    {
    //设置标记视图投影矩阵已更新
        _viewProjectionDirty = false;
        //投影矩阵和视图矩阵相乘得到视图投影矩阵。
        Mat4::multiply(_projection,_view,&_viewProjection);
    }

    return _viewProjection;
}

void Camera::setAdditionalProjection(const Mat4& mat)
{
    //可以在原基础投影上做投影
    _projection = mat * _projection;
    getViewProjectionMatrix();
}

bool Camera::initDefault()
{
    auto size = Director::getInstance()->getWinSize();
    //默认相机的初始化根据投影类型创建。
    auto projection = Director::getInstance()->getProjection();
    switch (projection)
    {
        case Director::Projection::_2D:
        {
            initOrthographic(size.width,size.height,-1024,1024);
            setPosition3D(Vec3(0.0f,0.0f,0.0f));
            setRotation3D(Vec3(0.f,0.f,0.f));
            break;
        }
        case Director::Projection::_3D:
        {
            /* zeye的大小:_winSizeInPoints.height(设计分辨率的高) / 1.1566f 1.1566约等于2/1.732,这个值正是使相机60度角视角正好覆盖屏幕分辨率高的值。 */
            float zeye = Director::getInstance()->getZEye();
            initPerspective(60,(GLfloat)size.width / size.height,10,zeye + size.height / 2.0f);
            Vec3 eye(size.width/2,size.height/2.0f,zeye),center(size.width/2,size.height/2,0.0f),up(0.0f,1.0f,0.0f);
            setPosition3D(eye);
            lookAt(center,up);
            break;
        }
        default:
            CCLOG("unrecognized projection");
            break;
    }
    return true;
}

bool Camera::initPerspective(float fieldOfView,float farPlane)
{
    _fieldOfView = fieldOfView;
    _aspectRatio = aspectRatio;
    _nearPlane = nearPlane;
    _farPlane = farPlane;
    //新建透视相机投影矩阵
    Mat4::createPerspective(_fieldOfView,_aspectRatio,_nearPlane,_farPlane,&_projection);
    _viewProjectionDirty = true;
    _frustumDirty = true;

    return true;
}

bool Camera::initOrthographic(float zoomX,float farPlane)
{
    _zoom[0] = zoomX;
    _zoom[1] = zoomY;
    _nearPlane = nearPlane;
    _farPlane = farPlane;
    //新建正交相机投影矩阵
    Mat4::createOrthographicOffCenter(0,_zoom[0],0,_zoom[1],&_projection);
    _viewProjectionDirty = true;
    _frustumDirty = true;

    return true;
}

Vec2 Camera::project(const Vec3& src) const
{
    Vec2 screenPos;

    auto viewport = Director::getInstance()->getWinSize();
    Vec4 clipPos;
    getViewProjectionMatrix().transformVector(Vec4(src.x,src.y,src.z,1.0f),&clipPos);

    CCASSERT(clipPos.w != 0.0f,"clipPos.w can't be 0.0f!");
    float ndcX = clipPos.x / clipPos.w;
    float ndcY = clipPos.y / clipPos.w;

    screenPos.x = (ndcX + 1.0f) * 0.5f * viewport.width;
    screenPos.y = (1.0f - (ndcY + 1.0f) * 0.5f) * viewport.height;
    return screenPos;
}

Vec2 Camera::projectGL(const Vec3& src) const
{
    Vec2 screenPos;

    auto viewport = Director::getInstance()->getWinSize();
    Vec4 clipPos;
    getViewProjectionMatrix().transformVector(Vec4(src.x,"clipPos.w can't be 0.0f!");
    float ndcX = clipPos.x / clipPos.w;
    float ndcY = clipPos.y / clipPos.w;

    screenPos.x = (ndcX + 1.0f) * 0.5f * viewport.width;
    screenPos.y = (ndcY + 1.0f) * 0.5f * viewport.height;
    return screenPos;
}

Vec3 Camera::unproject(const Vec3& src) const
{
    Vec3 dst;
    unproject(Director::getInstance()->getWinSize(),&src,&dst);
    return dst;
}

Vec3 Camera::unprojectGL(const Vec3& src) const
{
    Vec3 dst;
    unprojectGL(Director::getInstance()->getWinSize(),&dst);
    return dst;
}

void Camera::unproject(const Size& viewport,Vec3* dst) const
{
    CCASSERT(src && dst,"vec3 can not be null");

    Vec4 screen(src->x / viewport.width,((viewport.height - src->y)) / viewport.height,src->z,1.0f);
    screen.x = screen.x * 2.0f - 1.0f;
    screen.y = screen.y * 2.0f - 1.0f;
    screen.z = screen.z * 2.0f - 1.0f;

    getViewProjectionMatrix().getInversed().transformVector(screen,&screen);
    if (screen.w != 0.0f)
    {
        screen.x /= screen.w;
        screen.y /= screen.w;
        screen.z /= screen.w;
    }

    dst->set(screen.x,screen.y,screen.z);
}

void Camera::unprojectGL(const Size& viewport,src->y / viewport.height,screen.z);
}

bool Camera::isVisibleInFrustum(const AABB* aabb) const
{
    //每次判断视锥是否需要重新初始化
    if (_frustumDirty)
    {
        _frustum.initFrustum(this);
        _frustumDirty = false;
    }
    //判断aabb是否在视锥内
    return !_frustum.isOutOfFrustum(*aabb);
}

float Camera::getDepthInView(const Mat4& transform) const
{
    Mat4 camWorldMat = getNodeToWorldTransform();
    const Mat4 &viewMat = camWorldMat.getInversed();
    float depth = -(viewMat.m[2] * transform.m[12] + viewMat.m[6] * transform.m[13] + viewMat.m[10] * transform.m[14] + viewMat.m[14]);
    return depth;
}

void Camera::setDepth(int8_t depth)
{
    if (_depth != depth)
    {
        _depth = depth;
        if (_scene)
        {
            //更改了相机层级后需要重新更新渲染顺序
            _scene->setCameraOrderDirty();
        }
    }
}

void Camera::onEnter()
{
    if (_scene == nullptr)
    {
        auto scene = getScene();
        if (scene)
        {
            setScene(scene);
        }
    }
    Node::onEnter();
}

void Camera::onExit()
{
    // remove this camera from scene
    setScene(nullptr);
    Node::onExit();
}

void Camera::setScene(Scene* scene)
{
    //设置当前相机所属场景。
    if (_scene != scene)
    {
        /* 移除相机所属场景的相机列表中的该相机。 相机列表是一个数组容器,我们需要从中找到相机地址并移除。 */
        if (_scene)
        {
            auto& cameras = _scene->_cameras;
            auto it = std::find(cameras.begin(),cameras.end(),this);
            if (it != cameras.end())
                cameras.erase(it);
            _scene = nullptr;
        }
        /* 添加相机到场景,若场景的相机列表已经包含该相机则将相机添加到末尾,否则不做处理。 */
        if (scene)
        {
            _scene = scene;
            auto& cameras = _scene->_cameras;
            auto it = std::find(cameras.begin(),this);
            if (it == cameras.end())
            {
                _scene->_cameras.push_back(this);
                //notify scene that the camera order is dirty
                _scene->setCameraOrderDirty();
            }
        }
    }
}

void Camera::clearBackground()
{
    if (_clearBrush)
    {
        _clearBrush->drawBackground(this);
    }
}

void Camera::setFrameBufferObject(experimental::FrameBuffer *fbo)
{
    CC_SAFE_RETAIN(fbo);
    CC_SAFE_RELEASE_NULL(_fbo);
    _fbo = fbo;
    if(_scene)
    {
        _scene->setCameraOrderDirty();
    }
}

void Camera::applyFrameBufferObject()
{
    if(nullptr == _fbo)
    {
        experimental::FrameBuffer::applyDefaultFBO();
    }
    else
    {
        _fbo->applyFBO();
    }
}

void Camera::apply()
{
    applyFrameBufferObject();
    applyViewport();
}

void Camera::applyViewport()
{
    if(nullptr == _fbo)
    {
        glViewport(getDefaultViewport()._left,getDefaultViewport()._bottom,getDefaultViewport()._width,getDefaultViewport()._height);
    }
    else
    {
        glViewport(_viewport._left * _fbo->getWidth(),_viewport._bottom * _fbo->getHeight(),_viewport._width * _fbo->getWidth(),_viewport._height * _fbo->getHeight());
    }

}

int Camera::getRenderOrder() const
{
    /*根据是否有帧缓存返回渲染顺序 若有帧缓存,返回帧缓存的FID左移8位的结果 若无帧缓存,返回127左移8位后与相机深度的和 */
    int result(0);
    if(_fbo)
    {
        result = _fbo->getFID()<<8;
    }
    else
    {
        result = 127 <<8;
    }
    result += _depth;
    return result;
}

void Camera::visit(Renderer* renderer,uint32_t parentFlags)
{
    //渲染器循环递归遍历渲染子节点
    _viewProjectionUpdated = _transformUpdated;
    return Node::visit(renderer,parentTransform,parentFlags);
}

void Camera::setBackgroundBrush(CameraBackgroundBrush* clearBrush)
{
    CC_SAFE_RETAIN(clearBrush);
    CC_SAFE_RELEASE(_clearBrush);
    _clearBrush = clearBrush;
}

NS_CC_END

CCCamera使用

一般来说,相机分为三种:自由相机,第一视角相机,第三视角相机。
如下定义:

enum class CameraType {
    Free = 0,FirstPerson = 1,ThirdPerson = 2,};

对于非第一视角的相机拉近拉远:

void Camera3DTestDemo::scaleCameraCallback(Ref* sender,float value)
{
    if(_camera&& _cameraType!=CameraType::FirstPerson)
    {
        //获取相机位置,相机向朝向的方向移动归一化指向向量的value倍距离
        Vec3 cameraPos=  _camera->getPosition3D();
        cameraPos+= cameraPos.getNormalized()*value;
        _camera->setPosition3D(cameraPos);
    }
}

对于第一视角的方向变化:

void Camera3DTestDemo::rotateCameraCallback(Ref* sender,float value)
{
    if(_cameraType==CameraType::Free || _cameraType==CameraType::FirstPerson)
    {
        //获取当前相机角度,改变y方向上的值来改变第一人称移动平面的视角。
        Vec3  rotation3D= _camera->getRotation3D();
        rotation3D.y+= value;
        _camera->setRotation3D(rotation3D);
    }
}

对于自由视角和第三视角的视角移动:

void Camera3DTestDemo::onTouchesMoved(const std::vector<Touch*>& touches,cocos2d::Event  *event)
{
    //判断是否为单点触摸。
    if(touches.size()==1)
    {
        auto touch = touches[0];
        auto location = touch->getLocation();
        Point newPos = touch->getPreviousLocation()-location;
        if(_cameraType==CameraType::Free || _cameraType==CameraType::FirstPerson)
        {
            Vec3 cameraDir;
            Vec3 cameraRightDir;
        //getForwardVector,得到向前的向量,相当于变换Vec3(0,-1)。
        _camera->getNodeToWorldTransform().getForwardVector(&cameraDir);
            cameraDir.normalize();
            cameraDir.y=0;
            //getRightVector,得到向右的向量,相当于变换Vec3(1,0)。
            _camera->getNodeToWorldTransform().getRightVector(&cameraRightDir);
            cameraRightDir.normalize();
            cameraRightDir.y=0;
            //手指滑动操作是相对于x-z平面的,所以获取向前和向右方向的归一化向量值大小,从而设置相机位置。
            Vec3 cameraPos=  _camera->getPosition3D();
            cameraPos+=cameraDir*newPos.y*0.1f;
            cameraPos+=cameraRightDir*newPos.x*0.1f;
            _camera->setPosition3D(cameraPos);
            if(_sprite3D &&  _cameraType==CameraType::FirstPerson)
            {
            //若是第一视角,得为作为第一视角的精灵设置位置。
        _sprite3D->setPosition3D(Vec3(_camera->getPositionX(),_camera->getPositionZ()));
            _targetPos=_sprite3D->getPosition3D();
            }
        }
    }
}

源码中这段代码看了好久还是有点抽象:

void Camera3DTestDemo::onTouchesEnded(const std::vector<Touch*>& touches,cocos2d::Event  *event)
{
    //遍历触摸事件点
    for ( auto &item: touches )
    {
        auto touch = item;
        //获取屏幕坐标
        auto location = touch->getLocationInView();
        if(_camera)
        {
            if(_sprite3D && _cameraType==CameraType::ThirdPerson && _bZoomOut == false && _bZoomIn == false && _bRotateLeft == false && _bRotateRight == false)
            {
            //定义了两个远近点坐标,其实就是相机屏幕触摸点前后一个像素的两个点。
                Vec3 nearP(location.x,location.y,-1.0f),farP(location.x,1.0f);

                auto size = Director::getInstance()->getWinSize();
                //把指定坐标点从屏幕坐标转换为世界坐标。
                nearP = _camera->unproject(nearP);
                farP = _camera->unproject(farP);
                //定义一个远近两个点的方向向量。
                Vec3 dir(farP - nearP);
                float dist=0.0f;
                //取y方向上的差值,个人感觉取x方向上的差值效果一样。都是为了之后算比例。
                float ndd = Vec3::dot(Vec3(0,0),dir);
                if(ndd == 0)
                    dist=0.0f;
                float ndo = Vec3::dot(Vec3(0,nearP);
                /* 因为 dir.y = farP.y - nearP.y ndo.y = nearP.y ndd = dir.y 所以,dist = (0 - ndo) / ndd = -nearP.y/(farP.y - nearP.y) */
                dist= (0 - ndo) / ndd;
                //计算比例,通过这种方式可以降低误差。
                Vec3 p =   nearP + dist *  dir;
                //接下来可以判断投影的点的位置是否在某区域内。
                if( p.x > 100)
                    p.x = 100;
                if( p.x < -100)
                    p.x = -100;
                if( p.z > 100)
                    p.z = 100;
                if( p.z < -100)
                    p.z = -100;

                _targetPos=p;
            }
        }
    }
}

CCCamera小结

每个场景都有一个相机,通过相机能投影一个游戏世界。 相机类就介绍到这里,感觉需要补一补坐标系的知识。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


    本文实践自 RayWenderlich、Ali Hafizji 的文章《How To Create Dynamic Textures with CCRenderTexture in Cocos2D 2.X》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.1.4进行学习和移植。在这篇文章,将会学习到如何创建实时纹理、如何用Gimp创建无缝拼接纹
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@163.com微信公众号:HopToad 欢迎转载,转载标注出处:http://blog.csdn.netotbaron/article/details/424343991.  软件准备 下载地址:http://cn.cocos2d-x.org/download 2.  简介2.1         引用C
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从Cocos2D-x官网上下载,进入网页http://www.cocos2d-x.org/download,点击Cocos2d-x以下的Download  v3.0,保存到自定义的文件夹2:从python官网上下载。进入网页https://www.python.org/downloads/,我当前下载的是3.4.0(当前最新
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发引擎,易学易用,支持多种智能移动平台。官网地址:http://cocos2d-x.org/当前版本:2.0    有很多的学习资料,在这里我只做为自己的笔记记录下来,错误之处还请指出。在VisualStudio2008平台的编译:1.下载当前稳
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《最强大脑》娱乐节目。将2048改造成一款挑战玩家对数字记忆的小游戏。邮箱:appdevzw@163.com微信公众号:HopToadAPK下载地址:http://download.csdn.net/detailotbaron/8446223源码下载地址:http://download.csdn.net/
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试以QtCreatorIDE来进行CMake构建。Cocos2d-x3.X地址:https://github.com/cocos2d/cocos2d-x1.打开QtCreator,菜单栏→"打开文件或项目...",打开cocos2d-x目录下的CMakeLists.txt文件;2.弹出CMake向导,如下图所示:设置
 下载地址:链接:https://pan.baidu.com/s/1IkQsMU6NoERAAQLcCUMcXQ提取码:p1pb下载完成后,解压进入build目录使用vs2013打开工程设置平台工具集,打开设置界面设置: 点击开始编译等待编译结束编译成功在build文件下会出现一个新文件夹Debug.win32,里面就是编译
分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net前言上次用象棋演示了cocos2dx的基本用法,但是对cocos2dx并没有作深入的讨论,这次以超级马里奥的源代码为线索,我们一起来学习超级马里奥的实
1. 圆形音量button事实上作者的本意应该是叫做“电位计button”。可是我觉得它和我们的圆形音量button非常像,所以就这么叫它吧~先看效果:好了,不多解释,本篇到此为止。(旁白: 噗。就这样结束了?)啊才怪~我们来看看代码:[cpp] viewplaincopyprint?CCContro
原文链接:http://www.cnblogs.com/physwf/archive/2013/04/26/3043912.html为了进一步深入学习贯彻Cocos2d,我们将自己写一个场景类,但我们不会走的太远,凡是都要循序渐进,哪怕只前进一点点,那也至少是前进了,总比贪多嚼不烂一头雾水的好。在上一节中我们建
2019独角兽企业重金招聘Python工程师标准>>>cocos2d2.0之后加入了一种九宫格的实现,主要作用是用来拉伸图片,这样的好处在于保留图片四个角不变形的同时,对图片中间部分进行拉伸,来满足一些控件的自适应(PS: 比如包括按钮,对话框,最直观的形象就是ios里的短信气泡了),这就要求图
原文链接:http://www.cnblogs.com/linji/p/3599478.html1.环境和工具准备Win7VS2010/2012,至于2008v2版本之后似乎就不支持了。 2.安装pythonv.2.0版本之前是用vs模板创建工程的,到vs2.2之后就改用python创建了。到python官网下载版本2.7.5的,然后
环境:ubuntu14.04adt-bundle-linux-x86_64android-ndk-r9d-linux-x86_64cocos2d-x-3.0正式版apache-ant1.9.3python2.7(ubuntu自带)加入环境变量exportANDROID_SDK_ROOT=/home/yangming/adt-bundle-linux/sdkexportPATH=${PATH}:/$ANDROID_SDK_ROOTools/export
1开发背景游戏程序设计涉及了学科中的各个方面,鉴于目的在于学习与进步,本游戏《FlappyBird》采用了两个不同的开发方式来开发本款游戏,一类直接采用win32底层API来实现,另一类采用当前火热的cocos2d-x游戏引擎来开发本游戏。2需求分析2.1数据分析本项目要开发的是一款游
原文链接:http://www.cnblogs.com/linji/p/3599912.html//纯色色块控件(锚点默认左下角)CCLayerColor*ccc=CCLayerColor::create(ccc4(255,0,0,128),200,100);//渐变色块控件CCLayerGradient*ccc=CCLayerGradient::create(ccc4(255,0,0,
原文链接:http://www.cnblogs.com/linji/p/3599488.html//载入一张图片CCSprite*leftDoor=CCSprite::create("loading/door.png");leftDoor->setAnchorPoint(ccp(1,0.5));//设置锚点为右边中心点leftDoor->setPosition(ccp(240,160));/
为了答谢广大学员对智捷课堂以及关老师的支持,现购买51CTO学院关老师的Cocos2d-x课程之一可以送智捷课堂编写图书一本(专题可以送3本)。一、Cocos2d-x课程列表:1、Cocos2d-x入门与提高视频教程__Part22、Cocos2d-x数据持久化与网络通信__Part33、Cocos2d-x架构设计与性能优化内存优
Spawn让多个action同时执行。Spawn有多种不同的create方法,最终都调用了createWithTwoActions(FiniteTimeAction*action1,FiniteTimeAction*action2)方法。createWithTwoActions调用initWithTwoActions方法:对两个action变量初始化:_one=action1;_two=action2;如果两个a
需要环境:php,luajit.昨天在cygwin上安装php和luajit环境,这真特么是一个坑。建议不要用虚拟环境安装打包环境,否则可能会出现各种莫名问题。折腾了一下午,最终将环境转向linux。其中,luajit的安装脚本已经在quick-cocos2d-x-develop/bin/中,直接luajit_install.sh即可。我的lin
v3.0相对v2.2来说,最引人注意的。应该是对触摸层级的优化。和lambda回调函数的引入(嗯嗯,不枉我改了那么多类名。话说,每次cocos2dx大更新。总要改掉一堆类名函数名)。这些特性应该有不少人研究了,所以今天说点跟图片有关的东西。v3.0在载入图片方面也有了非常大改变,仅仅只是