PlayCanvas便利スクリプト
非同期でのシーン移動
async loadScene(sceneName) {
const oldHierarchy = this.app.root.findByName("Root");
const scene = this.app.scenes.find(sceneName);
return new Promise((resolve, reject) => {
this.app.scenes.loadSceneHierarchy(scene.url, (err, parent) => {
if (!err) {
resolve(oldHierarchy);
} else {
reject();
}
});
});
}
}
使用例
async changeVrScene(sceneName) {
this.app.root.findByName("CameraParent").setPosition(0, 1.5, 0);
const old = await this.loadScene(sceneName);
old.destroy();
}
円を書く
var CircleEntityCreator = pc.createScript('circleEntityCreator');
CircleEntityCreator.attributes.add("createEntity", { type: "entity" });
CircleEntityCreator.attributes.add("itemCount", { type: "number", default: 40 });
CircleEntityCreator.attributes.add("radius", { type: "number", default: 5 });
CircleEntityCreator.attributes.add("repeat", { type: "number", default: 2 });
CircleEntityCreator.prototype.initialize = function () {
const oneCycle = 2.0 * Math.PI;
for (let i = 0; i < this.itemCount; i++) {
const point = (i / this.itemCount) * oneCycle;
const repeatPoint = point * this.repeat;
const x = Math.cos(repeatPoint) * this.radius;
const y = Math.sin(repeatPoint) * this.radius;
const position = new pc.Vec3(x, y);
const entity = new pc.Entity();
entity.addComponent("render", {
type: "box"
});
entity.setLocalPosition(position);
this.entity.addChild(entity);
}
};
参考にさせていただいたページ
円状にオブジェクトを生成する
全ての日本語を表示する方法
こちらの日本語の一覧を元にフォントアセットにて「Process Font」をクリック
https://gist.github.com/kgsi/ed2f1c5696a2211c1fd1e1e198c96ee4
多くの日本語フォント(Googleフォント)を追加したプロジェクト
Render To Texture
PlayCanvas Editor用スクリプト
var TextureScript = pc.createScript('textureScript');
// script attributes for material
TextureScript.attributes.add('material', {
type: 'asset',
assetType: 'material'
});
TextureScript.prototype.initialize = function() {
var app = this.app;
// create texture and render target for rendering into
const texture = new pc.Texture(app.graphicsDevice, {
width: 512,
height: 512,
format: pc.PIXELFORMAT_RGB8,
mipmaps: true,
minFilter: pc.FILTER_LINEAR,
magFilter: pc.FILTER_LINEAR,
addressU: pc.ADDRESS_CLAMP_TO_EDGE,
addressV: pc.ADDRESS_CLAMP_TO_EDGE,
});
const renderTarget = new pc.RenderTarget({
name: `camera-to-texture`,
colorBuffer: texture,
depth: true,
flipY: true,
samples: 2,
});
// set renderTarget to the camera component
this.entity.camera.renderTarget = renderTarget;
this.material.resource.emissiveMap = texture;
this.material.resource.diffuseMap = texture;
this.material.resource.update();
// get world and skybox layers
const worldLayer = app.scene.layers.getLayerByName("World");
const skyboxLayer = app.scene.layers.getLayerByName("Skybox");
// set layers of this camera
this.entity.camera.layers = [worldLayer.id, skyboxLayer.id];
};
// call this function when material attribute changes
TextureScript.prototype.onAttributeChanged = function(name, oldValue, newValue) {
if(name === 'material' && newValue) {
var materialAsset = this.app.assets.get(newValue);
if (materialAsset) {
// change material of render component
this.entity.render.material = materialAsset.resource;
}
}
};
PlayCanvas用にモーフ(シェイプキー)をBlenderから全て書き出すためのPythonスクリプト
- シェイプキーのあるオブジェクト(Body / Faceなど)を選択
- BlenderのScriptを実行するとGLB形式ですべてアニメーション付きのGLBがBlenderから出力されます。
import bpy
# オブジェクトを選択します。
obj = bpy.context.object
# シェイプキーのリストを取得します。
shape_keys = obj.data.shape_keys.key_blocks
# ループの回数を制限します。
loop_count = min(5, len(shape_keys) - 1)
# 各シェイプキーに対してアニメーションを作成し、GLBとしてエクスポートします。
for key in shape_keys[1:1+loop_count]: # Basisはスキップし、ループを5回までに制限します。
# 新しいアクションを作成してアサイン
action_name = f"Action_{key.name}"
action = bpy.data.actions.new(action_name)
obj.data.shape_keys.animation_data_create()
obj.data.shape_keys.animation_data.action = action
# シェイプキーの値をすべて0にリセット
for k in shape_keys[1:]:
k.value = 0.0
k.keyframe_insert(data_path="value", index=-1, frame=0)
# ターゲットとなるシェイプキーのアニメーションを設定
key.value = 1.0
key.keyframe_insert(data_path="value", index=-1, frame=24)
# GLBとしてエクスポート
export_path = f"your_directory_path/{key.name}.glb" # 保存先のパスを設定してください
bpy.ops.export_scene.gltf(filepath=export_path, export_format='GLB')
# 作成したアクションを削除
bpy.data.actions.remove(action)
- PlayCanvasのAnim State Graphで設定します。
シーンのデータを元に360度動画の撮影をする
PlayCanvasのカメラを使って360度動画を撮影する方法を紹介します。
プロジェクトの元になるのは、以下のPublicプロジェクトです。
https://playcanvas.com/project/1203128
実行URLはこちらです。
https://playcanv.as/p/wrwiuEbZ/
360度動画撮影の仕組み
プロジェクト内のコードは、正距円筒図法(Equirectangular projection)でカメラを描画をすることで作成ができました。実装は、PlayCanvasのReflection Cubemapのデモを元に実装をしたコードがこちらです。
const EquirectangularCamera = pc.createScript('equirectangular-camera');
EquirectangularCamera.attributes.add("srcSphere", { type: "entity" });
EquirectangularCamera.prototype.postInitialize = function () {
this.textureEqui = this.createReprojectionTexture(pc.TEXTUREPROJECTION_EQUIRECT, 2048);
};
EquirectangularCamera.prototype.postUpdate = function () {
pc.reprojectTexture(this.srcSphere.script.cubemapRenderer.cubeMap, this.textureEqui, { numSamples: 1 });
this.app.drawTexture(0, 0, 2, 2, this.textureEqui);
}
EquirectangularCamera.prototype.createReprojectionTexture = function (projection, size) {
return new pc.Texture(this.app.graphicsDevice, {
width: size,
height: size,
format: pc.PIXELFORMAT_RGB8,
mipmaps: false,
minFilter: pc.FILTER_LINEAR,
magFilter: pc.FILTER_LINEAR,
addressU: pc.ADDRESS_CLAMP_TO_EDGE,
addressV: pc.ADDRESS_CLAMP_TO_EDGE,
projection: projection
});
};
このプロジェクトをフォークするか、一部を使用することで360度動画を撮影することができます。
プロジェクトを移植する際の設定
1. レイヤーの設定
- 新規にレイヤー(
excludedLayer
)を作成します。 - レイヤーを適切に設定します。
2. ヒエラルキーの設定
- カメラに
equirectangular-camera.js
を設定します。 - カメラの子エンティティ(Sphere)を追加し、
cubemap-renderer.js
を設定します。
それぞれカメラのエンティティとカメラの子エンティティの設定はこちらです。
カメラの設定
カメラの子エンティティの設定
3. 解像度の設定
PlayCanvasの設定から、解像度とFill Mode
を設定します。
これにより、起動時にウィンドウのサイズに関わらずアスペクト比を固定することができます。
YouTubeにアップロードをする
設定が完了したら、F11キーを押してシーンを全画面表示にし、録画を行います。
YouTubeにアップロードする場合は、撮影した動画(mp4 / mov形式)に対して「Spatial Media Metadata Injector」を使ってメタデータを付与することで、360度動画としてアップロードできます。
特定のタグのアセットをロード、アンロードを動的に行う方法。
ヒエラルキーにあるRenderコンポーネントを使ったエンティティで使用しているアセットをUnloadをするには、Texture
/ Render
/ Container
にそれぞれの共通のタグを付けることでリソースの開放ができます。
コード 1 アセットのロード / アンロード
var AssetManager = pc.createScript('assetManager');
AssetManager.prototype.initialize = function() {
// アセットロードイベントのリスナーを設定
this.app.on('loadAsset', this.loadAssetWithTag, this);
// アセットアンロードイベントのリスナーを設定
this.app.on('unloadAsset', this.unloadAssetWithTag, this);
};
// タグに基づいてアセットをロードする関数
AssetManager.prototype.loadAssetWithTag = function(tag) {
var assets = this.app.assets.findByTag(tag);
assets.forEach((asset) => {
asset.ready(() => {
console.log('Asset loaded: ' + asset.name);
});
asset.load();
});
};
// タグに基づいてアセットをアンロードする関数
AssetManager.prototype.unloadAssetWithTag = function(tag) {
var assets = this.app.assets.findByTag(tag);
assets.forEach((asset) => {
asset.unload();
console.log('Asset unloaded: ' + asset.name);
});
};
コード 2 アセットのロード / アンロードを順次行う
var AssetManager = pc.createScript('assetManager');
// タグリストと現在のタグインデックス、そして一度にロード/アンロードするアセットの数を定義
AssetManager.attributes.add('tagList', { type: 'string', array: true });
AssetManager.attributes.add('loadUnloadCount', { type: 'number', default: 1 });
AssetManager.prototype.initialize = function () {
this.currentTagIndex = 0;
this.app.on('loadNext', this.handleLoadNext, this);
setInterval(() => {
this.app.fire("loadNext");
}, 1000)
};
// 次のアセットをロードするための関数
AssetManager.prototype.handleLoadNext = function () {
var totalTags = this.tagList.length;
var unloadIndex = this.currentTagIndex;
// 現在のタグのアセットをアンロード
for (let j = 0; j < this.loadUnloadCount; j++) {
var currentUnloadTag = this.tagList[unloadIndex % totalTags];
this.unloadAssetsWithTag(currentUnloadTag);
unloadIndex++;
}
// インデックスを更新
this.currentTagIndex = (this.currentTagIndex + this.loadUnloadCount) % totalTags;
// 次のタグのアセットをロード
var loadIndex = this.currentTagIndex;
for (let i = 0; i < this.loadUnloadCount; i++) {
var currentLoadTag = this.tagList[loadIndex % totalTags];
this.loadAssetsWithTag(currentLoadTag);
loadIndex++;
}
};
// 特定のタグのアセットをロードする関数
AssetManager.prototype.loadAssetsWithTag = function (tag) {
var assets = this.app.assets.findByTag(tag);
assets.forEach((asset) => {
asset.ready(() => {
console.log('Asset loaded: ' + asset.name);
});
this.app.assets.load(asset);
});
};
// 特定のタグのアセットをアンロードする関数
AssetManager.prototype.unloadAssetsWithTag = function (tag) {
var assets = this.app.assets.findByTag(tag);
assets.forEach((asset) => {
asset.unload();
console.log('Asset unloaded: ' + asset.name);
});
};