Akashic Engine プログラミングでの注意点2020/04/04

「今日からはじめるニコ生ゲーム制作」
なんて放送もあって、新たに Akashic Engineを使ってみようという方も出てきそうなこの頃。

しかし放送でやっていたように画像を差し替えるくらいなら良いのですが、プログラムを組んでゲームを作るとなるといろいろ注意しなければいけない点も出てきます。
一応 Akashic Engineで何本かゲームを作ってきた身として、そのあたりをいくつか挙げてみたいと思います。

なお、サンプルコードは TypeScriptで書かれています。
JavaScriptの方は型宣言等、読み飛ばしちゃってください。


1.マルチタッチ

PCで開発してマウスでテストプレイしていると気づきにくいのですが、Akashic Engineはマルチタッチに対応しています。画面の2点以上を同時にタッチすると、それぞれのポイントのイベントが発生します。

pointDownでタッチを見るだけなら、気にする必要はありません。
pointMovepointUpで移動したとき・離したときのイベントを使うときは、引数のメンバpointerIdでそれぞれ区別・対応するようにしましょう。
一緒にすると、スマホだけの裏技なんてものができてしまうかもしれません。


2.シーンオブジェクトのポイントイベント

エンティティ毎のポイントイベントとは別に、シーンオブジェクトのpointDownCaptureでシーン全体のポイントイベントを取得することができます。
タッチした座標は引数のメンバpointで得られるのですが、touchableなエンティティの上をクリックした場合、その値が画面上の座標ではなくエンティティ上での相対座標になってしまいます。

例えば (100, 100) - (200, 200)の座標・大きさでtouchableなエンティティがあったときに画面上の座標(120, 120)をクリックすると、取得される値は(20, 20)になってしまいます。

