今后我们实验室的研究重点将会聚焦在“基于游戏的测评”和”教育游戏化“这两个主题上,因此很有必要研究实现“爆款”游戏的一些基本的技术方法。这篇文章将介绍如何借助Matlab GUI + 面向对象编程技术实现贪吃蛇游戏。

所有的游戏都可以解构成至少两个层次:模型层和表现层。为了实现游戏目标,我们最优先要解决的是映射问题。

Q1,如何从模型层映射到表现层?

这里有一个关键词叫做渲染(Render)。贪吃蛇游戏的场景是一张地图,它是由一个个方块组成的。它的模型层是一个所有参数都为1的数值矩阵,映射到表现层则是由一个个白色方块组成的图像矩阵。我们试试在中间抠出来一个小黑块。代码如下:

% filename is:: drawMap_SnakeGame01.m

这里专门设计了一个30x30的方块:

fkMatrix = ones(30,30);

我们借助一个叫做kron的函数,将模型矩阵modelMatrix中的点放大成方块。显示效果如图:

地图上游走着一条蛇(坐标x,y的向量Array),而且一般这条蛇都是带颜色的(不能只是黑白的),所以,上面这种渲染机制还不够完美。

假如贪吃蛇一开始是3个小方块组成的,而且蛇的头部和身体是不同颜色的(蛇头是红色的,身体是蓝色的)。我们试着写一个这样的向量出来:

snakeArray = [6,6; 6,5; 6,4];

我们先实现蛇头红色方块的渲染机制:

% filename is:: drawMap_SnakeGame01.m

我们分别对RGB三层的矩阵进行渲染,然后把三者用cat函数合并为一个矩阵,bigMatrix = bigMatrix_R + bigMatrix_G + bigMatrix_B。我们来看一下效果:

现在,我们争取一口气把蛇的头部和身体都打印出来看看:

% filename is:: drawMap_SnakeGame01.m

这么看感觉有点乱,我们截图看下Matlab编辑器窗口的样子:

最后出来的效果:

这就相当于解决了渲染问题,接下来我们可以专注在模型层,把蛇的数学模型对象设计好,然后通过渲染机制映射到表现层(坐标轴)。

第一个类是蛇Snake

classdef 

暂时只有一个初始化的方法(也可以叫做构造函数),给snakeArray和snakeDirection两个变量赋值。

第二个类是主窗口类WinViewer

我们把蛇的坐标向量获取过来之后,利用之前代码中的渲染机制,把模型层的数值矩阵映射到了表现层的坐标轴上。效果如下:

渲染的问题解决了之后,我们借助面向对象编程(极简版),把蛇的模型层的数据,渲染到了表现层。这个时候我们就遇到了第二个问题:

Q2,怎么才能让蛇动起来?

我们需要添加一些功能:

第一,在主窗口类中添加一个Timer定时器。它的功能是每间隔一段时间,都会驱动它的回调函数执行一次。

第二,在蛇类中添加一个move方法,方便主窗口每次执行更新函数时调用蛇对象的这个move方法。

第三,在move移动之后,可以再渲染一次蛇对象,更新到表现层。

针对第二个问题,怎么才能实现贪吃蛇的移动?

它其实是一个技术活,涉及到一个“栈”的概念。

snakeArray = [6,6; 6,5; 6,4]; % 原始状态

--- 1 --->

snakeArray = [6,6; 6,5]; % 去掉尾巴

--- 2 --->

snakeArray = [6,7; 6,6; 6,5]; % 增加头部

所以,这个问题就又变成了,Matlab中向量是如何去掉尾巴?

snakeArray(end,:) = [];

Matlab又是如何增加头部的?

 tmpPoint = [snakeArray(1,1) snakeArray(1,2)+1];snakeArray = [tmpPoint; snakeArray];

这只是所有四种情况的其中一种代码形式(因为存在四个方向的问题,暂时取向右方向)。

