第 3 回 3Dゲームの作成

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

3-1. 参考文献

3-2. プログラム

flightsample.zip

src

MainActivity.java


package jp.ac.dendai.c.jtp.flightsample;
import android.app.Activity;
import android.os.Bundle;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.LinearLayout;
public class MainActivity extends Activity {
    private MyGLView glView;
    private ScoreView scoreView;
    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
    }
    @Override
    public void onStart() {
        super.onStart();
        scoreView = new ScoreView(this);
        glView = new MyGLView(this, scoreView);
        setContentView(glView);
        addContentView(scoreView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
    }
    @Override
    public void onResume() {
        super.onResume();
        glView.onResume();
        glView.threadstart();
    }
    @Override
    public void onPause() {
        super.onPause();
        glView.onPause();
        glView.threadpause();
    }
}

Constant.java


package jp.ac.dendai.c.jtp.flightsample;
public interface Constant {
    float MaxV = 0.2f;
    float EYEDIS = 2.0f;
    float boxsize = 2.0f;
    float[] red = {1.0f, 0.0f, 0.0f, 1.0f};
    float[] blue = {0.0f, 0.0f, 1.0f, 1.0f};
    float[] yellow = {1.0f, 1.0f, 0.0f, 1.0f};
    float[] green = {0.0f, 0.8f, 0.0f, 1.0f};
    float[] brown = {0.8f, 0.4f, 0.0f, 1.0f};
    float[] lightpos = {10.0f, 10.0f, 10.0f, 0.0f};
    float[][] colorList = {red, blue, yellow};
    long tic = 10;
    float acc = 0.0005f;
    float bacc = 0.0005f;
    float YukaSIZE = 100;
    float YukaY = -5;
    float YukaZERO = 0;
    float AreaRatio = 0.9f;
    int turnAngle = 2;
}

ScoreView.java


package jp.ac.dendai.c.jtp.flightsample;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
public class ScoreView extends View{
    private final Context context;
    private int score =0;

    public ScoreView(Context context) {
        super(context);
        this.context=context;
    }
    @Override
    protected void onDraw(Canvas canvas) {
        Point p = new Point();
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        display.getSize(p);
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        int fontsize = (p.x-200)/20+10;
        paint.setTextSize(fontsize);
        paint.setColor(Color.WHITE);
        canvas.drawText("SCORE:" + Integer.toString(score), 10, fontsize, paint);
    }
    public void clear(){
        score =0;
    }
    public void add(int point){
        score += point;
    }
    public void update(){
        invalidate();
    }
}

MyGLView.java


package jp.ac.dendai.c.jtp.flightsample;

import android.content.Context;
import android.opengl.GLSurfaceView;

public class MyGLView extends GLSurfaceView  implements Runnable {
    private MyRenderer renderer;
    private Thread        thread;
    private final Object lock;
	public MyGLView(Context context, ScoreView scoreView) {
		super(context);
        lock = new Object();
        this.renderer = new MyRenderer(lock,scoreView);
        setRenderer(renderer);
        setOnTouchListener(renderer.getOnTouchListener());

	}
	@Override
	public void run() {
        double previous = (double) System.currentTimeMillis();
        double now;
        while (thread != null) {
            synchronized (lock) {
                now = System.currentTimeMillis();
                float tstep = (float) ((now - previous) / Constant.tic);

                renderer.tick(tstep);
            }
            previous = now;

            try {
                Thread.sleep(Constant.tic);
            } catch (Exception ignored) {
            }
        }
    }

    public void threadpause(){
		thread = null;
	}
	
	public void threadstart(){
		thread = new Thread(this);
		thread.start();
	}
}

MyRenderer.java


package jp.ac.dendai.c.jtp.flightsample;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.GLU;
import android.view.MotionEvent;
import android.view.View;

import java.util.Iterator;


public class MyRenderer implements Renderer {
    private final Object lock;
    private final ScoreView score;
    private Angle screen;
    private volatile Position touch;
    private Scene scene = Scene.S_START;
    private Anata anata;
    private DrawList<Drawable> list;

