関連する記事
- そういえば、JavaScript でリアルタイム通信のゲームってどうやって作るのってなったとき。
- 【JavaScript】Photon + PlayCanvas を使ってモバイル・デスクトップで動く一人称視点のマルチプレイができる空間を作る【WebGL】
PlayCanvas について
PlayCanvasは、JavaScriptで記述されたWebGLエンジンであり、OSSのエンジンとSaaS型のエディターを兼ね備えているゲームエンジンです。このエンジンを使用することで、Three.jsやBabylon.jsなどの他のエンジンと同様の方法で3Dゲームを作成できます。また、ビジュアルエディターとコードエディターを備えたエディターがあり、クラウド上でプロジェクトを作成し、公開することができます。エディターは、ドラッグアンドドロップによるGUIからの素材の操作や配置が可能で、FBXやOBJなどの3Dモデル素材を簡単に扱えます。
Photon について
マルチ対戦のゲームを作るためには、Photonというリアルタイム通信エンジンを使います。このエンジンには、JavaScript SDKというツールが用意されており、それを使ってプログラムを書いていくことで、他のプレイヤーとの通信を実現します。なお、同時に接続できるユーザー数が20人以下であれば、Photonは無料で利用することができます。
PlayCanvas でリアルタイム通信をするをプロジェクト を作ってみました。
Photon SDK のドキュメント
PlayCanvas のプロジェクト
PlayCanvas はクラウド上でスクリプトを管理します。
init.js
起動 Photon の起動を行っています。index.html
, style.css
を読み込み UI のセットアップも行います。
app.js
app.js にて Photon と PlayCanvas の同期をしています。Photon には様々なライフサイクルで発行されるイベントがありますが、今回使用したものについて説明します。
/*jshint esversion: 6, asi: true, laxbreak: true*/
const App = pc.createScript("app");
App.prototype.initialize = function () {
this.photon = this.app.root.children[0].photon;
this.photon.setLogLevel(999);
this.photon.onJoinRoom = () => {
Object.values(this.photon.actors).map((event) => {
const { isLocal, actorNr } = event;
if (isLocal) {
} else {
const entity = new pc.Entity();
entity.addComponent("model", {
type: "box",
});
entity.setPosition(0, 1, 0);
entity.tags.add(`${actorNr}`);
this.app.root.addChild(entity);
}
});
};
this.photon.onActorJoin = (event) => {
const { isLocal, actorNr } = event;
if (isLocal) {
} else {
const entity = new pc.Entity();
entity.addComponent("model", {
type: "box",
});
entity.setPosition(0, 1, 0);
entity.tags.add(`${actorNr}`);
this.app.root.addChild(entity);
}
};
this.photon.onEvent = (code, content, actorNr) => {
const entities = this.app.root.findByTag(`${actorNr}`);
const [entity] = entities;
switch (code) {
case 1: {
const { x, y, z } = content;
entity.setLocalPosition(x, y, z);
break;
}
case 2: {
const { x, y, z, w } = content;
entity.setLocalRotation(x, y, z, w);
break;
}
default: {
break;
}
}
};
};
photon.onJoinRoom
onJoinRoom はルームに入った際の処理になります。今回のような作りでは呼ばれるタイミング - ルームを新しく作ったとき - 既存のルームに入る時
- ルームに入った時
- 他のプレイヤーを取得
- 新しいエンティティ("Model:Box")としてゲーム画面に配置
// 1.ルームに入った時
this.photon.onJoinRoom = () => {
//2. 他のプレイヤーを取得
Object.values(this.photon.actors).map((event) => {
const { isLocal, actorNr } = event;
if (isLocal) {
} else {
// 新しいエンティティを作成
const entity = new pc.Entity();
entity.addComponent("model", {
type: "box",
});
// 新しく生成したエンティティのポジションの設定とタグを付与している
entity.setPosition(0, 1, 0); // x, y, z
entity.tags.add(`${actorNr}`);
// PlayCanvasの画面上に配置する
//3. 新しいエンティティ("Model:Box")としてゲーム画面に配置
this.app.root.addChild(entity);
}
});
};
photon.onActorJoin
onActorJoin は自分の入っているルームに他のプレイヤーが参加してきたときの処理になります。
- 他のプレイヤーが入ってきた時
- 新しいエンティティを作成
- 新しいエンティティ("Model:Box")としてゲーム画面に配置
// 1.他のプレイヤーが入ってきた時
this.photon.onActorJoin = (event) => {
// isLocal: 自分であるかの判定
// actorNr: 入った順に1から振られる、ユニークな番号
const { isLocal, actorNr } = event;
// 自分自身であるかの判定
if (isLocal) {
} else {
// 2. 新しいエンティティを作成
const entity = new pc.Entity();
entity.addComponent("model", {
type: "box",
});
// ポジションとタグを設定
entity.setPosition(0, 1, 0);
entity.tags.add(`${actorNr}`);
// 3. 新しいエンティティ("Model:Box")としてゲーム画面に配置
this.app.root.addChild(entity);
}
};
photon.onEvent
onEvent は、今回はplayer.js
により、データが送信された場合に呼び出されます。
-
actorNr
のタグを元にエンティティ
を検索 -
Code
によってPosition
かRotation
かの判定を行う
this.photon.onEvent = (code, content, actorNr) => {
// 1. `actorNr`のタグを元に`エンティティ`を検索
const entities = this.app.root.findByTag(`${actorNr}`);
const [entity] = entities;
//2. `Code`によって`Position`か`Rotation`かの判定を行う
switch (code) {
case 1: {
const { x, y, z } = content;
entity.setLocalPosition(x, y, z);
break;
}
case 2: {
const { x, y, z, w } = content;
entity.setLocalRotation(x, y, z, w);
break;
}
default: {
break;
}
}
};
player.js
Player.js はキーボードの操作が押された際に Photon サーバーにデータを送るものになります。
/*jshint esversion: 6, asi: true, laxbreak: true*/
const Player = pc.createScript("player");
// PlayCanvas Editor上で使用するためにAttributesを作成
// Attributesの説明
// https://developer.playcanvas.com/ja/user-manual/scripting/script-attributes/
Player.attributes.add("moveSpeed", { type: "number", default: 0.1 });
Player.attributes.add("rotateSpeed", { type: "number", default: 2 });
const move = (direction, entity, self) => {
const { photon } = self;
switch (direction) {
case "up": {
entity.translateLocal(0, 0, -self.moveSpeed);
break;
}
case "down": {
entity.translateLocal(0, 0, self.moveSpeed);
break;
}
default: {
break;
}
}
// send position
// コード番号 1で現在のEntityのポジションを送信
photon.raiseEvent(1, entity.getLocalPosition());
};
const rotate = (direction, entity, self) => {
const { photon } = self;
switch (direction) {
case "left": {
entity.rotate(0, -self.rotateSpeed, 0);
break;
}
case "right": {
entity.rotate(0, self.rotateSpeed, 0);
break;
}
default: {
break;
}
}
// send rotation
// コード番号 2で現在のEntityのポジションを送信
photon.raiseEvent(2, entity.getLocalRotation());
};
Player.prototype.initialize = function () {
this.photon = this.app.root.children[0].photon;
if (this.app.touch) {
this.app.touch.on(
pc.EVENT_TOUCHSTART,
() => {
const { photon } = this;
const { x, y, z } = this.entity.getPosition();
this.entity.rigidbody.teleport(x, y + 0.5, z);
photon.raiseEvent(1, this.entity.getLocalPosition());
photon.raiseEvent(2, this.entity.getLocalRotation());
},
null
);
}
};
// update code called every frame
Player.prototype.update = function (dt) {
const { keyboard } = this.app;
// 移動のキーが押されていたら
if (keyboard.isPressed(pc.KEY_W) || keyboard.isPressed(pc.KEY_UP)) {
move("up", this.entity, this);
} else if (keyboard.isPressed(pc.KEY_S) || keyboard.isPressed(pc.KEY_DOWN)) {
move("down", this.entity, this);
}
// 回転のキーが押されたら
if (keyboard.isPressed(pc.KEY_A) || keyboard.isPressed(pc.KEY_LEFT)) {
rotate("right", this.entity, this);
} else if (keyboard.isPressed(pc.KEY_D) || keyboard.isPressed(pc.KEY_RIGHT)) {
rotate("left", this.entity, this);
}
};
photon.raiseEvent
raiseEvent を使用して任意のタイミングでデータの送信を行います。今回は移動が行われた時と回転が行われた際に現在の場所を送信しています。
raiseEvent(eventCode, data, options);
型はそれぞれ
eventCode:number
object: object
options: object
移動時
....
// send position
// コード番号 1で現在のEntityのポジションを送信
photon.raiseEvent(1, entity.getLocalPosition());
};
回転時
...
// send rotation
// コード番号 2で現在のEntityのポジションを送信
photon.raiseEvent(2, entity.getLocalRotation());
};
参考
initialize
PlayCanvas のスクリプトのライフサイクルには、スクリプト一度だけ呼び出されるinitialize
、毎フレーム呼び出されるUpdate
があります。
- Photon のプロパティを代入
- タッチされた際に発火するイベントの定義
Player.prototype.initialize = function () {
// Rootエンティティからphotonプロパティを取得
// 1. Photonのプロパティを代入
this.photon = this.app.root.children[0].photon;
// タッチ操作が可能だったら
if (this.app.touch) {
// タッチ操作が行われたら
// PlayCanvas: タッチについて
// https://support.playcanvas.jp/hc/ja/articles/227190908
// 2. タッチされた際に発火するイベントの定義
// PlayCanvas: イベント種類について
// https://developer.playcanvas.com/ja/user-manual/scripting/communication/
this.app.touch.on(
pc.EVENT_TOUCHSTART,
() => {
const { photon } = this;
const { x, y, z } = this.entity.getPosition();
this.entity.rigidbody.teleport(x, y + 0.5, z);
// コード1で現在のポジションを送信
photon.raiseEvent(1, this.entity.getLocalPosition());
// コード2で現在の回転を送信
photon.raiseEvent(2, this.entity.getLocalRotation());
},
null
);
}
};
update
update
はフレームごとに呼ばれます
- 移動のキーが押されていたら
- 回転のキーが押されたら
Player.prototype.update = function (dt) {
const { keyboard } = this.app;
// 1. 移動のキーが押されていたら
if (keyboard.isPressed(pc.KEY_W) || keyboard.isPressed(pc.KEY_UP)) {
// 移動
move("up", this.entity, this);
} else if (keyboard.isPressed(pc.KEY_S) || keyboard.isPressed(pc.KEY_DOWN)) {
// 移動
move("down", this.entity, this);
}
// 2. 回転のキーが押されたら
if (keyboard.isPressed(pc.KEY_A) || keyboard.isPressed(pc.KEY_LEFT)) {
// 回転
rotate("right", this.entity, this);
} else if (keyboard.isPressed(pc.KEY_D) || keyboard.isPressed(pc.KEY_RIGHT)) {
// 回転
rotate("left", this.entity, this);
}
};
前回
そういえば、JavaScript でリアルタイム通信のゲームってどうやって作るのってなったとき。
以下 PlayCanvas 開発で参考になりそうな記事の一覧です。
- PlayCanvas 入門- モデルの作成~ゲームに入れ込むまで
- JavaScript でスロットを実装する。【PlayCanvas】
- 3D モデルのビューワーを 3 分で作る【初めての PlayCanvas】
- PlayCanvas のコードエディターで es6 に対応する
- Gulp のプラグインを書いたら PlayCanvas での開発がめちゃくちゃ便利になった
- PlayCanvas Editor に外部スクリプトを読み込む新機能が追加されたので開発方法を考える。- Redux を組み込む
その他の記事はこちらになります。
- AR 年賀状を作る
- React Native + PlayCanvas を使ってスマートフォンゲームを爆速で生み出す
- PlayCanvas のエディター上で HTML, CSS を組み込む方法
- 【iOS13】新しくなった WebVR の使い方
PlayCanvas のユーザー会の Slack を作りました!
少しでも興味がありましたら、ユーザー同士で解決・PlayCanvas を推進するための Slack を作りましたので、もしよろしければご参加ください!