PhotonのJavaScript SDKとPlayCanvasを組み合わせて利用するためのリポジトリです。
大体30分ほどで、ブラウザー上で動く、リアルタイムで位置と回転が同期されているゲームを作ることができます。PlayCanvasとネットワークエンジン PhotonのJavaScript SDKを使用してリアルタイム通信のできる空間を作っていきます。
今回作るもの
前回の記事【JavaScript】 PlayCanvasの公式サンプルを使ってモバイル・デスクトップで動く一人称視点の空間を作る【WebGL】
の続きとして、リアルタイムで動ける3Dのゲームを作ります。
※ こちらは一つのURLを3画面で開いたものを録画したものです。
今回の完成品となるプロジェクトはこちらです
https://playcanv.as/p/f6yr7YSW/
それでは早速作っていきます
準備するもの
- ブラウザー
- PlayCanvasアカウント
https://playcanvas.com/ -
Photonアカウント
https://www.photonengine.com/ja/photon
準備 アカウント作成 (PlayCanvasとPhotonアカウントの作成)
PlayCanvasについて
PlayCanvas Editorを使用して開発をしていきますので、
今回のプロジェクトを実装する上で必要なPlayCanvasアカウントについてはこちらから作成ください。
https://playcanvas.com/
Photonについて
マルチプレイを簡単に実現!世界No.1の独立系ネットワーキングエンジンかつマルチプレイヤープラットフォーム— 迅速で信頼性が高く、拡張可能です。インディーかプロかを問わず、あらゆる用途に対応します。
https://www.photonengine.com/ja/photon
Photonは、Unity多くで使われているようですが、PhotonのREALTIMEにはJavaScriptのSDKも存在しており、20CCU(同時接続)までのプロジェクトでは無料で使用できます。
準備 プロジェクトの準備
こちらの手順は前回のプロジェクトを作成してる方はスキップして頂いても構いません。
前回の記事
【JavaScript】 PlayCanvasの公式サンプルを使ってモバイル・デスクトップで動く一人称視点の空間を作る【WebGL】
の続きですが、こちらを作成されている方は、そのまま利用できます。
この記事で初めてPlayCanvasを触る方は、フォーク機能を使って前回までのプロジェクトをコピーしましょう。
プロジェクトをフォークする
こちらは前回作成したプロジェクトをのASSETS
を一部フォルダを整理したプロジェクトです。
このプロジェクトをフォーク
して使います。
1. フォークするプロジェクトへアクセス
2. Fork
をクリックしてプロジェクトを複製する
3. フォークしたプロジェクトからEDITOR
へアクセス
4. シーンを選択
PlayCanvas エディタ
PlayCanvas エディタはゲームを構成するシーンやエンティティを作成および編集するために使用する視覚的な編集ツールです。
ブラウザー内で実行できるのでどこでも利用可能。
PlayCanvas エンジンを使ってシーンをレンダリングします。WYSIWYG。
ボタン一つでゲームを新規タブでプレイ。
ライブ編集によりゲーム実行時にもイテレート可能。
https://developer.playcanvas.com/ja/user-manual/designer/
起動
プロジェクトの作成はこれで完了しました。
起動すると、一人称視点で動き回れる事がわかります。
このプロジェクトからPhotonとPlayCanvasを組み合わせて作っていきましょう。
Photonの準備 プロジェクト作成 ~SDKの準備
1.管理画面から「新しくアプリを作成する」をクリック
Photonの管理画面にアクセスをします。
2.Photon Realtimeを選択してプロジェクトを作成
3. アプリケーションIDのコピー
プロジェクトを作成することができました、こちらのアプリケーションIDを使用しますので保存をしておきましょう。
4. SDKをダウンロード
こちらからPhotonのJavaScript SDKをダウンロードします。
使用するのは、v4.1.0.1のバージョンのSDKを使用します。
SDKはZIP形式でダウンロードされますので解凍します。
5. SDKをPlayCanvasへドラッグアンドドロップ
解凍したPhotonのSDKをドラッグアンドドロップドロップで配置します
ファイルはphoton-javascript-sdk_v4-1-0-1/lib
ディレクトリにある。Photon-Javascript_Emscripten_SDK.min.js
を使用します。
6. scriptsフォルダーへ移動
ディレクトリの構造によって動作は変わりませんが、管理しやすくするために、scriptsフォルダーにPhotonのSDKをドラッグアンドドロップで移動させましょう。
実装
PlayCanvasでPhotonのSDKを利用するためのスクリプトを書きます。
今回は、3つのファイルを作成します。
スクリプトファイルを3つ作成する
3つのファイルをエディタ上から保存します。
photon-playcanvas.js
photon-loadbalancing.js
photon-playcanvas-connect.js
1. photon-playcanvas.jsを作成
a. ASSETS
で右クリック→ New Asset → Script
b. ファイル名photon-playcanvas.js
を入力
2. 同じ手順でphoton-loadbalancing.js
,photon-playcanvas-connect.js
を作成
先程と同様の手順でphoton-playcanvas.js
とphoton-playcanvas-connect.js
を作成します。
この手順を終えたときにこの3つのファイルが新規に追加されていれば大丈夫です。
スクリプトファイルをコードエディターで編集する
作成した3つのスクリプトファイルをコードエディタから編集をします。
それぞれのコードについては、GitHub こちらのリポジトリにも公開しております。
コードエディタで編集する方法は、ASSETS
の中にあるスクリプトファイルをダブルクリックすることでコードエディタを開くことができます。コードエディタを開くことができたらそれぞれのファイルにソースコードをコピーしましょう。
コードエディタについて
コードエディタについて、ショートカットキー詳しく知りたい方は公式ドキュメントが参考になります。
スクリプトアセットはPlayCanvasのコードエディタを使用して編集されます。エディタでファイルを開くにはスクリプトアセットをダブルクリックします。コード編集の権限を持つ全てのユーザはエディタでリアルタイムに共同編集することができます。コードエディタ画面の右下に他のユーザーのアバターが表示されます。
https://developer.playcanvas.com/ja/user-manual/scripting/code-editor/
それぞれのスクリプトをファイルにコピーしていきます。
こちらのコードになります。
1. photon-playcanvas.js
PlayCanvasのゲームが起動されたときにPhotonを利用するためのスクリプトになります。
/*jshint esversion: 6, asi: true, laxbreak: true*/
class PhotonPlayCanvas extends pc.ScriptType {
initialize () {
const options = {
app: this.app,
appId: this.appId,
appVersion: this.appVersion,
wss: this.wss ? 1 : 0
}
// Photonのセットアップ
this.app.photon = new LoadBalancing(options)
// Connect to Photon Server
this.app.photon.connectToRegionMaster(this.region)
this.app.on('lobby:join', () => {
if (this.app.photon.roomInfos.length === 0) {
try {
this.createRoom(this.roomName)
} catch (e) {
this.joinRoom(this.roomName)
}
} else {
this.joinRoom(this.roomName)
}
})
}
createRoom (roomName) {
this.app.photon.createRoom(roomName)
}
joinRoom (roomName) {
this.app.photon.joinRoom(roomName)
}
}
pc.registerScript(PhotonPlayCanvas)
// LoadBalancing options
PhotonPlayCanvas.attributes.add('appId', { type: 'string' })
PhotonPlayCanvas.attributes.add('appVersion', {
type: 'string',
default: '1.0'
})
PhotonPlayCanvas.attributes.add('wss', { type: 'boolean', default: true })
// Photon realtime options
PhotonPlayCanvas.attributes.add('region', {
type: 'string',
default: 'jp',
description:
'Photon Cloud has servers in several regions, distributed across multiple hosting centers over the world.You can choose optimized region for you.',
enum: [
{ 'Select Region': 'default' },
{ 'Asia, Singapore': 'asia' },
{ 'Australia, Melbourne': 'au' },
{ 'Chinese Mainland (See Instructions) Shanghai': 'cn' },
{ 'Canada, East Montreal': 'cae' },
{ 'Europe, Amsterdam': 'eu' },
{ 'India, Chennai': 'in' },
{ 'Japan, Tokyo': 'jp' },
{ 'South America, Sao Paulo': 'sa' },
{ 'South Korea, Seoul': 'kr' },
{ 'USA, East Washington': 'us' },
{ 'USA, West San José': 'usw' }
]
})
// Room options
PhotonPlayCanvas.attributes.add('roomName', { type: 'string', default: 'room' })
2. photon-loadbalancing.jsを作成
Photon SDK内のLoadBalancingClient
を利用して、Photonを使用するためのスクリプトになります。
class LoadBalancing extends Photon.LoadBalancing.LoadBalancingClient {
constructor (props) {
// Photon Settings
const wss = props.wss
const appId = props.appId
const appVersion = props.appVersion
super(wss, appId, appVersion)
// pc.Application
this.app = props.app
this.setLogLevel(4)
}
onRoomList (e) {
this.app.fire('lobby:join')
}
onJoinRoom (e) {
this.app.fire('room:join')
}
onEvent (code, content, actorNr) {
const payload = { code, content, actorNr }
if (code === 0) {
this.app.fire('sync:position', payload)
return
}
if (code === 1) {
this.app.fire('sync:rotation', payload)
}
}
onActorJoin (e) {
const payload = Object.assign({}, this.myRoomActors())
this.app.fire('sync:players', payload)
}
onActorLeave (e) {
const payload = Object.assign({}, this.myRoomActors())
this.app.fire('sync:players', payload)
}
}
3. photon-playcanvas-connect.jsを作成
Photonからイベントが発火された際にPlayCanvasに変更を加えるスクリプトになります。
/*jshint esversion: 6, asi: true, laxbreak: true*/
const EVENT_LIST = {
POSITION: 0,
ROTATION: 1,
COMMAND: 2
}
class PhotonPlaycanvasConnect extends pc.ScriptType {
raiseEvent (event, data) {
// Photonを使用
this.app.photon.raiseEvent(event, data)
}
// スクリプト読み込まれた際に一度実行される
initialize () {
this.isSync = false
this.app.on('room:join', () => {
this.isSync = true
if (pc.platform.mobile) {
//モバイル端末だった場合の処理
this.app.touch.on(pc.EVENT_TOUCHMOVE, () => {
// get current Player position & rotation
const position = this.Player.getLocalPosition()
const rotation = this.Player.findByName('Camera').getLocalRotation()
this.raiseEvent(EVENT_LIST.ROTATION, Object.assign({}, rotation))
this.raiseEvent(EVENT_LIST.POSITION, Object.assign({}, position))
})
} else {
// KEYDOWNイベントが発火されたら同期をする
this.app.keyboard.on(pc.EVENT_KEYDOWN, () => {
// get current Player position & rotation
const position = this.Player.getLocalPosition()
this.raiseEvent(EVENT_LIST.POSITION, Object.assign({}, position))
})
// MOUSEMOVEイベントが発火されたら同期をする
this.app.mouse.on(pc.EVENT_MOUSEMOVE, () => {
const rotation = this.Player.findByName('Camera').getLocalRotation()
this.raiseEvent(EVENT_LIST.ROTATION, Object.assign({}, rotation))
})
}
})
this.app.on('sync:players', actors => {
const prefix = 'player_'
const tagName = 'player'
const actorNums = Object.values(actors).map(actor => actor.actorNr)
// Playerを同期する
for (let actor of Object.values(actors)) {
const { isLocal, actorNr, name } = actor
if (!isLocal) {
const otherPlayer = this.PlayerTemplate.clone()
otherPlayer.setName(`${prefix}${actorNr}`)
otherPlayer.tags.add(tagName)
otherPlayer.enabled = true
this.app.root.addChild(otherPlayer)
otherPlayer.setLocalPosition(0, 0, 0)
}
}
// Playerのタグタグから現在いないPlayerを消す
{
const players = this.app.root.findByTag(tagName)
for (let player of players) {
const actorNr = player.name.replace(prefix, '')
if (!actorNums.includes(Number(actorNr))) {
player.destroy()
}
}
}
})
this.app.on('sync:rotation', ({ content, actorNr }) => {
const { y, w } = content
const entity = this.app.root.findByName(`player_${actorNr}`)
if (!entity) return
entity.setLocalRotation(0, y, 0, w)
})
this.app.on('sync:position', ({ content, actorNr }) => {
const { x, y, z } = content
const entity = this.app.root.findByName(`player_${actorNr}`)
if (!entity) return
entity.setPosition(x, y + this.yOffset, z)
})
}
}
pc.registerScript(PhotonPlaycanvasConnect)
// Room Option
PhotonPlaycanvasConnect.attributes.add('roomName', {
type: 'string',
default: 'room'
})
// Sync option
PhotonPlaycanvasConnect.attributes.add('Player', { type: 'entity' })
PhotonPlaycanvasConnect.attributes.add('PlayerTemplate', { type: 'entity' })
PhotonPlaycanvasConnect.attributes.add('yOffset', {
type: 'number',
default: -0.7
})
PhotonPlaycanvasConnect.attributes.add('reverse', {
title: "逆向き",
type: 'boolean',
default: true
})
設置
スクリプトをPlayCanvas Editorを使って適用していきます。
スクリプトの読み込み順を変える
PlayCanvasではSCRIPTを読み込む順番をエディタから制御します。
今回PhotonのSDKを使用しているのですが、SDKが読み込まれる前に、PhotonのAPIを使用するとエラーが出てしまいますので、スクリプトの読み込み順序を変更していきます。
1. 設定ボタンをクリック
2. ASSET LOAD TASKを変更する
スクリプトの読み込み順序を、PhotonのSDK → photon-loadbalancing → その他の順に変更します。
変更前
変更後
スクリプトの追加
1. Rootにスクリプトコンポーネントを追加
2. ADD SCRIPTSからphoton-playcanvas
, photon-playcanvas-connect
を追加
a. パースボタンをクリック
追加ができたらパースボタンをクリックします。
スクリプトのパースが行われると、roomName
やPlayer
などの属性を追加できる要素が追加されます。
!
マークなどが出た際には、パースボタンを押してから一度、パースボタンの横からスクリプトを削除してから、もう一度、ADD SCRIPT
を押してください。
3. Photon
のアプリケーションIDを追加する
a. アプリケーションIDを取得
こちらのページからPhotonのアプリケーションIDを取得します。
b. 属性にアプリケーションIDをペースト
これでPhotonとPlayCanvasのつなぎこみの設定は終わりました。
次にプレイヤーを追加していきます。
その他の属性の設定
1. Player
を選択して追加
a. PhotonPlaycanvasConnect
スクリプトのPlayer
属性をクリック
b. ヒエラルキー
からPlayer
エンティティを選択
スクリプト属性について
これで、スクリプトのPlayer
属性にPlayer
エンティティが追加されました。このような形でPlayCanvasでは、ソースコードを変更せず値を変更したい場合などについてはスクリプト属性を使用して値を設定します。
スクリプト属性
スクリプトのアトリビュート機能は、スクリプト内で使用する変数をPlayCanvasエディタ内で編集することができるようにする便利な機能です。この機能を使うことで、一度コードを書いた後にエンティティごと作られるインスタンスにそれぞれ違うパラメータを設定する調整ができるようになります。これにより、アーティスト、デザイナーやその他のプログラマーではないチームメンバーがコードを書かずに値を変更できるにプロパティを露出させることができます。
https://developer.playcanvas.com/ja/user-manual//script-attributes/
自分以外を表示するためのEntityを追加
今回はFPS視点なので、自分のキャラクターは追加をしないで、他の人が入ってきたときにきつね
のアバターで参加をしてもらうことにします。今ヒエラルキーを上にあるFox
を使用して、他の人のアバターを設定しましょう。
1. ヒエラルキーから
エンティティを追加する
ヒエラルキーの+
ボタンをクリックし、Entity
を追加します。
2. エンティティの名前をPlayerTemplate
という名前に変更します。
3. ヒエラルキー上のFox
をPlayerTemplate
ヒエラルキーの配下に配置
ヒエラルキー上から、Fox
エンティティをPlayerTemplate
の配下にドラッグアンドドロップをして配置します。
4. Root
のスクリプトコンポーネントに適用する
5. PlayerTemplate
スクリプトを非表示にする
このPlayerTemplate
としたエンティティは他のプレイヤーが入ってきた際にスクリプトから制御をして表示を切り替えますので、ページを起動した際には、非表示にしておきます。
ここまでできれば終わりです。あとは起動するだけでマルチプレイのゲームができているかと思います。
起動
これで起動をしてみます。
これでPlayCanvasで動くマルチプレイのできるゲームを作成することができました。
ルームの機能がありますので、ルーム名を変えたりすることで参加するユーザーを区切ることもできます。
本日使用したPhotonのドキュメントと、PlayCanvasのドキュメントはそれぞれこちらになります。
Photon LoadBalancingClient
https://doc-api.photonengine.com/en/javascript/current/Photon.LoadBalancing.LoadBalancingClient.html
PlayCanvas ユーザーマニュアル
https://developer.playcanvas.com/ja/user-manual/
補足
向きを変更する
逆向きになっていますのでヒエラルキーをからFox
を選択してy
のrotate
を180
度に設定してあげるといい感じになると思います。
ゲームを公開する
PlayCanvasでは作成したゲームをウェブ上ですぐに公開できます。
1. 左のMEBUからPUBLISH/DOWNLOADをクリック
2. PUBLISH TO PLAYCANVASから公開
3. BUILDSを確認
PUBLISHが成功するとBUILDSに共有できるURLが生成されます。
こちらを共有することで、第三者に完成したプロジェクトを公開できます。
今回の完成品となるプロジェクトはこちらになります。
今回のプロジェクトで質問や意見がありましたら。GitHubのIssueか@mxcn3まで連絡をお願いします。
PlayCanvas開発で参考になりそうな記事の一覧です。 - [PlayCanvasのコードエディターでes6に対応する](https://qiita.com/yushimatenjin/items/a61a21c64c1c1a550dd4) - [Gulpのプラグインを書いたらPlayCanvasでの開発がめちゃくちゃ便利になった](https://qiita.com/yushimatenjin/items/5f0f178e8a4ba4a5ee57) - [PlayCanvas Editorに外部スクリプトを読み込む新機能が追加されたので開発方法を考える。- Reduxを組み込む](https://qiita.com/yushimatenjin/items/7a64220cceac66843d7d) - [React Native + PlayCanvasを使ってスマートフォンゲームを爆速で生み出す](https://qiita.com/yushimatenjin/items/7c7ad5d35473c11f32f2) - [PlayCanvasのエディター上でHTML, CSSを組み込む方法](https://qiita.com/yushimatenjin/items/814b4a32db53397219df)PlayCanvasのユーザー会のSlackを作りました!
少しでも興味がありましたら、ユーザー同士で解決・PlayCanvasを推進するためのSlackを作りましたので、もしよろしければご参加ください!