日更第3期-2015-1-18-openFrameworks系列第二講-規范學習函數式編程!


昨天周六,我休息了一次。今天感覺心情還不錯,就干脆多更一點。恩,同學們,注意啦,今天的課程可是不僅長,

還非常的不輕松哦!我們主要要講一下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鍵就可以截圖。

 

 

你可以試着以自己的想法改變一下我的代碼,然后觀察現象是不是符合你的推測。畢竟,實踐是學習的最好方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM