Membuat Game Space Shooter Berbasis Android
Tugas Kelompok 3
Nama Anggota:
Satrio Yudho Utomo ( 56415435 )
Andre Syafrudin ( 50415719 )
Baruna Adiharto ( 51415269 )
Baruna Adiharto ( 51415269 )
Membuat Game Space Shooter Berbasis Android
Space Shooter
merupakan game klasik yang dari dulu sudah ada dan terus berkembang dengan
gameplay yang lebih menantang, musuh yang beraneka ragam dan tentunya dengan
grafis yang lebih bagus. Berbagai tools jg bisa digunakan untuk membuatnya
seperti Unity, Unreal Engine dan untuk tutorial kali ini kita akan membuatnya
menggunakan Android Studio.
Game ini bisa
dibilang termasuk game legenda yang masih eksis hingga sekarang. Saya pertama
kali memainkan game ini waktu itu masih di console Nintendo dan bagi saya waktu
itu cukup seru untuk dimainkan. Lalu dari waktu ke waktu dengan munculnya
berbagai console baru, game ini berkembang dengan berbagai variasi didalamnya.
Saya
cukup senang memainkan game ini, lalu saya penasaran bagaimana jika game ini
kita sendiri yang buat? Apakah rasa senang itu akan terasa lebih jika
dibandingkan dengan bermain dari game orang lain. Menjawab pertanyaan itu saya
coba untuk membuat game SpaceShooter ini dengan gameplay yang sangat simple dan
tidak memerlukan waktu yang lama.
Sekaligus
mengungkap rasa penasaran saya, apakah bisa kita membuat game dengan android
studio? Dimana android studio ini biasa digunakan untuk membuat aplikasi
android bukan game. Hasil googling sebentar, ternyata banyak tutorial yang
menunjukkan bahwa kita dapat membuatnya lewat android studio, bahkan mungkin
lewat IDE lain seperti Eclipse dan Netbeans pun bisa.
Dalam
pembuatan game pada umumnya dibutuhkan beberapa orang dengan talenta
masing-masing, karena dalam pembuatan game tidak hanya melibatkan orang yang jago
dalam programming saja namun melibatkan orang-orang yang ahli dalam grafis,
pembuatan musik, perancangan gameplay dan masih banyak lagi. Namun ditutorial
kali ini kita akan fokus pada programmingnya saja karena kita akan menggunakan
asset dari luar yang banyak orang sudah menyediakannya secara free.
Berikut adalah daftar asset yang
bisa diunduh untuk nantinya digunakan ditutorial ini:
Lalu dari kumpulan asset diatas
kita akan menggunakan beberapa diantaranya yaitu:
1.
SpaceShip warna biru sebagai
player
2.
Enemy warna oranye dengan 3 model
berbeda
3.
Meteor dengan 3 model berbeda
4.
Star dengan 3 model berbeda
5. Laser sebagai senjata
dari player dengan warna biru
1 hal lagi sebelum masuk ke
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 programnnya. Sekarang
kita buka Android Studio terlebih dahulu. Kemudian buat project baru.
Untuk membuat SpaceShooter simple
ini hanya dibutuhkan 1 Activity yaitu MainActivity. Terdapat beberapa settingan
di MainActivity ini:
MainActivity.java
package com.example.satrioyudhoutomo.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; 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.
·
FLAG_FULLSCREEN akan membuat
tampilan Activity menjadi full screen.
·
FLAG_KEEP_SCREEN_ON akan 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.
·
getWindowManager().getDefaultDisplay()
ini akan mengembalikan ukuran layar yang nantinya akan digunakan untuk
menentukan koordinat berbagai objek.
·
Sensor Accelerometer digunakan
untuk menggerakkan player kekanan dan kekiri.
Tampilan dari MainActivity ini
menggunakan sebuah class yang me-extends SurfaceView.
Kemudian kita akan membuat
GameView.java, codingannya seperti dibawah.
GameView.java
package com.example.satrioyudhoutomo.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; 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 https://developer.android.com/reference/android/view/SurfaceView.html
·
Class ini jua 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.
·
mNewHighScore digunakan sebagai kondisi ketika game over,
jika ada high score baru, maka tampilkan tulisan NewHighScore dan beberapa data
lainnya.
·
Beberapa drawText baru untuk menampilkan jumlah enemy dan
meteor yang berhasil dihancurkan.
·
Ketika game over dan user menekan layar, maka akan berpindah
ke MainMenuActivity.
Kemudian kita akan membuat
SoundPlayer.java agar pada saat kita main game nya ada suara yg keluar.
Codingannya seperti dibawah ini.
SoundPlayer.java
package com.example.satrioyudhoutomo.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; 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 intinya digunakan 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.example.satrioyudhoutomo.spaceshooter; import android.content.Context; import android.content.SharedPreferences; 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.example.satrioyudhoutomo.spaceshooter; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Rect; import java.util.ArrayList; 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 class-class
lain kurang lebih sama konsepnya jadi lebih detailnya bisa dipelajari sendiri.
Kodenya akan saya share digithub dengan harapan game ini bisa terus
dikembangkan.
Kita memerlukan1 activity baru, yaitu MainMenuActivity.
Di
MainMenuActivity akan terdapat 3 button sesuai yang saya jelaskan sebelumnya,
kemudian ada judul game berupa gambar dan background yang juga berupa gambar.
Untuk buttonnya sendiri saya juga buat custom menggunakan gambar dari https://kenney.nl/assets/ui-pack,
kemudian saya konversi ke 9-patch image. Untuk tutorial konversi gambar ke
9-patch image bisa dilihat di https://blog.andevindo.com/memahami-9-patch-image-dan-cara-menggunakannya/.
Berikut gambar untuk judul dan backgroundnya:
Tampilan
akhir dari MainMenuActivity:
Kode untuk custom background buttonnya:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/blue_button01" android:state_pressed="true"/>
<item android:drawable="@drawable/blue_button01" android:state_focused="true"/>
<item android:drawable="@drawable/blue_button00"/>
</selector>
blue_button00 dan
blue_button01 harus dibuat terlebih dahulu, tutorialnya bisa dilihat di https://blog.andevindo.com/memahami-9-patch-image-dan-cara-menggunakannya/
Kode
untuk tampilan MainMenuActivity:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainMenuActivity"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/space_shooter_main_menu_background" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_marginLeft="32dp" android:layout_marginRight="32dp" android:orientation="vertical" android:gravity="center_horizontal"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="80dp" android:adjustViewBounds="true" android:src="@drawable/logo" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingLeft="48dp" android:paddingRight="48dp"> <Button android:id="@+id/play" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:background="@drawable/custom_button_background" android:text="PLAY" android:textColor="@android:color/white" android:textSize="20sp" android:textStyle="bold" /> <Button android:id="@+id/high_score" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:background="@drawable/custom_button_background" android:text="HIGH SCORE" android:textColor="@android:color/white" android:textSize="20sp" android:textStyle="bold" /> <Button android:id="@+id/exit" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/custom_button_background" android:text="EXIT" android:textColor="@android:color/white" android:textSize="20sp" android:textStyle="bold" /> </LinearLayout> </LinearLayout> </RelativeLayout> |
Kode untuk MainMenuActivity
package com.example.satrioyudhoutomo.spaceshooter; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.view.WindowManager; import android.widget.Button; 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; } } } |
Membuat High Score Menu
Kita perlu membuat
activity baru yaitu HighScoreActivity dan berikut adalah tampilannya:
Terdapat back
button, judul, keterangan kalau belum pernah main dan juga keterangan jumlah
score, meteor dan pesawat musuh yang dihancurkan ketika mendapatkan high score.
Kode untuk tampilan HighScoreActivity:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".HighScoreActivity"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/space_shooter_main_menu_background" /> <ImageView android:layout_margin="16dp" android:id="@+id/back" android:src="@drawable/arrow_left_circle" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <LinearLayout android:layout_marginLeft="32dp" android:layout_marginRight="32dp" android:layout_marginTop="80dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical"> <TextView android:text="HIGH SCORE" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/white" android:fontFamily="@font/iceberg_regular" android:textSize="32dp"/> <TextView android:id="@+id/null_high_score" android:layout_marginTop="16dp" android:text="Belum ada high score" android:textColor="@android:color/white" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <LinearLayout android:id="@+id/high_score_container" android:layout_marginTop="16dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:background="@android:color/white" android:paddingLeft="48dp" android:paddingRight="48dp" android:paddingTop="16dp" android:paddingBottom="16dp" android:gravity="center_horizontal"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginBottom="16dp"> <TextView android:textStyle="bold" android:text="SCORE: " android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:textStyle="bold" android:id="@+id/score" android:text="100" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:layout_marginBottom="16dp"> <ImageView android:src="@drawable/meteor_1" android:layout_width="48dp" android:layout_height="wrap_content" android:adjustViewBounds="true" android:layout_marginRight="8dp"/> <TextView android:textStyle="bold" android:id="@+id/meteor" android:text="x100" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical"> <ImageView android:src="@drawable/enemy_red_1" android:layout_width="48dp" android:layout_height="wrap_content" android:adjustViewBounds="true" android:layout_marginRight="8dp"/> <TextView android:textStyle="bold" android:id="@+id/enemy" android:text="x100" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout> </LinearLayout> </RelativeLayout> |
Kode
untuk HighScoreActivity
package com.example.satrioyudhoutomo.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; } } } |
Nah sekarang kita akan Run Game
Space Shooter-nya.
Pada saat dijalankan, game akan menampilkan sebuah
Splash Screen yang berisi tentang nama permainan ini. Tampilannya seperti ini:
Kemudian setelah Splash Screen, game akan masuk
ke halaman Main Menu. Di dalam Main Menu terdapat tombol button Play, High
Score dan Exit. Pada bagian High Score ini berisi tentang high score yang sudah
di mainkan. Tampilannya seperti ini :
Selanjutnya ketika menekan tombol play maka
akan tampil menu game dan dapat langsung dimainkan. Pada bagian ini terdapat
pesawat luar angkasa sebagai pemain, terdapat musuh seperti pesawat alien dan
meteor untuk membuat pemain Game Over dan tidak lupa juga dengan adanya score
yang dapat bertambah dengan menghancurkan pesawat alien dan meteor.