程序的流程控制-判断语句

程序流程控制-if 语句

上节提到,for 循环可以让某段代码反复执行,如果我们用一根线条来去类比程序的执行流程,它就像其中打圈的部分。而后面介绍的 if 语句,则会使程序创建多个分支,呈现树根一样的形态。

这节主要展示 if 语句的特性,同时辅以多个知识。你会了解到如何使用逻辑运算符,关系运算符,掌握加载文字图片和调用插件的方法。结尾会将之糅合到一起,从零开始,制作一个文字冒险游戏。

下面先铺垫有关数据类型以及关系,逻辑运算符的基础知识。

数据类型 bool,char,string

数据类型 含义 作用
int 整形 储存整数数据
float 浮点形 储存小数数据
char 字符类型 储存字符
string 字符串类型 储存字符串
bool 布尔类型 保存 true 值或 false 值

前面提到的 int ,float 可以保存数值数据。但显然是不够用的。例如我们想储存一些中文或是英文字符,就需要用到 char 和 string。

代码示例(6-1):


	void ofApp::setup(){
	    char a = 'x';
	    string b = "words";
	    cout << a << endl;
	    cout << b << endl;
	}

对照上面的示例。char 是一种用于存储单个字符的数据类型。赋值时,字符必须带上单引号,以此与变量名区分开来。而对 string 类型进行赋值,字符则需要使用双引号。它可以比 char 存储更多的字符信息。假如我们希望在程序中储存地址,对话,人名等信息,就可以考虑用 string。

既然 string 存储的信息比 char 更多,那能否直接将 char 类型赋值给 string 类型呢?是可以的,前提是需要写上一个类型转换函数,否则程序会报错。

代码示例(6-2):


	void ofApp::setup(){
	    char a = 'x';
	    string b = ofToString(a);
	    cout << a << endl;
	    cout << b << endl;
	}

这里的 ofToString(), 就是类型转换函数。

连接字符串

string 类型有一个特殊的用法,使用“+” 号可以将两个字符串连接起来。

代码示例(6-3):


	void ofApp::setup(){
	    string a = "words1";
	    string b = "words2";
	    string c = a + b;
	    cout << c << endl;
	}

当我们希望将多个文本信息组装在一起,就可以考虑 + 号。

bool 类型

下面介绍另一种数据类型 - bool。它能存储的东西非常少,只有两个值,true(真) 与 false(假)。就像抛一个没有厚度的硬币,要么正面,要么反面。非黑即白,没有中间状态。布尔类型会与后面提到的 if 语句结合得非常紧密,根据 true 值或 false 值,可以决定某些语句是否执行。

先看一个有关 bool 的例子

代码示例(6-4):


	void ofApp::setup(){
	    bool a = true;
	    bool b = false;
	    cout << a << endl;
	    cout << b << endl;
	}

在 OF 中你会发现,控制台中并不会直接输出 true 或 false,你只能看到 1 与 0。这里的 1 其实就代表 true ,0 就代表 false 值。反过来,除了用 true ,false 来代表真假值之外,还能用数字来进行赋值。

代码示例(6-5):


	void ofApp::setup(){
	    bool a = 1;
	    bool b = 0;
	    bool c = 3;
	    bool d = 3.2;
	    cout << a << endl;
	    cout << b << endl;
	    cout << c << endl;
	    cout << d << endl;
	}

OF中可以对 bool 类型的变量直接赋整形数据又或是浮点型数据。而无需像 P5 一样需要类型转换函数。对于所有“非0”的数字,都会处理成 true 值。而当数字为 0 ,则会处理成 false 值。

P5 与 OF 的异同对比

  • 要特别注意的是,P5 中的 String 类似为大写,OF 中的 string 为小写。布尔类型 P5 中写作 boolean ,OF 中写作 bool。两者十分相似,但不要混淆。

关系运算符与逻辑运算符号

在程序中不仅能做加减乘除的运算,还有一些我们意想不到的运算方式。

关系运算符

像加减乘除这类运算符,最终返回的结果都是数值。而关系运算符,返回的则是布尔值。

关系运算符 含义 调用方法
== 等于 a == b
!= 不等于 a != b
> 大于 a > b
>= 大于等于 a >= b
< 小于 a < b
<= 小于等于 a <= b

代码示例(6-6):


void ofApp::setup(){

    int a = 1;
    int b = 2;
    cout << (a == b) << endl;
    cout << (a != b) << endl;
    cout << (a > b) << endl;

}

