Android 300行代码实现经典小游戏贪吃蛇 您所在的位置:网站首页 713小游戏 Android 300行代码实现经典小游戏贪吃蛇

Android 300行代码实现经典小游戏贪吃蛇

2023-11-20 15:51| 来源: 网络整理| 查看: 265

前言

贪吃蛇算是一个非常经典的小游戏了,本人00后,初次游玩是在小时候用诺基亚手机进行游玩的。这次算是复刻一下经典hhh,贪吃蛇算是一个制作起来非常简单的小游戏,本文使用Kotlin语言进行开发,用Java的小伙伴可以自己对照代码转化一下,不过2021年了写Android还是有必要学学Kotlin的。后面我也会出一些Kotlin知识点文章。

制作思路 地图

地图的创建可以使用一个二维数组,或者一维数组,这里使用二维数组,到时候操作就通过xy进行操作。

蛇头蛇身和食物

蛇头可以通过一个Point类进行标识,食物同理。蛇身呢就可以用一个List,里面存放Point,蛇头也需要存放进去。

地图元素标识

地图使用了二维数组,那么里面存放的值就必须要有一定的含义,所以可以创建两个类,一个用来设置标识,一个类用来声明标识,一当然也可以直接写在一起,这里我会抽离出来两个类。

移动

移动就是上下左右,可以通过滑动进行移动,也可以通过按钮点击进行移动,这里我使用按钮点击进行移动,滑动移动我后续也会补充。移动的话有个规则,就是当蛇在一个方向移动时,它不能直接转向到它的反方向,比如正在向右移动,改变移动方向时不能直接就向左移动。

游戏结束

结束条件很简单,就是吃到自己身体就结束,有些版本还有碰到边界就结束,我这版本是碰到边界不结束,而是继续移动。

效果预览

Screenrecorder-2021-09-06-18-57-54-6.gif

开始制作 创建标识

Type类

object Type { const val GRID = 0 const val FOOD = 1 const val HEAD = 2 const val BODY = 3 }

有了标识其实还不够,还需要一个类用来设置标识,根据标识的不同返回不同的颜色才行

class GameStyle(var type: Int) { fun getColor() = when (type) { Type.BODY -> Color.BLUE Type.HEAD -> Color.RED Type.GRID -> Color.GRAY Type.FOOD -> Color.YELLOW else -> Color.GRAY } }

代码都很简单,就不细说了

方向也需要标识

object Direction { const val LEFT = 0 const val RIGHT = 1 const val UP = 2 const val DOWN = 3 }

创建完这三个类就可以开始写游戏类了

创建GameView class GameView : View, Runnable { override fun onDraw(canvas: Canvas) {} override fun run() {} constructor(context: Context?) : this(context, null) constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int): super(context,attrs,defStyleAttr) }

创建完即成View,同时实现Runnable接口,因为贪吃蛇需要不停的移动,我们可以开一个线程在子线程中进行。

现在创建一些后续需要的变量,这块代码都很简单同时都写了注释可以根据注释进行理解

一些变量

private val gameSize = 14 // 地图的长宽 private var screenHeight = 0 // 屏幕的整体高度 private var screenWidth = 0 // 屏幕的整体宽度 private val map = arrayListOf() // 整个地图的元素 private val snakeLocation = arrayListOf() // 蛇的位置 private val snakeHead = Point(gameSize / 2, gameSize / 2) // 蛇头位置 private var foodLocation = Point() // 食物位置 private var moveSpeed = 4 // 移动速度 private var snakeLength = 4 // 蛇的长度 private var snakeDirection = Direction.UP // 移动方向 private var eatCount = 0 // 吃的食物数量 private val thread = Thread(this) // 游戏线程 private var gameStart = false // 游戏是否开始 private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG) // 画笔 /** * 在onSizeChanged可以获取到外部给GameView设置的宽高,所以这里给先前创建的变量进行赋值 */ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) screenHeight = height screenWidth = width } 初始化函数

绘制之前还需要一个初始化函数,然后再第三个构造函数中调用

/** * 初始化函数 */ private fun init() { // 地图初始化 for (y in 0 until gameSize) { val styleList = arrayListOf() for (x in 0 until gameSize) { styleList.add(GameStyle(Type.GRID)) // 默认全部为格子 } map.add(styleList) } // 随机食物的位置 randomCreateFood() // 蛇头位置更新到蛇身上 snakeLocation.add(Point(snakeHead.x, snakeHead.y)) gameStart = true thread.start() // 开始游戏 } // 第三个构造函数中调用 constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( context, attrs, defStyleAttr ) { init() }

你会发现这里调用了一个新的函数,就是随机食物位置。

随机食物位置

食物的位置理应是随机的,但是它不能随机到蛇的身上,所以需要一个循环判断,如果到蛇身上了就重新随机

/** * 随机生成食物 */ private fun randomCreateFood() { var food = Point(Random.nextInt(gameSize), Random.nextInt(gameSize)) var index = 0 while (index food = Point(Random.nextInt(gameSize), Random.nextInt(gameSize)) index = 0 } index++ } foodLocation = food refreshFood() }

代码很简单,可以根据上述描述理代码

食物刷新

生成了食物还不够,还需要刷新到地图中,最后绘制出来,所以在末尾调用了refreshFood(),这一块代码就更加简单了,一看应该就能懂

/** * 食物更新到地图上 */ private fun refreshFood() { map[foodLocation.y][foodLocation.x].type = Type.FOOD } 重写onDraw(canvas: Canvas)

