ツールを作ってみよう

本ページの内容

本ページは、2018年10月25日~2018年12月14日(結果発表は2019年01月26日)までかけて行われる(行われた)、ニコニコ自作ゲームフェス新人賞 「実験放送ゲーム部門」に合わせて制作された特集記事の第2回目です。

はじめに

ドワンゴの、主に実験放送のコンテンツ制作に携わっているエンジニアのツゲハラと申します。

実験放送でニコニコ新市場を通して利用できるコンテンツ群(以後ニコニコ新市場対応コンテンツ)が、2018年10月25日より自分達で作れるようになりました。

皆様がスムーズに制作に挑める助力になればと、制作ガイドとなりそうな記事を公開させていただいております。この記事は、この一連の記事の第2回になります。今回から、記事の対象がプログラミング経験者向きとなります。

ニコニコ新市場対応コンテンツについてのご不明点などがあれば、お気軽にお問合せください。

準備

第1回を参考に、Akashicの環境を用意しましょう。

今回は改造ではないので、新規にプロジェクトを作るところから実施します。

参考となるAkashic本家の記事はこちらです。

まずは、第1回同様、CUIツールを起動します。

適当な作業場所のフォルダにcdコマンドで移ってください。仮にc:\akashicというところに移るものとします。以下のようなコマンドになります。

c:
cd \akashic

ここに、今回のゲーム用のフォルダを用意したいと思います。以下のコマンドを実行してください。

mkdir tool
cd tool

mkdirtoolというフォルダを作り、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でツールを作成します。この解像度比率にしておけば、映像とぴったり合ったサイズのコンテンツを作成できます。

widthheightに表示されている(320)fps(30)はデフォルト値を意味しているので、fpsはそのままEnterを押してもらう事で30fpsになります。

この状態で一度akashic-sandboxを実行してみましょう。

akashic-sandbox

http://localhost:3000 にアクセスすると、赤い四角が延々と右に行くだけのゲーム?が実行出来ていると思います。

赤い四角

この時、今回作成したフォルダをエクスプローラで眺めてみると、各種ファイルが自動生成されています。以下のようなファイル群になります。

ファイル、フォルダ名 開設 備考
game.json ゲームの各種情報を記述しているファイルです。widthheightfps等の情報もこちらに記載されています。
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で書く必要があり、letconst等を利用して動かなくなる問題を防ぐ事ができる

TypeScript版もJavaScript版も、node.jsの経験のある方であればpackage.jsonにスクリプトも記述してあり、npm startnpm 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;

これで、画像が一枚登場して何も動かないだけのツール?になりました。

この節で登場したエンティティの詳しい説明は、公式のリファレンスを参照してください。

入力を扱ってみる

さて、固定の画像を表示して「ウォーターマーク表示ツール」等と名乗ってもよいのですが、さすがに寂しいので、入力くらい扱えるようにしたいですね。この節では、「タッチした場所にハートマークが表示される」という機能を実現したいと思います。

最初に、「タッチした」というイベントをとる必要があります。

イベントを取る方法はエンティティのイベントを取る方法と、シーン全体でイベントを取る方法の2種類があるのですが、今回は「どこにタッチしてもその場所にハートが出てくる」という形にしたいので、シーン全体のイベントを取る方法を採用します。

タッチしたイベントをとるには、トリガーと呼ばれる機構を利用します。「そのシーンでタッチが発生した」というイベントを取るため、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のエンティティを削除する方法はremovedestroyの二つがあるのですが、removeだけだとイベントハンドラ等は解除されないので、destroyで存在ごと消滅させています。

50フレームを超えていたら、opacityというプロパティを操作して、透明度を操作しています。opacity01.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;

以下は完成体を実行している様子です。放送者が画面中央にいる事を想像しながら見ると、ツールっぽくなっているのではないでしょうか。

最終的なハートツール

完成と投稿

最後にアツマールに投稿しておきたいと思います。以下の文書を参考にします。

「ニコニコ新市場対応コンテンツ作成ガイド」に従い、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アツマールへの投稿はこちらからできます。是非皆さんも挑戦してみてください。

今後の手引き

今回は、簡単なツールの作成を行いましたが、こういったツールであればあるほど演出面をこだわりたいという話が出てくると思います。

以下に、演出強化のためによく使いそうな文書やライブラリについて、リンク集を置いておきます。今後の参考ということで、必要に応じてご参照ください。

長い記事でしたが、ここまで読んでくださりありがとうございました。

本記事では導入部のみですが、本記事をとっかかりに、皆さんも是非様々な生放送向けツールの制作に挑戦してみてください。

次回は、いよいよゲームの作成になります。また次回もよろしくお願いします。


本記事でご紹介したソースコードは、すべて以下のURLで公開されています。素材、ソースコードいずれも二次利用が可能ですので、ご活用ください。