    public MyRenderer(Object lock, ScoreView scoreView){
        this.lock = lock;
        score = scoreView;
        synchronized (lock) {
            list = new DrawList<Drawable>();
            list.add(new Yuka());
            list.add(new Box(Constant.red, new Vect(1.0f, 1.0f, 1.0f), 2.0f));
            list.add(new Box(Constant.blue, new Vect(4.0f, 1.0f, 4.0f), 2.0f));
            list.add(new Box(Constant.yellow, new Vect(8.0f, 1.0f, 8.0f), 2.0f));
        }
        touch = new Position();
        anata = new Anata();
    }

    @Override
    public void onSurfaceCreated(GL10 gl10,EGLConfig eglConfig) {
        //光源位置の指定
        gl10.glLightfv(GL10.GL_LIGHT0,GL10.GL_POSITION, Constant.lightpos,0);
        //デプステストと光源の有効化
        gl10.glEnable(GL10.GL_DEPTH_TEST);
        gl10.glEnable(GL10.GL_LIGHTING);
        gl10.glEnable(GL10.GL_LIGHT0);
    }

    @Override
    public void onSurfaceChanged(GL10 gl10,int w,int h) {
        gl10.glViewport(0,0,w,h);
        screen = new Angle(w,h);
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        //画面の初期化
        gl10.glClearColor(0.8f,0.8f,1.0f,1.0f);
        gl10.glClear(GL10.GL_COLOR_BUFFER_BIT|
                GL10.GL_DEPTH_BUFFER_BIT);

        //射影変換
        gl10.glMatrixMode(GL10.GL_PROJECTION);
        gl10.glLoadIdentity();

        GLU.gluPerspective(gl10,
                60.0f,                        //Y方向の画角
                screen.ratio(),//アスペクト比
                0.01f,                        //ニアクリップ
                100.0f);                      //ファークリップ
        //ビュー変換
        gl10.glMatrixMode(GL10.GL_MODELVIEW);	//行列演算ターゲットの指定
        gl10.glLoadIdentity();					//行列の単位行列化
        anata.setLookAt(gl10);
        synchronized (lock) {
            list.draw(gl10);
        }

    }
    //定期処理
    public void tick(float tstep) {
        switch (scene) {
            case S_START:
                touch.setInvalid();
                anata.init();
                score.clear();
                break;
            case S_PLAY:
                if(touch.isValid()){
                    if(touch.isInLeft(screen)){
                        anata.turnLeft();
                    }else if(touch.isInRight(screen)){
                        anata.turnRight();
                    }
                    anata.speedUp(tstep);
                }else {
                    anata.speedDown(tstep);
                }
                anata.goForward(tstep);
                synchronized (lock){
                    boolean changed=false;
                    Iterator<? extends Drawable> i = list.iterator();
                    while(i.hasNext()){
                        Drawable d = i.next();
                        if(d instanceof Touchable){
                            Touchable t = (Touchable)d;
                            if(t.isContained(anata.getPostion())){
                                anata.reverseVelocity();
                                t.die();
                                i.remove();
                                score.add(10);
                                changed=true;
                            }
                        }
                    }
                    if(changed){
                        list.add(Box.createBox());
                    }
                }


                break;
        }
    }

    //タッチイベントの処理
    //タッチされた座標を取得、離れたときに—1を格納
    public View.OnTouchListener getOnTouchListener(){
        return new MyOnTouchListener();
    }

    class MyOnTouchListener implements View.OnTouchListener {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            score.update();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_MOVE:
                    switch (scene) {
                        case S_START:
                            scene = Scene.S_PLAY;
                            break;
                        case S_PLAY:
                            touch.set(event.getX(),event.getY());
                            break;
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    touch.setInvalid();
                    break;
            }
            return true;
        }
    }
}

Scene.java


package jp.ac.dendai.c.jtp.flightsample;
public enum Scene {
    S_START ,
    S_PLAY
}

Position.java


package jp.ac.dendai.c.jtp.flightsample;

