ツールを作ってみよう
この文書は 2018 年当時のものです。
最新のニコ生ゲーム作成については https://akashic-games.github.io/shin-ichiba/ をご参照ください。
本ページの内容
本ページは、2018年10月25日~2018年12月14日(結果発表は2019年01月26日)までかけて行われる(行われた)、ニコニコ自作ゲームフェス新人賞 「実験放送ゲーム部門」に合わせて制作された特集記事の第2回目です。
はじめに
ドワンゴの、主に実験放送のコンテンツ制作に携わっているエンジニアのツゲハラと申します。
実験放送でニコニコ新市場を通して利用できるコンテンツ群(以後ニコニコ新市場対応コンテンツ)が、2018年10月25日より自分達で作れるようになりました。
皆様がスムーズに制作に挑める助力になればと、制作ガイドとなりそうな記事を公開させていただいております。この記事は、この一連の記事の第2回になります。今回から、記事の対象がプログラミング経験者向きとなります。
- 2018年10月25日: 泥棒バスターを改造してみよう
- 2018年11月01日: ツールを作ってみよう(この記事です)
- 2018年11月08日: ゲームを作ってみよう
ニコニコ新市場対応コンテンツについてのご不明点などがあれば、お気軽にお問合せください。
- 公式Twitterカウント: @akashic_talk
準備
第1回を参考に、Akashicの環境を用意しましょう。
今回は改造ではないので、新規にプロジェクトを作るところから実施します。
参考となるAkashic本家の記事はこちらです。
まずは、第1回同様、CUIツールを起動します。
適当な作業場所のフォルダにcd
コマンドで移ってください。仮にc:\akashic
というところに移るものとします。以下のようなコマンドになります。
c:
cd \akashic
ここに、今回のゲーム用のフォルダを用意したいと思います。以下のコマンドを実行してください。
mkdir tool
cd tool
mkdir
でtool
というフォルダを作り、cd
で作業場所フォルダを作成した新しいフォルダにしたので、このフォルダでAkashicのセットアップしてみたいと思います。
以下のコマンドを実行してください(実行には、第1回でインストールした@akashic/akashic-cli
というツールが必要です。まだの人は npm i -g @akashic/akashic-cli
などでインストールしてから実行してください)。
akashic init
このコマンドを実行すると、対話型のインターフェースで色々聞かれると思いますが、それぞれ以下のように入力してEnterを押してください。
項目 | 入力値 | 備考 |
---|---|---|
width:(320) | 640 | 横幅 |
height:(320) | 360 | 縦幅 |
fps:(30) | (入力せずそのまま) | フレームレート |
width
は横幅、height
は縦幅で、今回は放送で利用するツールを作りたいので、ニコニコ生放送の一般的な解像度である16:9でツールを作成します。この解像度比率にしておけば、映像とぴったり合ったサイズのコンテンツを作成できます。
width
やheight
に表示されている(320)
やfps
の(30)
はデフォルト値を意味しているので、fps
はそのままEnterを押してもらう事で30fpsになります。
この状態で一度akashic-sandbox
を実行してみましょう。
akashic-sandbox
http://localhost:3000 にアクセスすると、赤い四角が延々と右に行くだけのゲーム?が実行出来ていると思います。
この時、今回作成したフォルダをエクスプローラで眺めてみると、各種ファイルが自動生成されています。以下のようなファイル群になります。
ファイル、フォルダ名 | 開設 | 備考 |
---|---|---|
game.json | ゲームの各種情報を記述しているファイルです。width 、height 、fps 等の情報もこちらに記載されています。 |
|
package.json | この記事では利用しません。 | |
README.md | この記事では利用しません。 | |
audio | この記事では利用しません。 | 音声ファイルを格納するためのフォルダです。 |
image | 画像ファイルを格納するためのフォルダです。 | |
script | スクリプトを格納するためのフォルダです。 | |
script\main.js | 赤い四角を横に動かすためのコードが入っているスクリプトです。 | |
text | この記事では利用しません。 | テキストファイルを格納するためのフォルダです。 |
※.gitkeep
、.eslint.json
というファイルも作成されていますが、環境によっては表示されないこともあるため割愛しています。
なお、TypeScriptで書きたいという方は、以下のコマンドを利用してセットアップする事もできます。
akashic init -t typescript
npm i
本記事では、JavaScriptのまま記述を続けますが、以下の理由でTypeScriptの採用も検討の余地があると思います。
- Visual Studio Code + TypeScriptで書くと、Akashic Engineの型を推測しながら記述できる
- AkashicはECMAScript 5th editionで書く必要があり、
let
やconst
等を利用して動かなくなる問題を防ぐ事ができる
TypeScript版もJavaScript版も、node.jsの経験のある方であればpackage.json
にスクリプトも記述してあり、npm start
やnpm run lint
などのコマンドに対応しているのも確認できると思います。機会があればTypeScript、node.js、npm
周りを活用していくと制作の幅が広がるので、試してみてください。
画像を扱ってみる
さて、赤い四角ではさすがに味気ないので、画像を登場させましょう。
この記事ではこちらのハートを用いてみたいと思いますが、皆さんもよかったらお手元で「64 x 64の背景が透明なPNG」を適当な内容でご用意ください。
画像を扱う際のAkashicの公式チュートリアルはこちらになりますが、本記事でも制作に必要な要点に絞って記載を続けていきます。
まず、このファイルをimage
フォルダに配置します。c:\akashic\tool\image\heart.png
というファイルがある状態にした上で、CUIツールをcdなどでc:\akashic\tool
に移動させた後、以下のコマンドを実行してください。
akashic scan asset
以下のような表示が出れば成功です。
C:\akashic\tool>akashic scan asset
INFO: Added/updated the declaration for heart (image/heart.png)
INFO: Done!
この時にgame.json
の中身を覗いてみると、assets
というものの中にheart
というリソースが追加されている様子がなんとなく見てとれます。
game.json
はテキストエディタ等で開く事ができます。テキストエディタをお持ちでない方は、Visual Studio Code辺りをインストールしてから開く事をお勧めします。
{
"width": 640,
"height": 360,
"fps": 30,
"main": "./script/main.js",
"assets": {
"main": {
"type": "script",
"path": "script/main.js",
"global": true
},
"heart": {
"type": "image",
"width": 64,
"height": 64,
"path": "image/heart.png"
}
},
"environment": {
"sandbox-runtime": "2"
}
}
これで画像の利用準備が整いました!いよいよAkashicの対応コードを書いてみたいと思います。
script\main.js
を手元のテキストエディタで開いてください。main.js
は現状以下の内容になっています。
function main(param) {
var scene = new g.Scene({game: g.game});
scene.loaded.add(function() {
// 以下にゲームのロジックを記述します。
var rect = new g.FilledRect({
scene: scene,
cssColor: "#ff0000",
width: 32,
height: 32
});
rect.update.add(function () {
// 以下のコードは毎フレーム実行されます。
rect.x++;
if (rect.x > g.game.width) rect.x = 0;
rect.modified();
});
scene.append(rect);
});
g.game.pushScene(scene);
}
module.exports = main;
ここまではプログラミング未経験の方でも読める内容でしたが、いよいよソースコードも登場しましたので、ここから経験者でないと難しい内容になってきます。具体的には、以下のような説明を省略し、Akashic固有の部分のみ記述します。
- 変数、関数、配列、クラス
new
、++
等の演算子- JavaScriptの基本構文
Akashicは、ゲームの中には複数のシーンがあり、シーンの中に複数のエンティティがある、という基本構造をとっています。
エンティティについては公式のチュートリアルからの以下を説明として抜粋します。
画像や文字列などシーン上で描画されるオブジェクトを Akashic Engine ではエンティティと呼びます。
エンティティを描画するには、まずシーンが必要です。
先ほどのコードでは以下のようにシーンを作成しています。
var scene = new g.Scene({game: g.game});
// ~ 中略 ~
g.game.pushScene(scene);
scene
変数にg.Scene
というエンティティのインスタンスを格納し、最後にゲームに対してscene
変数を追加しています。
このシーンに、先ほど追加したheart
という画像を追加してあげましょう。
var scene = new g.Scene({game: g.game, assetIds: ["heart"]});
g.Scene
初期化時にassetIds
というパラメータを"heart"
という文字列が含まれている配列で渡します。
このheart
という文字列が先ほどの画像に紐付くという定義が、game.json
に記述されていますので、これで画像を登場させる準備が整いました。
これから、このハート画像を登場させるコードをサンプルのコードにある以下のコードの下に記述していきます。
scene.append(rect);
Sprite
というエンティティを利用するコードを追加します。Sprite
は画像を扱う際にもっとも基本となるエンティティです。(一番上の行は、ここから追加するという事を意味しているだけですので、追加しないでください)
scene.append(rect);
var sprite = new g.Sprite({
scene: scene,
src: scene.assets["heart"]
});
scene.append(sprite);
コードを書き換えたら、またakashic-sandbox
を実行して確認してみましょう。以下のような表示になるはずです。
無事ハートが登場しました!・・しかしちょっと後ろの赤い四角が邪魔ですね。
FilledRect
というエンティティを操作するサンプルコードが入っているためなので、消してしまいましょう。最終的には以下のコードになります。
function main(param) {
var scene = new g.Scene({game: g.game, assetIds: ["heart"]});
scene.loaded.add(function() {
var sprite = new g.Sprite({
scene: scene,
src: scene.assets["heart"]
});
scene.append(sprite);
});
g.game.pushScene(scene);
}
module.exports = main;
これで、画像が一枚登場して何も動かないだけのツール?になりました。
この節で登場したエンティティの詳しい説明は、公式のリファレンスを参照してください。
FilledRect
: https://akashic-games.github.io/reference/akashic-engine-v2/classes/_lib_main_d_.g.filledrect.htmlSprite
: https://akashic-games.github.io/reference/akashic-engine-v2/classes/_lib_main_d_.g.sprite.html
入力を扱ってみる
さて、固定の画像を表示して「ウォーターマーク表示ツール」等と名乗ってもよいのですが、さすがに寂しいので、入力くらい扱えるようにしたいですね。この節では、「タッチした場所にハートマークが表示される」という機能を実現したいと思います。
最初に、「タッチした」というイベントをとる必要があります。
イベントを取る方法はエンティティのイベントを取る方法と、シーン全体でイベントを取る方法の2種類があるのですが、今回は「どこにタッチしてもその場所にハートが出てくる」という形にしたいので、シーン全体のイベントを取る方法を採用します。
タッチしたイベントをとるには、トリガーと呼ばれる機構を利用します。「そのシーンでタッチが発生した」というイベントを取るため、pointDownCapture
というトリガーを利用します。詳細は公式のリファレンスやチュートリアルを参照してください。
- チュートリアル: https://akashic-games.github.io/tutorial/v2/4-click.html
- リファレンス: https://akashic-games.github.io/reference/akashic-engine-v2/classes/_lib_main_d_.g.scene.html#pointdowncapture
要素が多いので、個別に説明する前にコード全体を見てから、この後で解説するようにしたいと思います。まずはコード全体を見てみましょう。
function main(param) {
var scene = new g.Scene({game: g.game, assetIds: ["heart"]});
scene.loaded.add(function() {
var sprite = new g.Sprite({
scene: scene,
src: scene.assets["heart"]
});
scene.append(sprite);
// -- 「入力を扱ってみる」で追加
// pointDownCaptureトリガーに関数を登録
scene.pointDownCapture.add(function(e) {
// heart変数にheart画像のSpriteを追加
var heart = new g.Sprite({
scene: scene,
src: scene.assets["heart"]
});
// heartの位置をタッチされた場所にする。Akashicの基準座標は左上なので、タッチされた中心になるように微調整する
heart.moveTo(
e.point.x - heart.width / 2,
e.point.y - heart.height / 2
);
// 作成したheartスプライトをシーンに追加
scene.append(heart);
});
// ここまで「入力を扱ってみる」 --
});
g.game.pushScene(scene);
}
module.exports = main;
-- 「入力を扱ってみる」で追加
と書かれているところが該当コードで、コード内にコメントも書いておきました。
要点を解説していきます。
pointDownCapture
トリガーのadd
メソッドでイベントハンドラを登録し、
scene.pointDownCapture.add(function(e) {
タッチされたら、Sprite
を生成します。
var heart = new g.Sprite({
scene: scene,
src: scene.assets["heart"]
});
moveTo
メソッドで位置を調整し、この際イベントハンドラに渡ってくる変数e
の中に入っているpoint
変数から座標を計算しています。
heart.moveTo(
e.point.x - heart.width / 2,
e.point.y - heart.height / 2
);
コメントにも記載があるように、Akashicの座標は左上を起点としているため、point
変数の値をそのまま利用していますと、思ったより右下に配置されてしまいます。
タッチされたポイントの中心に置くように、Sprite
のサイズを基に位置を微調整し、最後に調整済のSprite
をシーンに追加しています。
実行し、タッチをするとSpriteが増えてこのような絵面のツール?が実行できているかと思います。
一定時間で削除する
これで完成、としてもいいのですが、さすがにこのまま放送で使うと画面がハートで埋まってしまいます。
放送画面をデコるツール、という事にしてもいいのですが、もう少し踏み込んで一定時間で削除する機能を入れたいと思います。
先にコードを見てみましょう。
function main(param) {
var scene = new g.Scene({game: g.game, assetIds: ["heart"]});
scene.loaded.add(function() {
var sprite = new g.Sprite({
scene: scene,
src: scene.assets["heart"]
});
scene.append(sprite);
// -- 「入力を扱ってみる」で追加
// pointDownCaptureトリガーに関数を登録
scene.pointDownCapture.add(function(e) {
// heart変数にheart画像のSpriteを追加
var heart = new g.Sprite({
scene: scene,
src: scene.assets["heart"]
});
// heartの位置をタッチされた場所にする。Akashicの基準座標は左上なので、タッチされた中心になるように微調整する
heart.moveTo(
e.point.x - heart.width / 2,
e.point.y - heart.height / 2
);
// -- 「一定時間で削除する」で追加
// heartのtagという変数にcounterを格納
heart.tag = {
counter: 0
};
// 毎フレーム実行されるイベントであるupdateにイベントを登録
heart.update.add(function(e) {
// 毎フレームカウンタを追加
heart.tag.counter++;
if (heart.tag.counter > 100) {
// カウンタが100を超えていたら削除する
heart.destroy();
} else if (heart.tag.counter > 50) {
// カウンタが50を超えていたら半透明にしていく
heart.opacity = (100 - heart.tag.counter) / 50;
// このエンティティが変更されたという通知
heart.modified();
}
});
// ここまで「一定時間で削除する」で追加 --
// 作成したheartスプライトをシーンに追加
scene.append(heart);
});
// ここまで「入力を扱ってみる」 --
});
g.game.pushScene(scene);
}
module.exports = main;
-- 「一定時間で削除する」で追加
以降が追加分です。
エンティティごとに削除したいので、まずはエンティティ単位の変数にカウンタの値を入れています。
heart.tag = {
counter: 0
};
エンティティ単位の変数を確保する方法は色々とあるのですが、TypeScriptなどを利用して型を厳密に定義した場合でも運用しやすいよう、今回はtag
というところにオブジェクトを入れ、そのオブジェクトの変数にcounter
という値を持たせる形にしました。
tag
についてはリファレンスにも記載があるので、必要に応じてそちらも参照してください。
次に、毎フレーム実行されるイベントを利用する為、update
というトリガーにイベントハンドラを登録しています。
heart.update.add(function(e) {
このupdate
トリガーは、赤い四角を右に動かしていたサンプルコードも使っていたトリガーです。このトリガーに登録されたイベントハンドラは、毎フレーム実行されます。
3秒ちょっとで消える事にしました。100フレームを30fpsで考えると3.3秒なので、100フレーム辺りで制御しようと思います。
また、最初の50フレームは変化なくそのままで、50フレームからだんだん半透明にする事にしました。それをコードで表したのがこちらです。
if (heart.tag.counter > 100) {
// カウンタが100を超えていたら削除する
heart.destroy();
} else if (heart.tag.counter > 50) {
// カウンタが50を超えていたら半透明にしていく
heart.opacity = (100 - heart.tag.counter) / 50;
// このエンティティが変更されたという通知
heart.modified();
}
100フレームを超えていたらdestroy
を呼び出しています。Akashicのエンティティを削除する方法はremove
とdestroy
の二つがあるのですが、remove
だけだとイベントハンドラ等は解除されないので、destroy
で存在ごと消滅させています。
50フレームを超えていたら、opacity
というプロパティを操作して、透明度を操作しています。opacity
は0
~1.0
で透明度を指定するので、式でだんだん透明度を上げていく形にしました。
最後のmodified
はAkashicで「このエンティティが変更された」という宣言をするためのメソッドです。このメソッドを呼び出さないと、プロパティの変更が正しく画面に反映されないため、opacity
の変更を描画するために呼び出しています。
最後、左上に最初からいるハートが邪魔ですね。この辺りの仕上げはみなさんのお手元で実際にやっていただくのがいいと思いますが、私はこうしました。皆さんも工夫してみてください。
function main(param) {
var scene = new g.Scene({game: g.game, assetIds: ["heart"]});
scene.loaded.add(function() {
function generateHeart(x, y) {
// heart変数にheart画像のSpriteを追加
var heart = new g.Sprite({
scene: scene,
src: scene.assets["heart"]
});
// heartの位置をタッチされた場所にする。Akashicの基準座標は左上なので、タッチされた中心になるように微調整する
heart.moveTo(
x,
y
);
// heartのtagという変数にcounterを格納
heart.tag = {
counter: 0
};
// 毎フレーム実行されるイベントであるupdateにイベントを登録
heart.update.add(function(e) {
// 毎フレームカウンタを追加
heart.tag.counter++;
if (heart.tag.counter > 100) {
// カウンタが100を超えていたら削除する
heart.destroy();
} else if (heart.tag.counter > 50) {
// カウンタが50を超えていたら半透明にしていく
heart.opacity = (100 - heart.tag.counter) / 50;
// このエンティティが変更されたという通知
heart.modified();
}
});
// 作成したheartスプライトをシーンに追加
scene.append(heart);
}
// pointDownCaptureトリガーに関数を登録
scene.pointDownCapture.add(function(e) {
// タッチされたらハートを中心点に配置する
generateHeart(
e.point.x - scene.assets["heart"].width / 2,
e.point.y - scene.assets["heart"].height / 2
);
});
// 最初に中心にハートを出現させる
generateHeart(
g.game.width / 2 - scene.assets["heart"].width / 2,
g.game.height / 2 - scene.assets["heart"].height / 2
);
});
g.game.pushScene(scene);
}
module.exports = main;
以下は完成体を実行している様子です。放送者が画面中央にいる事を想像しながら見ると、ツールっぽくなっているのではないでしょうか。
完成と投稿
最後にアツマールに投稿しておきたいと思います。以下の文書を参考にします。
- ニコニコ新市場対応コンテンツ作成ガイド: https://akashic-games.github.io/guide/ranking.html
- ニコニコ新市場対応コンテンツの投稿方法; https://akashic-games.github.io/guide/export-atsumaru.html
「ニコニコ新市場対応コンテンツ作成ガイド」に従い、game.json
に以下の記述を加えます。これはツールなのでランキングには対応しませんので、"single"
にのみ対応します。
"niconico": {
"supportedModes": ["single"]
}
最終的に、game.jsonは以下のようになっていると思います。
{
"width": 640,
"height": 360,
"fps": 30,
"main": "./script/main.js",
"assets": {
"main": {
"type": "script",
"path": "script/main.js",
"global": true
},
"heart": {
"type": "image",
"width": 64,
"height": 64,
"path": "image/heart.png"
}
},
"environment": {
"sandbox-runtime": "2",
"niconico": {
"supportedModes": ["single"]
}
}
}
続いて、「ニコニコ新市場対応コンテンツの投稿方法」に従い、以下のコマンドをCUIツールから実行します。
akashic export html --output atsumaru.zip --atsumaru
あとはRPGアツマールのページに行き、「ゲーム投稿」を押して、ZIPファイルをアップロードします。
ゲーム名、アイコン、ジャンル、紹介文が最低限必要です。アイコンは320x320
がおすすめです。この辺りを適当に入力して、公開すると、アツマールへの投稿が完了します。
アツマール投稿後、自身のページに表示されるゲーム一覧の「その他」メニューから「ニコニコ新市場に登録申請」を選ぶ事で、登録申請をすることができます。
RPGアツマールへの投稿はこちらからできます。是非皆さんも挑戦してみてください。
今後の手引き
今回は、簡単なツールの作成を行いましたが、こういったツールであればあるほど演出面をこだわりたいという話が出てくると思います。
以下に、演出強化のためによく使いそうな文書やライブラリについて、リンク集を置いておきます。今後の参考ということで、必要に応じてご参照ください。
- アニメーションをさせたい場合
- アニメーションに関する公式文書
- FrameSprite(いわゆるスプライトシートを使いたい場合)
- Akashic Timeline(イージング等をかけて動かす場合)
- Akashic Animation(より複雑なアニメーション)
- 音をつけたい場合
- 文字列を扱いたい場合
長い記事でしたが、ここまで読んでくださりありがとうございました。
本記事では導入部のみですが、本記事をとっかかりに、皆さんも是非様々な生放送向けツールの制作に挑戦してみてください。
次回は、いよいよゲームの作成になります。また次回もよろしくお願いします。
本記事でご紹介したソースコードは、すべて以下のURLで公開されています。素材、ソースコードいずれも二次利用が可能ですので、ご活用ください。