查看原文
其他

超强开发辅助语言,定义复杂实体不在话下!

发发菌 我的世界Minecraft开发者 2022-06-09

方块世界里的动物

不仅负责可爱

还能上天下地无所不能噢!

发发菌真的没骗你!

本期教程我们开启新篇章

学习如何自定义复杂的实体

不要再犹豫啦~

快来发发菌一起学习吧!


本章目录

在本章中,我们将一起更详细地学习Molang,了解Molang在实体中的应用,并一起在实体中实践Molang

1

Molang是什么?

在第一节(认识Molang)中,我们将一起认识Molang,了解Molang语言词法结构基本用法

2

用在哪里呢?

在第二节(Molang在自定义实体中的常用场景)中,我们将一起来学习Molang在实体中的应用,了解Molang的应用场景

3

哈喽呀小松鼠~

在第三节(使用配置功能创建基础实体)中,我们将使用编辑器创建一个基础的松鼠实体,作为Molang学习的演示实体

4

更好看的小松鼠~

在第四节(自定义松鼠实体资源)中,我们将一起使用Molang自定义松鼠的资源。

5

更活泼的小松鼠~

在第五节(自定义松鼠实体行为)中,我们将一起定义松鼠行为

6

准备接招吧!

在第六节(将实体动画与行为结合)中,我们将把实体的动画和行为相结合,制作一个松鼠的攻击动画

7

来不及了快跑吧!

在第七节(联动生物事件与行为)中,我们将把实体的事件和行为相结合,制作一个逃跑机制

8

看我闪电飘移!

在最后一节(挑战:制作一辆卡丁车)中,我们将进行一个挑战,一起制作一辆卡丁车


本期内容:

第一节(认识Molang)

第二节(Molang在自定义实体中的常用场景)

今天我们来学习新语言Molang,掌握了Molang我们就可以自定义更为复杂实体,把方块玩出新花样o(* ̄▽ ̄*)o!


Molang是一种基于表达式的类脚本语言,旨在使用简单的类脚本语言在低层次的系统中不脱离数据驱动实现复杂的行为。当然,在脚本中我们依旧可以使用Molang,这有助于我们轻松获取一些内部成员或旗标的,也可以实现和数据驱动复杂联动Molang常用于实体的资源控制世界生成器相关的操作中,支持单一的简单表达式和多行的复杂表达式的运算。


基本概念

Molang在数据驱动的JSON文件中经常作为一个字段的而出现,这往往是一个字符串。字符串中的表达式便是Molang表达式。比如:

"some_field": "math.sin(query.anim_time * 1.23)"

其中math.sin(query.anim_time * 1.23)便是一个Molang表达式。根据国际版最新的概念标准,一个Molang表达式(Expression)指一个被引号包裹住的全部Molang语句的总和。如果一个表达式中存在多个语句,那么每个语句都被称为一个子表达式(Sub-expression)。比如:

temp.moo = math.sin(query.anim_time * 1.23);

temp.baa = math.cos(query.life_time + 2.0);

return temp.moo * temp.moo + temp.baa;

这里有三个语句,分别是两个赋值语句和一个返回语句。这三条语句中的每条语句都被称为一个Molang子表达式。在最新的标准中,我们不用“脚本”一词来称呼Molang

通过上面的例子,我们可以看出,Molang表达式存在两种形态,一种是只包含一个语句的表达式,并且结尾不存在,作为“结束标识”,这种表达式称为简单表达式(Simple Expression)

另一种是包含多个子表达式(即多个语句)的表达式,每个子表达式的结尾必须存在一个,作为“结束标识”,这种表达式称为复杂表达式(Complex Expression)。两种表达式均可作为Molang参数写在JSON字段的值中。复杂表达式在作为字符串值时,所有的语句可以写在同一行中,也可以换行且保留缩进,类似于:

向上滑动阅览

