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

# はじめに
今回から描画関係について書いていきます。
と言っても、まだ初回なのでfillRectとかそんな感じのを書くのは次回以降です。
# 説明
まず、どんな風に描画していくかを説明します。
~~~
Fortis.Game/
　　　　　　┗シーン/
                 ┣背景
                 ┣オブジェクト
                 ┗UI
~~~
Fortis.Gameにsceneという描画するエンティティをまとめて格納するものを作ります。
描画の順番は図の上からです。
以上。
# シーン
Forits.Gameにsceneを入れる変数を追加します。
~~~javascript:engine/core.js
Fortis.Game = {
    //変数系
    canvas: null,//オフスクリーンキャンバス(エンジン外からのアクセスする可能性もあるので、の便宜上この名前とする)
    context: null,//canvasのコンテキスト(名前の理由は同上)
    finalCanvas: null,//最終的に描画されるキャンバス
    finalContext: null,//finalCanvasのコンテキスト
    config: {//設定
        debug: false,//デバッグモード
    },
    
    //new
    scene: null,//シーン
}
~~~
そして、FortisにSceneというクラスを作ります。
~~~javascript:engine/core.js
let Fortis = {
    //変数
    Game: null,//メインのゲームシステム

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

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

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

    //クラス
    //new
    Scene: null,//シーン
}
~~~
新しくengineフォルダにscene.jsというファイルを作ります。
~~~javascript:engine/scene.js
Fortis.Scene = class{
    constructor(){
        this.type = "Scene";
        this.bg = [];
        this.objs = [];
        this.uis = [];
    }
    getType(){
        return this.type;
    }
    delete(){
        for(let key in this){
            if(this.hasOwnProperty(key)){
                this[key] = null;
            }
        }
    }
    getBG(){
        return this.bg;
    }
    getObjects(){
        return this.objs;
    }
    getUIs(){
        return this.uis;
    }
}
~~~
ひとまずはこんな感じです。
delete関数については、オブジェクトの要素をすべてnullにすることによってメモリ開放を促すはず!
オブジェクトに直接nullを代入しちゃってもいいのかもしれないけど、一応用意しておきました。
有識者の方、教えてください。

# ベクトル
二次元ベクトルクラスを作っていきます。
これがあると座標の管理・計算が楽になります。

FortisにVector2を作成します。
~~~javascript:engine/core.js
let Fortis = {
    //変数
    Game: null,//メインのゲームシステム

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

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

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

    //クラス
    Scene: null,//シーン
    //new
    Vector2: null,//二次元ベクトル
}
~~~
中身は新しくengine/vector.jsを作成し、そこに書いていきます。
~~~javascript:engine/vector.js
Fortis.Vector2 = class {
    constructor(x, y) {
        this.type = "Vector2";//タイプ

        //x要素の判定
        if (x == null) {
            this.x = 0;
        } else {
            this.x = x;
        }

        //y要素の判定
        if (y == null) {
            this.y = 0;
        } else {
            this.y = y;
        }
    }
    getType() {//タイプを取得
        return this.type;
    }
    delete() {//削除
        for (let key in this) {
            if (this.hasOwnProperty(key)) {
                this[key] = null;
            }
        }
    }
    add(vec) {//足し算
        this.x += vec.x;
        this.y += vec.y;
        return this;
    }
    sub(vec) {//引き算
        this.x -= vec.x;
        this.y -= vec.y;
        return this;
    }
    mul(scale){//掛け算
        this.x*=scale;
        this.y*=scale;
        return this;
    }
    mag(){//大きさ、原点(左上)からの距離
        return Math.sqrt(this.x**2,this.y**2);
    }
    normalize(){//単位ベクトルにする
        let mag = this.mag();
        let vec = this.copy();
        return vec.mul(1/mag);

    }
    distance(vec){//2点間の距離
        vec.sub(this);
        return Math.sqrt(vec.x**2+vec.y**2);
    }
    copy() {//コピー
        return new Fortis.Vector2(this.x, this.y);
    }
}
~~~
コメントに書いてある通りなのですが、
足し算・引き算・掛け算・大きさ・正規化・２点間の距離・コピー
の機能を書きました。

