【pixi.js】Electronで動くゲームプログラミングをしよう準備編

javascript

Electron ゲームプログラミング

こんにちは。継続の錬金術士なおキーヌです。

ブログ毎日更新は112日目になります。

pixi.jsを使って生javascriptでアクションゲームの衝突判定の基礎を完成させましたが、
そろそろちゃんとしたゲームを作っていきたくなったので一旦アクションゲームに関する記事はストップします!

今回からはWebブラウザで動かすゲームではなく、Electronを使ってゲームを作ります。

といってもElectronはGoogleChromeと同じ仕組みを採用しているのでhtmlを表示したりjavascriptが動きます。

そのうちElectronについての記事もあげますが、今回はpixi.jsに焦点を当てています。

Electronについては詳しくお話しませんので知りたい人はググってください。

後、JavaScriptの記述では色々と面倒な所もあるのでTypeScriptも併用していきます。

そして同じくしてTypeScriptの解説もあまりしないので各自調べてください。

Electronでゲームを作る準備

ゲーム作り 準備

今回ゲーム作りに使う技術は以下の通りです。

  • Node.js
  • GIT
  • Webpack
  • TypeScript
  • Electron
  • pixi.js

使用しているエディタはVisual Studio Codeです。
慣れているエディタを使いたい人はコンソールが使える状態にしておいてください。

以上になります。
Node.jsを触ったことが無い人は聞きなれないものばかりかもしれません。

これらを簡単に使えるように今回は土台を用意したのですが、
最低限Node.jsのインストールだけは済ませておいてください。

「Node.js インストール」

でググったらいっぱい出てくるのでそちらを参照してください。

今回はゲーム作りに焦点を当てた記事を書きたいのでpixi.js以外のライブラリや環境構築の説明は極力省きます。

それでは今回からゲームを作るための土台(ElectronやTypescript)を用意したので、
私のgithubからcloneかZIPを落としてウィンドウを出すところまでやりましょう。

git clone https://github.com/deNuoGit/electron_ts_pixi.git

GITを導入していない人はpixi.jsでゲーム作る土台から
「Clone or Download」というところから「Download ZIP」を選択して任意の場所に解凍してください。

ディレクトリ構成は以下のようになっています。

  • config
  • src/ts
  • .gitignore
  • LICENSE
  • README.md
  • index.html
  • package.json
  • tsconfig.json
  • tslint.json

中身を確認したら下記のコマンドのどちらかを実行すると、ElectronとTypeScriptとpixi.jsを一括でインストールできます。

npm install

インストールが終わったら次に下記のコマンドを順番に入力してください。
1つ1つ終わってから次を入力して実行する感じです。

npm run main-dev
npm run renderer-dev
npm run dev

真っ黒のウィンドウが出て来たら問題なく起動できています。

今回は準備ということでウィンドウを出すところまでにしておきます。

次回は黒い画面にマップチップを描写していきたいのですが、テクスチャをロードする話もしなければいけないので
一旦衝突判定の時と同じように四角をマップチップの代わりにして描写する予定です。

ここで終わるのは少し早いので、pixi.jsでウィンドウを出すところまで解説しようと思います。

src→ts→renderer→pixijsとディレクトリを辿っていってその中にある「index.ts」を開いてください。

以下のソースが現れると思います

import * as PIXI from 'pixi.js'
import { SYSTEM } from '../../config/system';

class PIXI_MainProcess {

    /**
     * ----------------------------------------------------
     * エイリアス作成
     * ----------------------------------------------------
     *  */ 
    // pixi.jsアプリケーション
    private PIXI_Application = PIXI.Application
    // ローダー
    private PIXI_loader = PIXI.loader
    // リソースローダー
    private PIXI_resources= PIXI.loader.resources

    /**
     * ----------------------------------------------------
     * クラス変数
     * ----------------------------------------------------
     *  */ 
    // pixiアプリケーション生成
    private App:PIXI.Application = this.pixiApplicationCreate(SYSTEM.width,SYSTEM.height);

    // コンストラクタ
    constructor() {

        // 初期化プロセス
        this.initializePIXI();
    }

    // 初期化プロセス
    private initializePIXI() {

        // ピクセル倍率変更
        this.pixelScaleUP();

        // htmlのbodyにPIXIAPPを追加
        document.body.appendChild(this.App.view);

    }

    // PIXIアプリケーションオブジェクト作成
    private pixiApplicationCreate(screenWidth:number,screenHeight:number) {
        return new this.PIXI_Application({
            width:screenWidth,
            height:screenHeight,
            antialias: false,
            transparent: false,
            resolution: 1
            }
        );
    }

