是不是很炫,不過我們今天要用C++繪制的不是上面這幅,而是下面這幅。注意,是動態的哦。
在今天之前,你能想象用C++幾十行代碼就能做出上面這個程序嗎?
代碼實現
在EasyX的文檔中,就有這么一個Demo程序。我們先來分析一下這段代碼。
#include <graphics.h>
#include <time.h>
#include <conio.h>
#define MAXSTAR 200 // 星星總數
struct STAR
{
double x;
int y;
double step;
int color;
};
STAR star[MAXSTAR];
// 初始化星星
void InitStar(int i)
{
star[i].x = 0;
star[i].y = rand() % 480;
star[i].step = (rand() % 5000) / 1000.0 + 1;
star[i].color = (int)(star[i].step * 255 / 6.0 + 0.5); // 速度越快,顏色越亮
star[i].color = RGB(star[i].color, star[i].color, star[i].color);
}
// 移動星星
void MoveStar(int i)
{
// 擦掉原來的星星
putpixel((int)star[i].x, star[i].y, 0);
// 計算新位置
star[i].x += star[i].step;
if (star[i].x > 640) InitStar(i);
// 畫新星星
putpixel((int)star[i].x, star[i].y, star[i].color);
}
// 主函數
void main()
{
srand((unsigned)time(NULL)); // 隨機種子
initgraph(640, 480); // 創建繪圖窗口
// 初始化所有星星
for (int i = 0; i < MAXSTAR; i++)
{
InitStar(i);
star[i].x = rand() % 640;
}
// 繪制星空,按任意鍵退出
while (!kbhit())
{
for (int i = 0; i < MAXSTAR; i++)
MoveStar(i);
Sleep(20);
}
closegraph(); // 關閉繪圖窗口
}
看過之前C語言專題的同學們一定能夠獨立看明白這段代碼。代碼結構大體如下:
1. 星星結構體
通過結構體保存每克星星的位置信息、顏色信息和移動信息。
- 位置信息
EasyX坐標系中的點坐標位置。
- 顏色信息
所有的星星都是白色,不同的是明亮程度不同。近處的星星比較亮,遠處的星星比較暗。
- 移動速度
每一次循環,所有的星星都會向右移動,通過這個參數來記錄每個星星每次向右移動的距離。近處的星星移動得快,遠處的星星移動得慢。
2. 星星初始化
用一個數組來保存所有的星星。每個星星都用InitStar()函數隨機出一組特征值。利用這些特征值將每顆星星畫在畫布上。
這里使用了EasyX的畫點接口:
void putpixel(int x, int y, COLORREF color);
3. 星星移動
每20毫秒循環一次,每一次循環中,每顆星星都向右移動。移動調用MoveStar()函數。
星星的移動很好實現,將之前畫在畫布上的點用一顆黑色的點蓋掉,之后計算這顆星的新位置,最后再用這顆星的顏色把點畫在新的位置上。
這里要注意,當星星移動出畫布的范圍時,需要給它重新初始化一組新的特征值。相當于這顆星星消失了,同時產生了一顆新星。
這里需要提一下,kbhit函數負責監聽鍵盤輸入信息。當按下鍵盤任意鍵時,返回值不為0。此時程序結束。
int kbhit(void);
這個函數我們后面還會遇到,這里不多說了。
注意:
文章開頭的動圖由于是圖片拼接生成的gif圖,與真正的程序界面相比效果差了很多。真正運行程序,你會看到比較震撼的3D效果。
沒錯,我說的是3D效果
面向對象的思想
對應上面的結構,其實這個程序并不太難。在實現過程中,它加入了C++的編程思想,每個星星成為獨立管理的數據結構。這其實就是面向對象的初級階段。
如果是傳統的結構化編程,應該是分別用四個數組保存所有星星的橫坐標、縱坐標、顏色、步長。就像下面一樣。
double x[MAXSTAR];
int y[MAXSTAR];
double step[MAXSTAR];
int color[MAXSTAR];
雖然用這種方法也能實現這個功能,但仔細想想,這么設計數據結構的后果是我們設計程序時將會把每一次重繪看做一個獨立的動作來實現。
有興趣的同學可以自己寫一下,只后你會發現,面向對象的思想會使你的思路更加清晰。
C++的面向對象
下面真正進入今天的主題。上面的程序雖然使用了面向對象的思想,但代碼形式上依然還是結構化的。我們要用C++的類重新實現這段代碼。
星星類
首先,我們創建一個Star類,用來封裝每顆星星的特征數據和動作。代碼如下:
class Star
{
public:
Star(){}
~Star(){}
void Init();
void Move();
private:
double m_x = 0;
int m_y;
double m_step;
int m_color;
};
私有成員變量中,四個變量就是之前結構體中的四個成員變量。另外,星星只有兩種動作,一個是創建自己,另一個是移動。這里設計了兩個公有方法Init()和Move()。
C++中,總有人爭論public和private究竟如何排列。我個人傾向于把public內容寫在前面,因為外部使用者在使用這個類的時候,只關心public的內容。
類功能實現
兩個公有函數的實現如下:
void Star::Init()
{
if (m_x == 0)
{
m_x = rand() % SCREEN_WIDTH;
}
else
{
m_x = 0;
}
m_y = rand() % SCREEN_HEIGHT;
m_step = (rand() % 5000) / 1000.0 + 1;
m_color = (int)(m_step * 255 / 6.0 + 0.5); // 速度越快,顏色越亮
m_color = RGB(m_color, m_color, m_color);
}
void Star::Move()
{
// 擦掉原來的星星
putpixel((int)m_x, m_y, 0);
// 計算新位置
m_x += m_step;
if (m_x > SCREEN_WIDTH)
this->Init();
// 畫新星星
putpixel((int)m_x, m_y, m_color);
}
代碼和之前差不多,只不過操作的都是成員變量。
類的使用
void main()
{
srand((unsigned)time(NULL));
initgraph(SCREEN_WIDTH, SCREEN_HEIGHT);
Star star[MAXSTAR];
for (int i = 0; i < MAXSTAR; i++)
{
star[i].Init();
}
while (!kbhit())
{
for (int i = 0; i < MAXSTAR; i++)
star[i].Move();
Sleep(30);
}
closegraph();
}
程序啟動后,先創建Star類的一組對象,保存在star數組中。之后循環進行初始化。
每30微妙,循環一次,每顆星星按順序調用自己的move方法。可以理解為每顆星星按順序移動一下。直到捕獲按鍵消息,程序退出。
最后,在文件前面加上這部分:
#include <graphics.h>
#include <time.h>
#include <conio.h>
#define SCREEN_WIDTH 1024
#define SCREEN_HEIGHT 768
#define MAXSTAR 400
這里通過宏來管理畫布大小和星星的顆數。
好了,下面執行一下我們的新代碼吧。
如果你還沒感覺到這兩種方法的差別,那么請你刪掉代碼,自己從零開始用著兩種方法實現一下這個程序,相信你會有更多的體會。
面向對象的特點
面向對象的三大要素是:封裝、繼承和多態。
我們今天只用了封裝這個特性。在后面的項目中,我們還會用到后面兩種特性,到時候你會發現面向對象真正強大的地方。
學習編程的捷徑
捷徑就是——沒有捷徑。
編程能力是練出來的,就這一個小程序,你自己從零開始寫10次就會比寫9次收獲多那么一些。只有反復練習才能有令自己滿意的提高。
諸位加油!
我是天花板,讓我們一起在軟件開發中自我迭代。
如有任何問題,歡迎與我聯系。