CARA MEMBUAT GAME "SPACE SHOOTER" DI ANDROID STUDIO

CARA MEMBUAT GAME "SPACE SHOOTER" DI ANDROID STUDIO





cara membuat game space shooter


Jakarta, 21 Agust 2019

Apakah kalian pernah memainkan game Space Shooter?atau Galaga? Memang game tersebut adalah game klasik, tetapi game tersebut tetap masih enak di mainkan. Nah tentu kita bangga jika kita sendiri bisa membuat game tersebut. Di tutorial ini akan kita coba membuat nya.

Yang harus kita siapkan :
  1. Android Studio --> ini uda pasti
  2. Asset berupa gambar, suara --> bisa di ambil dari google atau download sini untuk gambar dan download sini untuk suara

ambil beberapa gambar untuk :
  1. SpaceShip warna Merah sebagai player
  2. Enemy warna oranye,gray,biru dengan 3 model berbeda
  3. Meteor dengan 3 model berbeda
  4. Star dengan 3 model berbeda
  5. Laser sebagai senjata dari player dengan warna merah
Catatan penting sebelum masuk ke script programnya yaitu koordinat di android, ini penting karena nanti dalam pembuatan objek atau pembaruan posisi objek akan selalu menggunakan posisi koordinat. Jadi di Android mempunyai sistem koordinat seperti ini:
  1. 0,0 itu letaknya di sudut kiri atas
  2. maxX, 0 itu letaknya di sudut kanan atas
  3. 0, maxY itu letaknya di sudut kiri bawah
  4. maxX, maxY itu letaknya di sudut kanan bawah
Jadi untuk bergerak dari kiri ke kanan maka nilai xnya ditambah dan kebalikannya jika ingin bergerak dari kanan ke kiri maka nilai xnya dikurangin. Berlaku jg untuk axis y, jika ingin bergerak turun maka nilai ynya ditambah, jika ingin bergerak naik maka nilai ynya dikurangin.
Objek yang digambarpun juga mempunyai sistem koordinat yang sama, nilai posisi 0,0 itu berada dikiri atas
Jika asset sudah siap dan koordinat sudah paham kita masuk ke script programnnya. Untuk membuat SpaceShooter simple ini hanya dibutuhkan 1 Activity yaitu MainActivity. 
Terdapat beberapa settingan di MainActivity ini:

MainActivity.java

package com.henry.spaceshooter;
import android.content.Context;import android.graphics.Paint;import android.graphics.Point;import android.hardware.Sensor;import android.hardware.SensorEvent;import android.hardware.SensorEventListener;import android.hardware.SensorManager;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.Display;import android.view.WindowManager;
/** * Created on   : 8/21/2019 * Developed by : Go Henry * Github       : https://github.com/andevindo */public class MainActivity extends AppCompatActivity implements SensorEventListener{

    private GameView mGameView;    private float mXTemp;
    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);        //Membuat tampilan menjadi full screen        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        //Membuat tampilan selalu menyala jika activity aktif        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        //Mendapatkan ukuran layar        Display display = getWindowManager().getDefaultDisplay();        Point point = new Point();        display.getSize(point);        Log.d("X and Y size", "X = " + point.x + ", Y = " + point.y);
        mGameView = new GameView(this, point.x, point.y);        setContentView(mGameView);
        //Sensor Accelerometer digunakan untuk menggerakan player ke kanan dan ke kiri        SensorManager manager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);        Sensor accelerometer = manager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0);        manager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME);    }

    @Override    protected void onResume() {
        super.onResume();        mGameView.resume();    }

    @Override    protected void onPause() {
        super.onPause();        mGameView.pause();    }

    @Override    public void onSensorChanged(SensorEvent event) {
        mXTemp = event.values[0];
        if (event.values[0] > 1){
            mGameView.steerLeft(event.values[0]);        }
        else if (event.values[0] < -1){
            mGameView.steerRight(event.values[0]);        }else{
            mGameView.stay();        }
    }

    @Override    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }
}
Terlihat dari kode diatas beberapa settingan diperlukan agar game ini bisa berjalan dengan baik.
  1. FLAG_FULLSCREEN --> fungsinya adalah membuat tampilan Activity menjadi full screen.
  2. FLAG_KEEP_SCREEN_ON --> fungsinya adalah membuat smartphone on, kenapa diperlukan ini karena controller dari game ini berupa sensor Accelerometer yang membuat kita tidak menyentuh layar dan hanya menggoyangkan hp saja. Ini akan membuat layar off jika tidak ada interaksi sentuhan selama beberapa detik atau menit tergantung setingan hp.
  3. getWindowManager().getDefaultDisplay() --> fungsinya adalah mengembalikan ukuran layar yang nantinya akan digunakan untuk menentukan koordinat berbagai objek.
  4. Sensor Accelerometer --> fungsinya adalah untuk menggerakkan player kekanan dan kekiri.
  5. Tampilan dari MainActivity ini menggunakan sebuah class yang me-extends SurfaceView.

GameView.java

package com.henry.spaceshooter;
import android.app.Activity;import android.content.Context;import android.content.Intent;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Rect;import android.util.Log;import android.view.MotionEvent;import android.view.SurfaceHolder;import android.view.SurfaceView;
import java.util.ArrayList;import java.util.Random;
/** * Created on   : 8/21/2019 * Developed by : Go Henry * Github       : https://github.com/andevindo */
public class GameView extends SurfaceView implements Runnable {

    private Thread mGameThread;    private volatile boolean mIsPlaying;    private Player mPlayer;    private Paint mPaint;    private Canvas mCanvas;    private SurfaceHolder mSurfaceHolder;    private ArrayList<Laser> mLasers;    private ArrayList<Meteor> mMeteors;    private ArrayList<Enemy> mEnemies;    private ArrayList<Star> mStars;    private int mScreenSizeX, mScreenSizeY;    private int mCounter = 0;    private SoundPlayer mSoundPlayer;    private SharedPreferencesManager mSP;    public static int SCORE = 0;    public static int METEOR_DESTROYED = 0;    public static int ENEMY_DESTROYED = 0;    private volatile boolean mIsGameOver;    private volatile boolean mNewHighScore;
    public GameView(Context context, int screenSizeX, int screenSizeY) {
        super(context);
        mScreenSizeX = screenSizeX;        mScreenSizeY = screenSizeY;        mSP = new SharedPreferencesManager(context);
        mSoundPlayer = new SoundPlayer(context);        mPaint = new Paint();        mSurfaceHolder = getHolder();
        reset();    }

    void reset() {
        SCORE = 0;        mPlayer = new Player(getContext(), mScreenSizeX, mScreenSizeY, mSoundPlayer);        mLasers = new ArrayList<>();        mMeteors = new ArrayList<>();        mEnemies = new ArrayList<>();        mStars = new ArrayList<>();        for (int i = 0; i < 20; i++) {
            mStars.add(new Star(getContext(), mScreenSizeX, mScreenSizeY, true));        }
        mIsGameOver = false;        mNewHighScore = false;    }

    @Override    public void run() {
        while (mIsPlaying) {
            if (!mIsGameOver) {
                update();                draw();                control();            }
        }
        Log.d("GameThread", "Run stopped");    }

    public void update() {
        mPlayer.update();        if (mCounter % 200 == 0) {
            mPlayer.fire();        }

        for (Meteor m : mMeteors) {
            m.update();
            if (Rect.intersects(m.getCollision(), mPlayer.getCollision())) {
                m.destroy();                mIsGameOver = true;                if (SCORE>mSP.getHighScore()){
                    mNewHighScore = true;                    mSP.saveHighScore(SCORE, METEOR_DESTROYED, ENEMY_DESTROYED);                }
            }

            for (Laser l : mPlayer.getLasers()) {
                if (Rect.intersects(m.getCollision(), l.getCollision())) {
                    m.hit();                    l.destroy();                }
            }
        }

        boolean deleting = true;        while (deleting) {
            if (mMeteors.size() != 0) {
                if (mMeteors.get(0).getY() > mScreenSizeY) {
                    mMeteors.remove(0);                }
            }

            if (mMeteors.size() == 0 || mMeteors.get(0).getY() <= mScreenSizeY) {
                deleting = false;            }
        }
        if (mCounter % 1000 == 0) {
            mMeteors.add(new Meteor(getContext(), mScreenSizeX, mScreenSizeY, mSoundPlayer));        }

        for (Enemy e : mEnemies) {
            e.update();            if (Rect.intersects(e.getCollision(), mPlayer.getCollision())) {
                e.destroy();                mIsGameOver = true;                if (SCORE>=mSP.getHighScore()){
                    mSP.saveHighScore(SCORE, METEOR_DESTROYED, ENEMY_DESTROYED);                }
            }

            for (Laser l : mPlayer.getLasers()) {
                if (Rect.intersects(e.getCollision(), l.getCollision())) {
                    e.hit();                    l.destroy();                }
            }
        }
        deleting = true;        while (deleting) {
            if (mEnemies.size() != 0) {
                if (mEnemies.get(0).getY() > mScreenSizeY) {
                    mEnemies.remove(0);                }
            }

            if (mEnemies.size() == 0 || mEnemies.get(0).getY() <= mScreenSizeY) {
                deleting = false;            }
        }
        if (mCounter % 2000 == 0) {
            mEnemies.add(new Enemy(getContext(), mScreenSizeX, mScreenSizeY, mSoundPlayer));        }

        for (Star s : mStars) {
            s.update();        }
        deleting = true;        while (deleting) {
            if (mStars.size() != 0) {
                if (mStars.get(0).getY() > mScreenSizeY) {
                    mStars.remove(0);                }
            }

            if (mStars.size() == 0 || mStars.get(0).getY() <= mScreenSizeY) {
                deleting = false;            }
        }

        if (mCounter % 250 == 0) {
            Random random = new Random();            for (int i = 0; i < random.nextInt(3) + 1; i++) {
                mStars.add(new Star(getContext(), mScreenSizeX, mScreenSizeY, false));            }

        }


    }

