Script dan Story Board Game Space Shooter berbasis Android
Script 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:
1.
http://www.kenney.nl/assets/space-shooter-redux,
sebagian besar asset didapatkan dari sini
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. 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.
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.
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 darihttps://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:
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
Untuk
gambar back button bisa didownload di https://materialdesignicons.com/
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; } } } |
Story Board Game Space
Shooter berbasis Android Studio
Game ini bercerita tentang sebuah karakter
pesawat luar angkasa sebagai jagoannya. Dimana nanti pesawat luar angkasa ini
sebanyak mungkin harus menghancurkan benda-benda di luar angkasa seperti meteor
dan pesawat alien, semakin banyak pemain menghancurkan pesawat alien dan meteor
akan menambah skor dan jika pesawat luar angkasa mengenai salah satu dari itu.
Maka player akan game Over.
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.
Bagan Story Board Game
Space Shooter