Learn-mindustry-logic-A4

来自Mindustry中文wiki
绿豆留言 | 贡献2026年1月21日 (三) 18:23的版本
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)

引言

这是一本逻辑书, 用于入门名为逻辑语言的一门编程语言

这门语言是来源于游戏 Mindustry 游戏中的一种类似汇编的语言,
用于控制游戏中的各种东西.

由于其可以和 '显示屏' '信息板' '按钮' '炮台' 等交互, 所以可以制作一些交互式工具

本书基于的游戏版本约为 146.0

欢迎对本书提出建议

Author: A4-Tacks
repo: https://github.com/A4-Tacks/learn-mindustry-logic

最新的单文件 Markdown 或网页 (html) 可以在 https://github.com/A4-Tacks/learn-mindustry-logic/actions 找到

目录


Index

引言


认识逻辑

逻辑是什么? 逻辑是 Mindustry 中的一系列建筑, 以下是常用的不完全列表:

  • 微型处理器
  • 逻辑处理器
  • 超核处理器
  • 世界处理器

这些建筑, 统称为逻辑块,
可以在上述建筑中, 编写类似汇编1的语言, 来自动处理一些事.

以及一系列常用的配套设施, 如:

  • 内存元 / 内存库 / 世界内存元
  • 按钮
  • 信息板 / 显示屏 / 大显示屏

当然, 逻辑普遍有两种形式, 一种是文本形式, 一种是在游戏内置的编辑器中的形式.
此教程更加偏向于前者, 所以会稍显硬核一些, 可以在游戏中对照, 方便理解

开始这一切

基本的概念已经说了一些, 接下来让我们写出第一个逻辑, 标志着本教程的正式开始

首先, 在游戏中找一个位置, 放置微型处理器, 然后放置一个信息板.
接下来, 寻找一个地方编写以下文本, 如备忘录 信息板 记事本 等

print "Hello, World!"
printflush message1

接着, 将这些文本复制, 然后在游戏中, 点击逻辑块, 再点击弹出的按钮.
你会看到一个黑色背景的界面, 你可以在底部找到几个按钮.

点击编辑, 并从弹出窗口中点击从剪贴板导入.
接着, 点击返回, 退出这个编辑界面.

然后继续点击逻辑块, 再点击信息板, 你会看到信息板被一个蓝框框住了,
这个被称作链接, 你可以使用逻辑块在一定范围内链接建筑, 详细用法后文会讲.

此时, 随便找个空地点击, 退出逻辑的选中状态,
再去点击信息板, 你应该可以看到信息板中已经存在一行字: Hello, World!.

恭喜你, 你已经成功写出了一个逻辑程序!

如果此教程劝退了你, 或自觉基础足够, 你可以直接跳转到 25-编译器 章节,
以高效的方式编写复杂逻辑

或者也可以翻看目录中的相关附录, 也比较有用


目录
下一章


认识字面量

在上一章, 我们成功的写出了一个逻辑程序,
它的作用是将连接的一个信息板中内容设置成一段话.

还记得我们编写的"Hello, World!"吗? 可以注意的,
信息板中被更改成的内容正是双引号之间的内容.


接下来, 我们开始认识几种字面量

字符串

一些字, 但不能包含双引号和换行, 由双引号包裹起来

你可以在其中编写一些特殊的格式, 比如\n, 这会使其另起一行,
比如[red], 这会改变之后的文本颜色

这只能使用英文引号, 也就是 "", 而不是中文引号 “”, 许多逻辑新手都会犯这个错误,
要养成看变量表2的好习惯, 可以从变量表中发现异常的变量命名与字符串引号问题

如果你有其它编程语言的经验, 那需要额外注意一下,
该字符串并不支持转义反斜杠本身, 即 "\\n" 不会输出 \n, 而是 \ 并新起一行

数字

用来表示一个数字, 有以下几种格式

  1. 整数, 例如 123-20-0
  2. 带小数的数, 例如 1.2-1.512.34
  3. 简易科学记数法, 例如 2e3, 相当于数学中的 2 × 10³,
    -2e32e-3 同理, 注意并不支持 1.2e3 这种小数前缀的科学记数法
  4. 二进制, 例如 0b101 表示 5, 而 0b-101 表示 -5
  5. 十六进制, 例如 0xFF 表示 255, 而 0x-80 表示 -128

在高版本中 (约为 148) 中, 扩展了 二进制、十六进制 的格式,
在旧版本中只能使用如 0b-1010x-80 等格式表示负数

而在高版本中, 可以使用更符合直觉的 -0b101-0x80 等格式表示负数了,
且支持正数符号 +0b101

颜色数

用来表示一个特殊的数字, 以表示颜色

RGB 格式: %ff1bcc
RGBA 格式: %ff1bccff
命名格式: %[red]

产生的特殊数字如 14-打包颜色

变量

在不符合上述任意形式, 且不含双引号、井号、分号的任意连续非白符号

有些你认为不是变量的东西实际上也是变量, 只不过是不可修改的内置变量

例如 truefalsenull, 或者是后续 sensor 使用到的如 @x 等都是变量

详情请参阅 附录03-环境变量


上一章
目录
下一章


什么是变量

变量, 是逻辑中不可或缺的一个东西. 它的意义是, 在某个时间和位置, 储存一个值.

逻辑顺序执行每一条语句 (通常一行是一条语句), 而执行到末尾会回到开头重新执行

会通过一些示例来描述类型方便理解, 但是用到了后面学的一些东西, 也可以先翻翻

逻辑是支持注释的, 使用一个井号#来表示, 在其之后直到一行末尾的内容都会被忽略,
可以用来描述代码的作用, 来方便阅读

逻辑也支持缩进, 也就是行开头的空格或者制表符, 也会被忽略,
可以用来将代码分成嵌套的块, 来让其更具有层次感, 使其更好阅读

逻辑也支持空行, 可以把相关度较低的部分用空行分隔开, 可以让代码更好阅读

在逻辑中, 变量值有以下几种类型

(数值) number

普通的数字类型, 如字面量中的数字, 其类型就是这个

set a 1
set b 2.5
set c -5
print a; print ","; print b; print ","; print c

(空值) null

表示什么都没有的一种类型, 例如发生无效数学运算比如除以 0 后,
或者一个还没有赋值3的变量, 其值类型都会是 null

# a 未赋值, 所以是null值
op div b 3 0
print a; print ","; print b

(字符串) string

表示一段文本, 比如字面量中的字符串, 其类型就是这个

set str "Hello, World"
print str

(内容) content

表示某个游戏内容, 例如某种建筑, 某种单位等,
通常在 "核心数据库" 中看到的大多数内容都能用 content 来指代

建议直接使用 "content" 来说明,

而不是 "内容"、"类型"、"种类" 等用词, 以防引起误解

set a @sorter # 路由器的 content
getlink building 0
sensor c building @type
print a; print ","; print c

@sorter 是一个内置变量, 每种建筑、单位等都有一个 content 和与之对应的内置变量,
内置变量通常以 @ 开头, 并且大部分在变量表中都是隐藏的,
如果你查看变量表时看到以 @ 开头的变量类型为 null 的话很有可能你变量名写错了

可以使用 sensor result input @type 获取 building 和 unit 的 content

(建筑) building

表示某个特定的建筑, 而不是某类建筑 (content)

这和 content 不同, content 可以描述某类建筑, 例如路由器 (@sorter),
而 building 描述了某个建造出来的建筑, 比如链接到的 sorter1

getlink a 0
print a # 可能输出和 content 的结果很像, 变量表能看到类型

(队伍) team

这个值表示某个队伍, 一般出现的很少不用去理会

set a @crux # 表示红队, 通常是敌方
print a

通常用于世界处理器4, 较少的用于 sensor 语句的 @team 属性用于辨别队伍

(单位) unit

这个值表示某个特定的单位, 而非某类单位 (content)

# 注: @unit 是一个环境变量, 代表目前绑定的单位, 类型是 unit
ubind @flare # 绑定一个场上的新星
sensor x @unit @x # 获取其x坐标
sensor y @unit @y # 获取其y坐标
print @unit; print ": "; print x; print ","; print y

(枚举) enum

这个值很普通, 一般是在某些特殊的地方代表某一个具体的意义, 不用去深究

# 注: 环境变量是自带的变量, 后面会讲
set x @type # 比如 sensor 的选项就是一个环境变量, 类型是 enum
print x

(未知) unknown

一般除非修改游戏源码, 或者使用mod什么的方式创建特殊的东西, 否则几乎不会遇到


好了, 值的概念已经明白了, 接下来我们将在下一章了解变量的常见操作


上一章
目录
下一章


修改变量

这一章会介绍如何的去修改变量

set

这是一个普通的语句, 有两个参数, 行为是直接将右边参数5的值赋值6给左边

set msg "a value"
print msg
printflush message1

以上代码我们直接在print的参数中输入了一个变量,
当一个普通参数中是变量时, 那么赋值或者获取值都是针对变量所表示的值来说的

以上代码会在信息板中输出 a value, 可以发现, 确实是使用了变量的值

op (operation) (操作/运算)

这是对1至2个参数进行某些运算后, 并将值赋给另一个变量的语句

set m 2
op add n m 3
print m
print "\n"
print n
printflush message1

此处的add表示相加, 并且这不是一个普通的参数, 并不会像变量那样变成变量代表的值.
而这个参数用于表示对参数使用相加操作

它将当前m的值加上了3赋给n, 而此时此地, m的值是2, 所以n在这里被赋予了值5,
它应打印出

2
5

全部的算符参考这里附录01-op方法

op 和之后学习的 jump 语句除了进行严格相等(strictEqual)时之外,
都会尝试将两个运算成员转换成同一类型再运算,
类型不同或不支持时基本都是往数字转换

对于0""null的值, 都能被转换成数字0, 反之则为数字1

也就是说对于op add n "x" "y", n会等于2

在 op 中, 除了相等、不等和严格相等, 都不支持数字以外的类型进行运算

在游戏中, 通常需要观察逻辑实际是怎样运行的, 可以打开变量表 (游戏内逻辑编辑器下方 '变量' 按钮),
变量表可以看到当前所有普通变量的值和类型, 其中 @counter 变量指示了将要执行哪一行,
也可以配合游戏暂停来研究复杂的情况

: 变量表的刷新频率较低, 可能会对 @counter 的变化产生误判


上一章
目录
下一章


跳转

跳转语句(jump), 可以在条件满足时, 跳转到某一行中.

行号的起始是0, 并且可以用分号;来分隔行, 也可以用标记8来作为跳转的目标点.

如果选择在游戏外手写的话, 最好是使用标记进行跳转, 否则行号处理很费劲.

目前所拥有的条件有:

条件 含义
equal 两值相等则成立
notEqual 两值不等则成立
lessThan 两值成小于关系则成立
lessThanEq 两值成不大于关系则成立
greaterThan 两值成大于关系则成立
greaterThanEq 两值成不小于关系则成立
strictEqual 两值类型和值都相等则成立9
always 永远成立

示例代码, 这将会打印出0,1,2,3,4

set i 0
loop1:
    jump sk1 equal i 0 # 在头部跳过打印逗号
    print ","
sk1:
    print i
    op add i i 1
jump loop1 lessThan i 5
printflush message1

上述代码再从游戏中导出后jump会变成行号的形式, 如下

set i 0
jump 3 equal i 0
print ","
print i
op add i i 1
jump 1 lessThan i 5
printflush message1

对于 equal, notEqual, strictEqual, 是可以判断特殊类型的变量的,
通常所有变量类型都可以被判断

例如判定两个 unit 是不是同一个单位, 判定两个 content 是不是同一个游戏内容,
判定两个 string 中的文本是否相同, 等

逻辑中有1000的语句数上限, 即不含跳转标记、注释的情况下最多只能写一千条语句
(set a 1; set b 2 这种用分号分隔的也算两条语句)

op 语句中, 与 jump 语句中同名的条件有着相同的效果,
当条件成立时, 值为 1, 否则为 0

关于标记实现的坑

逻辑块解析跳转标记时, 不会将最后一行的标记链接到最头部, 例如如下代码

set var true
jump label always 0 0
    print "skipped"
label:

导入逻辑块时, jump 的跳转目标为 -1, 也就是未连接任何目标, 就像这行什么都没做

要是想要如同预期的那样跳转到最后一行然后回到首行,
我们需要将标签挪至首行或者使其不在尾部, 例如如下两种做法

label:
set var true
jump label always 0 0
    print "skipped"
set var true
jump label always 0 0
    print "skipped"
label:
end # 这样会多执行一行 end, 或许会变慢?

使用 select 简化通过条件选择值

在 150 版本中, 加入了一个新语句: select

该语句通过一个单一的 jump 条件, 在两个值中选一个赋值给结果, 例如:

set a 2
set b 3

select result lessThan a b 3 5

print result
printflush message1

相当于

set a 2
set b 3

jump yes lessThan a b
    set result 5
jump finish always 0 0
yes:
    set result 3
finish:

print result
printflush message1

对于这种通过单一条件在两值中选一个的情况,
可以使用 select 更为方便并且语句数更少


上一章
目录
下一章


环境变量

在上一章我们讲了变量, 这章我们讲环境变量 (也被称作内置变量 built-in variable)

环境变量是不需要去赋值或使用就已经存在的变量, 虽然大部分都不会显示在变量表中.

环境变量大多不可赋值, 但是较少环境变量是可赋值的

一些环境变量会自动改变, 比如@time会随着游戏时间改变

常见的一些环境变量, 例如:

  • true: 1
  • false: 0
  • null: null
  • @copper: 铜的 content
  • @time: 目前游戏进行的时间 (秒)
  • @links: 当前链接的建筑总数
  • @unit: 当前绑定的单位
  • @counter10: 执行完这行后要执行的行号 (可被赋值)
  • @this: 逻辑块自身, building 类型
  • @thisx: 逻辑块的x坐标, 类似sensor thisx @this @x

并且, 当逻辑链接建筑时, 会生成一个以建筑短名称11加同类建筑数量组成的环境变量,
来指向这个建筑, 变量类型是 building

例如链接两个信息板就会得到 message1message2, 其短名称是message

当你解除某个建筑的链接, 建筑和编号12会保留, 也就是说,
只要下一次链接的建筑相对逻辑的朝向和位置都相同时, 还会是同一个编号

如果在同一个位置链接了另一名称的建筑, 那保留建筑的会被换成新的建筑

较为完整的环境变量列表可以参考 附录03-环境变量


上一章
目录
下一章


打印与绘制

打印

还记得上文中, 我们使用的print(打印)语句吗?

这个语句接受一个参数, 会将参数值附加到预打印区, 一般值的结果就是变量表中看到的那样

printflush也接受一个参数, 它的作用是将预打印区全部内容一次性输出到某个信息板建筑中.
(有字数上限, 约为220字)

绘制

draw语句, 它有一个特殊参数, 和六个普通参数

特殊参数接受的是绘制方法, 普通参数是绘制的数据

绘制的方法都有以下几种

描述 参数
全屏覆盖为某颜色 clear r, g, b
设置颜色 color r, g, b, 不透明度
用打包颜色设置颜色 col 颜色
设置线条或轮廓粗细 stroke 粗细
绘制线 line x1, y1, x2, y2
绘制矩形 rect x, y, 宽度, 高度
绘制矩形轮廓 lineRect x, y, 宽度, 高度
绘制圆 poly x, y, 边数, 半径, 角度
绘制圆轮廓 linePoly x, y, 边数, 半径, 角度
绘制三角形 triangle x1, y1, x2, y2, x3, y3
绘制图像 image x, y, 图像13, 大小, 旋转角度
打印文本 print x, y, 对齐方式14
改变位置属性 translate 原点x轴偏移, 原点y轴偏移
改变比例属性 scale x轴拉伸比例, y轴拉伸比例
改变旋转属性 rotate 坐标系要旋转的角度
设置属性至默认 reset

没用到的参数位置以0填充, 当然你也可以写点其它的或者不写

draw就像print那样, 会把操作堆积在逻辑的预绘制缓冲区中,
直到使用drawflush才会输出到建筑

一个使用例子, 如下

draw clear 0xFF 0xAD 0xAD 0 0 0    # 清空背景
draw color 0x80 0xED 0x99 0xFF 0 0 # 设置绘制颜色
draw rect 20 20 40 20 0 0
drawflush display1

将逻辑连接一下小显示屏, 上述代码会以粉色的背景绘制出一个绿色的长方形

小显示屏的可绘制范围是80x80, 大显示屏的可绘制范围是176x176

计算公式是 方块宽高 * 32 - 16, 其中 16 是两侧边框

关于某些细节:

  • 打印文本 (print) 将预打印区的文字输出到预绘制缓冲区中,
    仅支持以英文为主的部分文字
  • 属性的改变是在上一次的基础上改变的,
    例如 draw translate 20 0 后原点被移至 20,0,
    再执行 draw translate 10 5 后原点是移至 30,5 而不是 10,5
    改变属性后, 所有绘制语句的坐标都会根据属性操作,
    例如原点坐标改变至 20,40 后,
    draw rect 5 5 2 2 将会在 25,45 处绘制, 即偏移原点后的坐标
    : draw scale 改变的比例有一定的下限, 改变过小并不会生效,
    例如 draw scale 0.001 1 并不会拉伸 x 轴
  • 清屏 (clear) 在低版本中, 输出时会位于其它绘制前,
    而在高版本中, 不会被排序到其它绘制之前,
    部分旧逻辑将 clear 放置在其它绘制之后, 导致在高版本中无法显示内容

从 draw 开始, 一些语句的记忆负担就有些重了, 如果你使用 VSCode 可以考虑使用片段补全,

bang-lang-releases 中下载后缀为 .vsix 的文件, 作为 VSCode 外部插件,
安装后建立一个后缀为 .mdtlbl 的文件即可补全代码片段

这些代码片段大部分可用于逻辑, 少部分片段带有单引号, 用于逻辑需将单引号手动删除,
还有一些片段属于 bang-lang 专属可以不用管, 例如 if 片段无法在逻辑使用

格式化

这是在146版本之后添加的语句, 写为format, 有一个普通参数

将预打印区中{0}{1} ... {9}中数字最小的首个内容替换成指定参数

printflush message1 # 先清空内容

print     "{3}-{1}-{3}-{1}"
format 8 # {3}-8-{3}-{1}
format 9 # {3}-8-{3}-9
format 2 # 2-8-{3}-9
printflush message1

stop # 自此停止执行

可以挪动printflush的位置, 来观察替换结果是否符合上述描述

这个 format 和别的常用语言中设计的 format 不太一样,
别的语言通常是替换所有, 而这个则是只会替换首个

例如 format("{0}-{0}", 2) 可能希望它是 "2-2",
但是逻辑中 print "{0}-{0}"; format 2 实际上是 2-{0}

如果你用 format 来填充字符串, 内容中依旧含有 {0} 这类内容, 结果可能会很奇怪,
例如: print "1. {1}\n2. {2}"; format "{1}"; format 2; printflush message1

打印字符

这是在146版本之后添加的语句, 用于将一个整数表示的 utf-16 单元打印出来

printchar 20320 # 你
printchar 22909 # 好
printflush message1

配合另一个语句可以实现读取打印字符串中任意字符,
参考 读取字符

由于打印的是 utf-16 单元, 部分生僻字或者不常见字符,
超出一个 utf-16 单元表达的字符将无法正确处理

printchar 可以输入 content, 例如 @copper, 用于打印对应的图标


上一章
目录
下一章


获取链接

上文中,
我们说过 逻辑每链接一个建筑时, 会以建筑的部分名称生成一个表示该建筑的环境变量,
我们可以通过这个环境变量获取这个建筑, 并与之交互.

但是如果我们想批量获取全部或者某一部分链接的建筑时, 就很费劲了.
所以这章我们将学习getlink语句, 来直接获取第n个链接的建筑

getlink 顾名思义, 获取链接. 拥有两个参数,
它会将右侧参数 n 当成一个整数, 并把建筑值赋值给左侧参数.

编号 n 从 0 开始计数, 基本逻辑中你能遇到的编号序号等都是以 0 开始.

比如以下在每一个链接的信息板中打印这个信息板的编号

set n 0
loop:
    getlink msg n
    print n
    printflush msg
    op add n n 1
jump loop lessThan n @links

环境变量 @links 是当前链接的建筑数量

运行上面这段逻辑, 我们可以发现, 一个新链接的建筑直接编号到最末尾,
但是取消链接某个建筑再重新链接, 它还会是之前的编号.

通过简单的从0一直循环增加到@links, 我们可以简单的对所有链接的建筑进行操作

当然你也可以不从0开始, 跳过某些事先链接好的建筑

使用取余简化

对于一些常用情况, 想简单的让逻辑操作每一个链接的建筑, 那么可以将上述写法简化

getlink msg n
print n
printflush msg
op add n n 1
op mod n n @links # 当n==@links时, 由于取余的性质n将变为0

这种用法对于简单的逻辑, 非常方便

并且相较于之前的方法, 可以在循环开始时少执行一个 set

只是这种方法更为死板一些


上一章
目录
下一章


传感器

在前文, 我们学到了一些必要基础的操作, 接下来我们将开始学习如何从游戏中获取信息.

sensor语句, 这是获取信息的第一渠道.

在游戏中, 无论是建筑还是单位, 都有着各种属性, 比如大小、朝向、承装的物品等.
而想要获取这些, 通通都需要使用sensor来进行.

sensor的参数有三个, 分别是返回值、建筑和属性.
当然在游戏中编辑器参数顺序有些不同, 在游戏中的顺序是返回值、属性和建筑.

sensor所使用的属性参数, 是直接从环境变量中获取的,
不需要关系它们实际表示了什么值, 只需要知道它可以告知sensor取什么属性即可.

以下是一个代码示例, 可以打印出每个链接的建筑坐标

set i 0
loop:
    getlink block i
    sensor x block @x
    sensor y block @y
    print "id: "
    print i
    print ", x: "
    print x
    print ", y: "
    print y
    print "\n"
    op add i i 1
jump loop lessThan i @links
printflush message1 # 仅在遍历完成后一起输出

注: 上文中使用到的名词 遍历 大致表示对某一些东西全部操作一遍

全部的属性参考这里附录02-传感器选项

有时游戏内逻辑编辑器显示的参数顺序, 和实际导入导出的参数顺序是不一样的,
例如 sensor x block @x 游戏内显示为 Sensor x = @x in block


上一章
目录
下一章


控制

在上文, 我们已经学习了sensor(传感器)来从游戏中获取足够的信息.
在这章, 我们可以借助这些信息来操作链接的建筑, 从而帮助我们完成一些事.

control语句, 拥有一个字面参数15, 和五个普通参数.

用法如下表

模式 参数 解释
enabled 建筑, 是否启用 禁用或启用某个建筑
shoot x, y, 是否射击 控制炮台向某个点射击
shootp 目标, 是否射击 控制炮台向某个目标比如单位或建筑射击, 可以省去手动获取坐标和计算提前量
config 目标, 设置 设置某个建筑的额外配置, 比较多的是分类器选中的物品
color 目标, 颜色16 设置灯箱的颜色

以下是一段代码示例, 可以在链接的钍反应堆冷冻液少于指定值时将其禁用,
从而避免直接爆炸

getlink block i # i的默认值是null, 自动转换到数字则为0
sensor liquid block @cryofluid # 通过冷冻液的content获取反应堆冷冻液量
op greaterThan enabled liquid 20 # 判定开启标志为冷冻液量大于20
control enabled block enabled 0 0 0
op add i i 1
op mod i i @links # i自身对链接总数取余, 这样i在到达链接总数时将回绕至0

像 '是否启用' '是否射击' 等, 会判定真假值, 对于数字 0, 和空值 null 为假值, 其余则为真值,
当结果为真值则生效, 例如为真值时将启用建筑, 炮台开火, 而为假值时将禁用建筑, 炮台不开火

在高版本 (约 152) 中, control 改为无需链接, 只要被控制建筑在链接范围内就可以控制,
不确定是否是暂时的 bug, 未来或许更容易改变


上一章
目录
下一章


内存的读和写

在这一章, 我们将了解两个语句和两个逻辑建筑, 这是复杂逻辑中的常客

在游戏中, 有一类特殊的建筑, 叫内存(memory).

通常我们使用内存元(cell)或者内存库(bank), 它们可以存储一系列数字,
并且以指定的编号去访问(从0起始).

内存元和内存库的区别仅在于大小和最大编号数目.

操作内存的语句有两条, 一条是read(读), 一条是write(写).

这两个语句都有三个参数, 区别在于最左边的参数,
读语句是获取目标并赋值到参数, 写语句是求值参数并将值存储到目标.

另外两个参数分别是 操作哪个内存 和 内存中哪个编号, 用来确定目标

比如以下逻辑, 将一个数字存储到内存元中编号3位置, 并获取再打印出来

write 123 cell1 3 # 存储, 当然更合适的称呼叫做写入
read num cell1 3  # 从编号3的位置读取这个数字并赋值到num
print num
printflush message1

当然从上面这个小例子, 应该是看不出使用内存的必要性,
但是希望在后期, 在编写稍微复杂的思路碰壁时, 能想到是否可以使用内存.

