Cocos Creator 2D摄像机 [Lv.1] 小视图

Posted VermillionTear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Cocos Creator 2D摄像机 [Lv.1] 小视图相关的知识,希望对你有一定的参考价值。

摘要

本系列文章主要实操2D摄像机,在官方Demo的基础上,做了适当的修改,并增加了新的内容。
文中对相关知识点进行适当展开,不做深入研究。
以实际操作做为出发点,帮助读者快速实现并且掌握2D摄像机

环境

  • Cocos Creator 2.4.4

资源

准备工作

  1. 一张小姐姐的图片。
  2. 一张心形的图片。
  3. 创建Empty工程。

正式开始

对工程做一些修改

资源管理器

层级管理器

  1. 对节点的层级稍稍做了规范化,保证UI与代码分开,代码均挂载在Controller下对应的节点上。
  2. 创建MiniMapController.js脚本,并且挂载在MiniMapController节点上。

之后节点相关的设置请参考工程中的设置,文中不再赘述。
场景编辑器

如何互动

小姐姐又来啦~
这回我们要来做一个类似于小地图的功能,在一个小的视图里,显示小姐姐的指定部位。
而这个功能就是通过2D摄像机实现的。

上摄像机

创建一个2D摄像机

rect属性,决定摄像机绘制在屏幕上哪个位置,值为(0 ~ 1),xy为左下角的点。
所以如下设置,摄像机所照到的图像,就会绘制在屏幕的右上角。

看实际效果,

这么简单的设置,就有种已经要完成的感觉?
no, no, no,接下来我们要让它更加灵活一些。

缩放(看清黑头和脂肪粒)

缩放是通过控制摄像机的视窗大小(orthoSize属性)而实现的,而默认创建出来的摄像机,会自动将视窗大小调整为整个屏幕的大小。所以如果想要完全自由地控制摄像机,则需要将alignWithScreen属性设置为false

视窗的大小 * 2,就是摄像机能看到的高度。由于Canvas默认的高度是640,所以这里将orthoSize设置为320,让摄像机默认能看到整个屏幕。当然,你也可以设置其他的值,作为初始的视窗大小。
MiniMapController.js中实现功能。

let _canvasWidth = 0	// 场景宽
let _canvasHeight = 0	// 场景高

cc.Class(
    extends: cc.Component,

    properties: 
        camera: cc.Camera, 	// 摄像机。
    ,

    onLoad () 
	    // 初始化。
        _canvasWidth = cc.Canvas.instance.node.width
        _canvasHeight = cc.Canvas.instance.node.height

		// 鼠标滚轮事件。通过鼠标滚轮,控制放大和缩小。
        this.node.on(cc.Node.EventType.MOUSE_WHEEL, this.onMouseWheel, this)
    , 

	// 鼠标滚轮事件响应函数。
    onMouseWheel: function (event) 
        let scrollY = event.getScrollY()		// 滚动值,向上滚为1,向下滚为-1。
        let orthoSize = this.camera.orthoSize

		// orthoSize 的值越大,摄像机的视野就越大,所以画面就会变小,反之画面就会变大。
		// 而我这里实际想要的功能是,滚轮向上滚,画面变大,所以这里减去滚动值。
		// 1 个单位作为基本变化量,有些太慢了,所以这里用 5 个单位作为基本变化量。
        orthoSize -= scrollY * 5
        this.camera.orthoSize = orthoSize		// 设置摄像机的视窗大小。
    , 
)

将节点挂载到脚本对应变量上。

由于鼠标事件需要依附于Node节点上才能触发,所以我们需要将MiniMapController节点设置为场景的大小(确保覆盖了整个场景),才能真正的触发鼠标事件。

运行起来,看看效果,

哇~ 原来小姐姐没有黑头,也没有脂肪粒,皮肤光滑且富有弹性~

动起来

缩放是做到了,但是如果我们想单独看看小姐姐那明亮的双眸,或是那bulingbuling的双唇……
MiniMapController.js中实现功能。

