学习了李刚老师的《疯狂Android讲义》,其中18章是介绍连连看的设计。从而学会了如何设计一个android小程序。
这个游戏,难度适中,适合初学者学习。
开发连连看游戏,除了需要理解游戏界面的数据模型外,程序开发者还需要判断两个方块是否可以相连,为了判断两个方块是否可以相连,开发者需要对两个方块所处的位置进行分类,然后针对不同的情况采用不同的判断算法进行判断,这需要开发者采用条理化的思维方式进行分析、处理,这也是这小程序需要重点掌握的能力。
开发游戏界面
连连看的游戏界面分为两个区域:
·游戏主界面区。
·控制按钮与数据显示区。
(一)开发界面布局
本程序将会使用一个RelativeLayout作为整体的界面布局元素,界面布局的上面是一个自定义组件,下面是一个水平排列的LinearLayout。
布局文件代码如下:res\layout\main.xml
1 27 8 12 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 25 26 34 35
这个界面布局很简单,指定按钮的背景色时使用了@drawable/button_selector,这是一个在res\drawable目录下配置的StateListDrawable对象。
配置文件代码如下:res\drawable-mdpi\button_selector.xml
1 23 4 - 6 7
- 9
(二) 开发游戏界面组件
本游戏的界面组件采用了一个自定义View:GameView,它从View基类派出而出,这个自定义View的功能就是根据游戏状态来绘制游戏界面的全部方块。为了开发这个GameView,本程序还提供了一个Piece类,一个Piece对象代表游戏界面上的一个方块,它除了封装方块上的图片之外,还需要封装该方块代表二维数组中的哪个元素;也需要封装它的左上角在游戏界面中X、Y坐标。方块左上角的X、Y坐标可决定它的绘制位置,GameView根据这两个坐标绘制全部方块即可。
Piece类代码如下:src\org\crazyit\link\view\Piece.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() / 276 + 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类的代码如下:src\org\crazyit\link\view\PieceImage.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的代码如下:src\org\crazyit\link\view\GameView.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 Listpoints = 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是游戏的业务逻辑实现类。后面的篇幅会介绍()
(三)处理方块之间的连接线
LinkInfo是一个非常简单的工具类,它用于封装两个方块之间的连接信息——其实就是封装一个List,List里保存了连接线需要经过的点。在实现LinkInfo对象之前,先分析两个方块可以相连的情形。两个方块之间最多只能用3条线段相连,也就是说最多只能有2个“拐点”,加上两个方块的中心,方块的连接信息最多只需要4个连接点。考虑到LinkInfo最多需要封装4个连接点,最少需要封装2个连接点,因此定义LinkInfo类的代码如下:src\org\crazyit\link\object\Linkinfo.java
1 public class LinkInfo 2 { 3 // 创建一个集合用于保存连接点 4 private Listpoints = new ArrayList (); 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 getLinkPoints()33 {34 return points;35 }36 }
LinkInfo中所用的Point代表一个点,程序直接使用了android.graphics.Point类,每个Point封装了该点的X,Y坐标。
具体实现步骤连接: