曾經的一個朋友當時剛學一點C++嘗試編寫坦克世界大戰,我們來看看他的戰國
那會他剛看完少戰,中二附體max手打像素矩陣歡樂の小曲兒穿甲爆炸燃燒 瞬間完成,事戰車中的豪傑用txt文件儲存地圖,玩家可自己設計地圖,哈哈。當時還想弄地圖編輯器的,最後還是咕咕咕了
相當搞笑的一點:前面有段代碼是獲取地圖文件夾下所有文件名,存入字符數組。當時不知道字符串尾部要加結束符,導致後面使用該字符串讀入文件時屢屢崩潰(沒有結束符嘛)。
於是,宇宙大聰明答主寫了這麼一段處理代碼,大概思路是將這個字符串複製一遍,檢查複製後字符串的長度是否異常,如果異常則再複製一遍……(笑哭.jpg)
當時win7和現在win10的控制臺空格長度不相同,現在運行會字符錯位,地圖看上去亂糟糟。正常應該是沒有這些縫縫的。穿甲彈,擊中牆壁後產生2格殺傷區
穿甲彈殺傷大,但是有機率跳彈。有效擊穿時會在對方炮塔處顯示「#」。
高爆彈,產生3x3爆炸範圍
「謝爾曼M1型」自帶速射主炮,每分鐘350發(大霧)。可惜為了平衡,並沒有採用原版APHEI(高爆穿甲燃燒彈),而是殺傷力很低的糖豆炮。
炮射飛彈,射出去能拐個彎。cd長 傷害低,但打掩體後的敵人是為一絕。
炸藥桶地圖塊,被擊中產生5x5爆炸範圍。灰色地圖塊無法被破壞,淺黃色的可以。
坦克被擊毀時有機率殉爆,產生5x5爆炸範圍,而且持續時間比炸藥桶長得多。
程序的穩定性相當差勁,截這些圖的時候答主n次卡死或控制臺彈窗報錯了……
是的,當時連printf都不知道,列印字符用效率奇低的cout;而且一幀裡邏輯處理和渲染混雜執行,導致畫面容易閃爍甚至掉幀……
這是敵人AI的一部分,用n個套娃循環實現類似A*算法的尋路。AI尋找能射擊到敵人的最近位置,還會根據自身血量等計算攻擊欲望,躲避可能傷害到自己的炮彈,拾取有用的道具。整套算法效率特別特別低,每幀都要完整執行一次;容易導致掉幀。所以場上只能有一隻精英怪使用完整的AI,其他小怪用隨機函數xjb亂逛。
小怪:不是很聰明的亞子類似移動、碰撞檢測之類的部分,每一份代碼都要手動複製四次,對應上下左右四個方向。
再放開頭定義的一段吧:
#include <iostream>
#include <windows.h>
#include <fstream>
//#include <iomanip>
//#include <string.h>
//#include <stdio.h>
//#include <conio.h>
#include <sstream>
#include <cstdlib>
#include <ctime>
#include <io.h>//文件
#include <vector>//文件
#include<Mmsystem.h>//音頻相關
#pragma comment(lib,"winmm.lib")//音頻
#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0)
using namespace std;
int map[38][55] = {0};//儲存地圖
int mapX = 55;//地圖x大小
int mapY = 38;//地圖y大小
int allX = 30;//整體x位移
int allY = 0;//整體y位移
int circlet = 0;//循環次數
int model;//當前遊玩模式
int buff[3] = {0};//0[儲存道具:1動能彈,2高爆彈,9生命值 ] ,1x,2y
int tank[10][8] = {0};//儲存坦克:[編號],[0朝向,1x,2y,3生命值,4武器,5移速,6外形,7開炮裝填時間]
//武器:0普通炮,1速射炮,2高爆彈,3破甲彈,4跟蹤彈
int shell[10][10][4] = {0};//儲存炮彈:[所屬坦克],[編號],[0朝向,1x,2y,3類型]
int smoke[100][3] = {0};//儲存硝煙"#":[編號],[0已殲滅時間,1x,2y]
//**********基礎功能**********
void ProgramBegin();//初始化程序
void FuZhi();//初始賦值
void gotoxy(int,int);//跳轉光標
void color(int);//顏色
void getFiles(string, vector<string>&);//獲取目標文件夾內所有文件名
void InMap(char*);//將地圖導入數組
void Tout(int);//列印地圖方塊
void PrintMap();//列印地圖
void PrintTank(int,int,int,int);//列印坦克,朝向,x,y,類型
void DelTank(int,int);//屏幕刪除坦克
void sound(int);//音效
//**********菜單界面**********
int MenuModel();//模式菜單
char* MenuMap();//選圖菜單
void MenuTank1vN();//選車菜單1vN模式
void MenuTank1v1();//選車菜單1v1模式
void Interface();//玩家狀態界面
void NewUI();//刷新狀態界面
int MenuPveWin(int);//結束人機戰局
int MenuPvpWin(int);//結束雙人戰局
//**********戰鬥功能**********
void CtrlTank(int,int);//坦克控制
void InShell(int);//載入炮彈
void SkyShell();//炮彈移動
bool HitTank(int,int,int);//判斷坦克是否受到傷害
void HitShell(int,int,int,int,int,int);//炮彈命中.朝向,x,y,彈藥類型,被命中者 ,發射者
void Boom(int,int,int);//爆炸。半徑,x,y
void InBuff();//載入道具
void OffBuff(int);//使用(消除)道具
void InSmoke(int,int);//載入硝煙
void OffSmoke();//刪除硝煙
//**********AI功能**********
int FindMap(int,int,int,int);//尋路。起點x,y,終點x,y。返回方向,1234上下左右。
void AiTank();//坦克AI
void XieTank(int);//屑AI
void Born(int,int);//生成新車,編號、難度
//**********模式**********
void annihilate();//殲滅模式
void pve();//人機
void pvp();//雙人對戰
//**********附加**********
void op();//片頭
void loading();//偽加載界面
void xy();//顯示硝煙參數
void pd();//顯示炮彈參數
void MenuZhanYi();//戰役
是不是已經想吐了~3.5k行代碼塞在一個.cpp裡
有沒有留意到,上面那一堆全局變量的數據結構只有int數組。怎麼用它儲存數量和參數不等的坦克、炮彈、粒子(爆炸火光、煙霧……)等東西的呢?答主「發明」了這樣一種方法:
一個數組儲存一種類的物體,第一維記錄該類所有物體。如坦克tank數組第一維長度是10,那地圖上最多只能同時存在10輛坦克;tank[5] 表示編號為5的坦克。數組第二維儲存這個物體的個體屬性。如tank[5][0]代表5號坦克的朝向,[1]、[2]代表其坐標,等。
遊戲中每一幀,依次遍歷坦克、炮彈、特效等數組,提取數組中每一位物體,根據玩家輸入和遊戲邏輯更新該物體的參數(也就是更新這個數組)。
關鍵的來了:怎麼實現生成和銷毀物體,比如炮彈命中時銷毀自身?很簡單,要銷毀物體,將那個物體所在的數組第二維清零就行了。刷新物體遍歷數組時,遇到這種「空槽」就跳過。要增加物體,也遍歷一遍該數組,找到一個「空槽」,將新物體的初始數據寫入。
一段時間後,答主知道了有種東西叫結構體,有門課叫數據結構,有種數據結構叫鍊表,有種對象叫面向對象。可惜到現在也沒找到對象,唉。
最爛的代碼果然還是自己寫的代碼,這簡直是個珠穆奧力給峰。好在沒有人需要為這個「項目」負責,沒有人需要維護它,它只需要靜靜地躺在硬碟深處的角落就行了。
總結:
後來呢我還是一直熱衷於遊戲開發。坦克大戰後不久,寫的第二個「大工程」是基於qt的2d海戰遊戲,有點類似頂視角的wows。為啥當時突然想學qt?也許是前一個學期寫作業用mfc太痛苦了吧。總之,按我的壞習慣,看了兩三天文檔就興衝衝開工寫demo了。後期的代碼和工程現在已經找不到了,只找到一些前期的素材:
按照艦船的真實頂視圖 用ps畫svg矢量圖精靈代碼風格也是極其奧力給,回憶一下大概像這樣:
thisGuanQia->ui->myship->labelPao->setText("主炮0開火cd:"+QString::number(thisGuanQia->jianDui->ship->my->ship[0]->wuQiZu[0]->pao[0]->cd->nowCD));//在標籤上列印 我方艦隊 第一隻船 第一個武器組 第一門炮的cd
thisGuanQia->ui->myship->labelKey->setText("W"+QString::number(ctrl.keyW)+"S"+QString::number(ctrl.keyS)+"A"+QString::number(ctrl.keyA)+"D"+QString::number(ctrl.keyD)+"↑"+QString::number(ctrl.keyUp)+"↓"+QString::number(ctrl.keyDown)+"←"+QString::number(ctrl.keyLeft)+"→"+QString::number(ctrl.keyRight)+"GN"+QString::number(ctrl.keyShift)+QString::number(ctrl.keyCtrl)+QString::number(ctrl.keyAlt)); //列印鍵盤事件
那時不會調編輯器縮進,現在也忘了qt原生編輯器是長啥樣的。反正基本上整段代碼寫在一行裡…………看代碼要按緊shift+滾輪,對小拇指可是個考驗。
終於會用隊列了,比如開炮時的火光就是將特效序列幀存入一個隊列,然後每幀讀取一張。還寫了一套幾何碰撞檢測,雖然只支持點、線、橢圓、長方形這四種元素。邏輯和渲染終於分開了。還寫了一段幀率控制算法,按本幀邏輯部分的執行時間決定接下來sleep多久,使幀率儘量穩定在30。(然而加這玩意使幀率更不穩定,後來全刪了)
最後先帝創業未半而中道崩黜,加了很多功能後幀率實在太低。用的應該是2代i5m集顯,幀率只有12不到……