连连看游戏分析设计与实现_第1页
连连看游戏分析设计与实现_第2页
连连看游戏分析设计与实现_第3页
连连看游戏分析设计与实现_第4页
连连看游戏分析设计与实现_第5页
已阅读5页,还剩37页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

连连看游戏分析设计与实现1连连看(picture matching)游戏简介连连看游戏界面上均匀分布2N个尺寸相同的图片,每张图片在游戏中都会出现偶数次,游戏玩家需要依次找到两张相同的图片,而且这两张图片之间只用横线、竖线相连(连线上不能有其他图片),并且连线的条数不超过3条,那么游戏会消除这两个图片。连连看是一款广受欢迎的小游戏,它具有玩法简单、耗时少等特征,尤其适合广大白领女性在办公室里休闲、放松。2.分析连连看连连看是一个小的、简单的游戏程序,所以不需要大量的分析。首先,我们列出用例。用例不多。有:用户开始游戏,用户进行配对图片。图1.连连看用例图下一步就是为每个用例和相关场景写一个文本描述。连连看相当简单,只有一个参与者,就是游戏玩家。在使用这个程序的过程中也不会碰到出错的情况,所以场景也很短。开始游戏的场景:玩家打开应用程序,点击“开始”按钮,会生成三种不同的图片排列方式(矩阵、竖向、横向排列)。配对图片的场景:玩家对图片进行配对,配好后会消除这对图片。当在规定的时间内配对完所有图片时,弹出胜利对话框,否则弹出失败对话框。尽管只有2个简单用例,但它们确实揭示了我们所需完成的任务的重要方面。大的应用程序会有更多的用例,有些更为复杂,有些一样简单。用例导致了场景。场景通常要比这个例子中的复杂,反映了在某项特征或功能上,用户和开发者之间的更为细节化的合约。每个场景所需的细节程序取决于许多方面,但将场景写下来有助于确保每个人理解系统应该完成什么任务。我们在连连看的用例和场景的呈现上不是太正规。有时,这种非正规的方式和几张纸或白板就足够了。更为正规的面向对象方法学在确定用例及相应场景方面有更正规的做法,也提供了特定的软件来创建和跟踪用例和场景。3.(分析阶段)发现对象、属性和操作通过阅读问题描述以及实际情况,我们得到以下名词清单:图片,游戏视图,图片的排列方式,服务组件。包图通过对问题的声明的名词进行分析,我们得到游戏的包图:comexamplelinkboardimplobjectviewutil 图2,连连看包图其中util包负责与图片加载有关的处理,view包负责呈现界面,Object包是整个游戏的配置参数,impl是图片的排列方式,board包含了整个游戏的面板类。Link包全部的包和Link类(游戏程序的入口)。Util包中有ImageUtil类Object包中有GameConf和LinkInfo类Board包中有AbstraBoard和GameService类View包中有GameView、Piece、PieceImage类Board.impl包中有FullBoard、HorizontalBoard、VerticalBoard类Link包中有GameServiceImpl和Link类。类图由于类比较多,属性和方法更多,因此类图只画出类名称,以后再详细说明类的属性和方法。图3.连连看类图1. PiececImage类图4.PieceImge类 PieceImage代表了该方块上的图片,使用它来封装:Bitmap对象和图片资源ID。其中Bitmap对象用于在游戏界面上绘制方块;而图片资源的ID则代表了该Piece对象的标识,当两个Piece所封装的图片资源的ID相等时,即可认为这两个Piece上的图片相同。2. Piece类图5.Piece类一个Piece对象代表游戏界面上的一个方块,它除了封装它在左上角在游戏界面中X,Y坐标。3. GameView类 图6.GameView类 GameView主要是根据游戏的状态数据来绘制界面上的方块,GameView继承了View组件,重写了View组件上onDraw(Canvas canvas)方法,重写该方法主要就是绘制游戏里剩余的方块,除此之外,它还会负责绘制连接方法的连接线。4. GameConf图7.GameConf类GameConf类是游戏的配置类,记录方块的大小像素,图片的起始坐标,运行时间,方块的维数,游戏上下文等。5. LinkInfo类 图8.LinkInfo类LinkInfo是一个非常简单的工具类,它用于封装两个方块之间的连接信息其实就是封装一个List,List里保存了连接线需要经过的点。连连看的游戏规则约定:两个方块之间最多只能用3条线段相连,也就是说最多只能有2个“拐点”,加上两个方块的中心,方块的连接信息最多只需要4个连接点。6. 连连看的状态数据模型实际上连连看的游戏界面是一个N*M的风格,每个网格上显示一张图片。这个网格只需要用一个二维数据来定义即可,而每个网格上所显示的图片对于底层的数据模型来说,不同的图片对应于不同的数值即可。图9显示所了数据模型的示意。012113 图9 连连看的数据模型对于图9所示的数据模型,只要让数值为0的网络上不绘制图片,其他数值的网络则绘制相应的图片,就可显示出连连看的游戏界面了。本程序实际上并不是直接使用int数组来保存游戏的状态数据,而是采用Piece来保存游戏的状态模型因为Piece对象封装的信息更多,不仅包含了该方块的左上角的X、Y坐标,而且还包含了该Piece所显示的图片、图片ID这个图片ID就可作为该Piece的数据。为了初始化游戏状态,程序需要创建一个Piece数组,为此程序定义一个AbstractBoard抽象类。 图10.AbstractBoard类上面有一个createPieces(config,pieces)抽象方法来创建一个List集合,该抽象方法将会交给其子类去实现。由于连连看游戏的初始状态可能有很多种比如横向分布、竖向分布的方块、矩阵排列的方块、随机分布的方块等,该程序为了考虑以后的扩展性,此处只是采用了模板模式:定义AbstractBoard抽象基类来完成通用的代码,而暂时无法确定、需要子类实现的方法定义成createPieces(GameConf config,Piecepieces)抽象方法。矩阵排列的方块 图11.FullBoard类竖向排列的方块 图12.VerticalBoard横向排列的方块 图13.HorizontalBoard类ImageUtil工具类(静态类),它的作用是自动搜寻/res/drawable-mdpi目录下的图片,并根据需要随机读取该目录下的图片 图14.ImageUtil类7. 实现游戏Activity 图15.Link类Link类是游戏的入口,通过它来负责显示,Activity还需要为游戏界面的按钮、GameView组件的事件提供事件监听器。对于GameView组件,程序需要监听用户的触碰动作,当用户触碰屏幕时,程序需要获取用户触碰的是哪个方块,并判断是否需要“消除”该方块。为了判断能否消除该方块,程序需要进行如下判断: 如果程序之前已经选中了某个方块,就判断当前触碰的方块是否能与之前的方块“相连”,如果可相连,则消除两个方块;如果两个方块不可以相连,则把当前方块设置为选中方块。 如果程序之前没有选中方块,直接将当前方块设置为选中方块。Link Activity用的两个类如下: GameConf:负责管理游戏的初始化设置信息 GameService:负责游戏的逻辑实现。8. 实现游戏逻辑GameSerive组件是整个游戏逻辑实现的核心,而且GameService是一个可以复用的业务逻辑类,它与游戏实现平台无关,既可在Java Swing程序中使用,也可在Android游戏中使用。 图16.GameServie接口 图17.实现了GameService接口的GameServiceImpl类时序图游戏玩家开始游戏时序图 图18.开始游戏时序图游戏玩家单击“开始”按钮,触发OnClick事件,调用startGame()函数,它启动计时器,记录游戏剩余的时间,同时向GameService发送消息,启动游戏,而GameService则调用AbstractBoard负责生成界面。玩家玩游戏时序图当游戏方块界面生成好后,玩家就可开始玩游戏了。当玩家接触屏幕时,触发触碰事件绑定监听器,Link类调用gameViewTouchDown(e)函数,GameService调用findPiece(touchX,touchy)将玩家刚才选中的图片进行标记,然后调用Links(this.selected,currentPiece)进行配对。如果能的话,则返回消息给Link,Link调用handleSuccessLink(linkInfo,selected,currentPiece,pieces)进行成功连接后的处理。当玩家手指离开屏幕时,调用gameViewTouchUp(MotionEvent e),使GameView调用postInvalidate(),对屏幕进行重绘,然后返回给Link。 “连连看”实现虽然本程序比较小,但是代码还是比较多的。这里就不再列出所有的代码了。只列出Link类和GameServiceImpl类的代码,因为这两个类是本程序最重要的。Link类package com.example.link;import java.util.Timer;import java.util.TimerTask;import com.example.link.board.GameService;import com.example.link.object.GameConf;import com.example.link.object.LinkInfo;import com.example.link.view.GameView;import com.example.link.view.Piece;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.os.Vibrator;import android.app.Activity;import android.app.AlertDialog;import android.app.AlertDialog.Builder;import android.content.DialogInterface;import android.view.Menu;import android.view.MotionEvent;import android.view.View;import android.view.View.OnClickListener;import android.view.View.OnTouchListener;import android.widget.Button;import android.widget.TextView;public class Link extends Activity /游戏配置对象private GameConf config;/游戏业务逻辑接口private GameService gameService;/游戏界面 private GameView gameView;/开始按钮private Button startButton;/记录剩余时间的TextViewprivate TextView timeTextView;/失败后弹出的对话框private AlertDialog.Builder lostDialog;/游戏胜利后的对话框private AlertDialog.Builder successDialog;/定时器private Timer timer = new Timer();/* * 游戏剩余时间 */private int gameTime;/ record games remain time/记录是否处于游戏状态private boolean isPlaying;private Vibrator vibrator;/ 振动处理类private Piece selected = null;/记录已经选中的方块private Handler handler = new Handler() public void handleMessage(Message msg) switch (msg.what) case 0x123:timeTextView.setText(剩余时间: + gameTime);gameTime-;/时间小于0,游戏失败if (gameTime 0) stopTimer();/更改游戏的状态 isPlaying = false;lostDialog.show();return;break;Overridepublic void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState);setContentView(R.layout.main);init();/ 初始化界面/* * 初始化游戏的方法 */private void init() config = new GameConf(8, 9, 2, 10, 100000, this);/得到游戏区域对象gameView = (GameView) findViewById(R.id.gameView);/获取显示剩余时间的文本框timeTextView = (TextView) findViewById(R.id.timeText);/获取“开始按钮startButton = (Button) findViewById(R.id.startButton);/获取振动器vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);gameService = new GameServiceImpl(this.config);gameView.setGameService(gameService);/为”开始“按钮的单击事件绑定事件监听器startButton.setOnClickListener(new OnClickListener() public void onClick(View v) startGame(GameConf.DEFAULT_TIME););/为游戏区域的触碰事件绑定监听器this.gameView.setOnTouchListener(new OnTouchListener() public boolean onTouch(View v, MotionEvent event) if (event.getAction() = MotionEvent.ACTION_DOWN) gameViewTouchDown(event);if (event.getAction() = MotionEvent.ACTION_UP) gameViewTouchUp(event);return true;);/初始化失败的对话框lostDialog = createDialog(Lost, 游戏失败!重新开始, R.drawable.lost).setPositiveButton(确定, new DialogInterface.OnClickListener() public void onClick(DialogInterface dialog, int which) startGame(GameConf.DEFAULT_TIME););/初始化游戏胜利的对话框successDialog = createDialog(Success, 游戏胜利!重新开始, R.drawable.success).setPositiveButton(确定, new DialogInterface.OnClickListener() public void onClick(DialogInterface dialog, int which) startGame(GameConf.DEFAULT_TIME););protected void onPause() /暂停游戏stopTimer();super.onPause();protected void onResume() /如果处于游戏状态中if (isPlaying) /以剩余时间重写开始游戏startGame(gameTime);super.onResume();/创建对话框的工具方法private Builder createDialog(String title, String message,int lostImageResource) return new AlertDialog.Builder(this).setTitle(title).setMessage(message).setIcon(lostImageResource);/触碰游戏区域的处理方法protected void gameViewTouchUp(MotionEvent event) this.gameView.postInvalidate();/* * 成功连接后的处理 * * param linkInfo 连接信息 * param selected2 前一个选中方块 * param currentPiece 当前选择方块 * param pieces 系统中还剩的全部方块 */private void handleSuccessLink(LinkInfo linkInfo, Piece selected2,Piece currentPiece, Piece pieces) /它们可以相连,让GamePanel处理LinkInfothis.gameView.setLinkInfo(linkInfo);/将gameView中的选中方块设为null;this.gameView.setSelectedPiece(null);this.gameView.postInvalidate();/将两个Piece对象从数组中删除piecesselected2.getIndexX()selected2.getIndexY() = null;piecescurrentPiece.getIndexX()currentPiece.getIndexY() = null;/将选中方块设置nullthis.selected = null;/手机振动(100毫秒)this.vibrator.vibrate(100);/判断是否还有剩下的方块,如果没有,游戏胜利if (!this.gameService.hasPieces() /游戏胜利this.successDialog.show();/停止定时器stopTimer();/更改游戏状态isPlaying = false;/触碰游戏区域的方法protected void gameViewTouchDown(MotionEvent event) /获取GameServiceImpl中的Piece数组Piece pieces = gameService.getPieces();/获取用户点击的X坐标float touchX = event.getX();/获取用户点击的Y坐标float touchY = event.getY();/根据用户触碰的坐标得到对应的Piece对象 Piece currentPiece = gameService.findPiece(touchX, touchY);/如果没有选中任何Piece对象(即鼠标点击的地方没有图片),不再往下执行if (currentPiece = null) return;/将GameView中的选中方块设为当前方块this.gameView.setSelectedPiece(currentPiece);/表示之前没有选中任何一个Pieceif (this.selected = null) /将当前方块设为已选中方块,重新将GamePanel绘制,并不再往下执行this.selected = currentPiece;this.gameView.postInvalidate();return;/表示之前已经选择一个if (this.selected != null) /在这里要对currentPiece和PrePiece进行判断并进行连接LinkInfo linkInfo = this.gameService.link(this.selected,currentPiece);/两个Piece不可连,linkInfo为nullif (linkInfo = null) /如果连接不成功,将当前方块设为选中方块this.selected = currentPiece;this.gameView.postInvalidate(); else /处理成功连接handleSuccessLink(linkInfo, this.selected, currentPiece, pieces);/以gameTime作为剩余时间开始或恢复游戏protected void startGame(int gameTime) /如果之前的timer还未取消,取消timerif (this.timer != null) stopTimer();/重新设置游戏时间this.gameTime = gameTime;/如果游戏剩余时间与总游戏时间相等,则重新开始游戏if (gameTime = GameConf.DEFAULT_TIME) /开始新的游戏gameView.startGame();isPlaying = true;this.timer = new Timer();/启动计时器this.timer.schedule(new TimerTask() public void run() handler.sendEmptyMessage(0x123);, 0, 1000);/将选中方块设为null;this.selected = null;protected void stopTimer() /停止计时器timer.cancel();timer = null;Overridepublic boolean onCreateOptionsMenu(Menu menu) getMenuInflater().inflate(R.menu.main, menu);return true;GameServiceImpl类package org.crazyit.link;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Random;import org.crazyit.link.board.AbstractBoard;import org.crazyit.link.board.GameService;import org.crazyit.link.object.GameConf;import org.crazyit.link.object.LinkInfo;import org.crazyit.link.view.Piece;import android.graphics.Point;/* * Description: 游戏逻辑的实现类*/public class GameServiceImpl implements GameService/ 定义一个Piece数组,只提供getter方法private Piece pieces;/ 游戏配置对象private GameConf config;public GameServiceImpl(GameConf config)/ 将游戏的配置对象设置本类中this.config = config;Overridepublic void start()/ 定义一个AbstractBoard对象AbstractBoard board = null;Random random = new Random();/ 获取一个随机数, 可取值0、1、2、3四值。int index = random.nextInt(4);/ 随机生成AbstractBoard的子类实例switch (index)case 0:/ 0返回VerticalBoard(竖向)board = new VerticalBoard();break;case 1:/ 1返回HorizontalBoard(横向)board = new HorizontalBoard();break;default:/ 默认返回FullBoardboard = new FullBoard();break;/ 初始化Piece数组this.pieces = board.create(config);/ 直接返回本对象的Piece数组Overridepublic Piece getPieces()return this.pieces;/ 实现接口的hasPieces方法Overridepublic boolean hasPieces()/ 遍历Piece数组的每个元素for (int i = 0; i pieces.length; i+)for (int j = 0; j piecesi.length; j+)/ 只要任意一个数组元素不为null,也就是还剩有非空的Piece对象if (piecesij != null)return true;return false;/ 根据触碰点的位置查找相应的方块Overridepublic Piece findPiece(float touchX, float touchY)/ 由于在创建Piece对象的时候, 将每个Piece的开始座标加了/ GameConf中设置的beginImageX/beginImageY值, 因此这里要减去这个值int relativeX = (int) touchX - this.config.getBeginImageX();int relativeY = (int) touchY - this.config.getBeginImageY();/ 如果鼠标点击的地方比board中第一张图片的开始x座标和开始y座标要小, 即没有找到相应的方块if (relativeX 0 | relativeY 0)return null;/ 获取relativeX座标在Piece数组中的第一维的索引值/ 第二个参数为每张图片的宽int indexX = getIndex(relativeX, GameConf.PIECE_WIDTH);/ 获取relativeY座标在Piece数组中的第二维的索引值/ 第二个参数为每张图片的高int indexY = getIndex(relativeY, GameConf.PIECE_HEIGHT);/ 这两个索引比数组的最小索引还小, 返回nullif (indexX 0 | indexY = this.config.getXSize()| indexY = this.config.getYSize()return null;/ 返回Piece数组的指定元素return this.piecesindexXindexY;/ 工具方法, 根据relative座标计算相对于Piece数组的第一维/ 或第二维的索引值 ,size为每张图片边的长或者宽private int getIndex(int relative, int size)/ 表示座标relative不在该数组中int index = -1;/ 让座标除以边长, 没有余数, 索引减1/ 例如点了x座标为20, 边宽为10, 20 % 10 没有余数,/ index为1, 即在数组中的索引为1(第二个元素)if (relative % size = 0)index = relative / size - 1;else/ 有余数, 例如点了x座标为21, 边宽为10, 21 % 10有余数, index为2/ 即在数组中的索引为2(第三个元素)index = relative / size;return index;/ 实现接口的link方法Overridepublic LinkInfo link(Piece p1, Piece p2)/ 两个Piece是同一个, 即选中了同一个方块, 返回nullif (p1.equals(p2)return null;/ 如果p1的图片与p2的图片不相同, 则返回nullif (!p1.isSameImage(p2)return null;/ 如果p2在p1的左边, 则需要重新执行本方法, 两个参数互换if (p2.getIndexX() p1.getIndexX()return link(p2, p1);/ 获取p1的中心点Point p1Point = p1.getCenter();/ 获取p2的中心点Point p2Point = p2.getCenter();/ 如果两个Piece在同一行if (p1.getIndexY() = p2.getIndexY()/ 它们在同一行并可以相连if (!isXBlock(p1Point, p2Point, GameConf.PIECE_WIDTH)return new LinkInfo(p1Point, p2Point);/ 如果两个Piece在同一列if (p1.getIndexX() = p2.getIndexX()if (!isYBlock(p1Point, p2Point, GameConf.PIECE_HEIGHT)/ 它们之间没有真接障碍, 没有转折点return new LinkInfo(p1Point, p2Point);/ 有一个转折点的情况/ 获取两个点的直角相连的点, 即只有一个转折点Point cornerPoint = getCornerPoint(p1Point, p2Point,GameConf.PIECE_WIDTH, GameConf.PIECE_HEIGHT);if (cornerPoint != null)return new LinkInfo(p1Point, cornerPoint, p2Point);/ 该map的key存放第一个转折点, value存放第二个转折点,/ map的size()说明有多少种可以连的方式Map turns = getLinkPoints(p1Point, p2Point,GameConf.PIECE_WIDTH, GameConf.PIECE_WIDTH);if (turns.size() != 0)return getShortcut(p1Point, p2Point, turns,getDistance(p1Point, p2Point);return null;/* * 获取两个转折点的情况 * * param point1 * param point2 * return Map对象的每个key-value对代表一种连接方式, * 其中key、value分别代表第1个、第2个连接点 */private Map getLinkPoints(Point point1, Point point2,int pieceWidth, int pieceHeight)Map result = new HashMap();/ 获取以point1为中心的向上, 向右, 向下的通道List p1UpChanel = getUpChanel(point1, point2.y, pieceHeight);List p1RightChanel = getRightChanel(point1, point2.x, pieceWidth);List p1DownChanel = getDownChanel(point1, point2.y, pieceHeight);/ 获取以point2为中心的向下, 向左, 向上的通道List p2DownChanel = getDownChanel(point2, point1.y, pieceHeight);List p2LeftChanel = getLeftChanel(point2, point1.x, pieceWidth);List p2UpChanel = getUpChanel(point2, point1.y, pieceHeight);/ 获取Board的最大高度int heightMax = (this.config.getYSize() + 1) * pieceHeight+ this.config.getBeginImageY();/ 获取Board的最大宽度int widthMax = (this.config.getXSize() + 1) * pieceWidth+ this.config.getBeginImageX();/ 先确定两个点的关系/ point2在point1的左上角或者左下角if (isLeftUp(point1, point2) | isLeftDown(point1, point2)/ 参数换位, 调用本方法return getLinkPoints(point2, point1, pieceWidth, pieceHeight);/ p1、p2位于同一行不能直接相连if (point1.y = point2.y)/ 在同一行/ 向上遍历/ 以p1的中心点向上遍历获取点集合p1UpChanel = getUpChanel(point1, 0, pieceHeight);/ 以p2的中心点向上遍历获取点集合p2UpChanel = getUpChanel(point2, 0, pieceHeight);Map upLinkPoints = getXLinkPoints(p1UpChanel,p2UpChanel, pieceHeight);/ 向下遍历, 不超过Board(有方块的地方)的边框/ 以p1中心点向下遍历获取点集合p1DownChanel = getDownChanel(point1, heightMax, pieceHeight);/ 以p2中心点向下遍历获取点集合p2DownChanel = getDownChanel(point2, heightMax, pieceHeight);Map downLinkPoints = getXLinkPoints(p1DownChanel,p2DownChanel, pieceHeight);result.putAll(upLinkPoints);result.putAll(downLinkPoints);/ p1、p2位于同一列不能直接相连if (point1.x = point

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论