JavaScriptでゲームエンジンを作りたいPart1

# はじめに
今回からゲームエンジン制作をやっていきます。
名前は
**「Fortis Engine」(フォーティスエンジン)** 
です。
今回は描画の処理などを書いていく前に、初期化の流れを書いていきます。
結構長くなってしまうかもしれないですが、最後までお付き合いいただけると幸いです。

# 製作１(htmlとaddthis)
まずhtmlファイルの作成。
~~~ html:index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>engine</title>
</head>

<body>
    <script src="engine/addthis.js"></script>
    <script src="script/main.js"></script>
</body>

</html>
~~~
VSCodeのEmmetで「html:5」で作成し、addthis.jsとmain.jsを追加しました。
ファイルの階層としては
~~~
root/
    ┣index.html
    ┣engine/
    ┃　　　┣addthis.js
    ┃　　　┗その他のJSファイル
    ┗script/
     　　　┣main.js
     　　　┗その他のJSファイル
~~~
という感じです。
main.jsはあとで書きます。
~~~javascript:addthis.js
let files = [
    "ファイルの名前(拡張子はなし)"
];

onload = function () {
    loadFiles(files);
}

let loadFiles = async function (paths) {
    for (let i = 0; i < paths.length; i++) {
        let loadFile = new Promise((resolve, reject) => {
            let script = document.createElement("script");
            script.src = "./engine/" + paths[i] + ".js";
            document.body.appendChild(script);
            script.onload = function () {
                resolve("[Info] [" + new Date().toUTCString() + "] - " + paths[i] + ".jsがロードされました。");
            }
            script.onerror = function () {
                reject("[Error] [" + new Date().toUTCString() + "] - " + paths[i] + ".jsはロードできませんでした。")
            }
        });
        let result = await loadFile;
        console.log("[Fortis] " + result);

    }
    Fortis.setup();
}
~~~
onloadでfilesに記述されているファイルを読み込むようにしました。
ファイルの読み込みが終わったときには
~~~
[Fortis] [タイプ] [時間] - 内容
~~~
のフォーマットで出力するようにしました。これは後々も使う予定です。
タイプは今のところ「Info」と「Error」の２つだけです。

# 製作２(Fortis.setup)
addthisの最後の方にある「Fortis.setup()」。これからエンジンを初期化など始めます。
なので、engineフォルダの中にcore.jsというファイルを作ります。
~~~javascript:core.js
let Fortis = {
    //変数
    Game: null,//メインのゲームシステム

    //関数
    setup: null,//ファイルの読み込みが終わったときの処理
}

Fortis.setup = function () {
    Init();//ゲーム設定を想定
    Fortis.Game.init();//ゲームシステムの初期化
    Ready();//ゲームが初期化された後に実行。素材の読み込みなどを想定
    console.log("[Fortis] [Info] [" + new Date().toUTCString() + "] - ゲームループを開始します。");
    Fortis.Game.loop();//ゲームループスタート
}

Fortis.Game = {
    //変数
    config = {
        debug: null,
    }

    //関数
    init(){
    //ゲームシステムの初期化処理
    },
    loop(){
    //ゲームループ
    requestAnimationFrame(this.loop.bind(this));
    }
}
~~~
ひとまずこんな感じ。
&nbsp;  
setup関数の中身について、コメントに書かれている通りなのですが、追加で説明すると、
Fortisがついていない関数(InitとReady)はscriptフォルダ内のmain.jsの方に記述します。
Fortis.Game.init関数で描画するキャンバスの設定などをするつもりなので、main.js中のInit関数内でFortis.Game.configを設定するのを想定してます。
&nbsp;  
それと、「ゲームループを開始します。」というアナウンス。このプログラムをいちいち書いていると見ずらいので、分かりやすくしましょう。
ということで、
~~~javascript:core.js
let Fortis = {
    //変数
    Game: null,//メインのゲームシステム

    //関数
    setup: null,//ファイルの読み込みが終わったときの処理

    //new
    error: null,
    info: null,
}
~~~
「error」と「info」というのを作りました。
そして、util.jsというファイルをengineフォルダ内に追加し、
~~~javascript:util.js
Fortis.error = {}
Fortis.info = {
StartGameLoop() { console.log("[Fortis] [Info] [" + new Date().toUTCString() + "] - ゲームループを開始します。"); },
}
~~~
と書きました。さらに、より見やすくするために
~~~javascript:core.js
let Fortis = {
    //変数
    Game: null,//メインのゲームシステム

    //new
    util: {
        console: null,
    },

    //関数
    setup: null,//ファイルの読み込みが終わったときの処理

    error: null,//エラー処理まとめ
    info: null,//インフォ
}
~~~
core.js内にutilを追加し、その中にconsoleという関数を作りました。consoleの処理はutil.jsに書きます。
~~~javascript:util.js
Fortis.util.console = function (type, content) {
    if (Fortis.Game.config.debug) {
        //「[Fortis] [タイプ] [日付(UTC)] - 内容」のフォーマット
        //タイプは「Info」「Error」
        switch (type) {
            case "Error"://Errorのとき
                let error = new Error();
                console.log("[Fortis] [" + type + "] [" + new Date().toUTCString() + "] - " + content, error);
                break
            case "Info"://Infoのとき
                console.log("[Fortis] [" + type + "] [" + new Date().toUTCString() + "] - " + content);
                break
        }
    }
}
~~~
どんなときでもconsole.logが実行されると処理が重くなりそうなので、ゲームの設定でdebugがtrueになっているときのみに限定しました。
typeがErrorのときは、Errorオブジェクトを作成して、プログラムのどのへんから発せられているかを分かるようになっています。
&nbsp; 
するとinfo/errorは、
~~~javascript:util.js
Fortis.error = {}
Fortis.info = {
StartGameLoop() { Fortis.util.console("Info", "ゲームループを開始します。") },
}
~~~
となり、もともとのcore.jsのFortis.setup関数内も
~~~javascript:core.js
Fortis.setup = function () {
    Init();//ゲーム設定を想定
    Fortis.Game.init();//ゲームシステムの初期化
    Ready();//ゲームが初期化された後に実行。素材の読み込みなどを想定
    Fortis.info.StartGameLoop();//ここ
    Fortis.Game.loop();//ゲームループスタート
}
~~~
にします。
大分見やすくなったと思います。