顾名思义,关系运算符就是用作比较左右两边的关系,非常浅显易懂。当条件成立的时候,就返回 ture 值,不成立则返回 false 值。其中唯一要注意的是“==” 的写法,必须写两个等于号才是关系运算符。只写一个就成了赋值“=”符号。

逻辑运算符

接下来轮到介绍逻辑运算符,它返回的一样是布尔值。并且有三种类型:与,或,非。

逻辑运算符 含义 调用方法
&& a && b
|| a || b
! !a

使用 && 运算符时,只有当表达式两边都为真时,返回的才会是 true 值。当其中一边为假,又或是两边都为假,返回的会是 false 值。

|| 运算符的满足的条件则宽松一些,只要其中有一边为真,返回的就是 true 值。两边为假,返回的就是 false 值。

! 运算符的作用是取反。它需要直接写在表达式前面,而不存在需要比较左右两边的情况。若原有的表达式为真,使用后返回的结果则为 false。若原来为假,返回的结果则为 true。

代码示例(6-7):


	void ofApp::setup(){
	    cout << (true && true) << endl;
	    cout << (true && false) << endl;
	    cout << (false && false) << endl;
	    cout << (true || true) << endl;
	    cout << (true || false) << endl;
	    cout << (false || false) << endl;
	    cout << !true << endl;
	    cout << !false << endl;
	}

条件判断语句

前面提及的概念都比较抽象。似乎也很难想通,关系运算符与逻辑运算符具体能有何作用。其实只要放在图形创作的语境下去理解,一切都会非常直观。这节的主角终于来了,那就是 if 语句。

下面是 if 语句的一般调用方式:

if(表达式){

}

在没有学习 if 语句之前,写在程序中的语句,都是必须执行的,没有选择的机会。而使用了 if 语句后,在执行前需要先判断表达式的值。当表达式的布尔值为 true 的时候,大括号内的代码则会执行,否则会跳过。

代码示例(6-8):


	void ofApp::setup(){
	    if(true){
	        cout << 1 << endl;
	    }
	    if(3 > 2){
	        cout << 2 << endl;
	    }
	    if(false){
	        cout << 3 << endl;
	    }
	    if(3 < 2){
	        cout << 4 << endl;
	    }
	}

你可以通过以上的输出值,看到哪些语句被真正执行,它们都与表达式的布尔值密切相关。

if 语句控制图形的显示与隐藏

现在我们可以不看黑乎乎的控制台了,尝试用 if 实现些简单的图形效果。

代码示例(6-9):


	void ofApp::setup(){
	    ofSetWindowShape(700,700);
	}

	void ofApp::draw(){
	    ofBackground(32,48,65);
	    if(mouseX > ofGetWidth()/2){
	        ofSetColor(3,240,175);
	        ofDrawCircle(ofGetWidth()/2,ofGetHeight()/2,100);
	    }
	}

将鼠标的位置的判断写在 if 中,就能影响后续的语句是否执行,从而达到控制图形显示或是消失的效果。

if 语句的扩展 - else 与 else if。

if 语句只有在满足条件时,才会执行大括号中的代码。上例表达式的条件是 mouseX > ofGetWidth()/2,所以鼠标一旦移到屏幕右方,就会显示圆形。假如我们希望鼠标移到屏幕左方出现的是另一个图形。应该怎么办?需要多写一个 if 语句吗?

大可不必,如果两个条件之间是非此则彼的关系,那直接用 else 会更方便。

调用形式:

if(条件){
    语句1;
}else{
    语句2;
}

满足条件时,执行语句 1。不满足则执行语句 2.

代码示例(6-10):


	void ofApp::setup(){
	    ofSetWindowShape(700,700);
	}

	void ofApp::draw(){
	    ofBackground(32,48,65);
	    if(mouseX > ofGetWidth()/2){
	        ofSetColor(3,240,175);
	        ofDrawCircle(ofGetWidth()/2,ofGetHeight()/2,100);
	    }else{
	        ofSetColor(204, 3, 204);
	        ofSetRectMode(OF_RECTMODE_CENTER);
	        ofDrawRectangle(ofGetWidth()/2,ofGetHeight()/2,200,200);
    }

	}

这样写就简便多了。除此之外,关于 if 还有另一种用法,那就是 else if。当我们有更多的条件需要判断时,就可以考虑它。

调用形式:

if(条件 1){
    语句 1;
}else if(条件 2){
    语句 2;
}
...
else if(条件 n){
   语句 n;
}else{
    语句 n+1;
}

else if 语句是可以不断叠加。它的执行顺序依然是自上而下,先从条件 1 开始进行判断。若条件 1 满足则执行语句 1,同时后面的语句都跳过,不执行。当条件 1 不满足,才会检测 else if 中的条件 2。若条件 2 满足,则执行语句 2。否则就往复循环,直到执行为止。最后,如果所有条件都不满足,则执行 else 中的语句。

当然,最后的 else 也不是必须添加的。如果你不写 else,同时所有条件过一遍后,仍不满足,那 if 中的语句一条都不会执行。

代码示例(6-11):


	void ofApp::setup(){
	    ofSetWindowShape(700,700);
	}

	void ofApp::draw(){
		 ofBackground(32,48,65);
	    if(mouseX < ofGetWidth()/3){
	        ofSetColor(3,240,175);
	        ofDrawCircle(ofGetWidth()/2,ofGetHeight()/2,100);
	    }else if(mouseX < ofGetWidth()/3 * 2){
	        ofSetColor(204, 3, 204);
	        ofSetRectMode(OF_RECTMODE_CENTER);
	        ofDrawRectangle(ofGetWidth()/2,ofGetHeight()/2,200,200);
	    }else{
	        ofSetColor(3, 165, 204);
	        ofDrawTriangle(250, 450, 450, 450, 350, 250);
	    }
	}

这个例子就相当于把屏幕划分成了 3 块。左,中,右。鼠标横坐标不同,显示的图形也不同。

if 的嵌套

if 语句是可以嵌套使用的。可以由此组合出更复杂的情况。

代码示例(6-12):


	void ofApp::setup(){
	    ofSetWindowShape(700,700);
	}

	void ofApp::draw(){
	    if(mouseX > ofGetWidth()/2){
	        ofBackground(235,16,0);
	        if(mouseY > ofGetHeight()/2){
	            ofSetColor(32, 48, 65);
	            ofSetRectMode(OF_RECTMODE_CENTER);
	            ofDrawRectangle(ofGetWidth()/2,ofGetHeight()/2,200,400);
	        }else{
	            ofSetColor(255);
	            ofSetRectMode(OF_RECTMODE_CENTER);
	            ofDrawRectangle(ofGetWidth()/2,ofGetHeight()/2,400,200);
	        }
	    }else{
	        ofBackground(48,180,89);
	        if(mouseY > ofGetHeight()/2){
	            ofSetColor(32, 48, 65);
	            ofDrawEllipse(ofGetWidth()/2,ofGetHeight()/2,200,400);
	        }else{
	            ofSetColor(255);
	            ofDrawEllipse(ofGetWidth()/2,ofGetHeight()/2,400,200);
	        }
	    }
	}

  • 由于在第一层 if else 的两个分支里,写入了不同的背景颜色。所以鼠标左右位置变化,就会直接影响背景色。后面在两个分支内,又分别写入了一个 if else。通过多重状态的组合,就相当于将程序划分成了 4 个区域,4 种状态。

if 语句与取模运算

这里介绍一个比较巧妙的用法。它在 for 循环中使用频率非常高。过去我们一直通过 for 循环的 “i” 值,来影响绘图元素的部分参数。例如画一排圆,就需要用到 “i” 值来确定位置坐标。除此之外,我们其实可以对这个 “i” 作一些判断。比如进行取模运算(相当于求余数)。根据余数数值的不同,结合 if 语句就可以对大量元素进行分组处理。下面的例子中,当 “i” 为偶时,元素的填充色便设置为白色。否则(奇数时),填充色则为黑色。

代码示例(6-13):


	void ofApp::setup(){
	    ofSetWindowShape(700,700);
	}

	void ofApp::draw(){
	    ofBackground(132,197,100);
	    int num = 10;
	    float w = mouseX / 10;
	    for(int i = 0;i < num;i++){
	        if(i % 2 == 0){
	            ofSetColor(255);
	        }else{
	            ofSetColor(0);
	        }
	        ofSetRectMode(OF_RECTMODE_CENTER);
	        ofDrawRectangle(ofGetWidth()/(float)num * (i + 0.5),ofGetHeight()/2,w,600);
	    }
	}

KeyPressed 事件

现在相信你已经理解 if 的特性了。但要让它更好玩,你还需要用到后面介绍的一些编程概念。接着要介绍的,就是 keyPressed 事件。

前面与程序发生交互,基本上都是靠鼠标坐标 mouseX,mouseY 来实现的。虽然灵活,但局限性也很明显。当我们想通过鼠标位置来做选择的时候,就得写许多许多的if。在还没掌握如何写一个按钮控件的情况下。用键盘来做交互才是最明智的方式。

keyPressed 是与键盘相关的事件。它和 setup,draw 一样。在程序中已经事先定义好。你只需要把它的结构写进去,就能直接调用。

OF 中由于已经事先写好了 keyPressed 事件的结构,所以你无需像 P5 一样自己手写一遍。只需往里面填充代码即可。

下面先通过一个示例,理解 keyPressed 对程序流程的影响。

keyPressed 的运行流程

代码示例(6-14):


	void ofApp::setup(){
		cout << ofGetFrameNum() << ":setup" << endl;
	}

	void ofApp::update(){
	    cout << ofGetFrameNum() << ":update" << endl;
	}

	void ofApp::draw(){
	    cout << ofGetFrameNum() << ":draw" << endl;
	}

	void ofApp::keyPressed(int key){
	    cout << ofGetFrameNum() << ":keyPressed" << endl;
	}

代码说明:

  • 运行程序后,记得在键盘上任意按下一些键,然后关闭程序,查看控制台的输出
  • 这里主要观察各个结构的执行顺序,其中 ofGetFrameNum() 可获取程序当前已运行的帧数

  • 在控制台处往下翻,寻找带有“keyPressed”的字符串。它的输出位置,取决于你的按键时间。在这个例子中就表示,程序运行到 26 帧时,就触发了按键事件。
  • 通过观察 ofGetFrameNum(),不难发现 keyPressed 事件是在 update 前才执行的
  • cout 可以直接输出整形数据。所以直接写 cout << 1 << endl 是可行的,但当你希望使用 cout 输出字符,字符本身则需要加上双引号,如 cout << “:update” << endl;,这表示输出的是字符串类型,否则会出错
  • 由于 ofGetFrameNum() 是整形数据,而“update” 是字符串类型。因而不能直接写成“ cout << ofGetFrameNum() “:update” << endl; ”。正确写法是在中间多加一个 “<<” 来分隔开
  • 另外也存在另一种写法 “ cout << ofToString(ofGetFrameNum()) + “:update” << endl; ” ,就是用 ofToString 函数先将 ofGetFrameNum 转换成字符串类型,这时用 “+” 号就能连接两个字符串了

  • 上图揭示了 keyPressed 的运行流程

keyPressed 获取按键键值

keyPressed 不仅能判断你何时按下按键,通过获取 key 的值,还能知道具体按了哪个键。

代码示例(6-15):


	void ofApp::keyReleased(int key){
	    if(key == '1'){
	        cout << 1 << endl;
	    }
	    if(key == '2'){
	        cout << 2 << endl;
	    }
	    if(key == '3'){
	        cout << 3 << endl;
	    }
	    if(key == 'a'){
	        cout << 'a' << endl;
	    }
	    if(key == 's'){
	        cout << 's' << endl;
	    }
	    if(key == 'd'){
	        cout << 'd' << endl;
	    }
	}

上面例子对数字键 1,2,3,以及字母 a,s,d 添加了按键检测。当你按下这几个键。就会在控制台输出对应的字符。key 是系统定义好的,它会自动获取你按下的键值。所以若想了解 key 是否为某个值时,判断条件就需要写成 if(key == 某值)。当你想检测的是数字键或是字母键,只要在数字或是字母的基础上加单引号即可。

对于某些无法用数字或是字母表示的特殊按键,有另外的写法

代码 代表键值
OF_KEY_UP 方向键上
OF_KEY_DOWN 方向键下
OF_KEY_LEFT 方向键左
OF_KEY_RIGHT 方向键右
OF_KEY_SHIFT shift 键
OF_KEY_DEL Del 键
OF_KEY_ALT Alt 键
OF_KEY_CONTROL Ctrl 键
OF_KEY_RETURN 回车键
OF_KEY_TAB Tab 键
OF_KEY_F1 F1
OF_KEY_F2 F2 (以此类推)
  • 因此,若想检测向上的方向键,就可以这么写 if(key == OF_KEY_UP){ 语句;}
  • 需特别注意的是 OF_KEY_ALT , OF_KEY_SHIFT ,OF_KEY_CONTROL 都会执行两遍,你可以通过 cout 观察结果