内存也常常被用于多逻辑协同

上文使用的名词术语表

易于理解的 标准的
编号 地址、下标、索引
存储到 写入
获取 读取

对内存的读写并不要求链接目标内存, 具体的权限控制如下:

exec.privileged || (from.team == exec.team && !mem.block.privileged)

当你的处理器是世处, 或者你与内存队伍相同且内存不是世处内存元即可读写

读取其它逻辑变量

自 BE-25666 版本后, readwrite 的功能被进行了扩展, 可以对其他逻辑中的变量进行读写

被读写的变量必须是已声明的17

例如有逻辑块A链接信息板至message1代码如下:

print n
printflush message1

由于在 print 中使用了变量 n, 所以变量 n 被声明

而逻辑块B链接逻辑块A至processor1, 代码如下:

read local processor1 "n"
op add result local 1
write result processor1 "n"

而观察逻辑块A链接的信息板, 会发现数字在不断递增,
这是来自逻辑块B对变量n的修改导致

读取字符单元

在 146 后的某个版本中(建议 147-Beta 版本起),
read 语句可以读取字符串中某个字符的 utf-16 单元,
可以配合 printchar 使用

字符串的 utf-16 单元长度由 sensor 语句的 @size 属性来获取

例如以下示例逻辑, 将字符串内容每个 utf-16 单元后加上一个换行再打印:

set str "你好,测试"
sensor size str @size
set i 0
loop:
    read char str i
    printchar char
    print "\n"
    op add i i 1
jump loop lessThan i size
printflush message1

由于处理的是 utf-16 单元, 所以可能出现一个字符码点被分当成两个字处理的情况,
例如某个生僻字 "𪙛"

读取信息板

自 150 版本起, 类似读取字符串, read 语句还可以从信息板中读取,
不过获取长度使用 @bufferSize 而不是 @size

例如将 message1 的信息转移到 message2

set i 0
sensor size message1 @bufferSize
jump finish equal size 0
loop:
    read char message1 i
    printchar char
    op add i i 1
jump loop lessThan i size
finish:
printflush message2

读取画板 (Canvas)

自 150 版本起, 类似读写内存库, readwrite 语句还可以从画板中读取,
长度为 144 (12x12), 不过边缘容易被边框遮住

需要注意的是, 仅支持写入 [0,7] 范围的整数值

例如将 canvas1 的图形转移到 canvas2

set i 0
set size 144
loop:
    read color canvas1 i
    write color canvas2 i
    op add i i 1
jump loop lessThan i size
printflush message2

上一章
目录
下一章


其它控制流

这讲解了一些内置的其它逻辑控制语句, 相较于核心的jump, 这些语句使用频率相对较低

分别是waitendstop

  • wait 接受一个数字参数, 作用是等待一段时间, 单位为秒
  • end 不接受参数, 直接无条件跳转到逻辑起始, 相当于jump 0 always 0 0
  • stop 不接受参数, 永远卡死在这, 相当于跳向自己的无条件循环,
    但是写起来方便些并且性能有一些优化

wait 不适合做极端精确的时间等待, 但是可以做模糊的大致时间等待, 可以方便的等一会

endstop 都是目的明确的语句, 方便看懂, 且写起来更加省事


上一章
目录
下一章


根据编号获取类型

我们知道, 物品、液体、方块和单位, 都有一个content类型的变量来表示它们的类型

比如我们可以通过环境变量@copper得到一个类型为content的值, 来表示物品铜的类型.

但是, 有时我们需要依次获取所有的content值, 批量进行一些处理,
此时, lookup语句就派上用场了.

lookup 有一个字面参数, 两个普通参数, 其字面参数的取值有以下五种:

  1. block (方块 / 建筑)
  2. unit (单位)
  3. item (物品)
  4. liquid (液体)
  5. team (队伍) (这是特例, 返回值类型为team而不是content)

接下来的两个参数分别是最终要赋值到的变量, 和需要取的编号

编号从0开始, 你可以通过一个固定的编号取得某个大类中任意一个content

至于最终取到多大合适, lookup 有四个环境变量, 如@itemCount表示总编号数,
当然你也可以判定是否取到了null来决定是否完成

以下是一个不断设置分类器的小逻辑, 用于演示

set i 0
loop:
    lookup item my_item i
    control config sorter1 my_item 0 0 0
    op add i i 1
    wait 0.1 # 因为逻辑执行太快了, 所以等一会
jump loop lessThan i @itemCount

在以前的版本, 想通过content获取到lookup使用的id, 需要lookup所有物品去依次比较,
不过在146版本, sensor 可以使用@id属性, 不需要费劲了

一些简单的用途, 例如因为内存元只能传递数字,
所以想在多个逻辑之间沟通使用某个物品时, 就可以利用@id和lookup进行传递.

自 BE-25666 版本后, readwrite 的功能被进行了扩展, 可以对其他逻辑中的变量进行读写

在版本 >150 时, 有时也可以利用直接读写逻辑变量来传递物品的 content, 而不是其 @id

上一章
目录
下一章


打包颜色

这是一个简单的语句, 有五个参数, 一个是结果需要赋值到的变量,
剩下四个是四个颜色通道, 分别是 R G B A, 三种颜色和不透明度.

它在新版本被加入是为了解决一些颜色传递需要的变量太多的问题的,
把多给颜色通道挤压到单个值里面, 当然这个值的类型有点特殊,
不读游戏源码的话不建议深究.

使用到打包颜色的地方有drawcol, 还有controlcolor, 这些是已经讲过的.
没有讲过的有世处(世界处理器)的一些语句

这个语句使用的不是8位颜色, 而是归一化颜色,
也就是说颜色取值范围区间不是 0 ≤ n ≤ 255, 而是 0 ≤ n ≤ 1.

一个简单的例子, 使用packcolor来调色绘制一个红底蓝色方块

packcolor red 1 0 0 1
packcolor blue 0 0 1 1
draw col red 0 0 0 0 0
draw rect 0 0 176 176 0 0
draw col blue 0 0 0 0 0
draw rect 44 44 88 44 0 0
drawflush display1

打包颜色字面量

可以直接使用字面量来创建打包颜色, 就像创建一个数字值一样

例如:

set color %ff00ff
draw clear 0 0 0 0 0 0
draw col color 0 0 0 0 0
draw rect 30 30 20 20 0 0
drawflush display1

你可以使用如 %ff00ff 的 16 进制 RGB 颜色代码,
或者 %ff00ffff 的 16 进制 RGBA 颜色代码 来创建颜色

在高版本(约为148)中, 你还可以使用预设颜色语法, 如 %[red] 来创建颜色

拆解打包颜色

packcolor 的本质是利用给定颜色, 强行创造一个特殊的数字来表示颜色,
这给了我们可趁之机, 例如:

set color1  %1c2b3d4e
set color2 0x1c2b3d4e
op idiv depack color1 %00000001 # 运算以解包颜色

print "{0}\n{1}"
format color2
format depack # 472595790  0x1c2b3d4e
printflush message1

可以看到, 我们之间将其解开成了 RGBA 格式,
如果需要解开成 RGB 格式可以将除数改为 %00000100

如果需要获取确切的 RGB 值, 可以利用位运算来完成:

set color1  %1c2b3d4e
set color2 0x1c2b3d4e
op idiv depack color1 %00000100 # 解包为 RGB

op shr r depack 16
op shr t depack 8
op and g t 255
op and b depack 255

print "{0}\n{1},{2},{3}"
format color2
format r # 28  0x1c
format g # 43  0x2b
format b # 61  0x3d
printflush message1

在高版本 (150) 中, 添加了一个新语句可以快速完成此操作,
例如如下例子一次性将颜色中 RGBA 解出并赋值给变量 r g b a

unpackcolor r g b a color

注意: r g b a 的值并不是 [0,255] 而是 [0,1], 这和 packcolor 对应


上一章
目录
下一章


雷达

这是一个特殊的语句, 当你需要侦测敌方单位,
或者方便的关于某个建筑的己方单位时, 可以使用雷达语句.

雷达语句可以以一个建筑为中心, 以一定的范围18去搜寻单位,
并且可以指定多个筛选条件, 和排序依据, 并找到依据排序依据最符合的那个单位.

搜索的单位只有满足所有的筛选条件才会被搜索.

雷达语句参数很多, 如下:

radar, 筛选器A, 筛选器B, 筛选器C, 排序依据, 充当雷达的建筑, 是否逆序, 返回目标赋值到的变量

可用的筛选器都有以下几种:

字面参数 解释
any 任意
enemy 敌方
ally 友方
player 玩家
attacker 有攻击能力的
flying 飞行的
boss BOSS
ground 地面的

可选的排序依据有以下几种

字面参数 解释 排序依据
distance 距离 距离取负
health 血量 默认
shield 盾量 默认
armor 护甲 默认
maxHealth 血量上限 默认

以下是一个简单的小例子, 让链接的炮塔攻击距离最近的友方玩家控制的单位

getlink turret i
radar ally player any distance turret 1 unit
op notEqual shooted unit null
control shootp turret unit shooted 0 0
op add i i 1
op mod i i @links

由于距离的排序依据是距离取负, 且 是否逆序 指定为1, 也就是逆序,
所以会找到排序依据最大的那个目标, 也就是距离最近的目标(因为距离取了负数)

雷达的检测是有一定时间间隔的, 当同一条雷达语句连续快速执行, 隔一段时间才会重新搜索目标,
如果时间间隔未达到, 则返回旧目标


上一章
目录
下一章


单位绑定

这章将开启单位篇章, 这是一个比较大的篇章, 我们将从单位绑定开始

语句UnitBind, 写做ubind, 有一个普通参数, 传入需要绑定的单位content, 或者某个特定的unit

执行ubind后, 如果场上有符合的友方单位的话, 那么环境变量@unit将会被设置为这个单位(unit)

你可以对这个变量进行sensor等操作获取所需信息

简而言之, ubind 的作用就是找到一个单位并将该单位赋值给只读的环境变量 @unit

以下是一个示例程序, 它会实时打印某个玩家单位的坐标

# find player unit
set id 0
loop:
    lookup unit unit_ty id
restart:
    ubind unit_ty
    jump skip strictEqual @unit null
    set first @unit
    jump do_bind_loop_cond always 0 0
    # 这里会将该种单位依次绑定一遍, 直到找到玩家控制的那个
do_bind_loop:
    sensor is_dead first @dead
    jump restart notEqual is_dead false # 头单位已经死亡, 我们永远无法到达, 所以我们需要重新开始
do_bind_loop_cond:
    sensor ctrl_type @unit @controlled
    jump finded equal ctrl_type @ctrlPlayer # 绑定到的是玩家
    ubind unit_ty
    jump do_bind_loop notEqual @unit first
skip:
    # 该种单位全部绑定了一遍, 未找到玩家单位, 尝试下一种单位
    op add id id 1
jump loop lessThan id @unitCount
end

finded:
set player @unit
follow_loop:
    sensor x player @x; sensor y player @y
    sensor name player @name # 如果有, 则可以显示玩家名
    # 取两位小数避免小数部分过长
    op idiv x x 0.01; op idiv y y 0.01 # 通过整除省去乘再向下取整运算
    op div  x x 100;  op div  y y 100
    print "player "; print name; print "[]: "
    print unit_ty; print " -> "
    print x; print ", "; print y
    printflush message1
sensor ctrl_type player @controlled
jump follow_loop equal ctrl_type @ctrlPlayer # 仅在玩家控制期间进行执行, 玩家解除控制重新寻找
printflush message1 # clear message

单位绑定的扩展用法 (绑定具体单位)

ubind 可以直接输入一个 unit , 而不是一个 content

当输入一个 unit 时, ubind foo 效果类似 set @unit foo

可以用于一些巧妙的用途:

  • 快速绑定曾经绑定过的单位
  • 绑定来自雷达 (radar) 获取的友方单位
  • 世处绑定来自 fetch 等语句获取的单位

非特权处理器 (世界处理器) 使用此用法依旧不能绑定和控制其它队伍单位

单位绑定的顺序

当使用content绑定单位时, 顺序较为随机

通常只需要关注, 通常在单位数量不变时,
绑定的顺序不变且总是会刚好每个单位绑定一次, 直到没有下一个单位时再从头开始

一个不确定是否稳定工作的特性:
在直接绑定一个unit而非content时, 不会影响绑定content时将要绑定的下一个单位

如果有单位减少将会使用置换删除, 将表中最后的单位交换到删除的单位空位中,
研究源码的话可以看看, 可能一些极端环境可以利用该顺序

简单的单位绑定例子

计数某种单位数量:

restart:
    set count 0
    ubind @flare # 绑定星辉, 想计数其它单位可改成其它单位或使用 lookup
    jump finish equal @unit null
    set head @unit

loop:
    sensor dead head @dead
    jump restart notEqual dead false # 如果首个单位死亡, 则永远绑定不到, 重新计数

    op add count count 1
    ubind @flare
    jump loop notEqual @unit head

finish:
    print count; print " "; print @unit
    printflush message1

上一章
目录
下一章


单位控制

在上一章, 我们学习了单位绑定, 这章我们学习单位控制(UnitControl)语句,
写作ucontrol. 它有一个字面参数, 五个普通参数.

本章只说要点, 语句具体用法过多, 建议直接进入游戏内逻辑编辑器查看说明,
一般是长按选项, 或将鼠标悬停至选项上方.

查看说明配合参数旁的文字, 以及靠经验还有字面参数的命名, 应该可以直接了解大致用法.

首先, 单位语句(米黄色语句)中, 除了ubind, 其它的语句都属于控制语句.
此类语句会控制当前绑定的单位, 也就是@unit, 同一时间只能绑定一个单位.

这些控制语句一旦运行, 会给当前绑定单位发出一道控制命令, 控制命令一旦发出,
该单位将被标记为被当前逻辑控制, 直到控制结束或者被其它逻辑控制.

发出的控制命令只会对绑定的单位发送, 和控制标记无关, 且只能同时绑定一个单位.

单位只能同时被一个逻辑所控制, 点击单位可以显示控制单位的逻辑

一般单位响应控制命令行为的持续时间都是控制标记的有效期内有效.

以下是一个简单示例代码,
它会将所有的星辉(flare)控制聚集到逻辑连接的一个电弧(arc)炮台射击点.

sensor shooting arc1 @shooting
jump skip equal shooting false # 没有开火时忽略选点
    sensor x arc1 @shootX
    sensor y arc1 @shootY
skip:

ubind @flare
ucontrol move x y 0 0 0

避免不必要的控制

对于使用 within 来判定是否接近某点时, 记得如果你不需要控制状态,
就不要使用这个, 因为是控制语句, 会造成控制状态.

如果不需要控制状态, 请手动应用公式计算距离: len(x1-x0, y1-y0)

以下是一段代码, 模拟了 ucontrol within tx ty 10 within 0

sensor ux @unit @x
sensor uy @unit @y
op sub dx ux tx
op sub dy uy ty
op len len dx dy
op lessThan within len 10

建造复杂建筑

对于build建造的建筑需要额外信息, 如带桥连接点或者逻辑内容等, 可以借助config参数,
它输入一个building类型值, 可以把另一个建筑的config复制过来

此处的 config 不要和 sensor@config 混淆

以下是一段代码, x坐标为奇数时, 会将自身复制一份在右边.
事先链接好四周建筑的位置, 这样新逻辑一被放下就会链接从而终止建筑条件

jump 0 notEqual @links 0
op mod c @thisx 2
jump 0 equal c 0
op add tx @thisx 1
sensor type @this @type
sensor r @this @rotation
ubind @poly
ucontrol build tx @thisy type r @this

@this 变量表示逻辑块自身 building 的环境变量, 而 @thisx 是逻辑自身位置的x坐标环境变量

对于部分同种类建筑可以直接覆盖, 例如可以在电力节点的位置覆盖建造电池, 或在传送带处覆盖建造带桥

单位控制语句中的命名问题

对于ucontrol unbind, 命名并不合理, 其作用是解除控制(control)而不是绑定(bind),
所以或许应该叫做 ucontrol decontrol

由于 ucontrol 语句本身会产生控制, 而 unbind 会解除控制,
所以除非你确信自己已经控制了单位, 否则不要使用 unbind,
不然很容易打断其它逻辑的操作, 或者是单位的默认AI

更多单位控制操作

名称 参数 返回值变量 描述
idle - - 原地不动,但继续进行手上的采矿/建造动作, 也是单位的默认状态
stop - - 停止移动/采矿/建造动作
move x, y - 移动到某个位置
approach x, y, 半径 - 移动到靠近某个位置至一定的距离内
pathfind - - 寻路移动至指定地点19
autoPathfind - - 自动寻路至最近的敌方核心或敌人生成点。
boost 是否开启 - 开始/停止助推
target x, y, 是否射击 - 向某个位置瞄准/射击
targetp 目标, 是否射击 - 根据提前量向某个目标瞄准/射击
itemDrop 目标, 数量 - 将携带的物品放入一座建筑
itemTake 目标, 物品, 数量 - 从建筑中取出某种物品
payDrop - - 卸下当前载荷
payTake 拾取数量 - 从当前位置拾取载荷
payEnter - - 进入/降落到单位下方的荷载方块中
mine x, y - 从某个位置采集矿物
flag 数字 - 给单位赋予数字形式的标记, 也就是在单位上储存一个数字20, 就像内存元
build x, y, 建筑, 方向, 配置21 - 建造建筑
getBlock x, y, 类型, 建筑, 地板 类型, 建筑, 地板 在坐标处获取建筑、地板和方块类型, 坐标需位于单位 @range 半径内
within x, y, 半径, result result 检查单位是否接近了某个位置
unbind - - 停用单位的逻辑控制, 恢复常规AI
deconstruct x, y - 拆除指定坐标的建筑, 通常需在地图规则中允许

很多单位控制操作都需要在单位接近某个范围内才生效, 通常是 @range,
使用时注意用 move, approach 等方式使单位移动到范围内再操作

稳定控制一个单位

通常, 并不想依次批量操作所有单位, 而是持续操作指定的一个, 可以用以下方式完成:

  1. 绑定一个完全自由的单位 (没有被玩家控制、没有被其它逻辑块控制)
  2. 将其控制
  3. 如果它的控制者不再是当前逻辑 (被其它逻辑或其它因素抢走),
    那么放弃该单位, 回到步骤 1., 否则回到步骤 2. 并持续控制
find:
    ubind @flare
    sensor controlled @unit @controlled
jump find notEqual controlled 0

op add y @thisy 2

controlled:
    op add i i 0.1; op mod i i 4 # 持续改变移动坐标让单位正在被多次控制变得明显
    op add x @thisx i
    # 控制语句是必须的, 并且两次控制间隔不能超过十秒
    # 否则解除控制后, 该设计就会被认为发生了单位被抢等情况
    ucontrol move x y 0 0 0

sensor dead @unit @dead
jump find notEqual dead false # 如果单位死亡则重新查找

sensor controller @unit @controller
jump controlled equal controller @this # 如果单位已经被当前逻辑块控制, 则继续

该种控制方式简单、好写、坑少, 且可以进阶为某靠谱的多控方案, 故在此推荐

该种方法不建议在版本低于 146 的情况下使用,
在旧版本因游戏 bug 导致该方法在重新进入地图后会重新查找单位

(在 150 附近的一些版本该 bug 再次出现并再次被修复)


上一章
目录
下一章


单位雷达

就像普通的radar语句一样, 但是搜索者变成了单位,
然后在搜索者的位置是一个未使用的普通参数空位, 随便写个0就行

普通的radar语句参考这里

这是单位控制语句, 会对单位发出控制信号并标记为已控制

一个简单的用例, 只是展示格式, 并不做什么事:

uradar enemy any any distance 0 1 result;

像普通的 radar 语句一样, 该语句也有时间间隔, 但是关联在单位上的:
对于同一个单位, radar 搜索后, 在较短之内, 该 radar 语句重复搜索返回旧目标


上一章
目录
下一章


单位定位

这个语句可以借助单位定位一些静止的东西, 并且被定位的通常是距离单位'较近的'

可以搜索的目标都有

  • 敌我的某类建筑, 如核心
  • 某类矿物
  • 受损建筑
  • 敌方出生点

语句为ulocate, 有两个字面参数,
一个是定位模式, 一个是定位建筑类型(在搜索建筑时使用)

定位模式

字面参数 类型
building 定位建筑
ore 定位矿物
spawn 定位敌方出生点
damaged 定位己方受损建筑

建筑类型

字面参数 类型
core 核心
storage 仓库
generator 发电机
turret 炮塔
factory 工厂
repair 修复器
rally 指挥中心
battery 电池
reactor 反应堆

用法: ulocate, 定位模式, 建筑类型, 是否搜索敌方, 要搜索的矿物, 目标点x, 目标点y, 目标是否找到, 找到的建筑

类似另外两个单位控制语句, 这个语句也会发出控制信号并标记已控制

游戏内逻辑编辑器创建的 ulocate 语句, '是否搜索敌方' 默认是 true,
如果你需要搜索己方目标, 例如己方核心需要将其改为 false0

小干扰的利用单位获取核心

对于一些并不是单位控制类的逻辑, 如果反复绑定使用 ulocate 会造成大量控制干扰其它逻辑和单位默认行为

所以需要以下逻辑, 绑定任意未被控制的单位, 才对其使用 ulocate, 且核心没有死亡不重复获取

sensor coredead core @dead
jump found equal coredead false

lookup:
    lookup unit unit i
    op add i i 1; op mod i i @unitCount
    ubind unit
    jump lookup equal unit null

    sensor ctrl @unit @controlled
    jump lookup notEqual ctrl 0

ulocate building core false 0 corex corey 0 core
ucontrol unbind 0 0 0 0 0

found:
    print core; print "  "
    print corex; print ","; print corey
    printflush message1

上一章
目录
下一章


进阶控制流-基础

从这章开始, 将进入逻辑进阶部分讲解.

这一章主要是讲解规范控制结构, 掌握后可以更好的规划逻辑结构和层次

在本章及后文中出现的如 if cond { ... } 这种代码一般是用来说明逻辑的,
并不是能直接导入逻辑中的语言, 需要经过处理才能被逻辑所使用

跳过结构

这个结构很简单, 就是条件满足即跳过而已, 直接编写一条jump跳过一些语句即可

例如skip a < b { print 1; } printflush message1;表示为如下形式

jump end lessThan a b
    print 1
end:
printflush message1

这样即可在条件满足时, 跳过某段代码, 也意味着只有满足相反的条件才执行某段代码

if分支结构

一个if分支结构, 我们希望分出两条分支, 条件满足时执行一个, 条件不满足时执行另一个

我们可以将不满足的分支放在上面, 然后前面写一个跳过的跳转, 从而完成这个结构

比如if a < b { print 1; } else { print 2; } printflush message1;,
我们可以写成这样:

jump true lessThan a b
    print 2 # 执行完不满足的分支跳过满足分支, 跳出这个if
jump end always 0 0
true:
    print 1
end:
printflush message1

当然对于连续的多个条件我们可以直接以此类推, 参考上述写法编写这样的逻辑

if a < b {
    print 1;
} else {
    if c < d {
        print 2;
    } else {
        print 3;
    }
}
printflush message1;

然后以相同方式展开即可.

为了避免无意义的多步无条件跳转串联, 手动把中间的跳转指向最终无条件跳转链的终点.

然后我们就能从上述代码得到以下展开式

jump true1 lessThan a b
    jump true2 lessThan c d
        print 3
    jump end always 0 0
    true2:
        print 2
jump end always 0 0
true1:
    print 1
end:
printflush message1

do-while循环结构

这个结构很简单, 就是条件满足直接跳回头部, 就算条件永不满足也至少会执行一次

比如set i 0; do { print i; op add i i 1; } while i < 3; printflush message1;

应展开为

set i 0
cont:
    print i
    op add i i 1
jump cont lessThan i 3
printflush message1

while循环结构

这个结构建立在do-while循环结构的基础上, 以在外面增加一个相反的条件直接跳过来实现

这样条件不满足就会直接跳过, 而不是执行至少一次了

比如set i 0; while i < 3 { print i; op add i i 1; } printflush message1;

应展开为

set i 0
jump break greaterThanEq i 3
cont:
    print i
    op add i i 1
jump cont lessThan i 3
break:
printflush message1

gwhile循环结构

gwhile起到的作用和while循环结构相同, 但是实现上略有不同.

它用首次进入循环时多执行一行的代价, 换来条件部分不必重复编写一遍

这对于多条语句构成的复杂条件, 或者不想费事将条件反转很有效.

比如set i 0; gwhile i < 3 { print i; op add i i 1; } printflush message1;

应展开为

set i 0
jump cond always 0 0
cont:
    print i
    op add i i 1
cond:
jump cont lessThan i 3
printflush message1

上一章
目录
下一章


进阶控制流-选择

这是进阶结构中最重要的几个结构之一, 可以在恒定的时间内选到第n块代码, n为非负整数.

还记得我们前面接触过的@counter环境变量吗? 这也几乎是我们唯一能赋值的环境变量.

参考前文的脚注, 我们可以了解到逻辑运行语句的工作原理.

扩展思路, jump做的事也仅仅是在条件满足时去设置@counter到指定行罢了.

设我们有如下要实现的结构

select n {
    {
        print 0;
        jump end always 0 0;
    }
    {
        print 1;
        print "1!";
        jump end always 0 0;
    }
    {
        print 2;
    }
}
end:
printflush message1

我们可以构建出一张跳转子表, 在我们的预期中, n为整数且 0 ≤ n ≤ 2

op add @counter @counter n
jump n0 always 0 0
jump n1 always 0 0
jump n2 always 0 0
n0:
    print 0
    jump end always 0 0
n1:
    print 1
    print "1!"
    jump end always 0 0
n2:
    print 2
end:
printflush message1

观察上述代码, 根据前面讲过的流程,
在执行到op那行时, @counter 的行号指向第一个jump的行.
但是此时我们可以根据n去增加这个行数, 当n为1时, @counter 被增加1,
所以原本应该跳着至n0的变成跳转至n1, n为2时同理.

这个就是select结构, 据上例可以看出, 无论我们的块数量增加至多大, 跳转所需时间不变.

当然, select结构还有一种变体, 就是在我们每个块的长度差不多时, 我们可以使用另一种形式.
根据上述代码可以看出, 如果我们每个块均为1行时, 无需使用跳转子表进行跳转,
我们之间将目标行放在那里即可.

接着我们可以扩展一下, 每个块只有一个数要跳转过去且每个块长度相同时, 我们可以采取如下形式

op mul inc n 3 # 这里先计算出n去乘每个块行数应该产生多少偏移
op add @counter @counter inc # 偏移到块行数的正整数倍
    print 0
    print "\n"
    jump end always 0 0

    print 1
    print "\n"
    jump end always 0 0

    print 2
    print "\n"
end:
printflush message1

在上述代码中, 只要n满足我们预期的限制, 也就是n为非负整数且不大于最大块数,
那么n只会跳转到每个块的开头.

这种形式编写在一部分情况中, 因为不需要编写跳转表, 所以行数会得到一定的节省,
但是需要注意最大块行数, 并且不够长的需要填充一些语句,
而且穿透22顺序只能按n正序进行, 跳转表形式还可以让n的多个值指向同一个块.
所以这种还是没有跳转表形式实用.

匹配守卫

这个是跳转表形式的进阶, 可以在跳转前先检查是否满足某个条件, 再行跳转.
检查这个条件的被称作'匹配守卫'

比如下述代码 (这是一个方便编写逻辑的语言23, 在这用来演示思路, 可以不管它)

case 0表示当n为0时执行下面一块代码, 后面的if表示但是还要满足if的条件

gswitch n {
case 0 if a < b:
    print 0 "yes";
    break; # 相当于jump到整个gswitch之后
case 0:
    print 0 "no";
    break;
case 1:
    print 1 "...";
}
printflush message1;

修整后, 我们将会得到以下代码

op mul __1 n 2
op add @counter @counter __1
    jump n0g lessThan a b # case 0 符合条件
    jump n0 always 0 0    # case 0 不符合条件
    jump n1 always 0 0    # case 1
n0g:
    print 0
    print "yes"
    jump end always 0 0
n0:
    print 0
    print "no"
    jump end always 0 0
n1:
    print 1
    print "..."
end:
printflush message1

可以看出, 这里我们采用了select的另一种形式来容纳更复杂的跳转表,
即带有匹配守卫的跳转表


上一章
目录
下一章


复杂条件

对于需求的升级, 我们需要构建越来越复杂的逻辑.

我们可以定义以下表达

符号 名称 意义
!cond 条件非 当条件反转
a && b 短路与 当条件a成立时, 取b的结果做条件
a b 短路或 当条件a不成立时, 取b的结果做条件

上表优先级由高到低, 二元运算左结合, 也就是说
!a && b && c || d && e 应等价加上括号的 (((!a) && b) && c) || (d && e)

短路的概念为, 当一边条件满足某些条件,
就不需要考虑(求值)另一边条件, 因为最终条件无论另一边结果是什么都不会影响.

接下来我们讲解一下!运算, 也就是将条件反转.

基本条件的反转

比如! lessThan a b, 我们可以简单的将其变成greaterThanEq a b,
也就是小于变成不小于(大于或等于).

对于一些特殊的条件没有其对应的, 比如always,
我们可以手动捏造一个永远不成立的条件用来占位(如果我们不将这个跳转删除的话),
也就是例如 notEqual 0 0.

而有些条件, 反转后需要增加额外的行, 比如strictEqual a b,
严格的反转必须首先计算op strictEqual tmp a b, 然后判定equal tmp false

复杂条件的反转

我们在上面提出了两种复杂条件, 在最终构建条件时,
我们不应考虑!条件, 所以要将其消去.

基本条件直接将其反转便能消去, 而对于反转复杂条件&&||,
我们需要应用逻辑代数中的一个定律 德•摩根定律.

这个定律很简单, 他定义了两条等式

  • !(a && b) = (!a) || (!b)
  • !(a || b) = (!a) && (!b)

依照上述等式不断的将左边形式变换至右边形式, 直到问题化为基本条件的反转,
此时我们就将!符号消去了

复杂条件的构建

在消去了!条件后, 我们要考虑的就是如何将一个&&||和基本条件组成的一个复杂条件构建为一系列jump了.

这有着成熟的算法, 以下是一段伪代码

def build(cond, target)
    if cond.type == "&&"
        tmp = tmp_tag()
        build(not(cond.a), tmp)
        build(cond.b, target)
        add(tmp)
    else if cond.type == "||"
        build(cond.a, target)
        build(cond.b, target)
    else
        # 基本条件
        add(jump target ...)

在上述代码中, tmp_tag获取一个临时的tag, 用于构建中间跳转,
add可以将 jump 或者一个 tag 添加到最后一行.
not, 就像前面的条件反转一样的流程, 反转那个条件.

cond.a是运算的左边条件, 而cond.b是右边的条件

比如将以下条件转换成逻辑 jump target (a < b && c < d) || e < f

# 首先从顶部的短路或运算进入
# 开始构建左边的 (a < b && c < d)
# 这是一个与运算, 所以我们将左边条件反转为(a >= b),
# 并将其构建目标指向一个临时标签tmp1
jump tmp1 greaterThanEq a b
# 然后左边构建完成开始构建右边
jump target lessThan c d
# 然后右边构建完成, 将临时标签加入
tmp1:
# 然后回到顶层那个短路或运算, 开始构建右边, 也就是(e < f)
jump target lessThan e f
print "false"
target:
print "target"
printflush message1

当然, 虽然理论有了, 但是实践中, 除非你通过编写程序来转换这些条件,
不然条件复杂起来就算有理论, 思维负担依旧偏大.

这里依旧是推荐编译器项目: https://github.com/A4-Tacks/mindustry_logic_bang_lang


上一章
目录
下一章


进阶控制流-函数

非递归型函数

在手写时, 可以借此避免大量重复的代码, 但是需要那么几行的调用成本,
可以使用在那种例如几百行需要以不同的值重复调用的情况.

一个非递归函数结构由三个部分组成

  • 头部: 为了让函数定义处的代码不被执行,
    我们需要用一个跳转跳过整个函数函数体和返回部分
  • 函数体: 在这里编写函数所需的代码, 并约定函数需要的参数都有哪些变量
  • 返回部分: 约定好一个返回行号的变量, 如ret1, 在这里编写一行set @counter ret1

而调用函数也可分为三部分

  • 传参: 确保约定的参数变量如预期那样为要传入的值
  • 设置返回行: 设置函数结束后要返回到的行, 非递归的情况一般是@counter+1
  • 调用: 这个可以使用跳转到函数函数体, 或者在定义函数处设置一个变量得到行号,
    然后调用时直接设置@counter到函数函数体

比如我们编写一个函数, 打印一段话, 并返回, 调用两次

jump defined always 0 0
print_msg:
    print msg
    set @counter print_msg_ret
defined:

set msg "Hello, "
op add print_msg_ret @counter 1 # 记得加1跳过调用用的jump, 不然会一直原地调用
jump print_msg always 0 0

set msg "World!"
op add print_msg_ret @counter 1
jump print_msg always 0 0

printflush message1

递归型函数

这种函数一般有特殊的目的, 比如使用某种算法等.

其递归需要依赖内存, 因为递归运行的还是这代码自己, 所以用的变量是同一套,
所以递归时需要跨越调用前后的变量需要手动存至内存元做的函数栈中.

比如以下一段代码, 是斐波那契的递归定义式, 不考虑尾递归等情况

set stack_top -1
jump defined always 0 0
fib:
    # 栈内每次调用占两个内存位, 0:返回行号 1:参数n, 调用者负责调用的栈平衡
    # 栈顶直接指向1位置, 空的栈为-1
    read n cell1 stack_top
    jump t1 greaterThan n 1
        set result 1
    jump t2 always 0 0
    t1:
        # 调用fib(n-1)
        op add stack_top stack_top 1
        op add ret @counter 5 # 跳到最后写入n的后面
        write ret cell1 stack_top
        op add stack_top stack_top 1
        op sub n n 1
        write n cell1 stack_top
        jump fib always 0 0

        read n cell1 stack_top
        write result cell1 stack_top # 复用n的位置, 把上一次调用的结果存入栈

        # 调用fib(n-2)
        op add stack_top stack_top 1
        op add ret @counter 5 # 跳到最后写入n的后面
        write ret cell1 stack_top
        op add stack_top stack_top 1
        op sub n n 2
        write n cell1 stack_top
        jump fib always 0 0

        read prev_result cell1 stack_top
        op add result prev_result result
    t2:
    op sub stack_top stack_top 1
    read ret cell1 stack_top
    op sub stack_top stack_top 1
    set @counter ret
defined:

set i 0
loop:
    print i
    print ":"

    # 调用fib(i)
    op add stack_top stack_top 1
    op add ret @counter 4 # 跳到最后写入n的后面
    write ret cell1 stack_top
    op add stack_top stack_top 1
    write i cell1 stack_top
    jump fib always 0 0

    print result
    print "\n"
    op add i i 1
jump loop lessThanEq i 7 # 因为递归斐波那契太慢了, 所以这个数小一些
printflush message1

从上述代码可以看出, 递归是如此的麻烦, 若非特殊需求, 不要轻易去使用.

递归函数需要内存的原因是, 因为普通函数在一次外部调用中总是只会执行一次,
而递归函数则可能执行多次, 也就需要保存多个返回行, 所以一个变量不够

且有可能函数执行过程中算出了一些变量, 但是递归调用自身后依旧要使用,
这时这些变量就可能被递归的执行覆盖, 也需要将这些变量存入内存中再递归,
然后递归结束后再读出

例如上面的例子就将中间递归自身计算出的结果存入内存中,
刚好用于传参的内存槽在这就用不着了, 可以用来存这个结果

递归函数利用内存元完成调用栈,
且调用方或函数自身调用结束后需要栈顶挪回调用前的位置,
不然再使用调用栈时就可能获取到错误的结果,
比如从栈中读取参数读到了要返回到的行号

如果传参不用担心被递归调用覆盖, 那么完全可以用变量传, 用非递归函数的方式传

尾递归优化

对于一些函数, 可以从递归转换成循环

在一次执行只在尾部递归一次的, 且没有跨越递归的变量,
那可以尝试把整个函数转换成一个循环,
递归条件被满足直接用一个跳转跳回函数头部, 也不用记录返回行号了,
从而变为非递归函数


上一章
目录
下一章


世界处理器

还记得第一章提到的世界处理器吗? 它是一种特殊的处理器,
正常情况应只被地图编辑器或者特殊的mod才可以放置与编辑.

世界处理器相比普通的处理器拥有一些额外的语句, 可以操作的东西更多.

比如可以凭空更改某处的建筑、地板、启用等, 甚至可以在一定范围内更改自身的运行速度.

这章并不讲解太多其特有语句详细的用法, 只是介绍一下.

相信学习了前面的基础语句用法, 快速熟悉部分世界处理器的语句并不成问题.

关于世界处理器语句的介绍也有其它的教程、思维导图、解释等,
并且不要忘记逻辑编辑器有长按解释功能, 同时可以在附录-术语表中找到简介.

世处特权

世处除了可以使用一些额外的特权语句外, 一些普通语句在特权下也变得更强

语句 描述
ubind 绑定具体单位时可以不同队伍
ucontrol 控制单位时可以不同队伍
ucontrol build 可以无视规则是否允许逻辑单位建造
control 控制建筑时无需在链接范围内, 也无需同队伍, 可控制特权建筑
radar 在建筑上开启雷达无需同队伍, 可在特权建筑上开启雷达
drawflush 绘制建筑无需同队伍
printflush 输出建筑无需同队伍, 可以向特权建筑输出 (如世界信息板)
read 读取内存、画板、逻辑块变量无需同队伍, 可读取特权建筑如世处
write 写入内存、画板、逻辑块变量无需同队伍, 可写入特权建筑如世处