    public void draw() {
        if (mSurfaceHolder.getSurface().isValid()) {
            mCanvas = mSurfaceHolder.lockCanvas();            mCanvas.drawColor(Color.BLACK);            mCanvas.drawBitmap(mPlayer.getBitmap(), mPlayer.getX(), mPlayer.getY(), mPaint);            for (Star s : mStars) {
                mCanvas.drawBitmap(s.getBitmap(), s.getX(), s.getY(), mPaint);            }
            for (Laser l : mPlayer.getLasers()) {
                mCanvas.drawBitmap(l.getBitmap(), l.getX(), l.getY(), mPaint);            }
            for (Meteor m : mMeteors) {
                mCanvas.drawBitmap(m.getBitmap(), m.getX(), m.getY(), mPaint);            }
            for (Enemy e : mEnemies) {
                mCanvas.drawBitmap(e.getBitmap(), e.getX(), e.getY(), mPaint);            }
            drawScore();            if (mIsGameOver) {
                drawGameOver();            }
            mSurfaceHolder.unlockCanvasAndPost(mCanvas);        }
    }

    void drawScore() {
        Paint score = new Paint();        score.setTextSize(30);        score.setColor(Color.WHITE);        mCanvas.drawText("Score : " + SCORE, 100, 50, score);    }

    void drawGameOver() {
        Paint gameOver = new Paint();        gameOver.setTextSize(100);        gameOver.setTextAlign(Paint.Align.CENTER);        gameOver.setColor(Color.WHITE);        mCanvas.drawText("GAME OVER", mScreenSizeX / 2, mScreenSizeY / 2, gameOver);        Paint highScore = new Paint();        highScore.setTextSize(50);        highScore.setTextAlign(Paint.Align.CENTER);        highScore.setColor(Color.WHITE);        if (mNewHighScore){
            mCanvas.drawText("New High Score : " + mSP.getHighScore(), mScreenSizeX / 2, (mScreenSizeY / 2) + 60, highScore);            Paint enemyDestroyed = new Paint();            enemyDestroyed.setTextSize(50);            enemyDestroyed.setTextAlign(Paint.Align.CENTER);            enemyDestroyed.setColor(Color.WHITE);            mCanvas.drawText("Enemy Destroyed : " + mSP.getEnemyDestroyed(), mScreenSizeX / 2, (mScreenSizeY / 2) + 120, enemyDestroyed);            Paint meteorDestroyed = new Paint();            meteorDestroyed.setTextSize(50);            meteorDestroyed.setTextAlign(Paint.Align.CENTER);            meteorDestroyed.setColor(Color.WHITE);            mCanvas.drawText("Meteor Destroyed : " + mSP.getMeteorDestroyed(), mScreenSizeX / 2, (mScreenSizeY / 2) + 180, meteorDestroyed);        }

    }

    public void steerLeft(float speed) {
        mPlayer.steerLeft(speed);    }

    public void steerRight(float speed) {
        mPlayer.steerRight(speed);    }

    public void stay() {
        mPlayer.stay();    }

    public void control() {
        try {
            if (mCounter == 10000) {
                mCounter = 0;            }
            mGameThread.sleep(20);            mCounter += 20;        } catch (InterruptedException e) {
            e.printStackTrace();        }
    }

    public void pause() {
        Log.d("GameThread", "Main");        mIsPlaying = false;        try {
            mGameThread.join();            mSoundPlayer.pause();        } catch (InterruptedException e) {
            e.printStackTrace();        }
    }

    public void resume() {
        mIsPlaying = true;        mSoundPlayer.resume();        mGameThread = new Thread(this);        mGameThread.start();    }

    @Override    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (mIsGameOver){
                    ((Activity) getContext()).finish();                    getContext().startActivity(new Intent(getContext(), MainMenuActivity.class));                }
                break;        }
        return super.onTouchEvent(event);    }
}
Cukup banyak kode diclass ini, saya coba untuk menjelaskannya satu persatu.
  • Class ini extends SurfaceView dimana nantinya sebagai media untuk menempatkan beberapa objek diatasnya. Untuk lebih detailnya tentang SurfaceView bisa dibaca di link ini
  • Class ini juga implements Runnable karena didalam class ini akan dibuat sebuah thread baru yang nantinya digunakan untuk menggambarkan objek dengan posisi dan kondisi yang berbeda-beda setiap framenya.
  • Perlu dicermati pada method run(), didalamnya memanggil method update(), draw(), control() secara berulang-ulang. update() digunakan untuk melakukan perubahan disemua objek yang dibentuk ataupun bisa melakukan pembuatan dan penghapusan objek. draw() ini digunakan untuk menggambarkan objek dengan keadaan yang telah diupdate sebelumnya. Lalu didalam method control() terdapat Thread.sleep(20) itu artinya Thread akan berhenti selama 20 miliseconds.
  • Dari yang saya amati control() digunakan untuk mengatur jumlah frame per secondnya. Di kasus ini digunakan Thread.sleep(20) itu artinya dalam 1000 miliseconds atau 1 detik akan terdapat 1000/20 = 50 pemanggilan update() dan draw(), jadi ada 50 frame/second.
  • Lalu ada method pause() dan resume(), kedua method ini dipanggil dari MainActivity yang berfungsi untuk menghentikan Thread dan membuat Thread baru.
  • Setelah itu ada method steerLeft(), steerRight() dan stay(), ini juga dipanggil dari MainActivity ketika sensor berubah nilainya.
  • reset() ini membuat tampilan seperti pada awal sebelum game dimulai.
  • onTouchEvent() merupakan Listener yang menangkap response sentuhan dari user di smartphone. Method ini digunakan ketika sudah gameover dan ingin memulai game kembali yaitu dengan menyentuhnya sekali.

