Hướng dẫn lập trình game Android đơn giản sử dụng Android Studio (Phần 2)

Đăng bởi: Admin | Lượt xem: 4123 | Chuyên mục: Game

Mục tiêu của tài liệu hướng dẫn bạn làm quen với một vài kỹ thuật đơn giản trong lập trình Game 2D Android. Bao gồm: sử dụng SuffaceView, vẽ trên Canvas, chuyển động của các , nhân vật game, tương tác với cử chỉ của người chơi.


Xem phần 1 của hướng dẫn tại đây

4- Sét đặt chế độ fullscreen (Version:1)

Với trò chơi bạn cần phải sét đặt ảnh nền và một điều quan trọng bạn cần phải sét chế độ FullScreen (Toàn màn hình).

Class MainActivity của bạn phải extends từ class Activity.

MainActivity.java

package org.o7planning.android2dgame;
  
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
 
public class MainActivity extends Activity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        // Set fullscreen
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
 
        // Set No Title
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
 
    }
 
}

Tiếp theo sét đặt kiểu màn hình nằm ngang (Landscape). Bạn cần phải sét đặt trong AndroidManifest.xml.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.o7planning.android2dgame">
 
    <application
        android:screenOrientation="landscape"
        ... >
 
       ...
 
    </application>
 
</manifest>

Chạy thử ứng dụng:

5- Hiển thị nhân vật trên trò chơi (Version:2)

Tiếp theo bạn cần viết code để hiển thị nhân vật trò chơi trên màn hình và di chuyển nó từ trái qua phải theo một vận tốc nào đó.

Với một nhân vật trong trò chơi bạn chỉ có một file ảnh, nhưng file ảnh được chia ra làm nhiều vùng mô tả các hành động khác nhau của nhân vật.

Sử dụng code bạn có thể vẽ một vùng ảnh lên đối tượng Canvas của trò chơi, tại tạo độ x, y. Sử dụng vòng lặp for để liên tục vẽ lại trên Canvas bạn có thể tạo ra sự chuyển động của nhân vật.

Trong khi lập trình Game bạn cũng cần phải tính đến hướng di chuyển của nhân vật trong trò chơi, vận tốc của nhân vật.

Tạo lớp GameObject, các đối tượng của trò chơi mở rộng từ class này.

GameObject.java

package org.o7planning.android2dgame;
 
import android.graphics.Bitmap;
 
public abstract class GameObject {
 
    protected Bitmap image;
 
    protected final int rowCount;
    protected final int colCount;
 
    protected final int WIDTH;
    protected final int HEIGHT;
 
    protected final int width;
 
 
    protected final int height;
    protected int x;
    protected int y;
 
    public GameObject(Bitmap image, int rowCount, int colCount, int x, int y)  {
 
        this.image = image;
        this.rowCount= rowCount;
        this.colCount= colCount;
 
        this.x= x;
        this.y= y;
 
        this.WIDTH = image.getWidth();
        this.HEIGHT = image.getHeight();
 
        this.width = this.WIDTH/ colCount;
        this.height= this.HEIGHT/ rowCount;
    }
 
    protected Bitmap createSubImageAt(int row, int col)  {
        // createBitmap(bitmap, x, y, width, height).
        Bitmap subImage = Bitmap.createBitmap(image, col* width, row* height ,width,height);
        return subImage;
    }
 
    public int getX()  {
        return this.x;
    }
 
    public int getY()  {
        return this.y;
    }
 
 
    public int getHeight() {
        return height;
    }
 
    public int getWidth() {
        return width;
    }
 
}

Lớp ChibiCharactor mô phỏng một nhân vật trong trò chơi.

ChibiCharactor.java

package org.o7planning.android2dgame;
 
import android.graphics.Bitmap;
import android.graphics.Canvas;
 
public class ChibiCharacter extends GameObject {
 
    private static final int ROW_TOP_TO_BOTTOM = 0;
    private static final int ROW_RIGHT_TO_LEFT = 1;
    private static final int ROW_LEFT_TO_RIGHT = 2;
    private static final int ROW_BOTTOM_TO_TOP = 3;
 
    // Row index of Image are being used.
    private int rowUsing = ROW_LEFT_TO_RIGHT;
 
    private int colUsing;
 
    private Bitmap[] leftToRights;
    private Bitmap[] rightToLefts;
    private Bitmap[] topToBottoms;
    private Bitmap[] bottomToTops;
 