...
cc.Class(
    ...
    onLoad () 
        ...
        // 触摸移动事件。当在屏幕上滑动时,控制摄像机的移动。
        this.node.on(cc.Node.EventType.TOUCH_MOVE,this.onTouchMove,this)
    , 
	...
    onTouchMove: function (event) 
		// 这里获得的是,在屏幕坐标系下的坐标(不是this.node中的坐标)。
        let pos = new cc.Vec2(event.getLocationX(), event.getLocationY())
        
        // 转换为 camera 父节点中的坐标(camera的坐标实际上就是在其父节点坐标系下的坐标)。
        pos = this.camera.node.parent.convertToNodeSpaceAR(pos)
        // 重定位 camera 的位置。
        this.camera.node.setPosition(pos.x, pos.y)
    , 
)

运行起来,看看效果,

哇~ 小姐姐bulingbuling的双唇。

辅助的边框

上面已经将基本的功能都实现了,但是看起来还是感觉有些别扭。照相机照到哪里,在原图上根本看不到,只能通过小视图中的图像判断。
所以接下来添加一些辅助的边框,帮助我们标注小视图以及摄像机照到的区域。
创建一个空节点,

并给其添加Graphics组件,该组件可以像H5 Canvas那样在屏幕上画图。

MiniMapController.js中实现功能。

...
cc.Class(
    ...
    properties: 
        ...
        brush: cc.Graphics, 	// 笔刷,用于在屏幕上画图。
    ,
	...
    update (dt) 
	    // 摄像机的位置以及视窗大小会变化,所以每一帧都绘制,绘制前先清空之前绘制的。
        this.brush.clear()

        // 绘制小视图的边界。
        let cameraDisplayRect = this.calCameraDisplayRect()		// 计算出小视图的边界(rect)。
        this.brush.rect(cameraDisplayRect.x, cameraDisplayRect.y, cameraDisplayRect.width, cameraDisplayRect.height)	// 绘制矩形。
        this.brush.strokeColor = cc.Color.YELLOW		// 设置画笔颜色。
        this.brush.stroke()	// 实际绘制。

        // 绘制摄像机的视窗边界。
        let cameraOrthoRect = this.calCameraOrthoRect()		// 计算出视窗的边界(rect)。
        this.brush.rect(cameraOrthoRect.x, cameraOrthoRect.y, cameraOrthoRect.width, cameraOrthoRect.height)		// 绘制矩形。
        this.brush.strokeColor = cc.Color.BLUE		// 设置画笔颜色。
        this.brush.stroke()	// 实际绘制。
    , 
	...
    calCameraDisplayRect: function () 
        let cameraRect = this.camera.rect	// 摄像机绘制在屏幕上的位置。
        // 摄像机的锚点。
        let cameraAnchor = cc.Vec2(this.camera.node.anchorX, this.camera.node.anchorX)

		// 绘制的矩形以左下角为原点,而摄像机的锚点在视窗的正中心。
		// 根据 camera.rect 设置的比例,计算出实际在屏幕中的位置,以及宽高。
        return new cc.Rect((cameraRect.x - cameraAnchor.x) * _canvasWidth, 
                        (cameraRect.y - cameraAnchor.y) * _canvasHeight, 
                        cameraRect.width * _canvasWidth, 
                        cameraRect.height * _canvasHeight)
    , 
    
    calCameraOrthoRect: function () 
        let cameraPosition = this.camera.node.getPosition()		// 摄像机的位置。
        let orthoHeight = this.camera.orthoSize		// 摄像机视窗的高度。
        // 摄像机视窗的宽高比与场景的宽高比相同,所以通过场景的宽高比,计算出视窗的宽度。
        let orthoWidth = orthoHeight * (_canvasWidth / _canvasHeight)
        
        // 绘制的矩形以左下角为原点,而摄像机的锚点在视窗的正中心。
        // 视窗的高度 * 2 = 摄像机能看到的高度;视窗的宽度 * 2 = 摄像机能看到的宽度。
        return new cc.Rect(cameraPosition.x - orthoWidth, 
                        cameraPosition.y - orthoHeight, 
                        orthoWidth * 2, 
                        orthoHeight * 2)
    , 
)

将节点挂载到脚本对应变量上。

运行起来,看看效果,

有了辅助的边框,看起来就更加直观了。

得有边界

拖动,缩放,拖动,缩放……
照相机的视窗有时会飞出屏幕外,小姐姐的靓照也就飞出了小视图外,这可不行,得有个边界。
MiniMapController.js中实现功能。