keyPressed 控制图形

前面通过鼠标的位置,来控制图形的显示或是隐藏。现在可以改用 keyPressed 试试。

代码示例(6-16):


---ofApp.h 内

 	bool show;

---ofApp.cpp 内

	void ofApp::setup(){
	    ofSetWindowShape(700,700);
	}

	void ofApp::update(){

	}

	void ofApp::draw(){
	    ofBackground(32,48,65);
	    if(show){
	        ofSetColor(3,240,175);
	        ofDrawCircle(ofGetWidth()/2,ofGetHeight()/2,100);
	    }
	}

	void ofApp::keyPressed(int key){
	    if(key == '1'){
	        show = true;
	    }
	    if(key == '2'){
	        show = false;
	    }
	}

  • 绘图函数不能写在 keyPressed 中,只能通过创建一个布尔变量来作为中介。keyPressed 先影响布尔变量的值。从而 draw 函数中的 if 语句再根据此值来决定显示与否。

有关 keyPressed 的用法先介绍到这里,够用就好了。后面会有更多的篇幅去深入理解各样事件。

在窗口显示文字

这节的最终目标是完成一个文字冒险游戏,所以要先了解如何在程序中显示文字以及图片。之前输出的文字都在控制台上。但当我们导出程序的时候,实际上是看不到控制台的。现在要想办法把文字显示在窗口中。

OF 中不像 P5 ,可以直接使用中文。它必须结合插件ofxTrueTypeFontUC才能正确显示。在正式写代码前,你必须做好准备工作。