ですが、このままだと引数が間違っていたとき、そのまま計算してしまうので、それを判別できるようにします。
# エラーメッセージの追加・引数の識別
なくてもエラーで弾かれるとは思うのですが、あった方がより便利だと思うので追加します。
といっても、「引数の型が間違っています」とかその程度です。

まず、エラーメッセージをFortis.errorに追加します。
~~~javascript:engine/util.js
//関数
Fortis.error = {
    ArgNotExists() { Fortis.util.console("Error", "引数が指定されていません。") },
    ArgTypeWrong() { Fortis.util.console("Error", "引数の型もしくはタイプが間違っています。") },
}
~~~
必要な引数が指定されていないときと、引数のタイプが違うときのを追加しました。

次に 引数の型 と 引数の型がobjectのときのtype / objectではないとき のを識別をする関数を作ります。
Fortis.utilにcheckTypeを追加して、util.jsにcheckTypeを書いていきます。
~~~javascript:engine/core.js
let Fortis = {
    //便利なやつ
    util: {
        console: null,//コンソール出力
        
        //new
        checkType: null,//型・タイプ識別
    },
}
~~~
~~~javascript:engine/util.js
Fortis.util.checkType = function (variable, varType, type) {
    if (typeof (variable) != varType) return false;//変数方チェック
    if (type == null) return true;//引数のtypeがあるか
    if (variable.type == undefined) return variable.indexOf(type) != -1;//variableにtypeが存在するか + variableのチェック
    return variable.type.indexOf(type) != -1;//variable.typeのチェック
}
~~~
これをvector.jsに適応していきます。
~~~javascript:engine/vector.js
Fortis.Vector2 = class {
    constructor(x, y) {
        this.type = "Vector2";//タイプ

        //x要素の判定
        if (x == null) {
            this.x = 0;
        } else {
            if (Fortis.util.checkType(x, "number")) {
                this.x = x;
            } else {
                Fortis.error.ArgTypeWrong();
            }
        }

        //y要素の判定
        if (y == null) {
            this.y = 0;
        } else {
            if (Fortis.util.checkType(y, "number")) {
                this.y = y;
            } else {
                Fortis.error.ArgTypeWrong();
            }
        }
    }
    getType() {//タイプを取得
        return this.type;
    }
    delete() {//削除
        for (let key in this) {
            if (this.hasOwnProperty(key)) {
                this[key] = null;
            }
        }
    }
    add(vec) {//足し算
        if (vec == null) return Fortis.error.ArgNotExists();
        if (Fortis.util.checkType(vec, "object", "Vector2")) {
            this.x += vec.x;
            this.y += vec.y;
            return this;
        }
        return Fortis.error.ArgTypeWrong();
    }
    sub(vec) {//引き算
        if (vec == null) return Fortis.error.ArgNotExists();
        if (Fortis.util.checkType(vec, "object", "Vector2")) {
            this.x -= vec.x;
            this.y -= vec.y;
            return this;
        }
        return Fortis.error.ArgTypeWrong();
    }
    mul(scale) {//掛け算
        if (scale == null) return Fortis.error.ArgNotExists();
        if (Fortis.util.checkType(scale, "number")) {
            this.x *= scale;
            this.y *= scale;
            return this;
        }
        return Fortis.error.ArgTypeWrong();
    }
    mag() {//大きさ、原点(左上)からの距離
        return Math.sqrt(this.x ** 2 + this.y ** 2);
    }
    normalize() {//単位ベクトルにする
        let mag = this.mag();
        let vec = this.copy();
        return vec.mul(1 / mag);

    }
    distance(vec) {//2点間の距離
        if (vec == null) return Fortis.error.ArgNotExists();
        if (Fortis.util.checkType(vec, "object", "Vector2")) {
            vec.sub(this);
            return Math.sqrt(vec.x ** 2 + vec.y ** 2);
        }
        return Fortis.error.ArgTypeWrong();
    }
    copy() {//コピー
        return new Fortis.Vector2(this.x, this.y);
    }
}
~~~
試しにやってみましたが上手くできました。
# 色
四次元ベクトルとか三次元ベクトルとかにしてもよいのですが、今回は別で作ります。

クラスを作成する前に、htmlのカラーネームをrgbに変換できるようにします。
全部は大変なので、ニコニコ動画の一般会員でも使える10色(白、赤、ピンク、オレンジ、黄色、緑、シアン、青、紫、黒の)のみにします。

Fortis.utilにnamedColorsを追加します。
~~~javascript:engine/core.js
let Fortis = {
    //便利なやつ
    util: {
        console: null,//コンソール出力
        checkType: null,//型・タイプ識別

        //new
        namedColor: null,//カラーネーム
    },
}
~~~
~~~javascript:engine/util.js
Fortis.util.namedColors = {//カラーネーム
    white: { r: 255, g: 255, b: 255 },
    red: { r: 255, g: 0, b: 0 },
    pink: { r: 255, g: 192, b: 203 },
    orange: { r: 255, g: 165, b: 0 },
    yellow: { r: 255, g: 255, b: 0 },
    green: { r: 0, g: 128, b: 0 },
    cyan: { r: 0, g: 255, b: 255 },
    blue: { r: 0, g: 0, b: 255 },
    purple: { r: 128, g: 0, b: 128 },
    black: { r: 0, g: 0, b: 0 }
}
~~~


次にカラーコード・HSV←→RGBの処理を作ります。(RGBを基準とする)
Fortis.utilにhexToRGB・HSVToRGB・RGBToHex・RGBToHSVの4つを追加します。
HSVのs、vについては範囲は0~1にしときます。
~~~javascript:engine/core.js
let Fortis = {
    //色
    hexToRGB: null,//カラーコードをRGBに
    HSVToRGB: null,//HSVをRGBに
    RGBToHex: null,//RGBをカラーコードに
    RGBToHSV: null,//RGBをHSVに
}
~~~
~~~javascript:util.js
Fortis.util.hexToRGB = function (hex) {
    if (!Fortis.util.checkType(hex, "string", "#")) return Fortis.error.NotColorCode();
    if (hex.length != 7) return Fortis.error.NotColorCode();
    if (isNaN(parseInt(hex, 16))) return Fortis.error.NotColorCode();
    let rgb = {};
    rgb.r = parseInt(hex.slice(1, 3), 16);
    rgb.g = parseInt(hex.slice(3, 5), 16);
    rgb.b = parseInt(hex.slice(5, 7), 16);
    return rgb;
}

Fortis.util.HSVToRGB = function (hsv) {
    if (!Fortis.util.checkType(hsv, "object")) return Fortis.error.ArgTypeWrong();
    if (hsv.h == undefined || hsv.s == undefined || hsv.v == undefined) return Fortis.error.ArgTypeWrong();
    if (!(hsv.h >= 0 && hsv.h <= 360 && hsv.s >= 0 && hsv.s <= 1 && hsv.v >= 0 && hsv.v <= 1)) return Fortis.error.ArgTypeWrong();
    let RGB = {};
    let max = hsv.v * 255;
    let min = max * (1 - hsv.s);
    let common = (max - min);
    if (hsv.h <= 60) {
        RGB.r = max;
        RGB.g = (hsv.h / 60) * common + min;
        RGB.b = min;
    } else if (hsv.h <= 120) {
        RGB.r = ((120 - hsv.h) / 60) * common + min;
        RGB.g = max;
        RGB.b = min;
    } else if (hsv.h <= 180) {
        RGB.r = min;
        RGB.g = max;
        RGB.b = ((hsv.h - 120) / 60) * common + min;
    } else if (hsv.h <= 240) {
        RGB.r = min;
        RGB.g = ((240 - hsv.h) / 60) * common + min;
        RGB.b = max;
    } else if (hsv.h <= 300) {
        RGB.r = ((hsv.h - 240) / 60) * common + min;
        RGB.g = min;
        RGB.b = max;
    } else {
        RGB.r = max;
        RGB.g = min;
        RGB.b = ((360 - hsv.h) / 60) * common + min;
    }
    return RGB;
}

Fortis.util.RGBToHex = function (rgb) {
    if (!Fortis.util.checkType(rgb, "object")) return Fortis.error.ArgTypeWrong();
    if (rgb.r == undefined || rgb.g == undefined || rgb.b == undefined) return Fortis.error.ArgTypeWrong();
    if (!(rgb.r >= 0 && rgb.r <= 255 && rgb.g >= 0 && rgb.g <= 255 && rgb.b >= 0 && rgb.b <= 255)) return Fortis.error.ArgTypeWrong();
    let code_text = "#";
    let RGB = { r: rgb.r, g: rgb.g, b: rgb.b }
    for (let element in RGB) {
        let parsed = parseInt(RGB[element], 16).toString();
        if (parsed.length == 1) {
            code_text += "0";
        }
        code_text += parsed;
    }
    return code_text;
}

Fortis.util.RGBToHSV = function (rgb) {
    if (!Fortis.util.checkType(rgb, "object")) return Fortis.error.ArgTypeWrong();
    if (rgb.r == undefined || rgb.g == undefined || rgb.b == undefined) return Fortis.error.ArgTypeWrong();
    if (!(rgb.r >= 0 && rgb.r <= 255 && rgb.g >= 0 && rgb.g <= 255 && rgb.b >= 0 && rgb.b <= 255)) return Fortis.error.ArgTypeWrong();
    let HSV = {};
    let max = Math.max(rgb.r, Math.max(rgb.g, rgb.b));
    let min = Math.min(rgb.r, Math.min(rgb.g, rgb.b));
    switch (max) {
        case rgb.r:
            HSV.h = (rgb.g - rgb.b) * 60 / (max - min);
            break
        case rgb.g:
            HSV.h = (rgb.b - rgb.r) * 60 / (max - min) + 120;
            break
        case rgb.b:
            HSV.h = (rgb.r - rgb.g) * 60 / (max - min) + 240;
            break
    }
    if (rgb.r == rgb.g && rgb.g == rgb.b) HSV.h = 0;
    if (HSV.h < 0) HSV.h += 360;
    if (max == 0) {
        HSV.h = 0;
        HSV.s = 0;
        HSV.v = 0;
    } else {
        HSV.s = (max - min) / max;
        HSV.v = max / 255;
    }
    return HSV;
}
~~~
https://zenn.dev/hk_ilohas/articles/rgb-hsv-convert
こちらを参考にさせていただきました。