    // ピクセル倍率を2倍に変更
    private pixelScaleUP() {
        this.App.renderer.roundPixels = true;
        this.App.renderer.resize(SYSTEM.width, SYSTEM.height);
        this.App.stage.scale.set(2,2);
        this.App.stage.interactive = true;
        PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
    }


    // // セットアップ関数
    private setup(app:PIXI.Application) {

        app.ticker.add(delta => this.gameloop(delta));

        // // .load()は「undifinedを指定して何も返さない」としなければtslintでエラーが出る
        // // void型にして下記を消すとクラス変数がnullにされてしまうのでこれで対応
        // return undefined;
    }

    // メインループ
    private gameloop(delta:number) {
    }
}

export default PIXI_MainProcess;

const GameFrame:PIXI_MainProcess = new PIXI_MainProcess;

今までとは見慣れないソースコードになっていますが、始めなので1つ1つ見ていきましょう。
TypeScriptではありますが、javascriptを読めれば大体は読めると思います。

ライブラリ・モジュールの読み込み

import * as PIXI from 'pixi.js'
import { SYSTEM } from '../../config/system';

1行目は見ての通りpixi.jsを読み込んでますね。

今まではhtmlのscriptタグでcdnを読み込んでいましたが、Node.jsでインストールしていれば
このようにライブラリを呼び出すことが出来ます。

2行目は読込先が2つ上のディレクトリにあるconfigディレクトリの中にあるsystem.tsファイルを読み込んでいます。

参照しているファイルを開いてもらうとわかるのですが、ウィンドウのサイズ等の指定をしているファイルです。

system.tsでexportしている部分を、importの後に波カッコで呼び出すことが出来ます。

数値は直接指定(リテラル)してもいいですが、後々変更しなければいけなくなったときに
ソースコードの中から探し出すのは時間の無駄なので決まっている数値などは1カ所でまとめてしまうのが定石です。

クラスの作成

class PIXI_MainProcess {
  ~~~省略~~~
}

TypeScriptでは「class クラス名 {}」でクラスを作成することが出来ます。

オブジェクト指向系のプログラミング言語では普通にある機能ですが
javascriptはクラスという概念が存在しませんのでTypeScriptで使えるようにされています。

TypeScriptは結果的にwebpackというツールでjavascriptに変換されるので
クラスという機能は存在しなくなりますが、疑似的にクラスのような挙動に変換してくれます。

TypeScriptでは基本的にClassを作って他のプログラミング言語のようにソースコードが書けるので
javascriptの自由すぎる書き方を縛って、ある程度誰が読んでもわかりやすいように設計されています。

javascriptは本当にツギハギのように機能を付け加えられている言語なので
収拾がつかなくなってしまい、Microsoftはしびれを切らして普通のプログラミング言語のように書ける
TypeScriptを作り出しました。

個人的にはすごく使いやすくていいのですが、一々コンパイルして動かさなきゃいけないというのが面倒ですね。

C言語とかなら基本なのですが、Web言語で生きてきたので割りと面倒に感じます。

いっそのことTypeScriptのコードでそのまま動くブラウザが普及してくれればいいんですけどね。

というかもうそろそろjavascriptという言語を廃止してちゃんとした言語に切り替わってほしいものです。

エイリアスの作成

    /**
     * ----------------------------------------------------
     * エイリアス作成
     * ----------------------------------------------------
     *  */ 
    // pixi.jsアプリケーション
    private PIXI_Application = PIXI.Application
    // ローダー
    private PIXI_loader = PIXI.loader
    // リソースローダー
    private PIXI_resources= PIXI.loader.resources

特に語ることはないのですが、結構重要なので記載しておきます。

一番最初にライブラリとして読み込んだpixi.jsを、プライベートなクラス変数に代入して使えるようにしています。

なぜこのようなやり方をすると言うと、もしライブラリのアップデートをしたときに
ライブラリ側のクラスやメソッドの名前が変わってしまったときにエイリアスを使用していないと
全てのソースコードを書き換えなければいけないからです。

最初にエイリアスを作っておけば、変更されても1カ所変えるだけで済みます。

この考え方はimportの項目でも言ったリテラルを使わず変数にして設定を一カ所にまとめるのと同じ考え方ですね。

コンストラクタ

    // コンストラクタ
    constructor() {

        // 初期化プロセス
        this.initializePIXI();
    }

コンストラクタとは、いわゆるクラスの初期化ですね。
クラスのインスタンスを作ったときに自動的に呼び出されるメソッドです。

