2017-6-22 13:35 上傳
點擊文件名下載附件
單片機游戲
內容預覽:
單片機游戲設計
1。概念
對于大部分單片機+LCD的游戲設計,基本采用前后臺方式,就是一個臺中斷,一個臺循環
(哪個前哪個后忘了),LCD部分基本是以固定點陣形式設計,什么叫固定點陣??首先先
明確,我們設計的游戲不是什
么魔獸爭霸或CS,而是黑白形式的固定點陣游戲,例如常見著名游戲貪吃蛇或俄羅斯方
塊。他們的每個點
都是預先就固定下來的,而且是比較大的點,這類專門的游戲機玻璃是經過廠家開模出來
的,有固定的COM,SEG線,然后接到專門的單片機上,例如常用的6502指令集合的單片
機,呵呵,我以前就用6502設計過一個。
對于業余設計的游戲,我們一般用如128*64的LCD來顯示,那么我們設計的時候首先應該把
這個128*64的LCD分塊,也就是分出固定點陣出來。LCD的基本點陣是128*64,就是•
¥##¥總之就是好多個點啦,但我們事實上不一定要運算這么多個點,除非你做的游戲很
有看頭。例如你只用左邊64*64的地方來做貪吃蛇,那么你打算你的貪吃蛇的活動空間是多
少呢?如果是8*8個點的話,算一下就是每個點64/8,64/8,也就是8*8個基本點陣,不過
想好玩一點,當然就是要有16*16個點的活動空間啦,那么每個固定點陣就要占4*4的基本
點陣了。要注意,這些4*4的東西在64*64LCD上共16*16個,每個都要用來獨立運算。
2。時鐘
這個其實是游戲的速度,對于一般的弱智類游戲機,他也代表了難度,物體在每個時鐘到
達的時候就傳動一次,例如俄羅斯方塊沒個時刻向下跑一層。賽車游戲每個時刻想前走一
步。一般這類時鐘的時間在0.X秒到1秒之間,物體有規律地勻速運動,讓人看到感覺是連
動。
3。運動
在這里,我先介紹兩種比較普遍的弱智游戲機的物體運動規則:柔體傳動,剛體傳動。
剛體傳動
代表作是俄羅斯方塊,所謂剛體,就是硬硬的一個東東,運動的時候也不怎么旋轉(注
意,俄羅斯方塊是會旋轉,但其實他是沒有經過算法的旋轉,純提取數組的方式,也就是
把一個放塊做成4個模式的點陣結構,其實就是4個方向,呵呵)對于剛體的傳動,在每個
時鐘到達的時候向一個方向(很可能是用戶輸入的)運動一個固定點陣。如果以坐標來表
達,就是物體的所有基本點陣同時向一個方向(X或Y)移動一個單位。
柔體傳動
代表作是貪吃蛇,貪吃蛇跑動的時候并不是整條蛇向一個方向動的(呵呵,蛇蛇身體僵硬
了),而是在每個時鐘的到來,物體由能量頭帶動(如蛇頭),每個點的方向都向下一個
點傳播,然后自己向新的方向走動一步,走動后,下一個點由于得到了上一個點的方向并
同樣地運動一步,所以,他會馬上填補上一個點的地方,如此類推。
說的好象沒說,看不懂沒關系,因為實際的算法可以簡化(傻瓜才會一個個點來走的),
實際上在設計貪吃蛇的時候,只需要把蛇尾巴的那個點陣去掉,然后在蛇頭的新方向放一
個點陣就是了。期間需要記錄下每個蛇身的固定點陣的位置,并且在每個運動時刻過后刷
新一次每個點的位置。
4。顯示接口
我們用的一般是點陣式LCD,就是一大片點點,128*64,132*64,240*128等等等等啦,這
些又叫條屏,就是一寫就寫一條——8個點(有的也提供寫一個點的功能,但貴,至少我沒
有),那么如果你只想寫一個點怎么辦?那就得先把這個點所在的條讀出來,然后通過
與,或,的運算后,再放回到LCD上,這時候就要涉及到一個讀LCD的問題了,有的LCD提供
讀的功能,你寫過什么在上面他記的很清楚(就好象老丁實驗板上的LCD),但有的便宜貨
就不行了,那么我們怎么辦?沒關系,你在內存中提取出一片空間,虛擬一個LCD出來,每
次寫在真實LCD上面的時候,也同時寫到內存的哪個虛擬LCD上,那么你要讀出LCD的值的時
候實際就是讀出虛擬LD上的數據,然后與或后,再重新寫到LCD上,記得也要寫到虛擬LCD
上哦。你可以把這片緩沖叫做顯存(COOL吧??)
5。流程
這是成功設計游戲的靈魂,你在設計游戲之前必須能正確構思到一個基本模型出來。這個
基本是菜鳥和蝦米的一個區別,有了構思,其他的其實都是時間問題了。
以貪吃蛇為例,我們需要有這樣的基本思路:(普通手機上的那種)
蛇運動處理,吃到食物的處理,放新食物的處理,死亡的處理。
以上是基本的思路,至于那些記錄分數,音樂效果,玩到一定分數會自動加速度等不是游
戲的必須,可以在后期處理!
分析下來:
運動:根據用戶輸入按鍵進行柔體傳動。
吃到食物:置沒有食物標志了,蛇長大一個點陣。
放新食物:判斷食物標志,如果沒有食物,就要放食物,判斷放的食物是否和蛇身重疊,
重疊了要重放。
死亡處理:判斷是否撞中自己或撞墻。
這就是基本要做的東西,實際上就是程序要做的東西,那么把上面的東西連成一個流程是
怎樣的呢?我以文字表達:
蛇向一個固定方向進行柔體傳動,沒個運動時鐘到達要做:1。判斷食物標志,沒有食物了
就放一個,放的時候判斷,不能和蛇身重疊 2。得到用戶按鍵值,蛇走一步,并判斷是否
撞死了,沒撞死,再判斷是否吃到東西了,沒有吃到,就等下一個運動時鐘,吃了?就增
長一點。置一個沒有食物的標志。然后等待下一個時刻的來臨。
呵呵,其實程序就是這么簡單,基本設計只有LCD部分和按鍵部分是和單片機有關的,其他
都是程序思維和算法。對于菜鳥來說,難度在于思維,而不是單片機。
說了屁話一堆,還得放上個能玩的,這里我介紹我的貪吃蛇程序,在丁丁的DX實驗板上跑
的,很久以前就寫的了,老丁也玩過,基本和手機上的那個區別不是很大。
程序注意:這是在DX51板子上跑的程序,有些函數部分采用了丁丁寫的底層:例如鍵盤
掃描,漢字顯示,LCD顯示等,為了保障老丁的利益,我沒有完全給出所有的底層部分,其
實他們和貪吃蛇本身沒有太大關系。貪婪者別以為拿來就用,我只希望大家用來交流學
習。其實改改就能玩的了。
注釋應該很詳盡,有不懂自己想啦。
還有,我有點反感有些人公布程序了,但卻把很多注釋去掉,這個不知道是什么心態
呢??希望大家能大方點,要給,就要給最好的!!
////////////////////////////////////////////////////////////
/*snake_flag是游戲標志,第一位是跑動標志,在定時器中斷上設置,下面程序沒有定時
器中斷函數,因為定時器函數在丁板上是給很多個程序共用的,函數根據標志判斷當前是
為那個進程服務*/
//貪吃蛇游戲程序,屏左半部用于游戲活動,右半部為分數顯示
//游戲屏為16*16游戲點陣,可容納蛇身塊數256。每個游戲點陣又由4*4個LCD基本點陣組
成
//蛇行標志在定時器上置位,這里為游戲的主體部分。
#define uchar unsigned char
#define uint unsigned int
#define ulong unsigned long
#define LCMD XBYTE[0xAfff] // 液晶數據口
#define LCMC XBYTE[0xAbff] // 液晶命令口
#define TIME_RUN 10 //定時器分品系
數
#include "study.h"
#include "reg51.h"
#include "absacc.h"
#include "intrins.h"
//游戲部分
//x,y最大極限
#define MAX_GAME_X 15
#define MAX_GAME_Y 15
#define lcd_no_read 1 //編譯選項,把這項屏蔽掉就
采用LCD讀出方式,否則采用顯存形式
uchar snake_flag, //蛇頭標志 7 6 5 4 3 2
1 0
// 上 下 左 右 x gameover food run
snake_len, //蛇身長度
snake_food; //食物位置,高4位Y,低4位x
uchar xdata snake_body[256]; //蛇身每個部分的數據
// 7 6 5 4 3 2 1 0
//
高4位Y方向 低4位X方向
#ifdef lcd_no_read
uchar xdata lcd_buf[8][64];//lcd緩沖,用于記錄LCD內部的點陣,可以理解為顯存
//當
LCD無讀出功能時,就要采用顯示緩沖。本LCD為可讀,一般不用這個功能
//緩
沖只記錄蛇身活動的部分,即LCD左半屏
#endif
/******************************************************
* 游戲LCD部分,根據游戲的特點把LCD分成16*16塊
* 用作游戲點陣,
*******************************************************/
//
//函數名:clr_game_dot
//功能:清一個游戲點
//輸入參數:游戲點的X,Y坐標
//注意事項:這里的X,Y坐標和LCD底層的X,Y坐標不同,他最大只能是
MAX_GAME_X,MAX_GAME_Y
//使用方式:內部調用
void clr_game_dot(uchar x,uchar y)
{
uchar lcd_x,i,tmp;
while(x>MAX_GAME_X)x-=(MAX_GAME_X+1); //這個是寫程序習慣的保護措
施,預防輸入范圍過大
while(y>MAX_GAME_Y)y-=(MAX_GAME_Y+1);
lcd_x=x<<2;
LCMC=lcd_x&0x0f; //設置x位置
LCMC=(lcd_x>>4)|0x10;
LCMC=0xb0+y/2; //設置Y位置
LCMC=0xe0;
if(y%2)//行的下半部
{
for(i=0;i<4;i++)
{
#ifdef lcd_no_read //以下是顯存法的清點程序,
其他例如亮點的部分和這個原理一樣
tmp=lcd_buf[y>>1][(x<<2)+i];
//先從緩沖讀出要修改的LCD片的數據
tmp&=0x0f;
//清對應的游戲點
LCMD=tmp;
_nop_();
lcd_buf[y>>1][(x<<2)+i]=tmp;
//把新數據寫回緩沖
#else
tmp=LCMD;tmp=LCMD;
//讀LCD的方法,要求連讀2次
LCMD=tmp&0x0f;
#endif
}
}
else //行的上半部,下同
{
for(i=0;i<4;i++)
{
#ifdef lcd_no_read
tmp=lcd_buf[y>>1][(x<<2)+i];
tmp&=0xf0;
LCMD=tmp;
_nop_();
lcd_buf[y>>1][(x<<2)+i]=tmp;
#else
tmp=LCMD;tmp=LCMD;
LCMD=tmp&0xf0;
#endif
}
}
LCMC=0xee;
}
//函數名:fill_game_dot
//功能:亮一個游戲點
//輸入參數:游戲坐標的X,Y坐標
//注意事項:這里的X,Y坐標和LCD底層的X,Y坐標不同,他最大只能是
MAX_GAME_X,MAX_GAME_Y
// 這個函數和上面的clr_game_dot基本相同,只是在寫LCD數據的時候是全1而
不是0
//使用方式:內部調用
void fill_game_dot(uchar x,uchar y)
{
uchar lcd_x,i,tmp;
while(x>MAX_GAME_X)x-=(MAX_GAME_X+1);
while(y>MAX_GAME_Y)y-=(MAX_GAME_Y+1);
lcd_x=x<<2;
LCMC=lcd_x&0x0f; //設置x位置
LCMC=(lcd_x>>4)|0x10;
LCMC=0xb0+y/2; //設置Y位置
LCMC=0xe0;
if(y%2)//行的下半部
{
for(i=0;i<4;i++)
{
#ifdef lcd_no_read
tmp=lcd_buf[y>>1][(x<<2)+i];
tmp|=0xf0;
LCMD=tmp;
_nop_();
lcd_buf[y>>1][(x<<2)+i]=tmp;
#else
tmp=LCMD;tmp=LCMD;
LCMD=tmp|0xf0;
#endif
}
}
else
{
for(i=0;i<4;i++)
{
#ifdef lcd_no_read
tmp=lcd_buf[y>>1][(x<<2)+i];
tmp|=0x0f;
LCMD=tmp;
_nop_();
lcd_buf[y>>1][(x<<2)+i]=tmp;
#else
tmp=LCMD;tmp=LCMD;
LCMD=tmp|0x0f;
#endif
}
}
LCMC=0xee;
}
//函數名:fill_game_dot2
//功能:亮一個游戲點(另一種方式,這里用來顯示食物用)
//輸入參數:X,Y
//注意事項:X,Y為游戲的點陣,非LCD點陣...還有LCD填充數據是0x05或0x50
//使用方式:內部調用,顯示蛇的食物的時候用這個函數,區分開蛇身和食物.
void fill_game_dot2(uchar x,uchar y)
{
uchar lcd_x,i,tmp;
while(x>MAX_GAME_X)x-=(MAX_GAME_X+1);
while(y>MAX_GAME_Y)y-=(MAX_GAME_Y+1);
lcd_x=x<<2;
LCMC=lcd_x&0x0f; //設置x位置
LCMC=(lcd_x>>4)|0x10;
LCMC=0xb0+y/2; //設置Y位置
LCMC=0xe0;
if(y%2)
{
for(i=0;i<4;i++)
{
#ifdef lcd_no_read
tmp=lcd_buf[y>>1][(x<<2)+i];
tmp|=0x50;
LCMD=tmp;
_nop_();
lcd_buf[y>>1][(x<<2)+i]=tmp;
#else
tmp=LCMD;tmp=LCMD;
LCMD=tmp|0x50;
#endif
}
}
else
{
for(i=0;i<4;i++)
{
#ifdef lcd_no_read
tmp=lcd_buf[y>>1][(x<<2)+i];
tmp|=0x05;
LCMD=tmp;
_nop_();
lcd_buf[y>>1][(x<<2)+i]=tmp;
#else
tmp=LCMD;tmp=LCMD;
LCMD=tmp|0x05;
#endif
}
}
LCMC=0xee;
}
/************************************************************
*
* 游戲算法部分(8*8LCD)
*
**************************************************************/
//函數名 game_init()
//功能:游戲開始的時候初始化畫面的,這里只是簡單地把132*64LCD用一條中間線劃分開來
//注意事項:暫時在中間畫條線用來劃分游戲空間
//使用方式:內部調用,
void game_init()
{
uchar i;
uchar xdata *da;
for(da=0;da<0x8000;da++)//清空xDATA,
*da=0x0;
cls(9); //丁丁的清屏函
數
initlcd();
for(i=0;i<8;i++)
{
LCMC=64&0x0f;
LCMC=(64>>4)|0x10; //線在x=64,
LCMC=0xb0+i; //y=(0-15)的地方
LCMC=0xe0; //把LCD劃分,左邊用來游戲
LCMD=0xff;
}
LCMC=0xee;
}
//函數名:snake_init
//功能:蛇初始化
//注意事項:初始化只有3節蛇身,向右跑
//使用情況:內部調用
void snake_init()
{
fill_game_dot(0,0); //顯示射身
fill_game_dot(1,0);
fill_game_dot(2,0);
snake_len=2;
snake_flag=0x10; //蛇的初始化,3個身.向右跑
snake_body[0]=0x02; //裝入射身數據
snake_body[1]=0x01;
snake_body[2]=0x00;
//一開始游戲時的文字部分
setcursor(8,0);
lcdstring("分數為:\r\n");
setcursor(8,2);
lcddigit(snake_len-2);
}
//函數名:show_mark
//功能:顯示當前分數,暫時以蛇身個數為分數
//參數說明:0,和非0, 0代表游戲中的顯示,!0代表掛了的顯示
//注意事項:調用到LCD.c顯示函數,并需要漢字庫的支持.
// 返回值在GAMEOVER時候有效,返回0退出游戲,1從新游
戲
//使用情況:snake_run()在蛇吃到食物的時候調用,在GAMEOVER后調用
uchar show_mark(uchar mode)
{
uchar ch;
setcursor(8,0);
lcdstring("分數為:\r\n");
setcursor(8,2);
lcddigit(snake_len-2);
if(mode)//gameover中顯示
{
setcursor(8,0);
lcddigit(snake_len-2);
lcdstring(" 分.");
setcursor(8,2);
lcdstring("C退出");
setcursor(8,4);
lcdstring("回車繼續");
do ch=getkey(1000);
while( (ch!='C') && (ch!='Y') );
//游戲結束了會在這里死等,直到用戶按鍵
if(ch=='Y')
return(1);
else
return(0);
}
return(0);
}
//函數名:snake_run
//功能:蛇運行函數
//輸入參數:一個全局變量flag_snake,蛇根據這個變量判斷運動方向
//注意事項:蛇跑動函數,用于判斷路徑,食物,長大,死亡
//使用情況:內部調用
void snake_run()
{
uchar tmp_head_x,tmp_head_y;
uchar i;
…………限于本文篇幅 余下代碼請從51黑下載附件…………