# まとめ
以下、今のところのファイル・フォルダの状況
~~~
root/
    ┣index.html
    ┣engine/
    ┃　　　┣addthis.js
    ┃　　　┣core.js
    ┃　　　┗util.js
    ┗script/
     　　　┗main.js
~~~
~~~ html:index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>engine</title>
</head>

<body>
    <script src="engine/addthis.js"></script>
    <script src="script/main.js"></script>
</body>

</html>
~~~
~~~javascript:engine/addthis.js
let files = [
    "core",
    "util",
];

onload = function () {
    loadFiles(files);
}

let loadFiles = async function (paths) {
    for (let i = 0; i < paths.length; i++) {
        let loadFile = new Promise((resolve, reject) => {
            let script = document.createElement("script");
            script.src = "./engine/" + paths[i] + ".js";
            document.body.appendChild(script);
            script.onload = function () {
                resolve("[Info] [" + new Date().toUTCString() + "] - " + paths[i] + ".jsがロードされました。");
            }
            script.onerror = function () {
                reject("[Error] [" + new Date().toUTCString() + "] - " + paths[i] + ".jsはロードできませんでした。")
            }
        });
        let result = await loadFile;
        console.log("[Fortis] " + result);

    }
    Fortis.setup();
}
~~~
~~~javascript:engine/core.js
let Fortis = {
    //変数
    Game: null,//メインのゲームシステム

    //便利なやつ
    util: {
        console: null,//コンソール出力
    },

    //関数
    setup: null,//ファイルの読み込みが終わったときの処理

    error: null,//エラー処理まとめ
    info: null,//インフォ
}

Fortis.setup = function () {
    Init();//ゲーム設定を想定
    Fortis.Game.init();//ゲームシステムの初期化
    Ready();//ゲームが初期化された後に実行。素材の読み込みなどを想定
    Fortis.info.StartGameLoop();//ここ
    Fortis.Game.loop();//ゲームループスタート
}

Fortis.Game = {
    //変数
    config = {
        debug: null,
    }

    //関数
    init(){
    //ゲームシステムの初期化処理
    },
    loop(){
    //ゲームループ
    requestAnimationFrame(this.loop.bind(this));
    }
}
~~~
~~~javascript:engine/util.js
Fortis.error = {}
Fortis.info = {
StartGameLoop() { Fortis.util.console("Info", "ゲームループを開始します。") },
}

Fortis.util.console = function (type, content) {
    if (Fortis.Game.config.debug) {
        //「[Fortis] [タイプ] [日付(UTC)] - 内容」のフォーマット
        //タイプは「Info」「Error」
        switch (type) {
            case "Error"://Errorのとき
                let error = new Error();
                console.log("[Fortis] [" + type + "] [" + new Date().toUTCString() + "] - " + content, error);
                break
            case "Info"://Infoのとき
                console.log("[Fortis] [" + type + "] [" + new Date().toUTCString() + "] - " + content);
                break
        }
    }
}
~~~
&nbsp;
切りのいいとこまで書こうと思ってたら長くなってきたのでこの辺りにしときます。
次回はGame.initの部分についてやろうと思います。(なのであんまり長くないと思う。)
また、週一での更新を目標にしています。ですが、余裕のあるときはどんどん更新していきます。
それではまた次回。

前回：https://qiita.com/Rei-Miyakawa/items/efff6bf3788ce7902952
次回：https://qiita.com/Rei-Miyakawa/items/2353444e911b73bbc047