【18.5.31 日常】Android專案——飛機大戰詳解
整體實現思路
通過兩張背景圖片實現背景滾動,同時做出能根據使用者觸控位置不同而改變位置的飛機,能夠不停地射出子彈,Boss同理,Boss移動方式隨機,同時新增小飛機以增強遊戲的表現力,外加例如鐳射之類的特殊遊戲模組來增強遊戲的可玩性,最後新增主介面和啟動介面來增強遊戲的整體性。
如何繪製迴圈滾動的背景圖片
實現思路
通過編輯兩張同樣照片實現再螢幕上永遠顯示一張圖片,同時當第一張圖片完全移除視窗時迅速的移到另一張圖片的上方相接來製造出背景無限滾動的效果
注意
圖片的選擇與使用必須採用整體對稱相接的,以免出現接不到一起的尷尬,本次遊戲圖片預設第一關使用下圖
程式碼
`Public class BackGround {
private static int y1;
private static int y2;
private Bitmap bitmap;
static int height;
public BackGround(Bitmap bitmap,int height){
this.bitmap = bitmap;
this.height = height;
y1 = 0;
y2 = y1-MySurfaceView.height;
}
public void bGP(Canvas canvas,Paint paint){ //繪製每一幀的背景圖片
bYH();
paint.setColor(Color.RED);
canvas.drawBitmap(bitmap,0,y1,paint);
canvas.drawBitmap(bitmap,0,y2,paint);
}
public void bYH(){ //在每一幀中變換兩張圖片的Y座標實現圖片變化
y1+=10;
y2+=10;
if(y1>=MySurfaceView.height){
y1 = y2-bitmap.getHeight();
}
if (y2>=MySurfaceView.height){
y2 = y1-bitmap.getHeight();
}
}
}`
如何繪製飛機
飛機的整體移動原理
通過建立一個事件監聽器,在使用者每一次對螢幕進行觸控滑動操作時進行監聽,一旦使用者的觸碰點在飛機的座標範圍圍城的矩形範圍內時,就進行資料的賦值,將拖動後的座標賦予該幀的飛機,以此來實現飛機的拖動移動,至於飛機的繪製,直接使用canvas.drawBitmap即可
程式碼片
觸控事件監聽及判斷核心程式碼
public boolean onTouchEvent(MotionEvent event) {
plane.touchEvent(event);
return true; //該句程式碼實現了監聽器永遠監聽的功能
}
public void touchEvent(MotionEvent event) {//該方法處在飛機類中,用來對座標進行判斷並對飛機座標進行賦值
if (event.getY() > 0 && event.getY() < MySurfaceView.height && event.getX() >= x && event.getX() <= bitmap.getWidth() + x && event.getY() >= y && event.getY() <= y + bitmap.getHeight()) {
x = (int) event.getX() - bitmap.getWidth() / 2;
y = (int) event.getY() - bitmap.getHeight() / 2;
}
}
如何繪製子彈
實現思想
在繪製子彈時,我發現若是使用類似背景圖片的切換方式會導致子彈的頻率底下,不能全部從飛機的位置進行發射等問題;若使用啟用一個新執行緒的方式創立的話又會白白浪費資源做一件很小的事情,造成了資源的浪費同時也加重了工作量,最後我才用了Vector陣列對子彈進行存放
Vector陣列
這是一個類似於ArrayList陣列的型別,我將資料型別定義為Bullet型別,即子彈類,在MySurfaceView中定義一個int型別的計數器count,在每次迴圈一幀所有畫面時對count進行一次+1的操作,最後進行判斷,若count整除一個數,則新建一個Bullet的物件並將其新增至Vector中,在新增之後再通過for迴圈對每個陣列中的資料(即每一顆子彈)進行一次位置座標的改變
優點
減少了程式碼的複雜程度,同時應注意在子彈結束執行後,比如擊中了敵人,飛出地圖之後進行remove操作從而保證不會因為記憶體佔有量過大而造成的記憶體溢位錯誤
程式碼片
最重要的核心程式碼
if (count % 8 == 0) {
Bullet bullet = new Bullet(BitmapFactory.decodeResource(getResources(), R.mipmap.mybullet), Plane.x, Plane.y);
bulletVector.add(bullet);
gameSoundPool.playSound(2);
}
for (int i = 0; i < bulletVector.size(); i++) {
if (bulletVector.elementAt(i).isHit()) {
score += 200;
gameSoundPool.playSound(3);
if (Boss.isLife) {
boss.beFight(canvas, paint);
}
bulletVector.remove(i);
} else if (bulletVector.elementAt(i).isDead()) {
bulletVector.remove(i);
}
}
如何判斷碰撞
原理
其實子彈與飛機碰撞與飛機與飛機碰撞的原理時相同的,不過在考慮前者是我們一般只需要考慮其xy座標與以飛機xy座標為基準的矩形範圍的位置判定即可,但是如果是後者的話,由於他們的自身面積都比較大,因此不能僅僅使用xy座標來進行判定,而應該使用多種方法判斷多種情況下的擊中事件
程式碼片
下面為子彈與Boss飛機擊中判定的程式碼片
private void sg() {
count++;
y -= 40;
if (y<=0) {
isDead = true;
}
if(x >= Boss.x-70&& x <= Boss.x + Boss.bitmap.getWidth() / 10-70 && y <= Boss.y + Boss.bitmap.getHeight()){
isHit = true;
MySurfaceView.gameSoundPool.playSound(4);
}
}
如何繪製爆炸效果
原理及思想
在繪製爆炸效果之前其實繪製Boss飛機時就用到了該方法,即canvas中的裁剪方法clipRect,只需要裁剪出一個顯示空間,然後將圖片依次貼近該空間內即可實現一個簡短的爆炸效果
注意事項
使用canvas中的clipRect方法時一定要注意使用sava方法儲存當前畫布的狀態,以及restore方法恢復之前儲存的畫布狀態
程式碼片
public void draw(Canvas canvas, Paint paint){
canvas.save();
canvas.clipRect(x,y,x+frameW,y+frameH);
canvas.drawBitmap(bitmap,x-currentFrame*frameW,y,paint);
canvas.restore();
logic();
}
public void logic(){
if(currentFrame<totalFrame){
currentFrame++;
}else{
isEnd = true;
}
}
如何新增音效
方法的使用及思想
SoundPool方法
使用思路
該方法需要定義一個新的類,通過新增相應的音訊檔案後就可以在主程式中進行呼叫,但是需要注意的時,呼叫該類中的物件時一定要設定一個引數方便判斷具體播放哪一個音樂
程式碼片
public GameSoundPool(Context context) {
this.soundPool = new SoundPool(6, AudioManager.STREAM_MUSIC,0);
s1 = soundPool.load(context,R.raw.bgm_zhuxuanlv,2); //背景音樂
s2 = soundPool.load(context,R.raw.shoot,2); //射擊聲
s3 = soundPool.load(context,R.raw.explosion,3); //小兵被擊中
s4 = soundPool.load(context,R.raw.explosion2,4); //boss被擊中
s5 = soundPool.load(context,R.raw.button,1); //轟炸預警
s6 = soundPool.load(context,R.raw.explosion3,2); //轟炸
s7 = soundPool.load(context,R.raw.eryingzhang,2); //二營長
s8 = soundPool.load(context,R.raw.kaipao,2);//開炮
s9 = soundPool.load(context,R.raw.tiancai,2);//天才
}
public void playSound(int i){
switch (i){
case 8:
soundPool.play(s1,8,8,1,1,1);
break;
case 2:
soundPool.play(s2,0.2f,0.2f,1,2,1);
break;
case 3:
soundPool.play(s3,0.2f,0.2f,1,2,1);
break;
case 4:
soundPool.play(s4,0.2f,0.2f,1,2,1);
break;
case 5:
soundPool.play(s5,1.0f,1.0f,1,1,1);
break;
case 6:
soundPool.play(s6,0.2f,0.2f,1,1,1);
break;
case 9:
soundPool.play(s7,0.2f,0.2f,1,1,1);
break;
case 10:
soundPool.play(s8,0.2f,0.2f,1,1,1);
break;
case 0:
soundPool.play(s9,0.2f,0.2f,1,1,1);
break;
}
}
使用中碰到的問題
在實際使用該類時,我發現背景音樂無法進行完美的呼叫,並且由於只定義了一個SoundPool物件,造成了同一個時間段內無法播放過多的音訊,同時還要不斷地呼叫,傳參造成了一定程度上的工作量加大,並且定義一個新的音訊檔案時還要編寫很多無用的引數,同時該類僅僅可以播放音樂,功能在MediaPlayer前暫時稍顯單一
MediaPlayer方法
思路
當我在SoundPool中遇到了無數的問題,尤其是背景音樂無法播放的問題後我就想到了用一種新的方法來輔助SoundPool類進行程式設計,最後發現了這個功能強大使用方便的類
程式碼片
MediaPlayer mediaPlayer = null;
mediaPlayer = MediaPlayer.create(getContext(),R.raw.bgm_zhuxuanlv);
mediaPlayer.setVolume(1.0f,1.0f);
mediaPlayer.setLooping(thgg);//此處傳入的thgg是一個標誌位,為true
mediaPlayer.start();
使用心得
該方法大大的增強了遊戲的整體效果,並且還可以播放視訊,更加讓我對他愛不釋手
哪些地方用到封裝、繼承、多型、方法過載、介面等
封裝
概念
封裝的概念是使用private修飾的類中的成員變數,每個成員的該變數均不相同,並且提升了安全性
實際案例
我在每個類中對於成員變數總是採用封裝的思想,並且設定get方法來使得其他類可以在安全的狀態下訪問該變數而不會造成修改,例如飛機類中的x、y變數
繼承
概念
從已有的類中派生出新類,新的類能繼承原有類的屬性和方法並能派生出新的屬性和方法
實際案例
在飛機大戰遊戲中,最最典型的繼承莫過於MySurfaceView繼承SurfaceView類了
多型
概念
兩個或兩個以上的類中的不同物件對於同一個方法所產生的不同的結果
實際案例
在生成子彈時,根據不同的子彈而定義出相應的構造方法
Bullet bullet1 = new PlaneBullet();
Bullet bullet2 = new BossBullet();
方法過載
概念
同一個類中兩個方法的方法名相同,引數列表不同,即構成方法的過載
實際案例
在飛機大戰的遊戲中,構成方法過載的地方主要在子彈的建立中,根據傳入的引數不同進行不同的操作
介面
概念
使用interface修飾的特殊抽象類
實際案例
同理,在該遊戲中,我唯一運用到介面的地方即為定義MySurfaceView時呼叫了 Runnable, SurfaceHolder.Callback兩個介面
收穫及感悟
通過老師的講解,加上平時自己的鑽研,在短短的幾個星期內快速的上手了java,從無到有,現在能夠通過百度、貼吧等等教程對自己的知識進行不斷地填充,同時編寫飛機大戰使得我對之前學過的基礎知識有了更深的感悟,並且加強了我對基礎理論知識在實戰中如何變成實實在在的工具的理解,對於今後的程式設計道路有著無法比擬的作用,但是這次的程式碼還是不夠完善,今後我一定會對它進行優化的