引言:前两周,「Cocos Star Writer」Nowpaper 在《笼中窥梦》视错觉效果的实现中使用了 RenderToTexture 技术,本次 Nowpaper 将继续拓展 RenderToTexture 的使用法。

RenderToTexture 是个非常有趣的技术,它能够将一个摄像机画面渲染成纹理,然后和材质结合,让某一个 Mesh 显示成指定的画面。在游戏开发中,它被广泛用于实现镜子、监视器画面、瞄准镜、传送门,甚至用户界面显示、动态纹理喷涂等。

在 Cocos Creator 3.4 以后的版本中,RenderToTexture 技术已经相对完善,使用起来也更加方便。在我之前的传送门分享中,传送门里的画面就使用了这个技术来实现,当时还得写一些代码,而现在只需要在编辑器中编辑,就可以轻松实现可渲染纹理了。

今天我们将继续拓展一下,使用 RenderToTexture 做一个瞄准镜以及显示指定摄像机的画面监视器。

PS.源码和视频教程见文末。本次使用的是 v3.5。

准备

首先新建一个项目并搭建游戏场景。我这里就搭建一个简单的街道,包含一些物体,让场景看起来稍微不那么单调。

瞄准镜

一般来说当角色瞄准的时候,我们可以看到在镜头中,显示的画面被放大,视觉更加前向。

这个效果的原理其实就是:在枪械的瞄准位置增加一个摄像机,然后将摄像机的画面渲染到一个纹理上。

这样的话事情就简单了。我准备了一个枪械,它带有瞄准动画,现在为他增加一个第一人称摄像机,通过调整让它看起来比较合适。

现在新建一个可渲染纹理:

图像宽高数值默认都是1,这个需要我们依据自己的情况作修改,通常是按照视口的大小比例来调整。如果要想完美适配的话,最好是代码中作一些控制,在这里我们就直接使用 512x512:

现在再创建一个材质:

着色器选择为内置-unlit:

开启 UseTexture,勾选 RTTexture 选项,将刚刚新建的可渲染纹理拖动进下面的引用中:

现在为瞄准镜建立一个圆形面片。一般来说建模师会提供一个,在这里我们就直接自己放个圆柱形,通过节点调整到对应的 Node 中:

现在选择枪械的动画,去掉动画预烘培:

关于动画控制脚本,在这里就不提供了,播放 Animation 的指定动画即可:

选择这个圆柱,将它的材质更换为刚刚创建的瞄准镜材质:

接下来在瞄准组件中添加一个摄像机,并且调整好位置,拍摄倍率直接修改 Fov 数值即可:

根据摄像机预览画面,调整好数值以后,往下拉到最下面,在 RenderToTexture 中,将之前建立的名为「瞄准镜」的可渲染纹理,拖进其中:

如果一切顺利,我们可能直接就在编辑器中看到效果。现在运行一下(我这里使用了自定义脚本,让瞄准的动作看起来更加准确),你可以看到在瞄准镜中已经有了放大的画面,我们再走动一下瞄准不同的地方试试:

到此为止,瞄准镜的实现就已经搞定了,是不是很简单呢,下面我们试一下监视器效果。

监视器

在很多游戏中,玩家可以通过监视器屏幕看到摄像头传来的数据,这类效果同样也是使用 RenderToTexture 实现。本次我们将做一个无人机控制板+一个街头的摄像机

同样也是使用前面搭建的街景,在这个房间场景中,我们用一个框框来表示无人机控制板;而监视器的画面则直接投射到电视中。完成了这两个后,将空间中的面片放置到准确位置,并且放置一个摄像机观察场景:

新建一个可渲染纹理,命名为「无人机」,同样建立一个材质,着色器选内置-unlit,然后选择 UseTexture,勾选 RT,下面选择对应的可渲染纹理。这里我们就不新建监视器的渲染纹理和材质了,直接使用之前瞄准镜的即可:

现在分别建立两个摄像机,为方便观察将无人机简单做成一个小飞机的样子:

然后将街头摄像机摆好俯视即可,适当地作一些脚本完成控制,这些脚本如下:

first-person-camera.ts 来自官方例子工程:

import { _decorator, Component, math, systemEvent, SystemEvent, KeyCode, game, cclegacy, Touch, EventKeyboard, EventMouse } from "cc";
const { ccclass, property, menu } = _decorator;
const v2_1 = new math.Vec2();
const v2_2 = new math.Vec2();
const v3_1 = new math.Vec3();
const qt_1 = new math.Quat();
const id_forward = new math.Vec3(0, 0, 1);
const KEYCODE = {W: 'W'.charCodeAt(0),S: 'S'.charCodeAt(0),A: 'A'.charCodeAt(0),D: 'D'.charCodeAt(0),Q: 'Q'.charCodeAt(0),E: 'E'.charCodeAt(0),w: 'w'.charCodeAt(0),s: 's'.charCodeAt(0),a: 'a'.charCodeAt(0),d: 'd'.charCodeAt(0),q: 'q'.charCodeAt(0),e: 'e'.charCodeAt(0),SHIFT: KeyCode.SHIFT_LEFT ,
};@ccclass("COMMON.FirstPersonCamera")
@menu("common/FirstPersonCamera")
export class FirstPersonCamera extends Component {@propertymoveSpeed = 1;@propertymoveSpeedShiftScale = 5;@property({ slide: true, range: [0.05, 0.5, 0.01] })damp = 0.2;@propertyrotateSpeed = 1;_euler = new math.Vec3();_velocity = new math.Vec3();_position = new math.Vec3();_speedScale = 1;onLoad() {math.Vec3.copy(this._euler, this.node.eulerAngles);math.Vec3.copy(this._position, this.node.position);}onDestroy() {this._removeEvents();}onEnable() {this._addEvents();}onDisable() {this._removeEvents();}update(dt: number) {// positionmath.Vec3.transformQuat(v3_1, this._velocity, this.node.rotation);math.Vec3.scaleAndAdd(this._position, this._position, v3_1, this.moveSpeed * this._speedScale);math.Vec3.lerp(v3_1, this.node.position, this._position, dt / this.damp);this.node.setPosition(v3_1);// rotationmath.Quat.fromEuler(qt_1, this._euler.x, this._euler.y, this._euler.z);math.Quat.slerp(qt_1, this.node.rotation, qt_1, dt / this.damp);this.node.setRotation(qt_1);}private _addEvents() {systemEvent.on(SystemEvent.EventType.MOUSE_WHEEL, this.onMouseWheel, this);systemEvent.on(SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);systemEvent.on(SystemEvent.EventType.KEY_UP, this.onKeyUp, this);systemEvent.on(SystemEvent.EventType.TOUCH_MOVE, this.onTouchMove, this);systemEvent.on(SystemEvent.EventType.TOUCH_END, this.onTouchEnd, this);}private _removeEvents() {systemEvent.off(SystemEvent.EventType.MOUSE_WHEEL, this.onMouseWheel, this);systemEvent.off(SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);systemEvent.off(SystemEvent.EventType.KEY_UP, this.onKeyUp, this);systemEvent.off(SystemEvent.EventType.TOUCH_MOVE, this.onTouchMove, this);systemEvent.off(SystemEvent.EventType.TOUCH_END, this.onTouchEnd, this);}onMouseWheel(e: EventMouse) {const delta = -e.getScrollY() * this.moveSpeed / 24; // delta is positive when scroll downmath.Vec3.transformQuat(v3_1, id_forward, this.node.rotation);math.Vec3.scaleAndAdd(v3_1, this.node.position, v3_1, delta);this.node.setPosition(v3_1);}onKeyDown(e: EventKeyboard) {const v = this._velocity;if (e.keyCode === KEYCODE.SHIFT) { this._speedScale = this.moveSpeedShiftScale; }else if (e.keyCode === KEYCODE.W || e.keyCode === KEYCODE.w) { if (v.z === 0) { v.z = -1; } }else if (e.keyCode === KEYCODE.S || e.keyCode === KEYCODE.s) { if (v.z === 0) { v.z = 1; } }else if (e.keyCode === KEYCODE.A || e.keyCode === KEYCODE.a) { if (v.x === 0) { v.x = -1; } }else if (e.keyCode === KEYCODE.D || e.keyCode === KEYCODE.d) { if (v.x === 0) { v.x = 1; } }else if (e.keyCode === KEYCODE.Q || e.keyCode === KEYCODE.q) { if (v.y === 0) { v.y = -1; } }else if (e.keyCode === KEYCODE.E || e.keyCode === KEYCODE.e) { if (v.y === 0) { v.y = 1; } }}onKeyUp(e: EventKeyboard) {const v = this._velocity;if (e.keyCode === KEYCODE.SHIFT) { this._speedScale = 1; }else if (e.keyCode === KEYCODE.W || e.keyCode === KEYCODE.w) { if (v.z < 0) { v.z = 0; } }else if (e.keyCode === KEYCODE.S || e.keyCode === KEYCODE.s) { if (v.z > 0) { v.z = 0; } }else if (e.keyCode === KEYCODE.A || e.keyCode === KEYCODE.a) { if (v.x < 0) { v.x = 0; } }else if (e.keyCode === KEYCODE.D || e.keyCode === KEYCODE.d) { if (v.x > 0) { v.x = 0; } }else if (e.keyCode === KEYCODE.Q || e.keyCode === KEYCODE.q) { if (v.y < 0) { v.y = 0; } }else if (e.keyCode === KEYCODE.E || e.keyCode === KEYCODE.e) { if (v.y > 0) { v.y = 0; } }}onTouchMove(e: Touch) {e.getStartLocation(v2_1);if (v2_1.x > cclegacy.winSize.width * 0.4) { // rotatione.getDelta(v2_2);this._euler.y -= v2_2.x * 0.5;this._euler.x += v2_2.y * 0.5;} else { // positione.getLocation(v2_2);math.Vec2.subtract(v2_2, v2_2, v2_1);this._velocity.x = v2_2.x * 0.01;this._velocity.z = -v2_2.y * 0.01;}}onTouchEnd(e: Touch) {e.getStartLocation(v2_1);if (v2_1.x < cclegacy.winSize.width * 0.4) { // positionthis._velocity.x = 0;this._velocity.z = 0;}}changeEnable() {this.enabled = !this.enabled;}
}

PlayerController.ts:

import { _decorator, Component, Node, KeyCode, EventKeyboard, RigidBody, Vec3, v3, input, Input } from 'cc';
const { ccclass, property } = _decorator;@ccclass('PlayerController')
export class PlayerController extends Component {@propertymoveSpeed = 10;@propertyrotSpeed = 90;private keyMap = {};start() {input.on(Input.EventType.KEY_DOWN,this.onKeyDown,this);input.on(Input.EventType.KEY_UP,this.onKeyUp,this);}setRotSpeed(value){this.rotSpeed = value;}private onKeyDown(e: EventKeyboard) {this.keyMap[e.keyCode] = true;}private onKeyUp(e: EventKeyboard) {this.keyMap[e.keyCode] = false;}private vec3:Vec3 = v3();update(deltaTime: number) {        if (this.keyMap[KeyCode.KEY_W]) {Vec3.add(this.vec3,this.node.position,this.node.forward.clone().multiplyScalar(-this.moveSpeed * deltaTime));this.node.position = this.vec3; } else if (this.keyMap[KeyCode.KEY_S]) {Vec3.add(this.vec3,this.node.position,this.node.forward.clone().multiplyScalar(this.moveSpeed * deltaTime));this.node.position = this.vec3; }else {}if (this.keyMap[KeyCode.KEY_A]) {this.node.setRotationFromEuler(0,this.node.eulerAngles.y + deltaTime * this.rotSpeed,0);}else if (this.keyMap[KeyCode.KEY_D]) {this.node.setRotationFromEuler(0,this.node.eulerAngles.y + deltaTime * -this.rotSpeed,0);}}
}

FirstPersonGunCamreSc.ts:

import { _decorator, Component, Node, CCObject, Vec3, Quat, tween, Camera } from 'cc';
const { ccclass, property } = _decorator;@ccclass('FirstPersonGunCamreSc')
export class FirstPersonGunCamreSc extends Component {private original_position:Vec3;@property(Camera)aniCamera:Camera = null;start() {this.original_position = this.node.position.clone();}aim(){tween(this.node).to(0.3,{position:this.aniCamera.node.position}).start();tween(this.getComponent(Camera)).to(0.3,{fov:this.aniCamera.fov}).start();}unAim(){tween(this.node).to(0.3,{position:this.original_position}).start();tween(this.getComponent(Camera)).to(0.3,{fov:45}).start();}update(deltaTime: number) {}
}

GunSc.ts:

import { _decorator, Component, Node, SkeletalAnimation, input, Input, EventKeyboard, misc, KeyCode } from 'cc';
import { FirstPersonGunCamreSc } from './FirstPersonGunCamreSc';
import { PlayerController } from './PlayerController';
const { ccclass, property } = _decorator;@ccclass('GunSc')
export class GunSc extends Component {@property(SkeletalAnimation)gunSA:SkeletalAnimation = null;@property(FirstPersonGunCamreSc)FirstPersonGunCam:FirstPersonGunCamreSc = null;start() {this.playIndex(5);input.on(Input.EventType.KEY_DOWN,this.onKeyDown,this);}private _isaim = false;private onKeyDown(e:EventKeyboard){if(e.keyCode == KeyCode.SPACE){this._isaim = !this._isaim;if(this._isaim){this.aim();}else{this.unAim();}}}update(deltaTime: number) {}private playIndex(index) {const animatname = this.gunSA.clips[index].name;this.gunSA.play(animatname);this.gunSA.crossFade(animatname);}aim(){this.FirstPersonGunCam.aim();this.playIndex(1);this.getComponent(PlayerController)?.setRotSpeed(30);}unAim(){this.playIndex(5);this.FirstPersonGunCam.unAim();this.getComponent(PlayerController)?.setRotSpeed(90);}
}

现在给摄像机上添加渲染纹理,为监视器添加对应的材质。如此一来,我们在电视上看到了街头监视器画面,而屏幕左边则投射了无人机画面。由于有控制脚本,我们可以控制它到处飞行一下,看看效果:

资源链接

