2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > Android 300行代码实现经典小游戏贪吃蛇

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

时间:2019-12-11 12:45:40

相关推荐

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

前言

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

制作思路

地图

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

蛇头蛇身和食物

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

地图元素标识

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

移动

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

游戏结束

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

效果预览

开始制作

创建标识

Type类

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

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

class GameStyle(var type: Int) {fun getColor() = when (type) {Type.BODY -> Color.BLUEType.HEAD -> Color.REDType.GRID -> Color.GRAYType.FOOD -> Color.YELLOWelse -> Color.GRAY}}

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

方向也需要标识

object Direction {const val LEFT = 0const val RIGHT = 1const val UP = 2const 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<ArrayList<GameStyle>>() // 整个地图的元素private val snakeLocation = arrayListOf<Point>() // 蛇的位置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 = heightscreenWidth = width}

初始化函数

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

/*** 初始化函数*/private fun init() {// 地图初始化for (y in 0 until gameSize) {val styleList = arrayListOf<GameStyle>()for (x in 0 until gameSize) {styleList.add(GameStyle(Type.GRID)) // 默认全部为格子}map.add(styleList)}// 随机食物的位置randomCreateFood()// 蛇头位置更新到蛇身上snakeLocation.add(Point(snakeHead.x, snakeHead.y))gameStart = truethread.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 = 0while (index < snakeLocation.size - 1) {if (food.x == snakeLocation[index].x && food.y == snakeLocation[index].y) {food = Point(Random.nextInt(gameSize), Random.nextInt(gameSize))index = 0}index++}foodLocation = foodrefreshFood()}

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

食物刷新

生成了食物还不够,还需要刷新到地图中,最后绘制出来,所以在末尾调用了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) * blockWidthval top = y * blockHeight.toFloat()val bottom = (y + 1f) * blockHeight// 不同的标识设置不同的画笔样式when (map[y][x].type) {Type.GRID -> mPaint.style = Paint.Style.STROKEType.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.FILLmPaint.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 < 0) {snakeHead.x = gameSize - 1} else {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 < 0) {snakeHead.y = gameSize - 1} else {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 = snakeLengthfor (i in snakeLocation.indices.reversed()) {if (length > 0) {length--} else {val body = snakeLocation[i]map[body.y][body.x].type = Type.GRID}}length = snakeLengthfor (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++ // 长度+1randomCreateFood() // 刷新食物}}

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 -> returnsnakeDirection == Direction.RIGHT && direction == Direction.LEFT -> returnsnakeDirection == Direction.UP && direction == Direction.DOWN -> returnsnakeDirection == Direction.DOWN && direction == Direction.UP -> return}snakeDirection = direction}

总结

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

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。