public class Position {
    private float x;
    private float y;
    private boolean valid;
    public Position(){
        x=-1;
        y=-1;
        valid=false;
    }
    public void setX(float _x){
        x=_x;
    }
    public void setY(float _y) {
        y = _y;
    }
    public void setInvalid() {
        valid = false;
    }
    public boolean isValid() {
        return valid;
    }

    public boolean isInLeft(Angle screen) {
        return x < (float)screen.getW() / 3;
    }

    public boolean isInRight(Angle screen) {
        return x > (float)screen.getW() * 2 / 3;
    }
    public void set(float _x, float _y) {
        x=_x;
        y=_y;
        valid=true;
    }
}

Angle.java


package jp.ac.dendai.c.jtp.flightsample;

public class Angle {
    private int w;
    private int h;

    public Angle(int w, int h) {
        this.h = h;
        this.w = w;
    }

    public Angle(Angle a) {
        this.w=a.w;
        this.h=a.h;
    }

    public int getW() {
        return w;
    }

    public int getH() {
        return h;
    }

    public void addW(int i) {
        w+=i;
    }

    public void addH(int i) {
        h+=i;
    }
    private static float toRadian(float angle) {
        return (float)(angle*Math.PI/180);
    }
    public Vect getVect() {
        float fw = toRadian(w);
        float fh = toRadian(h);
        return new Vect((float) (Math.cos(fw) * Math.cos(fh)),
                (float) Math.sin(fh),
                (float) (Math.sin(fw) * Math.cos(fh)));
    }

    public float ratio() {
        return (float)w/h;
    }
}

Vect.java


package jp.ac.dendai.c.jtp.flightsample;

public class Vect {
    private float x;
    private float y;
    private float z;

    public Vect(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public Vect(Vect v) {
        this.x = v.x;
        this.y = v.y;
        this.z = v.z;
    }

    public float getX() {
        return x;
    }

    public float getY() {
        return y;
    }

    public float getZ() {
        return z;
    }

    public void mul(float e) {
        x*=e;
        y*=e;
        z*=e;
    }

    public void sub(Vect v) {
        x-=v.x;
        y-=v.y;
        z-=v.z;
    }

    public void add(Vect v) {
        x+=v.x;
        y+=v.y;
        z+=v.z;
    }
    public float squareOfDistance(Vect v){
        return sq(v.getX()-x)+sq(v.getY()-y)+sq(v.getZ()-z);
    }
    private float sq(float v) {
        return v*v;
    }
}

Drawable.java


package jp.ac.dendai.c.jtp.flightsample;
import javax.microedition.khronos.opengles.GL10;
public interface Drawable {
    void draw(GL10 gl10);
}

DrawList.java


package jp.ac.dendai.c.jtp.flightsample;
import java.util.ArrayList;
import javax.microedition.khronos.opengles.GL10;
public class DrawList<E extends Drawable > extends ArrayList<E> {
    public void draw(GL10 gl10){
        for(E d: this){
            d.draw(gl10);
        }
    }
}

Touchable.java


package jp.ac.dendai.c.jtp.flightsample;
public interface Touchable extends Drawable{
    boolean isContained(Vect v);
    void die();
    boolean isAlive();
}

AbstractDrawable.java


package jp.ac.dendai.c.jtp.flightsample;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

public abstract class AbstractDrawable implements Drawable{
    protected final float[][] materialColors;
    protected float[] vertexs;

    public AbstractDrawable(float[][] floats) {
        materialColors = floats;
    }

    //float配列をFloatBufferに変換
    protected FloatBuffer makeFloatBuffer(float[] array) {
        FloatBuffer fb= ByteBuffer.allocateDirect(array.length * 4).order(
                ByteOrder.nativeOrder()).asFloatBuffer();
        fb.put(array).position(0);
        return fb;
    }

    //byte配列をByteBufferに変換
    protected ByteBuffer makeByteBuffer(byte[] array) {
        ByteBuffer bb=ByteBuffer.allocateDirect(array.length).order(
                ByteOrder.nativeOrder());
        bb.put(array).position(0);
        return bb;
    }

