使用 Python 游玩我的世界。
参考资料来源:各种网络资料和《零基础学 Minecraft 编程》,人民邮电出版社。
开始 环境配置教程:https://www.bilibili.com/video/BV1FG4y1X7SQ
注意在安装 java -jar BuildTools.jar 的时候会打印一大大大坨信息,整个过程大约6分钟,需要梯子。出现如下信息则表示成功:
启动服务器:
1 2 cd D:\mcserverjava -Xms1024M -Xmx1024M -jar spigot-1 .19.4 .jar
多人游戏,服务器地址127.0.0.1
。
这个时候就可以使用作弊码了。作弊码附在文章末尾。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from mcpi.minecraft import Minecraftimport mcpi.block as blockimport timeif __name__ == '__main__' : time.sleep(3 ) print ('连接MC...' ) mc = Minecraft.create() mc.postToChat("hello frome Python" ) x, y, z = mc.player.getTilePos() print (x, y, z) mc.setBlocks(x, y, z+5 , x, y, z+5 , 1 , 0 )
生成方块
Minecraft 坐标信息:
正东:x轴正方向
正南:z轴正方向
正上:y轴正方向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from mcpi.minecraft import Minecraftimport mcpi.block as blockimport timeif __name__ == '__main__' : time.sleep(3 ) mc = Minecraft.create() x, y, z = mc.player.getTilePos() for i in range (16 ): mc.setBlock(x+3 , y, z+i, 35 , i) time.sleep(0.5 )
效果如下:
采用更多层循环以生成平面和立体。
多个方块同时放置的函数:
1 mc.setBlocks(x1, y1, z1, x2, y2, z2, blockid, someindex)
(x1, y1, z1)
和(x2, y2, z2)
对应一个长方体的两个对角顶点。根据这两个顶点我们可以绘制出一个由指定方块组成的长方体。
想要一个空心的?缩小一圈用air
方块填充。
简单的例子 实时显示玩家的位置:
1 2 3 4 5 6 7 8 9 10 from mcpi.minecraft import Minecraftimport timemc = Minecraft.create() while True : time.sleep(1 ) pos = mc.player.getTilePos() mc.postToChat("x=" +str (pos.x)+" y=" +str (pos.y)+" z=" +str (pos.z))
注意:若坐标错误,需输入/setworldspawn 0 0 0
。
玩家进入某一区域,超过3秒弹出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from mcpi.minecraft import Minecraftimport timex1 = 60 x2 = 63 z1 = -17 z2 = -15 infield = 0 def bump2sky (x, y, z ): for t in range (8 ): y += 0.90 *t mc.player.setPos(x+5 , y, z) time.sleep(3 ) mc = Minecraft.create() while True : time.sleep(1 ) pos = mc.player.getTilePos() if pos.x>=x1 and pos.x<=x2 and pos.z>=z1 and pos.z<=z2: mc.postToChat("warnning" ) infield += 1 else : infield = 0 if infield > 3 : mc.postToChat("too slow!" ) bump2sky(pos.x, pos.y, pos.z)
效果如下:
绘制任意图像 知道了如何生成方块,就知道了如何绘制任意图像。
对于一张图片,如果我们能根据每一个像素点的 RGB 值找到对应颜色的 Minecraft 方块,就有可能在游戏中绘制该图像。
然而,并不是所有颜色都可以找到对应的 Minecraft 方块。对于找不到的颜色,我们希望有一个替代颜色,这个颜色应当在人眼视觉上相近。
为此,我们需要一个寻找相近颜色的算法。
已经知道,颜色可用 RGB 表示,是否相近的 RGB 值表示的颜色在视觉上相似呢?答案是否定的。但可喜可贺,还是有以 RGB 值计算颜色距离的公式(具体参考此链接(该内容已备份) ):
可以根据以上公式编写代码计算两个颜色的距离。不过,我们采用另一种更好的方案。
CIEDE2000算法是CIE(国际照明委员会)于2000年提出的,它是Delta E算法的改进版本。CIEDE2000算法考虑了人眼感知颜色的非线性特性,并将颜色差异分解为亮度、色相和饱和度三个因素,从而更准确地计算颜色之间的相似度。CIEDE2000算法还考虑了颜色对比度的影响,因此在低对比度颜色之间的比较中表现更好。
这个算法已经封装好了,安装 colormath 库:
使用此库需要初步了解颜色空间,并大致知道一些函数接受什么,返回什么,做了什么。
「动态类型一时爽,代码重构火葬场。」
colormath 官方文档:
注意,如果提示:
1 AttributeError: module 'numpy' has no attribute 'asscalar' . Did you mean: 'isscalar' ?
这是因为:
numpy的版本过高,需要降版本(高情商)
numpy你真就不考虑历史兼容呗(低情商)
降版本的命令:
1 2 pip uninstall numpy pip install -U numpy==1.22 .4
OK,颜色替代的问题解决了。但我们还缺一个映射方案。我们需要将颜色编码映射到具体的 Minecraft 方块的编号上。这方面的资料找起来比较麻烦。我直接给出链接:
以上链接只是名字到编号的映射,我们总不能用眼睛看出物品的颜色编码吧?因此需要配合颜色到名字的映射表(该内容已备份):
注意有些方块是不能用的,它可能是个半砖,可能会随重力掉落,可能会自爆(例如仙人掌,不过仙人掌另有它用,之后再说),甚至可能根本不是一个方块。为了准确性,还需要在游戏里对比,这个比对过程是痛苦的,为了让世界 no more pain,我决定贴出初步整理的映射表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ffffff,80 dcdcdc,35 dcd9d3,155 d5c98c,216 909090,42 faee4d,41 b0a836,19:1 c5c52c,19 2c4199,35:11 8d909e,82 4f4f4f,1:6 848484,35:8 6db015,35:5 d06d8e,35:6 414141,35:7 ffc125,35:4 ba6d2c,35:1 9941ba,35:2 6699d8,35:3 486c98,251:3 416d84,35:9 6d3699,35:10 58412c,35:12 586d2c,35:13 842c2c,35:14 151515,35:15 4b3f26,5:5 7b663e,25 4fbcb7,57 8a8adc,79 667f33,251:13
这个映射表非常粗糙,如果希望有更丰富的色彩表现,请手动修改之。
注意:一些魔改的mcpi
版本并不需要此映射表,而可以直接使用物品名称。但是这种mcpi
版本与本文不兼容,也不与《零基础学 Minecraft 编程》兼容。更重要的,它们主要是通过某盘传播(而不是pip安装),如果你和我一样抵制某盘,不想在上面花钱,尽量不要倒向这个版本。
下面放出完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 from colormath.color_objects import sRGBColor, LabColorfrom colormath.color_diff import delta_e_cie2000from colormath import color_conversionsfrom imageio import imreadfrom mcpi.minecraft import Minecraftdef rgb2hex (red, green, blue ): """ 返回一个 HexColorString """ return '{:02x}{:02x}{:02x}' .format (red, green, blue) def hex2lab (hx ): color_srbg = sRGBColor.new_from_rgb_hex(hx) color_lab = color_conversions.convert_color(color_srbg, LabColor) return color_lab def closest_color (hexStr ): """ 接受一个 HexColorString 返回列表中与该颜色最接近的颜色 """ if hexStr in colorhave: return colorhave[hexStr] closest_color = None closest_distance = None labStr = hex2lab(hexStr) for color_code, color_name in colorhave.items(): color_c = hex2lab(color_code) distance = delta_e_cie2000(labStr, color_c) if closest_distance is None or distance < closest_distance: closest_distance = distance closest_color = color_name return closest_color def wip (r, g, b ): s = closest_color(rgb2hex(r, g, b)) tmpls = [] if ':' in s: tmpls.append(eval (s.split(":" )[0 ])) tmpls.append(eval (s.split(":" )[1 ])) else : tmpls.append(eval (s)) return tmpls fo = open ("D:\mypycode\mccode\colormap.txt" ) ls = [] for line in fo: line = line.replace("\n" ,"" ) ls.append(line.split("," )) fo.close() colorhave = {} for i in range (len (ls)): colorhave[ls[i][0 ]] = ls[i][1 ] im = imread('D:\mypycode\imageiostuff\\rmdpic.jpg' ) h, w, _ = im.shape mc = Minecraft.create() x0, y0, z0 = mc.player.getTilePos() x0 += 5 for y in range (h): for x in range (w): r, g, b = im[y][x] flagls = wip(r, g, b) if len (flagls)==1 : mc.setBlock(x0+x, y0+h-y, z0, flagls[0 ]) else : mc.setBlock(x0+x, y0+h-y, z0, flagls[0 ], flagls[1 ])
一个简单的优化是:把最相近颜色存储起来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 from colormath.color_objects import sRGBColor, LabColorfrom colormath.color_diff import delta_e_cie2000from colormath import color_conversionsfrom imageio.v2 import imreadfrom mcpi.minecraft import Minecraftdef rgb2hex (red, green, blue ): """ 返回一个 HexColorString """ return '{:02x}{:02x}{:02x}' .format (red, green, blue) def hex2lab (hx ): color_srbg = sRGBColor.new_from_rgb_hex(hx) color_lab = color_conversions.convert_color(color_srbg, LabColor) return color_lab closestcolor = {} def closest_color (hexStr ): """ 接受一个 HexColorString 返回列表中与该颜色最接近的颜色 """ if hexStr in colorhave: return colorhave[hexStr] if hexStr in closestcolor: return closestcolor[hexStr] closest_color = None closest_distance = None labStr = hex2lab(hexStr) for color_code, color_name in colorhave.items(): color_c = hex2lab(color_code) distance = delta_e_cie2000(labStr, color_c) if closest_distance is None or distance < closest_distance: closest_distance = distance closest_color = color_name closestcolor[hexStr] = closest_color return closestcolor[hexStr] def wip (r, g, b ): s = closest_color(rgb2hex(r, g, b)) tmpls = [] if ':' in s: tmpls.append(eval (s.split(":" )[0 ])) tmpls.append(eval (s.split(":" )[1 ])) else : tmpls.append(eval (s)) return tmpls fo = open ("D:\mypycode\mccode\colormap.txt" ) ls = [] for line in fo: line = line.replace("\n" ,"" ) ls.append(line.split("," )) fo.close() colorhave = {} for i in range (len (ls)): colorhave[ls[i][0 ]] = ls[i][1 ] im = imread('D:\mypycode\imageiostuff\delisha2.jpg' ) h, w, _ = im.shape mc = Minecraft.create() x0, y0, z0 = mc.player.getTilePos() x0 += 5 for y in range (h): for x in range (w): r, g, b = im[y][x] flagls = wip(r, g, b) if len (flagls)==1 : mc.setBlock(x0+x, y0+h-y, z0, flagls[0 ]) else : mc.setBlock(x0+x, y0+h-y, z0, flagls[0 ], flagls[1 ])
当然,更好的方案是在命令中传入要绘制的图片,而不是每次绘制都要手动修改代码。
看一下效果(图片太大了两边加载不出来):
作为对比,给出原图:
头发的细节:
考虑如下生成的图片:
我们希望只保留这个谜之柴犬,那么可以在数据文件中添加仙人掌的记录。因仙人掌自爆的特性,只会留下柴犬本犬:
对于更大的图片,可能需要在平坦的世界绘制,或者铺在地上。对于像素更高的图片,可能需要事先压缩,或者转换为像素画形式。普通图片转换为像素画形式可以使用 Python 实现,不再赘述。
从上图可以看出,这个颜色映射表还是非常简陋的。但我没耐心再一个个地比对了。
作弊码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /gamerule doDaylightCycle false # 永久白天 /gamemode creative # 创造 /gamemode survival # 生存 /gamemode adventure # 冒险 /gamemode spectator # 旁观 /spawnpoint # 重生点设置为当前位置 /seed # 种子 /give username minecraft:diamond_ore 64 # 钻石 /give username minecraft:emerald_block 64 # 绿宝石 /tp username {x} {y} {z} # 传送 /time set 0 # morning 6 /time set 9500 # noon /time set 12000 # 黄昏 /xp {int} # 给经验 /effect username {type} {time} {level} # 给药 /gamerule keepInventory true # 死亡不掉落 /summon minecraft:zombie # 召唤僵尸 /setblock {x} {y} {z} minecraft:redstone # 设置红石方块