この記事は最終更新日から1年以上が経過しています。
@programming
投稿日 2020/3/6
PixiJSでブラウザゲーム 06ドラクエ風流れるメッセージ
PixiJSでブラウザゲーム 05方向キーで操作では矢印方向キーでキャラを操作する方法を紹介しました。 今回は、ドラクエ風にメッセージが流れるパネルを実装してみたいと思います。 サンプルでは、JSONデータとして与えた文字列の配列を1要素づつ流して行きます。 流れている途中でメッセージパネルをクリックするとすぐに全文字列が表示され、もう一度クリックすると次の要素の文字列を流します。
目次:
前提
- PixiJS v5.2.1
サンプルコード
06-flow-text.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ドラクエ風流れるメッセージパネル</title>
</head>
<body>
<div>
<div>ドラクエ風にメッセージを流します。メッセージパネルをクリックすると次のメッセージが流れます。</div>
<div>流すデータ:</div>
<textarea id="ta" rows="8" style="width:100%">
</textarea>
<button onclick="callApi()">読み込み</button>
<div id="game"></div>
</div>
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.1.3/pixi.min.js"></script>
<script>
// Create a Pixi Application
const app = new PIXI.Application({
width: 512,
height: 512,
antialiasing: true,
transparent: false,
resolution: 1,
});
let gameScene;
let background; // 背景
let msgArea; // 文字表示領域
// #game要素にpixiApp.view追加
document.querySelector("#game").appendChild(app.view);
const dummyjson = {
"text": [
"いろはにほへと ちりぬるを わかよたれそ つねならむ",
"うゐのおくやま けふこえて",
"あさきゆめみし ゑひもせすん",
],
};
$("#ta").val(JSON.stringify(dummyjson, null, 2));
// fire this application!
load(dummyjson);
/**
* ロード
* @param {Object} json - テキストエリアから入力されるJSONデータ
*/
function load(json) {
console.log("initializing by json:", json);
//load an image and run the `setup` function when it's done
PIXI.Loader.shared
// リソースを登録
// 第一引数は任意のキー、第二引数は実体パス
.add("dungeon", "https://i.imgur.com/EzpxVBZ.png")
.load(function(loader, res) {
// ゲームシーンを作成
console.log("creating gameScene...");
gameScene = new PIXI.Container();
app.stage.addChild(gameScene);
//
// 背景作成(ここで画面の大枠サイズを確定)
//
console.log("creating background...");
background = new PIXI.Sprite(PIXI.utils.TextureCache["dungeon"]);
gameScene.addChild(background);
//
// 文字表示領域を作成
//
console.log("creating messaage area...");
msgArea = new MessageArea({
parent: gameScene,
json,
});
console.log("initializing done!");
});
}
/********************************************************************
*
* Custom component
*
********************************************************************/
/**
* 文字表示領域クラス
*/
class MessageArea {
/**
* コンストラクタ
* @param {Container} parent - このクラスが配置される親クラス
* @param {Object} json - データ: { text: Array<String> }
*/
constructor({
parent,
json, // from serverside
}) {
const root = new PIXI.Graphics();
root.beginFill(0x000000);
root.lineStyle(5, 0x00FF00);
root.drawRect(0, 0, 440, 140);
parent.addChild(root);
const textObject = new PIXI.Text("", {
fill: "#ccffcc",
stroke: '#4a1850',
strokeThickness: 6,
breakWords: true,
wordWrap: true,
wordWrapWidth: root.width,
});
root.addChild(textObject);
// 文字ポジ調整
textObject.position.x = 20;
textObject.position.y = 20;
// 領域ポジ調整
root.position.x = 30;
root.position.y = 340;
this.textObject = textObject;
this.root = root;
this.json = json;
this.init();
}
/** 流れるメッセージを開始 */
init(json) {
if (json) this.json = json;
json = this.json;
if (this.status === 1) return; // 処理中はスルー
this.texts = json.text; // メッセージの配列
this.maxIndex = json.text.length - 1; // メッセージの最大配列index
this.currentIndex = -1; // 現在表示しているメッセージ配列のindex番号
this.status = 0; // 0:メッセージ未表示, 1:メッセージ表示中, 2:メッセージ表示完了
this.immediate = false; // 即座表示フラグ
this.clear();
const txt = this.next();
if (txt) {
this.flowText(txt);
}
}
/**
* 文字列を流れるように表示
* @param {String} s - 文字列
*/
flowText(s) {
const self = this;
self.status = 1; // 「文字表示中...」状態に
self.immediate = false; // 即座表示クリア
const txtObj = this.textObject;
const arr = s.split("");
const ite = function*() {
addClickEvent(); // クリックイベントを設定
for (let len = arr.length, i = 0; i < len; i++) {
if (self.immediate) {
txtObj.text = s; // 即座に表示
self.immediate = false; // 即座表示フラグを戻す
break;
}
const item = arr[i];
yield setTimeout(() => {
txtObj.text = txtObj.text + item;
ite.next();
}, 100);
}
// まだ次のメッセージラインが在る場合は▼を付与
if (self.currentIndex < self.maxIndex) {
txtObj.text = txtObj.text + "▼";
}
self.status = 2; // 全文字表示完了
}();
ite.next();
/** クリックイベントを付加 */
function addClickEvent() {
if (self.root.eventNames().length >= 2) return; // 既にイベントが登録されている場合はスルー
self.root.interactive = true;
self.root
.on("click", onClick)
.on("touchstart", onClick);
}
/** クリックハンドラ */
function onClick(e) {
// 文字列表示中は重複処理を避けるために処理を抜ける
// ただし今すぐ全文字列を表示させる
if (self.status === 1) {
self.immediate = true;
return;
}
// 全文字列表示が完了し、次に表示すべきメッセージがある場合はそれを流す
if (self.status === 2) {
const txt = self.next();
if (txt) {
txtObj.text = null;
self.status = 0; // 全文字表示完了フラグをリセット
self.flowText(txt);
}
}
}
}
/**
* 次のメッセージラインを取得(無ければnullを返却)
*/
next() {
const self = this;
if (self.currentIndex >= self.maxIndex) {
return null;
}
self.currentIndex += 1;
return self.texts[self.currentIndex];
}
/** 文字列をクリア */
clear() {
const self = this;
self.textObject.text = null;
}
show() {
const self = this;
self.root.visible = true;
}
hide() {
const self = this;
self.root.visible = false;
}
}
/********************************************************************
*
* Global functions
*
*********************************************************************/
/** IMPLEMENT: APIが呼ばれてJSONを取得した風の処理 */
function callApi() {
// dummy!
onMessage(JSON.parse($("#ta").val()))
}
/** IMPLEMENT: データ受信ハンドラ */
function onMessage(json) {
console.log("onMessage:", json);
msgArea.init(json);
}
/** IMPLEMENT: エラーハンドラ */
function onError(json) {
console.error("onError:", json);
}
</script>
</body>
</html>
メッセージパネルをMessageArea
というコンポーネントクラスにしてちょっと汎用性を高めてみました。
実行
ひとりごと
PixiJSネタもそろそろネタ切れの予感。さて、次は何をやろうかな?
宣伝: PixiJSを使ったブラウザゲーム開発なら田中ソフトウェアラボにご相談ください!