  • 源码下载丨Cocos Store

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

  • 视频教程(UP 主:Nowpaper)

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

  • 论坛讨论帖:

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

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

Nowpaper 往期分享

《笼中窥梦》多维空间视错觉效果

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

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

《饥荒》同款视觉表现

用 RenderTexture 实现小地图与传送门

Cocos Store 正在举办618大促活动,超低优惠

还有Cocos周边实物礼品赠送!

Cocos Creator实现FPS经典瞄准镜+监视器相关推荐

  1. Cocos Creator 初探:修改Engine来调整FPS信息显示

    Cocos Creator 刚用上,感觉这不就是Unity的孪生兄弟嘛,简单看完example,然后翻了翻手册,开始上手第一个小项目. 创建了一个TypeScript项目,刚画了个背景,调整了一下大小 ...

  2. cocos creator学习个人踩坑(3)--关于项目调试在一些安卓机上FPS低

    一些cocos creator遇到的小问题 问题 在做点消游戏项目的时候,在一些旧的安卓机上测试时消除特效显示会有明显的卡顿,火箭消除会有残留,FPS下降明显 原因 通过真机调试,发现日志输出会明显导 ...

  3. cocos creator经典游戏英文版《俄罗斯方块》源码H5+安卓+IOS三端源码

    cocos creator2.2.2经典游戏英文版<俄罗斯方块>源码H5+安卓+IOS三端源码,开发脚本为typeScript方便扩展和阅读,支持cocos creator2.X版本,完整 ...

