matlab cat函数_如何用Matlab编写贪吃蛇游戏?(持续更新)
今后我們實驗室的研究重點將會聚焦在“基于游戲的測評”和”教育游戲化“這兩個主題上,因此很有必要研究實現“爆款”游戲的一些基本的技術方法。這篇文章將介紹如何借助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编写贪吃蛇游戏?(持续更新)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 极品芝麻官安卓和苹果互通吗(极品芝麻官安
- 下一篇: fprintf函数的用法matlab_极