Zoeken…


Invoering

Een korte introductie over het maken van een game op het Android-platform met behulp van Java

Opmerkingen

  • Het eerste voorbeeld behandelt de basis: er zijn geen doelstellingen, maar het laat zien hoe je een basisonderdeel van een 2D-spel maakt met SurfaceView.
  • Zorg ervoor dat je belangrijke gegevens opslaat wanneer je een game maakt; al het andere zal verloren gaan

Spel met Canvas en SurfaceView

Hierin wordt uitgelegd hoe je een eenvoudig 2D-spel kunt maken met SurfaceView.


Eerst hebben we een activiteit nodig:

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

}

De activiteit moet ook worden aangegeven in het Android-manifest.


Nu voor het spel zelf. Eerst beginnen we met het implementeren van een speldraad:

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

Dat is het basisgedeelte. Nu hebt u de mogelijkheid om op het scherm te tekenen.

Laten we beginnen met het toevoegen aan gehele getallen:

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;

Voor dit volgende deel heb je een afbeelding nodig. Het moet ongeveer 100x100 zijn, maar het kan groter of kleiner zijn. Om te leren kan een Rect ook worden gebruikt (maar dat vereist een kleine wijziging in de code)

Nu verklaren we een bitmap:

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

Voor weergave moeten we deze bitmap tekenen.

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

VOOR HET STARTEN moeten er nog enkele dingen worden gedaan

We hebben eerst een Boolean nodig:

boolean up = false;

in onTouchEvent voegen we toe:

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

En als teken hebben we dit nodig om de speler te verplaatsen:

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

en nu hebben we dit in eerste instantie nodig:

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();

En we hebben deze nodig voor variabelen:

public static int WIDTH, HEIGHT;

Op dit moment is het spel uit te voeren. Dit betekent dat u het kunt starten en testen.


Nu zou je een spelersafbeelding of rect op en neer op het scherm moeten hebben. De speler kan indien nodig als een aangepaste klasse worden gemaakt. Vervolgens kunnen alle spelersgerelateerde dingen naar die klasse worden verplaatst en een instantie van die klasse gebruiken om te verplaatsen, renderen en andere logica uit te voeren.

Nu, zoals je waarschijnlijk tijdens het testen zag, vliegt het van het scherm. We moeten het dus beperken.

Eerst moeten we de rect verklaren:

private Rect screen;

In init, na het initialiseren van breedte en hoogte, maken we een nieuw rect dat het scherm is.

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

Nu hebben we nog een rect in de vorm van een methode nodig:

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

en vink aan:

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

De implementatie van gameOVer kan ook worden gebruikt om het begin van een game te tonen.


Andere aspecten van een game die het vermelden waard zijn:

Opslaan (momenteel ontbreekt in documentatie)



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow