当前位置: 首页 > news >正文

安卓小游戏:贪吃蛇

安卓小游戏:贪吃蛇

前言

这个是通过自定义View实现小游戏的第二篇,实际上第一篇做起来麻烦点,后面的基本就是照葫芦画瓢了,只要设计下游戏逻辑就行了,技术上不难,想法比较重要。

需求

贪吃蛇,太经典了,小时候在诺基亚上玩了不知道多少回,游戏也很简单,就两个逻辑,一个是吃东西变长,一个是吃到自己死亡。核心思想如下:

  • 1,载入配置,读取游戏信息及掩图
  • 2,启动游戏控制逻辑
  • 3,手势控制切换方向

效果图

这里就稍微演示了一下,就这速度,要演示到死亡估计得一分钟以上了,掩图用的比较low,勉强凑合。

snark

代码

import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.silencefly96.module_views.R
import java.lang.ref.WeakReference
import kotlin.math.abs/*** 贪吃蛇游戏view** @author silence* @date 2023-02-07*/
class SnarkGameView @JvmOverloads constructor(context: Context,attributeSet: AttributeSet? = null,defStyleAttr: Int = 0
) : View(context, attributeSet, defStyleAttr) {companion object{// 四个方向const val DIR_UP = 0const val DIR_RIGHT = 1const val DIR_DOWN = 2const val DIR_LEFT = 3// 游戏更新间隔,一秒5次const val GAME_FLUSH_TIME = 200L// 蛇体移动频率const val SNARK_MOVE_TIME = 600L// 食物添加间隔时间const val FOOD_ADD_TIME = 5000L// 食物存活时间const val FOOD_ALIVE_TIME = 10000L// 食物闪烁时间,要比存货时间长const val FOOD_BLING_TIME = 3000L// 食物闪烁间隔const val FOOD_BLING_FREQ = 300L}// 屏幕划分数量及等分长度private val rowNumb: Intprivate var rowDelta: Int = 0private val colNumb: Intprivate var colDelta: Int = 0// 节点掩图private val mNodeMask: Bitmap?// 头节点private val mHead = Snark(0, 0, DIR_DOWN, null)// 尾节点private var mTail = mHead// 食物数组private val mFoodList = ArrayList<Food>()// 游戏控制器private val mGameController = GameController(this)// 画笔private val mPaint = Paint().apply {color = Color.LTGRAYstrokeWidth = 1fstyle = Paint.Style.STROKEflags = Paint.ANTI_ALIAS_FLAGtextAlign = Paint.Align.CENTERtextSize = 30f}// 上一个触摸点X、Y的坐标private var mLastX = 0fprivate var mLastY = 0finit {// 读取配置val typedArray =context.obtainStyledAttributes(attributeSet, R.styleable.SnarkGameView)// 横竖划分rowNumb = typedArray.getInteger(R.styleable.SnarkGameView_rowNumb, 30)colNumb = typedArray.getInteger(R.styleable.SnarkGameView_colNumb, 20)// 节点掩图val drawable = typedArray.getDrawable(R.styleable.SnarkGameView_node)mNodeMask = if (drawable != null) drawableToBitmap(drawable) else nulltypedArray.recycle()}private fun drawableToBitmap(drawable: Drawable): Bitmap? {val w = drawable.intrinsicWidthval h = drawable.intrinsicHeightval config = Bitmap.Config.ARGB_8888val bitmap = Bitmap.createBitmap(w, h, config)//注意,下面三行代码要用到,否则在View或者SurfaceView里的canvas.drawBitmap会看不到图val canvas = Canvas(bitmap)drawable.setBounds(0, 0, w, h)drawable.draw(canvas)return bitmap}// 完成测量开始游戏override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {super.onSizeChanged(w, h, oldw, oldh)rowDelta = h / rowNumbcolDelta = w / colNumb// 开始游戏load()}// 加载private fun load() {mGameController.removeMessages(0)// 设置贪吃蛇的位置mHead.posX = colNumb / 2mHead.posY = rowNumb / 2mGameController.sendEmptyMessageDelayed(0, GAME_FLUSH_TIME)}// 重新加载private fun reload() {mGameController.removeMessages(0)// 清空界面mFoodList.clear()mHead.posX = colNumb / 2mHead.posY = rowNumb / 2// 蛇体链表回收,让GC通过可达性分析去回收mHead.next = nullmGameController.isGameOver = falsemGameController.sendEmptyMessageDelayed(0, GAME_FLUSH_TIME)}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),getDefaultSize(0, heightMeasureSpec))}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)// 绘制网格for (i in 0..rowNumb) {canvas.drawLine(0f, rowDelta * i.toFloat(),width.toFloat(), rowDelta * i.toFloat(), mPaint)}for (i in 0..colNumb) {canvas.drawLine(colDelta * i.toFloat(), 0f,colDelta * i.toFloat(), height.toFloat(), mPaint)}// 绘制食物for (food in mFoodList) {if (food.show) canvas.drawBitmap(mNodeMask!!,(food.posX + 0.5f) * colDelta - mNodeMask.width / 2,(food.posY + 0.5f) * rowDelta - mNodeMask.height / 2, mPaint)}// 绘制蛇体var p: Snark? = mHeadwhile (p != null) {canvas.drawBitmap(mNodeMask!!,(p.posX + 0.5f) * colDelta - mNodeMask.width / 2,(p.posY + 0.5f) * rowDelta - mNodeMask.height / 2, mPaint)p = p.next}}@SuppressLint("ClickableViewAccessibility")override fun onTouchEvent(event: MotionEvent): Boolean {when(event.action) {MotionEvent.ACTION_DOWN -> {mLastX = event.xmLastY = event.y}MotionEvent.ACTION_MOVE -> {}MotionEvent.ACTION_UP -> {val lenX = event.x - mLastXval lenY = event.y - mLastYmHead.dir = if (abs(lenX) > abs(lenY)) {if (lenX >= 0) DIR_RIGHT else DIR_LEFT}else {if (lenY >= 0) DIR_DOWN else DIR_UP}invalidate()}}return true}private fun gameOver() {AlertDialog.Builder(context).setTitle("继续游戏").setMessage("请点击确认继续游戏").setPositiveButton("确认") { _, _ -> reload() }.setNegativeButton("取消", null).create().show()}// kotlin自动编译为Java静态类,控件引用使用弱引用class GameController(view: SnarkGameView): Handler(Looper.getMainLooper()){// 控件引用private val mRef: WeakReference<SnarkGameView> = WeakReference(view)// 蛇体移动控制private var mSnarkCounter = 0// 食物闪烁控制private var mFoodCounter = 0// 游戏结束标志internal var isGameOver = falseoverride fun handleMessage(msg: Message) {mRef.get()?.let { gameView ->mSnarkCounter++if (mSnarkCounter == (SNARK_MOVE_TIME / GAME_FLUSH_TIME).toInt()) {// 移动蛇体var p: Snark? = gameView.mHeadvar dir = gameView.mHead.dirwhile (p != null) {// 移动逻辑,会穿过屏幕边界when(p.dir) {DIR_UP -> {p.posY--if (p.posY < 0)  {p.posY = gameView.rowNumb - 1}}DIR_RIGHT -> {p.posX++if (p.posX >= gameView.colNumb) {p.posX = 0}}DIR_DOWN -> {p.posY++if (p.posY >= gameView.rowNumb)  {p.posY = 0}}DIR_LEFT -> {p.posX--if (p.posX < 0) {p.posX = gameView.colNumb - 1}}}// 死亡逻辑,蛇头撞到身体了if (p != gameView.mHead &&p.posX == gameView.mHead.posX && p.posY == gameView.mHead.posY) {isGameOver = true}// 移动修改方向为上一节的方val temp = p.dirp.dir = dirdir = tempp = p.next}mSnarkCounter = 0}// 食物控制val iterator = gameView.mFoodList.iterator()while (iterator.hasNext()) {val food = iterator.next()food.counter++// 食物消失if (food.counter >= (FOOD_ALIVE_TIME / GAME_FLUSH_TIME)) {iterator.remove()continue}// 食物闪烁if (food.counter >= ((FOOD_ALIVE_TIME - FOOD_BLING_TIME) / GAME_FLUSH_TIME)) {food.blingCounter++if (food.blingCounter >= (FOOD_BLING_FREQ / GAME_FLUSH_TIME)) {food.show = !food.showfood.blingCounter = 0}}// 食物被吃,添加一节蛇体到尾部if (food.posX == gameView.mHead.posX && food.posY == gameView.mHead.posY) {var x = gameView.mTail.posXvar y = gameView.mTail.posY// 在尾部添加when(gameView.mTail.dir) {DIR_UP -> y++DIR_RIGHT -> x--DIR_DOWN -> y--DIR_LEFT -> x++}gameView.mTail.next = Snark(x, y, gameView.mTail.dir,null)gameView.mTail = gameView.mTail.next!!// 移除被吃食物iterator.remove()}}mFoodCounter++if (mFoodCounter == (FOOD_ADD_TIME / GAME_FLUSH_TIME).toInt()) {// 生成食物val x = (Math.random() * gameView.colNumb).toInt()val y = (Math.random() * gameView.rowNumb).toInt()gameView.mFoodList.add(Food(x, y, 0, 0,true))mFoodCounter = 0}// 循环发送消息,刷新页面gameView.invalidate()if (!isGameOver) {gameView.mGameController.sendEmptyMessageDelayed(0, GAME_FLUSH_TIME)}else {gameView.gameOver()}}}}data class Food(var posX: Int, var posY: Int, var counter: Int, var blingCounter: Int, var show: Boolean)data class Snark(var posX: Int, var posY: Int, var dir: Int, var next: Snark? = null)
}

对应style配置

res -> values -> snark_game_view_style.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name ="SnarkGameView"><attr name="rowNumb" format="integer"/><attr name="colNumb" format="integer"/><attr name="node" format="reference"/></declare-styleable>
</resources>

蛇体掩图也给一下吧,当然你找点好看的图片代替下会更好!

res -> drawable -> ic_node.xml

<vector android:height="24dp" android:tint="#6F6A6A"android:viewportHeight="24" android:viewportWidth="24"android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"><path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z"/>
</vector>

主要问题

下面简单讲讲吧,大部分还是和上一篇的飞机大战类似,这里就讲讲不一样的或者做点补充吧。

资源加载

资源加载就是从styleable配置里面读取设置,这里贪吃蛇是完全网格化的游戏,这里读取了行数和列数,后面把屏幕等分,获取到了行高和列长,转换逻辑得注意下。蛇的掩图逻辑和上一篇博文一致,不细说了。

蛇体移动

蛇体的移动实际就要有一个方向,这里每一截蛇都是一个节点,构成了一个链表,每个节点的方向都是移动前上一个节点的方向,这样移动起来就有效果了。当然方向的获取也简单,在onTouchEvent中监听DOWN和UP事件就行了,比较起点和终点,看看往哪边滑动的,更改蛇头方向就行,后面会向后传递。

这里还有个穿墙的问题要更改下,从一边出去会从另一边出来,这里改下节点的方向和位置就行了。

食物闪烁

食物的控制是通过counter对游戏刷新频率计数实现的,超过计数数量就移除食物,到达闪烁时间food内部的blingCounter进行计数,在我的设置里是0.5秒反转一下show,这样就出来了闪烁效果。

位置摆放问题

这里用的坐标都是中心坐标,所以和掩图的宽高有关,在生成位置的时候按中心位置去生成,在onDraw按掩图的宽高来摆放,让掩图中心放在位置上,最后出来的效果就比较好看了。

相关文章:

安卓小游戏:贪吃蛇

安卓小游戏&#xff1a;贪吃蛇 前言 这个是通过自定义View实现小游戏的第二篇&#xff0c;实际上第一篇做起来麻烦点&#xff0c;后面的基本就是照葫芦画瓢了&#xff0c;只要设计下游戏逻辑就行了&#xff0c;技术上不难&#xff0c;想法比较重要。 需求 贪吃蛇&#xff0…...

CUDA中的图内存节点

CUDA中的图内存节点 文章目录CUDA中的图内存节点1. 简介2. 支持的架构和版本3. API基础知识3.1. 图节点 APIs3.2. 流捕获3.3. 在分配图之外访问和释放图内存3.4. cudaGraphInstantiateFlagAutoFreeOnLaunch4. 优化内存复用4.1. 解决图中的重用问题4.2. 物理内存管理和共享5. 性…...

你真的看好低代码开发吗?

低代码开发前景如何&#xff0c;大家真的看好低代码开发吗&#xff1f;之前有过很多关于低代码的内容&#xff0c;这篇就来梳理下国内外低代码开发平台发展现状及前景。 01、国外低代码开发平台现状 2014年&#xff0c;研究机构Forrester Research发表的报告中提到“面向客户…...

一篇带你MySQL运维

1. 日志 1.1 错误日志 错误日志是 MySQL 中 重要的日志之一&#xff0c;它记录了当 mysqld启动和停止时&#xff0c;以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时&#xff0c;建议首先查看此日志。 该日志是默认开启的&…...

《嵌入式 – GD32开发实战指南》第22章 SPI

开发环境&#xff1a; MDK&#xff1a;Keil 5.30 开发板&#xff1a;GD32F207I-EVAL MCU&#xff1a;GD32F207IK 22.1 SPI简介 SPI&#xff0c;是Serial Peripheral interface的缩写&#xff0c;顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的…...

一个优质软件测试工程师的简历应该有的样子(答应我一定要收藏起来)

个人简历 基本信息 姓 名&#xff1a;xxx 性 别&#xff1a; 女 年 龄&#xff1a;24 现住 地址&#xff1a; 深圳 测试 经验&#xff1a;3年 学 历&#xff1a;本科 联系 电话&#xff1a;18xxxxxxxx 邮 箱&#xff1a;xxxxl163.com 求职意向 应聘岗位&#xff1a;软件…...

C++ 浅谈之 STL Deque

C 浅谈之 STL Deque HELLO&#xff0c;各位博友好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 这里是 C 浅谈系列&#xff0c;收录在专栏 C 语言中 &#x1f61c;&#x1f61c;&#x1f61c; 本系列阿呆将记录一些 C 语言重要的语法特性 &#x1f3c3;&a…...

Koa2-项目中的基本应用

文章目录安装配置koa2配置nodemon,热更新我们的项目中间件什么是中间件&#x1f47b;洋葱模型路由中间件连接数据库 - mysql后端允许跨域处理请求getpostputdelete后续会继续更新安装配置koa2 &#x1f47b;安装 koa2 npm i koa2 -s&#x1f47b;在package.json 配置,当然是在…...

Flask入门(2):配置

目录2.Flask配置2.1 直接写入主脚本2.2 系统环境变量2.3 单独的配置文件2.4 多个配置类2.5 Flask内置配置2.Flask配置 我们都知道&#xff0c;Flask应用程序肯定是需要各种各样的配置。来满足我们不同的需求的&#xff0c;这样可以使我们的应用程序更加灵活。比如可以根据需要…...

Linux--fork

一、fork入门知识 fork&#xff08;&#xff09;函数通过系统调用创建一个与原来进程几乎完全相同的进程&#xff0c;也就是两个进程可以做完全相同的事&#xff0c;但如果初始参数或者传入的变量不同&#xff0c;两个进程也可以做不同的事。可以简单地说fork()的作用就是创建一…...

计算机组成原理(一)

1.了解计算机硬件的发展和软件的发展历程&#xff1b; 硬件&#xff1a;   电子管时代&#xff08;1946-1959&#xff09;&#xff1a;电子管、声汞延迟线、磁鼓   晶体管时代&#xff08;1959-1964&#xff09;&#xff1a;晶体管、磁芯   中、小规模集成电路时代&#…...

【SpringBoot】实现Async异步任务

1. 环境准备 在 Spring Boot 入口类上配置 EnableAsync 注解开启异步处理。 创建任务抽象类 AbstractTask&#xff0c;并分别配置三个任务方法 doTaskOne()&#xff0c;doTaskTwo()&#xff0c;doTaskThree()。 public abstract class AbstractTask {private static Random r…...

Node =>Express学习

1.Express 能做什么 能快速构建web网站的服务器 或 Api接口的服务期 Web网站服务器&#xff0c;专门对外提供Web网页资源的服务器Api接口服务器&#xff1a;专门对外提供API接口的服务器 2.安装 在项目所处的目录中&#xff0c;运行以下命令&#xff0c;简装到项目中了 npm …...

QT基础入门【布局篇】消除控件之间的间隔

一、相关参数 layoutLeftMargin: layout内的布局距离边框左端的距离。 layoutTopMargin: layout内的布局距离边框顶端的距离。 layoutRightMargin: layout内的布局距离边框右端的距离。 layoutBottomMargin: layout内的布局距离边框底端的距离。 layoutHorizontalSpacing: layo…...

vue脚手架 element-ui spring boot 实现图片上传阿里云 并保存到数据库

一.阿里云 注册登陆就不讲了&#xff0c;登陆进去后如下操作 1. 进入对象存储OSS 创建一个新的Bucket 随后点击新建的bucket 2.去访问RAM 前往RAM控制台 3.去创建用户 4.创建密匙 5.随后返回RAM控制台 给用户增加权限&#xff0c;文件上传所需权限&#xff0c;需要带含有…...

【FPGA】Verilog:组合电路 | 3—8译码器 | 编码器 | 74LS148

前言&#xff1a;本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载 示例&#xff1a;编码/译码器的应用 ​ 功能特性&#xff1a; 采用 Xilinx Artix-7 XC7A35T芯片 配置方式&#xff1a;USB-JTAG/SPI Flash 高达100MHz 的内部时钟速度 存储器&…...

GLP-1类药物研发进展-销售数据-上市药品前景分析

据一项2021 年的报告发现&#xff0c;当 GLP-1 类似物用于治疗 2 型糖尿病时&#xff0c;全因死亡率降低了 12%&#xff0c;它们不仅降糖效果显著&#xff0c;同时还兼具减重、降压、改善血脂谱等作用。近几年&#xff0c;随着GLP-1R激动剂类药物市场规模不断增长&#xff0c;美…...

C++远程监控系统接收端- RevPlayMDIChildWnd.cpp

void CRevPlayWnd::InitMultiSock() { int RevBuf; int status; BOOL bFlag; CString ErrMsg; SOCKADDR_IN stLocalAddr; SOCKADDR_IN stDestAddr; SOCKET hNewSock; int RevLensizeof(RevBuf); //创建一个IP组播套接字 MultiSock W…...

QT之OpenGL深度测试

QT之OpenGL深度测试1. 深度测试概述1. 1 提前深度测试1.2 深度测试相关函数2. 深度测试精度2.1 深度冲突3. Demo4. 参考1. 深度测试概述 在OpenGL中深度测试(Depth Testing)是关闭的&#xff0c;此时在渲染图形时会产生一种现象后渲染的会把最先渲染的遮挡住。而在启用深度测试…...

用LCR测试仪测试无线充电系统中的线圈

宽阻抗范围用来表征电感和质量因数– 高精度 DCR 测量– 制造环节快速测量– 大量夹具可供选择智能终端上不断增加新功能&#xff0c;电池寿命成为用户最头痛的问题之一。相比便携式电源和电缆供电而言&#xff0c;无线充电技术因其方便性和多功能性获得了很大的关注&#xff0…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

Java多线程实现之Callable接口深度解析

Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

多模态大语言模型arxiv论文略读(108)

CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题&#xff1a;CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者&#xff1a;Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》

这段 Python 代码是一个完整的 知识库数据库操作模块&#xff0c;用于对本地知识库系统中的知识库进行增删改查&#xff08;CRUD&#xff09;操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 &#x1f4d8; 一、整体功能概述 该模块…...