    // Velocity of game character (pixel/millisecond)
    public static final float VELOCITY = 0.1f;
 
    private int movingVectorX = 10;
    private int movingVectorY = 5;
 
    private long lastDrawNanoTime =-1;
 
    private GameSurface gameSurface;
 
    public ChibiCharacter(GameSurface gameSurface, Bitmap image, int x, int y) {
        super(image, 4, 3, x, y);
 
        this.gameSurface= gameSurface;
 
        this.topToBottoms = new Bitmap[colCount]; // 3
        this.rightToLefts = new Bitmap[colCount]; // 3
        this.leftToRights = new Bitmap[colCount]; // 3
        this.bottomToTops = new Bitmap[colCount]; // 3
 
        for(int col = 0; col< this.colCount; col++ ) {
            this.topToBottoms[col] = this.createSubImageAt(ROW_TOP_TO_BOTTOM, col);
            this.rightToLefts[col]  = this.createSubImageAt(ROW_RIGHT_TO_LEFT, col);
            this.leftToRights[col] = this.createSubImageAt(ROW_LEFT_TO_RIGHT, col);
            this.bottomToTops[col]  = this.createSubImageAt(ROW_BOTTOM_TO_TOP, col);
        }
    }
 
    public Bitmap[] getMoveBitmaps()  {
        switch (rowUsing)  {
            case ROW_BOTTOM_TO_TOP:
                return  this.bottomToTops;
            case ROW_LEFT_TO_RIGHT:
                return this.leftToRights;
            case ROW_RIGHT_TO_LEFT:
                return this.rightToLefts;
            case ROW_TOP_TO_BOTTOM:
                return this.topToBottoms;
            default:
                return null;
        }
    }
 
    public Bitmap getCurrentMoveBitmap()  {
        Bitmap[] bitmaps = this.getMoveBitmaps();
        return bitmaps[this.colUsing];
    }
 
 
    public void update()  {
        this.colUsing++;
        if(colUsing >= this.colCount)  {
            this.colUsing =0;
        }
        // Current time in nanoseconds
        long now = System.nanoTime();
 
        // Never once did draw.
        if(lastDrawNanoTime==-1) {
            lastDrawNanoTime= now;
        }
        // Change nanoseconds to milliseconds (1 nanosecond = 1000000 milliseconds).
        int deltaTime = (int) ((now - lastDrawNanoTime)/ 1000000 );
 
        // Distance moves
        float distance = VELOCITY * deltaTime;
 
        double movingVectorLength = Math.sqrt(movingVectorX* movingVectorX + movingVectorY*movingVectorY);
 
        // Calculate the new position of the game character.
        this.x = x +  (int)(distance* movingVectorX / movingVectorLength);
        this.y = y +  (int)(distance* movingVectorY / movingVectorLength);
 
        // When the game's character touches the edge of the screen, then change direction
 
        if(this.x < 0 )  {
            this.x = 0;
            this.movingVectorX = - this.movingVectorX;
        } else if(this.x > this.gameSurface.getWidth() -width)  {
            this.x= this.gameSurface.getWidth()-width;
            this.movingVectorX = - this.movingVectorX;
        }
 
        if(this.y < 0 )  {
            this.y = 0;
            this.movingVectorY = - this.movingVectorY;
        } else if(this.y > this.gameSurface.getHeight()- height)  {
            this.y= this.gameSurface.getHeight()- height;
            this.movingVectorY = - this.movingVectorY ;
        }
 
        // rowUsing
        if( movingVectorX > 0 )  {
            if(movingVectorY > 0 && Math.abs(movingVectorX) < Math.abs(movingVectorY)) {
                this.rowUsing = ROW_TOP_TO_BOTTOM;
            }else if(movingVectorY < 0 && Math.abs(movingVectorX) < Math.abs(movingVectorY)) {
                this.rowUsing = ROW_BOTTOM_TO_TOP;
            }else  {
                this.rowUsing = ROW_LEFT_TO_RIGHT;
            }
        } else {
            if(movingVectorY > 0 && Math.abs(movingVectorX) < Math.abs(movingVectorY)) {
                this.rowUsing = ROW_TOP_TO_BOTTOM;
            }else if(movingVectorY < 0 && Math.abs(movingVectorX) < Math.abs(movingVectorY)) {
                this.rowUsing = ROW_BOTTOM_TO_TOP;
            }else  {
                this.rowUsing = ROW_RIGHT_TO_LEFT;
            }
        }
    }
 
