查看原文
其他

藏在3D立方体中的视觉奇旅!Cocos呈现《笼中窥梦》多维空间视错觉效果

Nowpaper COCOS 2022-06-10

引言:去年,独立游戏《笼中窥梦》独特的美术表现令无数玩家赞叹。游戏利用「视错觉」来设计谜题关卡,创造了一个奇妙的立方体世界。本次,「Cocos Star Writer」Nowpaper 将在 v3.5 中实现同款视错效果,源码和视频教程见文末


「视错觉」解密游戏《笼中窥梦》


《笼中窥梦》有很多值得考究的技术细节点,那么今天,我们将在 Cocos Creator 3.5 中使用可渲染纹理和自定义着色器实现一下这个游戏的方盒视错功能,一窥其技术实现思路。


Demo 最终效果


这一次的技术实现灵感来自于我正在制作的另外一个技术视频,那是探讨如何在 v3.x 中实现瞄准镜和额外屏幕技术(PS.这个方案很快也会分享给大家)。偶然发现其中的部分,只要结合之前的传送门技术实现方法,就能做出《笼中窥梦》的方盒视错,于是率先完成了这个方案,第一时间分享给大家。


实现瞄准镜和额外屏幕技术


场景准备,实现分析


《笼中窥梦》中的盒子,各个面展示了完全不同的场景,玩家通过旋转摄像机找到合适的位置,组合达成过关条件。因此我们需要解决的是:如何让盒子的各个面显示不同的画面。


正常来说,我们将使用5个摄像机,渲染不同的画面到方盒的5个面上,这里使用 RenderToTexture 可以很容易地完成。我准备了一些素材搭建了几个不同的场景,需要注意的是,需要注意的是,这里将场景按节点分割,方便管理。



首先搭建一个基础场景,中间摆放一个盒子,并且建立对应各方向的面片。但如果五个表面都制作,那么场景搭建将会异常复杂,为了降低复杂度,我们先做出两个表面,对应的也得做出来2个场景用来当谜题。


这些场景搭建并不复杂,但是如果真的要实现完整的关卡,这就得需要精细设计了。本文主要探讨技术实现,就不做那么细致了。


在项目设置中,新增6个 Layer 用来标记渲染分组,因为对应的摄像机,只需要渲染指定的渲染层即可,不用全部都渲染出来。


现在为它们每个组的根节点设置对应的 Layer,以方便将来摄像机的设置。将基本场景的根节点设置成为 Base,而其他的两个场景根节点分别设置为 Layer 1 和2。如此一来准备工作就完成了。



摄像机的控制代码


接着我们需要先实现一个摄像机的渲染控制代码,让摄像机围绕着一个物体进行旋转。代码相对来说比较简单,通过监听 TouchMove 事件处理旋转,具体的请参看论坛中一些大佬的分享。

import { _decorator, Component, Node,  input, Input, EventTouch, Vec3, v3, Quat, math, quat } from 'cc';

const { ccclass, property } = _decorator;

@ccclass('CameraControl')

export class CameraControl extends Component {

    @property(Node)

    target: Node = null;

    public yMinLimit = -90;//相机向下最大角度

    public yMaxLimit = 0;//相机向上最大角度

    private targetX = 0;

    private targetY = 0;

    private xSpeed = 25;

    private ySpeed = 12;

    private disance: Vec3 = v3();

    start() {

        let angles = this.node.eulerAngles;

        this.targetX = angles.y;

        this.targetY = angles.x;

        input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);

        Vec3.subtract(this.disance, this.node.worldPosition, this.target.worldPosition);

        this.disance = v3(0,0,this.disance.length());

    }

    private onTouchMove(touch: EventTouch) {

        this.targetX -= touch.getDeltaX() * this.xSpeed * 0.02;

        this.targetY -= touch.getDeltaY() * this.ySpeed * 0.02;

        this.targetY = this.ClampAngle(this.targetY, this.yMinLimit, this.yMaxLimit);

       

    }

    private _quat = quat();

    private _vec3 = v3();

    update (deltaTime: number) {

        Quat.fromEuler(this._quat, this.targetY, this.targetX, 0);

        Vec3.transformQuat(this._vec3, this.disance, this._quat);

        Vec3.add(this._vec3, this.target.worldPosition, this._vec3);

        this.node.worldPosition = this._vec3.clone();

        this.node.worldRotation = this._quat.clone();

    }

    ClampAngle(angle, min, max) {

        if (angle < -360) angle += 360;

        if (angle > 360) angle -= 360;

        return math.clamp(angle, min, max);

    }

}


回到编辑器中,为主摄像机添加脚本,并且指定目标,测试旋转效果。



新建一个摄像机,命名为 Camera 1。然后复制主摄像机节点参数,粘贴给新建的摄像机,调整到合适的位置,用来拍摄第一个场景。设置这个摄像机的可见性掩码,只勾选 Layer 1 这个 Layer 层。



