PlayCanvas運営事務局が提供するチュートリアル内でもUIをHTMLで作成していますが、おおよそはPlayCanvasエディター内で作成したものを追加しているに過ぎません。
今回はPlayCanvasエディターではなく、セルフホスティングを前提としたPlayCanvasエディター外で作成したHTMLのUIを使ってPlayCanvasコンテンツを制御する方法を紹介します
※以下で説明する内容はユーザーマニュアルを参考にしています
→ https://developer.playcanvas.com/ja/user-manual/publishing/web/communicating-webpage/
用意したプロジェクト
今回はこちらのプロジェクトを使用して解説します。
https://playcanvas.com/project/1147869
Model Viewerのテンプレートをフォークして作成したプロジェクトになります。
このプロジェクトでは、3Dモデル、Planeのマテリアル、SkyboxをUIから変更できるようにするための処理を用意します。
ヒエラルキーのエンティティ [PlayCanvas] を [Orgin] の空のエンティティに変更します。
Originは3DモデルのEntityを配下に置くためのエンティティです。
3Dモデル、Planeのマテリアル、Skyboxを変更するスクリプトは、Rootエンティティに設定しています。
それぞれの処理に応じてスクリプト属性を設定しています。
ChangeModel
ChangeModel には、切り替える3DモデルのTemplateを設定しています。
設定したTempalteを、targetEntityのエンティティ配下に入れ替えで追加します。
var ChangeModel = pc.createScript('changeModel');
ChangeModel.attributes.add("targetEntity", {type:"entity"})
ChangeModel.attributes.add("targetModels", {type:"asset", assetType:"template", array:true})
ChangeModel.prototype.initialize = function() {
this.app.on('setEntity', this.setEntity, this); // スクリプト間の通信のためのイベントを作成
// this.app.fire('setEntity', num); // イベントを実行
};
ChangeModel.prototype.setEntity = function(num) {
if(this.targetEntity.children.length 0) {
this.targetEntity.children[0].destroy();
}
let instance = this.targetModels[num].resource.instantiate();
this.targetEntity.addChild(instance)
};
changeMaterial
ChangeMaterial には変更するマテリアルを設定します。
設定したマテリアルを、targetEntityのエンティティのマテリアルに変更されます。
var ChangeMaterial = pc.createScript('changeMaterial');
ChangeMaterial.attributes.add("targetEntity", {type:"entity"})
ChangeMaterial.attributes.add("targetMats", {type:"asset", assetType:"material", array:true})
ChangeMaterial.prototype.initialize = function() {
this.app.on('setMaterial', this.setMaterial, this); // スクリプト間の通信のためのイベントを作成
// this.app.fire('setMaterial', num); // イベントを実行
};
ChangeMaterial.prototype.setMaterial = function(num) {
this.targetEntity.render.material = this.targetMats[num].resource
};
changeSkybox
ChangeSkybox には変更するCubemapを設定します。
var ChangeSkybox = pc.createScript('changeSkybox');
ChangeSkybox.attributes.add("targetSkybox", {type:"asset", assetType:"cubemap", array:true})
ChangeSkybox.prototype.initialize = function() {
this.app.on('setSkybox', this.setSkybox, this); // スクリプト間の通信のためのイベントを作成
// this.app.fire('setSkybox', num); // イベントを実行
};
ChangeSkybox.prototype.setSkybox = function(num) {
this.app.scene.setSkybox(this.targetSkybox[num].resources);
};
3Dモデル、Planeのマテリアル、SkyboxをUIから変更する処理には、this.app.on(); を使用しています。
スクリプト間の通信方法に使用するイベントを追加しています。
参考 : https://developer.playcanvas.com/ja/user-manual/scripting/communication/
他スクリプトから呼び出すには、コメントアウトしている this.app.fire(); を実行します。
プロジェクトをZIP DOWNLOADして、セルフホスティングするHTMLで使う準備をします。
ダウンロードしたファイルの準備(v1.65.5時点)
ZIP DOWNLOADすると以下のようなファイルがダウンロードされます。
ここから使用するファイルは以下になります。
- playcanvas-stable.min.js
-
__settings__.js
-
__modules__.js
-
__start__.js
-
__loading__.js
-
config.json
-
[sceneid].json
それぞれheadタグの中やbody終了タグの手前に追加しています
<head>~~~
<script src="/playcanvas/playcanvas-stable.min.js"></script>
<script src="/playcanvas/__settings__.js"></script>
<script src="/playcanvas/__modules__.js"></script>
</head>
~~~
<body>
~~~
</body>
<script src="/playcanvas/__start__.js"></script>
<script src="/playcanvas/__loading__.js"></script>
このhtmlの全体像は以下のようになります。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title></title>
<script src="/playcanvas/playcanvas-stable.min.js"></script>
<script src="/playcanvas/__settings__.js"></script>
<script src="/playcanvas/__modules__.js"></script>
</head>
<body>
<main>
<section><canvas id="pcApp"></canvas></section>
<section>
<ul>
<li><button class="pcBtn" data-type="model" data-num="1"><span>Model 01</span></button></li>
<li><button class="pcBtn" data-type="model" data-num="2"><span>Model 02</span></button></li>
<li><button class="pcBtn" data-type="model" data-num="3"><span>Model 03</span></button></li>
</ul>
<ul>
<li><button class="pcBtn" data-type="material" data-num="1"><span>Material 01</span></button></li>
<li><button class="pcBtn" data-type="material" data-num="2"><span>Material 02</span></button></li>
<li><button class="pcBtn" data-type="material" data-num="3"><span>Material 03</span></button></li>
</ul>
<ul>
<li><button class="pcBtn" data-type="skybox" data-num="1"><span>Skybox 01</span></button></li>
<li><button class="pcBtn" data-type="skybox" data-num="2"><span>Skybox 02</span></button></li>
<li><button class="pcBtn" data-type="skybox" data-num="3"><span>Skybox 03</span></button></li>
<li><button class="pcBtn" data-type="skybox" data-num="4"><span>Skybox 04</span></button></li>
</ul>
</section>
</main>
<script src="/playcanvas/__start__.js"></script>
<script src="/playcanvas/__loading__.js"></script>
<script>
let pcBtns = document.querySelectorAll(".pcBtn");
let app = pc.Application.getApplication();
app.on("start", function () {
// アプリが実行状態になると"start"イベントを受信します
// 以降、各種処理を実行できる状態です
// PlayCanvas側へのイベントリスナーの登録も可能
// app.on('***', ***);
});
let setEntity = function() {
console.log("setEntity")
}
for(let i=0; i < pcBtns.length; i++) {
pcBtns[i].addEventListener("click", (e) = {
let type = pcBtns[i].getAttribute("data-type")
let num = pcBtns[i].getAttribute("data-num") - 1
if(type === "model") {
app.fire('setEntity', num);
} else if(type === "material") {
app.fire('setMaterial', num);
} else if(type === "skybox") {
app.fire('setSkybox', num);
}
});
}
</script>
</body>
</html>
設定するだけで動作するわけではありません。
それぞれのファイルに追記と修正を行います。
__settings__.js
window.ASSET_PREFIX = "/public/playcanvas/"; // Pathを修正
window.SCRIPT_PREFIX = "/public/playcanvas/"; // Pathを修正
window.SCENE_PATH = ASSET_PREFIX + "1873663.json"; // Pathを修正
window.CONTEXT_OPTIONS = {
'antialias': true,
'alpha': false,
'preserveDrawingBuffer': false,
'preferWebGl2': true,
'powerPreference': "default"
};
window.SCRIPTS = [ [各スクリプトID] ];
window.CONFIG_FILENAME = ASSET_PREFIX + "config.json"; // Pathを修正
window.INPUT_SETTINGS = {
useKeyboard: true,
useMouse: true,
useGamepads: false,
useTouch: true
};
pc.script.legacy = false;
window.PRELOAD_MODULES = [
];
__settings__.js では使用するアセットのパスやエディターで設定したSETTINGSの一部設定を行うことができます。
ここでは、コメントアウトしている箇所のPathを設定します。ZIP DOWNLOADしたファイル群のPathを設定します。
__start__.js
(function () {
// Shared Lib
var CANVAS_ID = 'pcApp'; // canvasを配置する要素のidを設定
// Needed as we will have edge cases for particular versions of iOS
// returns null if not iOS
var getIosVersion = function () {
if (/iP(hone|od|ad)/.test(navigator.platform)) {
var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
var version = [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)];
return version;
}
return null;
};
var lastWindowHeight = window.innerHeight;
var lastWindowWidth = window.innerWidth;
var windowSizeChangeIntervalHandler = null;
var pcBootstrap = {
reflowHandler: null,
iosVersion: getIosVersion(),
createCanvas: function () {
// 既に配置しているcanvas要素を取得
var canvas = document.getElementById(CANVAS_ID);
// 既存の記述は新しく作成するため削除
// var canvas = document.createElement('canvas');
// canvas.setAttribute('id', CANVAS_ID);
canvas.setAttribute('tabindex', 0);
// Disable I-bar cursor on click+drag
canvas.onselectstart = function () { return false; };
// Disable long-touch select on iOS devices
canvas.style['-webkit-user-select'] = 'none';
// 既存の記述は新しく作成するため削除
// document.body.appendChild(canvas);
return canvas;
},
resizeCanvas: function (app, canvas) {
canvas.style.width = '';
canvas.style.height = '';
// canvasのサイズを全画面に勝手にリサイズしようとするので削除
// app.resizeCanvas(canvas.width, canvas.height);
var fillMode = app._fillMode;
if (fillMode == pc.FILLMODE_NONE || fillMode == pc.FILLMODE_KEEP_ASPECT) {
if ((fillMode == pc.FILLMODE_NONE && canvas.clientHeight < window.innerHeight) || (canvas.clientWidth / canvas.clientHeight >= window.innerWidth / window.innerHeight)) {
canvas.style.marginTop = Math.floor((window.innerHeight - canvas.clientHeight) / 2) + 'px';
} else {
canvas.style.marginTop = '';
}
}
lastWindowHeight = window.innerHeight;
lastWindowWidth = window.innerWidth;
// Work around when in landscape to work on iOS 12 otherwise
// the content is under the URL bar at the top
if (this.iosVersion && this.iosVersion[0] <= 12) {
window.scrollTo(0, 0);
}
},
~~~~~~
__start__.js ではPlayCanvasのコンテンツを表示するためのcanvas要素を作成しリサイズなどの処理を実行します。
以下の箇所を修正しています。
3行目
var CANVAS_ID = 'pcApp'; // あらかじめ配置しているcanvas要素のidを設定
ここであらかじめ配置していたHTMLのcanvas要素のIDを設定します。
ここでは以下のようなcanvas要素を使用しています。
<section><canvas id="pcApp" tabindex="0"></canvas></section>
25行目
createCanvas: function () {
// 既に配置しているcanvas要素を取得
var canvas = document.getElementById(CANVAS_ID);
// 既存の記述は新しく作成するため削除
// var canvas = document.createElement('canvas');
// canvas.setAttribute('id', CANVAS_ID);
canvas.setAttribute('tabindex', 0);
// Disable I-bar cursor on click+drag
canvas.onselectstart = function () { return false; };
// Disable long-touch select on iOS devices
canvas.style['-webkit-user-select'] = 'none';
// 既存の記述は新しく作成するため削除
// document.body.appendChild(canvas);
return canvas;
},
先ほど設定したCANVAS_IDからcanvas要素を参照します。
元々はここでcanvasを作成し、canvas要素をHTMLに追加する処理をしていましたが、これらをほぼ全てコメントアウトで削除しています。
47行目
resizeCanvas: function (app, canvas) {
canvas.style.width = '';
canvas.style.height = '';
// canvasのサイズを全画面にリサイズするため削除
// app.resizeCanvas(canvas.width, canvas.height);
var fillMode = app._fillMode;
if (fillMode == pc.FILLMODE_NONE || fillMode == pc.FILLMODE_KEEP_ASPECT) {
if ((fillMode == pc.FILLMODE_NONE && canvas.clientHeight < window.innerHeight) || (canvas.clientWidth / canvas.clientHeight >= window.innerWidth / window.innerHeight)) {
canvas.style.marginTop = Math.floor((window.innerHeight - canvas.clientHeight) / 2) + 'px';
} else {
canvas.style.marginTop = '';
}
}
lastWindowHeight = window.innerHeight;
lastWindowWidth = window.innerWidth;
// Work around when in landscape to work on iOS 12 otherwise
// the content is under the URL bar at the top
if (this.iosVersion && this.iosVersion[0] <= 12) {
window.scrollTo(0, 0);
}
},
~~~~~~
ここではcanvas要素を全画面表示かつリサイズする処理を実行しますが、想定しているcanvas要素が全画面じゃない場合は app.resizeCanvas() をコメントアウトして実行させないようにします。
※ここではFill Modeが"Fill window"での対応した設定をしています。"Keep and aspect"や"none"で設定した場合は、適宜修正が必要になってしまいます。
対応には、htmlのcanvas要素のサイズを設定したFill Modeに合わせてCSS Styleでリサイズさせる必要がありますのでご注意ください。
__loading__.js
ローディング画面はPlayCanvasエディターからでもカスタマイズが可能ですが、document.body.appendChild(wrapper); を使って画面全体にローディング画面を表示してしまいます。
canvas要素上でのみローディングさせたい場合には、
canvas要素の親の要素を以下のようにIDを追加し、var wrapper = document.getElementById("pcAppWrap"); などを作成して、 wrapper.addChild() すると想定したものが実現できるでしょう。
<section id="pcAppWrap"><canvas id="pcApp" tabindex="0"></canvas></section>
ZIP DOWNLOADしたファイルを想定したホスティング環境に用意ができたら、前もって設定していた3Dモデル、Planeのマテリアル、SkyboxをUIから変更する処理を実行します。
PlayCanvasエディターで設定したスクリプトの処理を実行する
前もって設定した処理をHTMLのbutton要素で実行できるように以下のように準備をします。
<section>
<ul>
<li><button class="pcBtn" data-type="model" data-num="1"><span>Model 01</span></button></li>
<li><button class="pcBtn" data-type="model" data-num="2"><span>Model 02</span></button></li>
<li><button class="pcBtn" data-type="model" data-num="3"><span>Model 03</span></button></li>
</ul>
<ul>
<li><button class="pcBtn" data-type="material" data-num="1"><span>Material 01</span></button></li>
<li><button class="pcBtn" data-type="material" data-num="2"><span>Material 02</span></button></li>
<li><button class="pcBtn" data-type="material" data-num="3"><span>Material 03</span></button></li>
</ul>
<ul>
<li><button class="pcBtn" data-type="skybox" data-num="1"><span>Skybox 01</span></button></li>
<li><button class="pcBtn" data-type="skybox" data-num="2"><span>Skybox 02</span></button></li>
<li><button class="pcBtn" data-type="skybox" data-num="3"><span>Skybox 03</span></button></li>
<li><button class="pcBtn" data-type="skybox" data-num="4"><span>Skybox 04</span></button></li>
</ul>
</section>
button要素がクリックされた時のスクリプト処理を追加します。
let pcBtns = document.querySelectorAll(".pcBtn");
let app = pc.Application.getApplication();
app.on("start", function () {
// アプリが実行状態になると"start"イベントを受信します
// 以降、各種処理を実行できる状態です
// PlayCanvas側へのイベントリスナーの登録も可能
// app.on('***', ***);
});
let setEntity = function() {
console.log("setEntity")
}
for(let i=0; i < pcBtns.length; i++) {
pcBtns[i].addEventListener("click", (e) => {
let type = pcBtns[i].getAttribute("data-type")
let num = pcBtns[i].getAttribute("data-num") - 1
if(type === "model") {
app.fire('setEntity', num);
} else if(type === "material") {
app.fire('setMaterial', num);
} else if(type === "skybox") {
app.fire('setSkybox', num);
}
});
}
class="pcBtn" の要素を querySelectorAll() により配列で取得しています。これを展開してbutton要素それぞれにクリックイベントを追加しています。
クリックされたら、data-type、data-numを参照して処理を条件分岐しています。
ここで app.fire('setEntity', num); を使用するためには、pc.Application.getApplication() が必要になります。
ここまでできたらHTMLを確認しましょう。
スクリーンショットは上記を使用して作成したデモ画面になります。
クリックすることで設定したModel、Material、Skyboxを切り替えることができるのが確認できます。
以上がPlayCanvasコンテンツとコンテンツ外のWebUIの組み込みの紹介となります。
PlayCanvasコンテンツ上にHTMLを配置して制御方法は過去チュートリアルでも紹介しておりましたが、コンテンツ外からでも制御が簡単にできることが確認できたと思います。
この通信処理をぜひご活用いただければと思います。
コメント
0件のコメント
サインインしてコメントを残してください。