【AkashicEngine完全に理解した】環境構築からニコニコにゲームを投稿するまで
今年の10月末にニコ生の実験放送に自作ゲームを投稿できるようになった そうで、今回はそれについてまとめてみた記事です
この記事を読み終えた頃には、「AkashicEngine完全に理解した」って言えるようになります、たぶん
※本記事は グレンジ Advent Calendar 2018 22日目の記事です
- AkashicEngineとは
- 環境構築~サンプル実行まで
- 各ファイル・フォルダについて
- ゲームの構成
- エンティティ
- アセット
- 文字列の表示
- 入力イベント
- ランキングモード対応
- ニコニコ新市場に投稿
- おわりに
AkashicEngineとは
[このページ] (https://dwango.github.io/articles/akashic/) を見ればわかりますが、自分なりにまとめると
「iOSでもAndroidでもブラウザでも同様に快適に動き、多数の視聴者が同時にランキング対戦できる2Dゲームをjavascriptで簡単に作れるゲームエンジン」です
おまけにツールも作れるらしいです
実際にどんなゲームがあるか気になった方は「つりっくま」で検索してみてください
実験放送で体感9割は遊ばれてる覇権ゲーです
環境構築~サンプル実行まで
node.js のインストール
ここからインストール(推奨版でOK)
homebrew で入れる場合は下記の記事を参考
【2018年版】macのhomebrewでnodebrew入れてからnode.jsを入れるまで
AkashieEngine のインストール
ターミナル(コマンドプロンプト) に以下のコマンドを入力
npm install -g @akashic/akashic-sandbox npm install -g @akashic/akashic-cli
ここで npm ERR! Error: EACCES: permission denied, ~~
みたいなエラーが出た方は下記の記事を参考(自分がなりました)
npmでpermission deniedになった時の対処法[mac]
プロジェクトの作成
プロジェクトを作成したいフォルダに移動し、以下のコマンドを入力
akashic init
すると、以下のように解像度とFPSの初期設定を聞かれるので、それぞれ入力してEnter
prompt: width: (320) 640 prompt: height: (320) 360 prompt: fps: (30)
解像度はニコ生の解像度と同じ 16:9 が推奨されています
入力すると、現在のディレクトリ下に必要なファイルが生成されます
サンプル実行
akashic init
を行ったディレクトリで、
akashic-sandbox
と入力します
これでゲーム用のローカルサーバが起動しているので、http://localhost:3000/
にアクセスすると、サンプルが実行されます
各ファイル・フォルダについて
ファイル・フォルダ名 | 説明 |
---|---|
audio | サウンドファイルを格納するフォルダ |
image | 画像ファイルを格納するフォルダ |
script | スクリプトファイルを格納するフォルダ |
script/main.js | ゲームのエントリポイント。最初は赤い四角が右に流れるだけのサンプルコードが書かれてる |
.elintrc.json | ESLint (JSの性的検証ツール) の設定ファイル。気になる方は「 ESLint 最初の一歩 」 |
game.json | 解像度やFPS、各種アセットの情報等、さまざまな情報を記載するjson。詳細は「 game.jsonの仕様 」 |
package.json | npmのモジュールに関数する情報。気になる方は「 package.jsonの中身を理解する 」 |
README.md | マークダウンで書かれたreadme |
audio
, image
, text
フォルダ内には、必要なフォルダがgitで無視されないようにするために .gitkeep
ファイルが入ってます
詳細については次のページの最下部に記載されています(少し情報が古い)
akashic-cli利用ガイド
ゲームの構成
ゲーム内に複数のシーンが存在し、シーンにエンティティ(シーン上に描画されるオブジェクト)を配置してゲームを構成します
エンティティは親子関係を持つことができ、動的に生成、破棄することができます
以下、サンプルコードにコメントを追加したものです
エンティティ
エンティティとは、シーン上で描画されるオブジェクトのことで、以下のようなものがあります
コンストラクタ名 | 機能 |
---|---|
FilledRect | 単色で塗りつぶした矩形を描画する |
Sprite | 画像を描画する |
FrameSprite | 画像を分割してそれらの一つを描画する。自動的にアニメーションさせることができる |
Label | 単一行テキストを描画する |
SystemLabel | システムフォントでテキストを描画する |
E | 複数のエンティティをまとめる |
Pane | 複数のエンティティをまとめ、領域でクリッピングする |
シーンにエンティティを配置するには、次の2つのステップが必要です
new
演算子でエンティティオブジェクトを作る- シーンの
append()
メソッドでエンティティオブジェクトをシーンに追加する
※「 コンテンツ作成の基本 」より引用
ここでは全てのエンティティやそのプロパティについては説明しないので、気になったら上の表中の各"コンストラクタ名"をクリックしてください(公式リファレンスに飛びます)
エンティティができることの一部を、手っ取り早くスクリプトで説明します
実行すると以下のようになります
アセット
アセットの登録
akashic init
を行ったディレクトリで、
akashic scan asset
のコマンドを実行するとアセットが game.json
に自動で登録されます
登録されるのは以下のファイルです
script
フォルダ下の.js
,.json
image
フォルダ下の.jpg
,.png
audio
フォルダ下の.aac
,.ogg
text
フォルダ下のテキストとして読み込むファイル
アセットの利用
画像・オーディオ・テキストアセットはシーン作成時に、assetIds
プロパティにシーン内で利用するアセットIDを指定します
akashic scan asset
で登録したアセットの assetId
は基本的に拡張子なしのファイル名です
game.json
の以下の箇所に記載されてます
アセットを参照するときは、scene.assets["hoge"]
といった感じで、scene.assets
から assetsId
をキーとして取得します
画像表示
画像表示はエンティティの Sprite
を利用します
これだけでOKです
Spriteについての詳細は「 Sprite | @akashic/akashic-engine 」
オーディオ
ゲーム内で音を鳴らすには音ひとつごとに .ogg
と .aac
のファイルが必要です
(様々な実行環境に対応するためらしい)
再生処理は一行だけです
オーディオアセットはデフォルトではループしない音として登録されます
ループさせる場合は game.json
を以下のように変更します
BGMは音のループで実現させます
テキスト
テキストは以下のようにしてテキストデータを取得できます
文字列の表示
文字列の表示方法は、次の二種類があります
DynamycFont
とSystemLabel
エンティティを利用して表示BitmapFont
とLabel
エンティティを利用して表示
ここでは後者の方法を説明します
前者は "サンセリフ体(ゴシック)", "セリフ体(明朝体)", "等幅フォント" の3種類が使えますが、安っぽい見た目になってしまうので説明は省略します。
気になる方は 「 色々な描画 」
アセットの準備
ビットマップフォントの表示には "フォント(jpg/png)" とそれに対応した "グリフ(json)" のアセットが必要です
ここではAkashicEngineの [サンプルデモの素材] (https://akashic-games.github.io/asset/material.html)からダウンロードしたものを使います
展開したファイルの font16_1.png
を image
フォルダに、glyph_area_16.json
を text
フォルダに移します
ファイルの中身を見るとわかると思いますが、
フォント画像 = "同じ大きさのフォントを並べた画像"、グリフ = "文字に対応したテクスチャ座標(左上)が記述されているjson"
といった感じです
アセットをフォルダに移したら akashic scan asset
でアセットを登録します
ビットマップフォントの描画処理
スクリプトで説明します
これで以下のように文字列が表示されます
入力イベント
"触れた", "移動した", "離した"といった入力を取得するイベント(ポイントイベント)は以下の二種類があります
- エンティティに触れる入力
- シーン上での入力
それぞれスクリプトで説明していきます
エンティティに触れる入力
シーン上での入力
ev
で扱えるプロパティはエンティティに触れる入力と同じです
ランキングモード対応
game.json
の environment
に以下のように追記することで、ランキングモードのゲームとして扱われます
ランキングモード対応のゲームは以下の条件を満たす必要があります
- 一人プレイ
- 一定時間でのスコアを競うゲーム
- 0 ~ 99999 点のスコアを特定の変数に代入する
"特定の変数" とは g.game.vars.gameState.score
のことで、ここに代入した値がスコアとしてランキングに利用されます
また、変数 g.game.vars.gameState.playThreshold
にプレイ閾値を代入することで、そのスコア以下のプレイヤーを未プレイとみなし、ランキングから除外できます
ランキングモード時の残り時間はゲーム開始直後にサーバから通知される "セッションパラメータ" から受け取ります
以下のようにして受け取ることができます
これだけでランキング対応が完了です
ランキングに関しての詳細は「 ニコニコ新市場対応コンテンツ作成ガイド 」
ニコニコ新市場に投稿
「 ニコニコ新市場対応コンテンツの投稿方法」に全部書いてありますが、抜粋して簡略に説明します
1. game.jsonのあるディレクトリで、次のコマンドを実行
akashic export html --output <zipFileName> --atsumaru
注意点
- zipファイルの展開後のサイズが
10MB
以下でないと申請が通らない script
,text
下のアセットの文字コードをUTF-8
にしないと文字化けする恐れがある
2. 投稿ページ でゲームを登録 ※ニコニコのログインが必要
注意点
- ここで指定した「ゲーム名」「アイコン画像」「紹介文」はニコニコ新市場にも反映される
- 「アイコン」のサイズは
160x160
以上、320x320
以下 (正方形推奨) - 「ゲーム表示サイズ」はゲームの解像度と同じ値の指定を推奨
- 「公開」にしないとニコニコ新市場に申請できない
3. 内容保存後、マイページで投稿するゲーム中の「その他 > ニコニコ新市場に登録申請」を選択
4. 「ニコニコ新市場へ登録申請」というダイアログが表示されるので、「申請」ボタンを押す
5. 投稿対象のゲームに「ニコニコ新市場に申請済み」と表示されたら完了
おわりに
ここまで読んだ方は「AkashicEngine完全に理解した」とこの記事をシェアしつつ、つぶやいてOKです
実験放送対応の自作ゲームはまだ少ないので、暇つぶしにでも作ってみてはいかがでしょうか
リフレクションについてまとめた
最近 "Reflection" について調べていたので、それについて自分なりにまとめようと思います。
▼学習を兼ねて作ったもの
※この記事はUnityゆるふわサマーアドベントカレンダー 2018 12日目の記事です。(C#なのでギリUnityということで...)
Reflectionとは
クラスを他のプログラムから利用できるようにするため、 プログラムやライブラリ中にはクラス名やメンバー名、それらのアクセスレベル等の情報が格納されています。 これらの情報はメタデータと呼ばれ、 プログラムの実行時にメタデータを取り出すための機能をリフレクション(reflection)と呼びます。
(プログラムが自分自身の情報を調べることができる機能なので、reflection(鏡映、反射)と呼ぶわけです。)
実行時型情報 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
です。
アクセシビリティレベルに関係なくメンバをあれこれできるのは便利だけど危険、加えて処理も遅いようなので、デバッグ用途で使うことの多い機能だと思います。
MemberInfo
名前や宣言されたクラスなど、メンバとしての情報を持っています。
各 Info のクラスの基底クラスであり、MemberTypeによって変数なのか、関数なのかといったことが判別できます。
MethodInfo
関数に関する様々な情報を持ちます。
関数の実行もできます。
methodInfo.IsSpecialName は関数がコンストラクタ( .ctor )や自動実装プロパティ( プロパティの get/set メソッド( get_xxx, set_xxx )等 )であるときに true になります。
ParametorInfo.HasDefaultValue は .Net 4.5以降で使用可能です。
virtual, abstruct はそれぞれ MethodInfo.IsVirtual, MethodInfo.IsAbstract で判定できますが、overrideされたかのフラグは取得できません。判定したいときは
var isOverride = methodInfo.GetBaseDefinition().DeclaringType != methodInfo.DeclaringType;
のようにすればできます。
FieldInfo
変数に関する様々な情報を持ちます。
値の変更もできます。
IsDefined(), GetCustomAttribute(), GetCustomAttributes() はMethodInfoでも使用可能です。
PropertyInfo
プロパティに関する様々な情報を持ちます。
GetAccesser(), GetGetMethod(), GetSetMethod() の第一引数( bool nonPublic )は publicでないアクセサを取得するかどうかです。
PropertyのアクセサビリティレベルはPropertyInfoからは判定できないので、アクセサから求めます。
GetProperty() のオーバーロードに Type を渡すものがあり、なんだろうと思ったのですが、インデクサを取得するためのものでした。
上記の例では次のようなインデクサを定義しています。
[System.Runtime.CompilerServices.IndexerName("Indexer")]
public int this[int x, int y]
{
get { return _array[y * _width + x]; }
set { _array[y * _width + x] = value; }
}
private int _array;
private int _width;
IndexerNameAttributeで名前を指定しない場合、名前は"Item"になります。
ConstructorInfo
コンストラクタに関する様々な情報を持ちます。
ConstructorInfoを利用してインスタンスの作成ができます。
BindingFlags
各 Info 取得時に検索するメンバを指定するためのものです。
ここでは、主に扱われそうなものを紹介します。
- Public : publicなメンバ
- NonPublic : publicでないメンバ
- Static : 静的なメンバ
- Instance : 静的でないメンバ
- DeclaredOnly : 基底クラスから継承したメンバを含まない
BindingFlags は enum ですが、それぞれが各ビットのフラグを表しており、ビット演算のようにして扱うことができます。
// publicかつ非staticな関数を全取得
var methods = typeof(TestClass).GetMethods(BindingFlags.Public | BindingFlags.Instance);
最後に
リフレクションはどんなメンバにもアクセスでき、外部から関数の実行や値の変更ができる便利な機能です。
実行時に設定ファイル(ScriptableObject)の値を変更したり、クラスが保持する値を全て出力したり、PlantUML用のコードを出力したりと、色々なことができそうな気がします。
間違った認識や、紹介した以外で便利な機能などありましたら教えていただけると嬉しいです。
型の構造を出力する拡張メソッドを作った
指定した型の内容を全出力する拡張メソッドを作りました。
ソースコード
導入方法
- 以下のリンク先へ進み、右上の "Download ZIP" からダウンロード
- 解答したファイルをAssets内に移動
使い方
System.Typeの拡張メソッド、OutputStructure()を呼ぶことで、型の構造情報を持った文字列を得られます。
Debug.Log(typeof(Time).OutputStructure());
Debug.Log(typeof(int).OutputStructure());
第一引数の declearOnly は親クラスのメンバを含むかどうかのフラグ、
第二引数の ignoreSpecialName は特殊な名前のメンバ(自動実装プロパティによって追加されるメンバ等)を表示するかどうかのフラグです
public static string OutputStructure(this Type type, bool declaredOnly = false, bool ignoreSpecialName = true)
補足
基底クラスの型の全取得は以下の処理を利用しました
【C#】指定した型の基底クラスの情報をすべて取得する拡張メソッド - コガネブログ
Timelineエディタを使いやすくする
Timelineエディタを少し使いやすくする方法の紹介です
Trackのアイコンを変更する
Assets/Gizmos下に ファイル名をPlayableTrackのクラス名にしたアイコン画像を配置する
https://forum.unity.com/threads/icon-of-custom-timeline-track.509565/
Gismos/TrackAssets/(トラック名) とやりたかったが認識されず...
Gizmos直下のみ?
Track作成メニューの階層化
TrackAssetのクラスが所属しているnamespaceに応じて自動的に階層化されます
Test.Hoge.Test3Track とした場合、
Test > Hoge > Test3Track ではなく、
Test.Hoge > Test3Track となるみたいです
Trackの色を変える
TrackAsset継承クラスに、以下のような属性を付与するだけです
引数は前から順に 0~1 の r, g, b です
[TrackColor(0.22f, 0.35f, 0.9f)]
Trackの左端とClip下端の色が指定した色になります
CameraやUI、Materialなどで分類し、色を付けるとわかりやすいと思います
ClipのInspecter表示変更
以下、enumに応じてInspenterに表示する値を変えるサンプルです
TimelineでTweenを再生できるトラックを作った
TimelineでTweenが再生できたらいいなと思ったので作りました。
導入方法
- AssetStoreよりDOTweenをインポート
- 下のリンクよりダウンロード
-
パッケージをインポート
- TimelineウィンドウのTrack作成メニューにTweenPlayablesが追加されています
使い方
クリップごとに任意で開始値・終了値・イージングタイプを指定し、開始点から終了点にかけてアニメーションを行います。
クリップのブレンドも可能です。
パッケージに入っているTween用のBehaviour, Mixer, Clip, Trackの基底クラスを利用すれば、楽にTweenのカスタムトラックが作成できると思うので、よかったら使ってみてください。
LinQでも使える2次元配列
2次元配列でLinQを使ったら1次元配列扱いになって不便だったので、LinQでも(2次元座標が)使える2次元配列のクラスを作りました。
実装
使い方
LinQで2次元配列の要素の2次元座標を取得できます。
以下は使用例です。
// 幅10, 高さ10の二次元配列を作成
var width = 10;
var height = 10;
var ary = new Array2D<int>( width, height );
// 各要素に0~2のランダムな値を代入
for( int y = 0; y < height; y++ ) {
for( int x = 0; x < width; x++ ) {
ary.Set( x, y, Random.Range( 0, 3 ) );
}
}// "xが0"の要素に3を代入
ary.Set( elem => elem.x == 0, 3 );
// "xが5,またはyが3" の要素を全取得
var ary2 = ary.Where( elem => elem.x == 5 || elem.y == 3 );
// "valueが1" の要素を全取得
var ary3 = ary.Where( elem => elem.value == 1 );
// "xが5,またはyが3" かつ "valueが1"の全要素に
foreach( var elem in ary2.Union(ary3) ) {
// なんかする
}
参考記事
SceneManageWindow作成で気づいたこと&参考にした資料
初めてのUnityエディタ拡張の上で気づいたこと&SceneManageWindow作成の上で参考にした資料をまとめておきます。
気づいたこと
TextFieldの文字列が消えない
"TextFieldに文字を入力し、Buttonを押したら入力文字列をリセット"
っていうよく使いそうな処理をしたら奇妙な現象が起こりました。
TextFieldから文字が消えない…
上のウィンドウでは以下のような処理をしてます。
_text = EditorGUILayout.TextField( _text, EditorStyles.textField );
if( GUILayout.Button( "Log" ) ) {
Debug.Log( _text );
_text = "";
}
問題なのは、「TextFieldで入力された文字列は(内部でstringの値が変更されても)フォーカスされている間保持される」という点です。
つまり、適当なタイミングでTextFieldのフォーカスを外せばいいということでした。
以下のような処理でフォーカスを外すことができます。
GUI.FocusControll("");
画面上のマウス座標の取得
パスを表示するポップアップを作る際、右クリックメニューのようにクリックした点にポップアップを出したかったので、画面上のマウス座標を取得する必要がありました。
まず、Unityでのゲーム制作でよく使われるInput.mousePositionですが、これはエディタ実行時にはGameViewを基準としたマウス座標を返し、非実行時には(0,0,0)を返すので使えず。
調べると、Event.current.mousePositionという、いかにもな値があったのですが、これは処理を記述したエディターウィンドウのGUI上のマウス座標みたいです。
画面上のマウス座標は以下のように取得しました。
var mouseGUIPos = Event.current.mousePosition;
var mouseScreenPos = EditorGUIUtility.GUIToScreenPoint( mouseGUIPos );
他、参考にした資料
▼エディタ拡張入門
【エディタ拡張徹底解説】初級編①:ウィンドウを自作してみよう【Unity】 – ケットシーウェア
▼シーンの作成・削除・複製・名前変更
第26章 AssetDatabase - エディター拡張入門
▼開始シーンの設定
エディタ上で再生を開始するSceneを固定する【Unity】【エディタ拡張】 - (:3[kanのメモ帳]
▼マルチシーンを開く
楽にシーンを開く拡張機能を作ってみた(マルチシーン対応)【Unity】【エディタ拡張】 - (:3[kanのメモ帳]
▼ScenesInBuildの並び替え可能なリスト
第14章 ReorderbleList - エディター拡張入門
▼データ保存関連
JsonUtility をつかって Unity で JSON を取り扱う方法 - Qiita
▼Path関連処理(ディレクトリ名の取得、使用不可文字の取得など)
▼シーン一覧の折りたたみ
Unity のエディタ拡張で FoldOut をかっこよくするのをやってみた - 凹みTips
▼仕切り線
▼ビルトインのGUIStyleを見る
Show Built In Resources - Unify Community Wiki
最後に
前回の記事のPV数が思ってたより伸びてて、いそいそしてます。
これからもぼちぼち更新していくのでよろしくお願いします。