    protected void setColor(GL10 gl10, float[] color){
        gl10.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_AMBIENT, color, 0);
        gl10.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_DIFFUSE, color, 0);
    }
}

Box.java


package jp.ac.dendai.c.jtp.flightsample;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;
public class Box extends AbstractDrawable implements Touchable {
    //インデックスバッファの生成
    private static final byte[] indexs = new byte[]{
            0,1,2,
            2,1,3,
            2,3,6,
            6,3,7,
            6,7,4,
            4,7,5,
            4,5,0,
            0,5,1,
            1,5,3,
            3,5,7,
            0,2,4,
            4,2,6,
    };
    private final Vect center;
    private final float size;
    private boolean alive;
    public Box(float[] materialcolor, Vect v, float _size) {
        super(new float[][]{materialcolor});
    alive = true;
        size=_size;
        float mx = v.getX() - _size;
        float my = v.getY() - _size;
        float mz = v.getZ() - _size;

        //頂点バッファの生成
         vertexs=new float[]{
                v.getX(),  v.getY(),  v.getZ(),	//頂点0
                v.getX(),  v.getY(), mz,	//頂点1
                mx,  v.getY(),  v.getZ(),	//頂点2
                mx,  v.getY(), mz,	//頂点3
                v.getX(), my,  v.getZ(),	//頂点4
                v.getX(), my, mz,	//頂点5
                mx, my,  v.getZ(),	//頂点6
                mx, my, mz,	//頂点7
        };
        center = new Vect(v.getX()-_size/2,v.getY()-_size/2,v.getZ()-_size/2);
    }
    public static Box createBox(){
        float[] color = Constant.colorList[((int) (Math.random() * Constant.colorList.length))];
        float rx= randomPosition();
        float ry= randomPosition();
        return new Box(color, new Vect(rx,1.0f,ry),Constant.boxsize);
    }

    private static float randomPosition() {
        return (float) ((Math.random()*2* Constant.YukaSIZE-Constant.YukaSIZE)*Constant.AreaRatio);
    }

    @Override
    public void die(){
        alive=false;
    }
    @Override
    public boolean isAlive(){
        return alive;
    }

    //ボックスの描画
    @Override
    public void draw(GL10 gl10) {
        gl10.glEnableClientState(GL10.GL_VERTEX_ARRAY);

        setColor(gl10,materialColors[0]);

        //バッファ変換
        FloatBuffer vertexBuffer = makeFloatBuffer(vertexs);
        ByteBuffer indexBuffer = makeByteBuffer(indexs);

        //頂点バッファの指定
        gl10.glVertexPointer(3,GL10.GL_FLOAT,0,vertexBuffer);

        //面の描画
        gl10.glDrawElements(GL10.GL_TRIANGLES,
                indexBuffer.capacity(),GL10.GL_UNSIGNED_BYTE,indexBuffer);
    }
    @Override
    public boolean isContained(Vect v){
       return center.squareOfDistance(v)<size*size;
    }
}

Anata.java


package jp.ac.dendai.c.jtp.flightsample;
import android.opengl.GLU;

import javax.microedition.khronos.opengles.GL10;

public class Anata {
    private Vect spec;
    private Angle specAngle;
   private Angle eyeAngle;
    private float velocity;
    void setLookAt(GL10 gl10){
        eyeAngle = new Angle(specAngle);
        Vect eye = new Vect(spec);
        Vect vect = eyeAngle.getVect();
        vect .mul(Constant.EYEDIS);
        eye.sub(vect);
        GLU.gluLookAt(gl10,
                eye.getX(), eye.getY(), eye.getZ(),
                spec.getX(), spec.getY(), spec.getZ(),
                0.0f, 1.0f, 0.0f);
    }