我们先来完善蛇Snake类的代码,然后再在主窗口WinViewer类中添加Timer控件对象。

classdef 

这样看还是不够清楚,我们再截图看下:

有了这个带move方法的蛇Snake类之后,我们就可以再进一步完善WinViewer类。

classdef WinViewer < handlepropertiesviewSizehFigurehAxesfkMatrixbigMatrixmodelMatrix_RmodelMatrix_GmodelMatrix_BsnakeObjtimePeriodtimerObjendmethods% 构造函数function obj = WinViewer(obj)% 设置窗口的宽和高figWidth = 600;figHeight = 600;% 设置窗口最初的位置x和yfigX = 100;figY = 100;% 确定窗口位置大小viewSizeobj.viewSize = [figX figY figWidth figHeight];% 创建窗口obj.hFigure = figure(1);set(obj.hFigure, 'position',obj.viewSize);% 创建坐标轴obj.hAxes = axes('parent',obj.hFigure);set(obj.hAxes, 'units','pixels', 'position',[1 1 figWidth figHeight]);% 初始化方块矩阵fkMatrix和地图矩阵bigMatrixobj.fkMatrix = ones(30,30);obj.bigMatrix = ones(600,600,3);obj.modelMatrix_R = ones(20,20);obj.modelMatrix_G = ones(20,20);obj.modelMatrix_B = ones(20,20);% load the snake objectobj.snakeObj = Snake();% timerObj  <--- 添加Timer控件!obj.timePeriod = 1;obj.timerObj = timer('ExecutionMode','fixedDelay', 'Period',obj.timePeriod);set(obj.timerObj, 'TimerFcn',@obj.timerCallbackFcn);% updateSnakeobj.renderSnake();% Binding Mechanismset(obj.hFigure, 'DeleteFcn',@obj.hFigure_DeleteFcn);% 开启游戏obj.start();end% function --> updateSnakefunction obj = renderSnake(obj)% load the variablessnakeArray = obj.snakeObj.snakeArray;fkMatrix = obj.fkMatrix;modelMatrix_R = obj.modelMatrix_R;modelMatrix_G = obj.modelMatrix_G;modelMatrix_B = obj.modelMatrix_B;% LOOP: index is ifor i = 1:length(snakeArray)if i == 1tmpX = snakeArray(i,1);tmpY = snakeArray(i,2);% RmodelMatrix_R(tmpX,tmpY) = 1;% GmodelMatrix_G(tmpX,tmpY) = 0;% BmodelMatrix_B(tmpX,tmpY) = 0;elsetmpX = snakeArray(i,1);tmpY = snakeArray(i,2);% RmodelMatrix_R(tmpX,tmpY) = 0;% GmodelMatrix_G(tmpX,tmpY) = 0;% BmodelMatrix_B(tmpX,tmpY) = 1;endend% RbigMatrix_R = kron(modelMatrix_R,fkMatrix);% GbigMatrix_G = kron(modelMatrix_G,fkMatrix);% BbigMatrix_B = kron(modelMatrix_B,fkMatrix);% combineobj.bigMatrix = cat(3, bigMatrix_R, bigMatrix_G, bigMatrix_B);% show the bigMatrix in BlackWhiteimshow(obj.bigMatrix,'parent',obj.hAxes);end% function --> startfunction obj = start(obj)%启动Timerstart(obj.timerObj);end% function --> timerCallbackFcnfunction timerCallbackFcn(obj, src, data)obj.snakeObj.move();obj.renderSnake();end% function -->function hFigure_DeleteFcn(obj, src, data)ts = timerfind;timerN = length(ts);if timerN >0stop(ts); delete(ts);return;endendendend

添加Timer控件对象,不需要事先写好Timer类,因为它是Matlab自带的。

 % timerObj  <--- 添加Timer控件!
obj.timePeriod = 1;
obj.timerObj = timer('ExecutionMode','fixedDelay', 'Period',obj.timePeriod);
set(obj.timerObj, 'TimerFcn',@obj.timerCallbackFcn);

