冒险三:自动建造
在Minecraft中建造是很容易的。几乎所有你能想到的东西都可以造出来,只受限于Minecraft世界的大小和你的想象里。你可以建造房屋、城堡、底下水路、带泳池的多层宾馆,甚至是整个城镇!但是用不同的方块来建造复杂物体需要大量的时间和辛苦的工作,特别是有很多重复的情况下。要是能把某些建造任务自动化就好了。那对你的朋友们来说是不是就像魔法一样神奇?
Python这类编程语言是自动化复杂任务的理想工具。在这个冒险任务里,你可以学到在Minecraft世界里自动组合大量方块的魔法,然后循环使用这些指令来建造宏伟的、具有重复结构的建筑,例如一整条街的房子(如图3-1)。你的朋友在穿过一排排的房屋时,一定会为你的作品感到震惊,同时也会好奇你是怎么造出如此复杂的结构!你也会学到在程序首次运行时如何从键盘读取数字,这样用户就可以在不用修改代码的情况下改变你程序的行为!
创建方块
Minecraft世界中的每一种方块都有自己的数字代码。如果你是Minecraft的老玩家,你可能已经知道一些方块类型的代码。不过,Minecraft API让你可以通过名字而不是代码来引用方块类型,这让问题简单了很多。这里的名字就是一个记录了方块类型代码的常量(在冒险任务2里已经见过常量了)。附录B里列出了最常见的方块类型以及他们的名字和代码。
Minecraft世界被分割成一个个不可见的立方体(每个都有自己唯一的坐标),计算机记录了某个时刻每个立方体里的方块类型的代码,即使只是一个AIR(空气)类型的方块。作为Minecraft程序员,你可以通过Minecraft API来询问任意坐标位置的方块类型,也可以改变任意坐标位置的方块类型。这会改变显示在那里的方块。举个例子,你可以把AIR(空气)类型的方块变成STONE(石头)来建造一堵墙,在冒险任务4里你马上就需要这么做。
你现在要编写一个简单的程序来演示这一点,通过在角色眼前放置一个方块。学会了放置一个方块,就可以在Minecraft世界里建造任何你能想象的东西,自动的!
启动Minecraft,IDLE,如果你使用PC或者Mac,也要启动服务器。你应该已经练习过如何启动所有程序,如果需要提示请参考冒险任务1。
打开Python集成开发环境IDLE。这是你进入Python编程语言的窗口,也是你输入和运行所有Python程序的地方。
在菜单点击File->New File新建程序,点击File->Save As保存文件,命名为block.py。记住程序要保存在MyAdventures文件夹,不然不能正常工作。
现在,导入冒险任务需要的模块。你需要使用一个新的block模块,其中有Minecraft中的所有方块类型的代码常量。输入下面代码:
import mcpi.minecraft as minecraft import mcpi.block as block
输入下面代码连接Minecraft游戏:
mc = minecraft.Minecraft.create()
把角色的位置赋值给
pos
变量。你会用这个位置来计算角色面前一个位置的座标,也就是要创建方块的地方:pos = mc.player.getTilePos()
现在在角色的前方创建一个方块,用相对于角色位置的座标。你可以在这一节之后读到所有关于相对座标的知识。在程序中使用
pos.x+3
可以保证石头方块不会出现在角色的正上方!输入以下代码:mc.setBlock(pos.x+3, pos.y, pos.z, block.STONE.id)
点击File->Save保存程序,然后点击Run->Run Module运行程序。
你现在应该能看到一个石头方块在游戏角色的附近!你刚刚用编程的方法在Minecraft世界里创建了一个方块。从这些简单的例子开始,你将可以通过Minecraft编程来自动创建一些非常有趣的结构。
挑战
石头并不是一个有趣的方块。参考附录B然后试着把程序中的STONE换成其他方块类型。有些方块类型不是所有平台都支持的,所以附录B里列出了支持每种方块的平台类型。举个例子,GLOWING_OBSIDIAN在树莓派版上可以用,在PC/苹果电脑上就不行。有些方块会受重力影响,试试WATER(水)和SAND(沙子),会有一些有趣的效果。
定义
相对座标是指以其他点(在这里就是你的角色)为基准的一组座标。比如,
pos.x
、pos.y+10
、pos.z+3
就是Minecraft世界里角色上方10个方块、南方3个方块的位置:换句话说,这个座标是相对于你角色位置的。随着角色在Minecraft世界里的移动,相对座标的位置也会变化。绝对座标使用固定的数字表示某一点位置(在这里就是某个方块)的的一组坐标。坐标
x=10
、y=10
、z=15
是一个绝对坐标的例子。每次用到这个坐标,都表示Minecraft世界里10、10、15位置处的方块,这个位置是始终不变的。挑战
如果你已经完成了冒险任务2,打开并运行程序whereAmI.py,走到Minecraft世界中的某个有空闲空间的地方。记下角色当前位置的x、y、z坐标。修改程序block.py,用绝对坐标调用
setBlock()
,然后运行程序看会发生什么。想想看,绝对坐标和相对坐标在你的程序中都有些什么用处?
建造更多方块
以这个起点为基础,你可以建造任何东西!现在你可以对程序进行扩展来建造更多方块。在用Minecraft方块进行建造的时候,唯一需要记住的是,你需要用一些简单的数学运算来算出你想要建造方块的坐标。
现在扩展你的block.py程序来在角色身前建造另外5个方块,每个要建造的方块调用一次setBlock()
。将要编写的这个程序会建造像骰子上的点一样的结构。请按照一下步骤进行。
- 在编辑器菜单选择File->Save As,保存程序为dice.py。
在程序末尾添加下面几行:
mc.setBlock(pos.x+3, pos.y+2, pos.z, block.STONE.id) mc.setBlock(pos.x+3, pos.y+4, pos.z, block.STONE.id) mc.setBlock(pos.x+3, pos.y, pos.z+4, block.STONE.id) mc.setBlock(pos.x+3, pos.y+2, pos.z+4, block.STONE.id) mc.setBlock(pos.x+3, pos.y+4, pos.z+4, block.STONE.id)
保存并运行程序,你会看到像色子上六个点一样的简单结构,就在角色身前!(见图3-2)
挑战
修改dice.py程序,在石头点附近的空间填充上白色的方块,这样看起来更像色子的一面。尝试把不同的方块设置成STONE(石头)或者AIR(空气)来创造色子上的不同点数。记住附录B中有各种方块信息的详细列表。
使用for循环
能建造单个方块就能假造任何你想要的东西,但是这有点儿像在电脑屏幕上用手画出复杂的图片,每次只画一个点。你还需要某些重复放置方块的方法,这样才能在不增加程序长度的情况下建造更大规模的结构。
用for循环建造多个方块
幸运的是,Python和其他编程语言一样,有一种叫做“循环”的功能。你在冒险任务2里已经接触过while True
循环了,也就是游戏循环。循环就是在Python这类编程语言中将一件事情重复多次的方式。这里要用的循环称作for
循环,有时也称作计数循环,因为它会执行固定的次数。
定义
for
循环又称作计数循环,或者计数控制循环,因为它会重复一定的次数。你在range()
语句里决定循环的次数。Python里的for
循环比较特别,因为它可以对数字意外的东西进行计数,你会在冒险任务6里见到它的更高级用法。
就像在冒险任务2中构成游戏循环的while
循环一样,for
循环中的程序语句必须缩进,这样Python语言才知道只有这些语句需要在每次循环的时候重复执行。
为了帮助理解,请在Python Shell里按照下面步骤实验一下for
循环:
- 点击Python Shell窗口,最后一个>>>提示符右边的位置。
输入下面代码并按回车键:
for a in range(10):
Python现在不会执行任何操作,因为它还在等待属于
for
循环的程序语句(通常称作循环体)。现在输入一个
print()
语句。Python Shell自动为你缩进了下一行代码,所以Python知道print()
语句属于for
循环。print(a)
按两次回车键。第一次表示
print()
语句结束;第二次则告诉Python Shell循环结束。
你现在应该看到数字0到9显示在Python Shell窗口里。
深入代码
你刚才用到的for
循环有一些有趣的特性:
for a in range(10):
print(a)
在这里a
称作循环控制变量(或者“循环计数器”)。意思是这个变量用来保存循环执行的次数。第一次循环的时候,它的值是0,第二次是1,以此类推。
语句range(10)
产生一个0到9的数字序列,for
循环会通过它来计数。
行尾的:(冒号)作用跟之前用到的while
循环和if
语句中一样。冒号标记了循环体开始的位置。在这个例子里,循环体就是需要被重复执行10次的代码。
循环里的任何Python语句(换句话说,就是for
语句之后缩进了一级的语句)都可以使用变量a,并且每次循环执行时a
的值都不同。
循环体中的任意代码都可以随意使用循环控制变量a
。你不一定要使用a
这个变量名。你可以给它起任意你想要的名字,并且,给变量起一个能够表示其含义的名字是个很好的习惯,这样其他读你程序的人会更容易理解。在这个例子里一个比a
更好的名字是count
(计数)或者number_of_times
(次数)。
用for
循环建造高塔
在Minecraft中造一座巨大的高塔怎么样?学会了for
循环之后,你完全可以做到!只要按照下面步骤:
- 在IDLE菜单选择File->New File新建一个程序。点击菜单中File->Save保存,命名为tower.py。
像以前一样,导入需要的模块:
import mcpi.minecraft as minecraft import mcpi.block as block
连接Minecraft游戏:
mc = minecraft.Minecraft.create()
把塔造在你的角色所在的位置会比较容易找到,所以第一件事是要获得角色的坐标,输入下面代码:
pos = mc.player.getTilePos()
你的塔会有50个方块那么高,所以要用一个执行50次的
for
循环。不要忘记行尾的冒号:for a in range(50):
下一行代码需要缩进,因为它属于
for
循环的循环体。Minecraft世界中的y坐标表示高度,所以把循环控制变量a
加到角色所在位置的y坐标上。这告诉Python在建造过程中每次循环都要增加高度:mc.setBlock(pos.x+3, pos.y+a, pos.z, block.STONE.id)
保存并运行程序。成功了吗?
for
循环应该在角色身边创造了一个巨大的塔,类似图3-3这样。数数看,是不是有50个方块的高度?
在前面的代码里,for
循环执行了50次,每次循环都会执行for
语句下面缩进的代码,并且变量a
的值每次都比前一次要大1。代码把变量a
(循环控制变量)的值加到变量pos.y
(角色所在位置的高度)。这就建造了一个50个方块高度的塔。
挑战
修改
for
循环里的range()
语句可以创建更高的塔。试试要造多高的塔才能高不见顶。
清理空间
有时候,在Minecraft世界里寻找足够大的空间来建造也比较头疼。角色的周围通常环绕这很多树木和山脉,没有足够的空间来建造大型建筑。你可以写个清理空间的程序来解决这个问题。
使用setBlocks
提高建造速度
Minecraft世界中所有类型的方块都有一个编号,这也包括空方块,编号是block.AIR.id
。所以,只要把一大片区域里的每一个方块都设置成block.AIR.id
,就可以清理出空间用于建造其他建筑。附录B中列出了最常用的方块编号(就是之前讨论过的方块类型代码)。例如,block.AIR.id
是0,block.STONE.id
是1。记住,你在Minecraft世界里看到的空间其实只是一个编号为block.AIR.id
的方块——1立方米由空气填充的方块!
计算机是很快的,但是在for
语句循环体里的语句越多,程序运行就越慢。Minecraft游戏解释你设置方块的请求,改变方块类型,然后更新屏幕上的现实,这需要一些时间。你当然可以写一个循环很多次的程序把周围成百上千的方块设置成block.AIR.id
来清理空间,这也可以得到想要的结果,但是会很慢。幸运的是,有一种方法可以一次设置很多个方块,就是setBlocks()
。(注意名字后面多出来的字母s。)setBlock()
只设置一个方块,而setBlocks()
一次设置一整个立体空间。
Minecraft编程接口提供了setBlocks()
语句,可以把整个立方体空间里的所有方块设置成同样的类型。因为整个过程只需要请求Minecraft游戏一次,Minecraft可以优化执行过程,也就是说运行起来会比之前用for
循环setBlock()
要更快。
setBlocks()
作用于3D空间,它需要两组座标——立方体两个对角的座标。每个座标有3个数字,所以你需要6个数字来描述Minecraft里的立体空间。
现在你要编写一个很有用的工具程序,任何时候你都可以用它来清理周围的空间进行建造:
- 在IDLE菜单中选择File->New File新建程序。
- 选择File->Save As保存程序为clearSpace.py。
导入需要的模块:
import mcpi.minecraft as minecraft import mcpi.block as block
连接Minecraft游戏
mc = minecraft.Minecraft.create()
想清理角色附近的空间,需要使用相对座标。首先获得角色的位置
pos = mc.player.getTilePos()
现在清空一个体积为50 50 50方块的空间。空间左下角的座标是角色的位置,右上角的座标是x、y、z轴各距离50个方块的位置。
mc.setBlocks(pos.x, pos.y, pos.z, pos.x+50, pos.y+50, pos.z+50, block.AIR.id)
保存程序。
这个程序神奇的地方在于,你可以在任何需要的时候走到Minecraft世界里的任何位置然后清空一块50x50x50的空间。只要在菜单中选择Run->Run Module运行这个程序,周围的树木、山峰以及所有其他东西都会神奇的从眼前消失。
试着在Minecraft里换个位置,重新运行这个程序来清理周围的大片空间。
从键盘读取输入
对clearSpace.py的另一项有用的改进是让它可以很轻松的改变清理空间的大小。这样的话,如果只需要建造一个很小的东西,就可以只清理一小块空间;而如果打算建造很多大型建筑,就可以先清理好一大块空间。
要做到这样,首先你需要使用一个新的Python语句raw_input()
来从键盘读取一个数字。你可以走到Minecraft世界的任何位置,运行程序,然后只需要输入一个数字来表示你想要清理的空间的大小,程序就会帮你清理这块空间。这样你就不需要在清理不同大小的空间时去修改程序了。
Python有两个主要版本——Python 2和Python 3。Minecraft编程接口是专门为Python 2开发的,本书的所有冒险任务也都会使用Python 2。用Python 3编写Minecraft程序也是可以的,不过需要一个新版本的mcpi模块。如果你在网上看过有些Python 3程序,他们会使用input()
而不是raw_input()
来读取键盘输入。在写作这本书的时候,Python最新版本的准确版本号是2.7.6和3.3.3。Python 2和Python 3之间有一些显著的差别,所以在这本书里最好一直使用Python 2。
理解了raw_input()
的作用过之后,就要把它加到你的程序里去。只需要稍微修改clearSpace.py程序中两个地方就可以了。按照下面步骤进行:
首先,用
raw_input()
请求用户输入一个表示空间大小数字,把这个数字保存在size
变量里。就像在冒险任务里用str()
把其他类型转换成字符串一样,这里使用int()
把raw_input()
输入的字符串转换成可以计算的数字。你可以试试不加int()
会发生什么!把下面粗体字的一行代码加入程序中:pos = mc.player.getTilePos() size = int(raw_input("size of area to clear? "))
然后修改使用
setBlocks()
的那一行代码,使用新的变量size
取代数字50:mc.setBlocks(pos.x, pos.y, pos.z, pos.x+size, pos.y+size, pos.z+size, block.AIR.id)
保存程序,在菜单中选择Run->Run Module运行程序。
在Minecraft里移动到一个有很多山和树的地方,在Python Shell窗口出现提示后输入一个数字——比如100——然后按下回车键。你身边的所有山和树都会消失,你会像图3-4一样拥有一大块空地用来建造。
保存好这个工具程序,在以后你需要在Minecraft里清理空间的时候会很有用。
挑战
clearSpace.py程序清除以角色座标为左下角的立方体空间。但是,究竟是马上就能看到清理出来的空间,还是转身之后才能看见要取决于角色面朝的方向。如果清理出来空间是以角色位置为中心就方便多了。只需要修改
setBlocsks()
语句,改变相对坐标的计算方式就可以实现。有时候还需要减小y坐标让clearSpace.py程序能够向下挖出一些空间,用来建立地基、草坪或者其他地面方块。试试看能不能解决这些问题。有了这些功能,这个工具程序会更实用。
建造家园
玩Minecraft生存模式的时候,首先要做的事情之一就是给角色建造一个避难所来躲避夜间出没的怪物。要是只按一个按钮就能建造一所房子该多好!幸运的是,通过编程,你可以让复杂的任务变得像按一下按钮一样简单。
在clearSpace.py程序中,你已经学到如何只用一行程序语句就把很多方块变成同一类型。现在你要学习如何用同样的方式快速建造一所房子。
建造房子的一种方式是每一面墙使用一次setBlocks()
语句。这样做的话,就需要四条独立的setBlocks()
语句,还要算出每堵墙的角落坐标。幸运的是,有一种更快的方式可以建造中空的长方体结构:先建造一个巨大的长方体,然后用不同的坐标调用一次setBlocks()
把长方体内部的方块变成AIR(空气)类型。
在进行下一步之前,你需要花点时间搞清楚跟房子设计相关的数学计算,编写程序需要用这些知识来计算正确的坐标。图3-5是房子的设计草图,上面标明了建造时需要用到的所有重要坐标点。在建造复杂结构的时候,在纸上画出草图并计算出关键坐标点是很重要的。这所房子很特别,因为门廊和窗子的位置、尺寸都是程序计算出来的。稍后你会看到这一点的重要之处。
有了设计草图,就可以尝试按照下面步骤建造自己的房子:
- 在菜单中选择File->New File创建新程序。
- 选择Save->Save As保存程序,命名为buildHouse.py。
导入需要的模块:
import mcpi.minecraft as minecraft import mcpi.block as block
连接到Minecraft游戏:
mc = minecraft.Minecraft.create()
用一个常量作为房子的尺寸。在建造房屋的代码中会多次用到常量
SIZE
。在这里使用常量而不是直接使用数字20,会让以后修改房子尺寸容易很多。SIZE = 20
获得角色的位置以便在附近建造房子:
pos = mc.player.getTilePos()
把角色的x、y和z坐标保存在新变量中。这会让后面的程序语句更容易输入和阅读,如果以后你想对房子的设计进行一些巧妙的改动,也会更容易。把x坐标设置在距离角色坐标2个方块的位置,这样房子就不会正好建在角色身上了:
x = pos.x+2 y = pos.y z = pos.z
计算房子在x和y两个方向上的中间点,保存在
midx
和midy
两个变量中。这样以后计算窗子和门廊的坐标会更容易一点,如果要改变房子的大小,窗子和门廊的位置也会随之改变并保持在合适的位置。midx = x + SIZE/2 midy = y + SIZE/2
建造一个大的长方体作为房子的外墙。你可以使用任何类型的方块,不过建议使用圆石(COBBLESTONE),这会让房子有点历史感:
mc.setBlocks(x, y, z, x+SIZE, y+SIZE, z+SIZE, block.COBBLESTONE.id)
把房子内部空间填充上空气方块。这里使用设计草稿上的简单算数就可以算出房子内部空气部分相对于圆石外墙各个角落的相对坐标:
mc.setBlocks(x+1, y, z+1, x+SIZE-2, y+SIZE-1, z+SIZE-2, block.AIR.id)
用AIR类型的方块开辟一个门廊。这里不能使用Minecraft中普通的门,因为房子太大了。你需要创建的是一个3个方块高,两个方块宽的大门廊。门廊需要建在房子正面的中间,
midx
给出了x方向上中间点的坐标:mc.setBlocks(midx-1, y, z, midx+1, y+3, z, block.AIR.id)
用玻璃(GLASS)类型方块开辟两扇窗户。房子够大,窗子在房子正面距离外侧和中间点各3个方块的位置。如果改变SIZE常量的值重新运行程序,程序中的计算会自动调整门廊和窗子到合适的位置让它看起来还像一所房子:
mc.setBlocks(x+3, y+SIZE-3, z, midx-3, midy+3, z, block.GLASS.id) mc.setBlocks(midx+3, y+SIZE-3, z, x+SIZE-3, midy+3, z, block.GLASS.id)
添加木制屋顶:
mc.setBlocks(x, y+SIZE-1, z, x+SIZE, y+SIZE-1, z+SIZE, block.WOOD.id)
再加上羊毛地毯:
mc.setBlocks(x+1, y-1, z+1, x+SIZE-2, y-1, z+SIZE-2, block.WOOL.id, 14)
你会发现setBlocks()
语句的最后多了一个数字,这个数字指定了地毯的颜色;在这个例子里,数字是14,代表红色。这在后面“深入代码”一栏里会进行解释。
保存程序,然后在Minecraft世界里找一个有点空间的地方,从IDLE菜单选择Run->Run Module运行程序。你会看到自己的房子奇迹般的出现在眼前。走进去看看。抬头看看屋顶和窗外,惊叹自己如何迅速的凭空建造起这座房子。(图3-6)
不要忘记buildHouse.py程序总是在邻近角色位置的地方建造房子。在Minecraft世界里换个地方再运行一次buildHouse.py,就可以建造另一所房子。你可以在Minecraft世界的任何地方建造房子——是不是很酷?
深入代码
在使用setBlocks()
建造地毯时,括号里多了一个数字:
mc.setBlocks(x1, y1, z1, x2, y2, z2, block.WOOL.id, 14)
现在我们来看看它的含义是什么。
羊毛(WOOL)是一种很有意思的方块,因为它有“额外数据”。这表示不但可以设置方块为羊毛,还可以用额外数据来改变羊毛的样子。Minecraft里还有其他带有额外数据的方块,每一种使用额外数据的方式都不同。对于羊毛来说,额外数据决定了方块的颜色。这种特性使得羊毛成为了一种用途广泛的建造方块,因为你可以从Minecraft支持的调色板中选择任意一种颜色,这让你的设计变得多彩而真实。
在这个冒险任务的最后,你会看到一个羊毛方块支持的颜色和对应数字的列表。附录B里列出了所有带有附加数据的方块类型。
david says
如果你在网上看过一些其他的Minecraft程序,你也许会好奇为什么这本书里的方块都写成blocks.WOOL.id的形式。为什么方块类型的后面都要加上.id?为什么网上的其他程序不是这样?这是因为
setBlock()
和setBlocks()
在带有额外数据时可能不会正常工作,除非在方块类型的后面带上.id。所以,为了使保持一致,我们决定在所有使用方块的地方都带上.id,这样每次使用的时候,代码看起来都一样。你的代码中不一定总要带上.id,但是我们建议你这样做,这样就不需要记住什么时候需要带.id,什么时候不需要。
试试把程序里的SIZE
常量改成一个更大的数字,例如50,然后再运行程序。看看房子会发生什么变化?试试把SIZE
改成更小的数字比如10。看看又会发生什么?想想如果SIZE
里的数字非常小的时候会发生什么?为什么?
建造更多房子
建一座房子很有趣,但是为什么要止步于此呢?回忆一下在这个冒险之前的tower.py程序,写个for
循环让程序语句重复执行一定的次数是很容易的。所以,一定也有办法可以建造一整条街的房子,甚至整个小镇,只需要循环执行建造房子的程序很多次。
不过在开始之前,还是需要还是需要改进一下房子建造程序,让它不会变得过于庞大和复杂,不便管理。
使用Python函数
你以后可能会想建造很多设计迥异的房子来组成一个小镇。建造小镇的程序可能会很大很复杂,幸运的是Python编程语言有个功能可以帮助你把复杂的代码编程很多小段可以重复使用的代码片段。这被称作“函数”。
定义
Python的函数可以把相关的程序语句放在一起并取一个名字。以后只要用到这一组程序语句,就只需要使用函数的名字,后面跟上圆括号就可以了。
david says
你可能还没有意识到,但是在这本书里已经多次用到函数了,从你在第一个冒险任务里用
mc.postToChat("hello")
在聊天栏里显示一条信息就开始了。postToChat()
是一个函数,是一组你在程序里通过postToChat()
这个词就可以调用的相互关联的程序语句。
在修改buildHouse.py程序使用函数之前,最好在Python Shell里试验一下来加深对函数的理解:
- 点击选择Python Shell窗口。
把下面代码输入shell窗口,定义一个叫做
myname
的新函数:def myname():
def
的意思是“定义一个新函数,后面是函数的名字”。像之前的while
、if
和for
语句一样,你必须在这一行的最后加上冒号(:),这样Python才知道后面的需要组成函数体的程序语句。Python Shell会自动为你缩进下一行,让Python知道它是函数的一部分。输入一些
print()
语句,打印出你的名字和个人信息。输入的时候什么也没有发生千万不要感到惊讶,这是正常的。耐心点儿,稍后会有解释。print("my name is David") print("I am a computer programmer") print("I love Minecraft programming")
最后,按两次键盘回车键,Python Shell就会知道你已经输入完了需要缩进的语句。
现在输入函数名和括号让Python Shell运行这个函数:
myname()
多输入几次
myname()
看看会发生什么。
一开始可能有点奇怪,在Python Shell里输入了代码但是却没有执行。通常,在Python Shell里输入代码时,按下回车键之后就会有执行结果,为什么这次没有?因为这里做的事情有些不同:你“定义”了一个叫做myname
的新函数并且让Python记住这三个print
语句是属于这个函数的。Python已经把三个print
语句存储在计算机的内存里了,而不是直接运行。从现在开始,你只要输入myname()
,它就会执行已经存储的语句,这三行文字就会现实在屏幕上。
函数是非常强大的功能,可以让你用一个名字来表示任意多数量的Python程序语句,有点像迷你程序。要运行这些语句的时候只需要输入函数的名字。
让我们利用起这个功能,把建造房子的程序定义成一个函数。之后,只需要输入House()
,一座圆石房子就会自动建造起来。
- 为了不破坏已经正常工作的buildHouse.py程序,在编辑器菜单中选择File->Save保存代码到一个新文件,命名为buildHouse2.py。
在代码的最上方,
import
语句的后面增加一行,定义一个新函数house
:def house():
接下来修改这一行之后的所有语句,缩进一级。修改之后的程序应该像下面这样。注意缩进一定要正确:
import mcpi.minecraft as minecraft import mcpi.block as block mc = minecraft.Minecraft.create() SIZE = 20 def house(): midx = x + SIZE/2 midy = y + SIZE/2 mc.setBlocks(x, y, z, x+SIZE, y+SIZE, z+SIZE, block.COBBLESTONE.id) mc.setBlocks(x+1, y+1, z+1, x+SIZE-2, y+SIZE-2, z+SIZE-2, block.AIR.id) mc.setBlocks(x+3, y+SIZE-3, z, midx-3, midy+3, z, block.GLASS.id) mc.setBlocks(midx+3, y+SIZE-3, z, x+SIZE-3, midy-3, z, block.GLASS.id) mc.setBlocks(x, y+SIZE, z, x+SIZE, y+SIZE, z+SIZE, block.SLATE.id) mc.setBlocks(x+1, y+1, z+1, x+SIZE-1, y+1, z+SIZE-1, block.WOOL.id, 7) pos = mc.player.getTilePos() x = pos.x y = pos.y z = pos.z house()
注意代码的最后一行,只写了函数名
house()
。这一行代码会运行计算机内存中存储的,用def house():
定义的所有程序语句。- 保存程序,在Minecraft里走到一个新位置然后运行程序,应该会看到角色面前建起了一座房子。
david says
你或许在想,这有什么用呢?只是把一些代码加了缩进,做的事情还是完全一样的!很多情况下,在进行计算机编程的时候,首先要优化代码的布局和结构,然后才能在这个基础上做更有趣的事情。这里做的就是这样的事。稍微调整了一下代码的结构,就可以让重复使用建造房子的代码变得容易很多。
挑战
house()
函数建造的房子总是以存储在变量x、y和z中的坐标为基础的。在程序的后面增加几条语句来修改x、y和z的值,之后再添加一条house()
语句,看看会发生什么。你觉得用这种方式可以建造多少房子?
深入代码
在新的程序里,除了把setBlocks()
语句放在函数house()
里,还用到了一些其他的技巧。
变量x
、y
、z
和常量SIZE
被称作“全局”变量。之所以称作“全局”,是因为它们在主程序里被赋值。“全局”的含义是指,在程序的任何地方都可以使用它们,也包括house()
函数。所以,如果你做过试验,通过修改主程序里的x
、y
和z
来建造一些不同的房子,这是可以的,因为这些变量是全局的,可以在程序的任何部分被使用。
在冒险任务6里,你会了解到全局变量会让大型程序在出错时难以修复,有更好的方式可以和函数共享信息。不过,到目前为止,这样使用全局变量是没问题的,你的程序还很小,全局变量不会引起问题。
定义
全局变量是程序中任何地方都可以使用的变量。任何变量(包括常量)只要定义的时候左边没有缩进都是全局的,都可以在程序中任何位置访问。所以,如果使用
a = 1
并且左边没有缩进,这个变量就可以在程序其他任何地方使用。相反,在有缩进的情况下定义的变量都不是全局的(通常也称作“局部变量”)。所以,如果在
def
语句下缩进的代码块里(也就是在一个函数里)使用a = 1
,这个变量就只能在这个缩进代码块里面使用,其他任何地方都不能访问。这只是关于全局变量的一点皮毛,不过现在知道这些就足够了。
使用for
循环建造一整条街的房子
现在可以把这一章里学到的所有东西结合在一起,来建造一整条街的房子。如果你手动从物品栏里选择物品来建造这些房子,可能会花掉好几个小时,而且也可能偶然犯些错误,或者把房子简单偏大或者偏小。通过编程把房子和其他建筑的建造过程自动化,就可以加快建造速度,并且保证它们的大小都很精确!
要建造很多房子,你需要按照下面步骤在程序中添加for
循环:
- 为了不破坏已有的buildHouse2.py,从菜单中选择File->Save As把程序保存到名为buildStreet.py的新文件里。
在程序最后,
house()
语句的前面增加一个for
循环,并添加修改x坐标改变房子建造位置的语句。前后两座房子之间相距SIZE
个方块的距离。新加的两行代码用粗体表示:for h in range(5): house() x = x + SIZE
保存程序,在Minecraft世界里找一个有足够空间的地方,运行程序。如图3-7所示,你会得到5座房子连接而成的一条街!到每座房子里去看看,确认它们都建造正确。
挑战
怎样修改程序才能建造一座垂直的巨大塔楼,而不是一排房子呢?试试看。把建造塔楼的代码写成一个函数,然后用
for
循环在Minecraft里建造很多塔楼。david says
根据角色所处位置的不同,以及周边环境的不同,比如山和树木,有些房子可能会产生一些有趣的效果。可能会穿过一棵树,或者部分嵌入到山里。如果你的房子建在了海上,甚至可能产生沉降!你可能需要修改
house()
函数,在房子下方建造一层地基,来保证你的房子总是“脚踏实地”。
添加随机地毯
到这里,你可能已经在Minecraft世界里建起了很多房子,看起来很酷。只用了很少几行Python代码,就已经完成了大的建筑工程!
不过,你可能觉得一条街上的房子都是一模一样有点太无聊了。怎样才能把每座房子都变得不同,让这条街变得更有趣呢?
一个办法是写一些不同的house()
函数,像是cottage()
、townHouse()
甚至maisonette()
,并且修改程序在不同的位置使用不同的函数,使街上的建筑变得多样。
另一个让建筑变得有趣的方法是,修改建筑的某个部分,比如地毯,让程序每次运行时建造的结果都不一样。用这种方式,甚至连你,程序员本人,在逛完所有的房子之前都不知道自己建造的是什么。
产生随机数
计算机是很精确的机器。在现实生活的很多方面,我们都依赖于计算机的可预测性,在每次运行程序时都做同样的事。当你存10英镑到银行时,你希望计算机内存里你的余额也正好增加10英镑,每次都是如此,不会发生错误。所以随机的概念在这样一个精确的系统里看起来并不常见。
然而,有一个领域里随机性是很重要的,那就是游戏设计。如果游戏每次都做完全一样的事情,玩起来就太简单了——毫无趣味也毫无挑战性。几乎每一个电脑游戏都有某种程度的随机性,这让每次游戏都有些不同,使你保持兴趣。
幸运的是,Python编程语言自带了能够产生随机数的模块,你不需要自己写代码来实现,只要用内置的随机数生成器就好了。
为了能够更好的理解随机数,请在Python Shell里尝试下面操作:
点击Python Shell窗口让它置于顶层。
import random
导入
random
模块以使用随机数函数,输入以下代码:让程序生成一个1到100之间的随机数并打印到屏幕:
print(random.randint(1,100))
你应该看到1到100之间的某个数字显示在屏幕上。再次输入这个
print
语句。这次显示的又是哪个数字呢?现在,使用
for
循环打印很多随机数。注意第二行需要缩进,这样Python才知道print
语句是for
循环的一部分:for n in range(50): print(random.randint(1,100))
randint()
函数括号里的两个数字告诉它你需要的随机数的范围;1是可能产生的最小的数,100是最大的。
定义
随机数通常是从随机数序列——一组设计好的、没有明显模式和重复序列的数字——中产生的。
计算机是非常精确的机器,通常不会产生真正随机的数字;它们产生的是伪随机数。这些数字表面上是随机数序列的一部分,但其实是有固定模式的。你可以在这里读到更多关于随机数的知识,这里可以了解一些真正的随机数。
铺地毯
在这个冒险任务的前面,曾经在setBlock()
函数中使用了一个额外的数字让羊毛低碳的颜色变成红色,用WOOL类型方块的额外数据。WOOL方块的额外数据支持的数字范围是0~15——换句话说,你有16种颜色可以选择。修改建造房子的程序,产生一个随机数,并用这个数字作为房子里地毯的颜色:
- 在菜单中选择File->Save As把buildStreet.py保存为buildStreet2.py。
在代码最上方添加这个
import
语句,以便使用随机数生成器:import random
修改
house()
函数,每次使用的时候都生成一个随机的地毯颜色。WOOL方块的颜色范围是0到15,所以需要生成随机数范围也是0到15。把随机数保存在变量c中,这样建造地毯的那行代码就不会变得太长而难以阅读,确保这两行代码缩进正确,它们都是house()
函数的组成部分:c = random.randint(0, 15) mc.setBlocks(x+1, y+1, z+1, x+SIZE-1, y+1, z+SIZE-1, block.WOOL.id, c)
保存程序。
现在去Minecraft里找个有足够空间建房子的地方,然后运行程序。这会建造另一条街,不过这一次,当你走进房子的时候会发现每一座房子的地毯都是不一样的,颜色是随机的。这些房子看起来就像图3-8一样,不过你需要自己走进每一所房子看看它们的地毯是不是都不一样。
挑战
定义一个建造不同类型房子的
house2()
函数。在for
循环中使用随机数建造一到两座房子。这样你建造的没所房子都是随机的了。扩展一下这个程序,建造三种不同类型的房子。房子的类型越多,建造出的街道就会越有意思。你甚至可以尝试使用像y=-5
这样的负数坐标在房子里建造地下室或者游泳池。david says
大多数情况下,在Minecraft里能建造的大型建筑的类型只受限于你的想象力。当然,Minecraft世界的高度是有限的,这会限制建筑的最大高度。现实中的有些建筑有倾斜或者弯曲的结构。举个例子,看一下伦敦塔——目前欧盟最高的建筑——的设计,这栋宏伟的建筑也可以在Minecraft中建造出来,但是它的斜边会让建造比较困难。
耐心一点,从以长方体为主的简单结构开始。在冒险任务7中,Martin会教你写一些特殊的辅助函数,可以用来建造按角度倾斜的线条和曲线。在那之后,就没有什么困难能够阻止你在Minecraft世界里建造各种建筑了!
还有另一些问题可能限制在Minecraft中建造建筑的规模:计算机有多少内存,以及你能看到前方多远的距离。建筑规模越大需要的内存也越多。Minecraft世界也是有边界的。所以存储这个世界需要的内存数量是很实际的问题。其次,游戏中你只能看到一定距离内的东西,在树莓派版中更是如此,它的可视距离非常小。
本章中使用到的所有新的Python和Minecraft语句都可以在附录B的参考资料一节里查到。
Minecraft Wiki上有一个完整的方块类型代码列表。方块的附加数据值可以在这里找到。
羊毛(Wool)方块在建造时很有用,你在给房子铺设随机地毯的时候也许已经发现这一点了。下面列出了block.WOOL.id
方块可以使用的不同颜色。如果忘了怎样通过Minecraft API使用这些附加数据,可以复习一下buildStreet2.py程序。
更多的建造冒险
在这个冒险任务里,你学到了如何使用一条Python语句来创建单个方块或者一整个区域的方块。你已经建造一些很不错的建筑。你还学到了使用函数可以把程序代码分成更小的逻辑单元,以及使用for
循环可以重复做某件事情。有了这些知识,几乎可以建造所有你能想象到的结构!
- 使用这个冒险任务里学到的技巧,为色子的六个面各自写一个建造函数。写一个
for
循环按照随机次数转动色子,每次展示色子的一个面。使用random.randint()
函数,在遇到某个随机数时停止转动,你和你的朋友可以比赛看谁能猜中色子的点数。 - 列出其他一些你在建造街道是可以随机化的元素。也许可以走到外面的街上去看看不同的房子之间都有哪些区别。定义更多的函数,每一个用来建造一种类型的房子,然后尝试用更多不同风格的房子来建造一条更复杂的街道。
- 跟你的好朋友一起,把你们各自的程序组合成一个更大的程序。用它来建造一整个社区的房子。当你在这座新城镇里走动时,就会为多种多样的房子设计而感到惊讶。
下一个冒险任务
在冒险任务4里,你会学到如何感知方块并和它们交互,包括如何探测你脚下的方块类型,如何探测方块何时被角色击打(以及之后探测到之后做什么)。你会用所有这些新知识来设计一个寻宝游戏!