    public void draw(Canvas canvas)  {
        Bitmap bitmap = this.getCurrentMoveBitmap();
        canvas.drawBitmap(bitmap,x, y, null);
        // Last draw time.
        this.lastDrawNanoTime= System.nanoTime();
    }
 
    public void setMovingVector(int movingVectorX, int movingVectorY)  {
        this.movingVectorX= movingVectorX;
        this.movingVectorY = movingVectorY;
    }
}

GameThread là một luồng (thread) điều khiển việc cập nhập lại giao diện của trò chơi.

GameTread.java

package org.o7planning.android2dgame;
 
import android.graphics.Canvas;
import android.view.SurfaceHolder;
 
public class GameThread extends Thread {
 
    private boolean running;
    private GameSurface gameSurface;
    private SurfaceHolder surfaceHolder;
 
    public GameThread(GameSurface gameSurface, SurfaceHolder surfaceHolder)  {
        this.gameSurface= gameSurface;
        this.surfaceHolder= surfaceHolder;
    }
 
    @Override
    public void run()  {
        long startTime = System.nanoTime();
 
        while(running)  {
            Canvas canvas= null;
            try {
                // Get Canvas from Holder and lock it.
                canvas = this.surfaceHolder.lockCanvas();
 
                // Synchronized
                synchronized (canvas)  {
                    this.gameSurface.update();
                    this.gameSurface.draw(canvas);
                }
            }catch(Exception e)  {
                // Do nothing.
            } finally {
                if(canvas!= null)  {
                    // Unlock Canvas.
                    this.surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
            long now = System.nanoTime() ;
            // Interval to redraw game
            // (Change nanoseconds to milliseconds)
            long waitTime = (now - startTime)/1000000;
            if(waitTime < 10)  {
                waitTime= 10; // Millisecond.
            }
            System.out.print(" Wait Time="+ waitTime);
 
            try {
                // Sleep.
                this.sleep(waitTime);
            } catch(InterruptedException e)  {
 
            }
            startTime = System.nanoTime();
            System.out.print(".");
        }
    }
 
    public void setRunning(boolean running)  {
        this.running= running;
    }
}

Lớp GameSurface mô phỏng toàn bộ bề mặt của trò chơi, class này mở rộng từ SurfaceViewSurfaceView chứa một đối tượng Canvas, các đối tượng trong trò chơi sẽ được vẽ lên Canvas.

GameSurface.java

package org.o7planning.android2dgame;
 
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
 
public class GameSurface extends SurfaceView implements SurfaceHolder.Callback {
 
    private GameThread gameThread;
    private ChibiCharacter chibi1;
 
    public GameSurface(Context context)  {
        super(context);
 
        // Make Game Surface focusable so it can handle events. .
        this.setFocusable(true);
 
        // Sét callback.
        this.getHolder().addCallback(this);
    }
 
    public void update()  {
        this.chibi1.update();
    }
 
    @Override
    public void draw(Canvas canvas)  {
        super.draw(canvas);
 
        this.chibi1.draw(canvas);
    }
 
    // Implements method of SurfaceHolder.Callback
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Bitmap chibiBitmap1 = BitmapFactory.decodeResource(this.getResources(),R.drawable.chibi1);
        this.chibi1 = new ChibiCharacter(this,chibiBitmap1,100,50);
 
        this.gameThread = new GameThread(this,holder);
        this.gameThread.setRunning(true);
        this.gameThread.start();
    }
 
    // Implements method of SurfaceHolder.Callback
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
 
    }
 
    // Implements method of SurfaceHolder.Callback
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        boolean retry= true;
        while(retry) {
            try {
                this.gameThread.setRunning(false);
 
                // Parent thread must wait until the end of GameThread.
                this.gameThread.join();
            }catch(InterruptedException e)  {
                e.printStackTrace();
            }
            retry= true;
        }
    }
 
}

MainActivity.java

package org.o7planning.android2dgame;
 
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
 
public class MainActivity extends Activity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        // Set fullscreen
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
 
        // Set No Title
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
 
        this.setContentView(new GameSurface(this));
    }
 
}

Xem phần tiếp theo tại đây

vncoder logo

Theo dõi VnCoder trên Facebook, để cập nhật những bài viết, tin tức và khoá học mới nhất!



Khóa học liên quan

Khóa học: Game