  4. cocos creator 获取当前时间_前端开发者入门 Creator 必读吧

    写在前面 因为公司的业务需求,近期学习了Cocos Creator这款游戏引擎的开发,也基于此上线了一款游戏,因此写这系列文章记录一下我从入门到项目发布的学习过程. 相对于 web 开发,像Cocos ...

  5. 022 - cocos creator 3D

    #cocos creator 3D warning 报错:"project:///assets/main.js,将https中的export注视掉重试一遍 知识点 scrollview组件添 ...

  6. 《Cocos Creator游戏实战》你画我猜中的画板功能

    你画我猜中的画板功能 创建节点 完成脚本 本节我们来做一个画板,该画板一共有三个小功能: 调节笔刷大小 改变笔刷颜色 橡皮擦 运行效果如下: Cocos Creator版本:2.2.0 后台回复&qu ...

  7. Cocos Creator游戏开发教程 学习笔记

    学完提问几个问题吧: position的锚点位置数值原点在哪里? 因为position是相对坐标,所以原点是父节点的锚点 .所以Canvas下面的直属节点原点就是世界坐标系的原点Canvas的锚点. ...

  8. 麒麟子Cocos Creator实用技巧八:回合战棋类RPG战斗效果

    HELLO,大家好,我是麒麟子.作为Cocos社区高产用户,今天又给大家带来了一个看起来很酷,但实际上大多数人用不到的DEMO. 不知道大家是否记得梦幻西游.问道.英雄无敌.仙剑奇侠传.神仙道.神曲O ...

