【PixiJS 覚書】第二回 PIXI.Graphics（と、PIXI.DisplayObjectの掘り下げ）

##今回の主題
前回は基本のキ、PIXI.Applicationのインスタンスの生成から、描画の土台となる`view`、`stage`、PIXI.Containerの仕組みなどをざっと流しました。

今回は、PIXI.DisplayObjectの派生クラスの一つであるPIXI.Graphicsについて具体的な使い方を見ていきます。
また、PIXI.Graphicsを通して描画オブジェクトの基本クラスであるPIXI.DisplayObjectの機能についても軽く触れます。

##PIXI.Graphics
名前の通り「Graphics（図形）」を扱うクラスです。
リファレンス：[PIXI.Graphics](https://pixijs.download/dev/docs/PIXI.Graphics.html)
####描画メソッドをざっくり
どのような図形が描けるのかをリファレンスからざっと拾い出してみましょう。
名前が「draw（図形）」となっているメソッドが描画系のメソッドです

* drawRect　（四角）
* drawRoundedRect　（角丸四角）
* drawCircle （円）
* drawEllipse　（楕円）
* drawPolygon　（多角形）

基本的な図形は揃ってますね。
ところで、リファレンスを眺めていて気付いた方もいるかと思いますが、draw系メソッドには他にも

* drawChamferRect　（角を落とした四角）
* drawRegularPolygon　（正多角形）
* drawRoundedPolygon　（角丸の多角形）
* drawStar 　（星型）

等の使い勝手の良さそうなメソッドが記載されています。
が、しかし！
各メソッドの説明をよくよく読むと、最後に以下の一文があります。
>Note: Only available with @pixi/graphics-extras.
（pixi/graphics-extrasでのみ利用可能）

[pixi/graphics-extras](https://www.npmjs.com/package/@pixi/graphics-extras)というのはnpmで展開されている拡張機能です。
（第一回冒頭で宣言したように、ここで「npmとは～」のような話はしません。とりあえず上記のメソッドは標準機能では使えないということです。）
この手の「記載されているけど標準では使えない」というのはちょいちょい出てきますので、リファレンスの説明は一通り読むように心がけましょう（**自戒**）。

####実際に描画してみよう
さて、本題に戻って図形を描きます。
とりあえず、ただの一本線、長方形、丸、多角形、を順に描いてみます。

```JavaScript:graphics_sample
// Create our application instance
var app = new PIXI.Application({
    width: 600,
    height: 400,
    backgroundColor: 0x1199aa
});
document.body.appendChild(app.view);

// create Graphics
const graphics = new PIXI.Graphics();

//　straight line
graphics.moveTo(540, 40);
graphics.lineStyle(3, 0x000000);
graphics.lineTo(100, 360);
graphics.lineStyle();

// rectangle + fill
graphics.beginFill(0xffee88);
graphics.drawRect(10, 10, 180, 100);
graphics.endFill();

// circle + line + fill
graphics.lineStyle(2, 0x1111ff, 1, 1);
graphics.beginFill(0x22dd22);
graphics.drawCircle(160, 120, 70);
graphics.endFill();

// polygon + line + fill
const path = [230, 220, 380, 180, 420, 320, 400, 370, 240, 370];
graphics.lineStyle(5, 0xffffff);
graphics.beginFill(0x3500FA);
graphics.drawPolygon(path);
graphics.endFill();
graphics.lineStyle();

app.stage.addChild(graphics);
```
「サンプルコードの結果」
![graphics.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107226/60df5cc5-9d10-7145-a328-475bb9a1500b.png)

##サンプルコードの解説
第一回で書いたように、まずはPIXI.Applicationのインスタンスを生成し、HTMLドキュメントのbodyに`appendChild`しています。
PixiJSを利用する際の基本の手続きです。
**次回からはこの記述は省略します。**

さて、今日の主題はここからです。

####PIXI.Graphicsインスタンスの生成

```JavaScript:
// create Graphics
const graphics = new PIXI.Graphics();
```
PIXI.Graphicsのインスタンスを生成しています。
コンストラクタの引数は空ですが、必要に応じて「[PIXI.GraphicsGeometry](https://pixijs.download/dev/docs/PIXI.GraphicsGeometry.html)」を引数として渡せます。
が、しかし。
PIXI.GraphicsGeometryより先はWebGLによる描画処理の深淵へと繋がっていそうなので、深入りはしません。くわばらくわばら。
とりあえず図形をちょちょっと描く分には空で良いです。
このPIXI.Graphicsインスタンスに対し、メソッドをなんやかんやして図形を描き込み、最後に`app.stage`に`addChild`します。
（ちなみに最初に`addChild`して、後からあれこれ描画しても同じです。）

####まずは直線
```JavaScript:
//　straight line
graphics.moveTo(540, 40);
graphics.lineStyle(3, 0x000000);
graphics.lineTo(100, 360);
graphics.lineStyle();
```

・moveTo(x, y)
CPU依存の2D描画処理であるcanvas2Dを触ったことのある方なら懐かしさを覚えるかもしれません。
PIXI.Graphicsで線を引く際は、まず描画開始位置を指定する必要があり、moveToメソッドは開始位置を指定の座標へ移動するメソッドです。
これから描く紙の上で筆先を狙った位置へ合わせるイメージと言って良いでしょう。
これで`view`の右上辺り（x:540, y:40）へ筆先の狙いを定めました。

・lineStyle(width, color, alpha, alignment, native)
または
・lineStyle(options)
描線のスタイルを設定します。
引数の渡し方には設定内容を個別に渡す方法と設定をオブジェクトとして一まとめにして渡す方法の二種類があります。
オブジェクトで渡す方が設定できる項目が少し増えますが、簡単なのは必要な設定を個別に渡す方かと思います。
今回はwidth（線の太さ：3px）とcolor（色：黒）だけを設定しました。

・lineTo(x, y)
先ほどmoveToメソッドで設定した描画開始位置から指定の位置までズビャッと線を引きます。
今回は画面の左下辺り（x;100, y:360）まで線を引きました。
ちなみにここから更に`lineTo`を行うと、この線の終了位置から次の指定位置までの線が引かれます。
線を引いたのだから筆先の位置も動いているわけです。自然ですね。

その後、再び空の引数でlineStyleメソッドを実行してlineStyle情報を初期化しています。
というのも、PIXI.Graphicsは内部プロパティ`_lineStyle`にlineStyle情報を保持するようになっており、うっかり続けて他の図形を描画すると不要な線が描画されてしまうからです。
![ukkari.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107226/0379390a-5b6d-5808-37e4-858fbd44155c.png)
（うっかり未初期化のまま次の四角形を描画すると、次に描画する四角形のフチにまで黒線が入る）

####四角形と塗りつぶし

```JavaScript:
// rectangle + fill
graphics.beginFill(0xffee88);
graphics.drawRect(10, 10, 180, 100);
graphics.endFill();
```

・beginFill(color, alpha)
および
・endFill()
`beginFill`メソッドは「ここから先の図形描画は中身を塗り潰しますよ」という宣言です。
引数としてはcolor（色）とalpha（不透明度）を渡せます。
今回は色を黄色とし、不透明度は省略しました。
不透明度を設定する際には「0（透明）～1（不透明）」の範囲で設定します。
endFillは塗り潰しの終わりです。塗りつぶしを終えるだけなので引数はありません。

・drawRect(x, y, width, height)
指定座標から指定の幅と高さで四角形を描画します。まんまですね。

####円と線と塗りつぶし
```JavaScript:
// circle + line + fill
graphics.lineStyle(2, 0x1111ff, 1, 1);
graphics.beginFill(0x22dd22);
graphics.drawCircle(160, 120, 70);
graphics.endFill();
graphics.lineStyle();
```
どんどん行きましょう。
今度は図形を線で描画した上で、更に中身を塗り潰します。
やっていることは上と同じで`linStyle`メソッドで描線のスタイルを設定し、`beginFill`メソッドで塗りつぶしの色を指定しつつ塗りつぶし開始を宣言しています。
`lineStyle`については今回はalphaとalignmentも指定してみました。
alignmentは線を引く位置を図形の内側、図形上、外側のどこに引くか、です。
リファレンスによれば(0 = inner, 0.5 = middle, 1 = outer)とあるので、0（内側）～1（外側）の範囲で指定するのが自然かと思われますが、試しに10を与えてみたところ、図形の10px外側に線が描画されました。

・drawCircle(x, y, radius)
指定した座標を中心として、指定した半径の円を描きます。どぅってことない。

その後、`endFill`で塗りつぶしの終了を宣言し、`linStyle`でラインスタイル情報を初期化しています。

####多角形

```JavaScript:
// polygon + line + fill
const path = [230, 220, 380, 180, 420, 320, 400, 370, 240, 370];
graphics.lineStyle(5, 0xffffff);
graphics.beginFill(0x3500FA);
graphics.drawPolygon(path);
graphics.endFill();
graphics.lineStyle();
```

・drawPolygon(...path)
多角形を描画するのですが、形が不規則なだけに引数の指定の仕方にちょっとだけ注意が必要です。
引数として多角形の頂点座標（x、y）の集合を複数渡す必要があるのですが、この引数の渡し方が色々とあります。
今回の場合は頂点座標をただの数値の配列として渡しました。

```
const path = [230, 220, 380, 180, 420, 320, 400, 370, 240, 370];
```
この配列は、
(x:230, y:220) (x:380, y:180) (x:420, y:320) (x:400, y:370) (x:240, y:370)
の座標の集合として解釈されます。

他の方法としては、
・各頂点座標を[PIXI.Point](https://pixijs.download/dev/docs/PIXI.Point.html)という座標を表すクラスのインスタンスとし、その配列で渡す。
・[PIXI.Polygon](https://pixijs.download/dev/docs/PIXI.Polygon.html)という多角形情報のクラスのインスタンスとして丸ごと渡す
等があります。
状況に応じて使いやすい方法を選べるように把握しておきましょう。

####最後にaddChild
```JavaScript:
app.stage.addChild(graphics);
```

忘れずに。
`stage`に`addChild`しなければ幾ら`graphics`をこねくり回しても何も描画されません。だいじ。

####一筆書きにチャレンジ
駆け足で描画メソッドを流してきましたが、これらのメソッドには一つ面白い特徴があります。
これらのメソッドは、その戻り値として自身（this）をリターンするという点です。
つまりメソッドチェーンが使えるということですね。やってみましょう!

```JavaScript:
graphics.moveTo(100, 100).lineStyle(3, 0x000000).lineTo(280, 120).arc(280, 180, 60, Math.PI * 3 / 2, Math.PI * 3).bezierCurveTo(250, 100, 400, 10, 490,180);
```
長っ！！
結果、こうなります。
![hitohude.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107226/79d2101f-efce-e83e-8cf4-10178153aaff.png)

応用として円ではなく弧（`arc`メソッド）と、ベジエ曲線（`bezierCurveTo`メソッド）を使ってみました。
リファレンスに普通に乗っている標準メソッドですので、確認しておきましょう。

[Examples](https://pixijs.io/examples/#/graphics/advanced.js)には他にも色々とサンプルが用意されています。
他にどういうことができるのか、いちど目を通しておくのも良いですね。

##PIXI.DisplayObjectをもう少し掘り下げよう
ここまでPIXI.Graphicsの使い方をざざっと追ってきました。

ここで一度、原点に戻り、PIXI.Graphicsのクラスツリーを確認してみます。
>・PIXI.DisplayObject
　・PIXI.Container
　　・PIXI.Graphics

PIXI.GraphicsはPIXI.Containerの派生クラスであり、PIXI.ContainerはPIXI.DisplayObjectの派生クラスでした。
つまり、PIXI.GraphicsはPIXI.ContainerやPIXI.DisplayObjectのメソッドやプロパティも継承しているわけです。
あるというなら使いましょう。
今回はPIXI.DisplayObjectのプロパティの中でも基本的かつ利用頻度が高そうな`position`プロパティ、`scale`プロパティ、`rotation`プロパティと`pivot`プロパティを紹介します。
これらはPIXI.DisplayObjectの派生クラスならばどれも継承しているので、どれでも利用可能です。

####positon
PixiJSには(x, y)の平面座標を表す[PIXI.ObservablePoint](https://pixijs.download/dev/docs/PIXI.ObservablePoint.html)クラスが用意されており、PIXI.DisplayObjectはそのインスタンスを`position`プロパティに持っています。
その意味するところは`addChild`先となる親コンテナの原点（初期値は左上（0, 0））に対する相対座標です。

```JavaScript:
graphics.position.x = 100;
graphics.position.y = 100;
```
![position.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107226/db7727c6-bd81-a026-0a13-89c07fd9a9ba.png)

なお、毎回`dispObj.position.x`、`dispObj.position.y`のように「position」を介して記述するのはダルいので、ゲッター、セッターが用意されています。更にPIXI.ObservablePointは[`set`メソッド](https://pixijs.download/dev/docs/PIXI.ObservablePoint.html#set)を持っており、x,yの値を一度にセットできます。
以下どちらの記述も上記の記述と同じ結果となります。

```JavaScript:
graphics.x = 100;
graphics.y = 100;

//または
graphics.position.set(100, 100);

//更にx、yが同値の場合はsetの引数を一つに省略できる
graphics.position.set(100);
```

####scale
拡大縮小を表すプロパティです。
scaleもpositionと同様にPIXI.ObservablePointとして情報を持っており、x方向、y方向を個別に拡大縮小できます。

```JavaScript:
graphics.scale.x = 3;
```
![scale.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107226/e5135278-6c39-e758-30f9-db0c7fed94c7.png)

`set`メソッドでx,yを一度にセットできるのは`position`と同様です。

####rotation（およびpivot）
描画物の回転です。
回転角度はラジアンで指定するので、たとえば30度回転ならば`Math.PI / 6`です。

```JavaScript:
graphics.rotation = Math.PI / 6;
```
![rotation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107226/8491fb1d-4966-96c8-d81d-e169f1ef70ab.png)

左上の角（0, 0）を軸として30度回転しました。
しかし場合によっては、画像の中央や他の部分を軸として回転したい場合もあると思います。
その場合には`pivot`プロパティを変更します。
`pivot`プロパティも`position`や`scale`と同様にPIXI.ObservablePointオブジェクトとして情報を持っていますので、`set`メソッドが使えます。
ここで一つ注意すべきなのが、**`pivot`は単なる回転の軸というだけでなく、その描画オブジェクトを親コンテナに`addChild`する際の基準点となる**という点です。
つまり、`pivot`を変更すると、そのオブジェクト全体がズレます。
そのため、ズレた分をpositionで補正してあげる必要があります。

```JavaScript:
graphics.pivot.set(300, 200);  // 基準点をgraphicsの中央に移動
graphics.rotation = Math.PI / 6;  // 中央を軸として30度回転
graphics.position.set(300, 200);  // 中央を基準としてコンテナにaddされるので、その分だけ位置をズラす
```
![pivot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107226/b28aa699-c238-292e-6f55-f77ff3ee546c.png)
中央を回転軸として30度回転できました。


####まとめ
・`position`プロパティは親コンテナへの相対位置を設定する
・`scale`プロパティは縦横への拡大縮小
・`rotation`プロパティは回転、`pivot`プロパティは回転軸であり親コンテナへ追加する基準点

これらはPIXI.DisplayObjectの派生クラスでは常についてまわるのでここでしっかり認識しておきましょう。


----
今回はここまで！

次回、いよいよPIXI.Spriteでスプライトを扱う予定だけど予定はどこまでも予定です！