学习了李刚老师的《疯狂Android讲义》,其中18章是介绍连连看的设计。从而学会了如何设计一个android小程序。
这个游戏,难度适中,适合初学者学习。
开发连连看游戏,除了需要理解游戏界面的数据模型外,程序开发者还需要判断两个方块是否可以相连,为了判断两个方块是否可以相连,开发者需要对两个方块所处的位置进行分类,然后针对不同的情况采用不同的判断算法进行判断,这需要开发者采用条理化的思维方式进行分析、处理,这也是这小程序需要重点掌握的能力。
开发游戏界面
连连看的游戏界面分为两个区域:
·游戏主界面区。
·控制按钮与数据显示区。
(一)开发界面布局
本程序将会使用一个RelativeLayout作为整体的界面布局元素,界面布局的上面是一个自定义组件,下面是一个水平排列的LinearLayout。
布局文件代码如下:reslayoutmain.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <Relativelayout 3 xmlns:android="http://schemas.android.com/apk/res/android“ 4 android:layout_width="fill_parent" 5 android:layout_height="fill_parent" 6 android:background="@drawable/room"> 7 <!--游戏主界面的自定义组件--> 8 <org.crazyit.link.view.GameView 9 android:id="@+id/gameView" 10 android:layout_width="fill_parent" 11 android:layout_height="fill_parent"/> 12 <!--水平排列的LinearLayout--> 13 android:layout_width="fill_parent" 14 android:layout_height="fill_parent" 15 android:orientation="horizontal" 16 android:layout_marginTop="380px" 17 android:background="#le72bb" 18 android:gravity="center"> 19 <!--控制游戏开始的按钮--> 20 <Button 21 android:id="@+id/startButton" 22 android:layout_width="wrap_content" 23 android:layout_height="wrap_content" 24 android:background="@drawable/button_selector"/> 25 <!--显示游戏剩余时间的文本框--> 26 <TextView 27 android:id="@+id/timeText" 28 android:layout_width="wrap_content" 29 android:layout_height="wrap_content" 30 android:gravity="center" 31 android:textSize="20dip" 32 android:width="150px" 33 android:textColor="#ff9"/> 34 </LinearLayout> 35 </RelativeLayout>
这个界面布局很简单,指定按钮的背景色时使用了@drawable/button_selector,这是一个在resdrawable目录下配置的StateListDrawable对象。
配置文件代码如下:resdrawable-mdpiutton_selector.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <selector xmlns:android="http://schemas.android.com/spk/res/android"> 3 <!--指定按钮按下时的图片--> 4 <item android:state_pressed="true" 5 android:drawable="@drawable/start_down"/> 6 <!--指定按钮按下时的图片--> 7 <item android:state_pressed="false" 8 android:drawable="@drawable/start"/> 9 </selector>
(二) 开发游戏界面组件
本游戏的界面组件采用了一个自定义View:GameView,它从View基类派出而出,这个自定义View的功能就是根据游戏状态来绘制游戏界面的全部方块。为了开发这个GameView,本程序还提供了一个Piece类,一个Piece对象代表游戏界面上的一个方块,它除了封装方块上的图片之外,还需要封装该方块代表二维数组中的哪个元素;也需要封装它的左上角在游戏界面中X、Y坐标。方块左上角的X、Y坐标可决定它的绘制位置,GameView根据这两个坐标绘制全部方块即可。
Piece类代码如下:srcorgcrazyitlinkviewPiece.java
1 public class Piece 2 { 3 // 保存方块对象的所对应的图片 4 private PieceImage image; 5 // 该方块的左上角的x坐标 6 private int beginX; 7 // 该方块的左上角的y座标 8 private int beginY; 9 // 该对象在Piece[][]数组中第一维的索引值 10 private int indexX; 11 // 该对象在Piece[][]数组中第二维的索引值 12 private int indexY; 13 14 // 只设置该Piece对象在数组中的索引值 15 public Piece(int indexX , int indexY) 16 { 17 this.indexX = indexX; 18 this.indexY = indexY; 19 } 20 21 public int getBeginX() 22 { 23 return beginX; 24 } 25 26 public void setBeginX(int beginX) 27 { 28 this.beginX = beginX; 29 } 30 31 public int getBeginY() 32 { 33 return beginY; 34 } 35 36 public void setBeginY(int beginY) 37 { 38 this.beginY = beginY; 39 } 40 41 public int getIndexX() 42 { 43 return indexX; 44 } 45 46 public void setIndexX(int indexX) 47 { 48 this.indexX = indexX; 49 } 50 51 public int getIndexY() 52 { 53 return indexY; 54 } 55 56 public void setIndexY(int indexY) 57 { 58 this.indexY = indexY; 59 } 60 61 62 public PieceImage getImage() 63 { 64 return image; 65 } 66 67 public void setImage(PieceImage image) 68 { 69 this.image = image; 70 } 71 72 // 获取该Piece的中心 73 public Point getCenter() 74 { 75 return new Point(getImage().getImage().getWidth() / 2 76 + getBeginX(), getBeginY() 77 + getImage().getImage().getHeight() / 2); 78 } 79 // 判断两个Piece上的图片是否相同 80 public boolean isSameImage(Piece other) 81 { 82 if (image == null) 83 { 84 if (other.image != null) 85 return false; 86 } 87 // 只要Piece封装图片ID相同,即可认为两个Piece相等。 88 return image.getImageId() == other.image.getImageId(); 89 } 90 }
上面的Piece类中封装的PieceImage代表了该方块上的图片,但此处并未直接使用Bitmap对象来代表方块上的图片——因为我们需要使用PieceImage来封装两个信息:
·Bitmap对象。
·图片资源的ID。
其中Bitmap对象用于在游戏界面上绘制方块,而图片资源的ID则代表该Piece对象的标识,用于判断两个Piece上的图片是否相同。如前面代码所示。
PieceImage类的代码如下:srcorgcrazyitlinkviewPieceImage.java
1 public class PieceImage 2 { 3 private Bitmap image; 4 private int imageId; 5 // 有参数的构造器 6 public PieceImage(Bitmap image, int imageId) 7 { 8 super(); 9 this.image = image; 10 this.imageId = imageId; 11 } 12 public Bitmap getImage() 13 { 14 return image; 15 } 16 public void setImage(Bitmap image) 17 { 18 this.image = image; 19 } 20 public int getImageId() 21 { 22 return imageId; 23 } 24 public void setImageId(int imageId) 25 { 26 this.imageId = imageId; 27 } 28 }
GameView主要就是根据游戏的状态数据来绘制界面上的方块,GameView继承了View组件,重写了View组件上onDraw(Canvas canvas)方法,重写该方法主要就是绘制游戏里剩余的方块;除此之外,它还会负责绘制连接方块的连接线。
GameView的代码如下:srcorgcrazyitlinkviewGameView.java
1 public class GameView extends View 2 { 3 // 游戏逻辑的实现类 4 private GameService gameService; 5 // 保存当前已经被选中的方块 6 private Piece selectedPiece; 7 // 连接信息对象 8 private LinkInfo linkInfo; 9 private Paint paint; 10 // 选中标识的图片对象 11 private Bitmap selectImage; 12 13 public GameView(Context context, AttributeSet attrs) 14 { 15 super(context, attrs); 16 this.paint = new Paint(); 17 // 设置连接线的颜色 18 this.paint.setColor(Color.RED); 19 // 设置连接线的粗细 20 this.paint.setStrokeWidth(3); 21 this.selectImage = ImageUtil.getSelectImage(context); 22 } 23 24 public void setLinkInfo(LinkInfo linkInfo) 25 { 26 this.linkInfo = linkInfo; 27 } 28 29 public void setGameService(GameService gameService) 30 { 31 this.gameService = gameService; 32 } 33 34 @Override 35 protected void onDraw(Canvas canvas) 36 { 37 super.onDraw(canvas); 38 if (this.gameService == null) 39 return; 40 Piece[][] pieces = gameService.getPieces(); 41 if (pieces != null) 42 { 43 // 遍历pieces二维数组 44 for (int i = 0; i < pieces.length; i++) 45 { 46 for (int j = 0; j < pieces[i].length; j++) 47 { 48 // 如果二维数组中该元素不为空(即有方块),将这个方块的图片画出来 49 if (pieces[i][j] != null) 50 { 51 // 得到这个Piece对象 52 Piece piece = pieces[i][j]; 53 // 根据方块左上角X、Y座标绘制方块 54 canvas.drawBitmap(piece.getImage().getImage(), 55 piece.getBeginX(), piece.getBeginY(), null); 56 } 57 } 58 } 59 } 60 // 如果当前对象中有linkInfo对象, 即连接信息 61 if (this.linkInfo != null) 62 { 63 // 绘制连接线 64 drawLine(this.linkInfo, canvas); 65 // 处理完后清空linkInfo对象 66 this.linkInfo = null; 67 } 68 // 画选中标识的图片 69 if (this.selectedPiece != null) 70 { 71 canvas.drawBitmap(this.selectImage, this.selectedPiece.getBeginX(), 72 this.selectedPiece.getBeginY(), null); 73 } 74 } 75 76 // 根据LinkInfo绘制连接线的方法。 77 private void drawLine(LinkInfo linkInfo, Canvas canvas) 78 { 79 // 获取LinkInfo中封装的所有连接点 80 List<Point> points = linkInfo.getLinkPoints(); 81 // 依次遍历linkInfo中的每个连接点 82 for (int i = 0; i < points.size() - 1; i++) 83 { 84 // 获取当前连接点与下一个连接点 85 Point currentPoint = points.get(i); 86 Point nextPoint = points.get(i + 1); 87 // 绘制连线 88 canvas.drawLine(currentPoint.x , currentPoint.y, 89 nextPoint.x, nextPoint.y, this.paint); 90 } 91 } 92 93 // 设置当前选中方块的方法 94 public void setSelectedPiece(Piece piece) 95 { 96 this.selectedPiece = piece; 97 } 98 99 // 开始游戏方法 100 public void startGame() 101 { 102 this.gameService.start(); 103 this.postInvalidate(); 104 } 105 }
GameView根据游戏的状态数据来绘制界面中的所有方块,根据LinkInfo来绘制两个方块间的连接线。上面的代码中定义了GameService对象,调用了GameService的getPiece()方法来获取游戏中剩余的方块,GameService是游戏的业务逻辑实现类。后面的篇幅会介绍(android开发学习之路——连连看之游戏逻辑(五))
(三)处理方块之间的连接线
LinkInfo是一个非常简单的工具类,它用于封装两个方块之间的连接信息——其实就是封装一个List,List里保存了连接线需要经过的点。在实现LinkInfo对象之前,先分析两个方块可以相连的情形。两个方块之间最多只能用3条线段相连,也就是说最多只能有2个“拐点”,加上两个方块的中心,方块的连接信息最多只需要4个连接点。考虑到LinkInfo最多需要封装4个连接点,最少需要封装2个连接点,因此定义LinkInfo类的代码如下:srcorgcrazyitlinkobjectLinkinfo.java
1 public class LinkInfo 2 { 3 // 创建一个集合用于保存连接点 4 private List<Point> points = new ArrayList<Point>(); 5 6 // 提供第一个构造器, 表示两个Point可以直接相连, 没有转折点 7 public LinkInfo(Point p1, Point p2) 8 { 9 // 加到集合中去 10 points.add(p1); 11 points.add(p2); 12 } 13 14 // 提供第二个构造器, 表示三个Point可以相连, p2是p1与p3之间的转折点 15 public LinkInfo(Point p1, Point p2, Point p3) 16 { 17 points.add(p1); 18 points.add(p2); 19 points.add(p3); 20 } 21 22 // 提供第三个构造器, 表示四个Point可以相连, p2, p3是p1与p4的转折点 23 public LinkInfo(Point p1, Point p2, Point p3, Point p4) 24 { 25 points.add(p1); 26 points.add(p2); 27 points.add(p3); 28 points.add(p4); 29 } 30 31 // 返回连接集合 32 public List<Point> getLinkPoints() 33 { 34 return points; 35 } 36 }
LinkInfo中所用的Point代表一个点,程序直接使用了android.graphics.Point类,每个Point封装了该点的X,Y坐标。
具体实现步骤连接: