libGDXでActorクラスを使う場合のおまじない

libGDXでActorクラスを使う際には、draw()メソッドの中に以下の2行を記述した方が良いという話です。

```java
public void draw (Batch batch, float parentAlpha) {

	Color color = getColor();
	batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);


	//描写処理
}
```


##Actorとは
libGDXのStage内で画像を描写する際には主にImageクラスを使うと思います。
ActorとはそのImageクラスの大元ともいうべきクラスです。
ImageクラスはActorクラス(正確にはActorを継承したWidgetクラス)を継承して作成されています。
そのためActorでもImageと同じく画像描写ができます。

例えば、test.pngという画像![test.png](https://qiita-image-store.s3.amazonaws.com/0/92839/e6286d1b-927e-70ee-c6a3-c37a06e2a8e2.png)を描写する際、Imageでは以下のように記述しますが、

```java
final Texture textureTest = new Texture(Gdx.files.internal(test.png));

Image image=new Image(textureTest);
image.setBounds(0,0,64,64);

stage.addActor(image);
```

Actorでは以下のように記述すると同じことができます。

```java
final Texture textureTest = new Texture(Gdx.files.internal(test.png));

Actor actor=new Actor()
{
	public void draw (Batch batch, float parentAlpha) {
		batch.draw(textureTest ,getX(),getY(),getWidth(),getHeight());
	}
};
actor.setBounds(0,0,64,64);

stage.addActor(actor);
```

Actorクラスは内部に何も実装されていない空クラスなので全ての処理を自分で記述する必要がありますが、裏を返せば手動で細かい部分までいじれるということです。
例えば、以下のようにして画像と文字を両方同時に描写したりできます。

```java
final Texture textureTest = new Texture(Gdx.files.internal(test.png));

final BitmapFont font = new BitmapFont();//追加部分
font.setColor(0.0f, 0.0f, 0.0f, 1.0f);//追加部分

Actor actor=new Actor()
{
	public void draw (Batch batch, float parentAlpha) {
		batch.draw(textureTest ,getX(),getY(),getWidth(),getHeight());

		font.draw(batch,"E",getX(),getY()+getHeight());//追加部分
	}
};
actor.setBounds(0,0,64,64);
stage.addActor(actor);
```

描写結果
![test05.png](https://qiita-image-store.s3.amazonaws.com/0/92839/e8386acc-0c43-6a21-321a-df0704b96963.png)


細部まで自分で細かく設定したい人はActorを、細かい部分の設定はLibGDXに任せたい人はImageを使うことになるでしょう。


##本題
さて、本題に戻りましょう。
まずはImageとActorを両方使って画面に描写を行ってみます。

```java
final Texture textureTest = new Texture(Gdx.files.internal(test.png));

Image image=new Image(textureTest);
image.setBounds(100,100,64,64);
stage.addActor(image);


Actor actor=new Actor()
{
	public void draw (Batch batch, float parentAlpha) {
		batch.draw(textureTest ,getX(),getY(),getWidth(),getHeight());
	}
};
actor.setBounds(200,100,64,64);
stage.addActor(actor);
```

描写結果
![test01.png](https://qiita-image-store.s3.amazonaws.com/0/92839/a09660dd-1118-0897-33f0-86d5848876fd.png)


特に問題なく描写されました。次にImageクラスに色味を付けてみます。
Imageクラスでは、setColorメソッドを使って画像に色合いをつけることができます。
ゲームなどで敵味方識別のためにユニットを色分けをする場合に使います。

```java
final Texture textureTest = new Texture(Gdx.files.internal(test.png));

Image image=new Image(textureTest);
image.setBounds(100,100,64,64);
image.setColor(0.8f,0,0,1f);//追加部分
stage.addActor(image);


Actor actor=new Actor()
{
	public void draw (Batch batch, float parentAlpha) {
		batch.draw(textureTest ,getX(),getY(),getWidth(),getHeight());
	}
};
actor.setBounds(200,100,64,64);
stage.addActor(actor);
```

描写結果
![test02.png](https://qiita-image-store.s3.amazonaws.com/0/92839/75a044cb-ad76-7f2f-1458-434505140729.png)

なぜかImage だけではなくActor にまで赤みが付いてしまいました。

##原因を探る
原因を探るためにImageクラスのdraw()メソッドを見てみましょう。

```java
public void draw (Batch batch, float parentAlpha) {
	validate();

	Color color = getColor();//ここに注目
	batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);//ここに注目

	float x = getX();
	float y = getY();
	float scaleX = getScaleX();
	float scaleY = getScaleY();

	if (drawable instanceof TransformDrawable) {
		float rotation = getRotation();
		if (scaleX != 1 || scaleY != 1 || rotation != 0) {
			((TransformDrawable)drawable).draw(batch, x + imageX, y + imageY, getOriginX() - imageX, getOriginY() - imageY,
					imageWidth, imageHeight, scaleX, scaleY, rotation);
			return;
		}
	}
	if (drawable != null) drawable.draw(batch, x + imageX, y + imageY, imageWidth * scaleX, imageHeight * scaleY);
}
```

4～5行目で、Imageの持つColor情報を取得してbatchに設定しています。
これによりbatchを使って赤みが付いた描写ができるわけですね。
Javaではクラスは参照渡しになるので、draw()内でbatchの情報が変更されたら呼び出し元に戻っても情報は変更されたままです。
つまり、あるクラスでsetColorを使って色情報を変更した後、何もしないと次に描写するクラスでもその色情報が残ったまま描写が行われてしまうのです。
そこで、最初のコードで示したように、ActorでもImageと同じ様にbatchに自身の色情報を上書きして描写を行うと言うわけです。

```java
Actor actor=new Actor()
{
    public void draw (Batch batch, float parentAlpha) {

        Color color = getColor();
        batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);

        //描写処理
        batch.draw(textureTest ,getX(),getY(),getWidth(),getHeight());
    }
};
```

##大した問題ではないと思っている人も
色合いを付ける処理は使っていないので対処は不要だと考えている方も、なるべくは対策コードを記述することをお勧めします。
というのも、Image以外にも様々なクラスのdraw()メソッド内でbatchの色情報が弄られるからです。
色みをつけた場合だと因果関係が分かりやすいのですぐに原因を特定できますが、それ以外の状況で発生すると解決が困難だったりします。

例えば、ScrollPaneというスクロールができるレイアウトがあります。
このScrollPaneのdrawメソッド内では、スクロールバーのフェードアウト処理をするためにbatchの透明度情報を変更しています。
そのためScrollPaneを対策をしていないActorと一緒に使用すると、ScrollPaneで透明度の情報が変更されたbatchがActorの描写でもそのまま使われてしまい、Actorが徐々に透明になってフェードアウトしてしまうという事象が発生してしまいます。
自分の設定が起因して起こるのではなく各クラスの内部処理によって発生する事象ですから、身に覚えがないため原因特定は困難となるでしょう。

<hr>

参考にしたページ
<a href="https://libgdx.badlogicgames.com/" target="_blank">libGDX 公式ページ</a>
<a href="http://westplain.sakuraweb.com/translate/libGDX/" target="_blank">上記の日本語訳サイト(非公式)</a>