现在就需要绘制了,绘制我们可以通过不同的标识设置画笔是否是空心还是实心,如果是网格就空心,食物身体头都为实心,颜色一样可以通过标识进行获取,绘制的形状就可以绘制矩形.

完整的onDraw(canvas: Canvas)代码如下:

override fun onDraw(canvas: Canvas) { super.onDraw(canvas) val blockWidth = screenWidth / gameSize // 每个网格的宽度 val blockHeight = screenHeight / gameSize // 每个网格的高度 // 绘制地图元素 for (y in 0 until gameSize) { for (x in 0 until gameSize) { // 每个矩形的范围 val left = x * blockWidth.toFloat() val right = (x + 1f) * blockWidth val top = y * blockHeight.toFloat() val bottom = (y + 1f) * blockHeight // 不同的标识设置不同的画笔样式 when (map[y][x].type) { Type.GRID -> mPaint.style = Paint.Style.STROKE Type.FOOD, Type.BODY -> mPaint.style = Paint.Style.FILL } // 根据标识设置画笔颜色 mPaint.color = map[y][x].getColor() // 当前的位置是否为头部 if (x == snakeHead.x && y == snakeHead.y) { mPaint.style = Paint.Style.FILL mPaint.color = GameStyle(Type.HEAD).getColor() } // 绘制矩形 canvas.drawRect(left, top, right, bottom, mPaint) } } } 蛇的移动

如果蛇向上移动并且头部在移动一次的时候小于0,这时候我们就需要蛇头部在下一个移动的位置到gameSize - 1的位置上,不然就是直接当前的位置-1,最后我们将移动后的位置加入到蛇的位置数组中,最后的代码就是这样:

/** * 移动 */ private fun moveSnake() { when (snakeDirection) { Direction.LEFT -> { if (snakeHead.x - 1 snakeHead.x = snakeHead.x - 1 } snakeLocation.add(Point(snakeHead.x, snakeHead.y)) } Direction.RIGHT -> { if (snakeHead.x + 1 >= gameSize) { snakeHead.x = 0 } else { snakeHead.x = snakeHead.x + 1 } snakeLocation.add(Point(snakeHead.x, snakeHead.y)) } Direction.UP -> { if (snakeHead.y - 1 snakeHead.y = snakeHead.y - 1 } snakeLocation.add(Point(snakeHead.x, snakeHead.y)) } Direction.DOWN -> { if (snakeHead.y + 1 >= gameSize) { snakeHead.y = 0 } else { snakeHead.y = snakeHead.y + 1 } snakeLocation.add(Point(snakeHead.x, snakeHead.y)) } } }

when语句判断移动的方向,内部if判断是否到边界,然后根据这个条件进行设置移动后的新值,最后添加到蛇的位置数组。

绘制蛇

移动有了现在就是绘制蛇的位置了,蛇向前移动,那么移动后的原位置就需要更新成路面,同时因为上述移动位置的更新添加了一位,就需要在绘制蛇身删除一位

private fun drawSnakeBody() { var length = snakeLength for (i in snakeLocation.indices.reversed()) { if (length > 0) { length-- } else { val body = snakeLocation[i] map[body.y][body.x].type = Type.GRID } } length = snakeLength for (i in snakeLocation.indices.reversed()) { if (length > 0) { length-- } else { snakeLocation.removeAt(i) } } } 刷新蛇身 /** * 身体更新到地图上 */ private fun refreshBody() { // 减1是因为不需要包括蛇头 for (i in 0 until snakeLocation.size - 1) { map[snakeLocation[i].y][snakeLocation[i].x].type = Type.BODY } } 判断吃

现在就是最后一步了,判断吃的方法,如果吃的是食物,那么长度+1,食物重新刷新位置,如果吃的是身体游戏直接结束

/** * 吃判断 */ private fun judgeEat() { // 是否吃到自己 val head = snakeLocation[snakeLocation.size - 1] for (i in 0 until snakeLocation.size - 2) { val body = snakeLocation[i] if (body.x == head.x && body.y == head.y) { gameStart = false // 吃到身体游戏结束 } } // 吃到食物 if (head.x == foodLocation.x && head.y == foodLocation.y) { snakeLength++ // 长度+1 randomCreateFood() // 刷新食物 } } run()

上述的移动和绘制需要在自线程中执行,所以需要写到run方法里面

override fun run() { while (gameStart) { moveSnake() // 移动蛇 drawSnakeBody() // 绘制蛇身 refreshBody() // 刷新蛇身 judgeEat() // 判断吃 postInvalidate() // 刷新视图 Thread.sleep(1000 / moveSpeed.toLong()) } } 外部设置移动函数 /** * 设置移动方向 */ fun setMove(direction: Int) { when { snakeDirection == Direction.LEFT && direction == Direction.RIGHT -> return snakeDirection == Direction.RIGHT && direction == Direction.LEFT -> return snakeDirection == Direction.UP && direction == Direction.DOWN -> return snakeDirection == Direction.DOWN && direction == Direction.UP -> return } snakeDirection = direction } 总结

现在游戏就做完啦,是不是很简单,其实贪吃蛇算是制作起来非常简单的的一款游戏,同时代码量也很少,所以我没有过多解释每个函数的含义以及一些细节,而是以注释的形式进行了解释,您可以通过制作思路配合理解,最后谢谢您的观看!

最后欢迎关注我的csdn和掘金,掘金的地址:个人主页



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有