SoundPlayer.java

package com.henry.spaceshooter;
import android.content.Context;import android.media.AudioAttributes;import android.media.AudioManager;import android.media.SoundPool;import android.os.Build;import android.util.Log;
/** * Created on   : 8/21/2019 * Developed by : Go Henry * Github       : https://github.com/andevindo */
public class SoundPlayer implements Runnable {

    private Thread mSoundThread;    private volatile boolean mIsPlaying;    private SoundPool mSoundPool;    private int mExplodeId, mLaserId, mCrashId;    private boolean mIsLaserPlaying, mIsExplodePlaying, mIsCrashPlaying;
    public SoundPlayer(Context context){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            AudioAttributes attributes = new AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_GAME)
                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                    .build();            mSoundPool = new SoundPool.Builder()
                    .setMaxStreams(10)
                    .setAudioAttributes(attributes)
                    .build();        } else {
            mSoundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 1);        }
        mExplodeId = mSoundPool.load(context, R.raw.rock_explode_1, 1);        mLaserId = mSoundPool.load(context, R.raw.laser_1, 1);        mCrashId = mSoundPool.load(context, R.raw.rock_explode_2, 1);    }

    @Override    public void run() {
        while (mIsPlaying){
            if (mIsLaserPlaying){
                mSoundPool.play(mLaserId, 1, 1, 1, 0, 1f);                mIsLaserPlaying = false;            }

            if (mIsExplodePlaying){
                mSoundPool.play(mExplodeId, 1, 1, 1, 0, 1);                mIsExplodePlaying = false;            }

            if (mIsCrashPlaying){
                mSoundPool.play(mCrashId, 1, 1, 1, 0, 1);                mIsCrashPlaying = false;            }
        }
    }

    public void playCrash(){
        mIsCrashPlaying = true;    }

    public void playLaser(){
        mIsLaserPlaying = true;    }

    public void playExplode(){
        mIsExplodePlaying = true;    }

    public void resume(){
        mIsPlaying = true;        mSoundThread = new Thread(this);        mSoundThread.start();    }

    public void pause() throws InterruptedException {
        Log.d("GameThread", "Sound");        mIsPlaying = false;        mSoundThread.join();    }
}
Class ini fungsinya hanya untuk memainkan suara. Class ini juga menggunakan thread baru karena waktu pertama saya coba tanpa menggunakan thread baru terkadang ada lag didalam game.

SharedPreferencesManager.java

package com.henry.spaceshooter;
import android.content.Context;import android.content.SharedPreferences;
/** * Created on   : 8/21/2019 * Developed by : Go Henry * Github       : https://github.com/andevindo */
public class SharedPreferencesManager {

    private String mName = "SpaceShooter";    private Context mContext;
    public SharedPreferencesManager(Context context) {
        mContext = context;    }

    public void saveHighScore(int score, int meteorDestroyed, int enemyDestroyed){
        SharedPreferences sp = mContext.getSharedPreferences(mName, Context.MODE_PRIVATE);        SharedPreferences.Editor e = sp.edit();        e.putInt("high_score", score);        e.putInt("meteor", meteorDestroyed);        e.putInt("enemy", enemyDestroyed);        e.commit();    }

    public int getHighScore(){
        SharedPreferences sp = mContext.getSharedPreferences(mName, Context.MODE_PRIVATE);        return sp.getInt("high_score", 0);    }

    public int getMeteorDestroyed(){
        SharedPreferences sp = mContext.getSharedPreferences(mName, Context.MODE_PRIVATE);        return sp.getInt("meteor", 0);    }

    public int getEnemyDestroyed(){
        SharedPreferences sp = mContext.getSharedPreferences(mName, Context.MODE_PRIVATE);        return sp.getInt("enemy", 0);    }
}
Class ini digunakan untuk memudahkan dalam menyimpan dan memanggil nilai high score.
Lalu kemudian tersisa class-class yang berperan sebagai :
  1. Player
  2. Meteor
  3. Laser
  4. Star
  5. Enemy
Didalam class-class tersebut semuanya terdapat method udpate() yang fungsinya untuk memperbarui posisi dan kondisi objek yang dibuat dari class tersebut. Saya ambil contoh untuk class Player:

Player.java

package com.andevindo.spaceshooter;
import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Rect;import android.util.Log;
import java.util.ArrayList;
/** * Created on   : 8/21/2019         * Developed by : Go Henry         * Github       : https://github.com/andevindo         */
public class Player {

