前言java
今天來作個打方塊的小遊戲,繼續熟悉kotlin的語法,看下要實現的效果圖git
看着效果圖好像挺難的樣子,但理清思緒後,你會發現特別的簡單,仍是那句話,學習方法最重要github
思路canvas
構造界面 :bash
這個部分比較簡單,根據控件的比例來畫小球、擋板和擊打的方塊,全部擊打的方塊存儲在一個集合裏面,方塊裏面存儲的信息有left、top、right、bottom位置信息和是否被擊打過了的標誌app
擋板的滑動 :dom
下面的擋板須要根據手勢的左右移動來反彈小球,因此,咱們能夠重寫onTouch來實現ide
小球的運動 :post
咱們在線程裏面開啓一個white循環,不停的改變小球的位置,而後重繪界面,小球的運動是有規則的,碰到四周的界面要回彈,碰到擊打的方塊要回彈,碰到擋板也要回彈,那麼,如何回彈呢?咱們給小球作一個累加值,讓小球不停的去加這個值,碰到碰撞物咱們就給這個累加值取反,舉個例子,如今offsetX是一個正整數,那麼ballX+=offsetX,如今小球是往右移動,當碰撞到最右邊的時候,咱們給offsetX取反,也就是offsetX=offsetX*-1,這時候offsetX變成了一個負數,那麼小球ballX+=offset就會越加越少,也就是往左移動,移動到最左邊的時候咱們又給offsetX=offsetX*-1,這時候offsetX又變回了正數,這時候,來回的反彈就實現了,ballY的移動也是如此學習
小球擊打方塊 :
小球擊打到方塊有四個方向:左、上、右、下,咱們就說說擊打下方的判斷吧,小球頂部碰撞到方塊的區域爲方塊的left和right區域,而且當小球的頂部恰好突破方塊的bottom位置時,算是一次有效的碰撞,而後咱們給此次碰撞作一個標記,而後反彈小球,下次作碰撞的時候咱們忽略已經碰撞過的地方,而且不繪製碰撞過的區域
遊戲結束 :
在每次循環結束時都去統計集合裏碰撞標誌數量是否等於集合的size,是的話就結束循環,遊戲結束
思路整理清晰後,咱們來一一實現
構造界面
首先來繪製一下小球和擋板
var width: Float = 0f
var height: Float = 0f
/**
* 移動滑塊的寬度
*/
var boardWdith: Float = 0f
/**
* 擋板的高度
*/
var boardHeight: Float = 0f
/**
* 擋板距離頂部的距離
*/
var board2Top: Float = 0f
/**
* 擋板距離左邊的距離
*/
var board2Left: Float = 0f
/**
* 小球的半徑
*/
var ballRadius: Float = 0f
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
width = w.toFloat()
height = h.toFloat()
//擋板的寬度
boardWdith = width / 8
//擋板距離頂部的距離
board2Top = height / 8 * 7
//擋板的left距離左邊的距離,目的使擋板居中
board2Left = width / 2 - boardWdith / 2
//設置小球的半徑爲擋板的1/4
ballRadius = boardWdith / 4
//設置小球的x和y座標
ballX = width / 2
ballY = board2Top - ballRadius - dip(10).toFloat() / 2
ballPaint.style = Paint.Style.FILL
ballPaint.isAntiAlias = true
ballPaint.color = resources.getColor(R.color.colorAccent)
boardPaint.style = Paint.Style.STROKE
boardPaint.isAntiAlias = true
boardPaint.strokeWidth = dip(10).toFloat()
boardPaint.color = resources.getColor(R.color.colorPrimary)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
setBackgroundColor(resources.getColor(R.color.black))
canvas.drawLine(board2Left, board2Top, board2Left + boardWdith, board2Top, boardPaint)
canvas.drawCircle(ballX, ballY, ballRadius, ballPaint)
}
複製代碼
ok,擋板和小球已經畫好了
而後,咱們來畫一下被擊打的方塊,首先定義一個存儲方塊信息的Bean類
/**
* @author wangqi
* @since 2017/12/10 17:26
*/
public class Brick {
/**
* 存儲方塊的顏色
*/
private String color;
/**
* 存儲方塊的座標
*/
private RectF rectF;
/**
* 判斷是否碰撞到了,默認爲false未碰撞
*/
private boolean isImpact;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public RectF getRectF() {
return rectF;
}
public void setRectF(RectF rectF) {
this.rectF = rectF;
}
public boolean isImpact() {
return isImpact;
}
public void setImpact(boolean impact) {
isImpact = impact;
}
}
複製代碼
而後咱們來看看怎麼繪製
/**
* 定義一個存儲方塊的集合
*/
var brickList: MutableList<Brick> = mutableListOf()
/**
* 方塊的寬度
*/
var brickWidth = 0f
/**
* 方塊的高度
*/
var brickHeight = 0f
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
...
//方塊的寬度是view的1/5
brickWidth = width / 5
//方塊的高度是寬度的一半
brickHeight = brickWidth / 2
/*初始化方塊 設置一個三行四列的方塊*/
for (row in 0..3) {
for (col in 0..4) {
createBricks(row, col)
}
}
paintLine.strokeWidth = dip(1.0f).toFloat()
paintLine.isAntiAlias = true
paintLine.textSize = dip(width / 50).toFloat()
paintLine.style = Paint.Style.FILL
}
/**
* 建立方塊
*/
fun createBricks(row: Int, col: Int) {
var brick = Brick()
var rectF = RectF()
rectF.left = brickWidth * col
rectF.top = brickHeight * row
rectF.right = brickWidth * (col + 1)
rectF.bottom = brickHeight * (row + 1)
brick.rectF = rectF
val hex = "#" + Integer.toHexString((-16777216 * Math.random()).toInt())
brick.color = hex
brickList.add(brick)
}
複製代碼
ok,方塊完美的繪製
擋板的滑動
擋板的滑動部分,咱們只須要重寫onTouch方法,而後再每次move的過程當中去改變擋板距離View左邊界的距離
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
}
MotionEvent.ACTION_MOVE -> {
board2Left = event.x - boardWdith / 2
invalidate()
}
MotionEvent.ACTION_UP -> {
}
}
return true
}
複製代碼
小球的運動
小球的運動是這裏面最核心的部分了,咱們得細細的講講
首先,咱們須要定義一個線程,在線程裏面定義一個while循環,sleep50毫秒去重回界面,因此,咱們要在這50毫秒的時間裏,去改變小球的運動軌跡、邊界值狀況、是否碰撞到方塊、是否碰撞到擋板和遊戲是否結束,咱們先把小球給運動起來再說
/**
* 結束循環的標誌位
*/
var isOver: Boolean = false
/**
* 小球x方向每次移動的偏移量
*/
var vx: Float = 8f
/**
* 小球y方向每次移動的偏移量
* 默認爲負數,由於小球是向上運動
*/
var vy: Float = -8f
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
...
//開啓線程
thread {
while (!isOver) {
ballX += vx
ballY += vy
/*
邊界值斷定
若是小球小於左邊界或大於右邊界則x方向取反
*/
if (ballX + ballRadius > width || ballX - ballRadius < 0) {
vx *= -1
}
/*
邊界值斷定
若是小球大於底部邊界或小於頂部邊界則Y方向取反
*/
if (ballY - ballRadius < 0 || ballY + ballRadius > height) {
vy *= -1
}
Thread.sleep(50)
postInvalidate()
}
}.start()
}
複製代碼
小球開始運動了,咦,小球怎麼忽然不見了,哈哈,由於被方塊遮擋住了
小球移動解決了,接下來咱們來處理下小球彈到擋板反彈
//開啓線程
thread {
while (!isOver) {
//邊界值判斷
...
/*
判斷小球是否落在滑塊上
小球x軸的中心大於擋板的left而且小球x軸中心小於擋板的右邊而且小球的y軸中心加上半徑加上擋板高度的一半
*/
if (ballX >= board2Left && ballX <= board2Left + boardWdith
&& ballY >= board2Top - ballRadius - dip(10).toFloat() / 2
) {
//改變Y軸的運動方向
vy *= -1
}
...
}
}
複製代碼
擋板的判斷知道了,那麼小球和方塊的碰撞也就天然清晰了
//開啓線程
thread {
while (!isOver) {
//判斷小球是否落在滑塊上
...
/*
* 循環集合的每個方塊,判斷小球當前的位置是否碰撞到方塊
*/
for (i in brickList.indices) {
//拿到方塊
val brick = brickList[i]
//忽略撞擊過的方塊
if (brick.isImpact) {
continue
}
//獲取方塊的座標
val rectF = brick.rectF
/*
判斷小球是否撞擊到方塊的底部
小球x軸的中心大於方塊的left
小球x軸的中心小於方塊的right
小球y軸中心減去半徑,也就是小球的頂部,是否小於等於方塊的底部,也就是穿過方塊底部的一瞬間
*/
if (ballX >= rectF.left && ballX <= rectF.right && ballY - ballRadius <= rectF.bottom) {
//設置該方塊已被撞擊
brick.isImpact = true
//方向取反
vy *= -1
}
}
/*
* 統計被撞擊方塊的數量是否等於集合,是的話代表遊戲結束,設置結束標誌位,中止while循環
*/
if (brickList.count { it.isImpact } == brickList.size) {
isOver = true
}
...
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
...
if (isOver) {
val text = "通關成功"
//獲取文字的寬度,目的是爲了文字居中
val textWidth = paintLine.measureText(text)
canvas.drawText(text, width / 2 - textWidth / 2, 100, paintLine)
}
}
複製代碼
最終效果圖
通關成功
總結
小球碰撞到底部邊界的判斷我沒有去作,緣由是爲了能擊打到方塊,增長趣味性,還有碰撞方塊的四個方向,我只作了碰撞到底部的方向,有興趣的同窗能夠本身試着補上,查看完整源碼
理論和實踐相輔相成,理論是規劃實踐的實施性,實踐是爲了證實理論
關注公衆號codelang