C++代碼訓練營 | 繪制星空

星空

是不是很炫,不過我們今天要用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++的面向對象

OOP

下面真正進入今天的主題。上面的程序雖然使用了面向對象的思想,但代碼形式上依然還是結構化的。我們要用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次收獲多那么一些。只有反復練習才能有令自己滿意的提高。

諸位加油!

我是天花板,讓我們一起在軟件開發中自我迭代。
如有任何問題,歡迎與我聯系。


上一篇:C++代碼訓練營 | 經典繪圖工具EasyX
下一篇:C++代碼訓練營 | 另一片星空

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容