Android 教你完成最简单的贪吃蛇示例
先来看下实际效果图
先来初始化下
init {
biankuangPaint = Paint()
biankuangPaint!!.strokeWidth = 1f
biankuangPaint!!.color = context?.let { ContextCompat.getColor(it, R.color.w66) }!!
foodPaint = Paint()
foodPaint!!.color = ContextCompat.getColor(context, R.color.colorAccent)
snackPaint = Paint()
snackPaint!!.color = ContextCompat.getColor(context, R.color.snake_color)
val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.snake1)
snakeHead = ImageUtils.resizeBitmap(bitmap, foodWidth - 2 * wcharJaine, foodWidth - 2 * wcharJaine)
val bitmap2 = BitmapFactory.decodeResource(resources, R.mipmap.mushroom)
val mushroom = ImageUtils.resizeBitmap(bitmap2, foodWidth - 2 * wcharJaine, foodWidth - 2 * wcharJaine)
val bitmap3 = BitmapFactory.decodeResource(resources, R.mipmap.qingwa)
val quinta = ImageUtils.resizeBitmap(bitmap3, foodWidth - 2 * wcharJaine, foodWidth - 2 * wcharJaine)
val bitmap4 = BitmapFactory.decodeResource(resources, R.mipmap.mouse)
val mouse = ImageUtils.resizeBitmap(bitmap4, foodWidth - 2 * wcharJaine, foodWidth - 2 * wcharJaine)
foodBitmapArray[0] = mushroom
foodBitmapArray[1] = quinta
foodBitmapArray[2] = mouse
currentFoodBitmap = foodBitmapArray[0]
for (i in currentXIndex downTo 1) {
pjPoints?.add(PjPoint(i, 1))
}
}
由于贪吃蛇是用手指点击操作的,所以要对屏幕点击做处理(判断当前手指距离蛇头点击是横向多点还是纵向多点)
override fun onTouchEvent(event: MotionEvent): Boolean {
val action = event.action
if (action == MotionEvent.ACTION_DOWN) {
val pressX = event.x.toInt()
val pressY = event.y.toInt()
val jiangeX = pressX - currentXIndex * foodWidth
val jiangeY = pressY - currentYIndex * foodWidth
direction = if (Math.abs(jiangeX) > Math.abs(jiangeY)) {
if (jiangeX > 0) {
if (currentDirection == DirectEnum.leftEnum.ordinal) DirectEnum.leftEnum.ordinal else DirectEnum.rightEnum.ordinal
} else {
if (currentDirection == DirectEnum.rightEnum.ordinal) DirectEnum.rightEnum.ordinal else DirectEnum.leftEnum.ordinal
}
} else {
if (jiangeY > 0) {
if (currentDirection == DirectEnum.topEnum.ordinal) DirectEnum.topEnum.ordinal else DirectEnum.bottomEnum.ordinal
} else {
if (currentDirection == DirectEnum.bottomEnum.ordinal) DirectEnum.bottomEnum.ordinal else DirectEnum.topEnum.ordinal
}
}
currentDirection = direction
}
return true
}
然后就是测量,主要是测量屏幕大小和横竖网格所占的多少
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
measureWidth = measuredWidth
measureHeight = measuredHeight
allXIndex = measureWidth / foodWidth
measureWidth = allXIndex * foodWidth
allYIndex = measureHeight / foodWidth
measureHeight = allYIndex * foodWidth
}
画边框,蛇,食物
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//初始化边框
for (i in 0..allXIndex) {
biankuangPaint?.let {canvas.drawLine((foodWidth * i).toFloat(), 0f, (foodWidth * i).toFloat(), measureHeight.toFloat(),it)}
}
for (i in 0..allYIndex) {
biankuangPaint?.let {canvas.drawLine(0f, (foodWidth * i).toFloat(), measureWidth.toFloat(), (foodWidth * i).toFloat(),it)}
}
//初始化蛇
drawSnack(canvas)
//画食物
if (foodFlag) {
drawFood(canvas, foodPjPoint)
}
}
//画蛇
private fun drawSnack(canvas: Canvas) {
for (i in pjPoints!!.indices) {
val pjPoint = pjPoints!![i]
if (i == 0) { //画蛇头(图片)
canvas.drawBitmap(snakeHead!!, (foodWidth * pjPoint.x).toFloat(), (pjPoint.y * foodWidth).toFloat(), foodPaint)
} else { //画蛇身(基础圆)
canvas.drawCircle((pjPoint.x * foodWidth + foodWidth / 2).toFloat(), (pjPoint.y * foodWidth + foodWidth / 2).toFloat(), (foodWidth / 2).toFloat(), snackPaint!!)
}
}
}
//画食物
private fun drawFood(canvas: Canvas, pjPoint: PjPoint) {
canvas.drawBitmap(currentFoodBitmap!!, (foodWidth * pjPoint.x).toFloat(), (pjPoint.y * foodWidth).toFloat(), foodPaint)
}
结束的标志,撞了自己和墙
//判断蛇头是否撞向了自己
private fun isContainPoint(pjPoint: PjPoint): Boolean {
val size = pjPoints!!.size
for (i in 0 until size) {
val pjPoint1 = pjPoints!![i]
if (isContainFood(pjPoint, pjPoint1)) {
return true
}
}
return false
}
//判断蛇头是否吃了食物
private fun isContainFood(pjPoint: PjPoint, pjPoint1: PjPoint): Boolean {
return pjPoint.x == pjPoint1.x && pjPoint.y == pjPoint1.y
}
随机生成食物(如果生成的食物在蛇身上,则过滤继续生成食物)
private val randomFoodSnackPoint: PjPoint
get() {
var pjPoint: PjPoint
while (true) {
val randomX = (Math.random() * allXIndex).toInt()
val randomY = (Math.random() * allYIndex).toInt()
pjPoint = PjPoint(randomX, randomY)
if (isContainPoint(pjPoint)) {
continue
} else {
break
}
}
return pjPoint
}
更新蛇集合的数据和食物的标志(这部分是贪吃蛇的核心代码)
private fun updateSnackInfo() {
val headPjPoint = PjPoint(currentXIndex, currentYIndex)
if (isContainFood(headPjPoint, foodPjPoint)) {
pjPoints!!.add(0, headPjPoint)
foodFlag = false
foodPjPoint = randomFoodSnackPoint
val random = Random()
currentFoodBitmap = foodBitmapArray[random.nextInt(3)]
currentScore++
onGetScore!!.onGetScoreInfo(currentScore)
} else {
foodFlag = true
val size = pjPoints!!.size
//1,2,3,4,5,6
//x,1,2,3,4,5 (核心算法)
for (i in size - 2 downTo 0) {
pjPoints!![i + 1] = pjPoints!![i]
}
pjPoints!![0] = headPjPoint
}
invalidate()
}
下面最精彩的部分来了,线程跑起来,不断的更新蛇的所以点的集合(遇到食物吃掉增加长度,撞墙或者自己结束战斗)
//开始移动
fun startMoveSnack() {
currentScore = 0
onGetScore!!.onGetScoreInfo(currentScore)
initMoveSnack()
snackThread = Thread(snackRunnable)
snackThread!!.start()
}
private val snackRunnable = Runnable {
while (true) {
try {
if (isStop) {
break
}
Thread.sleep(snackSpeed)
when(direction){
DirectEnum.rightEnum.ordinal -> currentXIndex++
DirectEnum.bottomEnum.ordinal -> currentYIndex++
DirectEnum.leftEnum.ordinal -> currentXIndex--
DirectEnum.topEnum.ordinal -> currentYIndex--
}
mainHandler.sendEmptyMessage(DirectEnum.topEnum.ordinal)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
主线程更新ui
private val mainHandler: Handler = @SuppressLint("HandlerLeak")
object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
val what = msg.what
if (what == DirectEnum.gameOverQiang.ordinal) {
showToast("撞墙了游戏结束")
} else if (what == DirectEnum.gameOverself.ordinal) {
showToast("撞着自己了游戏结束")
}
updateSnackInfo()
}
}
其实贪吃蛇的原理的就是记录蛇头的位置就行了, 不断的改变蛇头, 然后把后面的部分覆盖前面的就行了, 说到底就是每次运动后的蛇所有点集合的改变, 如果对你感兴趣有用的可以点个赞,或者关注