使用 Python 游玩我的世界。
参考资料来源:各种网络资料和《零基础学 Minecraft 编程》,人民邮电出版社。

开始
环境配置教程:https://www.bilibili.com/video/BV1FG4y1X7SQ
注意在安装 java -jar BuildTools.jar 的时候会打印一大大大坨信息,整个过程大约6分钟,需要梯子。出现如下信息则表示成功:

启动服务器:
cd D:\mcserver
java -Xms1024M -Xmx1024M -jar spigot-1.19.4.jar
多人游戏,服务器地址127.0.0.1。
op username # 给权限
这个时候就可以使用作弊码了。作弊码附在文章末尾。
# hellofrompy.py
from mcpi.minecraft import Minecraft
import mcpi.block as block
import time
if __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轴正方向
# 16blocks.py
from mcpi.minecraft import Minecraft
import mcpi.block as block
import time
if __name__ == '__main__':
time.sleep(3)
# 连接到 MC
mc = Minecraft.create()
# 得到角色当前位置
x, y, z = mc.player.getTilePos()
# 羊毛
for i in range(16):
mc.setBlock(x+3, y, z+i, 35, i)
# 35是羊毛的代号,0是白色
time.sleep(0.5) # 加个延时以演示
效果如下:

采用更多层循环以生成平面和立体。
多个方块同时放置的函数:
mc.setBlocks(x1, y1, z1, x2, y2, z2, blockid, someindex)
(x1, y1, z1)和(x2, y2, z2)对应一个长方体的两个对角顶点。根据这两个顶点我们可以绘制出一个由指定方块组成的长方体。
想要一个空心的?缩小一圈用air方块填充。
简单的例子
实时显示玩家的位置:
# showpos.py
from mcpi.minecraft import Minecraft
import time
mc = 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秒弹出:
# bump2sky.py
from mcpi.minecraft import Minecraft
import time
x1 = 60 # y = 80
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 库:
pip install colormath
使用此库需要初步了解颜色空间,并大致知道一些函数接受什么,返回什么,做了什么。
「动态类型一时爽,代码重构火葬场。」
colormath 官方文档:
注意,如果提示:
AttributeError: module 'numpy' has no attribute 'asscalar'. Did you mean: 'isscalar'?
这是因为:
- numpy的版本过高,需要降版本(高情商)
- numpy你真就不考虑历史兼容呗(低情商)
降版本的命令:
pip uninstall numpy
pip install -U numpy==1.22.4
OK,颜色替代的问题解决了。但我们还缺一个映射方案。我们需要将颜色编码映射到具体的 Minecraft 方块的编号上。这方面的资料找起来比较麻烦。我直接给出链接:
- https://www.minecraftinfo.com/idlist.htm
- https://minecraft-ids.grahamedgecombe.com/
- https://www.stuffaboutcode.com/p/minecraft-api-reference.html
以上链接只是名字到编号的映射,我们总不能用眼睛看出物品的颜色编码吧?因此需要配合颜色到名字的映射表(该内容已备份):
注意有些方块是不能用的,它可能是个半砖,可能会随重力掉落,可能会自爆(例如仙人掌,不过仙人掌另有它用,之后再说),甚至可能根本不是一个方块。为了准确性,还需要在游戏里对比,这个比对过程是痛苦的,为了让世界 no more pain,我决定贴出初步整理的映射表:
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安装),如果你和我一样抵制某盘,不想在上面花钱,尽量不要倒向这个版本。
下面放出完整代码:
from colormath.color_objects import sRGBColor, LabColor
from colormath.color_diff import delta_e_cie2000
from colormath import color_conversions
from imageio import imread
from mcpi.minecraft import Minecraft
def 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])
一个简单的优化是:把最相近颜色存储起来。
from colormath.color_objects import sRGBColor, LabColor
from colormath.color_diff import delta_e_cie2000
from colormath import color_conversions
from imageio.v2 import imread
from mcpi.minecraft import Minecraft
def 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 实现,不再赘述。

从上图可以看出,这个颜色映射表还是非常简陋的。但我没耐心再一个个地比对了。
作弊码
/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 # 设置红石方块