文章目录
一、需求分析
####1. 核心需求
实现一款人机对战模式的小球拦阻休闲游戏,使得多个不同颜色(颜色随机)的小球以一定速度按先后顺序由静止状态朝不同方向运动,玩家通过手指触摸屏幕以控制自身的滑块阻挡迎面而来的小球,小球会撞击滑块而发生反弹。同时,电脑所控制的滑块也会自动阻挡玩家反弹回去的小球。其中,若玩家没能成功阻挡一个小球,则电脑积分加一分;反之,玩家积分加一分。最终,在不同的赛制下,根据双方对小球的阻挡情况判定胜负。
2. 个性化需求
为了增强玩家的游戏体验感,增加适当的游戏背景音乐、游戏中小球的撞击音效、小球没能被成功阻挡时的振动提醒、游戏音量的调节以及控制所有音乐\音效的开启\关闭的功能。此外,还应使得玩家可以自行选择游戏赛制、游戏难度等。
二、项目总体结构
1.项目逻辑代码结构
2. 项目资源管理结构
三、效果预览
虽然已经用上了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
电脑所控制的滑块的移动轨迹是通过随机数确定的,使得只能通过提高电脑滑块的移动速度来提高对小球的成功截获率,进而提高游戏难度。玩家看到高速运动的电脑滑块会觉得有些不妥,使得游戏体验感不佳。
(简单改进:可用实现一个对电脑滑块周围的小球的所在位置的探测机制,借助该机制,结合上随机算法的使用,使得电脑滑块可以根据迎面而来的小球的位置提前做好阻挡的前期准备,也就是停在预测小球会到达的位置。同时通过随机算法对滑块停放位置进行影响,使之在原来的预测位置的基础上产生额外的位移,使得滑块对小球的截获率降低。而且,随机算法影响越大,则电脑滑块对小球的截获率越低,游戏难度级别越低。此时滑块的速度就显得合情合理,玩家体验感便上升了。)
(进一步改进:可以借助机器学习的相关技术,来实现电脑滑块对小球的拦阻技术。)
七、项目全部源代码
顺便附上项目源码,支持开源精神,欢迎star、fork:
https://github.com/Yuziquan/HinderBall
(希望可以帮到有需要的人~~)
对于其中的某些细节不清楚的朋友,欢迎留言交流~~