《動手實現一款簡單的攔阻球遊戲》
一、需求分析
1. 核心需求
實現一款人機對戰模式的小球攔阻休閒遊戲,使得多個不同顏色(顏色隨機)的小球以一定速度按先後順序由靜止狀態朝不同方向運動,玩家通過手指觸控式螢幕幕以控制自身的滑塊阻擋迎面而來的小球,小球會撞擊滑塊而發生反彈。同時,電腦所控制的滑塊也會自動阻擋玩家反彈回去的小球。其中,若玩家沒能成功阻擋一個小球,則電腦積分加一分;反之,玩家積分加一分。最終,在不同的賽制下,根據雙方對小球的阻擋情況判定勝負。
2. 個性化需求
為了增強玩家的遊戲體驗感,增加適當的遊戲背景音樂、遊戲中小球的撞擊音效、小球沒能被成功阻擋時的振動提醒、遊戲音量的調節以及控制所有音樂\音效的開啟\關閉的功能。此外,還應使得玩家可以自行選擇遊戲賽制、遊戲難度等。
二、專案總體結構
1.專案邏輯程式碼結構
![1](https://img-blog.csdn.net/20180717215000178?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1d1Y2hhbmdJ/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)2. 專案資源管理結構
![2](https://img-blog.csdn.net/2018071721501745?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1d1Y2hhbmdJ/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)***
三、效果預覽
雖然已經用上了PS修圖/摳圖等技術,但自我感覺還是很醜,UI設計還是要鍛鍊~~
1. 遊戲主選單(主介面)
2.遊戲設定介面
3. 遊戲介面
***
四、實現程式碼(只貼出部分主要程式碼,全部程式碼見文末連結)
Talk is cheap, show me the code~
1. 遊戲主介面(主選單)
Code:
MainMenuActivity.java
package com.example.wuchangi.hinderball.activity;
import android.content.Intent;
......
import com.example.wuchangi.hinderball.service.GameBGMService;
/**
* Created by WuchangI on 2018/6/17.
*/
//主選單介面類
public class MainMenuActivity extends AppCompatActivity
{
private RippleButton startGameButton;
private RippleButton setGameRulesButton;
private RippleButton exitGameButton;
private SharedPreferences radioButtonsState = null;
private SharedPreferences.Editor editor;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
init();
setContentView(R.layout.main_menu_activity);
//為介面上的所有按鈕繫結監聽器
bindingListeners();
}
//介面初始化
public void init()
{
//去掉視窗標題
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
//沉浸式狀態列
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
radioButtonsState = getSharedPreferences("radioButtonsState", MODE_PRIVATE);
//第一次開啟應用時
if(!radioButtonsState.contains("ruleState") && !radioButtonsState.contains("levelState")
&& !radioButtonsState.contains("musicState"))
{
......
}
if(radioButtonsState.getString("musicState", "musicOn").equals("musicOn"))
{
......
}
}
//獲取介面上的控制元件,並繫結監聽器
public void bindingListeners()
{
......
}
//當玩家按手機返回鍵時,退出主選單
@Override
public void onBackPressed()
{
......
}
}
2. 遊戲設定介面
Code:
GameSettingsActivity.java
package com.example.wuchangi.hinderball.activity;
import android.app.AlertDialog;
......
import com.example.wuchangi.hinderball.service.GameBGMService;
/**
* Created by WuchangI on 2018/6/17.
*/
//遊戲設定介面類
public class GameSettingsActivity extends AppCompatActivity
{
private Toolbar gameSettingToolbar;
......
private SharedPreferences.Editor editor;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.game_settings_activity);
//初始化介面並繫結監聽器
initAndBindingListeners();
}
//當選單第一次被載入時呼叫
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
......
}
//響應選單項(MenuItem)的點選事件
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
......
}
//初始化介面,獲取介面上的控制元件,並繫結監聽器
public void initAndBindingListeners()
{
//沉浸式狀態列
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
......
}
//當玩家按手機返回鍵時,返回主選單
@Override
public void onBackPressed()
{
......
}
}
3. 遊戲介面
Code:
GameViewActivity.java
package com.example.wuchangi.hinderball.activity;
import android.content.Intent;
......
import com.example.wuchangi.hinderball.view.GameView;
/**
* Created by WuchangI on 2018/6/17.
*/
//遊戲介面類
public class GameViewActivity extends AppCompatActivity
{
//手機螢幕寬度
private int screenWidth;
......
//遊戲效果(含振動)是否開啟
private boolean isGameEffectOn;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//介面初始化
init();
//初始化GameView
initGameView();
//顯示遊戲介面
setContentView(gameView);
}
//介面初始化
public void init()
{
//去掉視窗標題
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
//沉浸式狀態列
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
//獲取視窗管理器
WindowManager windowManager = getWindowManager();
......
}
//初始化GameView
public void initGameView()
{
......
}
//當玩家按手機返回鍵時,結束當前遊戲
@Override
public void onBackPressed()
{
......
}
}
4. 攔阻球實體類
Code:
HinderBallView.java
package com.example.wuchangi.hinderball.view;
import android.content.Context;
......
import android.view.View;
/**
* Created by WuchangI on 2018/6/17.
*/
//攔阻球view類
class HinderBallView extends View
{
//設定畫筆
private Paint paint = new Paint();
......
//攔阻球顏色
private int hinderBallColor;
public HinderBallView(Context context, int hinderBallX, int hinderBallY, int hinderBallRadius, int hinderBallColor)
{
......
}
@Override
protected void onDraw(Canvas canvas)
{
......
}
......
}
5. 滑塊實體類
Code:
SlidingBlockView.java
package com.example.wuchangi.hinderball.view;
import android.content.Context;
......
import android.view.View;
/**
* Created by WuchangI on 2018/6/17.
*/
//滑塊view類
class SlidingBlockView extends View
{
//設定畫筆
private Paint paint = new Paint();
......
//滑塊顏色
private int slidingBlockColor;
public SlidingBlockView(Context context, int slidingBlockXLocation, int slidingBlockYLocation,
int slidingBlockWidth, int slidingBlockHeight, int slidingBlockColor)
{
......
}
@Override
protected void onDraw(Canvas canvas)
{
......
}
......
}
6. 遊戲介面實體類(包含多個攔阻球和兩個滑塊)
Code:
GameView.java
package com.example.wuchangi.hinderball.view;
import android.app.AlertDialog;
......
import java.util.TimerTask;
/**
* Created by WuchangI on 2018/6/17.
*/
//遊戲介面類
public class GameView extends View
{
//自動控制的滑塊
private SlidingBlockView autoSlidingBlock;
......
//遊戲效果(含振動)是否開啟
private boolean isGameEffectOn;
public GameView(Context context, int gameViewWidth, int gameViewHeight, String gameRule, String gameLevel, Handler handler, boolean isGameEffectOn)
{
......
}
//介面控制元件初始化
public void initComponents()
{
......
}
@Override
protected void onDraw(Canvas canvas)
{
......
}
//根據遊戲賽制和難度等級初始化相關引數
public void initGameSettings(String gameRule, String gameLevel)
{
......
}
//銷燬gameView中殘餘的東西
public void destroyGameView()
{
......
}
......
}
7. 遊戲主介面的波紋效果按鈕的自定義實現
Code:
RippleButton.java
package com.example.wuchangi.hinderball.custom;
import android.content.Context;
......
import android.view.ViewConfiguration;
/**
* Created by WuchangI on 2018/6/22.
*/
//一款自定義的陰影擴散效果按鈕
public class RippleButton extends AppCompatImageButton
{
......(具體見前一篇blog)
}
8. 後臺背景音樂控制
Code:
GameBGMService .java
package com.example.wuchangi.hinderball.service;
import android.app.Service;
......
import com.example.wuchangi.hinderball.R;
/**
* Created by WuchangI on 2018/6/17.
*/
//一個管理背景音樂播放器的後臺Service
public class GameBGMService extends Service
{
//背景音樂播放器
private MediaPlayer BGMMediaPlayer = null;
@Override
public IBinder onBind(Intent intent)
{
return null;
}
@Override
public void onCreate()
{
super.onCreate();
//播放背景音樂
startBGM();
}
@Override
public void onDestroy()
{
super.onDestroy();
//關閉背景音樂
stopBGM();
}
//開啟背景音樂並迴圈播放
public void startBGM()
{
......
}
//關閉背景音樂
public void stopBGM()
{
......
}
}
五、Mark一些坑
- 其中對於遊戲背景音樂的播放控制,沒有直接使用MediaPlayer來直接開啟或關閉背景音樂。考慮到背景音樂的播放具有永續性,以及使用者不可見,故藉助於Android的Service技術將其設定為後臺程序。具體實現是使用一個Service實現類GameBGMService來控制MediaPlayer的開啟和關閉。故開啟背景音樂時只需開啟GameBGMService服務,關閉背景音樂時只需關閉GameBGMService服務。
- 其中對於遊戲音效的實現,沒有直接使用MediaPlayer,因為MediaPlayer不支援同時播放多個音訊。但是考慮到遊戲中玩家滑塊和電腦滑塊存在同時阻擋到小球的可能,此時需要同時播放兩個遊戲音效檔案,故最終採用Android的音效池SoundPool來實現多個音效檔案的同時播放。
- 使用SharedPreferences管理關於遊戲設定的資料時,注意處理好資料讀入過程、資料寫入過程以及預設值的給定。
- 因為Android不支援非UI執行緒對介面的重新整理操作,故使用Android的Handler機制接受非UI執行緒的重新整理請求並在主執行緒(在Android中,主執行緒即UI執行緒)中完成對介面的重新整理操作。
六、此專案的缺點
UI設計
此項上面也有提到,這裡就不說了~~
電腦AI
電腦所控制的滑塊的移動軌跡是通過隨機數確定的,使得只能通過提高電腦滑塊的移動速度來提高對小球的成功截獲率,進而提高遊戲難度。玩家看到高速運動的電腦滑塊會覺得有些不妥,使得遊戲體驗感不佳。
(簡單改進:可用實現一個對電腦滑塊周圍的小球的所在位置的探測機制,藉助該機制,結合上隨機演算法的使用,使得電腦滑塊可以根據迎面而來的小球的位置提前做好阻擋的前期準備,也就是停在預測小球會到達的位置。同時通過隨機演算法對滑塊停放位置進行影響,使之在原來的預測位置的基礎上產生額外的位移,使得滑塊對小球的截獲率降低。而且,隨機演算法影響越大,則電腦滑塊對小球的截獲率越低,遊戲難度級別越低。此時滑塊的速度就顯得合情合理,玩家體驗感便上升了。)(進一步改進:可以藉助機器學習的相關技術,來實現電腦滑塊對小球的攔阻技術。)
七、專案全部原始碼
對於其中的某些細節不清楚的朋友,歡迎留言交流~~