最初に実行したい処理などを基本的には書き込みます。

pixi.jsで言えばステージを用意したりが基本でしょうか。

初期化プロセス

    // 初期化プロセス
    private initializePIXI() {

        // ピクセル倍率変更
        this.pixelScaleUP();

        // htmlのbodyにPIXIAPPを追加
        document.body.appendChild(this.App.view);

    }

コンストラクタの中で呼び出しているメソッドですね。

コンストラクタに直で処理を書き込んでもいいのですが見通しが悪くなるので、
一連の処理は1つの関数にまとめて呼び出すようにした方が良いでしょう。

pixi.jsのアプリケーションオブジェクトの生成

    // PIXIアプリケーションオブジェクト作成
    private pixiApplicationCreate(screenWidth:number,screenHeight:number) {
        return new this.PIXI_Application({
            width:screenWidth,
            height:screenHeight,
            antialias: false,
            transparent: false,
            resolution: 1
            }
        );
    }

pixi.jsを使ったゲームの土台となる部分です。

引数に横幅と高さを渡していますね。

中身で設定しているのはゲーム画面の大きさや、画面にアンチエイリアシングをかけるのかの設定です。

ドットをくっきりとしたい時にはアンチエイリアシングを切っておくとよいです。

ピクセル倍率を2倍に変更

    // ピクセル倍率を2倍に変更
    private pixelScaleUP() {
        this.App.renderer.roundPixels = true;
        this.App.renderer.resize(SYSTEM.width, SYSTEM.height);
        this.App.stage.scale.set(2,2);
        this.App.stage.interactive = true;
        PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
    }

ドットゲーを作る際にとても重要になってくるのがここです。

昔のゲームは解像度がすごく低く、現代の解像度でファミコンのゲームをやろうとすると
ものすごく小さくなって見辛くなってしまうのでピクセル数の表示を2倍にしてやらないといけません。

あえてドットバイドットにした方が良い時もありますが、RPG等は2倍率にした方がいいでしょう。

というのも2倍にしないとドット絵を描く作業がすさまじく辛くなるからです。

SFC風のゲームを作りたい!となるとこの記述は必須になります。

この記述は弄ることが無いので基本的には触らないようにしてください。

やっていることは表示するためのレンダラーというものを小さい解像度に指定し、
それを2倍にスケーリングしてスケールモードをニアレストに変更するとドット絵がくっきりしながら2倍のサイズで表示されます。

表示は2倍になっていますが、内部では等倍のままなのでソースコードの数値は2倍にしなくても大丈夫です。

セットアップとメインループについて

このソースコードは今は使っていません。

メインループに関しては今までの講座を見てくれた人はわかっていると思いますが、
ゲームを作るには必須のループ処理になります。

セットアップ関数は主に画像の読み込みなどを記載していく予定です。

作ったクラスの生成

export default PIXI_MainProcess;

const GameFrame:PIXI_MainProcess = new PIXI_MainProcess;

export defaultは外部ファイル化して別ファイルからクラス等を読み込ませるための設定ですが
現在はこのファイルのみで動いているのであまり気にしなくても大丈夫です。

ソースコードが膨れ上がってきた時に切り分けるときはこのようにクラスファイルとして1つにまとめて、
外部から読み込めるようにするのが基本的なオブジェクト指向プログラミングとなります。

最後に、一番重要なのがクラスからのインスタンスの生成ですね。

ここまでに気付いた方はいると思うのですが、変数の隣コロンがついてnumberやらクラス名やらが付いていると思います。

型情報を記載しなけ得ればいけない言語を触ったことがある人はすぐに理解できると思いますが、
Web系言語ばかり触っていると意外と使わない型指定です。

これはそれ以外の型の情報が入らないようにするためのものです。

例えば数値しか入らない変数に文字列が入ってしまうのを防いだりといった感じです。

もしコンパイルする時に違う数値型に文字列型の情報が入ってしまった場合コンパイルエラーを起こしてくれます。

少し面倒ではありますが、バグを引き起こすのを減らすためには必ずやらなければいけません。

──今まではjavascriptで書いていましたが言語仕様的に作っていると辛くなってくるので
今回はTypeScriptを採用していますが、慣れていないと意味が分からないと思います。

ちょっと書くぐらいなら生javascriptでもいいですがゲームを作ったりアプリを作ったりする場合
javascriptで組んでいては絶対に見通しの悪いコードが出来上がってしまいます。

今のうちにTypeScriptに慣れておくと良いでしょう。

私もそこまで慣れていないのですが使えているので実際に使いながら覚えていくのがベストです。

それでは。