画面上の座標を得るには、メンバtargetを見てエンティティの行列で変換する必要があります。
scene.pointDownCapture.add((ev: g.PointDownEvent) =>
{
    let pos: g.CommonOffset;
    if ( ev.target ) {
        pos = ev.target.getMatrix().multiplyPoint(ev.point);
    }
    else {
        pos = ev.point;
    }



3.アンカーポイント

エンティティはアンカーポイントによって表示する座標の原点や拡大・縮小・回転の位置を指定することができます。
原点を指定するようなものなのですが、appendで追加した子エンティティの相対座標に親のアンカーポイントは影響しません。

例えば次のように、
const   parent: g.FilledRect = new g.FilledRect(
{
    scene: scene,
    cssColor: "#ff0000",
    x: 100,
    y: 100,
    width:  64,
    height: 64,
    anchorX: 0.5,
    anchorY: 0.5,
});
scene.append(parent);

const   child: g.FilledRect = new g.FilledRect(
{
    scene: scene,
    cssColor: "#0000ff",
    x: 100,
    y: 100,
    width:  32,
    height: 32,
    anchorX: 0.5,
    anchorY: 0.5,
});
scene.append(child);

中心をアンカーポイントとした2つのエンティティを、それぞれ同じ座標(100, 100)に表示すると

Akashic Engine アンカーポイントの例

2つは同じ座標を中心として表示されます。当然ながら。

const   child = new g.FilledRect(
{
    scene: scene,
    cssColor: "#0000ff",
    x: 0,
    y: 0,
    width:  32,
    height: 32,
    anchorX: 0.5,
    anchorY: 0.5,
});
parent.append(child);

そこで座標が同じなんだから相対位置は(0, 0)だろうと、青い方の位置を x: 0, y: 0にして 赤い方に appendしてみると

Akashic Engine アンカーポイントの例

左上にずれます。
アンカーポイントに依らず、相対座標は親エンティティの左上を原点としているようです。

中心を合わせるには、
    x: 64/2 + 0,
    y: 64/2 + 0,
親エンティティのサイズを考慮する必要があります。

    x: parent.width*parent.anchorX + 0,
    y: parent.height*parent.anchorY + 0,
汎用的に書くと、こんな感じです。



というわけで、自分が Akashic Engineを使う上で実際にぶつかった点を3つ程挙げてみました。

あとはラベルやフォントなんかで気になる点もあるのですが、よくわからないままその場しのぎで対応してたりするので、ここでは割愛致します。


Akashic Engine 知られざる機能2019/11/28

アツマールAPIの話をしたので、Akashic Engineの方も少し。

Akashic Engineの知られざる機能
……というより、知っておくと便利な機能
……というか、このくらい知らせておいた方が良いんじゃないの?という機能を2つほど挙げてみます。


1.グローバルアセット
いわゆる常駐データです。
通常アセットはシーン(g.Scene)毎に読み替えられますが、グローバルアセットに指定されたアセットはずっと常駐しています。
そのため、シーンに依らない共通データをシーンが変わるたびに読み直す必要がありません。

グローバルアセットの指定は game.jsonでおこなわれ、アセット定義の"global"を trueにすることでグローバルアセットとして扱われます。
グローバルアセットはアプリ開始時に自動的に読み込まれ、アクセスするときは
 g.game.assets["data"]のように g.Gameから参照されます。

詳しくは、game.json の仕様で。


2.スプライトへの描画

みんなでいしかり ステージ選択

「みんなで いしかり」のステージ選択画面は、各ステージの配置画像がボタンとなって並んでいます。つまりステージを直接画面に描画するのではなく、一旦ボタン用のスプライトに描画してから、そのボタンを画面に描画しているわけです。

このスプライトの作成には、エンティティ(g.E)からスプライト(g.Sprite)を作成する関数である

g.Util.createSpriteFromE(scene: g.Scene, e: g.E, camera?: g.Camera): g.Sprite

を使用しています。
元になるのはエンティティということでスプライトでもラベルでも良く、また他のエンティティを子として appendすることもできます。

注意点としては、使用したエンティティは自分で destroy()する必要があるということです。
……なんて断言はできませんが、たぶんそういうことになってるんじゃないかと思います。少なくとも destroyして悪いことはないはず……

関数の詳しい使い方は、リファレンスで。



というわけで、おそらくあまり知られていない Akashic Engineの機能を挙げてみました。
……が、使いどころが限られる「スプライトへの描画」はともかく、「グローバルアセット」なんかはもっと周知させておくべきなんじゃないでしょうか?
データの読み込み頻度は、アプリの快適さに関わってくるものですし……

前述の通り game.json の仕様に記載はありますが、なかなかそこまでは読まんわー。



「みんなで いしかり」でのアツマールAPIの使い方2019/11/25

反射パズル みんなで いしかりでは、ユーザーがオリジナルステージを作成・公開することができます。
ステージデータの管理などアツマールAPIの機能を使っているのですが、その具体的な方法を以下に挙げてみます。


1.ステージデータの取得
公開されたステージでのデータは、制作したユーザーの共有セーブに保存されています。
それらの取得のために必要な共有セーブを持つユーザーのリストは、ランキング機能であるスコアボードを利用しています。

こういう場合ユーザー情報APIで最近プレイしたユーザーの情報を取得して使うのが一般的なようですが、このゲームでのステージエディットは少しハードルが高いこともありステージを公開しているユーザーが押し出されてしまう可能性があります。
ステージを公開した時だけプレイヤー間通信の有効化を行うという手もありますが、一旦公開したステージを非公開にして共有データが無くなる可能性も考慮して、スコアボードを使うことにしました。

ステージ作成側は、
・ステージ公開時
・起動時
にスコアボードに日にちをスコアとして登録します。起動時にも登録しているのは、新しいステージを作成しなくても埋もれてしまわないようにするためです。

そして作成ステージをプレイする側は、scroreboards.getRecordsでユーザーのリストを取得します。
日にち(1970/1/1からの日数)をスコアとしているので新しいほど上位にきますが、リストにさえ入っていれば順位は処理に関係ありません。


2.作成したステージをTwitterで紹介
APIの使い方自体は、特に変わったことはありません。
screenshot.setTweetMessageで、クエリ文字列を設定して、screenshot.displayModalでユーザーがツイート、
そのリンクで起動した側はquery[key]でステージ情報を得る
という処理になっています。

クエリ文字列には、ステージのデータと作成したユーザーの情報が丸々入っています。
ステージ情報をデータベース等で一括管理していれば通し番号でもやり取りすれば良いのですが、データ自体が個々のユーザーの共有セーブにあり変更されたり無くなったりすることもあるので、パラメータの文字列だけでステージを再現できるようにしました。


3.ステージ作者情報の表示
そういうAPIがあったので、せっかくだから使ってみました。
ステージを作ってくださったユーザー様の宣伝に、少しでもなってくれればというところでもあります。


4.できなかったこと
ユーザーシグナルあたりを使って、オリジナルステージを遊んだ人の数やクリアした人の数を制作者に伝えるようにしてみたかったのですが、取りこぼしがあったり制作者が定期的に起動する必要があったりするのであきらめました。値が不正確では、あまり意味もありませんので。

何かしらのフィードバックがあった方がステージの作り甲斐もあるのですが、そこはTwitterでのステージ紹介を使ってなんとか……



「みんなで いしかり」 エディットモードの詳しい説明2019/11/23

RPGアツマールで発表した


オリジナルステージ作成のための、エディットモードの詳しい説明です。


はじめにタイトル画面で「ステージ作成」を選ぶと、ステージ選択画面に移行します。

自作ステージ選択

最初はまだ作成されたステージが無いので、光線発射台だけのブランクステージが並んでいます。先頭のステージだけが選択可能なのでタップすると、テストプレイ画面になります。
ここでは作成されたステージのクリア確認や公開の設定等を行うのですが、まずはそのステージを作るために右上の「エディット」ボタンを押してステージエディット画面に移ってください。


エディット画面

右側に4色それぞれの石が並んでいます。これらの石をドラッグ&ドロップで左側のステージ上に配置して、オリジナルステージを作っていきます。
一旦ステージ上に置いた石も同様に動かすことができます。また、ステージ外に置くと消去されます。

ステージ上・右側どちらの石もタップで、反射板の向きそして反射板無しを切り替えます。

光線発射台も石と同じようにドラッグ&ドロップで動かすことができます。ただし、画面外に置いても消去されることはありません。
また、タップすると光線発射の向きが変わります。


オリジナルステージが完成したら、「プレイ」ボタンでテストプレイ画面に戻ってください。
ここでは通常のゲームプレイ同様に、ステージを楽しむことができます。

みんなでいしかり テストプレイ画面

作成されたステージを解いて、クリア可能であることを確認してください。
一度クリアが確認されれば、「メニュー」から「ステージを公開する」や「Twitterで紹介」で、ステージを他のユーザーに公開することができます。

ステージを修正したい場合は「エディット」ボタンで再びエディットモードに移ってください。
石の配置を変えたりするとまたクリア確認が必要になりますが、以下のような解き方が変わらないような変更の場合はクリア状態が維持されます。
・反射板の向きを変える
・別の色の石に替える
・反射板の無い石をある石に替える
・反射板の無い石を取り除く


・補足
エディットモード時の「メニュー」→「ステージチェック」で、現状のステージがクリア可能かどうかチェックすることができます。あくまでクリア可能かどうかのチェックで、解答を教えてくれるわけではありません。

この機能を利用して、まず適当に石を配置してクリア可能であることをチェック。そしてテストプレイで挑戦してみる、というように手軽に新しいステージで楽しむことができます。

自分で解くことができたら少し体裁を整えて、ぜひ他のユーザーに公開してみてください。


くるんくる~ぱ マニアックス2019/06/03

RPGアツマールで発表中のゲーム
の詳しい内部処理やデータ値をまとめてみました。

くるんくる~ぱ

ありがたいことにまだプレイ数は増えているようで、しかもハイスコア上位は製作者の自分も到底手の届かないハイレベルなものになっています。
この記事の情報で攻略法が変わるようなことは無いと思いますが、何かしらの参考になればと思います。

1.各動作のフレーム数
このゲームのフレームレートは30fpsなので、1フレーム=1/30秒となっています。

まず、回転軸をタッチすると4フレームかけて右に120°回転します。
回転中にタッチすると、そこから4フレームで次の回転を行います。

回転の終わった6フレーム後に、三角に並んだ球の消去判定を行います。
少し間を置くのは、操作上左回転が無いので右回転2回の確認を待っているため、そして適当な回転で適当に消えるのをなるべく避けるためです。

消去判定を受けた球は、12フレームかけて消去されます。
このとき消去開始から8フレームまでは、他の三角形の判定に使うことができます。つまり、消去中の三角形に他の球をくっつけて新たにできた三角形も同様に消去されます。

球が消えるとその上の球は、4フレームで球一つ分の速度で落下します。
落下終了の8フレーム後、次の消去判定が行われます。

2.COMBO数とCOMBOゲージ
球が消去されると三角形1つにつきCOMBO数が1上昇、COMBO数×5点がスコアに加算されます。COMBO数の上限は99となっています。(上限に達することなんて滅多に無いと思っていたのに……)
COMBOゲージは三角形1つで、まず4/5倍された上で18を加算されます。
これにより値が大きくなるほど実質的な上昇値は小さくなり、90という上限を超えることはありません。上限は持ちたいが、そこでぶった切るのではなく滑らかに上昇(微分が連続)させたくて、こんな処理になっています。

球を回転させたとき、
・COMBOゲージが0より大きい場合、COMBOゲージから8減算します(下限は0)
・COMBOゲージが0の場合、COMBO数を1つ減らします(下限は0)
ただし、同じ場所を連続で回転した場合は最初だけ上記の処理を行います。これもまた、左回転が無いための処置です。

また毎フレーム、球が稼働(落下・消去)していないときはCOMBOゲージから1減算します(下限は0)。

3.その他
球が消去され上から新たな球が落ちてくるとき、そのすぐ下にある球と同じ色の球は選ばれないようになっています。落ちてきた球で勝手に消えるのをなるべく避けるためです。

また、ゲーム開始から40秒後(残り20秒)盤上に一番少ない色の球は、それ以降出現しなくなります。最後の追い込みのために、色を減らして消しやすくなるようにしています。