Suche…


Einführung

Eine kurze Einführung in die Erstellung eines Spiels auf der Android-Plattform mit Java

Bemerkungen

  • Das erste Beispiel behandelt die Grundlagen: Es gibt keine Ziele, aber es zeigt Ihnen, wie Sie einen grundlegenden Teil eines 2D-Spiels mit SurfaceView erstellen.
  • Stellen Sie sicher, dass Sie wichtige Daten speichern, wenn Sie ein Spiel erstellen. alles andere wird verloren gehen

Spiel mit Canvas und SurfaceView

Hier erfahren Sie, wie Sie mit SurfaceView ein einfaches 2D-Spiel erstellen können.


Zuerst brauchen wir eine Aktivität:

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.
    }

}

Die Aktivität muss auch im Android-Manifest deklariert werden.


Nun zum Spiel selbst. Zuerst beginnen wir mit der Implementierung eines Game-Threads:

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");
    }
}

Das ist der grundlegende Teil. Jetzt haben Sie die Möglichkeit, auf den Bildschirm zu zeichnen.

Beginnen wir mit dem Hinzufügen von Ganzzahlen:

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;

Für den nächsten Teil benötigen Sie ein Bild. Sie sollte etwa 100x100 betragen, kann jedoch größer oder kleiner sein. Zum Lernen kann auch ein Rect verwendet werden (dies erfordert jedoch eine geringfügige Änderung des Codes).

Jetzt deklarieren wir eine Bitmap:

private Bitmap PLAYER_BMP = BitmapFactory.decodeResource(getResources(), R.drawable.my_player_drawable);

Beim Rendern müssen wir diese Bitmap zeichnen.

...
c.drawBitmap(PLAYER_BMP, x, y, null);
...

Vor dem Start gibt es noch einige Dinge zu tun

Wir brauchen zuerst einen Boolean:

boolean up = false;

In onTouchEvent fügen wir hinzu:

if(ev.getAction() == MotionEvent.ACTION_DOWN){
    up = true;
}else if(ev.getAction() == MotionEvent.ACTION_UP){
    up = false;
}

Und in tick brauchen wir das, um den Spieler zu bewegen:

if(up){
    velY -=1;
}
else{
    velY +=1;
}
if(velY >14)velY = 14;
if(velY <-14)velY = -14;
y += velY *2;

und jetzt brauchen wir das in 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();

Und wir brauchen diese Variablen:

public static int WIDTH, HEIGHT;

Zu diesem Zeitpunkt ist das Spiel lauffähig. Das heißt, Sie können es starten und testen.


Jetzt sollten Sie ein Player-Image haben oder den Bildschirm nach oben und unten bewegen. Der Player kann bei Bedarf als benutzerdefinierte Klasse erstellt werden. Dann können alle spielerbezogenen Dinge in diese Klasse verschoben werden und eine Instanz dieser Klasse zum Verschieben, Rendern und Ausführen anderer Logik verwenden.

Nun, wie Sie wahrscheinlich beim Testen gesehen haben, fliegt es vom Bildschirm. Wir müssen es also einschränken.

Zuerst müssen wir das Rect deklarieren:

private Rect screen;

In init erstellen wir nach dem Initialisieren von Breite und Höhe ein neues Rechteck, das der Bildschirm ist.

screen = new Rect(0,0,WIDTH,HEIGHT);

Nun brauchen wir ein weiteres Rect in Form einer Methode:

private Rect getPlayerBound(){
    return new Rect(x, y, x + PLAYER_BMP.getWidth(), y + PLAYER_BMP.getHeight();
}

und in tick:

if(!getPlayerBound().intersects(screen){
    gameOver = true;
}

Die Implementierung von gameOVer kann auch verwendet werden, um den Start eines Spiels zu zeigen.


Andere Aspekte eines Spiels, die es zu beachten gilt:

Speichern (derzeit fehlt in der Dokumentation)



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow