作品名称:水果家族
作品简介:创作此游戏的灵感来自近些年很火爆的两款游戏:连连看和切水果。练练看是一款图案配对的智益游戏,与“找不同”类的游戏同为比眼力的游戏。游戏节奏快,游戏图案丰富可爱,游戏规则简单。可以锻炼玩家的反应能力,可以受众于所有人群。
此游戏的可扩展性较强,能够适应应用要求的变化和修改,具有灵活的可扩充性。而切水果这款游戏同样很受人欢迎,游戏画面是其赢得人们青睐的重要原因,一个个造型精妙的水果
都给玩家留下了深刻的影响。“水果家族”这款游戏把连连看的游戏方式和切水果的游戏画面合为一体,给玩家带来了绝妙的享受。
设计过程如下
一、可行性分析
(1)技术可行性
该游戏全部使用JAVA实现,应用平台为安卓系统,美工设计使用PS。使用现有的技术实现该系统较为容易。现在的很多游戏都是采用C++编码,肯能会发生错误。C++中的指针可能会造成内存的非法访问。而且没有自己的安全机制,不能有效的防止病毒程序的产生。用户界面不够美观,植入很多广告。
(2)经济可行性
安卓平台开发使用普通的笔记本电脑都可以完成,无需缴纳额外的费用,PS也是可以免费下载,测试的时候只要使用一款安卓手机就可以实现,可以说开发费用几乎为零。根据资料显示,一款连连看安卓游戏的下载量在2014年已经超过300万。巨大的下载量会带来可观的经济效益。游戏的易维护性也会使游戏的开发成本大大减少。
(3)操作可行性
使用方面的的可行性主要是论证该游戏在玩家游戏过程中的感受与信息的反馈。在当前信息技术飞速发展的大环境下,计算机技术和软件技术的更新使得游戏更加容易掌握。
二、需求分析
(1)功能需求
该游戏应具有的所有功能包括普通连连看游戏方式,计时功能,玩家排行榜功能。
(2)性能需求
保证用户点击两个相同的,符合要求的图案的时候能够在0.5S之内在两个图案之内出现一条连线,连线出现同时两个图案消失。游戏过程中系统要保持顺畅,不能出现卡死的情况。
(3)可靠性和可用性需求
该游戏的寿命为3年,3年之内不出现致命的运行故障。
(4)用户需求
通过在查阅资料和访谈后得出以下结论:随着人们的生活水平不断提高,人们对精神生活的要求也越来越高,尤其随着大屏智能手机和平板电脑的发展,触屏游戏已经越来越收人们的欢迎。不仅是年轻人,中年人甚至老年人都对手机游戏的需求越来越大,能够迎合各个年龄阶层的游戏很收大众欢迎。
三、系统设计
此游戏的设计的处理流程图如下:
四、实现
游戏源代码如下:
游戏主界面代码
public class WelActivity extends Activity
implements OnClickListener,OnTimerListener,OnStateListener,OnToolsChangeListener{
private ImageButton btnPlay;
private ImageButton btnRefresh;
private ImageButton btnTip;
private ImageView imgTitle;
private GameView gameView;
private SeekBar progress;
private MyDialog dialog;
private ImageView clock;
private TextView textRefreshNum;
private TextView textTipNum;
private MediaPlayer player;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case 0:
dialog = new MyDialog(WelActivity.this,gameView,"ʤ����",gameView.getTotalTime() - progress.getProgress());
dialog.show();
break;
case 1:
dialog = new MyDialog(WelActivity.this,gameView,"ʧ�ܣ�",gameView.getTotalTime() - progress.getProgress());
dialog.show();
}
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.welcome);
btnPlay = (ImageButton) findViewById(R.id.play_btn);
btnRefresh = (ImageButton) findViewById(R.id.refresh_btn);
btnTip = (ImageButton) findViewById(R.id.tip_btn);
imgTitle = (ImageView) findViewById(R.id.title_img);
gameView = (GameView) findViewById(R.id.game_view);
clock = (ImageView) findViewById(R.id.clock);
progress = (SeekBar) findViewById(R.id.timer);
textRefreshNum = (TextView) findViewById(R.id.text_refresh_num);
textTipNum = (TextView) findViewById(R.id.text_tip_num);
//XXX
progress.setMax(gameView.getTotalTime());
btnPlay.setOnClickListener(this);
btnRefresh.setOnClickListener(this);
btnTip.setOnClickListener(this);
gameView.setOnTimerListener(this);
gameView.setOnStateListener(this);
gameView.setOnToolsChangedListener(this);
GameView.initSound(this);
Animation scale = AnimationUtils.loadAnimation(this,R.anim.scale_anim);
imgTitle.startAnimation(scale);
btnPlay.startAnimation(scale);
player = MediaPlayer.create(this, R.raw.bg);
player.setLooping(true);//����ѭ������
player.start();
// GameView.soundPlay.play(GameView.ID_SOUND_BACK2BG, -1);
}
@Override
protected void onPause() {
super.onPause();
gameView.setMode(GameView.PAUSE);
}
@Override
protected void onDestroy() {
super.onDestroy();
gameView.setMode(GameView.QUIT);
}
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.play_btn:
Animation scaleOut = AnimationUtils.loadAnimation(this,R.anim.scale_anim_out);
Animation transIn = AnimationUtils.loadAnimation(this,R.anim.trans_in);
btnPlay.startAnimation(scaleOut);
btnPlay.setVisibility(View.GONE);
imgTitle.setVisibility(View.GONE);
gameView.setVisibility(View.VISIBLE);
btnRefresh.setVisibility(View.VISIBLE);
btnTip.setVisibility(View.VISIBLE);
progress.setVisibility(View.VISIBLE);
clock.setVisibility(View.VISIBLE);
textRefreshNum.setVisibility(View.VISIBLE);
textTipNum.setVisibility(View.VISIBLE);
btnRefresh.startAnimation(transIn);
btnTip.startAnimation(transIn);
gameView.startAnimation(transIn);
player.pause();
gameView.startPlay();
break;
case R.id.refresh_btn:
Animation shake01 = AnimationUtils.loadAnimation(this,R.anim.shake);
btnRefresh.startAnimation(shake01);
gameView.refreshChange();
break;
case R.id.tip_btn:
Animation shake02 = AnimationUtils.loadAnimation(this,R.anim.shake);
btnTip.startAnimation(shake02);
gameView.autoClear();
break;
}
}
@Override
public void onTimer(int leftTime) {
Log.i("onTimer", leftTime+"");
progress.setProgress(leftTime);
}
@Override
public void OnStateChanged(int StateMode) {
switch(StateMode){
case GameView.WIN:
handler.sendEmptyMessage(0);
break;
case GameView.LOSE:
handler.sendEmptyMessage(1);
break;
case GameView.PAUSE:
player.stop();
gameView.player.stop();
gameView.stopTimer();
break;
case GameView.QUIT:
player.release();
gameView.player.release();
gameView.stopTimer();
break;
}
}
@Override
public void onRefreshChanged(int count) {
textRefreshNum.setText(""+gameView.getRefreshNum());
}
@Override
public void onTipChanged(int count) {
textTipNum.setText(""+gameView.getTipNum());
}
public void quit(){
this.finish();
}
}
绘图类 代码太多 这是其中一个类
package whu.iss.sric.view;
import java.util.ArrayList;
import java.util.List;
import whu.iss.sric.android.R;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
public class BoardView extends View {
/**
* xCount x轴方向的图标数+1
*/
protected static final int xCount =10;
/**
* yCount y轴方向的图标数+1
*/
protected static final int yCount =12;
/**
* map 连连看游戏棋盘
*/
protected int[][] map = new int[xCount][yCount];
/**
* iconSize 图标大小
*/
protected int iconSize;
/**
* iconCounts 图标的数目
*/
protected int iconCounts=19;
/**
* icons 所有的图片
*/
protected Bitmap[] icons = new Bitmap[iconCounts];
/**
* path 可以连通点的路径
*/
private Point[] path = null;
/**
* selected 选中的图标
*/
protected List<Point> selected = new ArrayList<Point>();
public BoardView(Context context,AttributeSet atts) {
super(context,atts);
calIconSize();
Resources r = getResources();
loadBitmaps(1, r.getDrawable(R.drawable.fruit_01));
loadBitmaps(2, r.getDrawable(R.drawable.fruit_02));
loadBitmaps(3, r.getDrawable(R.drawable.fruit_03));
loadBitmaps(4, r.getDrawable(R.drawable.fruit_04));
loadBitmaps(5, r.getDrawable(R.drawable.fruit_05));
loadBitmaps(6, r.getDrawable(R.drawable.fruit_06));
loadBitmaps(7, r.getDrawable(R.drawable.fruit_07));
loadBitmaps(8, r.getDrawable(R.drawable.fruit_08));
loadBitmaps(9, r.getDrawable(R.drawable.fruit_09));
loadBitmaps(10, r.getDrawable(R.drawable.fruit_10));
loadBitmaps(11, r.getDrawable(R.drawable.fruit_11));
loadBitmaps(12, r.getDrawable(R.drawable.fruit_12));
loadBitmaps(13, r.getDrawable(R.drawable.fruit_13));
loadBitmaps(14, r.getDrawable(R.drawable.fruit_14));
loadBitmaps(15, r.getDrawable(R.drawable.fruit_15));
loadBitmaps(16, r.getDrawable(R.drawable.fruit_17));
loadBitmaps(17, r.getDrawable(R.drawable.fruit_18));
loadBitmaps(18, r.getDrawable(R.drawable.fruit_19));
}
/**
*
* 计算图标的长宽
*/
private void calIconSize()
{
DisplayMetrics dm = new DisplayMetrics();
((Activity) this.getContext()).getWindowManager()
.getDefaultDisplay().getMetrics(dm);
iconSize = dm.widthPixels/(xCount);
}
/**
*
* @param key 特定图标的标识
* @param d drawable下的资源
*/
public void loadBitmaps(int key,Drawable d){
Bitmap bitmap = Bitmap.createBitmap(iconSize,iconSize,Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
d.setBounds(0, 0, iconSize, iconSize);
d.draw(canvas);
icons[key]=bitmap;
}
@Override
protected void onDraw(Canvas canvas) {
/**
* 绘制连通路径,然后将路径以及两个图标清除
*/
if (path != null && path.length >= 2) {
for (int i = 0; i < path.length - 1; i++) {
Paint paint = new Paint();
paint.setColor(Color.CYAN);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
Point p1 = indextoScreen(path[i].x, path[i].y);
Point p2 = indextoScreen(path[i + 1].x, path[i + 1].y);
canvas.drawLine(p1.x + iconSize / 2, p1.y + iconSize / 2,
p2.x + iconSize / 2, p2.y + iconSize / 2, paint);
}
Point p = path[0];
map[p.x][p.y] = 0;
p = path[path.length - 1];
map[p.x][p.y] = 0;
selected.clear();
path = null;
}
/**
* 绘制棋盘的所有图标 当这个坐标内的值大于0时绘制
*/
for(int x=0;x<map.length;x+=1){
for(int y=0;y<map[x].length;y+=1){
if(map[x][y]>0){
Point p = indextoScreen(x, y);
canvas.drawBitmap(icons[map[x][y]], p.x,p.y,null);
}
}
}
/**
* 绘制选中图标,当选中时图标放大显示
*/
for(Point position:selected){
Point p = indextoScreen(position.x, position.y);
if(map[position.x][position.y] >= 1){
canvas.drawBitmap(icons[map[position.x][position.y]],
null,
new Rect(p.x-5, p.y-5, p.x + iconSize + 5, p.y + iconSize + 5), null);
}
}
}
/**
*
* @param path
*/
public void drawLine(Point[] path) {
this.path = path;
this.invalidate();
}
/**
* 工具方法
* @param x 数组中的横坐标
* @param y 数组中的纵坐标
* @return 将图标在数组中的坐标转成在屏幕上的真实坐标
*/
public Point indextoScreen(int x,int y){
return new Point(x* iconSize , y * iconSize );
}
/**
* 工具方法
* @param x 屏幕中的横坐标
* @param y 屏幕中的纵坐标
* @return 将图标在屏幕中的坐标转成在数组上的虚拟坐标
*/
public Point screenToindex(int x,int y){
int ix = x/ iconSize;
int iy = y / iconSize;
if(ix < xCount && iy <yCount){
return new Point( ix,iy);
}else{
return new Point(0,0);
}
}
}
音效类
package whu.iss.sric.android;
import java.util.HashMap;
import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
public class SoundPlay {
// 音效的音量
int streamVolume;
// 定义SoundPool 对象
private SoundPool soundPool;
// 定义HASH表
private HashMap<Integer, Integer> soundPoolMap;
/***************************************************************
* Function: initSounds(); Parameters: null Returns: None. Description:
* 初始化声音系统 Notes: none.
***************************************************************/
public void initSounds(Context context) {
// 初始化soundPool 对象,第一个参数是允许有多少个声音流同时播放,第2个参数是声音类型,第三个参数是声音的品质
soundPool = new SoundPool(25, AudioManager.STREAM_MUSIC, 100);
// 初始化HASH表
soundPoolMap = new HashMap<Integer, Integer>();
// 获得声音设备和设备音量
AudioManager mgr = (AudioManager) context
.getSystemService(Context.AUDIO_SERVICE);
streamVolume = mgr.getStreamVolume(AudioManager.STREAM_MUSIC);
}
/**
* 把资源中的音效加载到指定的ID(播放的时候就对应到这个ID播放就行了)
* Function: loadSfx(); Parameters: null Returns: None. Description: 加载音效资源
* Notes: none.
*/
public void loadSfx(Context context, int raw, int ID) {
soundPoolMap.put(ID, soundPool.load(context, raw, 1));
}
/***************************************************************
* Function: play(); Parameters: sound:要播放的音效的ID, loop:循环次数 Returns: None.
* Description: 播放声音 Notes: none.
***************************************************************/
public void play(int sound, int uLoop) {
soundPool.play(soundPoolMap.get(sound), streamVolume, streamVolume, 1,
uLoop, 1f);
}
}
动作监听类
public class MyDialog extends Dialog implements OnClickListener{
private GameView gameview;
private Context context;
public MyDialog(Context context, GameView gameview, String msg, int time) {
super(context,R.style.dialog);
this.gameview = gameview;
this.context = context;
this.setContentView(R.layout.dialog_view);
TextView text_msg = (TextView) findViewById(R.id.text_message);
TextView text_time = (TextView) findViewById(R.id.text_time);
ImageButton btn_menu = (ImageButton) findViewById(R.id.menu_imgbtn);
ImageButton btn_next = (ImageButton) findViewById(R.id.next_imgbtn);
ImageButton btn_replay = (ImageButton) findViewById(R.id.replay_imgbtn);
text_msg.setText(msg);
text_time.setText(text_time.getText().toString().replace("$", String.valueOf(time)));
btn_menu.setOnClickListener(this);
btn_next.setOnClickListener(this);
btn_replay.setOnClickListener(this);
this.setCancelable(false);
}
@Override
public void onClick(View v) {
this.dismiss();
switch(v.getId()){
case R.id.menu_imgbtn:
Dialog dialog = new AlertDialog.Builder(context)
.setIcon(R.drawable.buttons_bg20)
.setTitle(R.string.quit)
.setMessage(R.string.sure_quit)
.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
((WelActivity)context).quit();
}
})
.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
})
.create();
dialog.show();
// gameview.startPlay();
// Toast.makeText(context, text, duration);
break;
case R.id.replay_imgbtn:
gameview.startPlay();
break;
case R.id.next_imgbtn:
gameview.startNextPlay();
break;
}
}
}
游戏界面如下:
测试结果如下:
游戏的难度可能比教大,水果的样子可能也比较难看,在后期都会做出一定的修改来不断完善这款游戏。
蓝色的图标是刷新水果界面,黄色图标是自动帮你连一队水果,用好这两个工具。可以让你更快地完成游戏。
该游戏暂时只做了一关。
五、结论
根据以上的分析可知,本游戏技术方面已经成熟,测试可靠,具有良好的市场拓展潜力。但是开发时间有限,游戏的运行还不够流畅,游戏界面还不够完美,玩家排行那一部分需要数据库的支持,这一点还不够完善。我们会继续加以改良和学习。