本帖最后由 createskyblue 于 2018-9-17 20:20 編輯
前排提示,游戲文件、在線模擬在2樓
1樓為制作教程
視頻: https://www.bilibili.com/video/av31933044/
第一步 導入素材
第二步 地圖生成器
kittenblock中小學創客名師推薦的圖形化編程軟件
void BuildMap() {
/*
0 空氣
1 不可摧毀墻
2 普通墻
3 TNT
4 爆炸3
5 爆炸2
6 爆炸1
*/
//生成墻和怪物
LIFE = 3; //開局3條命
byte MN = 0; //重置怪物計數器
PP = 2; //設置玩家方向頭朝下
首先要生成墻壁,先生成邊框防止玩家跑出地圖,最后生成邊框里面墻壁陣列
kittenblock中小學創客名師推薦的圖形化編程軟件
for (byte y = 0; y < 15; y++) {
for (byte x = 0; x < 31; x++) {
if (y == 0 || y == 14) {
MAP[x][y] = 1; 生成y軸墻壁外圍 下圖橙色區域
} else if (x == 0 || x == 30) {
MAP[x][y] = 1; 生成x軸墻壁外圍 下圖藍色區域
} else {
if (x % 2 == 0 && y % 2 == 0) {
MAP[x][y] = 1; 判斷當前x,y坐標是否為偶數,如果是則生成內部墻壁
} else if (random(0, 4) == 0) {
MAP[x][y] = 2; 1/4概率生成可以破壞的墻
} else if (random(0, 28) == 0) { 1/28 的概率生成怪物
//生成怪物
if (MN < LEVEL) { 確保怪物數量少于當前關卡數
monster[MN][0] = x; 記錄當前編號怪物的x,y坐標
monster[MN][1] = y;
MLRUD[MN] = 2; //設置怪物方向為頭朝下 0上 1右 2下 3左
MN++; 下一個怪物編號
}
} else MAP[x][y] = 0; 假若不滿足上邊的條件則生成空氣
}
}
}
kittenblock中小學創客名師推薦的圖形化編程軟件
//設置玩家出生點
for (byte py = 0; py < 3; py++) {
for (byte px = 0; px < 3; px++) {
//清空出生點附近的普通墻和怪物,確保出生點安全
MAP[15 - 1 + px][7 - 1 + py] = 0;
}
}
清除玩家出生點附近3*3區域內所有方塊,確保有較大的空間讓玩家開一條路出去
PX = 15; 設置玩家在地圖上的出生點
PY = 7;
}
上圖為生成器生成的初始游戲地圖 第三步 實現玩家移動 繪圖部分
kittenblock中小學創客名師推薦的圖形化編程軟件
void loop() {
if (LIFE == 0) FAIL(); //如果生命為0 游戲結束
key(); //掃描按鍵
Draw(); //繪圖
logic(); //邏輯處理
}
/*=========================================================
按鍵掃描
=========================================================*/
void key() {
/*
0 1 2 3 4 5
↑ ↓← → A B
*/
KeyBack = 255;
if (arduboy.pressed(UP_BUTTON)) KeyBack = 0;
if (arduboy.pressed(DOWN_BUTTON)) KeyBack = 1;
if (arduboy.pressed(LEFT_BUTTON)) KeyBack = 2;
if (arduboy.pressed(RIGHT_BUTTON)) KeyBack = 3;
if (arduboy.pressed(A_BUTTON)) KeyBack = 4;
if (arduboy.pressed(B_BUTTON)) KeyBack = 5;
}
/*===================================================================
繪圖
===================================================================*/
void Draw() {
DrawMap(); //渲染地圖
DrawEntity(); //渲染實體
arduboy.display(); //發送畫面到屏幕
arduboy.fillRect(0, 56, 128, 8, 0);
for (byte ni = 0; ni < LIFE; ni++) {
arduboy.drawSlowXYBitmap(ni * 9, 56, LOVE, 8, 8, 1); //生命條
}
arduboy.display(); //發送畫面到屏幕
}
/*=================================================================
渲染地圖
==================================================================*/
void DrawMap() {
arduboy.fillRect(0, 0, 128, 64, 1);
for (char y = PY - 4; y < PY + 5; y++) {
for (char x = PX - 8; x < PX + 10; x++) {
if (x >= 0 && y >= 0 && x <= 30 && y <= 14) {
用兩個嵌套的for語句來掃描玩家視線內的方塊,然后用switch語句顯示出對應方塊的位圖 下圖灰色區域為玩家所能看到的區域 黑框內為128x64OLED的顯示范圍 游戲中,無論怎樣移動移動的都是地圖而不是玩家本身,玩家是被固定在屏幕中心位置的,而且地圖方塊對應的位置不代表實際顯示的位置,為了正確顯示我們需要減去地圖方塊與玩家相對坐標 (參考下圖紫色箭頭) 為了直觀告訴大家哪里做了處理,下列顯示代碼會把要坐標處理的部分染為紫色
kittenblock中小學創客名師推薦的圖形化編程軟件
switch (MAP[x][y]) {
case 1:
arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, WALL_1, 8, 8, 0);
break;
case 2:
arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, WALL_2, 8, 8, 0);
break;
case 3:
arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, TNT_table[TNTS], 8, 8, 0);
break;
case 4:
arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, BOOM_1, 8, 8, 0);
break;
case 5:
arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, BOOM_2, 8, 8, 0);
break;
case 6:
arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, BOOM_3, 8, 8, 0);
break;
}
}
}
}
TNTS++;
if (TNTS >= 2) TNTS = 0;
}
/*====================================================================
渲染實體
====================================================================*/
void DrawEntity() {
if (LIFE > 0) {
//渲染怪物
for (byte n = 0; n < 10; n++) {
if (MLRUD[n] != 255) arduboy.drawSlowXYBitmap(monster[n][0] * 8 - (PX - 15) * 8 - 64 + CSX, monster[n][1] * 8 - (PY - 7) * 8 - 32 + CSY, M_table[byte(MLRUD[n])], 8, 8, 0);
}
Man_table 存儲的是炸彈人動作列表:{ 炸彈人_朝上_1, 炸彈人_朝上_2, 炸彈人_朝上_3, 炸彈人_朝右_1, 炸彈人_朝右_2, 炸彈人_朝右_3, 炸彈人_朝下_1, 炸彈人_朝下_2, 炸彈人_朝下_3, 炸彈人_朝左_1, 炸彈人_朝左_2, 炸彈人_朝左_3} 炸彈人有4個方向的貼圖,每個方向擁有3個動作,加起來一共12幅圖 變量PP指的是炸彈人的方向,決定選擇哪一組貼圖 變量PS為炸彈人當前動作幀,當每一幀動作顯示后自加或者復位 實現炸彈人移動動畫 因此得出Man_table[PP * 3 + PS]這條式子 當炸彈人受到傷害后將會被扣血,扣血后會進入一段時間的無敵狀態避免短時間重復受到傷害 而無敵模式中只會顯示炸彈人某個方向的第一幀 忽略掉2、3幀,因此在無敵模式中炸彈人會有閃爍的效果
kittenblock中小學創客名師推薦的圖形化編程軟件
//渲染玩家
if (millis() >= PIT + Invincible_Time) {
arduboy.drawSlowXYBitmap(56, 24, Man_table[PP * 3 + PS] , 8, 8, 0); //玩家圖像為方向*3+動畫幀
} else if (PS == 0) arduboy.drawSlowXYBitmap(56, 24, Man_table[PP * 3 + PS] , 8, 8, 0); //無敵模式的時候閃爍效果
if (PMove == true || millis() < PIT + Invincible_Time) { //只有在玩家移動的時候或者無敵模式 才會有移動動畫
PS++;
if (PS > 2) PS = 0;
} else PS = 0;
}
}
kittenblock中小學創客名師推薦的圖形化編程軟件
/*=========================================================
邏輯
=========================================================*/
void logic() {
/*
控制移動 以及移動相關動畫
*/
switch (KeyBack) {
case 0:
PP = 0;
SBDP(PP, PX, PY);
if (PY > 1 && BMove == true) {
PMove = true;
for (CSY = 1; CSY <= 7; CSY += 3) Draw();
PY--;
}
break;
case 1:
PP = 2;
SBDP(PP, PX, PY);
if (PY < 13 && BMove == true) {
PMove = true;
for (CSY = -1; CSY >= -7; CSY -= 3) Draw();
PY++;
}
break;
case 2:
PP = 3;
SBDP(PP, PX, PY);
if (PX > 1 && BMove == true) {
PMove = true;
for (CSX = 1; CSX <= 7; CSX += 3) Draw();
PX--;
}
break;
case 3:
PP = 1;
SBDP(PP, PX, PY);
if (PX < 29 && BMove == true) {
PMove = true;
for (CSX = -1; CSX >= -7; CSX -= 3) Draw();
PX++;
}
break;
}
if (PMove == true) {
CSX = 0;
CSY = 0;
PMove = false;
}
}
接下來研究炸彈人平滑移動,而不是直接瞬移到下一方塊 方法是按下方向鍵后先讓背景平滑移動,當移動整整一格方塊后背景復位并且玩家坐標移動到對應位置。給人一種平滑走動的感覺! 上面移動代碼中反復出現了以下類似的結構,我們取向右移動的代碼來研究:
第1步 PP = 1; 設置炸彈人朝向右邊 第2步 SBDP(PP, PX, PY); 調用SBDP障礙物判斷函數,判斷在炸彈人位置對應方向前是否有障礙物,如果沒有障礙物判斷函數會把變量BMove設置為true 第3步 if (PX < 29 && BMove == true) { 判斷障礙物判斷函數返回值 前方是否沒障礙物,并且前面是否小于x軸地圖最大范圍 第4步 PMove = true; 允許移動 第5步 for (CSX = -1; CSX >= -7; CSX -= 3) Draw(); 讓背景往左邊方向平滑移動 每次移動3個像素 如果想要更加平滑可以把-3改為-1 并且調用繪圖函數背景顯示部分,這里拿墻壁顯示片段舉例 arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, WALL_1, 8, 8, 0); 第6步 PX++; 讓炸彈人坐標真正往前一步,不過不用馬上調用繪圖函數刷新 } 第6步 背景復位 if (PMove == true) { CSX = 0; CSY = 0; PMove = false; } 這一步也沒有立即調用繪圖函數來刷新,盡管背景位置復位了,不過玩家位置已經向前,所以在下一次刷新的時候畫面不會變化
/*=================================================================== 障礙物判斷===================================================================*/ void SBDP(byte SBP, byte sx, byte sy) { 第1步 BMove = true; char SX, SY; 初始化變量 第2步 判斷返回值 調用障礙物判斷函數的時候還要傳遞3個數據 分別是 方向 和玩家位置x和y 用一個SWITCH語句判斷要檢測的坐標
kittenblock中小學創客名師推薦的圖形化編程軟件
switch (SBP) {
case 3:
SX = -1;
SY = 0;
break;
case 1:
SX = +1;
SY = 0;
break;
case 0:
SX = 0;
SY = -1;
break;
case 2:
SX = 0;
SY = +1;
break;
}
第3步 獲取列表長度 獲取障礙物列表最大的長度 默認障礙物列表有以下ID(1,2,3) 分別為堅固墻壁 可以摧毀的墻壁以及TNT 第4步 檢測是否為障礙物 獲用for逐個檢測目標位置ID是否為障礙物,如果是則設定變量BMOVE為false
第四步 TNT放置 爆炸效果 爆炸傷害
在上邊第二步中檢測按鍵返回值控制移動的switch語句中加多以下內容 case 4: //放TNT 第1步 檢查要放TNT的位置是否已經有TNT 并且這一刻全地圖內是否少于10個TNT if (TNTN < 10 && MAP[PX][PY] != 3) { //注意0為沒有TNT 范圍1-10 第2步 可以放置TNT 讓全地圖TNT數量+1 TNTN++;
第3步 在TNT列表中設置好當前編號的TNT位置 TntList[TNTN - 1][0] = PX; TntList[TNTN - 1][1] = PY;
第4步 在地圖對應位置寫入TNT的ID MAP[PX][PY] = 3;
第5步 記錄下放置TNT的時間 用于計算什么時候起爆 TntTime[TNTN - 1] = millis(); } break; 現在TNT已經放置了,可是還需要讓它到時間后起爆,在邏輯語句中加入以下部分 /* 計算TNT爆炸 */
第1步 判斷地圖內是否存在TNT,有的話繼續 if (TNTN != 0) { //存在炸彈 第2步 檢查TNT列表第一個也就是最接近起爆時間的TNT是否到時間 if (millis() >= TntTime[0] + BOOMTime) {
第3步 如果到了起爆時間那么在地圖TNT所在的位置上把ID-3 準備爆炸的TNT 替換為ID-4爆炸第一階段
MAP[TntList[0][0]][TntList[0][1]] = 4; //引爆 //摧毀附近的非堅固實體或者方塊
第4步 檢查起爆的TNT十字范圍內是否有可以摧毀的東西,有的話把ID替換為ID-4 爆炸的第一階段
先用for檢查X軸是否有可以摧毀的方塊,這里可以修改代碼擴大摧毀范圍 if (MAP[TntList[0][0] - 1 + BOOMx][TntList[0][1]] != 1 && MAP[TntList[0][0] - 1 + BOOMx][TntList[0][1]] != 3) {
MAP[TntList[0][0] - 1 + BOOMx][TntList[0][1]] = 4;
}
} 最后for檢查y軸是否有可以摧毀的方塊
kittenblock中小學創客名師推薦的圖形化編程軟件
for (byte BOOMy = 0; BOOMy < 3; BOOMy++) {
if (MAP[TntList[0][0]][TntList[0][1] - 1 + BOOMy] != 1 && MAP[TntList[0][0] - 1 + BOOMy][TntList[0][1]] != 3) {
MAP[TntList[0][0]][TntList[0][1] - 1 + BOOMy] = 4;
}
}
第5步 在TNT列表注銷以及爆炸的TNT kittenblock中小學創客名師推薦的圖形化編程軟件
//讓TNT列表向前移位
TNTN--; //減少一枚TNT
for (byte TNTi = 0; TNTi < TNTN; TNTi++) {
讓列表后邊的TNT坐標和TNT放置時間移動到前面一位
TntList[TNT編號][0] 對應編號TNT的X軸
TntList[TNT編號][1] 對應編號TNT的Y軸
TntTime[編號] 對應編號TNT的放置時間
TntList[TNTi][0] = TntList[TNTi + 1][0];
TntList[TNTi][1] = TntList[TNTi + 1][1];
TntTime[TNTi] = TntTime[TNTi + 1];
}
}
}
現在TNT可以爆炸,接下來是爆炸動畫
kittenblock中小學創客名師推薦的圖形化編程軟件
第1步 兩個嵌套的for遍歷地圖
for (byte y = 0; y < 15; y++) {
for (byte x = 0; x < 31; x++) {
第2步 如果是4替換為5 5替換為6 6替換為0(空氣)
if (MAP[x][y] == 4) MAP[x][y] = 5; else if (MAP[x][y] == 5) MAP[x][y] = 6; else if (MAP[x][y] == 6) MAP[x][y] = 0; //讓爆炸切換下一幀
}
}
有動畫還不夠,TNT存在的意義是破壞和傷害
第1步 檢查怪物列表
用for遍歷怪物列表,每個地圖怪物上限為10 很輕松就可以遍歷一次
for (byte i = 0; i < 10; i++) {
if (MAP[monster[0]][monster[1]] >= 4 && MLRUD != 255) {
假若當前列表編號的怪物不是死亡狀態并且腳下為ID-4 ~ ID-6 不同階段的爆炸則繼續執行
第2步 讓怪物死亡
MLRUD = 255;
}
第3步 檢查玩家是否在無敵狀態
if (millis() >= PIT + Invincible_Time) { //玩家不在無敵狀態
第4步 若不是在無敵狀態下檢查腳下是否為為ID-4 ~ ID-6 不同階段的爆炸
if (PX == monster[0] && PY == monster[1] || MAP[PX][PY] >= 4) { //怪物傷害 或者 TNT傷害
第5步 扣生命 以及設置無敵狀態開始的時間
LIFE--;
PIT = millis();
}
}
第五步 怪物AI要怪物有可用,為了被玩家攻擊和攻擊玩家給玩家帶來難度,所以我們要會動的怪物而不是呆住不動的木頭腦袋 在邏輯語句插入以下內容
kittenblock中小學創客名師推薦的圖形化編程軟件
/*
怪物AI
*/
第1步 假設游戲通關 怪物被消滅
bool PWIN = true;
第2步 是否到了刷新時間 若是繼續執行
if (millis() >= MMTime + MMTimeOut) {
重置刷新時間
MMTime = millis();
第3步 遍歷怪物列表
for (byte n = 0; n < 10; n++) {
假若對應列表編號的怪物不是死亡狀態
if (MLRUD[n] != 255) {
很遺憾,游戲還沒有結束 設置游戲勝利狀態為假
PWIN = false;
第4步 通過SBDP障礙物判斷函數判斷怪物坐標的對應方向是否有障礙物
SBDP(MLRUD[n], monster[n][0], monster[n][1]);
第5步 沒有障礙物 進行移動
在對應方向的坐標走一步
if (BMove == true) {
//移動合法
switch (MLRUD[n]) {
case 0:
monster[n][1]--;
break;
case 1:
monster[n][0]++;
break;
case 2:
monster[n][1]++;
break;
case 3:
monster[n][0]--;
break;
}
假若有障礙物,那么怪物方向隨機更改(通過無數次嘗試發現越簡單的越好用,窮舉法在這種情況下很好用)
} else MLRUD[n] = random(0, 4);
} else if (PWIN == true) WIN(); 假若通過為真,調用通關函數
}
}
第六步 游戲菜單 以及 游戲結束
kittenblock中小學創客名師推薦的圖形化編程軟件
/*=========================================================
通關
=========================================================*/
void WIN() {
第1步 假若關卡為10 則通關畫面
if (LEVEL == 10) {
第2步 清屏并顯示文字
arduboy.clear();
arduboy.setCursor(16, 0);
arduboy.println(F("CONGRATULATIONS"));
arduboy.println(F(" BOMBER MAN BECOMES"));
arduboy.println(F(" RUNNER"));
arduboy.println(F("SEE YOU AGAIN IN LODE"));
arduboy.println(F(" RUNNER"));
第3步 顯示一個面向右面的炸彈人
arduboy.drawSlowXYBitmap(56, 48, Man_table[3] , 8, 8, 1);
第4步 在最下方磚塊顯示的區域畫一個實心長方體,用于畫布底層
arduboy.fillRect(0, 56, 128, 8, 1);
通過for顯示16個轉頭
for (byte x = 0; x < 128; x += 8) {
arduboy.drawSlowXYBitmap(x, 56, WALL_2, 8, 8, 0);
}
第5步 在屏幕上顯示
arduboy.display();
while (1) {}
} else {
假若關卡小于10
LEVEL++; //關卡+1
BuildMap(); //構建地圖
ShowLevel(); //顯示第幾關
}
}
kittenblock中小學創客名師推薦的圖形化編程軟件
/*=========================================================
顯示關卡
=========================================================*/
void ShowLevel() {
arduboy.clear();
arduboy.setCursor(52, 16);
arduboy.println(F("LEVEL"));
arduboy.setCursor(64, 32);
arduboy.println(LEVEL);
arduboy.display();
delay(1000);
}
/*=========================================================
玩家死亡
=========================================================*/
void FAIL() {
for (byte y = 0; y < 15; y++) {
for (byte x = 0; x < 31; x++) {
MAP[x][y] = 4;
把整個地圖設置為爆炸
}
}
while (MAP[0][0] >= 3) {
Draw();
logic();
delay(500);
切換爆炸下一幀,直到爆炸結束
}
kittenblock中小學創客名師推薦的圖形化編程軟件
arduboy.display();
delay(5000);
resetFunc(); //重啟游戲
}
/*=========================================================
主菜單
=========================================================*/
void MENU() {
bool POA = false;
while (POA == true || KeyBack != 4) {
key();
switch (KeyBack) {
case 0:
POA = false;
break;
case 1:
POA = true;
break;
case 4:
if (POA == true) {
KeyBack = 255;
arduboy.clear();
arduboy.setCursor(0, 0);
arduboy.println(F(" >About"));
arduboy.println(F(""));
arduboy.println(F("LHW programming"));
arduboy.println(F("LHW Art"));
arduboy.println(F("E-mail"));
arduboy.println(F("[email protected]"));
arduboy.println(F(""));
arduboy.println(F("Any key back..."));
arduboy.display();
delay(200);
while (KeyBack == 255) key();
delay(200);
}
break;
}
arduboy.clear();
arduboy.drawSlowXYBitmap(39, 1, START_TITLE , 87, 39, 1); //大標題
arduboy.drawSlowXYBitmap(0, 23, TITLE_TNT , 37, 41, 1); //TNT圖標
arduboy.drawSlowXYBitmap(65, 58, LHW , 39, 5, 1); //作者信息
arduboy.setCursor(70, 39);
arduboy.println(F("PLAY"));
arduboy.setCursor(70, 47);
arduboy.println(F("ABOUT"));
if (POA == false) arduboy.setCursor(62, 39); else arduboy.setCursor(62, 47);
arduboy.println(F("*"));
arduboy.display();
}
}
|