FortisにColorクラスを作成します。中身はengineフォルダ内にcolor.jsを作成して、そこに記述します。
~~~javascript:engine/core.js
let Fortis = {
    //クラス
    Scene: null,//シーン
    Vector2: null,//二次元ベクトル

    //new
    Color: null,//色
}
~~~
~~~javascript:engine/color.js
Fortis.Color = class {
    constructor(hexOrR, g, b, a) {
        this.type = "Color";
        this.r, this.g, this.b, this.a = 1;
        if (hexOrR == null) {//hexOrRが空なら、引数はなしで白にする判定
            this.r = 255, this.g = 255, this.b = 255;
            return true;
        } else if (g == null && b == null) {//カラーコード判定
            if (Fortis.util.checkType(hexOrR, "string", "#")) {//＃がついていたらかつ7文字ならカラーコードだとみなす
                if (hexOrR.length == 7) {
                    if (parseInt(hexOrR, 16)) return Fortis.error.NotColorCode();
                    let RGB = Fortis.util.hexToRGB(hexOrR);
                    this.r = RGB.r;
                    this.g = RGB.g;
                    this.b = RGB.b;
                    return true;
                } else {
                    return Fortis.error.NotColorCode();
                }
            } else if (Fortis.util.checkType(hexOrR, "string")) {//名前付き色判定
                if (Fortis.util.namedColors[hexOrR] == undefined) {
                    return Fortis.error.KeyNotExistsInObject();
                } else {
                    this.r = Fortis.util.namedColors[hexOrR].r;
                    this.g = Fortis.util.namedColors[hexOrR].g;
                    this.b = Fortis.util.namedColors[hexOrR].b;
                    return true;
                }
            } else {
                return Fortis.error.NotColorCode();
            }
        } else if (Fortis.util.checkType(g, "number") && Fortis.util.checkType(b, "number")) {//RGBもしくはHSVもしくはRGBAの形
            if (hexOrR >= 0 && hexOrR <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {//RGB
                this.r = hexOrR;
                this.g = g;
                this.b = b;
            } else if (hexOrR >= 0 && hexOrR <= 360 && g >= 0 && g <= 1 && b >= 0 && b <= 1) {//HSV
                let RGB = Fortis.util.HSVToRGB({ h: hexOrR, s: g, v: b });
                this.r = RGB.r;
                this.g = RGB.g;
                this.b = RGB.b;
            }

            //aの処理
            if (a != null) {//RGBA
                if (Fortis.util.checkType(a, "number")) {
                    this.a = Math.max(0, Math.min(1, a));
                    return true;
                } else {
                    return Fortis.error.ArgTypeWrong();
                }
            }
        } else {
            return Fortis.error.ArgTypeWrong();
        }
    }
    getType() {//タイプを取得
        return this.type;
    }
    delete() {//削除
        for (let key in this) {
            if (this.hasOwnProperty(key)) {
                this[key] = null;
            }
        }
    }
    invert() {//反転
        this.r = 255 - this, r;
        this.g = 255 - this, g;
        this.b = 255 - this, b;
        return this;
    }
    getComplementaryColor() {//補色を取得
        return new Fortis.Color(255 - this, r, 255 - this, g, 255 - this, b);
    }
    adjustBrightness(variable) {//明るさ調節
        if (!Fortis.util.checkType(variable, "number")) return Fortis.error.ArgTypeWrong();
        this.r = Math.max(0, Math.min(255, this.r + variable));
        this.g = Math.max(0, Math.min(255, this.g + variable));
        this.b = Math.max(0, Math.min(255, this.b + variable));
        return this;
    }
    toHex() {//16進数変換
        return Fortis.util.RGBToHex({ r: this.r, g: this.g, b: this.b });
    }
    toHSV() {//HSV変換
        return Fortis.util.RGBToHSV({ r: this.r, g: this.g, b: this.b });
    }
    toRGB() {//RGB変換
        return "rgb(" + this.r + "," + this.g + "," + this.b + ")";
    }
    toRGBA() {//RGBA変換
        return "rgba(" + this.r + "," + this.g + "," + this.b + "," + this.a + ")";
    }
}
~~~
Fortis.errorにNotColorCodeを追加します。
~~~javascript:engine/util.js
Fortis.error = {
    ArgNotExists() { Fortis.util.console("Error", "引数が指定されていません。") },
    ArgTypeWrong() { Fortis.util.console("Error", "引数の型もしくはタイプが間違っています。") },
    NotColorCode() { Fortis.util.console("Error", "カラーコードは「#」と16進数6文字を足した計7文字で入力してください") },
}
~~~
こちらも問題なく動きました。
# まとめ
以下、今のところのファイル・フォルダの状況(変更されたところだけ)
~~~
root/
    ┣index.html
    ┣engine/
    ┃　　　┣addthis.js
    ┃　　　┣core.js
    ┃　　　┣draw.js
    ┃　　　┣vector.js
    ┃　　　┣scene.js
    ┃　　　┣color.js
    ┃　　　┗util.js
    ┗script/
     　　　┗main.js
~~~
~~~javascript:engine/addthis.js
let files = [
    "core",
    "vector",
    "scene",
    "color",
    "draw",
    "util",
];
~~~
~~~javascript:engine/core.js
let Fortis = {
    //変数
    Game: null,//メインのゲームシステム

    //便利なやつをまとめたもの-util.js
    util: {
        //変数
        namedColors: null,//名前付き色
        //関数
        console: null,//コンソール(ゲームの設定のデバッグがtrueでないと機能しない)
        checkType: null,//変数の型やタイプなどについてチェックする
        //色
        hexToRGB: null,//カラーコードをRGBに
        HSVToRGB: null,//HSVをRGBに
        RGBToHex: null,//RGBをカラーコードに
        RGBToHSV: null,//RGBをHSVに
    },

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

    error: null,//エラーをまとめたもの-util.js
    info: null,//処理完了などのお知らせをまとめたもの-util.js

    //クラス
    Vector2: null,//二次元配列(x,y)の形-vector.js
    Scene: null,//シーン-scene.js
    Color: null,//色-color.js
}
~~~
~~~javascript:engine/scene.js
Fortis.Scene = class{
    constructor(){
        this.type = "Scene";
        this.bg = [];
        this.objs = [];
        this.uis = [];
    }
    getType(){//タイプ取得
        return this.type;
    }
    delete(){//削除
        for(let key in this){
            if(this.hasOwnProperty(key)){
                this[key] = null;
            }
        }
    }
    getBG(){//背景取得
        return this.bg;
    }
    getObjects(){//オブジェクト取得
        return this.objs;
    }
    getUIs(){//UI取得
        return this.uis;
    }
}
~~~
~~~javascript:engine/vector.js
Fortis.Vector2 = class {
    constructor(x, y) {
        this.type = "Vector2";//タイプ

        //x要素の判定
        if (x == null) {
            this.x = 0;
        } else {
            if (Fortis.util.checkType(x, "number")) {
                this.x = x;
            } else {
                return Fortis.error.ArgTypeWrong();
            }
        }

        //y要素の判定
        if (y == null) {
            this.y = 0;
        } else {
            if (Fortis.util.checkType(y, "number")) {
                this.y = y;
            } else {
                return Fortis.error.ArgTypeWrong();
            }
        }
    }
    getType() {//タイプを取得
        return this.type;
    }
    delete() {//削除
        for (let key in this) {
            if (this.hasOwnProperty(key)) {
                this[key] = null;
            }
        }
    }
    add(vec) {//足し算
        if (vec == null) return Fortis.error.ArgNotExists();
        if (Fortis.util.checkType(vec, "object", "Vector2")) {
            this.x += vec.x;
            this.y += vec.y;
            return this;
        }
        return Fortis.error.ArgTypeWrong();
    }
    sub(vec) {//引き算
        if (vec == null) return Fortis.error.ArgNotExists();
        if (Fortis.util.checkType(vec, "object", "Vector2")) {
            this.x -= vec.x;
            this.y -= vec.y;
            return this;
        }
        return Fortis.error.ArgTypeWrong();
    }
    mul(scale) {//掛け算
        if (scale == null) return Fortis.error.ArgNotExists();
        if (Fortis.util.checkType(scale, "number")) {
            this.x *= scale;
            this.y *= scale;
            return this;
        }
        return Fortis.error.ArgTypeWrong();
    }
    mag() {//大きさ、原点(左上)からの距離
        return Math.sqrt(this.x ** 2 + this.y ** 2);
    }
    normalize() {//単位ベクトルにする
        let mag = this.mag();
        let vec = this.copy();
        return vec.mul(1 / mag);

    }
    distance(vec) {//2点間の距離
        if (vec == null) return Fortis.error.ArgNotExists();
        if (Fortis.util.checkType(vec, "object", "Vector2")) {
            vec.sub(this);
            return Math.sqrt(vec.x ** 2 + vec.y ** 2);
        }
        return Fortis.error.ArgTypeWrong();
    }
    copy() {//コピー
        return new Fortis.Vector2(this.x, this.y);
    }
}
~~~
~~~javascript:engine/color.js
Fortis.Color = class {
    constructor(hexOrR, g, b, a) {
        this.type = "Color";
        this.r, this.g, this.b, this.a = 1;
        if (hexOrR == null) {//hexOrRが空なら、引数はなしで白にする判定
            this.r = 255, this.g = 255, this.b = 255;
            return true;
        } else if (g == null && b == null) {//カラーコード判定
            if (Fortis.util.checkType(hexOrR, "string", "#")) {//＃がついていたらかつ7文字ならカラーコードだとみなす
                if (hexOrR.length == 7) {
                    if (parseInt(hexOrR, 16)) return Fortis.error.NotColorCode();
                    let RGB = Fortis.util.hexToRGB(hexOrR);
                    this.r = RGB.r;
                    this.g = RGB.g;
                    this.b = RGB.b;
                    return true;
                } else {
                    return Fortis.error.NotColorCode();
                }
            } else if (Fortis.util.checkType(hexOrR, "string")) {//名前付き色判定
                if (Fortis.util.namedColors[hexOrR] == undefined) {
                    return Fortis.error.KeyNotExistsInObject();
                } else {
                    this.r = Fortis.util.namedColors[hexOrR].r;
                    this.g = Fortis.util.namedColors[hexOrR].g;
                    this.b = Fortis.util.namedColors[hexOrR].b;
                    return true;
                }
            } else {
                return Fortis.error.NotColorCode();
            }
        } else if (Fortis.util.checkType(g, "number") && Fortis.util.checkType(b, "number")) {//RGBもしくはHSVもしくはRGBAの形
            if (hexOrR >= 0 && hexOrR <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {//RGB
                this.r = hexOrR;
                this.g = g;
                this.b = b;
            } else if (hexOrR >= 0 && hexOrR <= 360 && g >= 0 && g <= 1 && b >= 0 && b <= 1) {//HSV
                let RGB = Fortis.util.HSVToRGB({ h: hexOrR, s: g, v: b });
                this.r = RGB.r;
                this.g = RGB.g;
                this.b = RGB.b;
            }

            //aの処理
            if (a != null) {//RGBA
                if (Fortis.util.checkType(a, "number")) {
                    this.a = Math.max(0, Math.min(1, a));
                    return true;
                } else {
                    return Fortis.error.ArgTypeWrong();
                }
            }
        } else {
            return Fortis.error.ArgTypeWrong();
        }
    }
    getType() {//タイプを取得
        return this.type;
    }
    delete() {//削除
        for (let key in this) {
            if (this.hasOwnProperty(key)) {
                this[key] = null;
            }
        }
    }
    invert() {//反転
        this.r = 255 - this, r;
        this.g = 255 - this, g;
        this.b = 255 - this, b;
        return this;
    }
    getComplementaryColor() {//補色を取得
        return new Fortis.Color(255 - this, r, 255 - this, g, 255 - this, b);
    }
    adjustBrightness(variable) {//明るさ調節
        if (!Fortis.util.checkType(variable, "number")) return Fortis.error.ArgTypeWrong();
        this.r = Math.max(0, Math.min(255, this.r + variable));
        this.g = Math.max(0, Math.min(255, this.g + variable));
        this.b = Math.max(0, Math.min(255, this.b + variable));
        return this;
    }
    toHex() {//16進数変換
        return Fortis.util.RGBToHex({ r: this.r, g: this.g, b: this.b });
    }
    toHSV() {//HSV変換
        return Fortis.util.RGBToHSV({ r: this.r, g: this.g, b: this.b });
    }
    toRGB() {//RGB変換
        return "rgb(" + this.r + "," + this.g + "," + this.b + ")";
    }
    toRGBA() {//RGBA変換
        return "rgba(" + this.r + "," + this.g + "," + this.b + "," + this.a + ")";
    }
}
~~~
~~~javascript:engine/util.js
//変数
Fortis.util.namedColors = {//カラーネーム
    white: { r: 255, g: 255, b: 255 },
    red: { r: 255, g: 0, b: 0 },
    pink: { r: 255, g: 192, b: 203 },
    orange: { r: 255, g: 165, b: 0 },
    yellow: { r: 255, g: 255, b: 0 },
    green: { r: 0, g: 128, b: 0 },
    cyan: { r: 0, g: 255, b: 255 },
    blue: { r: 0, g: 0, b: 255 },
    purple: { r: 128, g: 0, b: 128 },
    black: { r: 0, g: 0, b: 0 }
}

//関数
Fortis.error = {
    ArgNotExists() { Fortis.util.console("Error", "引数が指定されていません。") },
    ArgTypeWrong() { Fortis.util.console("Error", "引数の型もしくはタイプが間違っています。") },
    NotColorCode() { Fortis.util.console("Error", "カラーコードは「#」と16進数6文字を足した計7文字で入力してください") },
}

Fortis.util.checkType = function (variable, varType, type) {
    if (typeof (variable) != varType) return false;//変数方チェック
    if (type == null) return true;//引数のtypeがあるか
    if (variable.type == undefined) return variable.indexOf(type) != -1;//variableにtypeが存在するか + variableのチェック
    return variable.type.indexOf(type) != -1;//variable.typeのチェック
}

Fortis.util.hexToRGB = function (hex) {
    if (!Fortis.util.checkType(hex, "string", "#")) return Fortis.error.NotColorCode();
    if (hex.length != 7) return Fortis.error.NotColorCode();
    if (isNaN(parseInt(hex, 16))) return Fortis.error.NotColorCode();
    let rgb = {};
    rgb.r = parseInt(hex.slice(1, 3), 16);
    rgb.g = parseInt(hex.slice(3, 5), 16);
    rgb.b = parseInt(hex.slice(5, 7), 16);
    return rgb;
}

Fortis.util.HSVToRGB = function (hsv) {
    if (!Fortis.util.checkType(hsv, "object")) return Fortis.error.ArgTypeWrong();
    if (hsv.h == undefined || hsv.s == undefined || hsv.v == undefined) return Fortis.error.ArgTypeWrong();
    if (!(hsv.h >= 0 && hsv.h <= 360 && hsv.s >= 0 && hsv.s <= 1 && hsv.v >= 0 && hsv.v <= 1)) return Fortis.error.ArgTypeWrong();
    let RGB = {};
    let max = hsv.v * 255;
    let min = max * (1 - hsv.s);
    let common = (max - min);
    if (hsv.h <= 60) {
        RGB.r = max;
        RGB.g = (hsv.h / 60) * common + min;
        RGB.b = min;
    } else if (hsv.h <= 120) {
        RGB.r = ((120 - hsv.h) / 60) * common + min;
        RGB.g = max;
        RGB.b = min;
    } else if (hsv.h <= 180) {
        RGB.r = min;
        RGB.g = max;
        RGB.b = ((hsv.h - 120) / 60) * common + min;
    } else if (hsv.h <= 240) {
        RGB.r = min;
        RGB.g = ((240 - hsv.h) / 60) * common + min;
        RGB.b = max;
    } else if (hsv.h <= 300) {
        RGB.r = ((hsv.h - 240) / 60) * common + min;
        RGB.g = min;
        RGB.b = max;
    } else {
        RGB.r = max;
        RGB.g = min;
        RGB.b = ((360 - hsv.h) / 60) * common + min;
    }
    return RGB;
}

Fortis.util.RGBToHex = function (rgb) {
    if (!Fortis.util.checkType(rgb, "object")) return Fortis.error.ArgTypeWrong();
    if (rgb.r == undefined || rgb.g == undefined || rgb.b == undefined) return Fortis.error.ArgTypeWrong();
    if (!(rgb.r >= 0 && rgb.r <= 255 && rgb.g >= 0 && rgb.g <= 255 && rgb.b >= 0 && rgb.b <= 255)) return Fortis.error.ArgTypeWrong();
    let code_text = "#";
    let RGB = { r: rgb.r, g: rgb.g, b: rgb.b }
    for (let element in RGB) {
        let parsed = parseInt(RGB[element], 16).toString();
        if (parsed.length == 1) {
            code_text += "0";
        }
        code_text += parsed;
    }
    return code_text;
}

Fortis.util.RGBToHSV = function (rgb) {
    if (!Fortis.util.checkType(rgb, "object")) return Fortis.error.ArgTypeWrong();
    if (rgb.r == undefined || rgb.g == undefined || rgb.b == undefined) return Fortis.error.ArgTypeWrong();
    if (!(rgb.r >= 0 && rgb.r <= 255 && rgb.g >= 0 && rgb.g <= 255 && rgb.b >= 0 && rgb.b <= 255)) return Fortis.error.ArgTypeWrong();
    let HSV = {};
    let max = Math.max(rgb.r, Math.max(rgb.g, rgb.b));
    let min = Math.min(rgb.r, Math.min(rgb.g, rgb.b));
    switch (max) {
        case rgb.r:
            HSV.h = (rgb.g - rgb.b) * 60 / (max - min);
            break
        case rgb.g:
            HSV.h = (rgb.b - rgb.r) * 60 / (max - min) + 120;
            break
        case rgb.b:
            HSV.h = (rgb.r - rgb.g) * 60 / (max - min) + 240;
            break
    }
    if (rgb.r == rgb.g && rgb.g == rgb.b) HSV.h = 0;
    if (HSV.h < 0) HSV.h += 360;
    if (max == 0) {
        HSV.h = 0;
        HSV.s = 0;
        HSV.v = 0;
    } else {
        HSV.s = (max - min) / max;
        HSV.v = max / 255;
    }
    return HSV;
}
~~~
結構長くなってしまいましたが以上です。
次回からは図形描画の処理を書いていきます。
グラデーションについては一通り図形描画の処理を書き終わってからにします。
それではまた次回。

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