Android
Tworzenie gier na Androida
Szukaj…
Wprowadzenie
Krótkie wprowadzenie do tworzenia gry na platformie Android za pomocą Java
Uwagi
- Pierwszy przykład obejmuje podstawy: Nie ma żadnych celów, ale pokazuje, jak utworzyć podstawową część gry 2D za pomocą SurfaceView.
- Pamiętaj, aby zapisać ważne dane podczas tworzenia gry; wszystko inne zostanie utracone
Gra z wykorzystaniem Canvas i SurfaceView
Obejmuje to sposób tworzenia podstawowej gry 2D za pomocą SurfaceView.
Po pierwsze potrzebujemy działania:
public class GameLauncher extends AppCompatActivity {
private Game game;
@Override
public void onCreate(Bundle sis){
super.onCreate(sis);
game = new Game(GameLauncher.this);//Initialize the game instance
setContentView(game);//setContentView to the game surfaceview
//Custom XML files can also be used, and then retrieve the game instance using findViewById.
}
}
Aktywność należy również zadeklarować w Manifeście Androida.
Teraz sama gra. Najpierw zaczynamy od implementacji wątku gry:
public class Game extends SurfaceView implements SurfaceHolder.Callback, Runnable{
/**
* Holds the surface frame
*/
private SurfaceHolder holder;
/**
* Draw thread
*/
private Thread drawThread;
/**
* True when the surface is ready to draw
*/
private boolean surfaceReady = false;
/**
* Drawing thread flag
*/
private boolean drawingActive = false;
/**
* Time per frame for 60 FPS
*/
private static final int MAX_FRAME_TIME = (int) (1000.0 / 60.0);
private static final String LOGTAG = "surface";
/*
* All the constructors are overridden to ensure functionality if one of the different constructors are used through an XML file or programmatically
*/
public Game(Context context) {
super(context);
init();
}
public Game(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public Game(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(21)
public Game(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
public void init(Context c) {
this.c = c;
SurfaceHolder holder = getHolder();
holder.addCallback(this);
setFocusable(true);
//Initialize other stuff here later
}
public void render(Canvas c){
//Game rendering here
}
public void tick(){
//Game logic here
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
if (width == 0 || height == 0){
return;
}
// resize your UI
}
@Override
public void surfaceCreated(SurfaceHolder holder){
this.holder = holder;
if (drawThread != null){
Log.d(LOGTAG, "draw thread still active..");
drawingActive = false;
try{
drawThread.join();
} catch (InterruptedException e){}
}
surfaceReady = true;
startDrawThread();
Log.d(LOGTAG, "Created");
}
@Override
public void surfaceDestroyed(SurfaceHolder holder){
// Surface is not used anymore - stop the drawing thread
stopDrawThread();
// and release the surface
holder.getSurface().release();
this.holder = null;
surfaceReady = false;
Log.d(LOGTAG, "Destroyed");
}
@Override
public boolean onTouchEvent(MotionEvent event){
// Handle touch events
return true;
}
/**
* Stops the drawing thread
*/
public void stopDrawThread(){
if (drawThread == null){
Log.d(LOGTAG, "DrawThread is null");
return;
}
drawingActive = false;
while (true){
try{
Log.d(LOGTAG, "Request last frame");
drawThread.join(5000);
break;
} catch (Exception e) {
Log.e(LOGTAG, "Could not join with draw thread");
}
}
drawThread = null;
}
/**
* Creates a new draw thread and starts it.
*/
public void startDrawThread(){
if (surfaceReady && drawThread == null){
drawThread = new Thread(this, "Draw thread");
drawingActive = true;
drawThread.start();
}
}
@Override
public void run() {
Log.d(LOGTAG, "Draw thread started");
long frameStartTime;
long frameTime;
/*
* In order to work reliable on Nexus 7, we place ~500ms delay at the start of drawing thread
* (AOSP - Issue 58385)
*/
if (android.os.Build.BRAND.equalsIgnoreCase("google") && android.os.Build.MANUFACTURER.equalsIgnoreCase("asus") && android.os.Build.MODEL.equalsIgnoreCase("Nexus 7")) {
Log.w(LOGTAG, "Sleep 500ms (Device: Asus Nexus 7)");
try {
Thread.sleep(500);
} catch (InterruptedException ignored) {}
}
while (drawing) {
if (sf == null) {
return;
}
frameStartTime = System.nanoTime();
Canvas canvas = sf.lockCanvas();
if (canvas != null) {
try {
synchronized (sf) {
tick();
render(canvas);
}
} finally {
sf.unlockCanvasAndPost(canvas);
}
}
// calculate the time required to draw the frame in ms
frameTime = (System.nanoTime() - frameStartTime) / 1000000;
if (frameTime < MAX_FRAME_TIME){
try {
Thread.sleep(MAX_FRAME_TIME - frameTime);
} catch (InterruptedException e) {
// ignore
}
}
}
Log.d(LOGTAG, "Draw thread finished");
}
}
To jest podstawowa część. Teraz możesz rysować na ekranie.
Teraz zacznijmy od dodania liczb całkowitych:
public final int x = 100;//The reason for this being static will be shown when the game is runnable
public int y;
public int velY;
Do następnej części będziesz potrzebować obrazu. Powinien wynosić około 100 x 100, ale może być większy lub mniejszy. Do nauki można również użyć Recta (ale wymaga to nieco zmiany kodu)
Teraz deklarujemy Bitmapę:
private Bitmap PLAYER_BMP = BitmapFactory.decodeResource(getResources(), R.drawable.my_player_drawable);
W renderowaniu musimy narysować tę mapę bitową.
...
c.drawBitmap(PLAYER_BMP, x, y, null);
...
PRZED URUCHOMIENIEM jest jeszcze kilka rzeczy do zrobienia
Najpierw potrzebujemy wartości logicznej:
boolean up = false;
w onTouchEvent dodajemy:
if(ev.getAction() == MotionEvent.ACTION_DOWN){
up = true;
}else if(ev.getAction() == MotionEvent.ACTION_UP){
up = false;
}
I w kleszczu potrzebujemy tego, aby przenieść gracza:
if(up){
velY -=1;
}
else{
velY +=1;
}
if(velY >14)velY = 14;
if(velY <-14)velY = -14;
y += velY *2;
a teraz potrzebujemy tego w init:
WindowManager wm = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
WIDTH = size.x;
HEIGHT = size.y;
y = HEIGHT/ 2 - PLAYER_BMP.getHeight();
Potrzebujemy ich do zmiennych:
public static int WIDTH, HEIGHT;
W tym momencie można uruchomić grę. Oznacza to, że możesz go uruchomić i przetestować.
Teraz powinieneś mieć obraz odtwarzacza lub prostokąt poruszający się w górę iw dół ekranu. W razie potrzeby gracz może zostać utworzony jako klasa niestandardowa. Następnie wszystkie rzeczy związane z odtwarzaczem można przenieść do tej klasy i użyć instancji tej klasy do poruszania się, renderowania i wykonywania innej logiki.
Teraz, jak zapewne widziałeś podczas testów, leci poza ekran. Więc musimy to ograniczyć.
Najpierw musimy zadeklarować Rect:
private Rect screen;
W init, po zainicjowaniu szerokości i wysokości, tworzymy nowy prostokąt, którym jest ekran.
screen = new Rect(0,0,WIDTH,HEIGHT);
Teraz potrzebujemy kolejnego prostownika w postaci metody:
private Rect getPlayerBound(){
return new Rect(x, y, x + PLAYER_BMP.getWidth(), y + PLAYER_BMP.getHeight();
}
i zaznacz:
if(!getPlayerBound().intersects(screen){
gameOver = true;
}
Implementacja gameOVer może być również wykorzystana do pokazania początku gry.
Inne aspekty gry, na które warto zwrócić uwagę:
Zapisywanie (obecnie brak w dokumentacji)