    private Bitmap mBitmap;
    private int mX;    private int mY;    private int mSpeed;    private int mMaxX;    private int mMinX;    private int mMaxY;    private int mMinY;    private int mMargin = 16;    private boolean mIsSteerLeft, mIsSteerRight;    private float mSteerSpeed;    private Rect mCollision;    private ArrayList<Laser> mLasers;    private SoundPlayer mSoundPlayer;    private Context mContext;    private int mScreenSizeX, mScreenSizeY;
    public Player(Context context, int screenSizeX, int screenSizeY, SoundPlayer soundPlayer) {
        mScreenSizeX = screenSizeX;        mScreenSizeY = screenSizeY;        mContext = context;
        mSpeed = 1;        mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.spaceship_1_blue);        mBitmap = Bitmap.createScaledBitmap(mBitmap, mBitmap.getWidth() * 3/5, mBitmap.getHeight() * 3/5, false);
        mMaxX = screenSizeX - mBitmap.getWidth();        mMaxY = screenSizeY - mBitmap.getHeight();        mMinX = 0;        mMinY = 0;
        mX = screenSizeX/2 - mBitmap.getWidth()/2;        mY = screenSizeY - mBitmap.getHeight() - mMargin;
        mLasers = new ArrayList<>();        mSoundPlayer = soundPlayer;
        mCollision = new Rect(mX, mY, mX + mBitmap.getWidth(), mY + mBitmap.getHeight());    }

    public void update(){
        if (mIsSteerLeft){
            mX -= 10 * mSteerSpeed;            if (mX<mMinX){
                mX = mMinX;            }
        }else if (mIsSteerRight){
            mX += 10 * mSteerSpeed;            if (mX>mMaxX){
                mX = mMaxX;            }
        }

        mCollision.left = mX;        mCollision.top = mY;        mCollision.right = mX + mBitmap.getWidth();        mCollision.bottom = mY + mBitmap.getHeight();
        for (Laser l : mLasers) {
            l.update();        }

        boolean deleting = true;        while (deleting) {
            if (mLasers.size() != 0) {
                if (mLasers.get(0).getY() < 0) {
                    mLasers.remove(0);                }
            }

            if (mLasers.size() == 0 || mLasers.get(0).getY() >= 0) {
                deleting = false;            }
        }
    }

    public ArrayList<Laser> getLasers() {
        return mLasers;    }

    public void fire(){
        mLasers.add(new Laser(mContext, mScreenSizeX, mScreenSizeY, mX, mY, mBitmap, false));        mSoundPlayer.playLaser();    }

    public Rect getCollision() {
        return mCollision;    }

    public void steerRight(float speed){
        mIsSteerLeft = false;        mIsSteerRight = true;        mSteerSpeed = Math.abs(speed);    }

    public void steerLeft(float speed){
        mIsSteerRight = false;        mIsSteerLeft = true;        mSteerSpeed = Math.abs(speed);    }

    public void stay(){
        mIsSteerLeft = false;        mIsSteerRight = false;        mSteerSpeed = 0;    }

    public Bitmap getBitmap() {
        return mBitmap;    }

    public int getX() {
        return mX;    }

    public int getY() {
        return mY;    }

    public int getSpeed() {
        return mSpeed;    }
}
Sedikit penjelasan mengenai kode diatas, terdapat pengecekan apakah mIsSteerLeft == true, jika true maka mX -= 10 * mSteerSpeed akan dipanggil yang artinya posisi player dalam axis x akan bergeser ke kiri dengan nilai 10 * mSteerSpeed. mSteerSpeed ini digunakan untuk menentukan seberapa besar perpindahan objek. Biasanya dalam pembuatan game jika menggerakkan sesuatu, speednya lah yang akan diubah nilainya.
Didalam method update() di class Player ini, juga terdapat kode lain seperti mCollision. Ini adalah sebuah variabel Rect yang mempunyai nilai pada masing-masing sisinya. Ini digunakan untuk mendeteksi tabrakan antar objek. Seperti meteor menabrak player atau laser mengenai meteor.
Lalu juga ada kode untuk memperbarui posisi dan kondisi laser yang dikeluarkan dari player. Laser akan dibuat didalam class player ini ketika method fire() dipanggil dari class GameView. Laser juga akan dihancurkan jika nilai Ynya kurang dari 0, ini dilakukan agar memori tidak penuh.
Untuk Script selanjutnya keterangan sudah di jelaskan di atas, hanya tinggal copy saja script nya.

Laser.java

package com.henry.spaceshooter;
import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Rect;
/** * Created on   : 8/21/2019 * Developed by : Go Henry * Github       : https://github.com/andevindo */
public class Laser {

    private Bitmap mBitmap;    private int mX;    private int mY;    private Rect mCollision;    private int mScreenSizeX;    private int mScreenSizeY;    private boolean mIsEnemy;
    public Laser(Context context, int screenSizeX, int screenSizeY, int spaceShipX, int spaceShipY, Bitmap spaceShip, boolean isEnemy){
        mScreenSizeX = screenSizeX;        mScreenSizeY = screenSizeY;        mIsEnemy = isEnemy;
        mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.laser_1);        mBitmap = Bitmap.createScaledBitmap(mBitmap, mBitmap.getWidth() * 3/5, mBitmap.getHeight() * 3/5, false);
        mX = spaceShipX + spaceShip.getWidth()/2 - mBitmap.getWidth()/2;        if (mIsEnemy){
            mY = spaceShipY + mBitmap.getHeight() + 10;        }else{
            mY = spaceShipY - mBitmap.getHeight() - 10;        }

