冒险二:追踪人物的运动
我们玩的Minecraft是别人设计好的游戏。尽管这个游戏世界很有趣,但是如果能按照我们自己的方式来玩,会更有趣一点。用Python进行Minecraft编程的时候,我们掌握着一个完整的编程环境,可以用编程来创造任何能想象到的东西。这个冒险任务里,我们会学到一些Minecraft和Python编程语言可以做的有趣的事情。
编程可以做到的事情之一,是在游戏中创造另一个游戏。Minecraft就是世界,我们的程序可以让这个世界按照一种新的方式运转。大多数Minecraft程序都包含三个有趣且让人激动的元素:感知这个世界,例如角色的位置;计算出一些新的东西,比如某种分数;以及做出某些行为,比如移动游戏角色到新的位置。
在这个冒险任务里,我们要学习如何感知角色的位置并随着角色的移动来触发不同的事情。在“欢迎回家”这个游戏里,我们要创造一个踏上去会跟你打招呼的魔法门垫。我们用Minecraft里的围栏来学习一种称作“地理围栏(geo-fencing)”的技术。你和你的朋友们需要用最快的速度来收集物品以完成游戏的挑战。最后,我们还会学习将游戏角色投射到空中,这样在游戏中如果玩家不能快速离开指定区域,就会被弹飞。
程序是你用某种编程语言编写的一组指令或者语句,计算机将会自动执行这些指令。必须使用编程语言中正确的指令。在本书中,我们会使用Python编程语言。
“语句”是一个通用计算机术语,通常是指给计算机的一个完整的指令,比如像print("Hello Steve")这样的一行计算机程序。有些人也用“命令”这个词,不过那通常是指在命令提示符后输入给电脑的命令。 Python对于什么是语句有自己的准确解释,不过对于这本书来讲,我会用语句这个词来表示Python程序中的一条指令或者一行。想了解更多Python的知识,可以在这里阅读在线文档。
感知角色的位置
在感知角色的位置之前,我们需要理解一下Minecraft的世界是如何组织的。Minecraft中每样东西的大小都是一个方块,方块是边长一米的立方体。游戏角色,也就是Steve,在Minecraft世界中移动的时候,游戏会用一组坐标来记录他的位置。游戏世界是通过在空方块里填充各种一立方米体积的材料而构建起来的。有一些情况例外,比如地毯和水、岩浆之类的液体看起来可能体积不足一立方米,但是也不能和其他材料放在一个方块,即使他们体积加起来也不到一立方米。
通过感知角色的位置,我们可以让游戏随着玩家的移动而作出智能的反应,比如在特定的位置显示某些信息,在移动过程中自动建造建筑,走到特定地点时自动改变角色的位置。这些在这本书里都可以学到,其中很多甚至在这个冒险任务里就可以学到!
坐标是一组表示唯一位置的数字。Minecraft使用3D坐标来表示三维世界中的准确位置,每组坐标包含三个数字。在这里了解更多关于坐标的知识,这里学习在Minecraft世界里如何使用坐标。
这些坐标被称作x、y和z。在Minecraft里提到坐标的时候需要注意;如果使用“左边”或者“右边”这样的词汇,是左还是右取决于你面朝那个方向。把Minecraft的坐标类比为指南针的方向会比较容易理解。图2-1展示了坐标x、y和z的改变跟运动方向的关系。
自己在游戏里试验一下会更容易理解角色移动的时候坐标是如何改变的。在树莓派上,当你移动角色时,x、y和z坐标会现实在屏幕左上角。在PC/苹果电脑上你可以按F3键来现实坐标。在这里你可以学到Minecraft的所有高级控制功能。
- 角色向东移动时,x变大,向西时变小。
- 在空中时y变大,深入地下时变小。
- 角色向南移动时,z变大,向北时变小。
开始
既然我们已经完成了冒险任务一里的“Hello Minecraft World”程序,是时候来编写自己的程序和Minecraft互动了!
冒险任务一详细解释了怎样让程序运行起来,随着冒险任务的进行我们会越来越熟悉这些步骤。从启动程序到开始输入代码需要的所有步骤都在冒险任务一里介绍了,不过在最初的几个冒险任务中,还是会给出步骤提示,方便你熟悉这些操作。
- 启动Minecraft,IDLE,如果在PC或者苹果电脑上运行,还要启动服务器。我们已经练习过如何启动这些程序,需要提示的话请复习冒险任务一。
- 打开Python集成开发环境IDLE,就像在冒险任务一里那样。这是我们进入Python编程语言的入口,也是输入和运行所有Python程序的地方。
显示角色的位置
理解坐标最好的办法是写一个试验性的小程序,这就是我们现在要做的。这个程序会显示游戏角色的3D坐标,并且让我们可以看到坐标随着角色的移动而改变。
在Python Shell里,选择File->New File新建一个程序。这会打开一个新的未命名窗口,是我们输入新程序的地方。
输入程序之前,先保存文件是一个好习惯,这样急需离开电脑的时候,就不用在压力之下想出一个文件名了。只需要选择保存即可。专业程序员总是用这种方法来保证不会丢掉自己的工作成果。在Python Shell菜单里,选择File->Save As,输入文件名whereAmI.py。请确保文件保存在MyAdventures文件夹里。
我们的程序将通过Minecraft API直接和Minecraft游戏进行通信。要调用这些API,首先要导入minecraft模块,它让你的程序能访问游戏的所有功能。用下面语句导入minecraft模块。
import mcpi.minecraft as minecraft
你需要一个连接来和Minecraft游戏进行通信。输入下面语句来连接到Minecraft游戏
mc = minecraft.Minecraft.create()
记住Python是大小写敏感的语言——所以请注意单词的大小写!大小写字母输入正确是很重要的。这里输入的第二个Minecraft必须以大写字母开头才能正确运行。
接下来,用
getTilePos()
来获得游戏角色的位置:pos = mc.player.getTilePos()
最后,显示角色位置的坐标。程序运行时,
print()
会在Python Shell里显示这些坐标。print(pos.x) print(pos.y) print(pos.z)
在编辑器菜单中选择File->Save保存文件。
在编辑器菜单中选择Run->Run Module运行程序。
现在应该能看到角色的坐标显示在Python Shell窗口中(图2-2)。在这个冒险任务的后半部分,你会用这些坐标来感知角色当前所处的位置,并建造一个神奇的门垫,当角色站在上面时,它会欢迎你回家。
API代表应用编程接口(Application Programming Interface)。API提供了一种从你的程序中安全的访问其他应用程序部分功能的方式。Minecraft API让你能够从自己的程序中访问游戏的功能。在你的程序通过API连接之前,Minecraft必须已经运行。代码行
import mcpi.minecraft as minecraft
里的mcpi代表Minecraft Pi,因为这个API最初的版本只能在树莓派上运行。接口是一组描述程序员如何访问计算机系统其他部分的规则。API是一组描述如何和一个运行中的程序——在这里就是Minecraft——进行交互的规则。所有的Minecraft程序都需要通过API来访问运行中的Minecraft游戏。
你在这本书里编写的所有Python程序都需要访问Minecraft API。为了正常工作,你的程序必须能访问mcpi.minecraft模块,它保存在MyAdventures文件夹里的mcpi里。保证这可以正常工作的最简单的办法就是在MyAdventures文件夹里创建Python程序。
深入代码
恭喜——你刚刚在自己的程序里使用了一个变量!pos.x
是一个变量,pos.y
和pos.z
也是。
变量是给计算机内存中某一位置起的名字。当你把变量的名字放在等号左边,就会在变量里存放一个数值,例如:
a = 10
如果在print
语句中使用变量,程序就会在Python Shell里显示显示这个变量的值。因此,如果a = 10,下面语句就会输出10,也就是a的值。
print(a)
你在pos = mc.player.getTilePos()
里使用的pos
变量是一种特殊类型的变量。把它想象成一个有三个隔层的盒子,隔层上分别贴着x、y和z的标签:
print(pos.x)
试试移动你的角色到另一个位置,然后再运行一次程序。查看Python Shell里显示的坐标:应该已经变成了角色新位置的坐标。任何时候你想要确认游戏世界中某个位置的坐标,都可以运行这段程序。
变量计算机内存中某个位置的名字。你的程序可以在任何时候向变量里存储新的数值,也可以把他们取回来进行显示或者计算。可以把它想象成计算器上的Memory按钮。
更清晰的位置显示
在继续做更多工作之前,你可以改进一下角色坐标的显示以便让它更容易理解。每次显示位置都输出3行会占用很多空间;把角色坐标的三部分全部放在一行会更好,像这样:
x=10, y=2, z=20
请按照以下步骤来做:
修改whereAmI.py程序,删除三个
print()
语句,替换成下面这一行:print("x="+str(pos.x) +" y="+str(pos.y) +" z="+str(pos.z))
在编辑器菜单中选择File->Save保存程序,然后选择Run->Run Module运行程序。看看输出效果——现在它们都在一行了!
深入代码
在新版本的whereAmI.py里,使用了一点额外的技巧,需要解释一下。
在Python里,print()
会打印括号里的任何东西,输出显示在Python Shell里。你已经使用过print()
的不同显示方式。现在你可以更深入的了解一下它们。看看这个例子:
print("hello")
像Hello Minecraft World程序一样,print()
打印了hello这个单词。单词两边的引号告诉Python你需要原原本本的显示hello这个单词。如果在引号中间放上空格,也同样会被显示。引号中的文本被乘坐字符串。
下面看另一种类型的print()
:
print(pos.x)
print()
显示pos.x
变量中的值。这是一个数字,但是根据存储在变量中的数值的情况,每次使用print()
打印的数字都有可能发生改变。
最后,你需要了解print()
语句中str()
的作用。print()
支持很多中不同的方式来组合数字和字符串。当你在Python中把字符串和数字混合在一起时,比如下面这样,你必须告诉Python把数字转换成字符串,这样它才能和其它要显示的字符连接在一起。str()
是Python内建的,它把括号里的任何变量或值转换成字符串。加号(+)告诉Python把"x= "
和pos.x
里的值连接起来组成一个字符串,之后会显示在Python Shell窗口中。
print("x= "+str(pos.x))
试试下面这一行没有str()
的代码,看看会发生什么。
print("x= "+pos.x)
字符串是用来存储一组字母、数字和符号的一种变量类型。把它想象成一条穿过珠子的细线,你可以像项链或手环一样在上面滑动,这就是它被称作字符串(string)的原因。每个念珠上面可以印着不同的字母、数字或符号。字符串按照你给的顺序保存它们。
字符串可以有很多不同的用法,比如保存你的名字、一个地址或者一条要在Minecraft聊天频道显示的消息。
用postToChat
改变坐标显示的位置
在whereAmI.py程序中,我们在Minecraft里进行游戏,但是坐标显示在Python Shell窗口中,这不太正常。用之前学到的技术可以很容易的解决这个问题,就是冒险任务1中的postToChat
!按照下面步骤来做:
把
print()
改成mc.postToChat()
,像这样:print("x="+str(pos.x) +" y="+str(pos.y) +" z="+str(pos.z)) mc.postToChat("x="+str(pos.x) +" y="+str(pos.y) + " z="+str(pos.z))
在菜单中点击File->Save保存程序,点击Run->Run Module再次运行程序。
干得漂亮!游戏角色的坐标现在应该出现在Minecraft聊天栏里了,这样在玩游戏的时候会更方便。
引入游戏循环
每次想知道角色位置的时候都去运行whereAmI.py程序并没有什么用处。幸运的是,修改程序添加一个游戏循环就可以解决这个问题。在这本书里我们要编写的其他所有程序几乎都需要一个游戏循环,这样才能持续运行并跟游戏过程进行交互。我们玩的大多数游戏都会持续运行直到被关闭,这是我们将要学到的一个很有用的技巧。在计算机程序里,这被称作无限循环(infinite loop),因为它会一直循环下去。
无限循环是永远不会结束的循环——一直持续运行到永远。唯一终止无限循环的方法是停止Python程序。你可以在Python Shell菜单中选择Shell->Restart Shell,或者在Python Shell窗口中同时按下键盘上的Ctrl + C来停止程序。
我们可以修改现有的程序来添加游戏循环;新程序跟现在的非常相似,这样可以节省一些时间。记住保存程序的时候使用一个新的文件名,否则就会丢掉旧的程序。
首先点击File->Save As把程序保存为一个名为whereAmI2.py的新文件。确保文件保存在MyAdventures文件夹里,否则将不能运行。
我们需要导入一个新的模块才能插入时间延迟。如果不像这样降低循环的速度,聊天栏汹涌而至的消息会让你头疼欲裂(bombarded),什么也看不清楚。把下面粗体字的一行添加到程序中:
import mcpi.minecraft as minecraft import time
接下来把下面以粗体字显示的行加入到程序的主体,注意你需要在while True下面的几行前面都加上缩进,这样Python才知道哪些指令需要重复执行。需要这段代码的原因见“深入代码”一节。
while True: time.sleep(1) pos = mc.player.getTilePos() mc.postToChat("x="+str(pos.x) +" y="+str(pos.y) + " z="+str(pos.z))
在编辑器菜单选择File->Save保存修改。
现在可以从编辑器菜单选择Run->Run Module来运行新程序。在Minecraft的世界里四处走走,注意,每过一秒钟,角色的坐标都会在聊天栏显示一次。
深入代码
缩进在所有编程语言里都非常重要,它展示了程序代码的结构,以及哪些语句属于同一组。对于Python编程语言来说,缩进尤其重要,因为Python会根据缩进来理解你的代码(这点不同于其他语言,比如C语言使用{}
来把语句组合在一起)。当你使用while
循环和其他语句时,缩进非常重要,因为它表示了哪些语句是属于这个循环,需要被重复执行的。在游戏循环里,while True:
下面所有的语句都进行了缩进,因为它们都属于这个循环,每次循环都需要重复执行这些语句。
在下面这个例子里,你会看到单词“hello”只会打印一次,而单次“tick”每秒都会打印一次。time.sleep()
和print()
前面有缩进,所以它们是属于循环内的。
print("hello")
while True:
time.sleep(1)
print("tick")
在Python里,缩进比其他语言里都要重要,因为缩进的次数(缩进层次)决定了哪些语句是属于哪个循环的。如果缩进错了,整个程序的含义也就变了。
缩进是程序代码中每一行最左侧的空白。缩进用来显示程序的结构,并且把语句组织在循环或其他语句的下面。在Python里,缩进同时也会改变程序的含义,所以很重要。
正确的缩进很重要。Python支持使用空格或者制表符(Tab)进行缩进,但是如果在一个程序里同时使用空格和制表符,Python就不能理解你的意图,所以最好在程序中始终使用同一的缩进方式。多数人都喜欢直接按一下Tab键进行一级缩进,这样比输入几个空格要快得多。如果已经开始使用Tab了,就要一直用下去,不要跟空格混用。
现在你的程序在一个无限游戏循环中运行,它永远不会停止。在启动一个新程序之前,你需要在Python Shell菜单里选择Shell->Restart Shell或者按下键盘的Ctrl+C,来停止当前程序。
欢迎回家游戏
是时候学以致用、用游戏循环来开发一个应用了。“欢迎回家”这个小游戏会用感知(跟踪角色的位置)技术来跟踪角色在Minecraft世界中的移动。我们会学到如何在Python中用if
语句进行更复杂的感知。我们要建造一个神奇的门垫,它会在游戏角色站上去的时候在聊天栏显示“welcome home”消息。
使用if
语句建造一个神奇门垫
要知道游戏角色是否正站在门垫上,我们需要使用if
语句来判断角色和门垫的位置是否一致。当两者相同时,我们的游戏角色就到家了。
首先,了解一下if
语句的作用,自己在Python Shell里试验一下有助于理解:
点击Python Shell窗口中>>>提示符的右边,这样才能保证输入在正确的位置。
在Python Shell中输入下面一行代码,按下回车(Enter)键之后Python就会立刻执行这一语句。这行代码不会有任何显示输出,因为它的作用只是把数值20存放进名为
a
的变量:a = 20
在Python Shell中输入下面语句来确认变量
a
的值正确,屏幕上会显示数字20:print(a)
试试用
if
语句查看a
里存储的数值是不是大于10。注意if语句的必须以:
结尾。第一行接收后按下回车键之后,Python会自动在下一行行进行缩进。if a>10: print("big")
按下回车键告诉Python Shell
if
语句已经输入完成。你应该能看到单词"big"显示在Python Shell里。就像图2-3那样。
if
语句有两种可能的结果,例如在上面的代码中if a>10
可能是True
(a大于10)或者False
(a不大于10)。if
语句只可能有这两种结果。在这个冒险任务的后面我们还会遇到True
和False
这两个值。
检查角色是否在指定位置
要让知道角色是否在神奇门垫上,我们的程序必须至少检查坐标的两个部分——门垫的x和z坐标——是否跟角色的位置相同,这看起来是一种合理的方法。我们可以在if
语句中用and
关键词来检查两个条件是不是同时成立。y坐标在这里不是很重要。因为这里不检查y坐标,所以如果角色悬浮在门垫上方,也一样会看到欢迎回家的消息。
用交互式的方式在Python Shell里试一下这能否工作:
在Python Shell的>>>提示符后输入下面语句来设置一个变量:
a = 8
现在你要输入一个带有
and
的if
语句。这条语句要检查变量a
的值是否在两个数字之间。第一次输入之后你会看到单词"between"显示在Python Shell里,因为8是大于5并且小于10的。记住输入if
语句的缩进区域(第二行)之后要按下回车键。if a>5 and a<10: print("between")
把a改成小于5的值,看看会发生什么(见图2-4):
a=3 if a>5 and a<10: print("between")
挑战 用不同的数字来尝试上面的例子,特别是那些接近5和10的数字。看看哪些会显示出单词“between”。
david says 当你在测试程序的时候,用很多不同的数字来测试是很有用的。把无限多的可能性变成可控数量测试的一种方式是查看if语句的条件值,只测试这些值附近的数字。在前面的例子里,我会测试4,5,6和9,10,11,如果这些数字结果正确,那么可以安全的任务其他所有的数字在程序里都能得到正确结果。
建造神奇门垫
在编写游戏之前,我们先在Minecraft世界中建造一个“真正”的门垫,这样程序才能跟它进行交互。请先打开Minecraft并且加载我们之前使用过的世界(存档)。
- 要在地板上放置一个门垫,请在物品栏选择一个道具,右键点击把它放在角色面前的地板上。羊毛是个不错的选择。
- 要知道门垫的坐标,再次运行whereAmI.py程序,然后站在门垫上。记下x、y和z坐标,编写新程序的时候会用它们来确定门垫在Minecraft世界里的位置。
你可能已经迫不及待的要建造宏伟的建筑了!在冒险任务3里,我们会学习如何通过编写Python程序进行自动建造,但是现在,只需要建造很简单的东西,专注于如何让程序正常工作。你可以在门垫周围建造一所简单的房子,并让门垫处于房子的开放门廊。
编写欢迎回家游戏
既然你已经理解了if
语句的作用,就可以按照下面步骤编写“欢迎回家”游戏:
在Python Shell菜单中选择File->New File新建一个程序。
在编辑器菜单中选择File->Save As保存程序,命名为welcomeHome.py。记住程序需要保存着MyAdventures文件夹才能正常工作。
输入下面代码来导入程序需要的模块:
import mcpi.minecraft as minecraft import time
连接Minecraft游戏,记住第二个"Minecraft"的首字母要大写:
mc = minecraft.Minecraft.create()
写下感知角色位置的主游戏循环,加上一个延迟避免它运行得太快:
while True: time.sleep(1) pos = mc.player.getTilePos()
添加if语句检查角色是否站在门垫上。这里要使用带有and的if语句来同时检查两个条件。门垫的x和z坐标必须同时和角色的位置一致才能判断角色正站在门垫上。还记得你之前得到的x和z坐标吗?写在这里让程序知道你的门垫在什么位置:
if pos.x == 10 and pos.z == 12 mc.postToChat("welcome home")
来试试我们的程序能否工作!在编辑器菜单选择Run->Run Module,然后在Minecraft世界里走走。当你的角色站在门垫上,程序应该会说“welcome home”,像图2-5这样,很酷吧!
注意在if语句里你要用
==
(双等号)。这里一个常犯的错误是使用=
(单等号)。试一下用=
看会发生什么。运行程序时你会看到一个错误。Python用一个等号=来表示“把语句中等号右边的值存入等号左边的变量里”,例如a = 3
。Python用==
(双等号)表示“比较左右两边的值是否相等”,例如if a == 3
。编写欢迎回家程序时请确保缩进正确。
while True
循环中的代码需要缩进一级。if
语句中的mc.postToChat()
需要缩进两级。如果缩进错了,程序运行的结果就不对了。不管信不信,你刚刚已经学到了在其他冒险任务里需要的所有基础知识。每个minecraft程序都需要导入模块,连接游戏,进入游戏循环,感知游戏中事件的发生以及对事件作出不同的反应。你会发现很多伟大的东西都是从这些简单的基本步骤衍生而来的。
挑战 你觉得检查y坐标(上下方向)能改进对角色是否站在门垫上的检测吗?试一下。修改程序来检测门垫的全部三个坐标,看看是不是工作得更好。
深入代码
我们在输入代码的时候会犯一些错误,那样就会在Python Shell窗口中看到红色的错误信息。碰到这种情况最好看清楚错误的类型,这样以后再出现这些错误的时候,就不会感到恐慌了。
如果输入了错误的符号,或者搞错了顺序,就会看到一个语法(syntax)错误。如果漏写了一个符号或者输入了一个不应该出现的变量名,也会出现语法错误。
比如,如果你在Python Shell里输入下面这一行,你就会看到图2-6中的语法错误,因为等号不应该单独出现;它通常要跟变量和其他值一起使用。
=
现在输入下面的代码到Python Shell:
print(zz)
Python会告诉你这是个名字错误(如图2-7所示)因为你还没有在变量zz
中存入一个值,所以Python还不知道有名为zz
的变量。
输入一个while
循环,但是不要输入末尾重要的冒号(:):
while True
结果还是语法错误,因为Python期待结尾的冒号,而你没有输入。
如果你的程序运行时出现了错误信息,不要太紧张。自习检查错误信息里指出的那一行代码,看看是不是能发现哪里输入错了。有时候,因为问题代码前面某一行的输入错误也会造成语法错误,检查错误信息(指出的代码)附近的代码看看能不能发现问题。通常发现别人代码的错误要比发现自己的错误更容易。Python Shell报告错误的时候,错误信息里会包含错误代码的行号。你可以从IDLE窗口的右下角看到当前光标所在行的行号,也可以用IDLE菜单中的Edit->Goto Line来跳转到指定行。
语法(Syntax)是指一种语言(在这里就是Python语言)的规则,它主要跟你输入内容的顺序有关。
使用地理围栏来收租
现在来写一个新有序,叫做rent.py。游戏里有一块栅栏围起来的土地。你的程序要检测角色是否在这块土地上,并且只有角色在这块地上就要收租金。你的角色要面临的挑战是以最快的速度清楚这块地上的所有物体,让租金尽可能的低。
程序welcomeHome.py使用if语句来检查角色的坐标是否跟门垫相同。要触发这个程序,你的角色必须刚好站在门垫上。你可以用地理围栏技术来进行一些改进,你在这个新游戏里也会用到这个技术。对角色位置的检测有所改进,因为它可以检查你的角色是否站在Minecraft世界的某个区域中,而不是某个特定的坐标。这样你就可以判断角色是否站在一大片区域中的任意位置,而不需要精确的检查每个方块。
地理围栏是一种通用技术,可以在任何地图上的指定坐标附近建立虚拟的围栏。当有东西进入虚拟围栏包围的区域,就会产生某些结果。这里的“东西”可以是Minecraft世界中的角色、现实世界的人、或者是割草机之类的设备,甚至是现实世界的动物。
在很多情况下,地理围栏用来在某些物体——比如牛,或者车辆——离开预定区域时发出警告。你可以在这里找到更多地理围栏的实际应用,也可以在这里了解地理围栏在现实世界中是如何被用来跟踪野外大象的。
实现地理围栏需要两样东西:一个需要跟踪的物体或者区域,和围栏为这个物体所圈定的坐标范围。你接下来要建造一块用Minecraft中的真实围栏所包围的土地,并且记下这块地四个角落的坐标。
这个程序并不是一定要建造这些围栏才能运行,但是如果造了的话游戏玩起来会更有意思,因为你必须跳过围栏或者绕到围栏的缺口,才能进入或者离开这块区域。增加障碍物可以让游戏变得更有挑战性和让人激动。Minecraft围栏要和你的虚拟地理围栏坐标一致,这种简单的方式可以判断角色是否在圈定的区域里。
建好的围栏应该看起来像图2-8这样。
找出区域四角的坐标
为了在程序中给一块区域设置地理围栏,你需要该区域四个角落的准确坐标以写在程序里。
最简单的方式是运行你之前编写的whereAmI.py程序,然后让角色移动到该区域的四个角落去以活动角落的x、y和z坐标。找一张纸,画一张围栏区域的草图并把坐标写上。写程序的时候会需要用到这些坐标。记住角色向南移动时z坐标变大,向北移动时变小,如图2-1所示。
在图2-9中,你可以看到我在写这本书是建造的区域的坐标。我写下了所有四个角落的坐标,我们需要用它们来确定x和z坐标方向的最大值和最小值。注意最小坐标在区域的左上角。
建造地理围栏的程序需要四个数字。首先,你需要知道x坐标的最小值和最大值,所以请从你的图里找出这两个数字。在前面的例子里,x的最小值是10,最大值是20。我把最小的x值叫做X1,最大的x值叫做X2。
根据角色在Minecraft世界中的位置,某些坐标值可能是负数(例如,x=-5,y=0,z=-2),这会让计算稍微复杂一点儿。
下一步需要找出z坐标的最小值和最大值。在例子中,z坐标的最小值是10,最大值是20。我把最小的z值叫做Z1,最大的z值叫做Z2。
X1 = 10
X2 = 20
Z1 = 10
z2 = 20
像这样列出你的最小和最大坐标,你在下一个步骤——编写程序——里会泳道它们。
编写地理围栏程序
最后一步是编写程序实现地理围栏并在角色站到指定区域中时收取租金。这个程序在结构上跟whereAmI.py相似。
在这个新游戏里还会用到常量,这让你以后可以很容易的移动划定的区域。用这种方式,如果要改变区域的位置,就不需要到程序代码中去四处寻找需要修改的坐标值了。
常量是计算机内存中某个部分的名字(像变量一样),这里存放了一些程序运行过程中不会被改变的值。
Python编程语言不像其他语言一样,它没有特别的方式来处理常量。Python程序员通常遵循常量使用大写字母命名的约定(这只是一种标准的工作方式)。这样你在编程中用到常量的时候会更明显,一旦设置了初始值就不应该再改变。其他编程语言有自己使用常量的方式,在这些语言里如果尝试修改常量的值会产生错误。Python并没有这种特性。
请按以下步骤编写程序:
首先在IDLE菜单中选择File->New File打开一个新窗口。
从菜单中选择File->Save As保存文件,命名为rent.py。
导入程序需要的模块:
import mcpi.minecraft as minecraft import time
连接到minecraft游戏程序:
mc = minecraft.Minecraft.create()
现在,你需要定义常量来表示围栏的4个坐标。确保你使用的是之前得到的坐标。这里列出的是我在自己的程序里使用的坐标,你的可能有所不同:
X1 = 10 X2 = 20 Z1 = 10 z2 = 20
接下来创建一个变量来记录游戏角色当前被收了所少租金。游戏开始时,你还没有收取任何租金,所以创建一个叫做rent的变量并把它设为0:
rent = 0
用while语句写一个游戏主循环。你的程序需要每秒钟循环一次,所以使用sleep语句在每次循环结束后延迟1秒。在每次循环之后延迟一小段时间可以放慢程序的运行速度,而且也是一个计算角色在围栏区域里待了多久的一种简单方法。稍后你将在每次循环之后给rent变量加1,因为在循环的最前面加了1秒的延迟,这就表示rent变量会每秒钟增加1。从这里开始,请确保代码中缩进是正确的:
while True: time.sleep(1)
判定角色是否在围栏圈定的区域之前,需要先获得角色的坐标。就像在之前的程序中一样,用player.getTilePos()来实现:
pos = mc.player.getTilePos()
现在到了程序最重要的部分。这里你要让程序使用之前获得的四个坐标来判断角色是否在围栏区域内,如果是,就要收取租金并且向聊天栏发送一条消息通知它。输入下面代码:
if pos.x>X1 and pos.x<X2 and pos.z>Z1 and pos.z<Z2: rent = rent+1 mc.postToChat("You owe rent:"+str(rent))
程序写到这里,你必须非常小心以保证缩进正确。Python通过缩进来判断哪些语句应该属于同一组。你可以看到
while True:
这一行下面的所有语句都缩进了一级,所以Python知道所有这些语句都属于while
循环。但是请仔细看一下if
语句:它下面的两行代码多了一级缩进。这表示这两行语句是if
语句的组成部分,只有当你的角色站在围栏区域内时才会执行。
再次保存程序,从菜单选择Run->Run Module运行程序。
操控你的角色在围栏内外游玩一番。当它在围栏里面时,每秒钟聊天栏都会出现一个消息,显示当前收取了多少租金。跑出围栏范围之外,就不会被收取租金。不过你的程序还是一样会记住当前应收的租金数额,这样当你重新回到围栏范围内时,程序就不会忘记你当前欠了多少租金。
挑战 要让这个游戏更有趣,你的角色需要在围栏里完成一些任务。在围栏里面的不同位置上放置一些随机的方块,然后再次运行你的程序。任务的挑战是在付出租金尽可能少的情况下收集到所有方块。击打并消除一个方块可以被认为是收集。程序并不知道发生了这些情况,但是这会让你的游戏更有趣味。你可以把你想到的任何方块分散在围栏里的区域,然后让你的伙伴们去收集这些方块,收集到所有方块并且付出租金最少的人就是最后的获胜者。
移动角色
有个办法可以让你的游戏更有挑战性和让人激动,这要用到Minecraft API的另一个在你以后编写游戏时很有用的功能——移动角色到Minecraft世界中的另一个位置!你需要修改现在的rent.py程序,如果角色在围栏里超过3秒钟,就会被弹上天飞出围栏外,必须重新回到围栏里才能继续任务。
你首先要选择一个围栏之外不远处的位置作为角色的降落地点。最简单的做法是找出围栏区域的最大x和z坐标,把它们各加上2获得新坐标。为了制造一些戏剧效果,选择一个天空中的y坐标。实际的数字取决于你建造围栏的地方在Minecraft世界中的有多高,如果围栏的y坐标是0,你可以把y坐标设置在10左右。这会把角色弹上空中,然后重力会让他们重新落回地面。
你现在要修改rent.py程序让游戏角色在在围栏内停留超过3秒之后被扔出围栏,请按下面步骤操作:
在程序最前面设置三个新常量保存一个初始坐标。初始坐标需要在围栏外面不远处,当你的角色被弹出围栏时,他们会被移动到这里。新加的代码用粗体显示:
X1 = 10 Z1 = 10 X2 = 20 Z2 = 20 HOME_X = X2 + 2 HOME_Y = 10 HOME_Z = Z2 + 2
现在需要对角色在围栏里待的时间进行计时,你需要一个叫做inField的新变量。这个变量存储了角色在围栏里待的秒数,用来判断角色待在围栏里的时间是否过长。跟前面一样,你需要添加下面粗体显示的代码:
rent = 0 inField = 0
接下来,你需要添加一行代码,当角色在围栏里时给inField加1。添加下面粗体代码:
if pos.x>X1 and pos.x<X2 and pos.z>Z1 and pos.z<Z2: rent = rent+1 mc.postToChat("You owe rent:"+str(rent)) inField = inField+1
现在你要增加一个
else
语句,让程序在角色离开围栏之后重置计时器为0。请确保else语句的缩进正确这样Python才能理解你的意思。你很快就会学到else
语句以及符号#的用法,但是现在,只要增加下面粗体的代码就可以了:mc.postToChat("You owe rent:"+str(rent)) inField = inField+1 else: # not inside the field inField = 0
最后,你需要在原来程序的末尾增加几行代码来弹射角色。这些代码正好在原先代码的后面。
if
语句必须缩进一级因为它是while
循环的一部分,if之下的语句需要缩进两级。输入下面代码:if inField>3: mc.postToChat("Too slow!") mc.player.setPos(HOME_X, HOME_Y, HOME_Z)
保存程序,在菜单选择Run->Run Module运行程序。
现在的游戏会比之前有趣得多,因为你必须小心的设计策略,反复进出围栏才能避免被弹上天。现在,像之前一样,选择一些不同的方块随机散布在围栏里面。挑战你自己和朋友们来“收集”(或者说是破坏)尽可能多的方块,同时尽可能少的付出租金(如图2-10所示)。
深入代码
你现在用到了Python语言的两个新特性,需要解释一下。
首先,你用了else
语句。else
是if
语句的一个可选的组成部分。它的缩进层次必须跟与之匹配的if
语句相同,else
内的语句也必须被缩进。这里有个例子:
if a>3:
print("big")
else:
print("small")
如果变量a里的值大于3,会在Python Shell里打印出“big”,否则(也就是说a不大于3)就会在打印“small”。或者换种说法,如果a>3是True,就打印big,如果a>3是False,则打印small。
if语句不一定要有对应的else
语句(是可选的),但是有时你会希望程序在条件为真和为假时分别采用不同的处理方式。
你用到的另一个功能是注释。在程序中给自己或者其他任何都这段代码的人留下一些提示信息是很有用的。
注释以#符号开始,像这样:
# this is a comment(这是注释)
如果要在程序中使用中文,则需要在程序最上方加入下面这一行:
#! encoding=utf8
我们可以在一行里的任意位置放置注释。从#
符号开始到那一行末尾的所有内容都会被Python语言忽略。
更多冒险
在这个冒险任务里,我们已经学习并且亲自实践了游戏循环,以及使用位置感知和地理围栏来创造一个有趣的游戏。在制作游戏时,最好能让朋友和家人来玩你的游戏并告诉你他们喜欢和不喜欢哪些部分。这是一种获取反馈来改进游戏的好方式。
- 欢迎回家游戏有个问题:如果角色站在门垫上,程序会不停的显示welcome home占满整个聊天栏。你能不能想个办法修改程序,让它只在你进入房子的时候显示一次“welcome home”?
- 想设计出让人激动的游戏,一个好方法是玩别人的游戏,看看它们是怎么吸引玩家的。玩很多其他的电脑游戏,尽可能多的列出游戏在角色位置改变时可以做出的不同反应。
- 在网上搜索地理围栏,看看能不能找到一些在工业应用中效果很好使用方式。如果有某个点子引起了你的兴趣,试试根据它的原理编写一个Minecraft程序,用你在这个冒险任务中学到的东西。