昨天周六,我休息了一次。今天感覺心情還不錯,就干脆多更一點。恩,同學們,注意啦,今天的課程可是不僅長,
還非常的不輕松哦!我們主要要講一下C++中繼承於C的函數式編程,還有判斷這種特殊結構;除此之外,
OpenFrameworks的一些基礎函數、概念,我也會悉數講解。
總之,做好覺悟再上吧!
有問題可以發到我的郵箱:nerver0stop@163.com
於是,開始今天的課程!
C++中的函數式編程
於是,先把我下面的這些代碼敲到你的編輯器里吧。
1 //ofApp.cpp 2 3 #include "ofApp.h" 4 5 int posX; 6 int posY; 7 //球的位置 8 double speedX; 9 double speedY; 10 //球的速度 11 int windX; 12 int windY; 13 //程序窗口大小 14 double lossRate; 15 //速度減慢的速率(即加速度的百分比形式) 16 float hue; 17 //球的色相 18 float ballRadius; 19 //球的半徑 20 21 bool isShot = false; 22 //是否截圖 23 bool isSmalling = true; 24 //是否球在變小 25 //以上代碼請放在最上面 26 27 void changeDirection(int x, int y); 28 //改變方向的函數 29 void outRange(); 30 //檢查是否出界的函數 31 void slow(); 32 //減慢小球速度的函數 33 void changeSize(); 34 //改變小球大小的函數 35 36 //-------------------------------------------------------------- 37 void ofApp::setup(){ 38 posX=200; 39 posY=180; 40 //賦予小球初始位置 41 speedX=1; 42 speedY=-1; 43 //賦予小球初始速度 44 lossRate = 0.999; 45 //賦予小球損失速率 46 ballRadius = 60; 47 //賦予小球初始半徑 48 ofSetCircleResolution(30); 49 //設置顯示小球的為 30邊形 50 hue = fmodf(ofGetElapsedTimef()*10,255); 51 //從系統時間得到色相值 52 ofColor c; 53 c.setHsb( hue,200,200); 54 //把色相值轉化為顏色 55 ofSetColor(c); 56 //設置顏色 57 } 58 59 //-------------------------------------------------------------- 60 void ofApp::update(){ 61 windX = ofGetWindowWidth(); 62 windY = ofGetWindowHeight(); 63 //獲取當前窗口寬高 64 hue++; 65 //色相增加 66 if(hue > 255) 67 { 68 hue = 0; 69 } 70 //如果色相值越界,重新取0 71 72 outRange(); 73 //檢查小球是否越界 74 75 posX += speedX; 76 posY += speedY; 77 //小球更新坐標 78 79 slow(); 80 //小球減慢速度 81 changeSize(); 82 //改變小球尺寸 83 } 84 85 86 //-------------------------------------------------------------- 87 void ofApp::draw(){ 88 ofBackgroundGradient(ofColor::white,ofColor(100,100,100), OF_GRADIENT_CIRCULAR); 89 //畫有漸變的背景 90 if(isShot) 91 { 92 ofBeginSaveScreenAsPDF("screenshot-"+ofGetTimestampString()+".pdf", false); 93 } 94 //開始准備截屏 95 ofFill(); 96 //確認填充狀態為填充 97 ofColor c; 98 c.setHsb( hue,200,200); 99 ofSetColor(c); 100 //確認現在填充顏色狀態為c 101 ofCircle(posX, posY, ballRadius); 102 //畫出小球 103 if(isShot) 104 { 105 ofEndSaveScreenAsPDF(); 106 isShot = false; 107 } 108 //截圖 109 } 110 111 //-------------------------------------------------------------- 112 void ofApp::keyPressed(int key){ 113 if(speedX>2000 || speedX < -2000 || speedY >2000 || speedY < -2000) 114 { 115 return; 116 } 117 //檢查小球是否超速;如果超速,不再增加 118 if(key == 'W' || key == 'w' || key ==OF_KEY_UP) 119 { 120 speedY -= 5; 121 } 122 if(key == 'S' || key == 's' || key ==OF_KEY_DOWN) 123 { 124 speedY += 5; 125 } 126 if(key == 'A' || key == 'a' || key ==OF_KEY_LEFT) 127 { 128 speedX -= 5; 129 } 130 if(key == 'D' || key == 'd' || key ==OF_KEY_RIGHT) 131 { 132 speedX += 5; 133 } 134 if(key ==' ') 135 { 136 speedX = 0; 137 speedY = 0; 138 //強制停止小球 139 } 140 //根據不同鍵位確定對小球的指令 141 } 142 143 //-------------------------------------------------------------- 144 void ofApp::keyReleased(int key){ 145 146 if(key =='p'|| key =='P') 147 { 148 isShot = true; 149 } 150 //確認截圖 151 } 152 153 //-------------------------------------------------------------- 154 void ofApp::mouseMoved(int x, int y ){ 155 156 } 157 158 //-------------------------------------------------------------- 159 void ofApp::mouseDragged(int x, int y, int button){ 160 161 } 162 163 //-------------------------------------------------------------- 164 void ofApp::mousePressed(int x, int y, int button){ 165 166 } 167 168 //-------------------------------------------------------------- 169 void ofApp::mouseReleased(int x, int y, int button){ 170 171 } 172 173 //-------------------------------------------------------------- 174 void ofApp::windowResized(int w, int h){ 175 176 } 177 178 //-------------------------------------------------------------- 179 void ofApp::gotMessage(ofMessage msg){ 180 181 } 182 183 //-------------------------------------------------------------- 184 void ofApp::dragEvent(ofDragInfo dragInfo){ 185 186 } 187 188 void outRange() 189 { 190 if(posX>windX-ballRadius) 191 { 192 posX = windX-ballRadius; 193 changeDirection(1,0); 194 } 195 if(posX<0+ballRadius) 196 { 197 posX = 0+ballRadius; 198 changeDirection(1,0); 199 } 200 if(posY>windY-ballRadius) 201 { 202 posY = windY-ballRadius; 203 changeDirection(0,1); 204 } 205 if(posY<0+ballRadius) 206 { 207 posY = 0+ballRadius; 208 changeDirection(0,1); 209 } 210 //檢查是否出界 211 } 212 213 void slow() 214 { 215 speedX *= lossRate; 216 speedY *= lossRate; 217 //減慢小球速度 218 } 219 220 void changeDirection(int x, int y) 221 { 222 if(x==1) 223 { 224 speedX *= -1; 225 } 226 if(y==1) 227 { 228 speedY *= -1; 229 } 230 //改變小球方向 231 } 232 233 void changeSize() 234 { 235 if(isSmalling == true) 236 { 237 ballRadius -= 0.1; 238 } 239 else 240 { 241 ballRadius += 0.1; 242 } 243 244 if(ballRadius <= 30) 245 { 246 isSmalling = false; 247 } 248 if(ballRadius >= 90) 249 { 250 isSmalling = true; 251 } 252 //改變小球大小 253 }
1 //main.cpp 2 #include "ofMain.h" 3 #include "ofApp.h" 4 5 //======================================================================== 6 int main( ){ 7 ofSetupOpenGL(800,600,OF_WINDOW); // <-------- setup the GL context 8 9 // this kicks off the running of my app 10 // can be OF_WINDOW or OF_FULLSCREEN 11 // pass in width and height too: 12 ofRunApp(new ofApp()); 13 14 }
我在這里先聲明一件事:我知道帶着行號的話不好粘貼復制;我這么做就是因為不想讓你們粘貼復制!
為什么呢?因為只是粘貼復制的話,是根本沒有任何意義的——但即便僅僅是用手打一遍,看着我的注釋,也能大概
理解我到底想做些什么。這就是區別。所以,請不要粘貼復制了,請用自己的手把相應的代碼打到相應的位置上去吧!
然后我來說一下今天怎么展開我的教程:第一步,講解OpenFrameworks程序的編程思路,並介紹這是如何實現的;
第二步,講解C++的判斷結構 與 函數式編程;第三步,講解這個程序里使用的OpenFrameworks函數接口;第四步,
就某些特殊步驟重點講解;第五步,提出問題和建議,引導讀者自行修改程序
那么,我先從第一步開始
OpenFrameworks編程思路
在正式開始講解之前,我先講解一個概念:程序其實是多種多樣的,常見的有獨占型應用、響應型應用、輔助型應用、
精靈型應用等等。(應用和程序指代的東西相同,只不過分類源頭不同)什么是獨占型應用呢?就是它會默認你在
打開這個應用的時候,就不會打開別的應用;或者,只是某些功能可以獨占。比如,游戲,就是一種典型的獨占型應用,
它會占據大量內存,也需要CPU的大量運算——你也知道這一點,所以玩游戲的時候就肯定會關上無關的程序,不然
游戲就要卡;當然,所謂獨占不一定是完全獨占,他可能只獨占了顯卡、內存,可能不會妨礙你的網絡通信,所以你
還可以下載東西之類的。然后,響應型應用就不同了——你如果沒有操作,不需要它響應的話,它就不會耗費額外的資源。
(不過有的軟件只要打開就會占用很大資源,比如word,但是也可以算為響應型應用,因為它不會自己增加消耗)網頁
瀏覽器是一個比較合適的例子(只要不在上面玩游戲或者播放流媒體的話;不過現在的情況又有了點變化,比如Ajax......
),而一般Windows程序都是以相應型應用的標准做出來的——不然你就不可能同時開着那么多進程了。輔助型應用是
占資源比較低,但是一般一直打開的應用;比如輸入法;精靈型應用其實是個老概念,你可以認為,凡是懸浮窗的部分,
就是精靈型應用。
那么,終於到重點了,OpenFrameworks上的應用是什么應用呢?答案就是:獨占型應用。
不知道大家熟不熟悉電影的原理——利用視覺暫留現象,快速的切換畫面,使人誤以為靜止的畫面是動起來的 那么一種
現象。而其中,每一幅畫面,叫做一幀。在動畫、游戲的領域,也同樣有“幀”這個稱呼。實際上,OpenFrameworks
產生的程序,大部分都是以交互為目標的,那么必然就需要動起來,於是OpenFrameworks就采取了每隔一段時間,
進行計算,更新圖形的相應信息,然后整個窗口重新繪制的方法。而這間隔的時間,一般是1/60秒,即16毫秒左右,以
人類的觀點來看,是很小的數字;但對於計算機來說,已經足夠大了。
(這些圖片是用並排放着的好幾個攝像機拍出來的,后來Muybridge根據這些賽馬的圖片,創作了人類歷史上的第一個小電影。)
那么,說了這些知識,無非就是想說明:OpenFrameworks程序每秒鍾都會繪制非常多次,而編程的重點,就是把連續
的動作,分為每一段每一段的循環往復的操作。OpenFrameworks現在提供了三個接口函數用於每秒調用:setup(),
update(),draw()。其中setup()是只有開始程序時調用,一般用於初始化;update()和draw()都是每秒調用多次,但是,
update()中一般會寫那些數值如何變化的部分;而draw()只負責畫。這樣的好處是,如果程序很卡,那么會出現掉幀的
現象,但不會是整個程序變慢(具體來講的話,可以想象競速類游戲——如果都在draw()里更新的話,那么結果就是,
不卡頓的情況下很正常,卡頓的話,整個車的速度都會變慢;而分開寫的話,車的速度不會變,只不過變成一下一下的
瞬移了)所以,我們設計的時候就要考慮怎么合理划分我們的程序內容,怎么把連續的動作化為分解動作。
C++的判斷結構 與 函數式編程
我這里以大家完全沒有過編程經驗為前提——雖然我強烈建議至少學過C,所以會從很基礎講起;但是,我這里並不是
基礎課教程,所以也會只講我用到的部分,相要學的更好,你就還是需要自己另外的學習的。
首先我把程序中的某個句子拿出來:
1 hue++; 2 //色相增加 3 if(hue > 255) 4 { 5 hue = 0; 6 } 7 //如果色相值越界,重新取0
這里的幾句話我都會講的。
首先,我說說色相是什么——我會在后面的色彩一節具體的講解的,但是此處我也粗略的講一講。你可以將之理解為
顏色的特征——也就是說只要色相不同,看起來就不一樣。其取值范圍是0~255。那么,我想要我顯示的那個小球的
顏色不停變化的話——我只有把這個色相的值不停地改變,然后每次畫的時候,都用新的顏色來畫,不就可以了嗎?
hue++;
這句話就說hue這個”變量“的值增加1。
變量:存儲數據的容器
++:自增符號,表示取到自己的值加上1再賦給自己
if(hue > 255) {
hue = 0;
}
上面這段代碼的意思是 如果 hue 大於 255 的話, 把0這個數賦給255
這里面包含了一個判斷if(){},就是說當()內的內容被判斷為真的時候,運行{}內的內容(也可以不加{},那樣默認
下面的第一行是運行內容)這樣,通過判斷,我們就保證了hue這個變量的取值一定在0~255之間了。
我的程序中還有很多用到了判斷的地方,大部分都用相應的注釋,請自行消化
那么我接下來講一講函數
(一張三角函數的圖,有沒有回憶起初高中)
函數是一個數學上的概念,當然不止是上圖所示的三角函數。真正的定義是什么呢:對於給定的某個輸入值,一定有唯一
確定的輸出。就是說,只要你的輸入不變,輸出也絕對不變。你可以利用函數來計算或者做某些變化,但是它具體怎么做的,
你在使用它的時候你並不需要關心。
實際上,我們在寫OpenFrameworks上的程序時,不可能不用OpenFrameworks自帶的函數;但是僅有那些還是不夠的,
我們有的時候為了簡化結構,就需要自己寫一些函數。如果你認真看我上面的代碼了的話,你就會發現,有這種結構:
void changeDirection(int x, int y); //改變方向的函數 void changeDirection(int x, int y) { if(x==1) { speedX *= -1; } if(y==1) { speedY *= -1; } //改變小球方向 }
上面的部分叫做聲明,下面是實現。
那么為什么要聲明?因為程序在編譯的時候,是從上到下的,所以如果你的函數在下面的話,使用的時候可能看不見,
那么就需要提前聲明一下。
根據上面這個函數,我們可以得到這個規律:
void 函數名 (...){...}
不過其實准確來說,是這樣:
類型名 函數名(...){...}
什么是類型?就是變量的結構(變量是存儲數據的地方),即這個變量是存儲什么類型的數據的。void是空類型,即什么
也沒有,就是說,我上面那個函數是什么都不會返回的。但是,你也可以讓它返回——只要你需要的話。數據類型有很多,
比如上面的int、double。分別代表整型和雙精度浮點型。如果展開來說實在太宏大了,所以還仰仗你們自己的學習。
我在程序里定義了好幾個函數,這樣,就縮短了update()這個函數的長度,加強了可讀性。
當然,把函數里的東西,都放回到調用它的地方,是完全可行的;但那樣很明顯不清晰。所以,為了簡化結構(從而寫出
更負責的程序),人們發明了函數式編程這是必須掌握的。
我用到的一些OpenFrameworks程序接口
我只介紹幾個,因為大部分在代碼里看着已經很清晰了
ofFill();
ofSetColor(c);
ofCircle(posX, posY, ballRadius);
以上三個函數是繪圖時的核心函數,第一個,用於確認現在的填充狀態為填充(如果寫ofNoFill()的話就沒有填充),第二個,
是設置現在的填充顏色為c這個變量里儲存的顏色,第三個,就是在(posX,posY)這個點上畫一個半徑為ballRadius的圓。
現在,你可以運行一下程序了,然后你就可以發現一個可以到處亂動的、不斷變着顏色、變着大小的小球就出現了。你按下wasd
或者方向鍵的話,它就會按你的要求加速減速;按空格就會馬上停在原地;按p鍵就可以截圖。
你可以試着以自己的想法改變一下我的代碼,然后觀察現象是不是符合你的推測。畢竟,實踐是學習的最好方法。