Fetch

这个语句用于获取场上的建筑、单位等

# @sharded处填写的是针对哪个队伍, @sharded是默认玩家队伍
# @sharded是环境变量, 其类型是team, 当然你用整数代表队伍也是可以的
fetch unit          result      @sharded i 0            # 获取场上第i个单位
fetch player        result      @sharded i 0            # 获取场上第i个玩家单位
fetch core          result      @sharded i 0            # 获取场上第i个核心
fetch build         result      @sharded i @conveyor    # 获取场上第i个传送带
fetch unitCount     result      @sharded 0 0            # 获取场上的单位总数
fetch playerCount   result      @sharded 0 0            # 获取场上的玩家单位总数
fetch coreCount     result      @sharded 0 0            # 获取场上的核心总数
fetch buildCount    result      @sharded 0 0            # 获取场上的建筑总数

你可以简单的通过上述示例用法写一个简单的遍历己方单位的逻辑

set i 0
# 我们应假定单位不少于一个
fetch unitCount unitCount @sharded 0 0
loop:
    fetch unit unit @sharded i 0

    sensor x unit @x
    sensor y unit @y

    # 去整, 使输出更短
    op floor x x 0
    op floor y y 0

    print unit; print ": "; print x; print ","; print y; print "\n"

    op add i i 1
jump loop lessThan i unitCount
printflush message1
# 虽然单位多了信息板会打印不下, 毕竟有输出上限

Flush Message (message)

类似 printflush, 清空文本缓冲区并将其内容输出到玩家屏幕界面

message 直接向玩家屏幕上显示信息
显示方式 消息将如何显示, 参考下表
显示时间 消息持续显示的时间, 按秒计
是否成功 如果成功显示, 赋值为 1, 否则为 0, 特殊情况如下

一种比较简单的做法是利用旧版兼容的一种模式, 将 '是否成功' 参数设置为 @wait,
这个环境变量的值并不重要, 你填入这个参数后如果显示失败将会类似 wait 语句那样原地等待直到成功显示

显示方式 描述
notify 在屏幕顶部作为提醒消息弹出
announce 显示在屏幕中心
toast 显示在屏幕顶部
mission 显示在波次计时器, 如果输出空信息则重置为正常

当显示失败时, 也就是 '是否成功' 为 0 的情况, 它并不会清空文本缓冲区,
也就是当显示失败时无需重新 print 内容

以下为一个示例, 将会持续向屏幕中心输出持续一秒的 测试文本:

print "测试文本"
message announce 1 @wait

该显示支持了一点本地化, 但不多, 在文本开头为 @ 时会尝试使用 bundle 文件中的翻译进行替换,

例如 print "@liquid.slag.name"; message announce 1 @wait 将显示 矿渣

Set Prop (setprop)

设置属性, 用于设置建筑、单位的属性, 包括物品、液体数量, 和部分可被 sensor 的属性

属性 描述
x 当前 x 坐标
y 当前 y 坐标
velocityX 当前 x 方向的运动速度
velocityY 当前 y 方向的运动速度
rotation 朝向
speed 移动速度属性 (上限)
armor 护甲
health 血量 (生命值)
shield 护盾
team 队伍
flag 单位标签 (类似 ucontrol flag)
totalPower 全电量, 参考 sensor 附录
payloadType 载荷类型

例如以下逻辑让世处自己自毁

setprop @health @this 0

Set Rule (setrule)

设置某个地图或队伍规则, 例如波次时间、建造速度等

规则 参数 描述
currentWaveTime 数值 当前波次倒计时 (tick)
waveTimer 数值 波次定时器是否启用
waves 数值 总波次数
wave 数值 当前波次
waveSpacing 数值 波次间隔时间
waveSending 数值 是否允许用波次按钮手动生成波次
attackMode 数值 游戏模式是否为攻击模式
enemyCoreBuildRadius 数值 敌人核心禁止建造区半径
dropZoneRadius 数值 敌人出生点爆破半径
unitCap 数值 基本单位上限 (可用核心等方式增加)
mapArea x, y, 宽, 高 地图可见范围 (范围外无法前往与交互)
lighting 数值 是否需求照明 (需要点亮地图)
canGameOver 数值 能否游戏结束 (毁灭所有核心后)
ambientLight 数值 环境光颜色
solarMultiplier 数值 太阳能板发电倍率
dragMultiplier 数值 环境阻力倍率
ban 方块/单位 不允许使用某些建筑或单位
unban 方块/单位 解除 ban
buildSpeed 队伍, 值 建造速度倍率
unitHealth 队伍, 值 单位血量倍率
unitBuildSpeed 队伍, 值 单位生产速度倍率
unitMineSpeed 队伍, 值 单位采矿速度倍率
unitCost 队伍, 值 单位成本
unitDamage 队伍, 值 单位攻击伤害倍率
blockHealth 队伍, 值 建筑血量倍率
blockDamage 队伍, 值 建筑伤害倍率
rtsMinWeight 队伍, 值 RTS 的 “谨慎” 最低值
rtsMinSquad 队伍, 值 RTS 小队最小规模

例如设置为需求照明

setrule lighting 1 0 0 0 0

Apply Status (status)

给单位添加状态效果, status false 为添加效果, status true 为移除效果

例如添加十秒潮湿并移除 boss 效果

status false wet unit 10
status true boss unit 0

上一章
目录
下一章


编译器

在编写逻辑行数过多时, 维护会非常艰难, 此时可以考虑使用编译器

编译器可以将其它语言翻译成逻辑语言,
而使用其它语言来编写代码会更加方便、结构更清晰、易于封装功能、编辑方便等

以下列出一些较为实用的编译器, 按star数排序

名称 特点 参考语言 安装难度(本地/在线) 编辑条件
mindcode 极强的优化, 优秀的编译期计算 B/B-下载jar, 运行需要Java环境 有一个网页编辑器, 和扩展语言
mlogjs 参考js, 好上手, 编辑体验好 JavaScript B/D-需要使用npm安装依赖, 有在线版本且很不错 直接使用 JavaScript/TypeScript 的补全, 有很好的网页编辑器
pyndustric 功能偏弱, 不过有内联函数 Python A/X-需要处理python依赖, 需要python环境 直接使用 Python 代码的补全
bang-lang 极强灵活度, 操作生成的代码 逻辑语言 C/X-下载可执行程序, 控制台运行 使用 VSCode 进行编辑, 具有高亮、代码片段、语言补全、预览等
go-mlog 内置支持了自动递归函数 Go D/C-下载可执行程序, 控制台执行, 有在线版本 直接使用 Go 代码的补全
mlogx 极端简陋, 增加了少量宏语句 逻辑语言 A/X-需要使用npm安装依赖 纯文本编辑器, 如记事本等
mlogpp 优秀的编译期计算, 结构体抽象 A/X-需要处理python依赖, 需要python环境 似乎没有任何编辑支持
slimlog LISP风格 LISP B/X-需要安装rust环境 没有任何编辑支持

如果你已经学过 '参考语言' 的话, 学习对应的编译器将会较为简单

例如如果你阅读完了本教程, 学习 bang-lang 将会较为简单

以下是一些特性列表

名称 代码性能 短路条件 switch 内联函数 非递归函数 递归函数 循环展开
mindcode A-极强的优化 - + + + + ?
mlogjs C-结构稍差 + + + + - ?
pyndustric C-结构稍差 - - + + - -
bang-lang B-无优化, 结构良好 + + + + # #
go-mlog D-经常更慢 - + ? + + ?
mlogx B-基本没做什么 - - - + - -
mlogpp C-结构稍差, 较少优化 - - + - - -
slimlog D-结构稍差, 冗余代码 - - - - - -

- 为未支持, + 为已支持, ? 为未知, # 为半手动支持

以下是元编程能力

名称 编译期求值能力 编译期代码操作能力 结构化抽象能力
mindcode A-很完善且统一 C-少量, 如重复展开代码 D-较弱
mlogjs D-基础的求值 D-基本没有 C-较弱
pyndustric E-无 E-无 E-基本没有
bang-lang C-通常仅做数值计算, 其余动静态求值并不怎么统一 B-将代码作为值, 传递并随处生成 B-手动操作代码
go-mlog E-无 D-基本没有 E-基本没有
mlogx E-无 D-基本没有 E-基本没有
mlogpp A-很完善, 较为统一 C-少量 A-极强
slimlog E-无 E-无, 有潜力 E-无

以下是反编译/嵌入逻辑

名称 反编译条件 嵌入逻辑
mindcode B-稍差, 但能处理蓝图 E-基本无法嵌入
mlogjs D-较差 C-能嵌入与插值
pyndustric E-无 E-似乎没有
bang-lang B-有两套反编译, 其中一套具有一点控制流重建 B-直接在逻辑风格上扩展, 但也能反编译直接转换
go-mlog D-较差 E-似乎没有
mlogx C-基本没有, 但不需要 A-严格意义上的直接在逻辑上扩展
mlogpp E-无 D-能嵌入, 但并没有那么方便, 且语法不一样
slimlog E-无 E-基本无法嵌入

以下是文档情况

名称 文档
mindcode B-英文独立文档, 质量较高
mlogjs B-英文独立文档, 质量还行
pyndustric D-仅README少量例子
bang-lang B-中英双版本教程, 质量勉强, 高级部分内容极长
go-mlog C-英文独立wiki, 内容较少
mlogx B-英文独立文档, 基本大致介绍
mlogpp C-仅README少量例子, 与一点代码示例
slimlog D-有一些代码示例

以下是版本跟进情况

名称 版本跟进
mindcode A-目前为止非常活跃
mlogjs C-目前较不活跃
pyndustric C-目前较不活跃
bang-lang B-目前较不活跃, 但是由于其设计不跟进也影响不大
go-mlog E-停滞(2022)
mlogx C-目前较不活跃
mlogpp E-停滞(2023)
slimlog E-停滞(2023)

如果发现编译器特性等描述错误, 请打开一个 issue 来讨论

不推荐的编译器

在上述列表中, 有意的忽略了一些编译器, 原因如下

名称 原因
c2logic 似乎bug极多, 生成的代码非常古怪
mlogls 零文档零示例
minasm 项目结构并不像一个项目, 而是将一组前端代码打包, 尝试浏览器打开, 失败
MlogEvo 基本无文档, 存在一些bug, 影响正常使用
VCode 零文档, 基本无示例, 无法在命令行运行, 测试不便
minpiler 零文档

上一章
目录
下一章


开始学习bang语言

在前文中, 我们有提到一种方便的语言, 这可以方便复杂逻辑的编写.

项目地址: https://github.com/A4-Tacks/mindustry_logic_bang_lang
学习引导: https://github.com/A4-Tacks/mindustry_logic_bang_lang/blob/main/examples/README.md

配置环境

首先, 我们从项目地址页面寻找到Releases栏目, 找到最新版本,
并根据自己的系统24和架构下载对应的压缩包.

接着我们将压缩包解压到合适的目录以方便执行,
在 PATH 环境变量中的可以不需要输入路径直接以名字执行

如果是 Linux、Mac, 请确保它有可执行(x)权限

接着我们测试它是否能够正确的执行, 在 shell25/cmd 中输入以下代码:

mindustry_logic_bang_lang cl <<< 'print "Hello, World!";'

如果是 Windows, 那么它应该有 exe 后缀

如果没有在 PATH 环境变量内, 你可能需要输入它的完整路径来执行

它应输出编译为mdt逻辑的如下代码

print "Hello, World!"

虽然目前这个例子看着和输入的没什么区别, bang做的尽量贴合逻辑的语法风格,
不过随着需要编写的复杂起来, 它可以成为强大有力的工具.

要想阅读之后的章节, 请先确保你能够使用这个编译器编译代码, 不然将基本无法进行

当前项目中关于 Bang 后面的部分已经移除,
学习 Bang 请移步本章开头的学习引导链接处学习


上一章
目录


附录01-op方法

这里描述了op语句的所有运算符