  9. Cocos Creator 微信创意小游戏《蛇它虫》团队专访:玩法画面均是新意

    <蛇它虫>是由杭州电魂网络投资的文艺复兴工作室自主研发的一款益智解谜小游戏,在今年 1 月 9 日的微信公开课上,与其他五款小游戏一起,作为首批微信创意小游戏,在公开课上的小游戏环节亮相. ...

  10. cocos creator 3.x 精灵不显示、加载动态图片、物理碰撞、人物跟随鼠标移动、碰撞后节点销毁

    温馨提醒:即刻转去Unity3d 精灵不显示: 不要在空节点下直接添加组件 正确的做法是:在空节点(Node)上右键创建一个精灵才会给看到 或者直接拖曳一个图片放到场景编辑器中也可 cocos cre ...

最新文章

  1. 研究速递:老年人的心智能力不一定在衰减,抗干扰的能力会更强
  2. linux c编程项目实例,Linux c编程实例_例子
  3. 记在两周Android实训之后
  4. 初学Linux第三周
  5. 图像领域深度学习的七个境界
  6. c语言中如何让鼠标在一个窗口之外不能点击_Excel系列教程:如何自动填充单元格...
  7. openlayers属性数据mysql_OpenLayers学习笔记8——使用servlet从mysql获取数据并标注
  8. java 反编译 行号对齐 decompiler如何去掉行号
  9. 笔记本电脑桌面的计算机不见了,小编为你分析win7系统笔记本电脑桌面计算机图标不见了的设置方案....
  10. 急聘:IDC机房建设相关职位
  11. 如何在线下载哔哩哔哩上的视频
  12. excel清单数据导入到开票软件中进行开票
  13. python培训班怎样收费
  14. java实现给手机发短信验证码
  15. 基于润和3516 dv300开发板,运行鸿蒙3.1 ArkUI helloworld
  16. PCL入门系列 —— PassThrough 直通滤波、点云裁剪
  17. obj文件格式与.mtl文件格式
  18. 计算机制图 教学大纲,《工程制图与计算机绘图》教学大纲
  19. java图形化Swing教程(一)
  20. idea跳到下一个断点_IDEA---断点调试Debug

热门文章

  1. python判断火车票座位_利用Python实现命令行版的火车票查看器
  2. 【日常】python脚本系列:拳皇13一键出招以及连段实现
  3. 两个3G模块相互通信
  4. 心理传染与恐怖的“模仿者效应”
  5. Android UI绘制流程源码详细讲解Draw(Canvas canvas)
  6. ios申请企业开发者账号的代理_苹果企业开发者账号的申请详解
  7. dcs常用的冗余方式_DCS系统冗余技术讲解
  8. ES6之Symbol详解
  9. 平均增长率不用计算机,官方数据:平均增长率计算公式如何使用excel计算平均增长率...
  10. 模拟人生 java 安卓版,超级模拟人生官方下载_超级模拟人生安卓版预约下载v1.0_3DM手游...