...
cc.Class(
    ...
    onMouseWheel: function (event) 
        ...
        orthoSize = this.limitOrthoSize(orthoSize)	// 限定摄像机视窗在边界内。
        ...
        // 摄像机不动,但视窗变大时有可能也会大到屏幕外,所以重新计算摄像机视窗的位置。
        let pos = this.limitPos(this.camera.node.getPosition())
        this.camera.node.setPosition(pos.x, pos.y)
    , 

    onTouchMove: function (event) 
        ...
        pos = this.limitPos(pos)	// 限定为边界内的坐标。
        ...
    , 
    ...
    limitOrthoSize: function (orthoSize) 
        let val = orthoSize
        // 摄像机视窗的size,也就是视窗的高度,是摄像机可以看到的高度的一半。
        // 所以最大就限定其为场景高度的的一半。
        let maxOrthoSize = _canvasHeight / 2

		// orthoSize 不可为 0 ;orthoSize 小于 0 时,图像是倒过来的。
		// 所以,1 <= orthoSize <= 场景高度的 / 2
        val = Math.max(1, Math.min(val, maxOrthoSize))

        return val
    , 

    limitPos: function (pos) 
        let val = pos
        let orthoHeight = this.camera.orthoSize		// 摄像机视窗的高度。
        // 摄像机视窗的宽高比与场景的宽高比相同,所以通过场景的宽高比,计算出视窗的宽度。
        let orthoWidth = orthoHeight * (_canvasWidth / _canvasHeight)
        let halfCanvasWidth = _canvasWidth / 2
        let halfCanvasHeight = _canvasHeight / 2

		// 摄像机的锚点在正中心。
        if (pos.x - orthoWidth < -1 * halfCanvasWidth) 	// 超出左边界。
            val.x = (-1 * halfCanvasWidth) + orthoWidth
         else if (pos.x + orthoWidth > halfCanvasWidth) 	// 超出右边界。
            val.x = halfCanvasWidth - orthoWidth
        
        if (pos.y - orthoHeight < -1 * halfCanvasHeight) 	// 超出下边界。
            val.y = (-1 * halfCanvasHeight) + orthoHeight
         else if (pos.y + orthoHeight > halfCanvasHeight) 	// 超出上边界。
            val.y = halfCanvasHeight - orthoHeight
        

        return val
    , 
);

运行起来,看看效果,

摄像机的视窗不会飞出屏幕,小姐姐也不会飞出小视图了。

用分组解决个bug

当摄像机移动到屏幕右上角时,会发现小视图中多出来的黄线,

当摄像机的视窗最小(camera.orthoSize = 1)时,会发现小视图中一片蓝,

这些问题都是由于,摄像机照到了笔刷,在小视图中也将笔刷显示了出来。而笔刷绘制的图像在小姐姐之上,所以造成了上述问题。
解决这个问题很简单,我们需要分组,将笔刷单独放到一个组中,然后告诉摄像机,笔刷所在的组你假装没看到就好了。
项目 -> 项目设置... -> 分组管理中添加分组,保存,

这时就能在每个节点的Group属性的下拉菜单中,看到新增加的brush组。
我们将brush节点的Group设置为brush

然后将camera节点的cullingMask属性设置为只勾选default

cullingMask决定摄像机用来渲染场景的哪些部分,我们未勾选brush,那么所在brush分组中的笔刷就不会被渲染出来。
运行起来,看看效果,

两个问题都消失了。

划重点

  • 默认创建出来的摄像机,会自动将视窗大小调整为整个屏幕的大小。alignWithScreen
  • rect属性,决定摄像机绘制在屏幕上哪个位置,值为(0 ~ 1),x和y为左下角的点。
  • 摄像机的视窗大小orthoSize,视窗的大小 * 2,就是摄像机能看到的高度。
  • 分组。

以上是关于Cocos Creator 2D摄像机 [Lv.1] 小视图的主要内容,如果未能解决你的问题,请参考以下文章

Cocos Creator 2D摄像机 [Lv.2] 截图

Cocos Creator 2D摄像机 [Lv.1] 小视图

Cocos Creator 2D摄像机 [Lv.1] 小视图

Cocos Creator 2D摄像机 [Lv.1] 小视图

Cocos Creator 2D摄像机 [Lv.1] 小视图

Cocos Creator JSB [Lv.1]