算符 解释
add 加法
sub 减法
mul 乘法
div 除法
idiv 整除26
mod 求余27
emod 取模27
pow
equal 相等
notEqual28 不等
land 逻辑与29
lessThan 小于
lessThanEq 不大于
greaterThan 大于
greaterThanEq 不小于
strictEqual 严格相等
shl 30 左移
shr 30 右移
ushr30 无符号右移
or 30 按位或
and 30 按位与
xor 30 按位异或
not 30 按位取反
max 最大值
min 最小值
angle 向量幅角31
angleDiff 幅角差绝对值
len 向量模长
noise 二维单形噪声
abs 绝对值
sign 符号数32
log 自然对数
logn 任意底对数
log10 底10对数
floor 向下取整
ceil 向上取整
round 四舍五入取整
sqrt 平方根
rand 取随机数33
sin 正弦31
cos 余弦31
tan 正切31
asin 反正弦31
acos 反余弦31
atan 反正切31

目录


附录02-传感器选项

这里描述了所有的sensor可用属性.

当然, 游戏中编辑器sensor语句可以直接选择选项,
你可以直接在游戏内编辑器编写一条sensor, 然后选择选项时长按或将鼠标悬停于某选项,
游戏内编辑器有对很多属性进行解释, 或许会很有帮助

物品属性, sensor支持直接通过物品的content获取建筑、单位等里面某种物品的数量

液体属性, sensor支持直接通过液体的content获取建筑、单位等里面某种液体的数量

固有属性, sensor始终可用的一系列通用属性, 参考下表

属性 翻译
totalItems 总物品数
firstItem 首个物品
totalLiquids 总液体数
totalPower 全电量34
itemCapacity 物品容量
liquidCapacity 液体容量
powerCapacity 电力容量
powerNetStored 电网电池储电
powerNetCapacity 电网电池容量
powerNetIn 电网净输入(发电)
powerNetOut 电网净输出(耗电)
ammo 弹药
ammoCapacity 弹药容量
currentAmmoType 当前弹药类型
memoryCapacity 内存容量35
health 血量
maxHealth 血量上限
heat 热量
shield
armor 护甲
efficiency 效率
progress 进度
timescale 时间系数
rotation 朝向
x X坐标
y Y坐标
velocityX 运动速度X
velocityY 运动速度Y
shootX 射击点X
shootY 射击点Y
cameraX 相机X
cameraY 相机Y
cameraWidth 相机宽度
cameraHeight 相机高度
size 大小36
solid 是否实心
dead 是否已死亡
displayWidth 显示屏宽度
displayHeight 显示屏高度
range 范围
shooting 是否射击
boosting 是否助推
bufferSize 缓冲区待绘制数
operation 方块操作次数
mineX 挖矿点X
mineY 挖矿点Y
mining 是否正在挖矿
speed 速度
team 队伍
type 类型
flag 标记
controlled 已控制37
controller 控制者
name 名称
payloadCount 荷载数量
payloadType 荷载类型
totalPayload 已用荷载空间
payloadCapacity 荷载容量
id 编号38
enabled 是否启用
config/configure 设置39
color 颜色

目录


附录03-环境变量

环境变量在 06-环境变量 章节得到了简单的介绍,
而本章节将做一个较为详细的列表

环境变量 也被叫做 内置变量 (built-in variables)

逻辑自身信息

和逻辑本身相关

  • @this: 逻辑块建筑自身
  • @thisx: 逻辑块的 x 坐标, 类似 sensor thisx @this @x
  • @thisy: 逻辑块的 y 坐标, 类似 sensor thisy @this @y
  • @links: 逻辑块链接的建筑数量
  • @unit: 当前绑定的单位
  • @ipt: 逻辑块在一个标准帧40内执行多少行
  • @counter: 逻辑块内当前行之后将要执行的行号,
    详见 06-环境变量 脚注

常量

  • false: 0
  • true: 1
  • null: null
  • @pi: 圆周率值
  • π: 同@pi
  • @e: 自然常数值
  • @degToRad: 角度转弧度使用的比例系数, 定义: @pi / 180, 使用: 270*@degToRad
  • @radToDeg: 弧度转角度使用的比例系数, 定义: 180 / @pi, 使用: @pi*@radToDeg

世界

和世界相关的量

  • @time: 游戏经过的时间(通常自创建以来), 按毫秒记
  • @tick: 游戏经过的标准帧40
  • @second: 游戏经过的时间, 按秒记
  • @minute: 游戏经过的时间, 按分记
  • @waveNumber: 当前出怪波数
  • @waveTime: 下一波时间, 按秒记
  • @mapw: 地图宽度
  • @maph: 地图高度
  • @wait: 该变量的值并没有什么意义, 用于世处 message 命令的特殊情况

网络相关 (特权)

关于世处(特权处理器)和客户端服务端通信相关的一些变量

暂时没研究这些具体干嘛的, 貌似可以判定在客户端还是服务端,
和独属于客户端的一些信息

  • @server: 如果此时处理器运行在服务器或单人游戏则为真, 否则为假
  • @client: 如果此时处理器运行在连接至服务器的客户端则为真, 否则为假
  • @clientLocale: 运行代码的客户端的区域(语言相关)设置, 如en_US
  • @clientUnit: 运行代码的客户端所代表的单位
  • @clientName: 运行代码的客户端的玩家名称
  • @clientTeam: 运行代码的客户端的玩家队伍 ID
  • @clientMobile: 运行代码的客户端为移动设备则为真, 否则为假

逻辑运行代码在服务器时, 服务器和客户端的逻辑是各自分开运行的,
只是通常运行结果相同不易察觉

特殊数字枚举变量

用于特殊用途的枚举变量

  • @ctrlProcessor: 传感器的 @controlled 返回结果, 表示单位被逻辑块控制
  • @ctrlPlayer: 传感器的 @controlled 返回结果, 表示单位被玩家控制 (附身)
  • @ctrlCommand: 传感器的 @controlled 返回结果, 表示单位被RTS指令控制
    在旧版本还有 @ctrlFormation 用于表示单位在编队中,
    目前编队功能已被删除, 相同的值被 @ctrlCommand 所替代

杂项

  • @sfx-...: 用于表示声音id的环境变量, ...处为具体声音, 例如 @sfx-pew

Content 相关

一些以 content 为主的环境变量

  • team: 队伍相关的环境变量, 如 @sharded @crux @green
  • item: 物品相关的环境变量, 如 @copper @lead
  • liquid: 液体相关的环境变量, 如 @water @oil
  • block: 建筑相关的环境变量, 如 @arc @liquid-tank
  • unit: 单位相关的环境变量, 如 @poly @flare
  • color: 颜色相关的环境变量, 如 @colorRed @colorPurple
  • weather: 天气相关的环境变量, 如 @rain @snow
  • sensor: 传感器选项环境变量, 如 @x @dead

还有一些较为特殊的环境变量, 如 @empty @air

Lookup 相关

主要配合 lookup 语句使用的计数, 反应了某类(如建筑是一类) content 环境变量的总数

  • @itemCount: 物品 content 总数
  • @unitCount: 单位 content 总数
  • @blockCount: 建筑 content 总数
  • @liquidCount: 液体 content 总数

目录


术语表

逻辑术语

