Skip to content

Akashic Engine 向け拡張ライブラリを作成する

INFO

本ページの内容は npm モジュール知識を前提にしています。詳細は npm の文書 などを参照してください。

Akashic Engine 向けに作成した拡張ライブラリは npmjsGitHub Packages などにより公開することもできます。

拡張ライブラリをパッケージを公開することで複数のゲームコンテンツでロジックを共有することができます。 Akashic が提供する公式の拡張ライブラリも適宜参考にしてください。

準備

拡張ライブラリを npm モジュールとして公開する場合、以下のファイルが必要になります。

  • package.json
  • メインスクリプト (.js ファイル)
  • README
  • LICENCE (必要に応じて)

以下では Akashic Engine の拡張ライブラリを作成する上で重要となる、メインスクリプトに関する説明を記述しています。

実装例 1: 独自のエンティティを提供するライブラリ

g.E を拡張したクラスの作成は JavaScript のプロトタイプチェーンと継承 により実現できます。 TypeScript または EcmaScript 6 以上の実行をサポートしたブラウザでは class シンタックス により簡潔に記述できます。 以下は class シンタックスを利用した g.E の継承例です。

javascript
// 拡張ライブラリ側

class HelloE extends g.E {
  constructor(param) {
    super(param);
    // 初期化処理...
  }

  hello() {
    console.log("hello!");
  }
}

module.exports.HelloE = HelloE;

ファイル構成

上記を npm モジュールとして公開する場合、以下のような package.json を作成します。(npm init コマンドでも作成できます。)

namelicense などの各フィールドの内容は適宜変更してください。

javascript
{
  "name": "sample-library",
  "version": "1.0.0",
  "description": "Helloを言えるエンティティ",
  "author": "akashic-games",
  "license": "MIT",
  "main": "lib/index.js",
  "files": [
    "lib" // lib ディレクトリをパッケージに含ませることに注意
  ]
}

main には HelloE を exports した js ファイルを指定します。 例えば lib ディレクトリに格納した場合は lib/index.js とします。 main に指定したスクリプトファイルは忘れずに files へ記述しておく必要があります。

大まかなファイル構成は以下となります。

package.json
README
LICENSE
lib/index.js
lib/index.d.ts (TypeScript で開発された場合)

ローカルでの動作確認

npm pack コマンドを利用すると npm パッケージされる .tgz ファイルが生成できます。 このファイルを akashic install コマンドでインストールすることでローカルで動作確認ができます。

sh
$ npm pack # sample-library-1.0.0.tgz が出力される
$ cd PATH/TO/CONTENT # コンテンツへ移動
$ akashic install PATH/TO/LIBRARY/sample-library-1.0.0-tgz # 拡張ライブラリをローカルでインストール

akashic install 後、コンテンツ側で以下のように require() が利用できるかを確認します。

javascript
var HelloE = require("sample-library").HelloE;

var e = new HelloE({
  scene: g.game.scene()
});
e.hello(); // hello!

拡張ライブラリの公開

ローカルでの動作確認ができれば npm パッケージとして公開できます。 詳細については割愛しますが、 npm adduser で任意のパッケージホスティングサービスへ認証後 npm publish を実行します。

sh
$ npm adduser # npmjs などにログイン
$ npm publish # パッケージを公開

パッケージが公開されたかどうかについては npm view コマンドで確認できます。

sh
$ npm view sample-library

公開された拡張ライブラリの利用

公開された拡張ライブラリは、ローカルでの動作確認と同様に akashic install コマンドで利用できます。

sh
$ akashic install sample-library

実装例 2: 画像アセットを含んだ拡張ライブラリ

DANGER

2024年10月現在、不具合のためニコ生ゲームでは require.resolve() は利用できません。

回避策として下記の記述が利用できますが、これは不完全であり間接的にインストールされるライブラリではうまくいかない可能性があることに注意してください。

javascript
scene.asset.getImage("/node_modules/<ライブラリ名>/assets/sample.png");

package.json と同一ディレクトリに akashic-lib.json という名前のファイルを設置しておくことで、akashic install コマンド時に画像アセットや音声アセットを自動的にスキャンすることができます。 akashic-lib.json の仕様については こちら を参照してください。

画像アセット・音声アセットを含むライブラリ側は、アセットのパスを解決するために require.resolve() を利用します。 通常のゲーム開発であれば scene.asset.getImage("/assets/sample.png") のように game.json からのパスを指定してアセットを読み込みますが、 拡張ライブラリ側では game.json の場所が不定となるため、この関数を使ってアセットのパスを解決します。

INFO

拡張ライブラリ内で scene.asset.getImage("/assets/sample.png") を実行すると通常は game.json の場所をルートとして /assets/sample.png を参照してしまいます。しかしアセットの実ファイルは /node_modules/sample-library/assets/sample.png のように node_modules 以下に格納されているため、resolve.require() を使わないと常にアセットが見つからない状態となってしまいます。

javascript
// 独自のアセットを利用したのスプライトを生成する例
module.exports.createSprite = function (scene) {
  var src = scene.asset.getImage(require.resolve("../image/frame.png")); // "node_modules/sample-library/lib/index.js" で実行された場合は "node_modules/sample-library/image/frame.png"
  return new g.Sprite({
    scene: scene,
    src: src,
    width: src.width,
    height: src.height
  });
};

拡張ライブラリの利用側は、 g.Scene の生成時に assetPaths で読み込むアセットのパスを指定します。

javascript
// main.js
var createSprite = require("sample-library").createSprite;

module.exports = function () {
  var scene = new g.Scene({
    // ...
    assetPaths: [
      "/node_modules/sample-library/image/frame.png" // ライブラリの画像アセットを指定 (通常はハードコーディングすべきではありません!)
    ]
  });

  scene.onLoad.addOnce(function () {
    var sprite = createSprite(scene);
    // ...
  });
  // ...
};

不要なハードコーディングを避けるためにも、拡張ライブラリ側で必要なアセットを利用側へ提供することを推奨します。 以下は getAssetPaths() という関数でアセットパスの配列を export する例です。

javascript
// my-dialog-lib

// 必要なアセットのパスを配列として返す
module.exports.getAssetPaths = function () {
  return [
    require.resolve("../image/frame.png"),
    require.resolve("../image/button.png")
    // ...
  ];
};

// 部分的に必要なアセットのパスを配列として返す
module.exports.getConfirmDialogAssetPaths = function () {
  return [
    require.resolve("../image/confirm_frame.png"),
    require.resolve("../image/confirm_button.png")
  ];
};

利用側は my-dialog-lib が提供する関数を呼び出すだけでアセットパスを解決できます。

javascript
var dlg = require("my-dialog-lib");

module.exports = function () {
  var scene = new g.Scene({
    // ...
    assetPaths: [
      "/assets/stage1/**/*",
      ...dlg.getAssetPaths() // my-dialog-lib の実行に必要なアセット
    ]
  });

  // ...

  var scene2 = new g.Scene({
    // ...
    assetPaths: [
      "/assets/stage1/**/*",
      ...dlg.getConfirmDialogAssetPaths() // 部分的に必要なアセット
    ]
  });
};

akashic-lib.json のスキャン

カレントディレクトリに akashic-lib.json が存在する状態で akashic scan asset を実行すると、 akashic-lib.json の assetList フィールドにアセットのスキャン結果が追記されます。 スキャンを初めて実行する場合は {} のような空の JSON を含ませた akashic-lib.json をあらかじめ作成しておいてください。

TIP

画像アセットを含むモジュールを npm publish する場合は、アセットファイルや akashic-lib.json を package.json の files フィールドに追加しておく必要があります。

注意点

拡張ライブラリ内では @akashic/akashic-enginerequire() しないでください。

Akashic における拡張ライブラリはゲームコンテンツと同様のコンテキスト・スコープで実行されるため、常に globalThis.g が参照できる状態となります。 したがって、var g = require("@akashic/akashic-engine") のように g の値を解決してしまうとコンストラクタ等価性が失われ、意図しない挙動を起こしてしまいます。

TypeScript の場合は g の型を解決する必要があります。 型解決の仕方については akashic-engine のバージョンにより異なりますのでご留意ください。 通常は akashic init --type typescript で出力される tsconfig.json の設定ファイルがそのまま利用できます。

関連情報