    public void init(){
        velocity=0;
        setAngle(new Angle(-90, 0));
        setVect(new Vect(0, 0, 50));
    }
    public void setAngle(Angle angle) {
        specAngle = angle;
    }
    public void speedUp(float tstep){
        velocity+=Constant.acc*tstep;
        if(velocity>Constant.MaxV){
            velocity=Constant.MaxV;
        }
    }
    public void speedDown(float tstep){
        float oldvelocity=velocity;
        if(velocity>0) {
            velocity -= Constant.bacc * tstep;
        }else if(velocity <0){
            velocity += Constant.bacc * tstep;
        }
        if(velocity*oldvelocity<0){
            velocity=0;
        }
    }

    public void setVect(Vect vect) {
        spec=vect;
    }

    public void turnLeft() {
        specAngle.addW(-Constant.turnAngle);
    }

    public void turnRight() {
        specAngle.addW(Constant.turnAngle);
    }

    public void goForward(float tstep) {
        Vect d = eyeAngle.getVect();
        d.mul(velocity * tstep);
        spec.add(d);
    }

    public Vect getPostion() {
        return spec;
    }

    public void reverseVelocity() {
        velocity=-velocity;
    }
}

Yuka.java


package jp.ac.dendai.c.jtp.flightsample;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import javax.microedition.khronos.opengles.GL10;
public class Yuka extends AbstractDrawable {
    //インデックスバッファの生成
    //3つの頂点で三角形を作成
    private static final byte[] indexs = new byte[]{
            0,1,2,
            2,1,3,
            2,3,4,
            4,3,5,
            1,6,3,
            3,6,7,
            3,7,5,
            5,7,8,
    };
    //コンストラクタ
    public Yuka() {
        super(new float[][]{Constant.green, Constant.brown});
        //頂点バッファの生成
        vertexs = new float[] {
                Constant.YukaSIZE, Constant.YukaY, Constant.YukaSIZE,	//頂点0
                Constant.YukaZERO, Constant.YukaY, Constant.YukaSIZE,	//頂点1
                Constant.YukaSIZE, Constant.YukaY, Constant.YukaZERO,	//頂点2
                Constant.YukaZERO, Constant.YukaY, Constant.YukaZERO,	//頂点3
                Constant.YukaSIZE, Constant.YukaY, -Constant.YukaSIZE,	//頂点4
                Constant.YukaZERO, Constant.YukaY, -Constant.YukaSIZE,	//頂点5
                -Constant.YukaSIZE, Constant.YukaY, Constant.YukaSIZE,	//頂点6
                -Constant.YukaSIZE, Constant.YukaY, Constant.YukaZERO,	//頂点7
                -Constant.YukaSIZE, Constant.YukaY, -Constant.YukaSIZE,	//頂点8
        };
    }
    //描画
    @Override
    public void draw(GL10 gl10) {
        //頂点配列の有効化
        gl10.glEnableClientState(GL10.GL_VERTEX_ARRAY);

        //バッファ変換
        FloatBuffer vertexBuffer = makeFloatBuffer(vertexs);
        ByteBuffer indexBuffer = makeByteBuffer(indexs);

        //頂点バッファの指定
        gl10.glVertexPointer(3,GL10.GL_FLOAT,0,vertexBuffer);

        //面の描画
        setColor(gl10, materialColors[0]);
        indexBuffer.position(0);
        gl10.glDrawElements(GL10.GL_TRIANGLES,
                6,GL10.GL_UNSIGNED_BYTE,indexBuffer);

        //面の描画
        setColor(gl10, materialColors[1]);
        indexBuffer.position(6);
        gl10.glDrawElements(GL10.GL_TRIANGLES,
                6,GL10.GL_UNSIGNED_BYTE,indexBuffer);

        //面の描画
        setColor(gl10, materialColors[1]);
        indexBuffer.position(12);
        gl10.glDrawElements(GL10.GL_TRIANGLES,
                6,GL10.GL_UNSIGNED_BYTE,indexBuffer);

        //面の描画
        setColor(gl10, materialColors[0]);
        indexBuffer.position(18);
        gl10.glDrawElements(GL10.GL_TRIANGLES,
                6,GL10.GL_UNSIGNED_BYTE,indexBuffer);
    }
}

坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科