【pixi.js】イベントシステムの構築:描写編
こんにちは。継続の錬金術士なおキーヌです。
ブログ毎日更新は123日目になります。
【pixi.js】ドラクエ1式の移動と衝突判定を作ってみよう【蟹歩き】の続きですね。
グリッド移動も衝突判定も出来たので、次はいよいよイベントシステム構築に突入します。
実は私、C言語やRubyやPythonでここまでは作ったことが何度かあったのですがここから先が分からず何度も挫折していました。
しかしわずか数時間で解決できたので今までの挫折と続けてきたプログラミングは無駄じゃなかったんだなと体感出来ました。
今回はイベント作りということで宝箱を出してみます。
それに伴いドット絵を打ったので画像の追加と一部設定ファイルの追加がありますので下記からDLしていつもの通り格納してください。
イベントシステムを作るにあたって、結構前回のコードから関数が増えたり書き換えたりしています。
同じような処理をするものは1つの関数化するというのは結構大事なことですが、
コードが長くなったり同じ処理を使う時がきたらでいいと思います。
無理に関数化することに拘るとそこに時間をかけてしまうので頓挫する要因の1つになるので気を付けましょう。
それではまずイベント用のマップレイヤーを作成しましょう。
やることは衝突判定レイヤーを作った時と全く同じで同じ配列を増やすだけです。
イベントレイヤーを作る
// マップデータ
private MapData:number[][] = [
[
2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,2,
2,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,2,
2,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,2,
2,1,2,1,1,1,1,1,1,2,2,2,2,1,2,2,1,2,2,
2,1,2,2,1,2,2,1,2,2,1,1,1,1,1,2,1,2,2,
2,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,2,2,
2,2,2,2,2,2,1,1,2,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,2,1,1,2,2,2,2,2,2,1,2,
2,1,2,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,2,
2,1,2,1,1,1,1,1,1,2,1,2,1,1,1,1,1,1,2,
2,1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
],
[
9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
9,0,9,0,0,0,0,0,0,9,0,0,0,0,9,0,0,0,9,
9,0,9,0,0,0,0,0,0,9,0,0,0,0,9,0,0,0,9,
9,0,9,0,0,0,0,0,0,9,0,0,0,0,9,0,0,0,9,
9,0,9,0,0,0,0,0,0,9,9,9,9,0,9,9,0,9,9,
9,0,9,9,0,9,9,0,9,9,0,0,0,0,0,9,0,9,9,
9,0,0,0,0,9,0,0,9,0,0,0,0,0,0,9,0,9,9,
9,9,9,9,9,9,0,0,9,0,0,0,0,0,0,0,0,0,9,
9,0,0,0,0,0,0,0,9,0,0,9,9,9,9,9,9,0,9,
9,0,9,9,9,9,9,9,9,9,0,9,0,0,0,0,0,0,9,
9,0,9,0,0,0,0,0,0,9,0,9,0,0,0,0,0,0,9,
9,0,9,9,9,9,9,9,9,9,0,9,9,9,9,9,9,9,9,
9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,
9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
],
[
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
],
];
これだけで長くなりますね……
マップエディタを作るまでの我慢です。
単純に衝突判定レイヤーの後ろに全て0の配列を引っ付けただけです。
これは例えば決定ボタンを押したときとか、動き回るNPCが利用するレイヤーになります。
次にイベントを置きたいところですが、これ以上やったことがなかったのでどうするかを結構悩んでしまいました。
しかし足踏みしてしまっては進まないのでとりあえずガムシャラにやってみたら意外となんとかなるもんですね。
イベントを用意する
RPGツクールをやったことがある人ならすぐ理解できると思うのですが、動き回るNPCや宝箱のことを総じてイベントと呼びます。
イベントもプレイヤーと同じくしてグラフィックをもつものなのでまず考えられるのはステージにスプライトを追加することですね。
ですが1つ1つ書いていては何個もイベントを作るときとても面倒になります。
なのでイベントの設定ファイルを外部で作成することにしました。
なるべく1つのソースコードにまとめたかったのですが見辛くなってしまうため、
やむなくイベントに関しては外部jsonファイルにまとめました。
今回DLしてもらった素材の中に既に含んでいるのですが中身を紹介しておきます。
場所とファイル名は「electron_ts_pixi\src\json\event\map\map001.json」になります。
{
"event" :
[
{
"id":"1",
"move": "static_move",
"image": "actor01.png,",
"json" : "/actor/chest01.json",
"defaultSpriteName": "chest01_c0.png",
"animateSprites": "chest01_c0.png,chest01_c1.png",
"xPos":"1",
"yPos":"2"
},
{
"id":"2",
"move": "static",
"image": "actor01.png",
"json" : "/actor/chest01.json",
"animateSprites": "none",
"xPos":"1",
"yPos":"3"
}
]
}
これの意味は、eventキーに配列を設定しています。
その配列の中にkey:value型のオブジェクトを仕込むことで、
配列0個目のidや状態や位置を簡単に取得できるようにしてしまおうというものです。
数値も文字列にしてしまっていますが、呼び出すときに数値にキャストすればOKです。
これらの情報を引っ張ってループで回してSpriteを作成していきます。
こうやってjsonで情報をまとめておくとループで生成できるのでコード記述量も短くなって最高ですね。
イベント用スプライト配列を作ってjsonファイルを元に生成する
プレイヤーは1つでしたが、イベントは複数あるので配列にします。
以下のクラス変数を作りましょう。
// イベント用変数
private EventSprites:Array<PIXI.extras.AnimatedSprite> = [];
イベントはNPCもまったく動かないオブジェクトも含むのでとりあえずAnimate機能を持ったSpriteにします。
次に、先ほど用意したイベントデータをまとめたjsonファイルを元にスプライトを作成しましょう。
ここから前回の作ったコードも結構変化するので1つずつ見ていきましょう。
座標を取得するコードを関数化しよう
前回の記事ではプレイヤーの座標を1次元配列の添え字に変換するコードを書きましたが
イベントも現在地を知りたいので、getGridCollision関数から一部を抜き出して「getEventPosition」として独立しましょう。
// 引数1つ目の座標を調べる
private getEventPosition(event:PIXI.extras.AnimatedSprite) {
// プレイヤーの現在の座標を取得(GUI位置補正もする)
let pos: { [key: string]: number; } = {
x:( event.x - (SYSTEM.guiChipSize*2)) / SYSTEM.chipSize ,
y:( event.y - (SYSTEM.guiChipSize*2)) / SYSTEM.chipSize
}
// 一次元配列の現在値を取得
return pos.x + ( pos.y * SYSTEM.gridX );
}
関数から独立化にともない、getGridCollisionを下記のように修正しましょう。
// 移動する先のグリッドの情報を取得する
private getGridCollision(direction:number) {
// 判定マップ参照
let gridNo:number | undefined = this.getDirectionGridNumber(direction,SYSTEM.mapLayer.collision);
switch (gridNo) {
case 9:
return false
// 条件に何も満たなければ移動可能と判定する
default :
return true;
}
}
だいぶすっきりしましたね。
ここで1つ新しい関数、「getDirectionGridNumber」を使っているので紹介します。
これは現在向いている方向と、リテラルでマップレイヤー番号を指定していたのをシステムモジュールで変数化しましたものを渡しています。
上を向いている時の衝突判定レイヤーを見るといった処理になります。
何をしているのかというと衝突判定レイヤーを元に現在向ている方向の1歩先の数値を取得しています。
その処理はもともとgetGridCollisionの中で作っていましたがこれも独立化させました。
理由は後に動き回るNPCイベントを作るときにも使えるからです。
そして取得した値を元にswitchで分岐しているのは特に変わらずですね。
9の時は移動できないのでそれ以外は移動できるといった具合です。
一応getDirectionGridNumberの関数の中身も書いておきます。
// 向いている先のグリッドの数値を取得する
private getDirectionGridNumber(direction:number,mapLayer:number) {
// プレイヤーが居る1次元配列の数値を取得
let nowPos:number = this.getEventPosition(this.PlayerSprite);
// 向いている方向の先にあるグリッドの数値を返す
switch (direction) {
case 0:
return this.MapData[mapLayer][nowPos - 1];
case 1:
return this.MapData[mapLayer][nowPos + 1];
case 2:
return this.MapData[mapLayer][nowPos - SYSTEM.gridX];
case 3:
return this.MapData[mapLayer][nowPos + SYSTEM.gridX];
}
}
イベント初期化関数を作成しよう
イベントはjsonで定義しておいて、それをループで回してイベントスプライト配列を作ります。
jsonファイルの読み込みはどうすればいいのか少し悩みました。
普通にjsonファイルを読み込むとどうやら内部側でキャッシュを保持する仕組みらしくよろしくないと検索で見かけたので、
それならnode.jsを使っているのでfsというファイル読み込みモジュールを使えばいいんじゃないかなと思いました。
しかしよく考えたらpixi.jsでローダーを使ってjsonファイルを読み込んでいることをすっかりと忘れていて
そこに同じように組み込んだら問題なく読み込めました。
読み込んだファイルはリソースローダーでファイル名をしていすれば呼び出すことが出来ます。
設定ファイルを呼び出せば配列化しているのでmapメソッドで回しながら1つずつ情報を取れるので
Sprite化するのがすごく楽になりますね。
早速作ってみましょう。
// イベントオブジェクト配列生成
private EventsSpriteInit() {
// イベントデータの取得
let eventData:Array<{ [key: string]: string; }> = this.PIXI_resources[__dirname + "/src/json/event/map/map001.json"].data.event;
// 取得したイベントデータをmapで回し、返り値をAnimatedSprite配列にする
this.EventSprites = eventData.map( (item,key) => {
// テクスチャの空配列を準備
let textureEvents:Array<PIXI.Texture> = [];
// 取得したイベントデータの保持しているアニメーション文字列をカンマ区切りで配列化
let textureStrings:Array<string> = item.animateSprites.split(",");
// 取得したイベントデータの保持しているjson文字列でテクスチャを取得
let textureGraphics:PIXI.Spritesheet | undefined = this.PIXI_resources[__dirname + "/src/json/spritesheets" + item.json].spritesheet;
// 取得したアニメーション文字列からアニメーションテクスチャ配列を作る
textureStrings.map( (name) => {
// json文字列からテクスチャを取得出来ているかチェック
if(textureGraphics !== undefined) {
// アニメーション文字列がnoneであれば
if(name === "none") {
// 空のテクスチャを入れる(透明扱い)
textureEvents.push(PIXI.Texture.EMPTY);
} else {
// そうでなければテクスチャ名を指定して配列に組み込む
textureEvents.push(textureGraphics.textures[name]);
}
}
});
// テクスチャ配列を元にアニメーションスプライトを作る
let sprite:PIXI.extras.AnimatedSprite = new PIXI.extras.AnimatedSprite(textureEvents);
// 取得したイベントデータをもとに座標を決める
sprite.setTransform(SYSTEM.guiChipSize*2 + ( Number(item.xPos) * SYSTEM.chipSize) ,SYSTEM.guiChipSize*2 + ( Number(item.yPos) * SYSTEM.chipSize));
// 取得したデータからアニメーションをするかどうかをチェック
if (item.move !== "static") {
sprite.animationSpeed = 0.05;
sprite.play();
}
// マップ配列のイベントレイヤーを自身のidで数値を塗り替える
this.eventMapLayerReWrite(sprite,Number(item.id));
// 形成したスプライトを返す
return sprite;
});
}
説明はほぼコメントで打ちましたので軽く説明します。
まずはマップイベントをまとめているmap001.jsonはローダーで読み込んでいるのでリソースローダーを使って情報を取得できます。
変数の型はkeyvalueの配列型にします。
配列型なのでmapメソッドが使えますね。
後やっていることはマップイベント1つ目から順に保持している情報を元にこねくり回して、
AnimatedSpriteクラスを最後に返すことで最終的に配列を作っています。
mapメソッドやfilterメソッドは本来は1つずつ回して新しい配列を作る命令なのですが
mapメソッドはreturnをしなければforeachのように使えます。
filterも使えますが、filterは合致したモノだけを配列化したいときに使うので基本配列を1つずつ回すのはmapメソッドでいいと思います。
話を戻しますと、スプライトを返す前にアニメーションを実行するかどうかの条件を設けました。
これもイベントデータファイルで動かすかどうかを決められます。
今回は動きを見たいので閉じた宝箱がパカパカ開くアニメーションしているイベントと透明のイベントを準備しました。
イベントマップの書き換え
イベント用スプライトを作成する際に一番重要になるのがこのイベントマップ書き換えです。
これをしないとスプライトが画面に表示されただけでイベントマップには何もない状態のままになります。
直接イベントマップに記述してしまってもいいのですが毎回やるのは手間過ぎるので、
生成時に自信の持つ座標でイベントマップを自分の持つidに書き換えてしまうという処理を行います。
イベントマップ書き換えを行っているのがこの「eventMapLayerReWrite」メソッドです。
中身を見てみましょう。
// イベント配列数値書き換え
private eventMapLayerReWrite(event:PIXI.extras.AnimatedSprite,eventNo:number) {
let pos = this.getEventPosition(event);
this.MapData[SYSTEM.mapLayer.event][pos] = eventNo;
}
座標を取得するコードを関数化したのが早速役に立ちました!
マップ配列はすべて同じ配列の数を持っているので現在地情報さえあれば、
レイヤーを指定すれば配列の同じ個所を変更できますね。
今回はイベントは移動しないので初期化の時点で位置情報を書き換えてしまえばいいのですが、
動き回るNPCだった場合毎回情報を書き換えなければいけません。
これがまたタイミングが難しいので次回辺りにしっかりと勉強しましょう。
ステージにイベントスプライトを追加する
イベントもスプライトなのでステージに追加する必要があります。
今までスプライト配列をステージに追加した要領で追加しましょう。
スプライト配列用のステージ追加メソッドは既に何回か使っているので説明不要ですね。
一応コードは載せておきます。
// 初回ステージ追加
private addStageInit() {
this.addStageArray(this.MapChips);
this.addStageArray(this.GuiFrame);
this.addStageArrayGraphic(this.GuiFrameBacks);
this.addStageArray(this.EventSprites);
this.App.stage.addChild(this.PlayerSprite);
}
何気なくステージに追加していますが、この順番は結構大切です。
一応後から明示的に変更はできますが基本的に追加した順に表示されます。
なので最後にマップを描写してしまうとプレイヤーがマップの下に来てしまって見えなくなりますね。
その為イベントとプレイヤーは最後に表示する方が良いでしょう。
もう1つ言うと掛布団や草むらのプレイヤーに覆いかぶさっているタイプの表現をしたい場合は、
マップレイヤーをもう1つ増やしてそこに描写するといいかもしれません。
例えばFFシリーズの隠し通路の実装をするときに役に立ちますね。
壁を別レイヤーで表示して一見通れなさそうですが、衝突判定は通れる状態になっているという感じです。
他にもかくしつうろのアビリティを付けているとフラグがONになって、
スプライトを半透明に切り替えたりすれば面白いですね。
それではイベントがちゃんと表示できているか試してみましょう。
プレイヤーと一緒に宝箱がパカパカしていたら完成です。
宝箱の下に透明のイベントがありますがテクスチャは透明の状態なので見えませんが、
次回に調べたらちゃんと存在していることを証明するためなので一旦は透明にしておいてください。
実際宝箱がこんなにパカパカしていたら確実にミミックですよね。
これはこれで面白いんじゃないでしょうか?
例えば、調べる前は閉じた状態のまま止まっていて調べたらパカパカ動かしてから
テキストで「宝箱はミミックだった!」ってだせばよりゲームらしくなりますね。
ドラクエみたいにパカパカせずともメッセージだけでもいいと思いますが
折角PCで容量をいっぱい使えるのですから動かした方がプレイヤーも楽しくなります。
動かすか動かさないかは、個人の自由ですけどね。
──次回は表示したイベントの方向に向いている状態でzキー(小文字なのが重要)を押したら
そのイベントの中身を参照してイベントフラグをONにしてメッセージを出すということをしたいと思います。
まずはconsole.logで実際に動くかどうかを試します。
テキストを画面に表示するのは次々回になりますが、console.logを置き換えるだけなので
先にイベント処理を作ってしまいましょう。
余談ですが調べるタイプのイベントは結構簡単ですが、接触タイプのイベントになるとちょっと大変です。
例えば動き回る敵とプレイヤーが同時に同じ位置に行ってしまったりするとグラフィックがめり込みます。
毒の沼とか足元にあるタイプだといいのですがNPCとかだとめり込むのはおかしいですよね。
そうした場合、移動可能だと判断した場合移動先のマップ配列を先行して書き換えておかないといけません。
なので今のプレイヤーの移動処理は少し問題があります。
そこは次々次回ぐらいにやりたいと思います。
最後に恒例のソースコードを掲載しておきます。
それでは。
import * as PIXI from 'pixi.js'
import { SYSTEM } from '../../config/system';
import Controller_Base from './Controller/Controller_Base';
class PIXI_MainProcess {
/**
* ----------------------------------------------------
* エイリアス作成
* ----------------------------------------------------
* */
// pixi.jsアプリケーション
private PIXI_Application = PIXI.Application
// ローダー
private PIXI_loader = PIXI.loader
// リソースローダー
private PIXI_resources= PIXI.loader.resources
/**
* ----------------------------------------------------
* クラス変数
* ----------------------------------------------------
* */
// pixiアプリケーション生成
private App:PIXI.Application = this.pixiApplicationCreate(SYSTEM.width,SYSTEM.height);
//--------------------------------------------
// プレイヤー用変数
private PlayerSprite:PIXI.extras.AnimatedSprite = new PIXI.extras.AnimatedSprite([PIXI.Texture.EMPTY]);
private moving:boolean = false;
private direction:number = 3;
// イベント用変数
private EventSprites:Array<PIXI.extras.AnimatedSprite> = [];
// マップ用変数
private MapChips:Array<PIXI.Sprite> = [];
// Window背景変数
private GuiFrameBacks:Array<PIXI.Graphics> = [];
// GUI用変数
private GuiFrame:Array<PIXI.Sprite> = [];
// マップデータ
private MapData:number[][] = [
// マップチップレイヤー
[
2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,2,
2,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,2,
2,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,2,
2,1,2,1,1,1,1,1,1,2,2,2,2,1,2,2,1,2,2,
2,1,2,2,1,2,2,1,2,2,1,1,1,1,1,2,1,2,2,
2,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,2,2,
2,2,2,2,2,2,1,1,2,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,2,1,1,2,2,2,2,2,2,1,2,
2,1,2,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,2,
2,1,2,1,1,1,1,1,1,2,1,2,1,1,1,1,1,1,2,
2,1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
],
// 衝突判定レイヤー
[
9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
9,0,9,0,0,0,0,0,0,9,0,0,0,0,9,0,0,0,9,
9,0,9,0,0,0,0,0,0,9,0,0,0,0,9,0,0,0,9,
9,0,9,0,0,0,0,0,0,9,0,0,0,0,9,0,0,0,9,
9,0,9,0,0,0,0,0,0,9,9,9,9,0,9,9,0,9,9,
9,0,9,9,0,9,9,0,9,9,0,0,0,0,0,9,0,9,9,
9,0,0,0,0,9,0,0,9,0,0,0,0,0,0,9,0,9,9,
9,9,9,9,9,9,0,0,9,0,0,0,0,0,0,0,0,0,9,
9,0,0,0,0,0,0,0,9,0,0,9,9,9,9,9,9,0,9,
9,0,9,9,9,9,9,9,9,9,0,9,0,0,0,0,0,0,9,
9,0,9,0,0,0,0,0,0,9,0,9,0,0,0,0,0,0,9,
9,0,9,9,9,9,9,9,9,9,0,9,9,9,9,9,9,9,9,
9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,
9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
],
// イベントレイヤー
[
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
],
];
//--------------------------------------------
// コンストラクタ
constructor() {
// 初期化プロセス
this.initializePIXI();
}
// 初期化プロセス
private initializePIXI() {
// ピクセル倍率変更
this.pixelScaleUP();
// htmlのbodyにPIXIAPPを追加
document.body.appendChild(this.App.view);
// Sprite初期化
this.loadingResrouces();
}
// PIXIアプリケーションオブジェクト作成
private pixiApplicationCreate(screenWidth:number,screenHeight:number) {
return new this.PIXI_Application({
width:screenWidth,
height:screenHeight,
antialias: false,
transparent: false,
resolution: 2
}
);
}
// ピクセル倍率を2倍に変更
private pixelScaleUP() {
this.App.renderer.roundPixels = true;
this.App.renderer.resize(SYSTEM.width, SYSTEM.height);
this.App.stage.scale.set(1,1);
this.App.stage.interactive = true;
PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
}
// セットアップ関数
private setup() {
this.App.ticker.add(delta => this.gameloop(delta));
}
// メインループ
private gameloop(delta:number) {
this.PlayerController(delta);
}
// PIXIローダー
private loadingResrouces() {
// エイリアスで設定したローダーの呼び出し
this.PIXI_loader
// addメソッドの引数にロードしたい画像ファイルパスを渡す
.add(__dirname + "/src/assets/images/games/mapchip/testmap.png")
.add(__dirname + "/src/assets/images/games/system/system.png")
.add(__dirname + "/src/assets/images/games/actor/actor01.png")
.add(__dirname + "/src/json/spritesheets/mapchip/testmap.json")
.add(__dirname + "/src/json/spritesheets/system/system.json")
.add(__dirname + "/src/json/spritesheets/actor/actor01.json")
.add(__dirname + "/src/json/spritesheets/actor/chest01.json")
.add(__dirname + "/src/json/event/map/map001.json")
// チェーンメソッドでプログレス機能を稼働
.on("progress", (loader, resource)=>{
console.log("progress: " + loader.progress + "%");
})
// ロード終了後に実行する関数を指定する
.load(() => {
this.spriteSetup();
// ループ関数起動
this.setup();
})
}
// テクスチャネームを返す
private getTextureName(str:string, i:number) {
return `${str}${i}.png`;
}
// セットアップ関数
private spriteSetup() {
// テクスチャを準備する
let mapchips:PIXI.Spritesheet | undefined = this.PIXI_resources[__dirname + "/src/json/spritesheets/mapchip/testmap.json"].spritesheet;
let systemGraphics:PIXI.Spritesheet | undefined = this.PIXI_resources[__dirname + "/src/json/spritesheets/system/system.json"].spritesheet;
let actorGraphics:PIXI.Spritesheet | undefined = this.PIXI_resources[__dirname + "/src/json/spritesheets/actor/actor01.json"].spritesheet;
if(mapchips !== undefined && systemGraphics !== undefined && actorGraphics !== undefined ) {
this.MapChipsInit(mapchips);
this.GuiFrameInit(systemGraphics);
this.ActorSpriteInit(actorGraphics);
}
// イベントオブジェクト生成
this.EventsSpriteInit();
// console.log(this.PIXI_resources[__dirname + "/src/json/event/map/map001.json"].data.event[0]);
// オブジェクトをステージに追加
this.addStageInit();
}
// イベント配列数値書き換え
private eventMapLayerReWrite(event:PIXI.extras.AnimatedSprite,eventNo:number) {
let pos = this.getEventPosition(event);
this.MapData[SYSTEM.mapLayer.event][pos] = eventNo;
}
// イベントオブジェクト配列生成
private EventsSpriteInit() {
// イベントデータの取得
let eventData:Array<{ [key: string]: string; }> = this.PIXI_resources[__dirname + "/src/json/event/map/map001.json"].data.event;
// 取得したイベントデータをmapで回し、返り値をAnimatedSprite配列にする
this.EventSprites = eventData.map( (item,key) => {
// テクスチャの空配列を準備
let textureEvents:Array<PIXI.Texture> = [];
// 取得したイベントデータの保持しているアニメーション文字列をカンマ区切りで配列化
let textureStrings:Array<string> = item.animateSprites.split(",");
// 取得したイベントデータの保持しているjson文字列でテクスチャを取得
let textureGraphics:PIXI.Spritesheet | undefined = this.PIXI_resources[__dirname + "/src/json/spritesheets" + item.json].spritesheet;
// 取得したアニメーション文字列からアニメーションテクスチャ配列を作る
textureStrings.map( (name) => {
// json文字列からテクスチャを取得出来ているかチェック
if(textureGraphics !== undefined) {
// アニメーション文字列がnoneであれば
if(name === "none") {
// 空のテクスチャを入れる(透明扱い)
textureEvents.push(PIXI.Texture.EMPTY);
} else {
// そうでなければテクスチャ名を指定して配列に組み込む
textureEvents.push(textureGraphics.textures[name]);
}
}
});
// テクスチャ配列を元にアニメーションスプライトを作る
let sprite:PIXI.extras.AnimatedSprite = new PIXI.extras.AnimatedSprite(textureEvents);
// 取得したイベントデータをもとに座標を決める
sprite.setTransform(SYSTEM.guiChipSize*2 + ( Number(item.xPos) * SYSTEM.chipSize) ,SYSTEM.guiChipSize*2 + ( Number(item.yPos) * SYSTEM.chipSize));
// 取得したデータからアニメーションをするかどうかをチェック
if (item.move !== "static") {
sprite.animationSpeed = 0.05;
sprite.play();
}
// マップ配列のイベントレイヤーを自身のidで数値を塗り替える
this.eventMapLayerReWrite(sprite,Number(item.id));
// 形成したスプライトを返す
return sprite;
});
}
// プレイヤー初期化
private ActorSpriteInit(textureData:PIXI.Spritesheet) {
let textureActors:Array<PIXI.Texture> = [];
textureActors.push(textureData.textures["actor01_c0.png"]);
textureActors.push(textureData.textures["actor01_c1.png"]);
this.PlayerSprite = new PIXI.extras.AnimatedSprite(textureActors);
this.PlayerSprite.setTransform(SYSTEM.guiChipSize*2 + SYSTEM.chipSize,SYSTEM.guiChipSize*2 + SYSTEM.chipSize);
this.PlayerSprite.animationSpeed = 0.05;
this.PlayerSprite.play();
}
// プレイヤー操作関数
private PlayerController(delta:number) {
let moveFlag:boolean = false;
if(!this.moving) {
if(Controller_Base.keyAry.indexOf("ArrowLeft") >= 0) {
this.direction = 0;
moveFlag = this.getGridCollision(this.direction);
}
else if(Controller_Base.keyAry.indexOf("ArrowRight") >= 0) {
this.direction = 1;
moveFlag = this.getGridCollision(this.direction);
}
else if(Controller_Base.keyAry.indexOf("ArrowUp") >= 0) {
this.direction = 2;
moveFlag = this.getGridCollision(this.direction);
}
else if(Controller_Base.keyAry.indexOf("ArrowDown") >= 0) {
this.direction = 3;
moveFlag = this.getGridCollision(this.direction);
}
} else {
switch(this.direction) {
case 0:
this.PlayerSprite.x += -1;
break;
case 1:
this.PlayerSprite.x += 1;
break;
case 2:
this.PlayerSprite.y += -1;
break;
case 3:
this.PlayerSprite.y += 1;
break;
}
// GUI分ズレたグリッド位置補正をする
let playerX = this.PlayerSprite.x - (SYSTEM.guiChipSize*2);
let playerY = this.PlayerSprite.y - (SYSTEM.guiChipSize*2);
if(playerX%SYSTEM.chipSize === 0 && playerY%SYSTEM.chipSize === 0 ) {
this.moving = false;
}
}
// ぶつかってなかったら移動フラグをONにする
if (moveFlag) { this.moving = true; }
}
// 引数1つ目の座標を調べる
private getEventPosition(event:PIXI.extras.AnimatedSprite) {
// プレイヤーの現在の座標を取得(GUI位置補正もする)
let pos: { [key: string]: number; } = {
x:( event.x - (SYSTEM.guiChipSize*2)) / SYSTEM.chipSize ,
y:( event.y - (SYSTEM.guiChipSize*2)) / SYSTEM.chipSize
}
// 一次元配列の現在値を取得
return pos.x + ( pos.y * SYSTEM.gridX );
}
// 向いている先のグリッドの数値を取得する
private getDirectionGridNumber(direction:number,mapLayer:number) {
// プレイヤーが居る1次元配列の数値を取得
let nowPos:number = this.getEventPosition(this.PlayerSprite);
// 向いている方向の先にあるグリッドの数値を返す
switch (direction) {
case 0:
return this.MapData[mapLayer][nowPos - 1];
case 1:
return this.MapData[mapLayer][nowPos + 1];
case 2:
return this.MapData[mapLayer][nowPos - SYSTEM.gridX];
case 3:
return this.MapData[mapLayer][nowPos + SYSTEM.gridX];
}
}
// 移動する先のグリッドの情報を取得する
private getGridCollision(direction:number) {
// 判定マップ参照
let gridNo:number | undefined = this.getDirectionGridNumber(direction,SYSTEM.mapLayer.collision);
switch (gridNo) {
case 9:
return false
// 条件に何も満たなければ移動可能と判定する
default :
return true;
}
}
// マップチップ初期化
private MapChipsInit(textureData:PIXI.Spritesheet) {
// y座標折り返しカウント
let yCount:number = 0;
// 画面サイズ分のマップチップ配列生成
for (let i=0; i<this.MapData[0].length; i++ ) {
// 長いのでテクスチャ名のエイリアスを作っておく
let textureName = this.getTextureName(textureData.data.meta.name,this.MapData[0][i]);
// i番目の配列にnewする
this.MapChips[i] = new PIXI.Sprite(textureData.textures[textureName]);
// i番目のx座標設定
this.MapChips[i].x = ((i%SYSTEM.gridX) * SYSTEM.chipSize) + SYSTEM.fixMapPosition;
// X座標が端っこまで行ったら改行する
if(i !== 0 && i%SYSTEM.gridX === 0) {
// y座標折り返しカウントをインクリメント
yCount++;
}
// i番目のy座標設定
this.MapChips[i].y = yCount * SYSTEM.chipSize + SYSTEM.fixMapPosition;
// i番目の横幅設定
this.MapChips[i].width = SYSTEM.chipSize;
// i番目の縦幅設定
this.MapChips[i].height = SYSTEM.chipSize;
}
}
// GUIフレーム用意関数
private GuiFrameInit(textureData:PIXI.Spritesheet) {
// iを8で割った余りによって生成するものを変化させる
for (let i=0; i<SYSTEM.guiParams.length; i++) {
if(i%8 < 4) {
switch(i%8) {
case 0:
// フレーム左上
this.GuiFrame[i] = new PIXI.Sprite(textureData.textures["windowLeftTop.png"]);
break;
case 1:
// フレーム左下
this.GuiFrame[i] = new PIXI.Sprite(textureData.textures["windowLeftBottom.png"]);
break;
case 2:
// フレーム右上
this.GuiFrame[i] = new PIXI.Sprite(textureData.textures["windowRightTop.png"]);
break;
case 3:
// フレーム右下
this.GuiFrame[i] = new PIXI.Sprite(textureData.textures["windowRightBottom.png"]);
break;
}
} else {
switch(i%8) {
case 4:
// フレーム上部繰り返し用
this.GuiFrame[i] = new PIXI.extras.TilingSprite(textureData.textures["windowRepeatFrameTop.png"]);
break;
case 5:
// フレーム下部繰り返し用
this.GuiFrame[i] = new PIXI.extras.TilingSprite(textureData.textures["windowRepeatFrameBottom.png"]);
break;
case 6:
// フレーム左部繰り返し用
this.GuiFrame[i] = new PIXI.extras.TilingSprite(textureData.textures["windowRepeatFrameTop.png"]);
break;
case 7:
// フレーム右部繰り返し用
this.GuiFrame[i] = new PIXI.extras.TilingSprite(textureData.textures["windowRepeatFrameBottom.png"]);
break;
}
}
}
// ウィンドウ背景スプライト生成
for (let i=0; i<SYSTEM.windowBack.length; i++) {
this.GuiFrameBacks[i] = new PIXI.Graphics();
}
// ウィンドウ背景設定
this.GuiFrameBacks.map((item,key) => {
item.x = SYSTEM.windowBack[key].x;
item.y = SYSTEM.windowBack[key].y;
item.width = SYSTEM.windowBack[key].width;
item.height = SYSTEM.windowBack[key].height;
item.alpha = SYSTEM.windowBack[key].alpha;
item.beginFill(SYSTEM.windowBack[key].color);
item.drawRect(0,0,SYSTEM.windowBack[key].width,SYSTEM.windowBack[key].height);
item.endFill();
});
// 全てのGUIチップサイズを一旦設定
this.GuiFrame.map( (item,key) => {
item.x = SYSTEM.guiParams[key].x;
item.y = SYSTEM.guiParams[key].y;
item.width = SYSTEM.guiParams[key].width;
item.height = SYSTEM.guiParams[key].height;
item.rotation = SYSTEM.guiParams[key].rotation;
item.anchor.set( SYSTEM.guiParams[key].anchor,SYSTEM.guiParams[key].anchor);
});
}
// 初回ステージ追加
private addStageInit() {
this.addStageArray(this.MapChips);
this.addStageArray(this.GuiFrame);
this.addStageArrayGraphic(this.GuiFrameBacks);
this.addStageArray(this.EventSprites);
this.App.stage.addChild(this.PlayerSprite);
}
// 配列ステージ追加メソッド
private addStageArray(ary:Array<PIXI.Sprite>) {
ary.map( (item) => {
this.App.stage.addChild(item);
});
}
// 配列ステージ追加メソッド
private addStageArrayGraphic(ary:Array<PIXI.Graphics>) {
ary.map( (item) => {
this.App.stage.addChild(item);
});
}
}
export default PIXI_MainProcess;
const Controller:Controller_Base = new Controller_Base;
const GameFrame:PIXI_MainProcess = new PIXI_MainProcess;