碎碎念:最近因为要做新的3D项目学习了playcanvas,之前用threejs,导入复杂模型(fbx)和大量纹理时,ios容易自动刷新页面,在网络上看到有人提到是因为IOS对于脚本执行使用的内存有限制,不过具体原因还未查明;playcanvas在这方面的处理貌似比较好,加上设计师可以很方便使用它的编辑器来调贴图等效果,以及能够自动烘焙贴图的优势,所以这次选择使用它来做项目,这篇博客主要是针对自己在项目中使用过的功能做一个总结。
这次学习有两个难点,第一个难点是不适应可视化编辑器,用threejs的时候可以很清楚了知道自己设置的坐标,脑内能建立起场景,对于代码逻辑会比较清晰,到了编辑器上反而有点搞不清了;第二个是playcanvas的开发模式,将脚本挂载在物体上的这种操作一开始让我很难理解,用threejs开发时,一般是一个脚本,使用对象时直接获取进行操作,playcanvas虽然也可以这样做,但是看官方案例,以及它在编辑器内不能直接拿到对象而是用
app.root.getByName()的获取形式,通过name去一个个获取对象明显是不推荐的,而挂载在对象上的脚本可以直接获取当前对象,也不用进行多次查找。
相关资料
- 用户手册:developer.playcanvas.com/zh/user-man…
- Api:developer.playcanvas.com/en/api/
- 案例:developer.playcanvas.com/en/tutorial…
用户手册是针对编辑器的,对编辑器有很详细的介绍;api比较繁复,我选择通读一下,没有细看,实际使用的时候再去查找;官方的案例也很多,基本覆盖了开发中所需要的功能,比较方便的是因为代码是直接挂载在对象上的,所以有相同的功能可以直接下载代码,挂载到对象就完事。
自定义加载
不设置自定义加载的时候,playcanvas会有一个默认的加载页面,一般做项目的时候还是会根据设计修改,不过它的写法有点麻烦,要直接写原生js操作som,css需要用字符串,编辑好在设置中的loading scene设置对应js即可生效。
官网案例: developer.playcanvas.com/zh/tutorial…
透明画布
有时候一些在模型后面的背景和动画没必要放在3维里面,所以将画布背景设为透明,直接展示页面元素更好。
- 设置页面 -> RENDERING -> Transparent Canvas 勾选
- 将当前摄像机的Clear Color属性设置为00000000
精灵图
精灵图可以很方便地做序列帧动画: developer.playcanvas.com/zh/tutorial…
摄像机轨道控制器
简单的控制相机脚本,一般展示模型用这个就够了 developer.playcanvas.com/zh/tutorial…
物体拖拽、缩放、自动旋转
鼠标/单指控制物体旋转
developer.playcanvas.com/zh/tutorial…
拖拽、双指缩放
// 使用hammerjs
// 初始化
var options = {
recognizers:[
[Hammer.Pinch],
[Hammer.Pan]
]
};
this.hammer = new Hammer(app.graphicsDevice.canvas, options);
var scaleRadio = 1; // 初始
var maxSize = 3.2, minSize = 1; // 限定大小范围
var dX = 0, dY = 0;
var xLimit = 100, yLimit = 100; // 限制移动范围
var s = new pc.Vec3(0, 0, 0); // 存储移动坐标
var moveObject = this.moveObject; // 要操作的对象
function pinch () {
moveObject.setLocalScale(scaleRadio);
marsContainer.setPosition(s.x, s.y, 0); // z固定
}
// 双指缩放
hammer.on("pinchstart", function(e) {
var _scaleRadio = scaleRadio;
hammer.on("pinchout", function(e) {
scaleRadio = _scaleRadio * e.scale;
if (scaleRadio > maxSize) scaleRadio = maxSize;
pinch();
});
hammer.on("pinchin", function(e) {
scaleRadio = _scaleRadio * e.scale;
if (scaleRadio < minSize) scaleRadio = minSize;
pinch();
});
}.bind(this));
// 移动
hammer.on("panstart", function (e) {
});
hammer.on("panmove", function (e) {
dX = dX + e.deltaX;
dY = dY + e.deltaY;
if (dY < -yLimit) dY = -yLimit;
if (dY > yLimit) dY = yLimit;
if (dX < -xLimit) dX = -xLimit;
if (dX > xLimit) dX = xLimit;
// 以750*1624屏幕中心为基准点计算,可自定义
s = _this.cameraEntity.camera.screenToWorld(375 + dX, 812 + dY, _this.cameraEntity.camera.farClip);
pinch();
}.bind(this));
脚本通信
因为playcanvas是将脚本挂载在物体上的,在运行过程中肯定会有A脚本触发的事件需要B脚本执行某些操作的情况,这时候就需要用到脚本通信了。 developer.playcanvas.com/zh/user-man…
射线
用户点选三维物体或者操作三维物体也是一个常用的功能,跟threejs一样,playcanvas有射线检测的功能,可以理解为从屏幕空间到三维连一条线,看看有没有在这条线上的物体。
屏幕截图
要做生成海报或者截取当前画面,直接调用canvas.toDataURL()是不行的,当前帧可能还没有渲染后,正确的方式是在app的postrender事件中调用
var Screenshot = pc.createScript('screenshot');
Screenshot.prototype.initialize = function() {
this._triggerScreenshot = false;
this._window = null;
// 可以通过用户点击事件触发
this.app.on('ui:takeScreenshot', function () {
this._triggerScreenshot = true;
// Open a new tab
this._window = window.open('', '');
this._window.document.title = "Screenshot";
this._window.document.body.style.margin = "0";
}, this);
this.app.on('postrender', this.postRender, this); // 在postrender中调用方法
};
Screenshot.prototype.takeScreenshot = function () {
// 获取当前canvas
var canvas = this.app.graphicsDevice.canvas;
// Show the image data in a new window/tab.
var img = new Image();
img.src = canvas.toDataURL();
this._window.document.body.appendChild(img);
};
Screenshot.prototype.postRender = function () {
if (this._triggerScreenshot) {
this.takeScreenshot();
this._triggerScreenshot = false;
}
};
developer.playcanvas.com/zh/tutorial…
线上贴图加载
做三维展示的时候,为了灵活的更换贴图,可能会从后台获取图片链接,这个时候就要将图片转为贴图,一般可以在需要改变贴图的物体上挂载一个属性指向Material文件,加载图片后用图片作为替换材质的贴图,再使用update进行更新。
// 从属性添加要改变贴图的材质
UiManager.attributes.add('myMaterial', {
title: 'myMaterial',
description: '贴图',
type: 'asset',
assetType: 'material'
});
var material = this.myMaterial.resource;
this.app.loader.getHandler("texture").crossOrigin = "anonymous"; // 如果有跨域问题
this.app.assets.loadFromUrl(url, "texture", function (err, asset) {
var texture = asset.resource;
material.diffuseMap = texture;
material.update();
});
playcanvas与dom的结合开发
- 在编辑器使用html和css解析 我习惯用vue开发,这边可以直接将vue.js放在资源文件夹,确保它在初始化之前加载,然后就可以使用了,加载顺序可以在设置 -> SCRIPTS LOADING ORDER中改变;使用这种方式,代码统一放在线上,资源有更新时能保持统一,但是线上编辑器对于html和css并不友好,开发时没有本地顺畅。
var HtmlcssDriver = pc.createScript('htmlcssDriver');
// 获取html和css资源并载入
HtmlcssDriver.prototype.initialize = function() {
var htmlAsset = this.app.assets.find('index.html');
var div = document.createElement('div');
div.innerHTML = htmlAsset.resource;
document.body.appendChild(div);
htmlAsset.on('load', function () {
div.innerHTML = htmlAsset.resource;
});
var cssAsset = this.app.assets.find('main.css');
var style = document.createElement('style');
document.head.appendChild(style);
style.innerHTML = cssAsset.resource;
cssAsset.on('load', function() {
style.innerHTML = cssAsset.resource;
});
};
- 在外部使用 写好程序后打包下载,再到生成的文件夹去写,打包时在选项中取消Concatenete Scripts的勾选,这样打包下来的js文件都是分开的,容易修改。不过这种方式代码容易分散,而且打包下来的文件并没有按类型分,而是每个文件都有一个父级文件夹,找对应文件有一点麻烦,另外就是修改线上资源时要替换本地代码容易混乱,但是这种方式在处理dom交互逻辑时比线上开发方便,也更容易使用其他插件和库。