术语 解释
字面量 11.2"foo"%ffff1b 这些, 注意不包括如 truenull@copper
字符串 字面量的一种, 像 "foo", 用来表示一些文本, 可用于打印(print), 例如玩家名称
注释 一些在井号后的文本, 直到一行末尾 (不包含字符串内的井号), 在导入逻辑时会被忽略, 这些文本可以用来解释, 便于以后重新理解
缩进 在一行开头的空格或制表符 (Tab \t), 不影响导入效果, 但是可以让内容具有层次, 方便阅读
变量/变量名 vari变量arc1resulttrue@copper 这些, 只不过 true@copper 是内置变量且为常量
变量表 表示了在某个时间点, 每个变量所表示的值, 通常被赋值所修改, 在游戏中逻辑编辑器的底部有 '变量' 按钮, 点击展开变量表
转义 通常于字符串内, \n 表示新起一行而不是反斜杠及字符n, 有时也指代渲染转义
渲染转义 并不改变字符串的含义, 但是在游戏内一些地方显示字符串时有特殊用处的格式, 如 [red]foo[], 方括号: [[
用于语句中, 常见值的产生由字面量直接产生 或 由变量引用变量表中的值, 变量的默认值为 null
参数 填写字面量或变量, 用于给语句输入值
固有参数/字面参数 也填写字面量或变量, 但变量并不能输入值, 而是改变语句本身的作用, 像 op add x z 2 中的 addadd 变量并没有关系, 只是使 op 语句进行加法
赋值 将变量表中某个变量的值修改为另一个值, 例如使用 setop
语句/逻辑行 逻辑中的一条语句如 set x 2, end, print "frog" 各为一条语句
声明变量 在语句的参数中使用某个变量名, 即声明该变量, 声明后变量可以在变量表中看到. 注意哪怕不执行该语句也会声明变量
执行语句/运行语句 逻辑块会从最头部的语句开始顺序执行, 由 @counter 决定该语句后要执行哪个语句, 使用 jump 或给 @counter 赋值来改变将要执行的语句
环境变量/内置变量 无需使用或赋值, 就具有值的变量, 通常此类变量都无法被赋值
常量 无法被赋值的变量, 通常几乎所有内置变量都是常量, 除了 @counter
链接/连接 点击逻辑块会出现一个圈, 点击圈内的建筑会使逻辑块链接建筑, 链接的建筑自动生成一个内置变量名 (building 类型) 来存储该建筑, 部分语句如 control 要求链接才可生效
世处/世界处理器 世界处理器, 通常只能在地图编辑器下进行放置与编辑, 可以使用一些特殊的指令, 具有特权
特权 世处和世处内存元等具有特权, 特权的处理器可以使用一些特权语句, 一些非特权而语句也会被特权影响, 像 control 语句在特权下无需链接即可控制建筑, 像普通处理器无法使用 write 写入世界内存元
逻辑/逻辑块/处理器 指一系列可以运行逻辑语句的建筑, 如 微型处理器、逻辑处理器、超核处理器、世界处理器
绑定 来自单位语句 ubind (Unit Bind), 该语句找到某个单位并将其赋值给 @unit 的过程称作绑定
单位控制语句 指单位语句 (排除 ubind, 即 ucontrolulocateuradar), 此类语句执行时给绑定的单位标记为被当前处理器控制, 一段时间后解除标记
控制 单位被标记为被处理器控制, 暂时失去默认 AI 行动 (如 mono (独影) 的自动采矿), 如果控制时有指示其行动, 标记期间将会持续执行该指示, 例如 ucontrol move 2 3 会在标记期间持续向 2,3 坐标移动
阻塞 由于某些条件未能得到满足, 重复的执行一行或者几行等待直到条件满足被称作阻塞, 例如 '阻塞等待按钮按下'
文本缓冲区/预打印区 由 print 等语句将一些文本添加到文本缓冲区, 可用 printflush 等语句一次性输出并清空文本缓冲区, 或者用 format 语句替换其中一些内容
绘制缓冲区/预绘制区 由 draw 语句将一些需要绘制的图形等按顺序暂时储存, 可用 drawflush 语句一次性将其绘制在某个屏幕上并清空绘制缓冲区

逻辑语句

导出格式 长名称 描述
noop Invalid 无效语句, 执行时什么都不会发生, 解析失败也会产生该语句
read Read 读取, 如内存等
write Write 写入, 如内存等
draw Draw 绘制, 将一些绘制指令添加到绘制缓冲区
print Print 打印, 用于将文本添加到文本缓冲区
printchar Print Char 打印字符, 用于将数字对应的字符添加到文本缓冲区
format Format 格式化, 将文本缓冲区内一定格式的括号替换成指定文本
drawflush Draw Flush 绘制刷新, 将绘制缓冲区内绘制指令一次性应用在某个逻辑显示屏上, 并清空绘制缓冲区
printflush Print Flush 打印刷新, 将某个信息板的内容设置为文本缓冲区内的文本, 并清空文本缓冲区
getlink Get Link 获取链接的第i(从0开始)个建筑, 逻辑块可以链接一些建筑以便后续使用
control Control 控制某个建筑射击、禁用等, 只能控制链接的建筑(世处除外)
radar Radar 雷达, 以某个建筑为中心, 一定的范围与条件下查找一个单位
sensor Sensor 传感器, 获取各种东西的附加信息, 如单位坐标等
set Set 设置(赋值), 设置一个变量的值为另一个值
op Operation 运算, 设置一个变量的值为另外两个值运算后的值
select Select 选择, 当条件成立时, 设置一个变量的值为其中一个值, 否则设置为另一个值
wait Wait 等待, 等待一段时间(秒), 可能稍微有些不精确
stop Stop 停止, 执行到这个语句时将不再继续往下执行, 一直重复执行该语句
lookup Lookup 索引内容, 根据给出的编号获取一个 content (如 @flare) 或队伍 (team)
packcolor Pack Color 打包颜色, 将 RGBA (值范围为[0,1]) 打包成一个数字, 方便使用 (如 draw col)
unpackcolor Unpack Color 解包颜色, 将 packcolor 的颜色拆解回 RGBA
end End 结束, 相当于 jump 0, 跳转回首行
jump Jump 跳转, 当条件满足时, 跳转到某行或某个标记
ubind Unit Bind 单位绑定, 绑定一个单位, 相当于将 @unit 赋值为某个单位
ucontrol Unit Control 单位控制, 控制绑定的单位并在这段时间内去做某件事
uradar Unit Radar 单位雷达, 类似 radar, 控制绑定的单位并以该单位为中心执行 radar
ulocate Unit Locate 单位定位, 控制绑定的单位并查找一个离单位较近的目标, 可以查找敌我建筑、矿物等

逻辑语句 (世处专用)

这方面内容该教程作者并没过多接触, 可能不太准确, 如有勘误请开启一个 issue

查看的同时可以参考逻辑编辑器内, 语句菜单中长按语句的解释, 还有语句中参数菜单的长按解释

导出格式 长名称 描述
getblock Get Block 获取方块, 能获取某个位置的建筑或地形
setblock Set Block 设置方块, 能直接往某个位置放建筑、替换建筑、替换地形
spawn Spawn Unit 生成单位, 在指定位置
status Apply Status 状态, 给单位添加状态效果
weathersense Weather Sense 天气感知, 检查某天气是否正在发生
weatherset Weather Set 设置天气, 设置某种天气启用或禁用
spawnwave Spawn Wave 生成播次, 可以不使播次计数来到下一播
setrule Set Rule 设置游戏规则
message Flush Message 信息, 类似 printflush, 但是向玩家界面显示信息
cutscene Cutscene 视角, 控制玩家视角, 例如移动、缩放
effect Effect 效果, 在位置生成粒子效果, 特效
explosion Explosion 爆炸, 往某个位置生成爆炸, 具有伤害
setrate Set Rate 设置世处执行速度 (@ipt) 即每 tick 执行多少条逻辑语句
fetch Fetch 获取, 获取地图中第i个建筑、单位等, 可限定队伍和获取总数
sync Sync 同步, 同步某个变量
clientdata Client Data ?
getflag Get Flag 获取全局标记
setflag Set Flag 设置全局标记, 可以被其它世处使用 getflag 获取
setprop Set Prop 设置属性, 例如可以设置单位的移动速度
playsound Play Sound 播放声音
setmarker Set Marker 设置 Marker 属性, 可以调整已经创建的 Marker
makemarker Make Marker 创建 Marker, 可以在地图上创建各种图形文本等
localeprint Locale Print 本地化打印, 类似print, 但可以查找语言文件产生翻译

常见建筑名称

逻辑内逻辑相关常用建筑的名称, 也就是建筑生成的环境变量名, 如 cell 生成 cell1, cell2 ...

变量名 名称 用途
cell 内存元 常用于存储一些数字
bank 内存库 与 cell 一样
message 信息板 用于输出字符串, 常用来主动显示一些信息说明逻辑执行状态
display 显示屏 逻辑使用 draw 在显示屏上画图
processor 处理器 逻辑块本身, 可以用于禁用或读写其它逻辑变量
switch 按钮 用于简单控制一些逻辑执行, 例如启动停止
canvas 画板 E星 的特殊建筑, 可以绘制几个固定颜色, 逻辑可类似内存一样读写
sorter 分类器 用于控制物品, 或通过选择的物品表示通常在 0-16 的一个数字
node 电力节点 用于一些电力相关逻辑, 可以通过带电建筑获取电网数据
battery 电池 与电力节点一样
diode 二极管 一些逻辑控制禁用二极管, 来管理电力输送
arc 电弧 由于无需炮弹, 常被一些需要触屏的逻辑当做鼠标, 读取射击位置
hail 冰雹 由于射程广, 常被用作雷达 (E星有 '创伤', 范围是冰雹的五倍多)

目录


License

MIT License

Copyright (c) 2024 A4-Tacks

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

注释

  • [1] 没有计算机基础的话, 并不需要过多的关注这个到底是什么
  • [2] 变量表用于显示逻辑运行到了哪一行, 和一些变量在此刻的值, 变量的概念在接下来的章节有讲
  • [3] 通过一些方法, 在某刻使变量的值改变为另一个值
  • [4] 24-世界处理器
  • [5] 参数, 就是一行语句中, 某个字面量或者变量应该填入的地方,
    哪怕可能不会被求值7只是取其字面形式, 比如op后面的add
  • [6] 赋值, 即在此时此地, 将某个变量代表的值, 修改为另一个值.
    当然, 既然提到了此时此地,
    那么可以在另外一个地方将同一个变量的值修改成另一个值.
    我们可以举出一个例子, 比如op, 它是在计算完结果后进行的赋值.
    所以op add x x 1这行表示的意思为运行这行之后x的值为运行这行之前x的值加上1.
  • [7] 求值, 比如去得到一个变量所代表的值, 或者去设置一个变量的值
  • [8] 标记就像一个没有参数的语句, 像 end 只不过多了一个冒号 end:,
    所以其后面也要像普通语句那样需要以换行或分号结尾.
    标记可以作为被跳转的目标, 不过标记导入进逻辑后就会被转换成行号, 所以导出就没有了
  • [9] 因为op的运算和jump的比较在两个值类型不相同时,
    都会先将其转换成同一个类型来比较, 比如null会转换成0,
    而很多的值都能转换成数字1, 这将产生一些隐藏的问题,
    区分null和0的情况相对比较常见, 所以我们需要使用严格相等, 也就是strictEqual
  • [10] 这个变量非常重要, 学习到中期一些重要的控制结构都依赖于此.
    逻辑中每个语句的运行可看成以下四步
    1. 读取 @counter 处的语句
    2. 将 @counter 自身增加1
    3. 执行步骤1.读取到的语句
    4. 回到步骤1.

通俗来讲, 这个变量控制了执行某行语句后要执行哪一行,
jump 语句做的事就是条件满足时将这个变量改为箭头指向的行号(行号从0开始)
从上述步骤我们可以看出, 执行语句时, 且因为@counter是可赋值的,
所以我们可以随意的控制执行完这行后要执行哪行
函数结构和select结构都依赖于此, 在之后会讲解这两个结构

  • [12] 建筑的英文名称以短横线-分割, 分割为多段, 并按以下情况处理:
    • 如果至少分割为了两段, 且最后一段是large或能被解析为一个数字,
      那么将会取倒数第二段
    • 如果不满足上述条件, 将会取倒数第一段

例如:

    • foo -> foo
    • foo-bar -> bar
    • foo-bar-2.5 -> bar
    • foo-bar-large -> bar
    • foo-bar-cell -> cell
  • [11] 每一个被链接的建筑都有一个编号, 由0开始, 在之后讲getlink时需要用到
  • [13] 这里一般是填写的content类型的值, 它们一般都附带一个图标可以被绘制出来
  • [14] 用于控制将输出文本的哪一点对齐到给定坐标,
    例如 bottomLeft 就是始终让输出文本的左下角位于给定坐标处
对齐方式 坐标点位置
topLeft 左上角
topRight 右上角
bottomLeft 左下角
bottomRight 右下角
center 中心
left 左侧中点
right 右侧中点
top 顶部中点
bottom 底部中点
  • [15] 字面参数, 比如之前见过的op语句中的add,
    它并不会像普通参数那样被求值, 而是直接取输入的原始形式,
    这一般是用来控制语句的行为.
  • [16] 在老版本, 这里直接输入rgb参数的, 但是在新版本有了颜色打包,
    就只输入一个打包后颜色了
  • [17] 声明指的是在参数中使用过此变量, 一旦使用过, 该变量将会在变量表中可见
    有时即便参数在逻辑编辑器的语句中并不可见, 也依旧声明了变量,
    例如 op abs result n b 会声明 result, n, b 三个变量,
    虽然 op abs 并不需要用到变量 b,
    但是通常大多数语句获取变量的个数都是固定的,
    所以哪怕其中一些语句的用法并没有用到某些变量, 这些变量也会被声明
    (大部分环境变量在变量表中被隐藏, @counter 除外)
  • [18] 这个范围一般点击建筑显示有个圈的话, 大致就是这个圈, 比如炮塔射程 超速器范围,
    或者单位射程, 没有的话一般这个范围应该很小.
    该范围可用 sensor range block1 @range 获取
  • [19] 在旧版本中, pathfind 其实是 autoPathfind 的功能, 所以旧版本的 pathfind 在新版本中导入会寻路至 0,0,
    而又因为 0,0 通常无法到达, 而原地不动
  • [21] 该数字和内存中的数字、变量中的数字是一样的, 都可以存储小数
  • [20] 参考建造复杂建筑
  • [22] 穿透, 观察之前编写的示例代码, 除了最后一个块我们都编写了跳转至select末尾的语句,
    我们可以思考一下, 如果没有这么做的话会怎样?
    会继续执行下一个块, 直到被某个跳转跳出. 这种行为称作穿透.
  • [23] https://github.com/A4-Tacks/mindustry_logic_bang_lang
  • [24] 如果是安卓请下载Linux版本, 架构通常为aarch64.
    使用方法为寻找一个可以执行二进制文件并附带环境的终端模拟器,
    如termux, MT管理器(sdk28)等软件.
  • [25] <<<是bash的一种语法, bash是一个常见的shell,
    这个语法直接将给定的字符串作为输入
    如果要从给定路径的文件输入请使用<, 如果要控制输出到的文件请使用>
  • [33] 该整除使用 floor 向负无穷取整实现, 所以如果在两者符号不同时想得到正确的商和余数需配合 emod 而不是 mod,
    因为逻辑的 mod 运算使用 truncate 向 0 取整而不是使用 floor 向负无穷取整, 得到的商和余数会不一致
    例如 op idiv c -7 3; op mod d -7 3 得到 (-3, -1),
    op idiv c -7 3; op emod d -7 3 得到 (-3, 2),
    b≠0 后者始终满足 a=c*b+d-7 = -3*3 + 2
  • [27] 虽然该运算被命名成 取模 mod(modulo), 但是其实际行为是 取余 rem(remainder),
    区别在于当操作数符号不同时的行为,
    例如 -7 mod 3 == 2, -7 rem 3 == -1
    如果你确实需要取模, 而不是取余, 可以使用模拟算法:
    i mod m == (i rem m + m) rem m
    此处 modulo 指向负无穷取整实现的取余, remainder 指向零取整实现的取余
    在 150 版本中, 添加了 emod 实现向负无穷取整的取模实现
  • [30] notEqual 在逻辑编辑器内被显示为 not, 而 not 在逻辑编辑器内被显示为 flip,
    不要将其混淆
  • [31] 逻辑与对于 a land b 来说, a != 0b != 0 时返回 1, 否则返回 0,
    并不具备一些短路算符设计的直接返回 a 或 b 的功能
  • [29] 位运算符(bit-wise operator),
    运算前操作数将被转换成 long (带符号的64位整数) 再进行运算,
    转换过程为直接丢弃小数部分
    逻辑使用的数字 number 的类型为 double (64位浮点数),
    而作为整数使用时能不损失精度利用的部分有53位, 加上表示符号的一位,
    所以位运算时要注意将范围控制在53位而不是64位
    对于 shr 运算, 由于是转换成有符号整数运算的, 有时并不能达到想要的结果,
    在 150 版本中, 添加了 ushr 实现转换成无符号整数再右移
  • [26] 这里的三角函数全部为角度制而非弧度制,
    也就是说你可以直接使用cos(360), 而不是cos(2*@pi)
  • [32] sign 符号数, 并非指符号位
    • 对于 n < 0 返回 -1
    • 对于 n == 0 返回 0
    • 对于 n > 0 返回 1
  • [28] 取随机数, 范围是[0, n), 即 0 <= rand(n) < n
  • [39] 如果是耗电建筑, 如工厂那么该属性代表工作时接收的电力百分比, 值小于 1 时将工作变慢 (间断),
    如果是电池则为电池储电量, 这仅限于单个建筑, 并不获取整个电网
  • [36] 指定内存中能存储多少个数据, 例如 cell 是 64 个, bank 是 512 个
  • [37] 不仅能获取建筑、单位的大小, 还能获取字符串的大小(utf-16长度)
  • [38] 如果返回 0 则表示未被控制, 返回 @ctrlProcessor, @ctrlPlayer, @ctrlCommand 则是控制的各种情况,
    详见 特殊数字枚举变量
  • [35] 获取 content 的对应 lookup 语句的编号, 注意是 content 而不是 unit building 等
  • [34] 比如分类器选中的物品, 注意不要和build系列语句的config记混
  • [40] 用于衡量计算的标准帧数, 每秒有 60 个标准帧