# 可成长的怪物体系
温馨提示:开始阅读这篇指南之前,我们希望你对《我的世界》基岩版附加包有一定了解,有能力撰写 JSON 数据格式,对 Python 进行模组开发有了解,并能够独立阅读《我的世界》开发者官网-开发指南或其他技术引用文档。
本文将带你了解怪物属性的 API,并结合之前学习的课程,带你搭建一个可跟随时间成长的超级僵尸。
# 成果演示
我们这节课将配合上一节课提到的时间规则,来实现一个可以跟随时间成长的超级僵尸,这个成长不仅仅是属性上的成长,还包括生物行为的丰富:
我们的需求很简单,先来简单梳理一下:
- 最开始就是普通的僵尸;
- 到游戏进行到第 10 天时,所有僵尸都拥有破坏墙体的能力;
- 到游戏进行到 20 天以后,所有僵尸除了拥有破坏墙体的能力之外,还拥有搭路的能力;
# 实践
要满足需求,这就需要对应改造一下我们的系统和我们的自定义僵尸,我们一步一步来。
# Step1. 改造 zombie 行为文件
我们首先要定义三种强度的僵尸事件,用来支持僵尸行为的变更,不过在此之前我们还要考虑一件事。
我们之前搭路的行为是直接在 query.has_target
之后就开始生效了,现在我们需要把这个行为做一个开关。最容易想到的办法就是像之前课程播放破坏方块动画的那样,使用一个用于设置状态的组件,比如我们使用 minecraft:mark_variant
:
// 省略其他无关内容
"minecraft:entity": {
"description": {
"animations": {
"put_block_sensor": "controller.animation.zombie.put_block"
},
"scripts": {
"animate": [
{
// 只有在有目标的情况下,并且能够搭建方块时才执行控制器的逻辑
"put_block_sensor": "query.has_target && query.mark_variant == 1"
}
]
}
},
"component_groups": {
// 搭建方块的能力
"can_not_put_block": {
"minecraft:mark_variant": {
"value": 0
}
},
"can_put_block": {
"minecraft:mark_variant": {
"value": 1
}
},
这样我们就可以使用组件组来标识该实体是否拥有该能力了。
然后定义好触发我们三种强度僵尸的事件:
"events": {
// 自定义的强度1:就是普通的僵尸
"tutorial:zombie_level_1": {
"add": {
"component_groups": ["can_not_put_block"]
},
"remove": {
"component_groups": ["destroy_block", "destroy_block_attack", "can_put_block", "put_block"]
}
},
// 自定义强度2:能够拆墙,但是不能够铺路的僵尸
"tutorial:zombie_level_2": {
"add": {
"component_groups": ["destroy_block", "can_not_put_block"]
},
"remove": {
"component_groups": ["can_put_block", "put_block"]
}
},
// 自定义强度3:既能够拆墙,又能够搭路的僵尸
"tutorial:zombie_level_3": {
"add": {
"component_groups": ["destroy_block", "can_put_block"]
},
"remove": {
"component_groups": ["can_not_put_block"]
}
}
至此,基础的 zombie.json
就改造完成了。
# Step2. 代码检测新的一天
核心代码如下:
def __init__(self, namespace, name):
super(TimeRuleServerSystem, self).__init__(namespace, name)
self.ListenEvent()
# 组件缓存,避免重复创建
self.mTimeComp = CompFactory.CreateTime(serverApi.GetLevelId())
#
self.mTimeCounter = 0
self.mLastDay = 0 # 上一次的天数,用来判断是否是新的一天
self.mCurrentDay = 0 # 今天的天数
def Update(self):
self.mTimeCounter += 1
# 每一秒都检查
if self.mTimeCounter % 30 == 0:
self._TriggerZombieEventIfNewDay()
def _TriggerZombieEventIfNewDay(self):
# 从游戏开始经过的总帧数
passedTime = self.mTimeComp.GetTime()
# 从游戏开始经过的游戏天数
day = passedTime / 24000
if day != self.mCurrentDay:
self.mCurrentDay = day
isNewDay = self.mLastDay != self.mCurrentDay
self.mLastDay = self.mCurrentDay
if isNewDay:
self._ResetAllZombieLevel()
为了配合玩家可能使用的 /time set
指令,所以我们这里每一秒都检测一下是否是新的一天。如果满足条件,那么重置当前世界中所有的僵尸等级。
# Step3. 重置僵尸等级和属性函数
有了检测是否进入新一天的代码了,那么我们可以着手开始考虑僵尸的属性和能力了。
首先需要定义好等级对应的属性值,以及游戏天数和僵尸等级的对应关系:
# 僵尸的标识符
ZombieIdentifier = 'minecraft:zombie'
# 僵尸的等级与天数的对应关系
ZombieLevelDay = {
1 : 1,
10: 2,
20: 3
}
# 僵尸等级与僵尸属性的对应关系
ZombieLevelAttr = {
1: {
serverApi.GetMinecraftEnum().AttrType.HEALTH: 10,
serverApi.GetMinecraftEnum().AttrType.SPEED : 0.25,
serverApi.GetMinecraftEnum().AttrType.DAMAGE: 1
},
2: {
serverApi.GetMinecraftEnum().AttrType.HEALTH: 20,
serverApi.GetMinecraftEnum().AttrType.SPEED : 0.3,
serverApi.GetMinecraftEnum().AttrType.DAMAGE: 2
},
3: {
serverApi.GetMinecraftEnum().AttrType.HEALTH: 30,
serverApi.GetMinecraftEnum().AttrType.SPEED : 0.4,
serverApi.GetMinecraftEnum().AttrType.DAMAGE: 3
}
}
由于我们保存了当前的游戏天数,所以很容易的写出重置一个僵尸的函数:
def _SetEntityLevelAndAttrAccordCurrentDay(self, entityId):
# 使用事件来触发对应僵尸的等级的行为
zombieLevel = self._GetZombieLevelByDay(self.mCurrentDay)
eventName = 'tutorial:zombie_level_' + str(zombieLevel)
CompFactory.CreateEntityEvent(entityId).TriggerCustomEvent(entityId, eventName)
# 根据僵尸等级来设置僵尸的属性值
attrComp = CompFactory.CreateAttr(entityId)
for _attrName, _attrValue in ZombieLevelAttr[zombieLevel].items():
attrComp.SetAttrMaxValue(_attrName, _attrValue)
attrComp.SetAttrValue(_attrName, _attrValue)
# 根据天数来获取当前僵尸等级
def _GetZombieLevelByDay(self, day):
for threshold, level in sorted(ZombieLevelDay.items(), reverse=True):
if day >= threshold:
return level
return 1 # 默认等级为1
重置所有僵尸,我们需要借助 serverApi.GetEngineActor()
API (opens new window) 来完成:
# 重置所有僵尸的等级
def _ResetAllZombieLevel(self):
print '=====重置所有僵尸等级===='
for entityId, entityDict in serverApi.GetEngineActor().items():
for _dimensionId, _identifier in entityDict.items():
if _identifier == ZombieIdentifier:
self._SetEntityLevelAndAttrAccordCurrentDay(entityId)
# Step4. 考虑新增的僵尸
新增的僵尸也需要立刻适配当前世界天数对应的等级,这需要我们关注实体新增的事件 (opens new window),我们需要在代码中进行监听,当新增时自动触发对应的事件:
def ListenEvent(self):
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "AddEntityServerEvent", self,
self.OnAddEntityServerEvent)
def UnListenEvent(self):
self.UnListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "AddEntityServerEvent", self,
self.OnAddEntityServerEvent)
def Destroy(self):
self.UnListenEvent()
def OnAddEntityServerEvent(self, args):
entityId = args['id']
engineTypeStr = args['engineTypeStr']
if engineTypeStr == ZombieIdentifier:
self._SetEntityLevelAndAttrAccordCurrentDay(entityId)
# Step5. 测试并验证
首先,我们使用命令 /time set 0
,可以看到僵尸对于在方块内的目标并没有任何办法,属性值和组件组也跟我们设定的一样:
然后,当使用命令 /time set 240000
把游戏进程过渡到第 10 天之后时,这时候僵尸属性值发生了变化,并且拥有了破坏墙体的能力:
当我们使用命令 /time set 480000
把游戏进程过渡到 20 天之后时,僵尸已经有能力击杀在高处的目标:
# 课后作业
本次课后作业,内容如下:
- 改造原版的
zombie.json
并配合上节课的时间管理系统来进行管理生物行为和属性值,达到一个跟随时间不断变强的僵尸。