Live2DWallpaperに背景を表示する

# 開発環境
Live2D Android SDK 2.0.05_1のliveWallpaperプロジェクト
MacでEclipse4.2

# Androidの壁紙の背景設定
「Live2D Android SDKで壁紙の設定方法がわからない」と困っている人がいたのでちょっとやり方を調べてみました。
　→　[How to set image as background in Live Wallpaper?](http://community.live2d.com/discussion/235/android-how-to-set-image-as-background-in-live-wallpaper)

SurfaceViewを使っているので背景表示もOpenGLでやる必要があります。
ちなみにLive2DのAndroid版はOpenGL1.0を使ってました（OpenGL2.0を使ってないのは、古い端末に対応するための名残り？）

SampleApp1プロジェクトからコピーして、比較的簡単に実装する方法がわかったのでメモしておきます。
liveWallpaperでのやり方を書きますが、SDK内のsimpleプロジェクトも同様な手順で背景表示できます。

# liveWallpaperの修正手順
１）sample/sampleApp1からsrc/jp/live2d/utils/androidをコピーする

２）sample/sampleApp1からsrc/jp/live2d/sample/LAppDefine.javaをコピーする

３）sample/sampleApp1からassets/image/back_class_normal.pngをコピーする

![img_packageexplorer.png](https://qiita-image-store.s3.amazonaws.com/0/15028/b30a0ad0-8abc-d38a-da0a-e48dd59bd0db.png)

４）LiveWallpaperService.javaを修正（修正箇所はAdd Codeとコメント入れました）

```java:LiveWallpaperService.java
/**
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */
package jp.live2d.sample;


import jp.live2d.utils.android.FileManager;
import android.view.MotionEvent;
import net.rbgrn.android.glwallpaperservice.*;

public class LiveWallpaperService extends GLWallpaperService {

	public LiveWallpaperService() {
		super();
	}

	public Engine onCreateEngine() {
		MyEngine engine = new MyEngine();
		return engine;
	}

	class MyEngine extends GLEngine {
		Live2DRenderer renderer;

		public MyEngine() {
			super();
			// handle prefs, other initialization
			renderer = new Live2DRenderer(getApplicationContext());
			setRenderer(renderer);
			setRenderMode(RENDERMODE_CONTINUOUSLY);
			// Add Code Start
			FileManager.init(getApplicationContext());
			// Add Code End
		}

		@Override
		public void onTouchEvent(MotionEvent event) {
			switch (event.getAction()) {
			case MotionEvent.ACTION_DOWN:
				break;
			case MotionEvent.ACTION_UP:
				renderer.resetDrag();
				break;
			case MotionEvent.ACTION_MOVE:
				renderer.drag(event.getX(), event.getY());
				break;
			case MotionEvent.ACTION_CANCEL:
				break;
			}
		}

		public void onDestroy() {
			super.onDestroy();
			if (renderer != null) {
				renderer.release();
			}
			renderer = null;
		}
	}
}
```

５）Live2DRenderer.javaを修正（修正箇所はAdd Codeとコメント入れました）
　gl.glOrthof()を2回使い無理やりな気がするのでOpenGL的には正しくないかもですが、一応これでAndroidアプリが作れるかと思います。

```java:Live2DRenderer.java
/**
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */
 package jp.live2d.sample;

import java.io.IOException;
import java.io.InputStream;

import jp.live2d.android.Live2DModelAndroid;
import jp.live2d.android.UtOpenGL;
import jp.live2d.framework.L2DPhysics;
import jp.live2d.framework.L2DStandardID;
import jp.live2d.framework.L2DTargetPoint;
import jp.live2d.motion.Live2DMotion;
import jp.live2d.motion.MotionQueueManager;
import jp.live2d.utils.android.FileManager;
import jp.live2d.utils.android.SimpleImage;
import android.content.Context;
import android.content.res.AssetManager;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import net.rbgrn.android.glwallpaperservice.*;

public class Live2DRenderer implements GLWallpaperService.Renderer
{
	Context con;

	Live2DModelAndroid	live2DModel ;
	Live2DMotion motion;
	MotionQueueManager motionMgr;
	L2DTargetPoint dragMgr;
	L2DPhysics physics;

	final String MODEL_PATH = "epsilon/Epsilon.moc" ;
	final String TEXTURE_PATHS[] =
		{
			"epsilon/Epsilon.1024/texture_00.png" ,
			"epsilon/Epsilon.1024/texture_01.png" ,
			"epsilon/Epsilon.1024/texture_02.png"
		} ;
	final String MOTION_PATH="epsilon/motions/Epsilon_idle_01.mtn";
	final String PHYSICS_PATH="epsilon/Epsilon.physics.json";

	float glWidth=0;
	float glHeight=0;

	// Add Code Start
	private SimpleImage bg;// BackGround Image
	private float modelWidth = 0;
	private float aspect = 0;
	// Add Code End

	public Live2DRenderer(Context context)
	{
		con = context;
		dragMgr=new L2DTargetPoint();
		motionMgr=new MotionQueueManager();
	}


	public void onDrawFrame(GL10 gl) {
        // Your rendering code goes here
		gl.glMatrixMode(GL10.GL_MODELVIEW ) ;
		gl.glLoadIdentity() ;
		gl.glClear( GL10.GL_COLOR_BUFFER_BIT ) ;
		// Add Code Start
		bg.draw(gl);	// background image draw
		// Live2D model adjust
		gl.glScalef(2.4f, 2.4f, 2.4f); // scale(x, y, z)
		gl.glTranslatef(0.0f, -0.3f, 0.0f);	// position(x, y, z)
		gl.glOrthof(0 ,	modelWidth , modelWidth / aspect , 0 , 0.5f , -0.5f ) ;
		// Add Code End

		live2DModel.loadParam();

		if(motionMgr.isFinished())
		{
			motionMgr.startMotion(motion, false);
		}
		else
		{
			motionMgr.updateParam(live2DModel);
		}

		live2DModel.saveParam();

		dragMgr.update();

		float dragX=dragMgr.getX();
		float dragY=dragMgr.getY();
		live2DModel.addToParamFloat(L2DStandardID.PARAM_ANGLE_X, dragX*30);
		live2DModel.addToParamFloat(L2DStandardID.PARAM_ANGLE_Y, dragY*30);
		live2DModel.addToParamFloat(L2DStandardID.PARAM_BODY_ANGLE_X, dragX*10);

		physics.updateParam(live2DModel);

		live2DModel.setGL( gl ) ;

		live2DModel.update() ;
		live2DModel.draw() ;

    }

    public void onSurfaceChanged(GL10 gl, int width, int height) {
		gl.glViewport( 0 , 0 , width , height ) ;

		gl.glMatrixMode( GL10.GL_PROJECTION ) ;
		gl.glLoadIdentity() ;

		// Add Code Start
		modelWidth = live2DModel.getCanvasWidth();
		aspect = (float)width/height;
//		gl.glOrthof(
//				0 ,
//				modelWidth ,
//				modelWidth * height / width,
//				0 ,
//				0.5f ,	-0.5f
//				) ;
//		gl.glOrthof(0, modelWidth, modelWidth * height / width, 0, 0.5f, -0.5f);
		// background image adjust
		gl.glOrthof(-2.0f ,	2.0f , -2.0f ,2.0f , 0.5f , -0.5f ) ;
		// Add Code End

		glWidth=width;
		glHeight=height;
    }

    public void onSurfaceCreated(GL10 gl, EGLConfig config)
    {
    	AssetManager mngr = con.getAssets();
		try
		{
			InputStream in = mngr.open( MODEL_PATH ) ;
			live2DModel = Live2DModelAndroid.loadModel( in ) ;
			in.close() ;
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}

		try
		{
			// Add Code Start
			setupBackground(gl);
			// Add Code End

			//texture
			for (int i = 0 ; i < TEXTURE_PATHS.length ; i++ )
			{
				InputStream in = mngr.open( TEXTURE_PATHS[i] ) ;
				int texNo = UtOpenGL.loadTexture(gl , in , true ) ;
				live2DModel.setTexture( i , texNo ) ;
				in.close();
			}
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}

		try
		{
			InputStream in = mngr.open( MOTION_PATH ) ;
			motion = Live2DMotion.loadMotion( in ) ;
			in.close() ;

			in=mngr.open(PHYSICS_PATH);
			physics=L2DPhysics.load(in);
			in.close();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
    }

    /**
     * Called when the engine is destroyed. Do any necessary clean up because
     * at this point your renderer instance is now done for.
     */
    public void release() {
    }

    public void resetDrag()
    {
    	dragMgr.set(0, 0);
    }


    public void drag(float x,float y)
    {
    	float screenX=x/glWidth*2-1;
    	float screenY=-y/glHeight*2+1;

//    	Log.i("", "x:"+screenX+" y:"+screenY);

    	dragMgr.set(screenX,screenY);
    }

	/*
	 * BackGround Image Setting
	 * @param context
	 */
	private void setupBackground(GL10 context) {
		try {
			InputStream in = FileManager.open(LAppDefine.BACK_IMAGE_NAME);
			bg=new SimpleImage(context,in);
			bg.setDrawRect(
					LAppDefine.VIEW_LOGICAL_MAX_LEFT,
					LAppDefine.VIEW_LOGICAL_MAX_RIGHT,
					LAppDefine.VIEW_LOGICAL_MAX_BOTTOM,
					LAppDefine.VIEW_LOGICAL_MAX_TOP);

			// uv area
			bg.setUVRect(0.0f,1.0f,0.0f,1.0f);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
```
![img_wallpaper.png](https://qiita-image-store.s3.amazonaws.com/0/15028/ef76c26a-3d60-2c61-1d93-6bf3cb2a4d9f.png)

あとはassets配下に違う画像を入れて、LAppDefine.javaのBACK_IMAGE_NAMEの変更すれば背景画像を変えられます。

# おまけ
ちなみにSimpleプロジェクトの方だとソースは以下のように修正すると背景が表示されます。
（SampleApp1からコピーしてくるものは同じ）


```java:SampleActivity.java
package jp.live2d.sample;

import jp.live2d.Live2D;
import jp.live2d.utils.android.FileManager;
import android.app.Activity;
import android.os.Bundle;

public class SampleActivity extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        Live2D.init();

        FileManager.init(this.getApplicationContext());

        SampleGLSurfaceView 	view = new SampleGLSurfaceView(this) ;
        setContentView( view ) ;
    }

}
```

```java:SampleGLSurfaceView.java
package jp.live2d.sample;

import java.io.IOException;
import java.io.InputStream;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import jp.live2d.android.Live2DModelAndroid;
import jp.live2d.android.UtOpenGL;
import jp.live2d.util.UtSystem;
import jp.live2d.utils.android.FileManager;
import jp.live2d.utils.android.SimpleImage;
import android.content.Context;
import android.opengl.GLSurfaceView;

public class SampleGLSurfaceView extends GLSurfaceView
{
	private SampleGLRenderer		renderer ;
	private SimpleImage bg;// 背景の描画
	private float modelWidth = 0;
	private float aspect = 0;

	public SampleGLSurfaceView(Context context )
	{
		super(context);

		renderer = new SampleGLRenderer() ;
		setRenderer( renderer ) ;
	}


	class SampleGLRenderer implements Renderer
	{
		private Live2DModelAndroid	live2DModel ;
		private final String MODEL_PATH = "haru/haru.moc" ;
		private final String TEXTURE_PATHS[] =
			{
				"haru/haru.1024/texture_00.png" ,
				"haru/haru.1024/texture_01.png" ,
				"haru/haru.1024/texture_02.png"
			} ;

		@Override
		public void onDrawFrame(GL10 gl)
		{
			gl.glMatrixMode(GL10.GL_MODELVIEW ) ;
			gl.glLoadIdentity() ;
			gl.glClear( GL10.GL_COLOR_BUFFER_BIT ) ;
			// background draw
			bg.draw(gl);

			// Live2Dモデル調整
			gl.glScalef(2.8f, 2.8f, 2.8f);
			gl.glTranslatef(0.0f, -0.3f, 0.0f);
			gl.glOrthof(0 ,	modelWidth , modelWidth / aspect , 0 , 0.5f , -0.5f ) ;

			double t = (UtSystem.getUserTimeMSec()/1000.0) * 2 * Math.PI  ;// 1秒ごとに2π(1周期)増える
			double cycle=3.0;// パラメータが一周する時間(秒)
			double sin=Math.sin( t/cycle );// -1から1の間を周期ごとに変化する
			live2DModel.setParamFloat( "PARAM_ANGLE_X" , (float) (30 * sin) ) ;// PARAM_ANGLE_Xのパラメータが[cycle]秒ごとに-30から30まで変化する

			live2DModel.setGL( gl ) ;

			live2DModel.update() ;
			live2DModel.draw() ;
		}


		@Override
		public void onSurfaceChanged(GL10 gl, int width, int height)
		{
			// ビューポートはデバイスの幅と合わせる。画面全体に表示される。
			gl.glViewport( 0 , 0 , width , height ) ;

			// 簡易的にプロジェクション行列一つですべての変換を行う。
			gl.glMatrixMode( GL10.GL_PROJECTION ) ;
			gl.glLoadIdentity() ;

			modelWidth = live2DModel.getCanvasWidth();// モデラーで設定したキャンバス幅
			aspect = (float)width/height;
			//modelWidth=2400.0, aspect=0.6642066, modelWidth /aspect = 3613.33356218
			// 描画範囲の設定 引数はleft,right,bottom,topの順
//			gl.glOrthof(0 ,	modelWidth , modelWidth / aspect , 0 , 0.5f , -0.5f ) ;
			// background 用に調整
			gl.glOrthof(-2.0f ,	2.0f , -2.0f ,2.0f , 0.5f , -0.5f ) ;
		}


		@Override
		public void onSurfaceCreated(GL10 gl, EGLConfig config)
		{
			//  モデルの初期化
			try
			{
				// 背景の作成
				setupBackground(gl);


				InputStream in = getContext().getAssets().open( MODEL_PATH ) ;
				live2DModel = Live2DModelAndroid.loadModel( in ) ;
				in.close() ;

				for (int i = 0 ; i < TEXTURE_PATHS.length ; i++ )
				{
					InputStream tin = getContext().getAssets().open( TEXTURE_PATHS[i] ) ;
					int texNo = UtOpenGL.loadTexture(gl , tin , true ) ;
					live2DModel.setTexture( i , texNo ) ;
				}
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}

		/*
		 * 背景の設定
		 * @param context
		 */
		private void setupBackground(GL10 context) {
			try {
				InputStream in = FileManager.open(LAppDefine.BACK_IMAGE_NAME);
//				InputStream in = getContext().getAssets().open( LAppDefine.BACK_IMAGE_NAME ) ;
				bg=new SimpleImage(context,in);
				// 描画範囲。画面の最大表示範囲に合わせる
				bg.setDrawRect(
						LAppDefine.VIEW_LOGICAL_MAX_LEFT,
						LAppDefine.VIEW_LOGICAL_MAX_RIGHT,
						LAppDefine.VIEW_LOGICAL_MAX_BOTTOM,
						LAppDefine.VIEW_LOGICAL_MAX_TOP);

				// 画像を使用する範囲(uv)
				bg.setUVRect(0.0f,1.0f,0.0f,1.0f);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}


```