写给设计师的OF编程指南7 - 程序流程控制-条件语句 (下)
在窗口显示文字
这节的最终目标是完成一个文字冒险游戏,所以要先了解如何在程序中显示文字以及图片。之前输出的文字都在控制台上。但当我们导出程序的时,实际上是看不到控制台的。需要想办法把文字显示在窗口中。
显示文字的简便方式
如果你对字体的样式没有要求,只需用一个命令就能实现显示效果
代码示例(7-1):
void ofApp::setup(){
ofSetWindowShape(400,400);
}
void ofApp::draw(){
ofBackground(0);
ofDrawBitmapString("Font is here", 50, 50);
}
调用格式:
ofDrawBitmapString(str,x,y);
ofDrawBitmapString 函数的作用是在窗口绘制字体。参数 str 写的就是文字内容,一般使用字符串类型,所以需要打上双括号以表明变量类型。除此之外也可以写整形,浮点型等数据。
参数 x , y 代表的是字体显示的横纵坐标。这个坐标会决定字符左下角顶点的位置。
直接使用 ofDrawBitmapString 命令,字体的大小都是固定的。如果你希望在窗口显示一些变量数据,而不是控制台,就可以采用这种省事的方式。但此命令只能用来显示英文字体,显示中文需要使用其他方式。
显示中文字
OF 中不像 P5 ,可以直接使用中文。它必须结合插件 ofxTrueTypeFontUC 才能正确显示。在正式写代码前,你必须做好准备工作。
首先在官网 addons 中寻找这个插件()
进入到 github 页面后,选 Clone or download 下面的 download ZIP 即可。
下载解压缩后,它的文件名会是 ofxTrueTypeFontUC-master。请记得将后面的 -master 去掉。并将整个文件夹剪切到 OF 自身的 addons 文件夹中。
当你完成上面的步骤,在通过 project generator 创建文件时,就会在addons 处找到 ofxTrueTypeFontUC。当你以后做的项目需要用到中文字体时,请记得点选它,最后才点 Generator 按钮来生成工程文件。
现在只是安装好插件,这个工程文件上还没有放字体文件。你可以从电脑的字体库中寻找一个支持中文显示的字体,格式建议为主流的 otf,ttf 或 ttc。并将它复制到工程文件的 data 文件夹中。
若是找不到具体位置,可以在 xcode 中右键 src 的文件夹,选 Show in Finder 就会自动弹出
在 bin 里面就能找到 data 文件夹,将 otf 字体文件复制进去。
接着你就可以正式敲代码了。
代码示例(7-2):
—- 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 就能正常输入。
改变字体颜色
可以将字体看作图形,它就像 ofDrawCircle 或 ofRectangle ,通过 ofSetColor 就能控制字体的颜色。
代码示例(7-3):
—- 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 文件夹中
准备完毕后,就可以开始写代码
代码示例(7-4):
—-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 时完全一致,但后两个参数可以决定图片的长宽。
设置图片颜色
代码示例(7-5):
—-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 语句提供了建造程序骨架的可能性,现在你需要寻找“血肉”,将它们依附到骨架之上。如果你希望自己构建的世界更有代入感,图片素材是不可或缺的。请在编码前准备好相关的素材。
即使你既不会画画,也不懂设计,不代表就无法做游戏。不仅是代码,网上其实也有不少游戏美术资源是开源的,并且允许运用到个人作品或者商业作品中。例如 。以下程序用到的图片素材都来源于此。
美术资源链接:
(终极Boss)
(森林背景)
(宝藏)
(骷髅)
(药瓶)
字体:思源黑体,Adobe与谷歌推出开源字体。
我们先来看看实例的最终效果:
看上去已经比较唬人了,但以下代码都没有超出前面的知识。
代码示例(7-6):
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
希望这节能激起你的创作欲望,代码世界中会有更多精彩的事物留待你去挖掘~
(点击阅读原文可获取示例 7-6 源码)