本チュートリアルの動画をYouTubeから試聴しながらの学習をお奨めしております。
目次
- 概要
- Chapter01 - ProjectをForkする
- Chapter02 - 3Dモデルを配置
- Chapter03 - カメラを移動させよう
- Chapter04 - カメラを移動させるためのマウスイベントを作る
- Chapter05 - レイキャストを使ってイベントを実行させる
- Chapter06 - DOMを表示させる
- おまけ - マウスカーソルのデザインを変えてみる
概要
このチュートリアルでは、3D空間を用いたWebページを作成します。
できるページは以下のようなページができます。
https://playcanv.as/p/kpI6PCif/
スワイプなどのイベントでシーンが変わっていくWebページになります。
配置された3Dモデル、ここではPrimitiveなBoxをクリックすることでもシーンが変わり、DOM要素を表示して情報を載せることもできます。
今回はこの上記のコンテンツを作成して行ってみましょう!
Chapter01 - ProjectをForkする
本チュートリアル用に準備したProjectがあるので、こちらをForkしてチュートリアルを始めます。
以下のURLからProjectをForkします。
https://playcanvas.com/project/1170727/
Fork後、Projectページに飛ばされます。
(この時、Projectページへ飛ばなかった場合はリロードしてください)
ProjectのEditorをクリックしてエディット画面へ進みます。
このProjectにはあらかじめアセットの中にファイルがいくつか入っています。
これらのファイルを使用しつつ、エディットしていきます。
Chapter02 - 3Dモデルを配置
フォークしたプロジェクトには既に3Dモデルを配置しています。
今回は配置している3DモデルはASSET STOREからインポートしたCity Packを使用しています。
もし使用したい3Dモデルデータが他にあれば、そちらを使用していただいて構いません。
fbxなどの3DモデルデータがあればPlayCanvasにimportすることでglbに自動変換してくれます。
他3Dモデルも配置する
他にもAdd EntityからPrimitiveでBoxなどを追加が出来ます。
Assetsの右上のAssets Storeから任意の3Dモデルをインポートして配置することもできます。
City Packはここから追加をしました。
Boxを追加して任意の位置に配置します。
ここでは以下のPositionとScaleで設定しています。
- Position:
- x: 2.5 , y: 1, z: 2
- Scale:
- x: 0.3, y: 0.3, z:0.3
3Dモデルの色を変更する
Assets Storeなど既存の3Dモデルをインポートしている場合はこの処理はスルーしてください。
Primitiveなモデルを追加した場合は真っ白で色がついていないので、マテリアルを付与して色をつけます。
Assetsからプラスボタンまたは右クリックからマテリアルを追加します。
マテリアルを作成したら、名前を任意のものに変えてDiffuseから色を付与します。
マテリアルではDiffuse以外にもSpecularやEnvironmentなど設定ができます。
どれも任意でお好みに変更してみてください。
マテリアルを作成できたら先ほど追加したPrimitiveなBoxに、作成したマテリアルを当てます。
ここで用意したBoxは後ほど使用します。
次はカメラを移動させるスクリプトなどを作っていきます。
Chapter03 - カメラを移動させよう
カメラを移動させていろんな方向から見れるようにしてみましょう。
カメラを移動させるための準備
カメラを移動させた先の視点を1つのシーンとして作っていきます。
そのために、Entityの親子関係を作成していきます。
Rootを選択してからAdd EntityでEntityを追加します。
新しく作ったEntityは親になります。
任意で構いませんが名前を「Scene」という名前に変更しておきます。
先ほどと同じ要領で、作成した親のEntityを選択してAdd EntityでEntityを追加します。
このEntityは子になります。
任意で名前を「scene1」と変更しておきます。
子の中には既存のCameraを複製しscene1に移動し、先ほど作成したBoxも移動して、scene1を作成します。
scene1を複製して scene2, 3, 4...と作成します。
複製はヒエラルキー上部のDuplicate Entityのボタンや [ctrl] + D でも複製ができます。(Macの場合、[command] + D)
複製後、sceneのCameraやBoxの配置を自由に変えてみましょう。
ここで設定したSceneのカメラの位置に、メインカメラを移動できるように設定します。
カメラを移動させるスクリプトを作る
アセットからスクリプト、cameraMove.jsを作成します。
選択しているディレクトリに新規作成されますので、あらかじめ scripts のディレクトリを選択しておきます。
名前を cameraMove.js で作成します。
cameraMove.js をコードエディターで開き、コードを以下に書き換えます
var CameraMove = pc.createScript('cameraMove');
// 属性設定
CameraMove.attributes.add("cameraEntity", { type: "entity" }); // メインカメラのEntity
CameraMove.attributes.add("targetEntityParent", { type: "entity" }); // Scenesの親Entity
CameraMove.prototype.initialize = function() {
var self = this;
self.slide = 0; // 今いくつ目のSceneを見ているか参照する数値
self.slideNum = self.targetEntityParent.children.length; // 子Entityの数
self.app.on("cameraMove:tween", function(num){ // イベント作成
self.slide += num;
if(self.slide >= self.slideNum) { // 子Entityの総数以上なら0に戻る
self.slide = 0;
}else if(self.slide < 0) { // 0以下なら子Entityの総数にする
self.slide = self.slideNum - 1;
}
// ↓子Entityの中のCameraという名前のEntityを参照して引数で渡す
self.tweenTarget(self.targetEntityParent.children[self.slide].findOne("name","Camera"));
}, this);
};
CameraMove.prototype.tweenTarget = function(target) { // カメラの移動する処理
var self = this;
var targetPos = target.getPosition(); // 子EntityのCameraのPosition
var targetRot = target.getEulerAngles(); // 子EntityのCameraのRotation
var t_camera = self.cameraEntity; // メインカメラEntity
// ↓メインカメラのPositionとRotationを子EntityのCameraの値にTweenさせる
t_camera.tween(t_camera.getLocalEulerAngles()).rotate(targetRot, 1.0, pc.SineOut).start();
t_camera.tween(t_camera.getLocalPosition()).to(targetPos, 1.0, pc.SineOut).start();
};
カメラの移動は子エンティティであるsceneを 1 → 2 → 3 → 4...と順に移動することを想定しています。
ここで使用している .tween はあらかじめAssetsに入れていた tween.js というPlayCanvasのライブラリを活用しています。
詳細はこちら↓
PlayCanvas Tweenライブラリ
TweenライブラリはAssets Storeからインポートすることができます。
Rootエンティティを選択して、ADD COMPONENTからScriptコンポーネントを追加し、先ほど作成したcameraMove.jsを登録します。
ADD SCRIPTでcameraMoveを設定できたらParseをクリックして、cameraEntityとtargetEntityParentを設定します。
cameraEntityにはメインカメラとなるCameraエンティティ、targetEntityParentにはSceneを束ねている親エンティティを選択します。
移動させる処理を設定ができましたが、イベントを取得しないとまだ移動することができません。
次は移動させるためのイベントを作成します。
Chapter04 - カメラを移動させるためのマウスイベントを作る
カメラを移動させるスクリプトを実行させるために、イベントを作成していきます。
ここではマウスやタッチでスワイプしたらカメラが移動する、というイベント処理を書いていきます。
アセットから「swipeChanger.js」というスクリプトを作成します。
以下のコードに書き換えます。
var SwipeChanger = pc.createScript('swipeChanger');
SwipeChanger.attributes.add("mainCamera",{type:"entity", title:"Main Camera"}); // メインカメラEntity
SwipeChanger.prototype.initialize = function() {
this.clickFlag = false; // クリックしているかフラッグ
this.moveFlag = false; // 今動いているかフラッグ
this.clickPos = new pc.Vec2(); // クリックしているPosition
this.moveX = 0; // スワイプしたX軸の値
this.moveWheel = 0; // マウスのホイールの値
if(!this.app.touch) { // タッチイベントのif文(スマホなど)
// ↓ マウスクリックがDown・UP・Moveなどの時のイベント実行
this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onDeviceDown, this);
this.app.mouse.on(pc.EVENT_MOUSEUP, this.onDeviceUp, this);
this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onDeviceMove, this);
this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onDeviceWheel, this);
} else { // タッチイベント(スマホなどの時)
// ↓ タッチ操作を始めた・終えた・動いた時のイベント実行
this.app.touch.on(pc.EVENT_TOUCHSTART, this.onDeviceDown, this);
this.app.touch.on(pc.EVENT_TOUCHEND, this.onDeviceUp, this);
this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onDeviceMove, this);
}
var self = this;
this.app.on("swipeChange:true", function(){ // TweenのflagをTrueにするイベント作成
self.moveFlag = true;
});
this.app.on("swipeChange:false", function(){ // Tweenflagfalseにするイベント作成
self.moveFlag = false;
});
};
SwipeChanger.prototype.onDeviceDown = function(ev) {
this.clickFlag = true;
// ↓ クリックしたPositionを格納
if(ev.button === pc.MOUSEBUTTON_LEFT) { // マウスクリックが左ボタンの場合
this.clickPos.x = ev.x;
this.clickPos.y = ev.y;
}
if(this.app.touch && ev.touches.length === 1 ){ // タッチ操作が一つの場合(指一本)
this.clickPos.x = ev.touches[0].x;
this.clickPos.y = ev.touches[0].y;
}
// スワイプする方向をリセット
this.swipemove = "";
};
SwipeChanger.prototype.onDeviceUp = function(ev) {
this.clickFlag = false;
if(this.swipemove === "prev") { // スワイプ方向が戻るなら(左)
// ↓ cameraMove.jsで作成したイベントを使用。1つ次のsceneに移動
this.app.fire("cameraMove:tween", 1);
}else if(this.swipemove === "next"){ // スワイプ方向が進むなら(右
// ↓ cameraMove.jsで作成したイベントを使用。1つ前のsceneに移動)
this.app.fire("cameraMove:tween", -1);
}
};
SwipeChanger.prototype.onDeviceMove = function(ev) {
if(this.clickFlag && !this.moveFlag) { // クリックしながらでかつTweenしていない場合
if(this.app.touch){ // タッチ操作の場合
if(ev.touches.length === 1){ // タッチ操作が一つの場合
this.moveX = ev.touches[0].x; // 移動したx軸分
}
} else {
this.moveX = ev.x; // 移動したx軸分
}
if(this.moveX === 0){ // 移動してなければ返す
return;
} else if(this.clickPos.x + 200 < this.moveX) { // 200以上でx軸が移動していたら
self.moveFlag = true; // Tween実行しますのtrue
this.swipemove = "next"; // スワイプ方向を指示
} else if(this.clickPos.x - 200 > this.moveX) { // -200以下でx軸が移動していたら
self.moveFlag = true; // Tween実行しますのtrue
this.swipemove = "prev"; // スワイプ方向を指示
}
}
};
SwipeChanger.prototype.onDeviceWheel = function(ev) {
this.moveWheel += ev.wheel; // マウスホイールの値を取得
if(!this.moveFlag) { // Tweenしていない場合
if(this.moveWheel > 10) { // マウスホイールの値が10以上の場合
this.moveFlag = true; // Tween実行しますのtrue
this.moveWheel = 0; // マウスホイールの値をリセット
this.app.fire("cameraMove:tween", -1); // 一つ前のsceneにTween
}else if(this.moveWheel < -10){ // マウスホイールの値が-10以下の場合
this.moveFlag = true; // Tween実行しますのtrue
this.moveWheel = 0; // マウスホイールの値をリセット
this.app.fire("cameraMove:tween", 1); // 一つ次のsceneにTween
}
}
};
スクリプトが作成できたら、cameraMove.jsと同様にRootエンティティに追加します。
swipeChanger.jsのParseをクリックしてスクリプト属性のMain CameraにCameraを設定します。
swipeChanger.jsで追加したイベント swipeChange:true/false を 呼び出すために、cameraMove.js の tweenTarget() の処理を以下のコードに書き換えます。
CameraMove.prototype.tweenTarget = function(target) { // カメラの移動する処理
var self = this;
var targetPos = target.getPosition(); // 子EntityのCameraのPosition
var targetRot = target.getEulerAngles(); // 子EntityのCameraのRotation
var t_camera = self.cameraEntity; // メインカメラEntity
// ↓メインカメラのPositionとRotationを子EntityのCameraの値にTweenさせる
t_camera.tween(t_camera.getLocalEulerAngles()).rotate(targetRot, 1.0, pc.SineOut).start();
t_camera.tween(t_camera.getLocalPosition()).to(targetPos, 1.0, pc.SineOut).onComplete(()=>{
self.app.fire("swipeChange:false"); // swipeChange.jsのイベントをTween完了後に呼び出します
}).start();
};
設定ができましたが、Launchで確認してもカメラは動かないと思います。
これは、Launchで見ているカメラがscene4のカメラになっているためです。
これは現状このシーンにはカメラが5つありますが、すべてのCameraのPriorityが0で均一になってしまっているからです。
これをメインカメラを最優先に設定します。
メインカメラであるCameraエンティティを選択し、Priorityの数値を大きくします。
ここでは10に値を変更しています。
変更後、Launchを確認しマウスやタッチでのイベントを確認してみます。
マウスをドラッグしてスワイプするとカメラがscene1,2,3...と設定したカメラの位置に移動しているのが確認できます。
これでチュートリアルの大部分が完成しました。
次は3Dモデルをクリックしてもカメラが移動するイベントも設定します。
Chapter05 - レイキャストを使ってイベントを実行させる
今回作成していく処理のレイキャストは、カメラから見てマウスと重なるCollisionを持つ3Dモデルをクリックしたら、その3Dモデルのエンティティ情報を取得するといったものになります。
早速 raycast.js をアセットから作成します。
今回はメインカメラのCameraエンティティに raycast.js を追加します。
raycast.js を以下のコードに書き換えます。
var Raycast = pc.createScript('raycast');
Raycast.prototype.initialize = function() {
this.rayStart = new pc.Vec3(); // カメラからのRay
this.rayEnd = new pc.Vec3(); // カメラから見てぶつかったRay
if(!this.app.touch) { // タッチ操作でない場合
this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseClick, this); // マウスクリックイベント
this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this); // マウスムーブイベント
} else {
this.app.touch.on(pc.EVENT_TOUCHSTART, this.onMouseClick, this); // タッチ操作イベント
}
};
Raycast.prototype.onMouseClick = function(e) {
if(e.button === pc.MOUSEBUTTON_LEFT) { // 左ボタンの場合
this.doRaycast(e.x, e.y); // マウスの座標を引数で渡す
}
if(this.app.touch && e.touches.length === 1){ // タッチ操作が一つの場合
this.doRaycast(e.touches[0].x, e.touches[0].y); // タッチした座標を渡す
}
};
Raycast.prototype.onMouseMove = function(ev) {
var farClip = this.entity.camera.farClip; // カメラのfarClipの設定(Editorから設定可能)
var nearClip = this.entity.camera.nearClip; // カメラのnearClipの設定(Editorから設定可能)
this.entity.camera.screenToWorld(ev.x, ev.y, nearClip, this.rayStart); // 平面座標から3D空間へ座標を変換しRaycastの始まりの座標を作成
this.entity.camera.screenToWorld(ev.x, ev.y, farClip, this.rayEnd); // 平面座標から3D空間へ座標を変換しRaycastの終わりの座標を作成
// ↓ ここでレイキャストの処理を記載
this.result = this.app.systems.rigidbody.raycastFirst(this.rayStart, this.rayEnd); // 作成した座標を元に当たり判定を持つEntityとぶつかったか結果を出す
};
Raycast.prototype.doRaycast = function (screenX, screenY) { // Raycast処理
if (this.result) { // raycastの結果で分岐
var hitEntity = this.result.entity; // Raycastで取得したEntity
console.log(hitEntity);
}
};
レイキャストの処理にはクリックする3Dモデルにコリジョンを設定する必要があります。
各scene配下のBoxにコリジョンを設定します。
エンティティは [Control] (macOSの場合 [Command]) を押しながらクリックすることで複数選択し、一括で設定することができます。
Collisionを設定後、IMPORT AMMO というボタンが警告と一緒に表示されますので、インポートします。
インポートされるAmmo.jsは物理エンジンで、Collisionなど物理演算を実行する際は使用してください。
また、Boxのサイズに応じて、Collisionのサイズも変更しておきましょう。
今回のBoxのScaleサイズは 0.3 となっていますので、以下のように設定します。
- Type:
- Box
- Half Extents:
- x: 0.15, y: 0.15, z: 0.15
Launchで確認してみます。
Collisionを入れた3Dモデルをクリックするとconsole.logでエンティティの情報が取得できていることが確認できます。
レイキャストの処理ができましたので、ログを表示するのではなくシーンが変更されるようにイベントを実行するように変更します。
クリックしたら「前のScene」「次のScene」と移動するように各sceneにBoxを追加してみましょう。
Boxを複製して、前のSceneへ移動するBoxをPrev、次のSceneへ移動するBoxをNextとします。
それぞれのBoxのTagsに「prev」「next」をそれぞれ付与します。
Tagsを追加したのは、クリックしたEntityがTagsに「prev」があれば前のScene、「next」があれば次のSceneに移動するといった処理を行うためです。
複製したBoxのPrevとNextを任意の位置に移動させておきましょう。
そのTagsを処理するために raycast.js に処理を追記します。
raycast.js の doRaycast() を以下に書き換えます。
Raycast.prototype.doRaycast = function (screenX, screenY) {
if (this.result) { // レイキャストの結果が出ているか
var hitEntity = this.result.entity; // レイキャストで取得したEntity
if(hitEntity.tags._list.includes("prev")) { // tagsにprevがある場合
this.app.fire("cameraMove:tween", -1); // カメラTweenで一つ前のSceneへ
}else if(hitEntity.tags._list.includes("next")) { // tagsにnextがある場合
this.app.fire("cameraMove:tween", 1); // カメラTweenで一つ次のSceneへ
}
}
};
Launchで確認します。
Boxをクリックするとカメラが指定の位置に移動するのが確認できます。
これで、スワイプとクリックでカメラの移動が可能になりました。
次はクリックしたらカメラ移動ではなく、HTMLの情報を見れるようにDOMを表示させてみます。
Chapter06 - DOMを表示させる
PlayCanvasにはElementコンポーネントというUIやテキストなどを作成することができるコンポーネントがありますが、テキスト情報など膨大な量を表示させたい場合はDOMの方が取り扱いやすくあります。
今回はDOMを表示する処理を追加します。
アセットからスクリプト「displayDom.js」を作成します。
displayDom.jsを以下のコードに書き換えます。
var DisplayDom = pc.createScript('displayDom');
DisplayDom.attributes.add("addhtml", {type:"asset", assetType:"html", title:"表示するDomのhtml"}); // 載せるhtmlを登録
DisplayDom.attributes.add("addcss", {type:"asset", assetType:"css", title:"表示するDomのcss"}); // 載せるhtmlのstyle cssを登録
domDisplay = false; // グローバル変数でDOMが表示されている状態を管理
DisplayDom.prototype.initialize = function() {
var self = this;
if(this.addcss){ // cssが登録されている場合
var stylecss = pc.createStyle(this.addcss.resource); // attributeのcssをstyleタグで流し込み
document.head.appendChild(stylecss); // headタグにstyleを追加
}
var pcCanvas = document.body.getElementsByTagName("canvas")[0]; // PlayCanvasのコンテンツを参照
self.wrapper = document.createElement("div"); // wrapperというdivを作成
self.wrapper.classList.add("wrapper"); // wrapperというclass名を付与
// ↓ wrapperにstyleを付与
self.wrapper.style.position = "fixed";
self.wrapper.style.top = "50%";
self.wrapper.style.left = "50%";
self.wrapper.style.transform = "translate(-50%, -50%)";
self.wrapper.style.width = "100vw";
self.wrapper.style.height = "100vh";
self.wrapper.insertAdjacentHTML("beforeend", self.addhtml.resource); // attributeのhtmlをwrapperに流し込み
self.closePointer = document.createElement("div"); // closePointerというdivを作成
self.wrapper.appendChild(self.closePointer); // closePointerをwrapperに入れ込み
// ↓ closePointerにstyleを付与
self.closePointer.style.position = "fixed";
self.closePointer.style.top = "5px";
self.closePointer.style.right = "5px";
self.closePointer.style.width = "60px";
self.closePointer.style.height = "60px";
self.closePointerChild = document.createElement("div"); // closePointerChildというdivを作成
self.closePointer.appendChild(self.closePointerChild); // closePointerChildをclosePointerに入れ込み
// ↓ closePointerChildにstyleを付与
self.closePointerChild.style.width = "100%";
self.closePointerChild.style.height = "100%";
self.closePointerChild.style.borderRadius = "50%";
self.closePointerChild.style.backgroundColor = "#ffffff";
self.closePointerChild.style.transform = "scale(1)";
self.closePointerChild.style.transition = "transform .2s";
self.closePointer.addEventListener("click", function(){ // closePointerのクリックイベント
self.app.fire("displayDom:false"); // イベント発火
});
self.closePointer.addEventListener("mouseover", function(){ // closePointerのマウスオーバーイベント
self.closePointerChild.style.transform = "scale(.5)"; // closePointerChildのscaleを小さく
});
self.closePointer.addEventListener("mouseout", function(){ // closePointerのマウスアウトイベント
self.closePointerChild.style.transform = "scale(1)"; // closePointerChildのscaleを元に戻す
});
self.app.on("displayDom:true", function(page){ // DOMを表示する時のイベント作成
domDisplay = true; // DOMが表示されているFlag
document.body.appendChild(self.wrapper); // wrapperをbodyタグに追加
self.closePointerChild.style.transform = "scale(1)"; // closePointerChildのscaleを付与
});
self.app.on("displayDom:false", function(page){ // DOMを表示しない時のイベント作成
domDisplay = false; // DOMが非表示になっているFlag
self.wrapper.remove(); // wrapperを削除
});
};
HTMLのコードをJavaScriptから設定し、イベントが呼ばれたら表示するような処理を書いています。
クリックしたらDOMが表示される3Dモデルを用意します。
先ほどのprev, nextのBoxと同じ要領で作成します。
DOM表示するエンティティと区別するために、Tagsを「dom」に書き換えます。
追加作成したDOM用のエンティティにADD COMPONENTからScriptコンポーネントを追加し、先ほど作成した displayDom.js を登録します。
その後、Parseしてスクリプト属性には使用するHTMLとCSSをそれぞれ登録します。
ここで使用するHTMLとCSSは、Fork時から事前に入っていたtop.htmlとstyle.cssになります。
表示させる準備ができましたので、レイキャストのクリックでDOMを表示させるために、
raycast.js の doRaycast() を以下のコードに書き換えます。
Raycast.prototype.doRaycast = function (screenX, screenY) { // Raycast処理を使って
if(domDisplay) return; // DOMが表示されている場合はRayCastは処理しない
if (this.result) { // raycastの結果が出ているか
var hitEntity = this.result.entity; // Raycastで取得したEntity
if(hitEntity.tags._list.includes("dom")) { // tagsにdomがある場合
this.app.fire("displayDom:true", "dom"); // DOMを表示する
}else if(hitEntity.tags._list.includes("prev")) { // tagsにprevがある場合
this.app.fire("cameraMove:tween", -1); // カメラTweenで一つ前のSceneへ
}else if(hitEntity.tags._list.includes("next")) { // tagsにpnextがある場合
this.app.fire("cameraMove:tween", 1); // カメラTweenで一つ次のSceneへ
}
}
};
クリックしたエンティティからTagsのdomを参照して、該当するエンティティに設定した displayDom.js にスクリプト属性で登録した HTML が呼ばれるといったものになっています。
(CSSは各エンティティで設定したdisplayDom.jsが読み込まれたタイミングでheadタグ内で追加されています)
最後にLaunchして確認してみましょう!
設定したDOMのエンティティをクリックすると設定したHTMLが表示されるのが確認できます
本チュートリアルはここまでで終わりです。
おつかれさまでした。
あとはお好みでオブジェクトを増やしたり、オブジェクトの形を変えたり…
自由に追加・変更をして、自分だけのコンテンツにしましょう!
また、今回は事前にHTMLとCSSを用意していましたが、是非オリジナルのHTMLとCSSを作成してみてください!
おまけ - マウスカーソルのデザインを変えてみる
マウスストーカーというマウスについてくるようなオリジナルカーソルを作ります。
スクリプト「mouseStalker.js」を作成し、以下のコードに書き換えます。
var MouseStalker = pc.createScript('mouseStalker');
MouseStalker.prototype.initialize = function() {
var self = this;
if(!self.app.touch) { // タッチ操作以外
document.body.style.cursor = "none"; // デフォルトのカーソルを非表示
self.domStalker = document.createElement("div"); // domStalkerのdiv作成
document.body.appendChild(self.domStalker); // bodyタグにdomStalkerを追加
self.domStalker.classList.add("mouseStalker"); // class名を追加
// ↓ styleを追加
self.domStalker.style.position = "fixed";
self.domStalker.style.top = 0;
self.domStalker.style.left = 0;
self.domStalker.style.zIndex = 999;
self.domStalker.style.border = "3px solid #ffffff";
self.domStalker.style.borderRadius = "50%";
self.domStalker.style.transform = "translate3d(0, 0, 0)";
self.domStalker.style.pointerEvents = "none";
self.domStalker.style.mixBlendMode = "difference";
self.domFollower = document.createElement("div"); // domFollowerのdiv作成
self.domStalker.appendChild(self.domFollower); // domStalkerにdomFollowerを追加
// ↓ styleを作成
self.domFollower.style.position = "absolute";
self.domFollower.style.top = 0;
self.domFollower.style.left = 0;
self.domFollower.style.width = "60px";
self.domFollower.style.height = "60px";
self.domFollower.style.border = "1px solid #ffffff";
self.domFollower.style.borderRadius = "50%";
self.domFollower.style.transform = "translate3d(-50%, -50%, 0)";
self.domFollower.style.transitionTimingFunction = "ease-out";
self.domFollower.style.transition = "all .2s";
self.domFollower.style.pointerEvents = "none";
self.app.mouse.on(pc.EVENT_MOUSEMOVE, self.onMouseMove, self); // マウスムーブのイベント
self.app.on("mouseStalker:over", function(){ // マウスが何かにoverした時の処理イベント
self.domFollower.style.borderRadius = "0%";
self.domFollower.style.transform = "translate3d(-50%, -50%, 0) scale(1.2) rotate(180deg)";
});
self.app.on("mouseStalker:out", function(){ // マウスが何かっからoutした時の処理イベント
self.domFollower.style.borderRadius = "50%";
self.domFollower.style.transform = "translate3d(-50%, -50%, 0) scale(1) rotate(0deg)";
});
}
};
MouseStalker.prototype.onMouseMove = function(ev) {
// マウスが移動している座標を基にpositionのtop, leftに値を与える
this.domStalker.style.top = ev.y + "px";
this.domStalker.style.left = ev.x + "px";
};
Rootエンティティに作成したmouseStalker.jsを追加したら、Launchで確認してみましょう。
丸いのがマウスについてくるようになっているのが確認できます。
次はエンティティにマウスオーバーした際にクリックできる場合にはアニメーションさせるようなイベントを実行するように処理を書きます。
まずは raycast.js の onMouseMove() を以下のコードに書き換える
Raycast.prototype.onMouseMove = function(ev) {
var farClip = this.entity.camera.farClip; // カメラのfarClipの設定(Editorから設定可能)
var nearClip = this.entity.camera.nearClip; // カメラのnearClipの設定(Editorから設定可能)
this.entity.camera.screenToWorld(ev.x, ev.y, nearClip, this.rayStart); // 平面座標から3D空間へ座標を変換しRaycastの始まりの座標を作成
this.entity.camera.screenToWorld(ev.x, ev.y, farClip, this.rayEnd); // 平面座標から3D空間へ座標を変換しRaycastの終わりの座標を作成
this.result = this.app.systems.rigidbody.raycastFirst(this.rayStart, this.rayEnd); // 作成した座標を元に当たり判定を持つEntityとぶつかったか結果を出す
if(domDisplay) return; // DOMが表示されている場合はRayCastは処理しない
if (this.result) { // raycastの結果が出ているか
this.app.fire("mouseStalker:over"); // Raycastでマウスオーバーしたらイベント発火
}else{
this.app.fire("mouseStalker:out"); // Raycastからマウスアウトしたらイベント発火
}
};
クリックできるエンティティにマウスオーバーすることでアニメーションするのが確認できます。
しかし、DOMを表示させた時にアニメショーンしたままになってしまうため、元に戻すためにdisplayDom.js に処理を追加します
displayDom.js の53行目から73行目のイベントの処理を以下のコードに書き換えます。
self.closePointer.addEventListener("click", function(){ // closePointerのクリックイベント
self.app.fire("displayDom:false"); // イベント発火
});
self.closePointer.addEventListener("mouseover", function(){ // closePointerのマウスオーバーイベント
self.closePointerChild.style.transform = "scale(.5)"; // closePointerChildのscaleを小さく
self.app.fire("mouseStalker:over"); // マウスオーバーイベント発火
});
self.closePointer.addEventListener("mouseout", function(){ // closePointerのマウスアウトイベント
self.closePointerChild.style.transform = "scale(1)"; // closePointerChildのscaleを元に戻す
self.app.fire("mouseStalker:out"); // マウスアウトイベント発火
});
self.app.on("displayDom:true", function(page){ // DOMを表示する時のイベント作成
domDisplay = true; // Domが表示されているFlag
document.body.appendChild(self.wrapper); // wrapperをbodyタグに追加
self.closePointerChild.style.transform = "scale(1)"; // closePointerChildのscaleを付与
self.app.fire("mouseStalker:out"); // マウスアウトイベント発火
});
self.app.on("displayDom:false", function(page){ // DOMを表示しない時のイベント作成
domDisplay = false; // DOMが非表示になっているFlag
self.wrapper.remove(); // wrapperを削除
self.app.fire("mouseStalker:out"); // マウスアウトイベント発火
});
これでDOMを表示した後でもアニメーションが正常に動作します。
右上のDOMを非表示するボタンにマウスオーバーでもアニメーションすることが確認できます。
これでマウスストーカーができました。
ほとんどがcssのstyleによるものですが、簡単に実装することができます。
PlayCanvasでいろんなコンテンツを作ってみてください!
コメント
0件のコメント
サインインしてコメントを残してください。