{

    "some_field": "

        temp.moo = math.sin(query.anim_time * 1.23);

        temp.baa = math.cos(query.life_time + 2.0);

        return temp.moo * temp.moo + temp.baa;

    ",

    "some_other_field": "something"

    // ...

}

《我的世界》可以解析这种换行的Molang字段,也仅有Molang字段可以如此这样解析。如果某个字段只接受普通的字符串,则引擎无法像上面的例子一样换行保留缩进地解析。

每个Molang表达式都必须返回一个值简单表达式将返回该语句本身计算的值。如果该语句是一个布尔校验,则返回布尔校验的结果值true等价于【1.0】false等价于【0.0】。如果该语句没有产生值,将返回【0.0】

如果是复杂表达式,则需要使用return子表达式返回值。如果不存在这种返回语句,则不论中间计算多么“激烈”,最后都将只返回一个【0.0】


运算符和关键字

Molang中有多种运算符关键字,以下是发发菌给大家整理的完整列表。值得注意的是,除了字符串内的内容外,其他地方Molang中的字母都是大小写不敏感的,也就是说,同一个字母的大写和小写没有任何分别

(滑动查看完整列表或复制链接至浏览器打开

https://g.126.fm/02b4dLS)

上述运算符关键字详细用法可以参考下方链接:


复制下方对应链接到电脑浏览器并打开,查看[bedrock.dev上托管的Molang文档]:

https://www.python.org/downloads/



查询函数

在上面列出的关键字中,存在一种比较特殊的变量类型,被称为查询函数(Query Function),它的语法query.function_name,其中function_name为某个查询函数名。顾名思义,查询函数是用于查询一个属性的值的函数。包括全局参数(Global Parameter)实体成员(Entity Member)实体旗标(Entity Flag)在内的各种各样的都可以被查询函数所查询。查询函数分为无参查询函数带参查询函数

(1)无参查询函数:

指的是不具有参数表的查询函数。对于这种查询函数,直接写出其函数名即可获得对应属性的返回。比如本节最开头例子中的query.anim_time,便是用来查询一个全局参数anim_time的无参查询函数。

(2)带参查询函数:

指的是比如先传入一些参数作为基础查询特定的值的查询函数。这样的查询函数末尾需要紧跟一对圆括号( ),然后在圆括号内写入参数表。比如下面的例子中的query.get_nearby_entities函数,他的第一个参数接受一个数字值,第二个参数接受一个字符串

v.x = 0;

for_each(v.pig, query.get_nearby_entities(4, 'minecraft:pig'), {

    v.x = v.x + v.pig->query.get_relative_block_state(0, 1, 0, 'flammable');

});

具体的查询函数列表依旧可以在bedrock.dev上托管的Molang文档中找到。在版本列表中选择与当前《我的世界》中国版相吻合的版本即可查看相对应版本查询函数列表(链接在上文请自行查询哦)


自定义变量

Molang中具备各种变量(Variable)。变量可以用来存储一个值,以供后续访问。Molang的变量存在三种类型,分别是实体变量临时变量上下文变量

(1)实体变量(Entity Variable):

是一种存储在实体上的变量,这里的实体指的是广义的实体,即ECS框架中的实体,包括了方块物品粒子世界生成器等。实体变量的生存周期与实体相一致,当实体在内存中被销毁时(比如实体在世界中消失),它的实体变量也将随之销毁而变得无法访问。这种变量我们使用variable.variable_name语法定义。

(2)临时变量(Temp Variable):

是一种存储在暂存器中的变量,这种变量在他们所定义的作用域中是有效的,在作用域的运算结束后将被销毁。不过,由于一些缺陷,目前的临时变量生命周期依然是全局的,所以在给临时变量命名时需要格外小心。这种变量使用temp.variable_name语法定义。

(3)上下文变量(Context Variable):

是一种只读变量,只能由硬编码的游戏引擎定义。这种变量是依赖于游戏运行状态的上下文而存在的。比如在铁砧上下文中,存在context.other代表铁砧的第二个输入槽位。如果脱离了铁砧的环境,将不存在该变量。这种变量使用context.variable_name语法定义。

开发者可以灵活地自定义实体变量临时变量,在合适的时机改写访问各种变量的,从而使开发事半功倍!٩(๑˃̵ᴗ˂̵๑)۶


Molang在附加包各处都有应用,但是如果要说应用次数最多的场景,那当自定义实体莫属。本节中我们一起来了解Molang自定义实体中的应用。


控制实体渲染

在之前的教程中我们了解过实体的渲染控制器。在实体的渲染控制器中,通过Molang可以来控制实体的几何材质纹理的选用。

(戳图回顾渲染控制器相关教程)

一般来说,我们存在两种比较常见的控制方法。第一种是使用数组,第二种是使用三元条件运算符【?:】

向上滑动阅览

"geometry": "array.crossbow_geo_frames[query.get_animation_frame]",

"materials": [

  { "*": "variable.is_enchanted ? material.enchanted : material.default" }

],

"textures": [

  "array.crossbow_texture_frames[query.get_animation_frame]",

  "texture.enchanted"

]


在上面的例子中,我们可以看到几何纹理都是使用了数组,通过query.get_animation_frame查询的值作为数组下标索来得到对应的几何纹理。而材质则是使用了三元条件运算符,通过对变量variable.is_enchanted的值进行布尔校验来决定是使用附魔材质默认材质。


参与动画表现力

我们曾了解过,动画中也可以使用Molang表达式来控制一个骨骼的某个通道的。比如之前水鸭的移动动画

向上滑动阅览

"animation.teal.move": {

  "loop": true,

  "anim_time_update": "query.modified_distance_moved",

  "bones": {

    "leg0": {

      "rotation": ["math.cos(query.anim_time*38.17)*80.0", 0, 0]

    },

    "leg1": {

      "rotation": ["math.cos(query.anim_time*38.17)*-80.0", 0, 0]

    }

  }

}


可以看到动画中使用了$\mathrm{cos}(query.anim_time \times38.17)\times80.0$$\mathrm{cos}(query.anim_time\times38.17)\times-80.0$分别控制了leg0和leg1在$yOz$平面的旋转角度query.anim_time是当前动画的播放时间,默认单位为(s),不过我们这里使用了anim_time_update字段更改了动画时间流逝速度,因此变为了使用移动速度来控制动画流速。且需要注意的是,math.cos三角数学函数只接受角度制的值作为输入,并输出对应的三角函数值


行为与动画结合

我们可以通过在动画中调用一些Molang变量使得动画行为相结合。当一个实体的行为包组件中定义了攻击的AI意向时,便可以在其动画中访问到一个正确的variable.attack_time变量,这个变量对于非玩家实体代表着其攻击时间。当该实体不攻击时,则返回【0.0】。对于玩家来说则会返回一个攻击动画完成的百分比,取值为【0.0】【1.0】。下面发发菌以僵尸的徒手攻击动画为例:

向上滑动阅览

"animation.zombie.attack_bare_hand" : {

  "loop" : true,

  "bones" : {

    "leftarm" : {

      "rotation" : [ "-90.0 - ((math.sin(variable.attack_time * 180.0) * 57.3) * 1.2 - (math.sin((1.0 - (1.0 - variable.attack_time) * (1.0 - variable.attack_time)) * 180.0) * 57.3) * 0.4) - (math.sin(query.life_time * 76.776372) * 2.865) - this", "5.73 - ((math.sin(variable.attack_time * 180.0) * 57.3) * 0.6) - this", "math.cos(query.life_time * 103.13244) * -2.865 - 2.865 - this" ]

    },

    "rightarm" : {

      "rotation" : [ "90.0 * (variable.is_brandishing_spear - 1.0) - ((math.sin(variable.attack_time * 180.0) * 57.3) * 1.2 - (math.sin((1.0 - (1.0 - variable.attack_time) * (1.0 - variable.attack_time)) * 180.0) * 57.3) * 0.4) + (math.sin(query.life_time * 76.776372) * 2.865) - this", "(math.sin(variable.attack_time * 180.0) * 57.3) * 0.6 - 5.73 - this", "math.cos(query.life_time * 103.13244) * 2.865 + 2.865 - this" ]

    }

  }

},


可以看到variable.attack_time作为一个类似于查询函数作用的变量,传入了旋转通道的各个分量中。


行为包实体组件和事件

实体的行为包定义文件中常常会定义一些组件和事件。Molang可以用来控制组件中相关变量的计算和事件的触发。比如,几乎所有的实体都会定义一个经验奖励组件,用于计算掉落经验值

"minecraft:experience_reward": {

  "on_bred": "Math.Random(1,7)",

  "on_death": "query.last_hit_by_player?Math.Random(1,3):0"

}



联动模组SDK

模组SDK中可以使用queryVariable引擎组件来注册一个自定义查询函数。比如,以下代码便可以在客户端中注册一个query.mod.is_custom_material查询:

向上滑动阅览

import mod.client.extraClientApi as clientApi

compFactory = clientApi.GetEngineCompFactory()

# 注册一个自定义材质切换的查询函数

comp = compFactory.CreateQueryVariable(clientApi.GetLevelId())

result = comp.Register('query.mod.is_custom_material', 0.0)

# 更改该查询函数的值

comp.Set('query.mod.is_custom_material', 1.0)


之后我们便可以在动画控制器中用query.mod.is_custom_material来控制实体的材质,而query.mod.is_custom_material则可以由Python脚本来控制,从而做到脚本使能资源控制

今天带大家认识了新朋友

之后也要和它多多“来往”哦!

下期发发菌带大家深入学习

通过配置实体“小松鼠”

一起解锁更多复杂方的奥秘

敬请期待啦!


以下哪项不是关于复杂表达式的说法?

A. 包含多个子表达式

B. 结尾不存在

C. 可作为Molang参数写在JSON字段的值中

D. 作为字符串值时可以换行且保留缩进

活动规则(注意,规则有所调整哦~):

① 活动截至2022年5月31日18:00:00,在评论区写下答案与解析,我们将在回答正确的开发者中随机抽取一位送出【神秘周边】*1

每个微信账号仅有一次参与本期活动的机会每月每人仅可中一次【神秘周边】

③ 答案、中奖名单及兑奖说明详见下期教程

上期答案:B

恭喜@焰 获得【神秘周边】一份!请于本月5月31日内在评论区回复“姓名+地址+联系方式”(非精选不会显示),周边将统一通过邮递发放~



开发养成企划

五月的活动现已开启,参与范围包括2022年5月1日00:00:00-5月31日23:59:59期间发布的所有教程,奖品更新为【熊猫伞】

戳这里回顾活动详情积极参与踊跃留言!说不定五月的优秀课代表就是你哦~


前篇回顾


天太黑找不到回家的路?发发菌教你不费末影珍珠也能“秒回家”!

更多开发教程


 看我的完美武器

会“吸血”的钻石剑?拿来吧你!

你们要悄悄学会改造武器工具,惊艳所有人!

炫到没朋友的模组是怎样炼成的

快速入门特效绘制!给你的模组加点“料”~

特效绘制的进阶操作,让你的模组升个级~

插件新手不迷路

走出插件开发新手村,码住这篇就够了!

云服务器已连接,这波真的“云”开发了!

超五十款插件随意使用,三步感受当服主的快乐!


针对“MC开发大师成长指南”

我们还设置了课程问卷

欢迎大家对课程提出宝贵的建议~

点击文末“阅读原文”来填写问卷吧!


将“我的世界Minecraft开发者”设为星标

↓第一时间掌握开发圈新鲜事↓


关注“我的世界Minecraft开发者”,世界在你手中

戳戳在看/点赞

实体进阶知识一网打尽~

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存