视频+文字教程丨使用 RenderTexture 实现 3D 场景中的小地图与传送门
RenderTexture 是游戏引擎的重要技术之一,它的原理是渲染摄像机的画面成为一个纹理,进阶的应用就是附着到材质上,变成对应的功能,比如:
俯视小地图
屏幕上分屏显示视角
能够看到目的地情景的传送门
狙击枪瞄准镜里的画面
引擎中的摄像机的预览
引擎中画布 UI
Cocos Creator 3.x 提供了完整的 3D 世界开发能力,使我们能够实现上述功能。本次,「Cocos Star Writer」Nowpaper 将通过视频+文字教程,在 v3.x 中使用 RenderToTexture,实现小地图和传送门。
视频地址:
https://www.bilibili.com/video/BV1K3411m7Ma
点击文末【阅读原文】至论坛讨论帖:
https://forum.cocos.org/t/topic/121977
注:首先我准备了一个测试场景,方便展示成果,这个测试场景是我自己随便搭建的,还有很多的问题,就不单独提供了。
小地图
先来实现小地图,小地图的实现比较简单,几乎不用写代码。
论坛上的其他小地图做法都是直接移动控制第二摄像机的画面来实现,本文使用 Sprite 精灵来接受和显示渲染纹理,能够更好地定制你的 UI 画面,具体做法如下:
首先建立一个 UI Canvas,这个 UI 中放置一个 Sprite 精灵,调整到你想要的位置;
然后建立一个摄像机,摄像机向下照射,设置好位置和旋转参数,调整到你想要的观看位置和角度;
接着在资产里,建立一个 RenderTexture 的资产,简单设置一下参数,主要要将资产的 Width 和 Height 设置成为小地图同等比例的大小,否则它将显示不出来;
选择摄像机,把摄像机的 RenderTexture 的目标,指向到这个资产;
然后选择之前的精灵,将这个 RenderTexture 的资产赋给它的 SpriteFrame 的属性;
保存运行看看效果,就可以达到渲染效果。但是现在我们发现,移动并不会跟着人物走。
所以要给摄像机简单建立一个 TopCamareFollow 的脚本,在脚本的 Update 里面,不停地为它设置和角色一样的 x、z 的位置信息。在这个脚本中添加一个目标指向,在 Update 中添加如下代码,除了 y 以外的所有轴坐标,都和目标同步更新,代码如下:
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('TopCameraFollow')
export class TopCameraFollow extends Component {
@property(Node)
target:Node = null;
start () {
}
update (deltaTime: number) {
this.node.setWorldPosition(
this.target.worldPosition.x,
this.node.worldPosition.y,
this.target.worldPosition.z
)
}
}
将它添加到摄像机中,并将主角作为跟随目标,现在摄像机就可以跟着人物行动了。
在摄像机和 RenderTexture 资产里,有很多有趣的参数,可以各种尝试,比如如果你不喜欢带有透视效果的小地图,也可以将摄像机的拍摄参数设置成为正交,变成无透视的效果,其实很多俯视游戏,都是用这种方法实现。
传送门
也许著名游戏《传送门》曾经震撼于你,也许任意门是你儿时的梦想,现在我们通过简单的开发,让你的游戏里拥有一个。
首先,在目标点设立一个摄像机。比如在遥远的地方,有个小岛,记住这是同一个场景。创建一个摄像机,调整到你想拍摄的的位置;
然后我们为它制作一个脚本,就叫传送门观察者吧。在代码里定一个目标指向,用来指向将渲染的画面投射到什么物体,然后在 start 里写下下面的代码。
官方文档-精灵帧资源(SpriteFrame):
https://docs.cocos.com/creator/3.3/manual/zh/asset/sprite-frame.html
这个部分的代码,在官方有介绍。但是在 v3.3 中会有一些报错,所以你需要稍微更新一下,然后将需要投射的材质设置和应用到的目标模型上,我的代码就是这样的:
import { _decorator, Component, Node, MeshRenderer, Camera, RenderTexture, Material } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('GateWayCamera')
export class GateWayCamera extends Component {
@property(MeshRenderer)
target:MeshRenderer = null;
start () {
const renderTex = new RenderTexture();
renderTex.reset({
width: 256,
height: 256
});
const cameraComp = this.getComponent(Camera);
cameraComp.targetTexture = renderTex;
const pass = this.target.material.passes[0];
const defines = { SAMPLE_FROM_RT: true, ...pass.defines };
const renderMat = new Material();
renderMat.initialize({
effectAsset: this.target.material.effectAsset,
defines,
});
this.target.setMaterial(renderMat, 0);
renderMat.setProperty('mainTexture', renderTex, 0);
}
}
回到 Creator 中,在你想要的位置上作一个模型,什么形状都行,取决于你的传送门到底是什么形式的;
然后我们需要为它换一个材质,用来接收 RenderTexture,新建一个材质,然后将 Sample From Rt 选项选上,并开启漫反射贴图(Use Albedo Map),保存设置;
将这个材质应用到传送门的展示的模型上,替换掉默认第一个 0 索引的材质;
为传送门摄像机添加刚刚写的脚本,并且将目标 Target 的属性将刚刚建立的模型引用;
运行,控制角色来看看,可以发现目标的画面已经投射到了这个模型上,如果角度有点问题,说明模型坐标不太对,做一下调整旋转即可。
在这个模型上已经渲染出了目标摄像机的画面,但是它目前只是一个固定的摄像机,从不同的角度来观察似乎太呆板了。它应该跟着主角在移动的时候,会相对着你的主摄像机一同同步,为了实现这个功能我们得写个同步脚本了,先创建一个脚本就叫 GateWayCameraSync:
import { _decorator, Component, Node, Vec3, v3 } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('GateWayCameraSync')
export class GateWayCameraSync extends Component {
@property(Node)
target:Node = null;
private offset:Vec3 = null;
private vec3:Vec3 = v3();
start () {
this.offset = this.node.worldPosition.clone().subtract(this.target.position);
}
update (deltaTime: number) {
if(this.target){
Vec3.add(this.vec3,this.target.worldPosition,this.offset);
this.node.setWorldPosition(this.vec3);
this.node.setWorldRotation(this.target.worldRotation);
}
}
}
在最开始,它需要计算出和主摄像机的差值, 通过 Target 属性引用到目标摄像机,在脚本运行的开始,通过向量的减法,得到相对于目标的世界坐标的偏移量,然后在 Update 中不停的作同步。
现在返回到 Creator,为摄像机挂上这个脚本,将主摄像机添加引用,为了更自然,你需要将摄像机稍微调整一下位置,因为初始位置和传送门其实并不是非常合适的。
真正的传送
传送门的效果肯定是传送,所以我们还得完成传送操作,为了达成这个能力,我们将会用上触发器。
选中传送门的模型,为它添加一个碰撞体,把作为触发器的选项勾上,由于它不是物理物件,所以不需要添加物理实体。
然后添加一个脚本,叫做触发传送,用来将触发碰撞后,将自己给瞬移到目标点:
import { _decorator, Component, Node, Collider } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('TriggerTransfer')
export class TriggerTransfer extends Component {
@property(Node)
target:Node = null;
start () {
const c = this.getComponent(Collider);
c.on('onTriggerEnter',this.onTriggerEnter,this);
}
private onTriggerEnter(){
this.node.setWorldPosition(
this.target.worldPosition
);
}
}
上面的代码中,使用了一个目标的引用来快速瞬移到它的坐标点。其实手动输入一个坐标也可以的,但是有个节点物体作参照,显得更直观一点。
现在 Start 的时候开始监听触发事件,触发器事件触发的时候,将自己的位置 position 直接设置成为目标 position 属性,就达到了瞬移效果。
现在回到 Creator,将脚本添加到主角身上,找到引用的物体,我就简单点,直接将目标点设置到了传送门摄像机身上。
现在看看效果,当主角碰到传送门物体的时候,触发了碰撞事件,从而将自己传送到了目标点。
优化
画面看起来也不怎么清晰,这个可以通过修改渲染图的大小来解决。
renderTex.reset({
width: 512,
height: 512
});
传送门里的画面有点黑,看起来非常不和谐,解决方法是直接增加传送门摄像机的曝光度,调两个档次就可以了。
正反问题,其实传送门在正反前后看到的内容应该一致,但是由于我们采用了摄像机完全同步,所以在后面看就比较诡异了。这个部分如果想修改,则需要作一些数学上的计算,在这里就不实现了,你只需要将传送门的目标位置,作一下反转判定就可以。
特效
这光秃秃的传送门肯定不如有特效的加持显得高级,后面我们用一下粒子系统,完成制作炫酷的效果。
先准备一个图像,弄一个有透明的光圈。
你可以依据自己的需求做调整,把贴图放到工程里,最开始新建一个粒子用的材质,因为自从 Creator 3.x 以后的版本,图片纹理不能直接应用到粒子上,需要一个材质承接贴图,打开漫反射贴图,把前面导入的光圈纹理放上去,点击“同意”应用。
在场景中新建一个粒子系统,然后去掉发射模块,调整一下大小参数和位置参数,将粒子材质做一下修改,替换成我们刚刚新建的那个,然后把渲染模式改成了模型,选择面片作为基本模型。
这里 v3.3 有个小 bug,你会发现之前引用的材质不见了, 不用担心,它只是没显示出来。由于方向问题,还需要修改一些基础的参数以及大小让它正好套住传送门的模型,具体还得看你的传送门大小跟朝向。
粒子系统上勾选上旋转模块,将旋转 z 轴的角度设置成为 -180,完成,现在基本上就能看到一个整体效果了。
如果你想整更多的活,比如加上其他喷射也未尝不可。剩下的就是各自发挥了,希望各位能够在其中发现更多的有趣的应用。
结语
Creator 3.x 以后的版本,有了更完善的 3D 游戏引擎的特性,使得很多有趣的玩法得以发挥。从 v3.0 到 v3.3,官方做了大量优化改进,相信以后会越来越好。
我是 Nowpaper,混迹游戏行业的一个老爸,如果你喜欢这期教程,欢迎一键三联加关注我的 B 站(UP:Nowpaper),这是我做更多视频的动力,咱们下次再见!