1. 本课我们要学一个新的类,称为字符串(string)。对我们来说,这并不是一个全新的概念,之前我们也曾接触过它们:
println("printing some text to the message window!"); // 列印一个字符串
PImage img = loadImage("filename.jpg"); // 为文件名使用一个字符串
这样看起来,我们似乎可以简单的将字符串理解为“在双引号内的内容”。
2. 字符串的核心其实是储存字母数组,如果没有string类,那么我们要写一行字的代码可能会变成:
char[] sometext = { 'H', 'e', 'l', 'l' , 'o' , ' ', 'W' , 'o' , 'r', 'l' , 'd'};
这么搞绝对会被搞死,如果使用string对象,世界立马变得简单美好起来:
String sometext = " 如何做一个字符串? 在引号间输入字符!";
3. 我们必须记住,一个string是一个伴随方法(method)的对象。类似于PImage对象可以在存储一个图像数据的同时,还可以使用方法copy(), loadPixels()等等。对于string来说,一个方法是 charAt(),它可以返回一个字符串里给定序号的字母。同数组一样,字符串的第一个字母同为第0号。
4. 通过Daniel随后出的一题我意外的发现,在一个字符串内,空格也算一个“字母”,也占序号,无论这个空格出现在字符串内的任何地方。
5. 另一有用的方法是length()。这常会与数组length的属性混淆。不过你记得按一下方式呼叫它就对了:
String message = "这个字符串有十五个字符那么长。"; println(message.length());
通过笨办法——数数的方式,我们看到,句号也会被当作一个“字母”。因此我们可以推论,在一个字符串内任何占位符都会被当作一个“字母”被进行计数。
6. 顺序列印一个字符串的每一个字母:
String message = "一堆文字."; for(int i = 0; i < message.length(); i++) { char c = message.charAt(i); println(c);
7. toUpperCase()可以使字符串内字母全大写:
String message = "a bunch of text here."; String uppercase = message.toLowerCase(); println(uppercase);
8. toLowerCase()可以使字符串内字母全小写,用法同上。
9. 细心的你可能会发现,在第7条的例子当中,为什么我们要用println(uppercase); ,而不用println(message);?因为string对象是一种特殊的不可变对象。所谓“不可变”是指它的数据永远都不可变。这就意味着任何时候我们要改变一个字符串,我们必须创建一个新的字符串。因此在转换大写的时候,方法toUpperCase()返回的是一个全大写的字符串对象的副本。
10. 最后,我们看看equals()。说到“等于”,也许你首先会想到这么写:
String one = "hello"; String two = "hello"; println(one == two);
从技术角度讲,当对对象使用"==",它比较的是每个对象的内存位置。尽管每个字符串包含相同的数据——"hello"——如果它们是不同的对象实例,则"=="可能会返回一个错误的对比。这样的时候,就是equals()发挥作用的时候,它用来对比两个字符串是否包含相同的数据,而无论它们存储的位置在哪里。
String one = "hello"; String two = "hello"; println(one.equals(two));
尽管以上两种方法都会返回正确的结果,但是用equals()更保险。基于string对象不同的创建方式,"=="不会始终保持正确。
11. 字符串还可用“+”号连接。
String helloworld = "Hello" + "World";
变量照使:
int x = 10; String message = "x的值是:" + x;
“图像”一课载入编号图像数组是这一用法一个很好的例子。
12. 要在萤幕上显示一些文本,我们需要做以下简单几步:
12.1. Tools > Creat Font 选择一个子体。这将创建并将字体文件放入你的data文件夹。p5的字体格式很特殊——"vlw",它使用图像来显示每个字母。因为如此,你同时还要指定你想要显示文字的大小。
12.2. 申明一个PFont类型的对象:
PFont f;
12.3. 用loadFont()载入字体。载入一次就够了,所以我门将它放在setup()里,和载入图像一个道理。
f = loadFont("ArialMT-16.vlw");
12.4. 用textFont()指定字体。两个引数,第一个字体变量,第二个字体大小(可选)。如果不指定字体大小,则字体会以最初载入的大小显示。指定一个与创建字体大小不同的字体大小会影像它的显示(和放大缩小图像一样)。
textFont(f,36);
12.5. 用fill()指定颜色。
12.6. 呼叫text()函数显示文本。三个引数:文本,x位置,y位置。
text( "呜呜呜... 字符串... " ,10,100);
12.7. 上完整的例子:
// Step 2: 申明PFont变量 PFont f; void setup() { size(200,200); // Step 3: Load Font f = loadFont( "ArialMT-16.vlw" ); } void draw() { background(255); textFont(f,16); // Step 4: 指定要用的字体 fill(0); // Step 5: 指定字体颜色 // Step 6: 显示文本 text ( "oNEwAY is a Chinese Post-Rock Band" ,10,100); }
通过上例,我发现这玩意不会自动换行,,多出来的部分就会显示在窗口之外而不可见。
13. 字体还可被createFont()函数创建:
myFont = createFont( "Georgia", 24, true); // 三个引数分别是:字体名字,字体大小,是否开启平滑的一个布尔表达式
createFont()允许你创建安装于本机却在p5默认选项中不支持的字体。另外,createFont()允许字体缩放到任意大小而不影响显示质量。更多介绍请参看p5在线手册.
14. PFont.list()可以查看所有可用的字体:
println(PFont.list()); // 列印全部可用于createFont() 的字体。
15. 下面这个练习在弹跳的球旁边伴随显示球的x,y位置(当然一个更好的显示方式是将文本位置固定):
PFont f; float x = 100; float y = 0; float speed = 0; float gravity = 0.05; void setup() { size(400,200); f = createFont("Arial", 16, true); smooth(); } void draw() { background(255); stroke(175); rectMode(CENTER); ellipse(x,y,10,10); y = y + speed; // Add gravity to speed. speed = speed + gravity; // If square reaches the bottom // Reverse speed if (y > height) { // Multiplying by -0.95 instead of -1 slows the square down each time it bounces (by decreasing speed). // This is known as a "dampening" effect and is a more realistic simulation of the real world (without it, a ball would bounce forever). speed = speed * -0.95; } textFont(f,10); fill(0); // 将浮点值转为整数并显示 text((int)x+", "+(int)y,x+8,y-8); }
16. textAlign() —— 指定文本对齐模式:
PFont f; void setup() { size(400,200); f = createFont("Arial", 16, true); } void draw() { background(255); stroke(175); line(width/2,0,width/2,height); textFont(f); fill(0); // textAlign()设置文本对齐模式. 它有一个引数: CENTER, LEFT, 或者 RIGHT. textAlign(CENTER); // 居中 text("这是居中的文字." ,width/2,60); textAlign (LEFT) ; // 居左 text("这是左对齐的文字." ,width/2,100); textAlign(RIGHT); // 居右 text("这是右对齐的文字." ,width/2,140); }
17. textWidth() —— 计算并返回任何字母或文本字符串的宽度。比如说我们要在萤幕下方做一个从右至左的新闻滚动条。当文字全部滚入左边后,从右边重新开始滚动。如果我们知道文本开头的x位置和文本的宽度,我们便可知道它什么时候会完全滚入左边。textWidth()给我们的正是这个宽度。
首先,我们在setup()内申明新闻,字体,x变量,并初始化它们。
// 一条新闻 String headline = "New study shows computer programming lowers cholesterol."; PFont f; // 全局字体变量 float x; // 新闻的水平位置 void setup() { f = createFont( "Arial",16,true); // 载入字体 x = width; // 将x放到右边 }
draw()里边:
// 在x位置显示新闻 textFont(f,16); textAlign(LEFT); text(headline,x,180);
让x减小,以使文本向左走:
x = x – 3;
稍微复杂一点的一步,测试文本是否已经全部滚入左侧,如果已经离开萤幕,重置其到最右边:
// 如果x小于文本宽度的负值,则这段文本便完全处于萤幕外 float w = textWidth(headline); if (x < -w) { x = width; }
下例即以上代码的合体,唯一不同的是,它分别滚动两段不同的文本:
// 新闻数组 String[] headlines = { "Processing downloads break downloading record." , "New study shows computer programming lowers cholesterol." , }; PFont f; // 全局字体变量 float x; // 水平位置 int index = 0; void setup() { size(400,200); f = createFont( "Arial" ,16,true); // 初始化x到最右 x = width; } void draw() { background(255); fill(0); // 在x位置显示新闻 textFont(f,16); textAlign (LEFT); // 基于"index"变量的值显示数组的一个特定字符串 text(headlines[index],x,180); // 递减x x = x - 3; // 如果x小于文本宽度的负值,则这段文本便完全处于萤幕外 // textWidth()被用来计算当前字符串的宽度. float w = textWidth(headlines[index]); if (x < -w) { x = width; // 递增index,这样当当前字符串离开萤幕便显示一个新的字符串。 index = (index + 1) % headlines.length; } }
18. 除了textAlign()和textWidth()外,还有textLeading(), textMode()和textSize()可用,自己看p5在线参考for more。
19. 习题17-6,做一个接头接尾持续滚动的文本条。看似简单,我却不会,,已经给了Daniel邮件,等他答案。
20. 结合像素数组,我们可以用字母做一个视频马赛克,代码长度不短:
import processing.video.*; // 缩放值 int videoScale = 14; // 我们系统中行和列的数量 int cols, rows; // capture对象 Capture video; // 马赛克模式中的源文本. 越长的字符串也许会出现越有趣的结果. String chars = "fuckyoubaby" ; PFont f; void setup() { size(640,480); // 建立行和列 cols = width/videoScale; rows = height/videoScale; video = new Capture(this,cols,rows,15); // 载入字体 // 使用一个固定宽度的字体. 对大多数字体来说, 不同的字母有不同的宽度. // 在固定宽度字体钟, 所有字母等宽. // 这对本例很有用,因为我们要在空间平均排布这些字母 f = loadFont("Courier-Bold-20.vlw"); } void draw() { background(0); // 从摄像头读取图像 if (video.available()) { video.read(); } video.loadPixels(); // 使用一个计数器变量来数字符串中的字母 int charcount = 0; // 开始循环行 for (int j = 0; j < rows; j ++ ) { // 开始循环列 for (int i = 0; i < cols; i ++ ) { // 我们在哪? int x = i*videoScale; int y = j*videoScale; // 在像素数组内寻找合适的颜色 color c = video.pixels[i + j*video.width]; // 显示字符串中的各个字母来替代矩形 textFont(f); fill(c); // 文本内的一个字母在像素位置显示并被染色. // 一个计数器变量—— "charcount"被用来每次触发一个字符串内的字母. text(chars.charAt(charcount),x,y); // 继续下一个字母 charcount = (charcount + 1) % chars.length(); } } }
21. 让字母根据亮度调整大小,你只需加入以下几行(如果你不想要彩色而只要黑白,那么将关于颜色的几行注释掉即可),不过记得使用createFont()函数创建字体,因为这样创建的字体不会因为缩放而失真:
float b = brightness(video.pixels[i + j*video.width]); float fontSize = 36 * (b / 255); textSize(fontSize);
虽然能运行成功,但我却持续的得到错误提示:“Thu Dec 3 23:53:34 xxx.local java[431]
22. 文本同样可以实现位移与旋转:
PFont f; String message = "这段文本正在旋转"; float theta; void setup() { size(200,200); f = createFont("Arial", 20, true); } void draw() { background(255); fill(0); textFont(f); // 设置字体 translate(width/2,height/2); // 由中点旋转 rotate(theta); // 以theta旋转 textAlign(CENTER) ; // 在平移和旋转后这段文本居中对齐并显示在(0,0). // 如果你忘了平移和旋转,返回去复习一下吧. text(message,0,0); // 增加旋转(加 为顺时针) theta += 0.05; }
23. 是的,如上例可见,p5支持并可正确显示中文,但是它并不允许你在程序中直接输入中文,没事,在文本编辑器里写好再把它们粘贴进去就好了~
24. 如果你要新起一行,可以这么写:
String message = "这是第一行\n这是第二行";
“\”意思是"新的一行"。在Java中,不可见字母可以与一个“转义序列(escape sequence)”一起北河并到一个字符串内——一个斜杠"\"伴随一个字母。以下是一些基本用法:
\n—新的一行
\r—回车
\t—tab
\’—单引号
\”—双引号
\\—斜杠
25. 在一些制图软件中,要求一串文本中每个字符都得独立渲染。在p5中,这样的解决方式是循环一个字符串,每次显示一个字符。
// 第一个字母在位于10个像素的水平位置 int x = 10; for (int i = 0; i < message.length(); i++ ) { // 使用charAt()函数使每次显示一个字符 text(message.charAt(i), x, height/2); // 每个字符间间隔10个像素 x += 10; }
26. 呼叫每个字符的text()函数给了我们之后的例子更多的发挥空间(染色,设置大小,在一个字符串内单独放置字符)。10个像素的间隔不一定准确,因为不一定每个字符的宽度都是10。准确的间隔在下例中由textWidth()实现。注意它是如何在随机字符大小的情况下仍然保持合适的间隔的:
PFont f; String message = "oNEwAY是一支来自昆明的后摇滚乐队"; void setup() { size(500,200); f = createFont( " Arial " ,12,true); } void draw() { background(255); fill(0); textFont(f,12); int x = 10; for (int i = 0; i < message.length(); i++ ) { textSize(random(12,36)); text(message.charAt(i), x, height/2); x += textWidth(message.charAt(i)); } }
我注意到如果让上边这段代码一直跑的话,最后会停止(因为出错了..),解决办法是,要么你加个noLoop();,要么你把它从draw()里边给整出来:
PFont f; String message = "oNEwAY是一支来自昆明的后摇滚乐队"; size(500,200); f = createFont( " Arial " ,12,true); background(255); fill(0); textFont(f,12); int x = 10; for (int i = 0; i < message.length(); i++ ) { textSize(random(12,36)); text(message.charAt(i), x, height/2); x += textWidth(message.charAt(i)); }
然后我发现在这样的情形下,使用"\n"换行的方式是没用的。
27. 用自适应间隔重做第一个字母马赛克的例子:
import processing.video.*; // 缩放比例 int videoScale = 16; // 行数和列数 int cols, rows; // capture对象 Capture video; // 一个字符串和字体 String chars = "oNEwAYisapostrockbandfromkunmingyunnanchina"; PFont f; void setup() { size(640, 480); //建立行和列 cols = width/videoScale; rows = height/videoScale; video = new Capture(this,cols,rows,15); // 载入字体 f = createFont("Arial",18,true); } void draw(){ // 从摄像头读取图像 if (video.available()) { video.read(); } video.loadPixels(); //image(video,0,0,width,height); background(0); // 使用一个计数器 int charcount = 0; // 开始循环行 for ( int j = 0; j < rows;j++) { // 现在开始循环列, 而不是逐一循环像素 // 我们基于字符宽度使用一个浮点变量x来进行移动 float x = 0; while (x < width) { // 我们垂直位置在哪? int y = j*videoScale; // 我们水平位置在哪? 转化到整数, 缩小, 并确保我们始终位于萤幕内 int pix = constrain((int) (x / videoScale),0,cols-1); // 在像素数组内选取正确的颜色 color c = video.pixels[pix+j*video.width]; // 显示字符串中的独立字符 // 替代矩形 textFont(f); fill(c); char ch = chars.charAt(charcount); text(ch,x,y); // 继续下一个字符, 到最末的时候重新从0开始循环 charcount = (charcount + 1) % chars.length(); // 依据字符宽度移动x x += textWidth(ch); } } }
28. 这种方法同样可以用于各个字符独立运动的案例。下边这个例子使用面向对象得方式制作,让字符串中的每一个字符成为一个对象,使它们既能在适当的位置显示又能在萤幕内独立运动:
PFont f; String message = "oNEwAY是一支来自云南昆明的后摇滚乐队"; // 字母数组 Letter[] letters; void setup() { size(460,200); // 载入字体 f = createFont("Arial", 20, true); textFont(f); // 创建与字符串同样大小的数组 letters = new Letter[message.length()]; // 在正确的x位置初始化字母 int x = 66; for (int i = 0; i < message.length(); i ++ ) { // 字符对象依它们在字符串中的位置及显示初始化 letters[i] = new Letter(x,100,message.charAt(i)); x += textWidth(message.charAt(i)); } } void draw() { background(255); for (int i = 0; i < letters.length; i ++ ) { // 显示全部字符 letters[i].display(); // 如果鼠标按下,字符开始抖动 // 如果放开,它们回归原位 if (mousePressed) { letters[i].shake(); } else { letters[i].home(); } } } // 一个描述单独字符的类 class Letter { char letter; // 对象知道它最初的位置 float homex,homey; // 以及它现在的位置 float x,y; Letter(float x_, float y_, char letter_) { homex = x = x_; homey = y = y_; letter = letter_; } // 显示字符 void display() { fill(0); textAlign(LEFT); text(letter,x,y); } // 随机移动字符 void shake() { x += random(-2,2); y += random(-2,2); } // 在任何一点, 通过呼叫home()函数便可将位置从当前重置到最初。 void home() { x = homex; y = homey; } }
29. 这种方法还允许我们将文字附于一个曲线上。在这么做之前,我们先看看如何将一系列矩形附于一个曲线上:
PFont f; // 圆形的半径 float r = 100; // 矩形的长、宽 float w = 40; float h = 40; void setup() { size(320,320); smooth(); } void draw() { background(255); // 从中心开始并画圆 translate(width/2, height/2); noFill(); stroke(0); // 这个圆就是我们的曲线 ellipse(0, 0, r*2, r*2); // 10个矩形围绕曲线 int totalBoxes = 10; // 我们必须保持记录围绕曲线的位置 float arclength = 0; // 为每个矩形 for (int i = 0; i < totalBoxes; i ++ ) { // 每个盒子都居中,因此我们将它们移动半个身位 arclength += w/2; // 角的弧度等于弧长除以半径 float theta = arclength / r; pushMatrix(); // 极坐标到笛卡尔坐标的转换 translate(r*cos(theta) , r*sin(theta)); // 旋转矩形 rotate(theta); // 显示矩形 fill(0, 100); rectMode(CENTER); rect(0, 0, w, h); popMatrix(); // 再次移动半个身位 arclength += w/2; } }
30. 尽管你觉得这个例子的数学可能有点难,但它的运行结果已经向我们揭示了下一步。我们需要做的只是用字符来填入各个矩形。并且因为每个字符并非同等宽度,所以相对"w"这个常量,我们需要使用一个变量,这个宽度由textWidth()函数得到:
// 被显示的文本 String message = "围绕曲线的汉字们"; PFont f; // 圆半径 float r = 100; void setup() { size(320,320); f = createFont("Georgia",40,true); textFont(f); // 文本必须居中! textAlign(CENTER); smooth(); } void draw() { background(255); // 从中心开始并绘制圆形 translate(width/2, height/2); noFill(); stroke(0); ellipse(0, 0, r*2, r*2); // 保存位置 float arclength = 0; // 为每个字 for (int i = 0; i < message.length(); i ++ ) { // 字符及其宽度 char currentChar = message.charAt(i); // 我们查询每个字符的宽度来替代固定的宽度. float w = textWidth(currentChar); // 每个字都是居中对齐的,所以我们移动半个身位 arclength += w/2; // 角的弧度等于弧长除以半径 // 通过增加PI由圆形左边开始 float theta = PI + arclength / r; pushMatrix(); // 极坐标到笛卡尔坐标的转换允许我们找到沿着曲线的点. translate(r*cos(theta), r*sin(theta)); // 旋转文字 (抵消旋转90度) rotate(theta + PI/2); // 显示文字 fill(0); text(currentChar,0,0); popMatrix(); // 再次移动半个身位 arclength += w/2; } }
31. 下边是书里的练习,让随机位置的文本归位,然后继续上边那个例子,鼠标点击,抖动,放开,归位。还要用到一些新函数,哦也,我不会做,直接看答案,慢慢学习吧。。。:
PFont f; String message = "呼啦啦啊呼啦啦,随机文字们各归其位!"; // 文字对象数组 Letter[] letters; void setup() { size(400, 200); // 载入字体 f = createFont("Georgia",20,true); textFont(f); // 以字符串大小创建数组 letters = new Letter[message.length()]; // 在正确的x位置初始化数组 int x = 30; for (int i = 0; i < message.length(); i++) { letters[i] = new Letter(x,height/2,message.charAt(i)); x += textWidth(message.charAt(i)); } } void draw() { background(255); for (int i = 0; i < letters.length; i++) { // Display all letters letters[i].display(); // If the mouse is pressed the letters shake // If not, they return to their original location if (mousePressed) { letters[i].shake(); } else { letters[i].home(); } } } // A class to describe a single Letter class Letter { char letter; // The object knows its original "home" location float homex,homey; // As well as its current location float x,y; // And an angle of rotation float theta; Letter (float x_, float y_, char letter_) { homex = x = x_; homey = y = y_; x = random(width); y = random(height); theta = random(TWO_PI); letter = letter_; } // Display the letter void display() { fill(0); textAlign(LEFT); // 用户位移和旋转来绘制文字 pushMatrix(); translate(x,y); rotate(theta); text(letter,0,0); popMatrix(); } // 随机移动文字 void shake() { x += random(-2,2); y += random(-2,2); theta += random(-0.5,0.5); } // 用lerp让文字归位! void home() { x = lerp(x,homex,0.05); y = lerp(y,homey,0.05); theta = lerp(theta,0,0.05); } }
lerp()——依一个特殊的增量计算两个数字间的一个数字。参数amt是两个值间的篡改(interpolate)总数,比如,0.0等于第一个值,0.1非常靠近第一个值,0.5在它们之间,等等。lerp函数便于创建直线运动以及绘制点状线。更多。
是的,貌似比较难理解,但我想了个笨办法,列印lerp出来的值,然后慢慢去琢磨它的用法。还是挺管用的~
这个博客不错,我也在学习processing,希望多交流。请问你是吧Daniel的书翻译过了吗?
边看边把重要的记录下来并发布到这里
你很强,我才看到第四章,很多概念理解得还不够
花时间的事情而已,学习并不能证明强弱,至少我现在还做不出什么让人耳目一新的东西来。。。一起加油吧~
我想知道怎么显示中文,还有我在网上看到一个小作品,它的字符串似乎不是用String message =..,而似乎是连到外面的文本文档了,StrArray = loadStrings(“affirmations.txt”);
请麻烦解释解释
@Blackburn,
1. 显示中文没问题,只是在p5环境中不支持中文输入到是真的,因此你只能将中文粘贴进代码中。
2. 关于读取外部txt文档,请看下一课。
感谢你的提醒,我已经用creatFont显示出了中文,但是我还是不太明白这个函数,所谓创造字体到底创造在哪里了,还是只是个名称而已,因为我再随意改动这个自造字体的名称,它依然可以显示出打好的中文,是不是也不需要data里的字体文件呢,另外似乎只能显示宋体的样子,这是否可以改呢;第二个问题就是读取外部文档,在代码中我依然用creatFont可是这时候连进来的中文显示的都是一个个小方块,这是怎么回事,当然,如果是loadFont就跟本不显示中文了
@Blackburn,
creatFont()用与字体创建工具同样的方式创建一个位图版本的字体。它依名字载入一个字体,并基于大小创建一系列的图像。在可能的情况下,函数text()将使用本地字体而不是创建的本地位图字体。例如,当使用默认渲染设置(JAVA2D)时,将使用本地字体,以提升绘图质量和性能。使用P2D,P3D和OPENGL渲染设置时,将使用位图版本。
PFont.list()可以查看所有可用的字体。理论上来说,能被列印出来的字体都是可用的。但由于Java的局限性,有时并非所有字体都可用,或者某些系统可用而其他系统却不可用。
loadStrings()创建的是一个字符串数组(每行为一个字符串形式的元素),用它读取txt文档后,用join()整合这个数组为一个长字符串后再输出看结果。
例如:
String[] lines = loadStrings(“text.txt”);
String onelongstring = join (lines, “”);
text(onelongstring,x,y);
我还是不会整合,因为我分不清你的例子哪个是函数哪个是名称,orz另外我不只要连接外部文本,还要根据外部文本的行数将字体随机显示在屏幕上,如果你有q的话希望可以把源文件传给你再看
@blackburn,
学到现在,你应该知道函数在P5内是以棕色高亮显示的,如果连这点都还不知道,那还是建议你从基础学起,不要急于求成。
您好,“习题17-6,做一个接头接尾持续滚动的文本条。” 您现在有进展么?我想了好久想不出来,能请教一下吗?
@四事一, 你好,这题我问过Daniel之后,他已经解答,看这里:http://www.learningprocessing.com/exercises/chapter-17/exercise-17-6/
请问文字沿弧形滚动应该怎么做啊T T
請問球碰到後要變小的程式如何寫?