昨天周六,我休息了一次。今天感觉心情还不错,就干脆多更一点。恩,同学们,注意啦,今天的课程可是不仅长,
还非常的不轻松哦!我们主要要讲一下C++中继承于C的函数式编程,还有判断这种特殊结构;除此之外,
OpenFrameworks的一些基础函数、概念,我也会悉数讲解。
总之,做好觉悟再上吧!
有问题可以发到我的邮箱:[email protected]
于是,开始今天的课程!
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键就可以截图。
你可以试着以自己的想法改变一下我的代码,然后观察现象是不是符合你的推测。毕竟,实践是学习的最好方法。