首先在官网 addons 中寻找这个插件(http://ofxaddons.com/categories)。又或者直接通过这个链接进行下载(https://github.com/hironishihara/ofxTrueTypeFontUC)

进入到 github 页面后,选 Clone or download 下面的 download ZIP 即可。

下载解压缩后,它的文件名会是 ofxTrueTypeFontUC-master。请记得将后面的 -master 去掉。并将整个文件夹剪切到 OF 自身的 addons 文件夹中。

当你完成上面的步骤,在通过 project generator 创建文件时,就会在addons 处找到 ofxTrueTypeFontUC。当你以后做的项目需要用到中文字体时,请记得点选它,最后才点 Generator 按钮来生成工程文件。

现在只是安装好插件,这个工程文件上还没有放字体文件。你可以从电脑的字体库中寻找一个支持中文显示的字体,格式建议为主流的 otf。并将它复制到工程文件的 data 文件夹中。若是找不到具体位置,可以在 xcode 中右键 src 的文件夹,选 Show in Finder 就会自动弹出

在 bin 里面就能找到 data 文件夹,将 otf 字体文件复制进去。

接着你就可以正式敲代码了。

代码示例(6-17):


--- ofApp.h 内

	#include "ofMain.h"
	#include "ofxTrueTypeFontUC.h"

	class ofApp : public ofBaseApp{

		public:
			void setup();
			void update();
			void draw();
			...


	    ofxTrueTypeFontUC font;
	};

--- ofApp.cpp 内

	void ofApp::setup(){
	    ofSetWindowShape(700,700);
	    font.loadFont("font.otf",80);
	}

	void ofApp::draw(){
	    ofBackground(0);
	    font.drawString("中文字体", mouseX,mouseY);
	}

代码说明:

  • ofApp.h 内,有一句前面没提到的新语法。就是 #include “ofxTrueTypeFontUC.h” 它代表在程序中调用插件。之前的工作只是将插件包含到工程文件中,并没有真正在程序中使用。以后如果希望加入其他插件,导入的方法都是类似的,在生成时先将插件加到工程文件上。再在 ofApp.h 内,写上 #include”插件名.h” 进行调用

  • 加载插件后,你就能使用 ofxTrueTypeFontUC 这个新的数据类型了。

  • ofxTrueTypeFontUC font; 表示声明的是 ofxTrueTypeFontUC 类型,它就像 int ,float。只是它储存的不是数字,而是字体。后面的 font 只是代表一个变量名,它是可以任意取的。

  • 创建了 ofxTrueTypeFontUC 类型,就相当于有了一个容器。我们需要告诉程序,这个容器里面存放什么。setup 中的 font.loadFont(),就代表往 font 中载入一个名为”font.otf”的字体。这是一个原本叫做思源黑体的中文字体,只是为了便于输入,将字体文件改成了“font.otf”。第二个参数则会控制字体的显示大小。

  • 为什么会有”.loadFont” ? 似乎和前面提到的函数调用方式都不一样。这里的点代表 loadFont 这个函数与 font 这个变量之间是父子关系,这个函数是属于 font 的。通过 font.loadFont 去调用,就只会影响 font 这个变量。现在可以无需深究,先熟悉这种调用方式。

  • draw 中的 font.drawString() 才是真正的执行部分,第一个参数填写字符显示的内容,第二第三个参数填写字符的横纵坐标。这个坐标决定字符的左下角所处的位置。

当你尝试往 OF 程序中输入中文字符时,会出现这个提示。

这是因为字符中包含了中文,这时只要点 Convert to UTF8 就能正常输入。

改变字体颜色

可以将字体看作形状,通过 ofSetColor 就能控制字体的颜色。

代码示例(6-18):


--- ofApp.h 内

		#include "ofMain.h"
		#include "ofxTrueTypeFontUC.h"

		class ofApp : public ofBaseApp{

			public:
				void setup();
				void update();
				void draw();
				...


		    ofxTrueTypeFontUC font;
		};

--- ofApp.cpp 内


	void ofApp::setup(){
	    ofSetWindowShape(700,700);
	    font.loadFont("font.otf",80);
	}

	void ofApp::draw(){
	    ofBackground(255);
	    ofSetColor(125);
	    font.drawString("中文字体", 150,300);
	    ofSetColor(255,155,100);
	    font.drawString("English", 150,500);
	}

在窗口显示图片

与创建字体类似,也有专门为图片打造的数据类型。在程序中载入图片之前,我们需要先把图片文件放在对应的文件夹中。

图片文件与字体文件一样,都放在 data 文件夹中

准备完毕后,就可以开始写代码

代码示例(6-19):


---ofApp.h内

	ofImage pic;

---ofApp.cpp内

	void ofApp::setup(){
	    ofSetWindowShape(700,700);
	    pic.load("1.jpg");
	}

	void ofApp::draw(){
	    ofBackground(0);
	    pic.setAnchorPercent(0.5,0.5);
	    pic.draw(ofGetWidth()/2,ofGetHeight()/2,400,400);
	}

代码说明:

  • ofImage 是用于存放图片的数据类型。
  • load 函数的作用是载入图片“1.jpg”。
  • .setAnchorPercent(0.5,0.5); 用于设置图片坐标的相对位置。若不加。图片的绘制方式则与矩形默认的绘制方式一致,坐标为左上角的顶点。开启此命令后,坐标则会居于图片中心。
  • .draw 的作用是显示图片。它有多种参数输入方式。可以添 3 个参数或是 5 个参数。当参数数量为 3 时,第一个参数填写 ofImage 变量,第二,第三参数决定图片的横纵坐标。此时源图片的尺寸有多大,绘制到窗口中就有多大。当参数数量为 5 时,前 3 个参数与参数数量为 3 时完全一致,但后两个参数可以决定图片的长宽。

设置图片颜色

代码示例(6-20):


---ofApp.h内

	ofImage pic;

---ofApp.cpp内

	void ofApp::setup(){
	    ofSetWindowShape(700,700);
	    ofSetBackgroundAuto(false);
	    pic.load("dog.png");
	}

	void ofApp::draw(){
	    pic.setAnchorPercent(0.5,0.5);
	    ofSetColor(mouseX/(float)width * 255,255,255);
	    pic.draw(mouseX,mouseY,400,400);
	}

代码说明:

  • 这里载入的图片格式是带有透明背景的 png。因此图片不会存在明显的矩形边框。
  • ofSetColor 可以设置图片的颜色。它决定的是图片每个通道的“放光”比例。假如图片某个像素的色值为(r,g,b,a),当使用 ofSetColor(A,B,C,D) 后,新像素的色彩就变为 (r * (A/255),g * (B/255),b * (C/255),a * (D/255))。ofSetColor 对于图片的作用,有点像一个滤镜,会对图片中的所有像素都进行同样的操作。当你不写 ofSetColor 时,程序默认执行 ofSetColor(255)。也就是百分百地还原图片的色彩。
  • 示例中通过 mouseX 来改变 ofSetColor 的第一个参数。就可以使红通道的放出量从 0% 变化到 100% 。当鼠标移到左方,红色光减少,绿蓝光就会显现。所以可以看到,doggy 的头像会呈现蓝绿色。

综合运用:文字冒险游戏

希望在开发游戏前,前面的基础知识都已经巩固好了。现在你已经具备制作一个文字冒险游戏的所有条件了。

文字冒险游戏是什么?前不久非常流行的 lifeLine 就能归到此列。如果你是个掌机控,你肯定也听说过逆转裁判系列,它们都是文字冒险游戏中的优秀作品。

if 语句提供了建造程序骨架的可能性,现在你需要寻找“血肉”,将它们依附到骨架之上。如果你希望自己构建的世界更有代入感,图片素材是不可或缺的。请在编码前搜集好相关的素材。

网上有许多游戏美术资源都是开源的,同时允许运用到个人作品或者商业作品中。

以下是程序中用到的字体和图片素材。

字体:思源黑体,Adobe与谷歌推出开源字体。

美术资源链接:

http://opengameart.org/content/roguelike-bosses (终极Boss)

http://opengameart.org/content/forest-background (森林背景)

http://opengameart.org/content/gold-treasure-icons (宝藏)

http://opengameart.org/content/animated-skeleton (骷髅)

http://opengameart.org/content/pink-potion-item (药瓶)

程序截图:

现在已经可以做些比较吓唬人的效果了,以下代码都没有超出前面的知识。

代码示例(6-21):


    ofxTrueTypeFontUC font;
    int count;
    ofImage background,bottle,boss,bone,gold;

	void ofApp::setup(){
	    ofSetWindowShape(700,400);
	    font.loadFont("font.otf",15);
	    
	    bottle.load("bottle.png");
	    background.load("background.png");
	    boss.load("boss.png");
	    bone.load("bone.png");
	    gold.load("gold.png");
	    count = 0;
	}

	void ofApp::draw(){
	    ofBackground(0);
	    
	    // 绘制背景
	    ofSetColor(255);
	    background.draw(0,0,ofGetWidth(),ofGetHeight());
	    
	    // 绘制文字后的半透明背景
	    ofSetColor(0,150);
	    ofDrawRectangle(0, 250, 700, 80);
	    ofDrawRectangle(0, 350, 700, 40);
	    
	    if(count == 0){
	        ofSetColor(255);
	        font.drawString("你為了尋找傳說中的寶藏,进入了黑暗森林。", 30,280);
	        font.drawString("突然,你和你的小伙伴在路上看见一个神秘药瓶,你打算怎么做。", 30,310);
	        ofSetColor(150,150,255);
	        font.drawString("1.踢飞它", 30, 380);
	        font.drawString("2.自己喝了", 180, 380);
	        font.drawString("3.怂恿小伙伴喝了", 330, 380);
	        
	        ofSetColor(255);
	        bottle.setAnchorPercent(0.5,0.5);
	        bottle.draw(ofGetWidth()/2,ofGetHeight()/3,100,100);
	    }
	    
	    
	    if(count == 1){
	        ofSetColor(255);
	        font.drawString("水壶翻倒了,突然一股浓烟冒出。", 30,280);
	        font.drawString("终极 Boss 直接出现到面前 …(⊙_⊙;),你准备", 30,310);
	        ofSetColor(150,150,255);
	        font.drawString("1.情况不妙,闪", 30, 380);
	        font.drawString("2.我是勇者我进攻!", 240, 380);
	        font.drawString("3.陪它聊聊天", 500, 380);
	        
	        ofSetColor(255);
	        boss.setAnchorPercent(0.5,0.5);
	        boss.draw(ofGetWidth()/2,ofGetHeight()/3,100,100);
	    }
	    
	    if(count == 2){
	        ofSetColor(255);
	        font.drawString("突然间,你拥有了超能力。", 30,280);
	        font.drawString("感应到宝藏的呼唤,来到了终极 Boss 的面前,你准备", 30,310);
	        ofSetColor(150,150,255);
	        font.drawString("1.不啰嗦,直接进攻", 30, 380);
	        font.drawString("2.陪它聊聊天", 250, 380);
	        
	        ofSetColor(255);
	        boss.setAnchorPercent(0.5,0.5);
	        boss.draw(ofGetWidth()/2,ofGetHeight()/3,100,100);
	    }


	    if(count == 3){
	        ofSetColor(255);
	        font.drawString("小伙伴痛苦地跪倒在地上。", 30,280);
	        font.drawString("化身成终极 Boss 站到你的面前 …(⊙_⊙;),你准备", 30,310);
	        ofSetColor(150,150,255);
	        font.drawString("1.情况不妙,闪", 30, 380);
	        font.drawString("2.我是勇者我进攻!", 240, 380);
	        font.drawString("3.陪它聊聊天", 500, 380);
	        
	        ofSetColor(255);
	        boss.setAnchorPercent(0.5,0.5);
	        boss.draw(ofGetWidth()/2,ofGetHeight()/3,100,100);
	    }
	    
	    if(count == 4){
	        ofSetColor(255,0,0);
	        background.draw(0,0,ofGetWidth(),ofGetHeight());
	        ofSetColor(255);
	        font.drawString("你的速度太慢了,Boss 向你扔了一个火球", 30,280);
	        font.drawString("由于实力的差距,你无法抵挡.....", 30,310);
	        ofSetColor(150,150,255);
	        ofSetColor(150,150,255);
	        font.drawString("游戏结束", 30, 380);

	        ofSetColor(255);
	        bone.setAnchorPercent(0.5,0.5);
	        bone.draw(ofGetWidth()/2,ofGetHeight()/3,100,100);
	    }
	    
	    if(count == 5){
	        ofSetColor(255);
	        font.drawString("Boss 被你的气势打倒了", 30,280);
	        font.drawString("宝藏出现", 30,310);
	        ofSetColor(150,150,255);
	        font.drawString("你和小伙伴带着宝藏逃离黑暗森林,通关成功!", 30, 380);
	        
	        ofSetColor(255);
	        gold.setAnchorPercent(0.5,0.5);
	        gold.draw(ofGetWidth()/2,ofGetHeight()/3,170,150);
	    }
	    
	    if(count == 6){
	        ofSetColor(255,0,0);
	        background.draw(0,0,ofGetWidth(),ofGetHeight());
	        ofSetColor(255);
	        font.drawString("Boss 拒绝你的请求,并向你扔了个火球", 30,280);
	        font.drawString("由于实力的差距,你无法抵挡.....", 30,310);
	        ofSetColor(150,150,255);
	        font.drawString("游戏结束", 30, 380);
	       
	        ofSetColor(255);
	        bone.setAnchorPercent(0.5,0.5);
	        bone.draw(ofGetWidth()/2,ofGetHeight()/3,100,100);
	    }
	}

	void ofApp::keyPressed(int key){
	    if(count == 0){
	        if(key == '1'){
	            count = 1;
	        }
	        if(key == '2'){
	            count = 2;
	        }
	        if(key == '3'){
	            count = 3;
	        }
	    }else if(count == 1 || count == 3){
	        if(key == '1'){
	            count = 4;
	        }
	        if(key == '2'){
	            count = 5;
	        }
	        if(key == '3'){
	            count = 6;
	        }
	    }else if(count == 2){
	        if(key == '1'){
	            count = 5;
	        }
	        if(key == '2'){
	            count = 6;
	        }
	    }
	}

代码浅析:

  • 示例中只有两次选择机会
  • 示例展示的只是基本用法,留待你去扩展,补充
  • count 变量代表一个计数器。通过它来标记不同的场景。当触发不同的按键,就能在场景间进行跳转,draw 函数也会因应 count 的变化,绘制出对应的场景。

思考题:如何简化代码

  • 能否用 string 简化代码
  • 尝试设计更复杂的分支结构。甚至给角色加上生命力,攻击力。与终极 Boss 进行决战。
  • 给场景配上战斗动画

导出作品

当你完成一个类似的游戏后,肯定想迫不及待地希望分享给其他人。从 OF 中导出程序十分简单。你甚至无需专门地导出,只要你的程序有被执行(run)过。

你可以在 xcode 中右键 src 的文件夹,选 Show in Finder

它会自动弹出工程文件夹。在里面找到 bin 文件夹。

bin 文件夹中,装载的就是整个可执行程序。之前编译过的文件,默认都是放在这里。这个文件可以拷贝给其他人,它就像普通程序一样,只要双击带有 OF 图标的程序就能打开。其他用户无需安装 OF 也能正常使用。

End

希望这节能激起你的创作欲望,代码世界中会有更多精彩的事物留待你去挖掘~ Keep Coding~

点击阅读原文可获取源码