typescript+enchant.js 制作記事の決定版!
今まで何度かenchant.jsの使い方についてお伝えしてきましたが、リリースから時は流れ、ついにenchant.jsの使い方について書かれた記事も少なくなってしまいました。
なんだかんだで自分は使い続けているので、今後使いたい人のために使い方をまとめることにしました。
以下の記事を読んでいることを前提として進めますが、typescriptにそこそこ慣れている方ならば読まなくても何とかなるかと思います。
それでははじめます。
enchant.jsとは
ゲーム制作ライブラリです。ただ、2020年になり公式サイトが消えてしまったので、消えゆくライブラリであります。
今ならpixi.jsを使うのがオススメらしいです。(RPGツクールMVも描画ライブラリにこれを用いている)
まあ、手軽に制作できる点は非常にメリットですし、javascriptは基本的に後方互換性が確保されているので、今回はこれを使います。
http://wise9.github.io/enchant.js/doc/core/ja/symbols/enchant.Sprite.html
環境を整えよう
ソースコードはこちらになります。
https://github.com/hothukurou/typescript_enchantjs
前回の記事と同じようにinstall とtypescriptのトランスパイラーをウォッチして、webpackを起動してください。
npm install
表示->コマンドパレット-> tasks run task -> tsc:ウォッチ
npm run webpack
準備ができたら、いよいよプログラムをいじります。
enchant.jsの基本構成
index.htmlで遊ぶことを想定しています。ここで、ページ構成について説明します。
index.htmlでiframeを使ってgame.htmlを呼び出しています。
そして、game.html内でenchant.jsライブラリを読み込んでいます。
なぜこんな二重でhtmlを作っているかというと、enchant.jsは「画面サイズいっぱいにゲーム画面を表示する」仕様があるためです。
もしindex.html内でenchant.jsを読んでしまうとindex.html画面全体にゲーム画面が表示されてしまうので、これを避けるためにgame.html内で呼んで、game.htmlページ全体にenchant.jsを表示させているのです。
game.html内でdistフォルダのmain.js(今回制作するゲームプログラム)を読み込んでいる、といった構成になっています。
では、enchant.jsの機能を使って画面に色々表示してみましょう。
enchant.jsプログラムの基本
typescriptフォルダ内のscript.tsを開いてください。
/// <reference path="./enchantjs.d.ts" />
enchant();
window.onload = function () {
const game = new Game(400, 600);
game.onload = function () {
// ここにenchant.jsのコードを書く
};
game.start();
};
前回使った構文 window.onload = function () { } 内にさらに「game.onload=function(){}」が追加されており、この中にゲームプログラムを書きます。そして、最後にgame.start();関数を実行することで、プログラムが開始します。これがenchant.jsの基本構成です。
とりあえず、どこにコードを書けばよいか覚えおけば問題ないです。
enchant.jsをtypescriptで使うにあたり、enchant.jsライブラリで使用するオブジェクト・関数をすべて型定義する必要があります。enchant.jsはjavascriptで書かれたライブラリなので、本来型という概念は存在しませんが、今回「./enchantjs.d.ts」にenchant.jsで使用する型を全て定義しているファイルを作成したので、typescript内で型を認識することができます。
このように「javascript ライブラリの型を定義してtypescriptでも型を認識できるようにしたファイル」を型定義ファイルと呼びます。
enchant.d.ts を見てみると、enchant.js内で使う関数・オブジェクトが全て型定義されているので確認してみると、どんな機能があるかわかるかと思います。
それでは、まずは画面に文字を表示させましょう。
画面に何かを表示しよう
画面にSpriteを表示
さっそく、画面に何か表示してみましょう。
以下のコードをコピーしてscript.tsに貼り付けてください。
/// <reference path="./enchantjs.d.ts" />
enchant();
window.onload = function () {
const game = new Game(400, 600);
game.onload = function () {
const scene = new Scene(); //Sceneを定義
scene.backgroundColor = "black"; // Scenenの色は黒
game.pushScene(scene); // 画面に表示
const obj = new Sprite(40, 40); //Spriteを定義(w:40 h:40)
obj.backgroundColor = "red"; // このSpriteは赤色で塗りつぶす
obj.moveTo(100, 100); // (x:100,y:100)に設置
scene.addChild(obj); // Sceneにobjを表示する
};
game.start();
};
index.htmlを開くと、こんな画面になっているはずです。
enchan.jsでは、game画面にSceneオブジェクトを貼り付けて、
次にSceneオブジェクトにSpriteオブジェクトなどの「描画オブジェクト」を貼り付けることで画面に物体を表示することができます。
Spriteは単色だけでなく、画像を読み込むこともできます。
今回は、imageフォルダの画像を表示しましょう。
/// <reference path="./enchantjs.d.ts" />
enchant();
window.onload = function () {
const game = new Game(400, 600);
const zoyamaSrc = "image/zoyama.png";
game.preload([zoyamaSrc]);
game.onload = function () {
const scene = new Scene(); //Sceneを定義
scene.backgroundColor = "black"; // Scenenの色は黒
game.pushScene(scene); // 画面に表示
const obj = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj.moveTo(100, 100); // (x:100,y:100)に設置
scene.addChild(obj); // Sceneにobjを表示する
};
game.start();
};
画像を表示することができました。
画像表示で注意すべき点として、「enchant.jsではgame.start()前に読み込む画像をgame.preload関数であらかじめ読み込まないと使用できない」です。
逆に言えば、ゲーム開始前に全ての画像を読み込み済のため、画像読み込み時間をほぼ0にして画像を表示できるのです。この機能をプリロード機能といいます。
次に文字を表示しましょう。
/// <reference path="./enchantjs.d.ts" />
enchant();
window.onload = function () {
const game = new Game(400, 600);
const zoyamaSrc = "image/zoyama.png";
game.preload([zoyamaSrc]);
game.onload = function () {
const scene = new Scene(); //Sceneを定義
scene.backgroundColor = "black"; // Scenenの色は黒
game.pushScene(scene); // 画面に表示
const obj = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj.moveTo(100, 100); // (x:100,y:100)に設置
scene.addChild(obj); // Sceneにobjを表示する
const label = new Label("テキスト"); //テキストという文字列を表示
label.font = "20px 'fantasy'"; // 20px fantasy というブラウザ標準書式で表示
label.color = "#ffffff"; // 色を設定
label.moveTo(0, 500); // (x:0,y:500)に設置
scene.addChild(label); //Sceneにlabelを表示する
};
game.start();
};
テキストは font プロパティからフォントの書式とフォントサイズを設定することができます。以下書式の文字列を代入する必要があります。font-sizeプロパrティ等にわけてくれたらよかったのですが、このあたりはenchant.jsのヘンな実装部分でもあります。
label.font = "20px 'fantasy'"; // 20px fantasy というブラウザ標準書式で表示
また、一度設置したラベルのテキストはtextプロパティを変更すればよいです。
/// <reference path="./enchantjs.d.ts" />
enchant();
window.onload = function () {
const game = new Game(400, 600);
const zoyamaSrc = "image/zoyama.png";
game.preload([zoyamaSrc]);
game.onload = function () {
const scene = new Scene(); //Sceneを定義
scene.backgroundColor = "black"; // Scenenの色は黒
game.pushScene(scene); // 画面に表示
const obj = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj.moveTo(100, 100); // (x:100,y:100)に設置
scene.addChild(obj); // Sceneにobjを表示する
const label = new Label("テキスト"); //テキストという文字列を表示
label.font = "20px 'fantasy'"; // 20px fantasy というブラウザ標準書式で表示
label.color = "#ffffff"; // 色を設定
label.moveTo(0, 500); // (x:0,y:500)に設置
scene.addChild(label); //Sceneにlabelを表示する
label.text="このテキストに変更"; //ラベルのテキストを変更
};
game.start();
};
移動・拡大縮小・回転について
Sprite,Labelのような表示オブジェクトは以下のプロパティを持っていて、変更することで自由に変形させることができます。
/// <reference path="./enchantjs.d.ts" />
enchant();
window.onload = function () {
const game = new Game(400, 600);
const zoyamaSrc = "image/zoyama.png";
game.preload([zoyamaSrc]);
game.onload = function () {
const scene = new Scene(); //Sceneを定義
scene.backgroundColor = "black"; // Scenenの色は黒
game.pushScene(scene); // 画面に表示
const obj = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj.moveTo(100, 100); // (x:100,y:100)に設置
scene.addChild(obj); // Sceneにobjを表示する
obj.scaleX = 2; // 横に2倍
obj.scaleY = 0.5; //縦に0.5倍
const obj2 = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj2.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj2.moveTo(150, 200); // (x:100,y:100)に設置
scene.addChild(obj2); // Sceneにobjを表示する
obj2.opacity = 0.5; // 透明度変更
const obj3 = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj3.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj3.moveTo(300, 200); // (x:100,y:100)に設置
scene.addChild(obj3); // Sceneにobjを表示する
obj3.rotation = 60; // 60degree 回転
const label = new Label("テキスト"); //テキストという文字列を表示
label.font = "20px 'fantasy'"; // 20px fantasy というブラウザ標準書式で表示
label.color = "#ffffff"; // 色を設定
label.moveTo(0, 500); // (x:0,y:500)に設置
scene.addChild(label); //Sceneにlabelを表示する
label.text = "このテキストに変更"; //ラベルのテキストを変更
};
game.start();
};
また、変形についてはこれで全てです。
次に、連番画像の読み込みについてみてみましょう。
連番画像の読み込み
こんな画像を用意しました。
40px*30px が3つ、横につながって120px*30pxの一枚画像です。
Spriteオブジェクトのコンストラクタの引数をnew Sprite(120,40)にするとこの1枚画像が表示されますが、new Sprite(40,40)と読み込むことで、40px,40pxの画像1枚だけを表示することができます。
/// <reference path="./enchantjs.d.ts" />
enchant();
window.onload = function () {
const game = new Game(400, 600);
const machine = "image/machine.png";
game.preload([machine]);
game.onload = function () {
const scene = new Scene(); //Sceneを定義
scene.backgroundColor = "black"; // Scenenの色は黒
game.pushScene(scene); // 画面に表示
const obj = new Sprite(40, 30); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj.image = game.assets[machine]; // 画面にぞう山君画像を表示
obj.moveTo(100, 100); // (x:100,y:100)に設置
obj.frame = 0; //フレーム0(3つのうち一番左を表示する)
scene.addChild(obj); // Sceneにobjを表示する
const obj2 = new Sprite(40, 30); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj2.image = game.assets[machine]; // 画面にぞう山君画像を表示
obj2.moveTo(100, 200); // (x:100,y:100)に設置
obj2.frame = 1; //フレーム1(3つのうち真ん中を表示する)
scene.addChild(obj2); // Sceneにobjを表示する
const obj3 = new Sprite(40, 30); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj3.image = game.assets[machine]; // 画面にぞう山君画像を表示
obj3.moveTo(100, 300); // (x:100,y:100)に設置
obj3.frame = 2; //フレーム2(3つのうち一番右を表示する)
scene.addChild(obj3); // Sceneにobjを表示する
{
const obj4 = new Sprite(40, 30); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj4.image = game.assets[machine]; // 画面にぞう山君画像を表示
obj4.moveTo(100, 400); // (x:100,y:100)に設置
obj4.frame = 3; //フレーム3(3つのうち一番左を表示する 要は一巡する)
scene.addChild(obj4); // Sceneにobjを表示する
}
};
game.start();
};
これで、enchant.jsの表示に関わる機能は一通り説明しました。シンプルですね。
あと、表示順番の話をします。
オブジェクトが重なった時、基本的には後からaddChildした方が上に表示されます。
/// <reference path="./enchantjs.d.ts" />
enchant();
window.onload = function () {
const game = new Game(400, 600);
const zoyamaSrc = "image/zoyama.png";
game.preload([zoyamaSrc]);
game.onload = function () {
const scene = new Scene(); //Sceneを定義
scene.backgroundColor = "black"; // Scenenの色は黒
game.pushScene(scene); // 画面に表示
const obj = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj.moveTo(100, 100); // (x:100,y:100)に設置
scene.addChild(obj); // Sceneにobjを表示する
const obj2 = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj2.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj2.moveTo(130, 130); // (x:100,y:100)に設置
scene.addChild(obj2); // Sceneにobjを表示する
const obj3 = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj3.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj3.moveTo(160, 160); // (x:100,y:100)に設置
scene.addChild(obj3); // Sceneにobjを表示する
const label = new Label("テキスト"); //テキストという文字列を表示
label.font = "20px 'fantasy'"; // 20px fantasy というブラウザ標準書式で表示
label.color = "#ffffff"; // 色を設定
label.moveTo(0, 500); // (x:0,y:500)に設置
scene.addChild(label); //Sceneにlabelを表示する
label.text = "このテキストに変更"; //ラベルのテキストを変更
};
game.start();
};
この表示順序を入れ替える方法はありません。これが非常に不便な点なので解決策をお伝えします。
groupというオブジェクトを追加することで、Scene内にgroupというレイヤーを追加することができます。
/// <reference path="./enchantjs.d.ts" />
enchant();
window.onload = function () {
const game = new Game(400, 600);
const zoyamaSrc = "image/zoyama.png";
game.preload([zoyamaSrc]);
game.onload = function () {
const scene = new Scene(); //Sceneを定義
scene.backgroundColor = "black"; // Scenenの色は黒
game.pushScene(scene); // 画面に表示
const group = new Group(); // Groupを定義
scene.addChild(group); // SceneにGroupを設置
const obj = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj.moveTo(100, 100); // (x:100,y:100)に設置
scene.addChild(obj); // Sceneにobjを表示する
const obj2 = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj2.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj2.moveTo(130, 130); // (x:100,y:100)に設置
scene.addChild(obj2); // Sceneにobjを表示する
const obj3 = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj3.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj3.moveTo(160, 160); // (x:100,y:100)に設置
group.addChild(obj3); // groupにobjを設置する(後ろにgroupの位置に表示される)
const label = new Label("テキスト"); //テキストという文字列を表示
label.font = "20px 'fantasy'"; // 20px fantasy というブラウザ標準書式で表示
label.color = "#ffffff"; // 色を設定
label.moveTo(0, 500); // (x:0,y:500)に設置
scene.addChild(label); //Sceneにlabelを表示する
label.text = "このテキストに変更"; //ラベルのテキストを変更
};
game.start();
};
これで表示周りの説明は以上です。ものすごくシンプルですね。
これはenchant.jsの便利さであり、逆に言えば機能貧乏な点であります。
イベントハンドラ(入力)を学ぼう
画面表示が出力だとすると、クリック等の動作は入力に当たります。
今回は入力について学びます。入力するイベントのことをイベントハンドラといいます。
Sprite, Label,Sceneはどれもイベントハンドラ用の関数を持っていて、この関数を定義してあげることで、イベントハンドラを設定することができます。
クリックでイベント発生
obj.ontouchstart=function(){} // クリックを押したらイベント発生
obj.ontouchend=function(){} // クリックを離したらイベント発生
具体的には以下のように使います。jquery等のタッチイベントではtouch系のイベントはマウス操作では反応しないですが、この関数についてはマウス操作でも反応するのでご安心ください。
/// <reference path="./enchantjs.d.ts" />
enchant();
window.onload = function () {
const game = new Game(400, 600);
const zoyamaSrc = "image/zoyama.png";
game.preload([zoyamaSrc]);
game.onload = function () {
const scene = new Scene(); //Sceneを定義
scene.backgroundColor = "black"; // Scenenの色は黒
game.pushScene(scene); // 画面に表示
const group = new Group(); // Groupを定義
scene.addChild(group); // SceneにGroupを設置
const obj = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj.moveTo(100, 100); // (x:100,y:100)に設置
scene.addChild(obj); // Sceneにobjを表示する
obj.ontouchend = function () {
// クリックを離した時にイベント発生
this.x += 100; // xが+100右に移動
};
const obj2 = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj2.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
obj2.moveTo(100, 300); // (x:100,y:100)に設置
scene.addChild(obj2); // Sceneにobjを表示する
obj2.ontouchstart = function () {
// クリックを押した時にイベント発生
this.y += 100; // yが+100移動
};
const label = new Label("テキスト"); //テキストという文字列を表示
label.font = "20px 'fantasy'"; // 20px fantasy というブラウザ標準書式で表示
label.color = "#ffffff"; // 色を設定
label.moveTo(0, 500); // (x:0,y:500)に設置
scene.addChild(label); //Sceneにlabelを表示する
label.text = "このテキストに変更"; //ラベルのテキストを変更
};
game.start();
};
毎フレーム毎に実行
onenterframe=function(){}
毎フレーム(enchnat.js 標準だと30fpsなので1/30秒ごと)で実行されるイベントハンドラです。
オブジェクトを動いて見せるときにはこの関数を使います。
/// <reference path="./enchantjs.d.ts" />
enchant();
window.onload = function () {
const game = new Game(400, 600);
const zoyamaSrc = "image/zoyama.png";
game.preload([zoyamaSrc]);
game.onload = function () {
const scene = new Scene(); //Sceneを定義
scene.backgroundColor = "black"; // Scenenの色は黒
game.pushScene(scene); // 画面に表示
const group = new Group(); // Groupを定義
scene.addChild(group); // SceneにGroupを設置
const obj = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
scene.addChild(obj); // Sceneにobjを表示する
obj.time = 0; // timeプロパティを設定
obj.onenterframe = function () {
// 1/30[s]毎に自動実行される
this.time++; //timeプロパティを加算
this.moveTo(
Math.sin(this.time / 30) * 100 + 150,
Math.cos(this.time / 30) * 100 + 150
); // 毎フレームごとに円軌道を描く
};
const label = new Label("テキスト"); //テキストという文字列を表示
label.font = "20px 'fantasy'"; // 20px fantasy というブラウザ標準書式で表示
label.color = "#ffffff"; // 色を設定
label.moveTo(0, 500); // (x:0,y:500)に設置
scene.addChild(label); //Sceneにlabelを表示する
label.text = "このテキストに変更"; //ラベルのテキストを変更
};
game.start();
};
音を鳴らそう
音を鳴らすときには、画像と同じようにpreloadしてから以下のようにするとなります。
/// <reference path="./enchantjs.d.ts" />
enchant();
window.onload = function () {
const game = new Game(400, 600);
const zoyamaSrc = "image/zoyama.png";
const seSrc = "sound/se.mp3";
game.preload([zoyamaSrc]);
game.preload([seSrc]);
game.onload = function () {
const scene = new Scene(); //Sceneを定義
scene.backgroundColor = "black"; // Scenenの色は黒
game.pushScene(scene); // 画面に表示
const group = new Group(); // Groupを定義
scene.addChild(group); // SceneにGroupを設置
const obj = new Sprite(100, 100); //Spriteを定義(w:100 h:100) 読み込む画像サイズをここで指定しないと、正しく表示されません
obj.image = game.assets[zoyamaSrc]; // 画面にぞう山君画像を表示
scene.addChild(obj); // Sceneにobjを表示する
obj.time = 0; // timeプロパティを設定
obj.onenterframe = function () {
// 1/30[s]毎に自動実行される
this.time++; //timeプロパティを加算
this.moveTo(
Math.sin(this.time / 30) * 100 + 150,
Math.cos(this.time / 30) * 100 + 150
); // 毎フレームごとに円軌道を描く
};
obj.ontouchend = function () {
game.assets[seSrc].clone().play(); // seSrcの音を重複あり(clone())してから鳴らす(play())
};
const label = new Label("テキスト"); //テキストという文字列を表示
label.font = "20px 'fantasy'"; // 20px fantasy というブラウザ標準書式で表示
label.color = "#ffffff"; // 色を設定
label.moveTo(0, 500); // (x:0,y:500)に設置
scene.addChild(label); //Sceneにlabelを表示する
label.text = "このテキストに変更"; //ラベルのテキストを変更
};
game.start();
};
音を鳴らすためには以下の文を鳴らしたいタイミングで追加します。
game.assets[seSrc].clone().play(); // seSrcの音を重複あり(clone())してから鳴らす(play())
これで、入力であるイベントハンドラの部分の説明は完了しました。
enchant.jsは入力と出力について補助してくれるライブラリなので、これにてenchant.jsの説明は以上となります。
ここから本格的にゲーム制作をはじめるためには、ゲームロジックを極めていく必要がでてきます。プログラムは入力→ロジック→出力 という流れで動いていくので、ここからが腕の見せ所になります。
まとめ、ゲームロジックを極めよう!
typescriptでenchant.js ライブラリを使用できる環境構築と、実際にenchant.jsを扱う方法についてまとめました。
enchant.jsを扱う上で必要な知識は一通り説明しました。enchant.jsはあくまでゲーム制作に必要な入力、出力を提供してくれるだけのツールです。
ここからは「いかに人を魅了する演出が作れるか」とか「自分の作りたいゲームシステムを構築できるか」という話になります。
まずは簡単なクリックゲームからはじめて、どんどん大きなゲームが制作できるように、試行錯誤を重ねてみてください。
【宣伝】ブラウザゲーム作りたいけど、何からやってよいかわからない!という方へ
有償で指導承りますので、興味ある方は以下メールアドレスにご連絡ください。
何事も最初は人に教えてもらった方が理解もはかどると思いますので、もし興味がありましたらご相談ください。
tukuchauojisan@gmail.com