我们一般都是要设置一下它的间隔时间timePeriod和它驱动的回调函数。

注意,Timer控件是独立于窗口存在的,因此你关闭窗口的时候,它仍然会待在内存里,不会自己消失掉。这样是不是很危险?!所以,但凡是Timer控件出现的地方,也一定要专门设计一个DeleteFcn,在窗口退出的时候调用它,以停止和删除Timer控件。

% Binding Mechanism

set(obj.hFigure, 'DeleteFcn',@obj.hFigure_DeleteFcn);

这个函数内容是:

% function --> hFigure_DeleteFcn

代码的意思是:查找内存中的timer,如果有的话,就都删掉。

这里要插播一个新需求,就是我很想呈现贪吃蛇的动图,在我之前的教程中从来没有探讨过如何制作一个gif动图,我们试着在现有的框架下,添加少量代码(用后即焚!),来实现这个功能,以使得大家能够直观地看到贪吃蛇跑起来的样子有多风骚

我们把整个过程分为两步来执行:

(1)在Timer控件启动的更新函数中,想办法进行截图并保存;

在更新函数中添加截图和保存图片的操作:

... ...

(2)重新写一个脚本文件,将10张jpg的图片合成一张gif的动图:

% filename is:: generateGIF.m

我们一起来看一下动图的效果:

从用户体验的视角来看,动态的比静态的好ღ( ´・ᴗ・` ) 完美回答第二个问题,如何让贪吃蛇动起来。

如果一直让贪吃蛇往一个方向前进,总有一天它会跑到矩阵外去了(发生错误!可能程序本身不会报错,但是,逻辑上是有问题的):

从这个图可以看出,Matlab的绘图功能是有自适应机制的,你看到左边那个游戏范围因为贪吃蛇超出边界太多会越变越小。所以,第三个问题是要避免这样的逻辑错误发生,就需要对贪吃蛇进行行为上的限定。

Q3,如何让贪吃蛇待在游戏设定的范围内?

这就需要进行专门的判断,如果贪吃蛇超出游戏限定的范围,游戏结束!

我们有两种选择,一种是把碰撞边界的判断写在Snake类中,另外一种是把碰撞边界的判断写在窗口类中。因为Snake类如果想探测边界碰撞,需要引入窗口的宽和高,暂时我想还是把碰撞检测放在窗口类下面:

% function --> judgeKnockEdge

在每次更新的时候,有一个调用碰撞检测的方法,一旦检测到碰撞的状态是1,也就是说蛇Snake超出游戏边界了,就不要再渲染蛇了,

% function --> timerCallbackFcn

直接调用gameover方法:

% function --> gameover

然后把结束指导语,也就是白色背景imgMat_White画到坐标轴hAxes上 + 写着”Game Over“的文本控件显示出来。

窗口类的完整代码分享如下(除了上述内容之外,还增加了一个文本控件):

classdef 

第四个问题是,贪吃蛇现在只往一个方向去,往右,它还没有接受来自键盘的控制。所以下一个问题我们要解决的是:

Q4,如何才能操控贪吃蛇的移动方向?

我们要给整个窗口绑定一个键盘操作的函数:

 % --- Binding Mechanism ---set(obj.hFigure, 'DeleteFcn',@obj.hFigure_DeleteFcn);set(obj.hFigure, 'WindowKeyPressFcn',@obj.hFigure_WindowKeyPressFcn);

键盘操作的具体函数的代码:

 % G. function --> function obj = hFigure_WindowKeyPressFcn(obj, src, data)KeyPressed=data.Key;if strcmp(KeyPressed,'escape')close(gcf);endend 

这里写的是最简单的键盘输入,就是在窗口游戏运行的时候,假如玩家按下'Escape' 退出键的话,游戏终止+窗口关闭。

我们只需要在这个框架下,将按键和贪吃蛇的方向绑定在一起即可。键盘操作函数的代码具体扩展如下:

% G. function --> hFigure_WindowKeyPressFcn

这个函数的目的是形成一个代表方向的参数dValue,想办法传给贪吃蛇对象的changeDirection方法,这个方法是蛇Snake类新增加的:

% changeDirection

这段代码看起来怎么这么复杂?

这是因为,贪吃蛇在向某个方向行进的时候,这个方向和反方向的按键是不起作用的;只有在这个方向垂直的两个方向按键才能真正改变方向。例如,贪吃蛇一开始是向右行进的(4),那么,你如果这个时候按下向右(4)或者向左方向键(3),它是不起作用的;你只有按下向上(1)或者向下方向键(2),它才能起到改变方向的作用。

最后,还要修改蛇Snake类中的移动move方法的代码,我把完整的代码粘贴过来,大家看一下move方法的代码修改后的逻辑:

classdef 

我们来看一下按键改变贪吃蛇的效果:

一旦引入了玩家的操控,这条贪吃蛇的运动轨迹就实时反映了玩家(现在是我)的意识做出的一系列输出动作。

Q5,如何才能随机贪吃蛇的食物?

和蛇Snake类很类似,我们也需要创建一个食物Food类。

classdef 

然后,我们需要在一开始的对象变量中添加一个foodObj

snakeObj

在窗口WinViewer类的初始化函数中,添加食物对象的初始化代码:

 % load the snake and food objectsobj.snakeObj = Snake();obj.foodObj = Food();

然后,我们将原来的渲染函数renderSnake修改为蛇和食物一起渲染的renderSnake_and_Food函数:

% A. function --> renderSnake_and_Food

最后,在Timer控件驱动的回调函数中,微调一下调用渲染函数的代码:

% D. function --> timerCallbackFcn

我们来看一下加入了食物的游戏开始的画面效果:

这个时候的贪吃蛇和食物之间是平行关系,即便贪吃蛇经过了食物的位置,它也是穿过的状态,食物不会被吃掉,贪吃蛇也不会长长。而且,在食物被吃掉之后,我们还需要随机一个新的位置来放置食物。

Q6,如何随机放置食物,并且让贪吃蛇能吃到食物,并且还能变长?

(1)随机放置食物的操作,我们打算在游戏窗口WinViewer类中添加一个随机的方法randomCoordinate,除了随机之外,还要限定随机食物的位置不能与贪吃蛇重合。代码分享如下:

% H. function --> randomCoordinate

我们在随机方法中嵌套了一个while循环,其目的是为了让随机满足条件后才成立。满足什么条件呢?就是你随机的食物,不能出现在贪吃蛇身上。我这里先预设了一个变量

mOverlap =0;

就是说,随机的食物和贪吃蛇一开始是不重叠的;然后将食物的坐标和贪吃蛇的头部和身体的所有坐标进行一次循环比对,如果有任何一次匹配成功,设置

mOverlap =1;

然后break,从while循环中退出

if mOverlap ==1
break;
end

(2)我们接下来要解决吃到食物的问题,这个问题又可以拆分为一个判断isEatFood是否贪吃蛇吃到食物,以及,如果吃到食物之后,应该进行的后续的操作。

这个判断函数应该写在哪个位置呢?这个判断函数应该发生在贪吃蛇移动之后,但是,又是在渲染之前。代码分享如下:

 % D. function --> timerCallbackFcnfunction timerCallbackFcn(obj, src, data)obj.snakeObj.move();obj.judgeKnockEdge();if obj.mState_KnockEdge == 1obj.gameover();return;endobj.judgeEatFood();if obj.mState_EatFood == 1obj.randomCoordinate();endobj.renderSnake_and_Food();% obj.getframeFigure();end

(3)贪吃蛇吃到食物之后,要随机新的坐标,

 obj.randomCoordinate();

(4)最后让蛇的长度增加1格,如何实现?

还记得那个删除掉的蛇的尾巴吗?你可以在每次删除的时候,把它记录一下,然后,在move之后,如果判断出贪吃蛇吃掉了食物的话,就把尾巴重新还给它。因此,第一步是在Snake类中增加一个变量,专门用来存储移动之后删掉的那个尾巴:

classdef 

在对蛇的尾巴删除之前,我们都先把snakeArray最后一个数据赋给obj.snakeTail:

obj

在窗口WinViewer类中,我们首先增加了一个变量

mState_KnockEdge

增加了Timer控件对应的回调函数中的内容方法:

% D. function --> timerCallbackFcn

每次Timer的回调函数要先进行贪吃蛇是否吃到了食物的判断,

 % F.2 function --> judgeEatFoodfunction obj = judgeEatFood(obj, src, data)snakeArray = obj.snakeObj.snakeArray;snakeHead = snakeArray(1,:);tmpX = snakeHead(1,1);tmpY = snakeHead(1,2);foodArray = obj.foodObj.foodArray;foodX = foodArray(1,1);foodY = foodArray(1,2);if tmpX == foodX & tmpY == foodYobj.mState_EatFood = 1;endend

如果贪吃蛇当前是刚好吃到食物,就想办法把mState_EatFood的状态设置为1:

obj.mState_EatFood = 1;

它会决定在Timer函数中运行随机食物位置的函数randomCoordinate,

% H. function --> randomCoordinate

并将蛇的长度增加一格的函数snakeLonger。

% I. function --> snakeLonger

我们来一起看下这个时候的效果:

下面还剩下一个工作,就是贪吃蛇不可以自己吃自己:

第一步,先在窗口WinViewer类的属性中添加mState_KnockSelf变量:

mState_KnockEdge

第二步,在窗口WinViewer类的构造函数中,初始化mState_KnockSelf变量的值:

 % mState_...obj.mState_KnockEdge = 0;obj.mState_KnockSelf = 0;obj.mState_EatFood = 0;

第三步,在Timer控件的回调函数中增加一个判断是否贪吃蛇吃到自己身体的逻辑:

% D. function --> timerCallbackFcn

第四步,创建judgeKnockSelf方法的代码:

% F.2 function --> judgeKnockSelf

整个贪吃蛇游戏的代码完成了

我们把蛇Snake类,食物类Food,窗口类WinViewer,还有生成gif动图的脚本文件代码全部分享出来:

蛇Snake类

classdef 

食物Food类:

classdef 

窗口WinViewer类:

classdef 

生成动图的脚本代码:

% filename is:: generateGIF.m

.

matlab cat函数_如何用Matlab编写贪吃蛇游戏?(持续更新)相关推荐

  1. matlab seed函数_如何用matlab生成随机数函数_matlab随机数生成函数

    如何用matlab生成随机数函数 1. MATLAB 函数 rand 产生在区间 (0, 1)的均匀随机数,它是平均分布在 (0,1)之间.一个称为seed的值则是用来控制产生随机数的次数.均匀随机数 ...

  2. Matlab——常用函数的用法总结(部分直接摘自mathwork,持续更新)

    Matlab--常用函数的用法总结(部分直接摘自mathwork,持续更新) 文章目录 Matlab--常用函数的用法总结(部分直接摘自mathwork,持续更新) 一.绘图篇 1.图像显示形式 ①f ...

  3. Linux下编写贪吃蛇游戏

    Linux下编写贪吃蛇游戏 文章目录 Linux下编写贪吃蛇游戏 前言 一.贪吃蛇代码 二.运行贪吃蛇代码 前言 本程序需要ncurses库,ubuntu下安装ncurses可以执行下面命令: sud ...

  4. 如何用html做一个贪吃蛇,如何用HTML5制作贪吃蛇游戏

    如何用HTML5制作贪吃蛇游戏 发布时间:2020-07-09 15:09:59 来源:亿速云 阅读:122 作者:Leah 如何用HTML5制作贪吃蛇游戏?很多新手对此不是很清楚,为了帮助大家解决这 ...

  5. 贪吃蛇python小白_面向 python 小白的贪吃蛇游戏

    代码和教程详见微信公众号:Python高效编程 效果图 图片 代码和教程详见微信公众号:Python高效编程 文字部分: 引言 作为python 小白,总是觉得自己要做好百分之二百的准备,才能开始写程 ...

  6. 单片机8×8点阵显示简单汉字的程序_干货 | 浅析单片机制作贪吃蛇游戏

    为了让大家更深入地了解底层的原理,在讲解时特意选择了51单片机(而非STM系列),另外16*16点阵由译码器和移位缓存器直接驱动(而非MAX系列芯片),摇杆也利用ADC功能判断方向. 那如何让单片机驱 ...

  7. python编写贪吃蛇游戏

    关于编写游戏,是博主非常向往的东西(博主喜爱游戏),编写游戏得一步一步的走!今天我简单的编写一下非常经典的游戏贪吃蛇!!!! 效果图: 首先引入pygame模块 pip install pygame ...

  8. 详解Linux终端下编写“贪吃蛇”游戏

    大一学习C语言的时候就想要用Turbo C编写一个视频小游戏出来,种种原因后面搁浅了,现在借着学习Linux系统编程的劲头,编写了一个终端下可以运行的贪吃蛇游戏,其中此视频游戏用到的一些知识和操作系统 ...

  9. python在人工智能应用锁_干货 | Python人工智能在贪吃蛇游戏中的应用探索(上)...

    文案&代码 白宇啸 排版&审校 邓发珩 前言 一个月前,人工智能对我来说都是很陌生的,更不用说神经网络.强化学习.DQN等名词了.疫情期间,经过在家努力学习,我对这些概念越来越清晰了, ...

最新文章

  1. 5G+AI,中国版无人驾驶可以有多猛?
  2. 给gridview动态生成radiobutton添加OnCheckedChanged监听函数
  3. linux shell 缺少 ps 命令
  4. Oracle 12C R2-新特性-转换函数的增强
  5. 微型计算机性能指标中可靠性是指连续,计算机接口原理定义
  6. Boost enable_if库的测试程序
  7. C++11- const, const expression和constexpr
  8. Linux命令行性能监控工具大全
  9. 高质量程序设计指南c++/c语言(30)--引用
  10. 救人无数七个心理寓言
  11. 目标检测综述——两阶段检测器
  12. 嵌入式学习流程(参考一)
  13. SQL2000和SQL2005同时安装问题(转载)
  14. Sublime Text 比较2个文件不同
  15. 填空什么的月牙_部编一年级上册语文第四单元知识梳理填空,附答案
  16. Burg法求解AR(p)模型参数及MATLAB实现
  17. 资源分享|免费注册申请永久的eu.org顶级域名创建属于自己的域名,再也不用给博客域名续费了!...
  18. 2019python程序员月薪多少_最新 | 2019年Python工程师的平均薪资是多少?
  19. 服务器无限指令箱子,我的世界箱子无限指令 | 手游网游页游攻略大全
  20. icare3.0医用his软件部署基本流程

热门文章

  1. java中月日年这种怎么转换成年月日_最“热乎”的Java社招面试经历分享(共40个面试题)...
  2. Linux 命令之 chmod -- 改变文件或目录权限
  3. Linux 关于查看 cpu 的命令
  4. php 表单 同步,Jquery点击按钮 异步和同步提交表单
  5. python3鄙视python2_Python3 正在毁灭 Python的原因分析
  6. linux 内核 性能,Linux内核十个版本性能对比
  7. webgl 基础渲染demo_WebGL + ThreeJS 实现实时水下焦散 Part 1
  8. C语言项目:灰度处理技术
  9. 删除一个程序Linux,一天一个Linux基础命令删除文件或目录命令rm
  10. 精简指令和复杂指令计算机,CPU精简指令集和复杂指令集的区别