playcanvas学习总结

2,586 阅读6分钟

碎碎念:最近因为要做新的3D项目学习了playcanvas,之前用threejs,导入复杂模型(fbx)和大量纹理时,ios容易自动刷新页面,在网络上看到有人提到是因为IOS对于脚本执行使用的内存有限制,不过具体原因还未查明;playcanvas在这方面的处理貌似比较好,加上设计师可以很方便使用它的编辑器来调贴图等效果,以及能够自动烘焙贴图的优势,所以这次选择使用它来做项目,这篇博客主要是针对自己在项目中使用过的功能做一个总结。

这次学习有两个难点,第一个难点是不适应可视化编辑器,用threejs的时候可以很清楚了知道自己设置的坐标,脑内能建立起场景,对于代码逻辑会比较清晰,到了编辑器上反而有点搞不清了;第二个是playcanvas的开发模式,将脚本挂载在物体上的这种操作一开始让我很难理解,用threejs开发时,一般是一个脚本,使用对象时直接获取进行操作,playcanvas虽然也可以这样做,但是看官方案例,以及它在编辑器内不能直接拿到对象而是用app.root.getByName()的获取形式,通过name去一个个获取对象明显是不推荐的,而挂载在对象上的脚本可以直接获取当前对象,也不用进行多次查找。

相关资料

用户手册是针对编辑器的,对编辑器有很详细的介绍;api比较繁复,我选择通读一下,没有细看,实际使用的时候再去查找;官方的案例也很多,基本覆盖了开发中所需要的功能,比较方便的是因为代码是直接挂载在对象上的,所以有相同的功能可以直接下载代码,挂载到对象就完事。

自定义加载

不设置自定义加载的时候,playcanvas会有一个默认的加载页面,一般做项目的时候还是会根据设计修改,不过它的写法有点麻烦,要直接写原生js操作som,css需要用字符串,编辑好在设置中的loading scene设置对应js即可生效。

官网案例: developer.playcanvas.com/zh/tutorial…

透明画布

有时候一些在模型后面的背景和动画没必要放在3维里面,所以将画布背景设为透明,直接展示页面元素更好。

  1. 设置页面 -> RENDERING -> Transparent Canvas 勾选
  2. 将当前摄像机的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交互逻辑时比线上开发方便,也更容易使用其他插件和库。