然后再实现一个摄像机同步的代码,用来处理面摄像机和主摄像机的同步。实现同步只需要在开始的时候,计算两个摄像机的偏移值,同步位置和旋转时候修正即可。

import { _decorator, Component, Node, Vec3, v3 } from 'cc';

const { ccclass, property } = _decorator;

// 同步摄像机脚本,事实上如果监听主摄像机坐标、旋转差值会更加合理

@ccclass('SyncCamera')

export class SyncCamera extends Component {

    @property(Node)

    mainCamera: Node = null;

    offset: Vec3 = v3();

    _vec3 = v3();

    start() {

        Vec3.subtract(this.offset, this.node.position, this.mainCamera.position);

    }

    update(deltaTime: number) {

        Vec3.add(this._vec3,this.mainCamera.position,this.offset);

        this.node.position = this._vec3.clone();

        this.node.rotation = this.mainCamera.rotation;

    }

}


先实现一个面


现在我们先实现一个面看看效果。


新建一个材质,选择着色器为内置 builtin-unlit,勾选 Use Texture,新建一个可渲染纹理,设置宽高为 512x512,把刚刚新建的可渲染纹理,拖到上面完成引用。



在场景中选择 surface 1 的面片,将它材质替换,就可以看到摄像机画面,渲染到表面1上,现在我们运行一下看看效果,由于有摄像机同步脚本,当主摄像机调整的时候辅摄像机也会跟着变动。



接着用同样的方式建立第二个表面的渲染画面。


但是很快我们会发现有个不对劲的地方:这个画面看起来非常的平,根本就不像是透视过去的画面。这是因为现在表面是直接将拍摄到的渲染画面显示出来,这样肯定是没有透视效果的。



这个时候就需要一些特殊的处理。在我之前有关传送门的分享中,这个问题也没有解决得很完美,后来在 Cocos 的 Panda 大佬的帮助下,通过修改着色器 Shader 达成了完美的传送门效果,本方案就将用到这个着色器文件,大家也可以到 Cocos Store 中免费下载。


「传送门实现」源码免费下载:

https://store.cocos.com/app/detail/3275

也可以参考 youtube 大佬 Sebastian Lague 的有关视频,他对透视计算 Shader 和原理的讲解非常详细,本文中的实现只是他的简化版本。

https://www.youtube.com/watch?v=cWpFZbjtSQg


核心代码如下:

float flip = cc_cameraPos.w == 0.0 ? -1.0 : 1.0;

v_screenPos = pos * 0.5;

v_screenPos.xy = vec2(v_screenPos.x, v_screenPos.y * flip) + v_screenPos.w;

v_screenPos.zw = pos.zw;


将修改过后的着色器添加到工程,然后选择 surface 的材质,修改 Effect 点击保存,运行就可以看到效果,现在透视基本上 OK 了。



不过好像还有一些小细节可以调整,比如画质发生了变化、显示的内容更近了,这个需要我们自行对摄像机进行调整。但是为了更好的设计关卡,两个摄像机的有关参数应该保持一致,否则可能会出现奇怪的视觉错误。


视错谜题


经过参数调整,效果已经非常不错了,那么我们也设计一个简单的谜题到其中。


这里有一幅画,放进第一个场景中,然后再往第二个场景中添加一个,此时需要一些空间视觉感,来调整对应的位置和角度。这里可能需要我们反复对比摄像机呈现的画面和最终运行结果,才能调出满意的效果。


运行一下看看效果。一般情况下,我们透过两个表面看到的画并不能完全重合,经过拖动旋转可以让其完全重合。



结语


好了,剩下的就是关卡设计的问题了,您可以利用本篇中的技巧,使用 Cocos Creator 完成一个自己的《笼中窥梦》游戏。今天这个技术实现本质上是将 RenderToTexture 给用活了,只要稍微调整一下,你也能做出令人叹为观止的创意。


我一直认为,技术工具都是为创意服务,人类的创意总是没有尽头,如何实现心中所想,除了对它的热爱,工具的熟练度和技术的沉淀,才是创意发挥的基础。


资源链接


  • 点击文末【阅读原文】前往 Cocos Store 下载本项目源码(含三个基础场景资源、三个面的实现场景,可以很方便地进行扩展):

https://store.cocos.com/app/detail/3791


  • 视频教程(UP 主:Nowpaper)

https://www.bilibili.com/video/BV1D3411G7jy


  • 论坛讨论帖:

https://forum.cocos.org/t/topic/135819


今天的文章就到这里了,我是 Nowpaper,一个混迹游戏行业的老爸,如果您喜欢我的分享,不妨多多点赞留言,也欢迎关注我的 B 站,您的支持是我更新的动力,下次再见!


Nowpaper 往期分享

《守望先锋》同级的枪弹射击体验

《时空幻境》时间倒放玩法

《饥荒》同款视觉表现

用 RenderTexture 实现小地图与传送门


*部分图片源于网络,如有侵权请与我们联系


往期精彩

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存