アニメーションさせる

このページのサンプルコードをダウンロード

# アニメーション

Akashic Engine のコンテンツは固定 FPS で動作します。 FPS の値は 1 秒間に画面を更新する回数です。エンティティの位置や大きさなどを画面更新の度に変化させると、エンティティをアニメーションさせることができます。

エンティティには位置や大きさを表すプロパティが用意されており、プログラムで読み書きできます。例えば FilledRect の場合、以下のプロパティが存在します。 cssColor 以外のプロパティは Sprite など他のエンティティにも存在します。

プロパティ名 値の種類 意味
cssColor CSS 色を表す文字列 塗りつぶす色
x 数値 エンティティのX座標
y 数値 エンティティのY座標
width 数値 エンティティの幅
height 数値 エンティティの高さ
opacity 0.0以上1.0以下の数値 不透明度。0.0で透明、1.0で不透明
scaleX 数値 横方向の拡大率
scaleY 数値 縦方向の拡大率
angle 数値 回転角度(度)。回転の軸はアンカーポイントの位置
anchorX 数値または null アンカーポイントの水平成分。 0.0でエンティティの左端、1.0で右端。(詳細)
anchorY 数値または null アンカーポイントの垂直成分。 0.0でエンティティの上端、1.0で下端。(詳細)

これらのプロパティの値を変更したら、ゲーム開発者は modified() メソッドを明示的に呼び出して、 Akashic Engine にプロパティの変更を通知する必要があります。

例えば、FilledRect オブジェクトの rect を右方向に 20、下方向に 10 移動するコードは次のようになります。

rect.x += 20;
rect.y += 10;
rect.modified();

シーンオブジェクトの onUpdate トリガーはシーンのフレームを描画するたびに呼び出されます。以下はシーンの onUpdate トリガーを利用して矩形 rect を右に移動させるコードです。

scene.onUpdate.add(() => {
  ++rect.x;
  rect.modified();
});

シーンオブジェクトの onUpdate トリガーにハンドラを登録する処理は、シーンの読み込み後に行う必要があります。プログラム全体は以下のようになります。

function main() {
  const scene = new g.Scene({ game: g.game });
  scene.onLoad.add(() => {
    const rect = createRect(scene);
    scene.append(rect);
    // scene の onUpdate を設定し、毎フレーム実行する処理を記述
    scene.onUpdate.add(() => {
      ++rect.x;
      rect.modified();
    });
  });
  g.game.pushScene(scene);
}

function createRect(scene) {
  return new g.FilledRect({
    scene: scene,
    width: 30,
    height: 30,
    cssColor: "red"
  });
}

module.exports = main;

エンティティ自体も onUpdate トリガーを備えています。以下はエンティティ rect のトリガーを利用したコードです。

rect.onUpdate.add(() => {
  ++rect.x;
  rect.modified();
});

シーンのトリガーでできることはエンティティのトリガーでも実現できますが、トリガーの寿命に違いがあります。シーンのトリガーがシーンが切り替わるまで有効なのに対して、エンティティのトリガーはエンティティが破棄されるまで有効になります。例えばシーンの途中でエンティティが破棄された場合はトリガーが無効になります。以下の例では矩形が画面の中央に到達したら destroy() メソッドでエンティティを破棄しています。

function main() {
  const scene = new g.Scene({ game: g.game });
  scene.onLoad.add(() => {
    const rect = createRect(scene);
    scene.append(rect);
    // rect の update を利用
    rect.onUpdate.add(() => {
      moveRect(rect);
    });
  });
  g.game.pushScene(scene);
}

function createRect(scene) {
  // 前の例と同じ
}

function moveRect(rect) {
  ++rect.x;
  rect.modified();
  if (rect.x > g.game.width / 2) rect.destroy();
}

module.exports = main;

# フレームアニメーション

g.FrameSprite を利用すると、パラパラ漫画のように画像を切り替えていくアニメーション(フレームアニメーション)を手軽に行うことができます。

例えば次のような画像を考えます。

元画像

この画像はアニメーションの様子が 100x100 サイズのフレームとして順番に並んでいます。

以下は、g.FrameSprite を使ってこの画像をアニメーションするプログラムです。

function main() {
  const scene = new g.Scene({
    game: g.game,
    assetIds: ["explosion"] // 上の画像は "explosion" というアセットIDであるとします
  });
  scene.onLoad.add(() => {
    const frameSprite = new g.FrameSprite({
      scene: scene,
      src: scene.asset.getImageById("explosion"),
      // エンティティのサイズ
      width: 100,
      height: 100,

      // 元画像のフレーム1つのサイズ
      srcWidth: 100,
      srcHeight: 100,

      // アニメーションに利用するフレームのインデックス配列
      // インデックスは元画像の左上から右にsrcWidthとsrcHeightの矩形を並べて数え上げ、右端に達したら一段下の左端から右下に達するまで繰り返す
      frames: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],

      // アニメーションをループする(省略した場合ループする)
      loop: true
    });
    scene.append(frameSprite);
    frameSprite.start();
  });
  g.game.pushScene(scene);
}

module.exports = main;