        mCollision = new Rect(mX, mY, mX + mBitmap.getWidth(), mY + mBitmap.getHeight());    }

    public void update(){
        if (mIsEnemy){
            mY += mBitmap.getHeight() + 10;            mCollision.left = mX;            mCollision.top = mY;            mCollision.right = mX + mBitmap.getWidth();            mCollision.bottom = mY + mBitmap.getHeight();        }else{
            mY -= mBitmap.getHeight() - 10;            mCollision.left = mX;            mCollision.top = mY;            mCollision.right = mX + mBitmap.getWidth();            mCollision.bottom = mY + mBitmap.getHeight();        }

    }

    public boolean isEnemy() {
        return mIsEnemy;    }

    public Rect getCollision() {
        return mCollision;    }

    public void destroy(){
        if (mIsEnemy){
            mY = mScreenSizeY;        }else{
            mY = 0 - mBitmap.getHeight();        }

    }

    public Bitmap getBitmap() {
        return mBitmap;    }

    public int getX() {
        return mX;    }

    public int getY() {
        return mY;    }
}

HighScoreActivity.java

package com.henry.spaceshooter;
import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.TextView;
public class HighScoreActivity extends AppCompatActivity implements View.OnClickListener {

    private ImageView mBack;    private TextView mScore, mMeteor, mEnemy, mNullHighScore;    private LinearLayout mHighScoreContainer;
    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_high_score);
        mBack = findViewById(R.id.back);        mScore = findViewById(R.id.score);        mMeteor = findViewById(R.id.meteor);        mEnemy = findViewById(R.id.enemy);        mNullHighScore = findViewById(R.id.null_high_score);        mHighScoreContainer = findViewById(R.id.high_score_container);        mBack.setOnClickListener(this);
        loadHighScore();    }

    void loadHighScore(){
        SharedPreferencesManager spm = new SharedPreferencesManager(this);        if (spm.getHighScore()!=-1){
            mNullHighScore.setVisibility(TextView.GONE);            mHighScoreContainer.setVisibility(LinearLayout.VISIBLE);            mScore.setText(spm.getHighScore() + "");            mMeteor.setText(spm.getMeteorDestroyed() + "");            mEnemy.setText(spm.getEnemyDestroyed() + "");        }else{
            mNullHighScore.setVisibility(TextView.VISIBLE);            mHighScoreContainer.setVisibility(LinearLayout.GONE);        }
    }

    @Override    public void onClick(View v) {
        switch (v.getId()){
            case R.id.back:
                finish();                break;        }
    }
}

MainMenuActivity.java

package com.henry.spaceshooter;
import android.content.Intent;import android.os.Bundle;import android.support.design.widget.FloatingActionButton;import android.support.design.widget.Snackbar;import android.support.v7.app.AppCompatActivity;import android.support.v7.widget.Toolbar;import android.view.View;import android.view.WindowManager;import android.widget.Button;
/** * Created on   : 8/21/2019 * Developed by : Go Henry * Github       : https://github.com/andevindo */public class MainMenuActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mPlay, mHighScore, mExit;
    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main_menu);
        //Membuat tampilan menjadi full screen        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        //Membuat tampilan selalu menyala jika activity aktif        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        mPlay = findViewById(R.id.play);        mHighScore = findViewById(R.id.high_score);        mExit = findViewById(R.id.exit);
        mPlay.setOnClickListener(this);        mHighScore.setOnClickListener(this);        mExit.setOnClickListener(this);    }

    @Override    public void onClick(View v) {
        switch (v.getId()){
            case R.id.play:
                startActivity(new Intent(this, MainActivity.class));                finish();                break;            case R.id.high_score:
                startActivity(new Intent(this, HighScoreActivity.class));                break;            case R.id.exit:
                finish();                break;        }
    }
}

Meteor.java

package com.henry.spaceshooter;
import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Matrix;import android.graphics.Rect;
import java.util.Random;
import static com.andevindo.spaceshooter.GameView.METEOR_DESTROYED;import static com.andevindo.spaceshooter.GameView.SCORE;
/** * Created on   : 8/21/2019 * Developed by : Go Henry * Github       : https://github.com/andevindo */
public class Meteor {

    private Bitmap mBitmap;    private int mX;    private int mY;    private int mMaxX;    private int mMinX;    private int mMaxY;    private int mMinY;
    private int mSpeed;    private Rect mCollision;    private int mScreenSizeX;    private int mScreenSizeY;    private int mHP;    private SoundPlayer mSoundPlayer;
    public Meteor(Context context, int screenSizeX, int screenSizeY, SoundPlayer soundPlayer){
        mScreenSizeX = screenSizeX;        mScreenSizeY = screenSizeY;        mSoundPlayer = soundPlayer;
        mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.meteor_1);        mBitmap = Bitmap.createScaledBitmap(mBitmap, mBitmap.getWidth() * 3/5, mBitmap.getHeight() * 3/5, false);
        mMaxX = screenSizeX - mBitmap.getWidth();        mMaxY = screenSizeY - mBitmap.getHeight();        mMinX = 0;        mMinY = 0;        mHP = 3;
        Random random = new Random();        mSpeed = random.nextInt(3) + 1;
        mX = random.nextInt(mMaxX);        mY = 0 - mBitmap.getHeight();
        mCollision = new Rect(mX, mY, mX + mBitmap.getWidth(), mY + mBitmap.getHeight());    }

    public void update(){
        mY += 7 * mSpeed;
        mCollision.left = mX;        mCollision.top = mY;        mCollision.right = mX + mBitmap.getWidth();        mCollision.bottom = mY + mBitmap.getHeight();    }

    public Rect getCollision() {
        return mCollision;    }

    public void hit(){
        if (--mHP ==0){
            SCORE += 20;            METEOR_DESTROYED++;            destroy();        }else{
            mSoundPlayer.playExplode();        }
    }

    public void destroy(){
        mY = mScreenSizeY + 1;        mSoundPlayer.playCrash();    }

    public Bitmap getBitmap() {
        return mBitmap;    }

    public int getX() {
        return mX;    }

    public int getY() {
        return mY;    }
}

