こちらはスーパー正男Re同盟 年越しカレンダー 2019-2020の7日目(12/30)の記事です。
正男のuserJSCallback
を入れるオブジェクトがあると思うんですが、あれにextensions
なるものを足すことでMasaoJSSを経由するよりもさらに正男の内部を制御できます。例えばかの有名なMasaoSpaceなる正男投稿サイトにもあるリプレイ機能はこれを利用して作られているような気がしています。
今回はこれを利用することで、現在のMasaoJSSでは難しいJS拡張をいくつか可能にしていこうと思います。
実装方式は色々ありそうですが、今回は一応お行儀よく(?)MasaoJSSを拡張する形で行います。例えばこのような感じで:
new CanvasMasao.Game({
// params
}, null, {
extensions: [(()=>{
function MyMasaoJSS(mc, apc) {
apc.apply(this, arguments);
// 追加したいメソッド
// 例:ファイヤーボールを装備しているか
this.isEquipFire = function () { return mc.mp.j_fire_f };
// 以下,個人的に使うだけのためMasaoJSSのメソッドほど安全性を考慮しないことに注意してください
// 例えばMasaoJSSではまずmc.mpが本当に存在するかを確かめることが慣例です
}
return {
inject: function(mc, options) {
const _ui = mc.userInit,
_us = mc.userSub;
mc.userInit = function() {
_ui.apply(mc);
const apc = this.masaoJSSAppletEmulator;
mc.masaoJSSAppletEmulator = new MyMasaoJSS(this, apc.constructor);
};
mc.userSub = function(g, image) {
_us.call(mc, g, image);
}
},
};
})()],
userJSCallback: // ...
})
追加したいメソッドの例でmc
を参照していることがわかるかと思います。これはCanvasMasaoのソースコードではMasaoConstruction.js
にあたります(GitHub上のURL)。mc.mp
はMainProgram.js
であり、これが正男や敵や敵の弾など、ゲーム性にかかわる部分を管理している部分です(おそらく)。
なお本筋とは関係ありませんがメソッド追加以外の部分が何をしているのか雑に説明しておきます。GlobalFunction.js
にあるように、正男を起動する際に作られたMasaoConstruction
は、配列extensions
に入った各オブジェクトのinject
メソッドに渡されます。これによりinject
内部からMasaoConstruction
、およびそれが持つMainProgram
やMasaoJSS
オブジェクト(masaoJSSAppletEmulator
)を触ることができ、好きなメソッドをMasaoJSS
に生やせるという寸法です。
まずはちょっとした例から:サンプルステージ。ソースをここに貼ると長くなるため、サンプルステージから見てください。現在のMasaoJSS
ではgetMyObjectCondition
という正男の状態を取得するメソッドは実装されていますが、状態を指定するメソッドはありません。これを実装し、正男が死亡したとき、すなわち正男の状態が200以上250以下の範囲内になったときに通常の正男の状態(=100)を代入することで、正男を生き返らせることができます。
正男を表すオブジェクトはmc.mp.co_j
に格納されており、正男の状態はmc.mp.co_j.c
です(どのフィールドがどれであるかはCharacterObject.js
を参照するとわかる……かもしれません)。したがってここに引数として与えられる数値を代入するメソッドを実装すれば実現可能です:
this.setMyObjectCondition = function(c) { if(mc.mp) mc.mp.co_j.c = c };
これは今回の主題からは少し外れた雑学的な話です。setMyPress(3)
を使えばよいと思われるかもしれませんが、実はこれはあまり正確ではありません。実際のゲーム内でボスを踏んだ際はmc.mp.co_j.c1
が-4
となっているのですが、setMyPress(3)
はそうしていないために踏んだ後のタメの時間が異なるからです。よって、
this.setMyPressBoss = function() { this.setMyPress(3); mc.mp.co_j.c1 = -4 };
を使うと実際の挙動に近づきます(サンプルステージ)(左がsetMyPress(3)
、中央がsetMyPressBoss()
)。もっともこの件については標準のMasaoJSS
に追加するPull Requestを出してもよいのかもしれませんが面倒なので誰かやってください。
こちらのサンプルステージのソースを参照してください。正男の放つ弾系オブジェクト(ファイヤーボールやグレネード)について位置や速度を取得し、壁に当たる場合に速度を反転させることで反射を実現させています。これらはmc.mp.co_jm
という配列に入っており(実はこれはあまり裏を取っていないのですが)、これについて色々と情報を取得したり、反映させたりするメソッドを実装すればよいです:
this.getFireballObjectCondition = function(i) { return mc.mp.co_jm[i].c };
this.getFireballObjectPattern = function(i) { return mc.mp.co_jm[i].pt };
this.getFireballObjectX = function(i) { return mc.mp.co_jm[i].x };
this.getFireballObjectY = function(i) { return mc.mp.co_jm[i].y };
this.getFireballObjectVX = function(i) { return mc.mp.co_jm[i].vx };
this.getFireballObjectVY = function(i) { return mc.mp.co_jm[i].vy };
this.getFireballObjectLength = function() { return mc.mp.co_jm.length };
this.setFireballObjectX = function(i, v) { mc.mp.co_jm[i].x = v };
this.setFireballObjectY = function(i, v) { mc.mp.co_jm[i].y = v };
this.setFireballObjectVX = function(i, v) { mc.mp.co_jm[i].vx = v };
this.setFireballObjectVY = function(i, v) { mc.mp.co_jm[i].vy = v };
this.setFireballObjectCondition = function(i, v){ mc.mp.co_jm[i].c = v };
ちなみに似たような用例として、ファイアーボールがある位置を光らせるというスポット処理も挙げられそうです。敵弾についても同様のメソッドを実装することで、ピカチーの電撃やエアームズの爆風なども光らせることが可能です。extensionを通さない汚い実装ではあるものの、過去にこの考え方で作ったスポットを使ったステージがこちら(そこまでお薦めできません)。
このような感じで実際にCanvasまさおのソースを読む必要がありますが、より幅の広い改造ができます。Canvasまさおも開発にかかわっているみなさんのおかげで昔より格段に読みやすくなったこともありますので、一度見てみてはいかがでしょうか。また既に触れたMasaoSpaceでおなじみのうひょ氏は今回取り上げたMainProgram
以外のもの、例えばキー入力を管理するGameKey
などを操作する正男も公開されており、参考になるかと思います。
ただしCanvasまさおのソースコードが更新された際はもしかしたら使えなくなる可能性もありますので、そのあたりは注意が必要かもしれません。あと私はCanvasまさおのリポジトリについて何の権限も持っていないので何とも言えないんですが、MasaoJSS
に標準で追加してもよさそうなものがあったらPull Requestを送ってもよいのかもしれません。
すべてのサンプルステージのextensions
およびuserJSCallback
部は著作者表示やリンクなしでご自由に利用して構いません。紹介したネタもお気兼ねなくお使いください(そのために書いたので)。それ以外のもの、例えば画像や非サンプルステージのデータなどについては遠慮してください。