フレームアニメーションを表示するには FrameSprite エンティティを使います。 FrameSprite エンティティのパラメータオブジェクトのプロパティには、最初に表示するフレームを指定する frameNumber やフレーム遷移の速度を指定する interval を指定することができます。 start() メソッドを呼び出すまでアニメーションは開始されません。アニメーションを終了したい場合は、 stop() メソッドを呼び出します。

# タイマー関数

3 秒後にエンティティを破棄したいなど、指定時間後に処理を行う場合は、シーンオブジェクトの scene.setTimeout() メソッドを利用します。 scene.setTimeout() にはコールバック関数とミリ秒単位の時間を引数として渡します。例えば、シーン scene があり、3 秒後すなわち 3000 ミリ秒後に処理を行う場合、以下のように記述します。

scene.setTimeout(() => {
  // 3秒後に行う処理
}, 3000);

scene.setTimeout() ではミリ秒単位で時間を指定できますが、実際にはフレーム単位で処理が行われます。例えばゲームが 30FPS、つまり 1 秒間に 30 フレーム描画している場合、1 フレームの長さは約 33 ミリ秒なので、実際の処理時間は 33 ミリ秒単位に切り上げられます。

以下は scene.setTimeout() を使って 3 秒後に矩形 rect を破棄するコードです。

scene.setTimeout(() => {
  rect.destroy();
}, 3000);

一定時間ごとに繰り返し処理をする場合は、シーンオブジェクトの scene.setInterval() メソッドを利用します。 scene.setInterval() メソッドの利用方法は scene.setTimeout() メソッドと同じです。以下は 0.5 秒おきに矩形の色を切り替えるコードです。

let flg = false;
scene.setInterval(() => {
  rect.cssColor = flg ? "red" : "black";
  rect.modified();
  flg = !flg;
}, 500);

scene.setTimeout()scene.setInterval() に登録した関数は、それぞれ scene.clearTimeout()scene.clearInterval() 関数で登録を解除できます。引数には scene.setTimeout()scene.setInterval() が返す ID を指定します。以下のプログラムでは scene.setInterval() で登録した関数を 3 秒後に scene.clearInterval() で解除しています。

function main() {
  const scene = new g.Scene({ game: g.game });
  scene.onLoad.add(() => {
    const rect = createRect(scene);
    scene.append(rect);
    const intervalId = scene.setInterval(() => {
      rect.x += 10;
      rect.modified();
    }, 200);
    scene.setTimeout(() => {
      scene.clearInterval(intervalId);
    }, 3000);
  });
  g.game.pushScene(scene);
}

function createRect(scene) {
  return new g.FilledRect({
    scene: scene,
    width: 30,
    height: 30,
    cssColor: "red"
  });
}

module.exports = main;

scene.setTimeout() ではない、グローバルの setTimeout() や、同じくグローバルの setInterval() は使用しないよう注意してください。 これは、Aksahic Engine のコンテンツは、現実の時間とは違う速度で実行されることがあるためです (例えば、Akashic Engine のゲームは、ニコニコ生放送上で実行されることがあります。その場合、タイムシフトではシークバーに連動する形で動作します)。この時、 scene.setTimeout() は実行速度を踏まえて経過時間を処理するのですが、 グローバルの setTimeout() は指定した時間そのままの時間経過を待ってしまうため、処理の実行タイミングがずれてしまうことがあり得ます。

# 乱数

Akashic Engine は独自の乱数生成器を備えています。以下のコードは 0 以上 1 未満の数値をランダムに一つ選んで返します。

g.game.random.generate();

この関数は JavaScript の標準にある Math.random() と同じ機能ですが、 Akashic Engine のゲームでは原則 g.game.random を利用してください 。 (このことは特にマルチプレイの場合に重要です。詳細は マルチプレイの基礎 の節で後述します)

generate() は実数を返しますが、整数の乱数が必要なことも多いでしょう。0 以上 x 未満の整数は、 generate() を使って次のように生成できます。

Math.floor(g.game.random.generate() * x);

このコードでは、 generate() の戻り値を x 倍することで「0 以上 x 未満の実数」にし、 そこから Math.floor() で小数点以下を切り捨てて「0 以上 x 未満の整数」にしています (Math.floor() は JavaScript の標準にある関数です)。

以下のプログラムは矩形を格子状に並べるプログラムです。ただし矩形の色は 4 種類の色の中からランダムに選んで決めています。

const size = 25;
const margin = 15;

function main() {
  const scene = new g.Scene({ game: g.game });
  scene.onLoad.add(() => {
    let x, y, rect;
    for (y = 0; y < g.game.height; y += size + margin) {
      for (x = 0; x < g.game.width; x += size + margin) {
        rect = createRect(scene, x, y);
        scene.append(rect);
      }
    }
  });
  g.game.pushScene(scene);
}

function createRect(scene, x, y) {
  const colors = ["blue", "navy", "royalblue", "skyblue"];
  const idx = Math.floor(g.game.random.generate() * colors.length);
  return new g.FilledRect({
    scene: scene,
    x: x,
    y: y,
    width: size,
    height: size,
    cssColor: colors[idx]
  });
}

module.exports = main;