Meteor.java

package com.henry.spaceshooter;
import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Matrix;import android.graphics.Rect;
import java.util.Random;
import static com.andevindo.spaceshooter.GameView.METEOR_DESTROYED;import static com.andevindo.spaceshooter.GameView.SCORE;
/** * Created on   : 8/21/2019 * Developed by : Go Henry * Github       : https://github.com/andevindo */
public class Meteor {

    private Bitmap mBitmap;    private int mX;    private int mY;    private int mMaxX;    private int mMinX;    private int mMaxY;    private int mMinY;
    private int mSpeed;    private Rect mCollision;    private int mScreenSizeX;    private int mScreenSizeY;    private int mHP;    private SoundPlayer mSoundPlayer;
    public Meteor(Context context, int screenSizeX, int screenSizeY, SoundPlayer soundPlayer){
        mScreenSizeX = screenSizeX;        mScreenSizeY = screenSizeY;        mSoundPlayer = soundPlayer;
        mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.meteor_1);        mBitmap = Bitmap.createScaledBitmap(mBitmap, mBitmap.getWidth() * 3/5, mBitmap.getHeight() * 3/5, false);
        mMaxX = screenSizeX - mBitmap.getWidth();        mMaxY = screenSizeY - mBitmap.getHeight();        mMinX = 0;        mMinY = 0;        mHP = 3;
        Random random = new Random();        mSpeed = random.nextInt(3) + 1;
        mX = random.nextInt(mMaxX);        mY = 0 - mBitmap.getHeight();
        mCollision = new Rect(mX, mY, mX + mBitmap.getWidth(), mY + mBitmap.getHeight());    }

    public void update(){
        mY += 7 * mSpeed;
        mCollision.left = mX;        mCollision.top = mY;        mCollision.right = mX + mBitmap.getWidth();        mCollision.bottom = mY + mBitmap.getHeight();    }

    public Rect getCollision() {
        return mCollision;    }

    public void hit(){
        if (--mHP ==0){
            SCORE += 20;            METEOR_DESTROYED++;            destroy();        }else{
            mSoundPlayer.playExplode();        }
    }

    public void destroy(){
        mY = mScreenSizeY + 1;        mSoundPlayer.playCrash();    }

    public Bitmap getBitmap() {
        return mBitmap;    }

    public int getX() {
        return mX;    }

    public int getY() {
        return mY;    }
}

Enemy.java

package com.andevindo.spaceshooter;
import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Rect;
import java.util.Random;
import static com.andevindo.spaceshooter.GameView.ENEMY_DESTROYED;import static com.andevindo.spaceshooter.GameView.SCORE;
/** * Created on   : 8/21/2019 * Developed by : Go Henry * Github       : https://github.com/andevindo */
public class Enemy {

    private Bitmap mBitmap;    private int mX;    private int mY;    private Rect mCollision;    private int mScreenSizeX;    private int mScreenSizeY;    private int mEnemies[];    private int mMaxX;    private int mMaxY;    private int mHP;    private int mSpeed;    private boolean mIsTurnRight;    private SoundPlayer mSoundPlayer;
    public Enemy(Context context, int screenSizeX, int screenSizeY, SoundPlayer soundPlayer) {
        mScreenSizeX = screenSizeX;        mScreenSizeY = screenSizeY;        mSoundPlayer = soundPlayer;
        mHP = 5;
        mEnemies = new int[]{R.drawable.enemy_red_1, R.drawable.enemy_red_2, R.drawable.enemy_red_3};        Random random = new Random();        mBitmap = BitmapFactory.decodeResource(context.getResources(), mEnemies[random.nextInt(3)]);        mBitmap = Bitmap.createScaledBitmap(mBitmap, mBitmap.getWidth() * 3/5, mBitmap.getHeight() * 3/5, false);
        mSpeed = random.nextInt(3) + 1;
        mMaxX = screenSizeX - mBitmap.getWidth();        mMaxY = screenSizeY - mBitmap.getHeight();
        mX = random.nextInt(mMaxX);        mY = 0 - mBitmap.getHeight();
        if (mX<mMaxX){
            mIsTurnRight = true;        }else{
            mIsTurnRight = false;        }

        mCollision = new Rect(mX, mY, mX + mBitmap.getWidth(), mY + mBitmap.getHeight());    }

    public void update(){
        mY += 7 * mSpeed;
        if (mX<=0){
            mIsTurnRight = true;        }else if (mX>=mScreenSizeX-mBitmap.getWidth()){
            mIsTurnRight = false;        }

        if (mIsTurnRight){
            mX += 7 * mSpeed;        }else{
            mX -= 7 * mSpeed;        }

        mCollision.left = mX;        mCollision.top = mY;        mCollision.right = mX + mBitmap.getWidth();        mCollision.bottom = mY + mBitmap.getHeight();    }

    public Rect getCollision() {
        return mCollision;    }

    public void hit(){
        if (--mHP ==0){
            SCORE += 50;            ENEMY_DESTROYED++;            destroy();        }else{
            mSoundPlayer.playExplode();        }
    }

    public void destroy(){
        mY = mScreenSizeY + 1;        mSoundPlayer.playCrash();    }

    public Bitmap getBitmap() {
        return mBitmap;    }

    public int getX() {
        return mX;    }

    public int getY() {
        return mY;    }
}

Enemy.java

package com.andevindo.spaceshooter;
import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Rect;
import java.util.Random;
import static com.andevindo.spaceshooter.GameView.ENEMY_DESTROYED;import static com.andevindo.spaceshooter.GameView.SCORE;
/** * Created on   : 8/21/2019 * Developed by : Go Henry * Github       : https://github.com/andevindo */
public class Enemy {

    private Bitmap mBitmap;    private int mX;    private int mY;    private Rect mCollision;    private int mScreenSizeX;    private int mScreenSizeY;    private int mEnemies[];    private int mMaxX;    private int mMaxY;    private int mHP;    private int mSpeed;    private boolean mIsTurnRight;    private SoundPlayer mSoundPlayer;
    public Enemy(Context context, int screenSizeX, int screenSizeY, SoundPlayer soundPlayer) {
        mScreenSizeX = screenSizeX;        mScreenSizeY = screenSizeY;        mSoundPlayer = soundPlayer;
        mHP = 5;
        mEnemies = new int[]{R.drawable.enemy_red_1, R.drawable.enemy_red_2, R.drawable.enemy_red_3};        Random random = new Random();        mBitmap = BitmapFactory.decodeResource(context.getResources(), mEnemies[random.nextInt(3)]);        mBitmap = Bitmap.createScaledBitmap(mBitmap, mBitmap.getWidth() * 3/5, mBitmap.getHeight() * 3/5, false);
        mSpeed = random.nextInt(3) + 1;
        mMaxX = screenSizeX - mBitmap.getWidth();        mMaxY = screenSizeY - mBitmap.getHeight();
        mX = random.nextInt(mMaxX);        mY = 0 - mBitmap.getHeight();
        if (mX<mMaxX){
            mIsTurnRight = true;        }else{
            mIsTurnRight = false;        }

        mCollision = new Rect(mX, mY, mX + mBitmap.getWidth(), mY + mBitmap.getHeight());    }

    public void update(){
        mY += 7 * mSpeed;
        if (mX<=0){
            mIsTurnRight = true;        }else if (mX>=mScreenSizeX-mBitmap.getWidth()){
            mIsTurnRight = false;        }

        if (mIsTurnRight){
            mX += 7 * mSpeed;        }else{
            mX -= 7 * mSpeed;        }

        mCollision.left = mX;        mCollision.top = mY;        mCollision.right = mX + mBitmap.getWidth();        mCollision.bottom = mY + mBitmap.getHeight();    }

    public Rect getCollision() {
        return mCollision;    }

    public void hit(){
        if (--mHP ==0){
            SCORE += 50;            ENEMY_DESTROYED++;            destroy();        }else{
            mSoundPlayer.playExplode();        }
    }

    public void destroy(){
        mY = mScreenSizeY + 1;        mSoundPlayer.playCrash();    }

    public Bitmap getBitmap() {
        return mBitmap;    }

    public int getX() {
        return mX;    }

    public int getY() {
        return mY;    }
}

Star.java

package com.andevindo.spaceshooter;
import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.util.Log;
import java.util.Random;
/** * Created on   : 8/21/2019 * Developed by : Go Henry * Github       : https://github.com/andevindo */
public class Star {

    private Bitmap mBitmap;    private int mX;    private int mY;    private int mMaxX;    private int mSpeed;    private int mScreenSizeX;    private int mScreenSizeY;    private int[] mStars = new int[]{R.drawable.star_1, R.drawable.star_2, R.drawable.star_3};
    public Star(Context context, int screenSizeX, int screenSizeY, boolean randomY){
        mScreenSizeX = screenSizeX;        mScreenSizeY = screenSizeY;
        Random random = new Random();        mBitmap = BitmapFactory.decodeResource(context.getResources(), mStars[random.nextInt(3)]);        float scale = (float)(random.nextInt(3) + 1)/5;        mBitmap = Bitmap.createScaledBitmap(mBitmap, (int)(mBitmap.getWidth() * scale), (int)(mBitmap.getHeight() * scale), false);
        mMaxX = screenSizeX - mBitmap.getWidth();
        mSpeed = random.nextInt(1) + 1;
        mX = random.nextInt(mMaxX);        if (randomY){
            mY = random.nextInt(mScreenSizeY);        }else{
            mY = 0 - mBitmap.getHeight();        }

    }

    public void update(){
        mY += 7 * mSpeed;    }

    public Bitmap getBitmap() {
        return mBitmap;    }

    public int getX() {
        return mX;    }

    public int getY() {
        return mY;    }
}

Jika semua script di atas sudah di ketik / di copy, silakan build, copy ke android dan instal di hp android masing - masing. Hasilnya akan seperti :

space shooter androidspace shooter android
 space shooter android



Sekian tutorial cara membuat game "space shooter" di android. Semoga bermanfaat

Comments

loading...