基本语法
- 基本语法的说明
基本语法的说明
-
编译期(compile-time)和运行时(run-time)
除声明变量外,任何非编译期代码都不能暴露在函数外。
包含任何运行时操作的函数都是非编译期函数。
所有对变量的声明、初始化、读取、运算和赋值操作都是运行时操作。
编译期代码即使 if 条件不成立也会执行。 -
大小写敏感
epScript 是大小写敏感的编程语言,A 和 a 含义不同。
-
值类型
epScript 基本的
值类型只有一种,就是 32 位无符号整数。 -
逻辑规则
整数
0逻辑为假(false)
整数非 0 值逻辑为真(true) -
字面量数字(literal number)
字面量数字包括 10 进制数字、16 进制数字和二进制数字。
// 以下代码中的 15、0xf、0b1111 均表示同一个数 15,只是写法不同,所以 a、b、c 是相等的var a = 15;var b = 0xf; // 16 进制数以 0x 打头var c = 0b1111; // 二进制数以 0b 打头 -
字面量字符串(literal string)
字面量字符串用成对单引号
'或成对双引号"包裹字面值即可。DisplayText("这是字面量字符串");DisplayText('这是字面量字符串');DisplayText("这是字面量\字符串"); // 当字符串太长时,可以用反斜杠 \ 换一行继续这个字面量字符串,这并不表示在字符串中插入换行符,以上两个写法完全等价字面量字符串支持使用反斜杠转义符。
描述 说明 示例 示例结果 \\ 表示 \ 本身 DisplayText("你好\\星际");你好\星际 \八进制数 表示 ASCII 编码对应的那个字符 DisplayText("星际\101\102\103");星际ABC \x十六进制数 表示 ASCII 编码对应的那个字符 DisplayText("星际\x41\x42\x43");星际ABC \ 换行表示续行,不换行 DisplayText("你好\星际");你好星际 \n 换行符,等同于 \x0A DisplayText("你好\n星际");你好
星际\t 横向制表符,等同于 \x09 DisplayText("你好\t星际");你好 星际 \r 回车符,等同于 \x0D,在游戏中基本没有效果 DisplayText("你好\r星际");你好星际 \" 在双引号字符串中表示双引号本身 DisplayText("你好\"星际\"");你好"星际" \' 在单引号字符串中表示单引号本身 DisplayText('你好\'星际\'');你好'星际' -
字面量字节串(literal bytes)
字面量字节串用
b"和"包裹,或用b'和'包裹字面值即可。字节串不以 \0 结尾,字面量字节串同样支持字面量字符串中的转义符。println("{}", b"ASCII\nliteral");println("{}", b'ASCII\nliteral'); -
命名规则
变量名、常量名及函数名只能包含
非 ASCII 部分的 UTF-8 字符(中日韩文都可以)、ASCII 部分的字母/数字以及下划线_,并且不能以 ASCII 数字打头// 合法的变量名var a;var a_b;var a_1;var _a1;var 这也行;// 不合法的变量名var 3y = 1;var abc# = 2;变量名、常量名及函数名不能使用关键字命名。关键字列表未在此完整列出,但至少包括以下内容:
static var const object this functionreturn for foreach while switch epdswitchbreak continue if else once import astrue false变量名、常量名不能以 Python 3 的关键字命名
False None Trueand as assert break class continuedef del elif else except finallyfor from global if import inis lambda nonlocal not or passraise return try while with yield另一个特例是:如果函数名以 py_ 开头,调用它时需要使用 py_py_ 开头。
function py_函数名() {}function onPluginStart() {py_py_函数名();} -
引入其它模块
可以使用
import关键字引入其他模块,并用as为引入的模块指定别名。以下代码说明用法:模块1.eps:const 模块1的一个常量 = 0;static var 模块1的一个变量 = 0;function 模块1的一个函数() {模块1的一个变量++;return 模块1的一个变量;}模块2.eps:import 模块1;function 模块2的一个函数() {return 模块1.模块1的一个函数();}模块3.eps:import 模块1 as m1;import 模块2 as m2;function onPluginStart() {println("{}", m1.模块1的一个常量);m2.模块2的一个函数();} -
符号
所有涉及语法的符号都是英文输入状态下的半角符号,可以在 ASCII 表中找到。
-
代码块
使用成对的大括号
{}包围单句或多句代码,即可形成一个代码块。{// 代码}如果代码块中只有单句代码,也可以省略大括号。以下示例是合法的:
function 无聊的示例函数()for (var i = 1; i < 10; i++)if (i < 5)println("{} 小于 5", i);else if (i == 5)println("{} 等于 5", i);elseprintln("{} 大于 5", i); -
语法层换行符
语法层的换行符是分号
;,而不是实际换行符。var a;var b; -
索引运算符
[]用于通过索引访问或修改数组中的元素。const a = EUDArray(10);a[0] = 11;var b = a[0]; -
赋值符
赋值符号是单个等号
=。var a; // 声明变量 avar b = 3; // 声明变量 b 并赋值为 3a = 2; // 将变量 a 赋值为 2 -
行注释符
两个斜杠
//表示行注释的开始。var a = 1; // 注释是代码中不会执行的部分,从 // 开始到当前行的结尾的内容不会被认为是代码 -
块注释符
/*到*/之间的内容称为块注释。var /* 注释是代码中不会执行的部分,块注释可以加在代码中间 */ a = 1; -
条件运算符
- 大于
> - 小于
< - 大于等于
>= - 小于等于
<= - 等于
== - 逻辑与
&& - 逻辑或
||
值得一提的是,对变量使用条件比较运算时,返回的是
条件表达式常量,而非条件表达式的结果
if 的参数是条件表达式列表,如果将变量直接传入 if,则 if 会将其转换成一个运行时的取值是否不等于 0 的表达式if (a == 2) // 逻辑相等比较是两个等号单句; // 如果逻辑比较后执行的是单句代码,可以省略大括号if (a == 2) {// a 等于 2单句1;单句2;}if (a != 2) {// a 不等于 2}if (a > 2) {// a 大于 2}if (a < 2) {// a 小于 2}if (a >= 2) {// a 大于或等于 2}if (a <= 2) {// a 小于或等于 2}if (a > 2 && a < 10) {// a 大于 2 并且小于 10}if (a > 10 || a <= 5) {// a 大于 10 或小于等于 5}if ( !(a < 2) ) {// a 不小于 2}var a = 3;var b = a > 0; // 错误!它返回的并非 true,而是 a > 0 这个条件表达式本身var c = l2v(a > 0); // 这样就对了,运行时 c 就会等于 true 或 false,取决于运行时 a 的状态const d = a > 0; // 这里的 d 就代表 a > 0 这个条件表达式本身,不是 a > 0 的结果var e = l2v(d); // 在运行时使用 l2v 将 d 表达式的结果赋值给 e- 逻辑非
!
对变量
a使用!会返回一个变量,它的值是l2v((a != 0) == 0)的结果
对变量a使用两倍连续的!例如!!a或!(!a)或!!!(!a)都是直接等于a本身
对常量或条件表达式使用!则仍然会返回条件表达式var four = 4;var b = !four; // b == 0if (!b != !!four) println("b is not !!four");if (four == !!four) println("four is !!four"); - 大于
-
数学运算符
+-*/a = a + 1;a = a - 1;a = a * 2;a = a / 2; // 整数除法运算符,向下取整a = a % 2; // 取余运算符a = a ** 3; // 幂运算符,返回 a 的 3 次幂a = a << 1; // 左位移 1 位a = a >> 1; // 右位移 1 位 -
自增/自减/自乘/自除运算符
a += 10; // 它相当于 a = a + 10;a -= 10; // 它相当于 a = a - 10;a++; // 它相当于 a = a + 1;a--; // 它相当于 a = a - 1;a *= 10; // 它相当于 a = a * 10;a /= 10; // 它相当于 a = a / 10;
-
-
条件判断语法
-
if
if 语法的形式为:
if (条件表达式1) {条件表达式1 满足后执行;} -
if else
if 的否则分支使用 else 语法:
if (条件表达式1) {条件表达式1 满足时执行;} else {条件表达式1 不满足时执行;} -
条件串联
可以将 if 串联到另一个 else 上。
if (条件表达式1) {条件表达式1 满足时执行;} else if (条件表达式2) {条件表达式1 不满足并且 条件表达式2 满足时执行;} else {条件表达式1 和 条件表达式2 都不满足时执行;} -
条件嵌套
可以将 if 写到另一个 if 的代码块中。
if (条件表达式1) {条件表达式1 满足时执行;} else if (条件表达式2) {if (条件表达式3) {条件表达式1 不满足并且 条件表达式2 和 条件表达式3 都满足时执行;} else {条件表达式1 和 条件表达式3 都不满足并且 条件表达式2 满足时执行;}} else if (条件表达式4) {条件表达式1 和 条件表达式2 都不满足并且 条件表达式4 满足时执行;} else {条件表达式1 和 条件表达式2 都不满足时执行;} -
单次执行
顾名思义,就是在运行时条件满足并执行一次后就不再执行。它通常用在 beforeTriggerExec 或 afterTriggerExec 中,每帧重复判断,直到条件达成时执行一次。
once (条件表达式) { // 在运行时重复运行 once 代码块时,会在条件表达式满足后只执行一次其中的代码// 代码}once { // 无条件只执行一次其中的代码// 代码}// 以下代码执行后将只打印一次 0for (var i = 0; i < 100; i++) {once {println("{}", i);}}// 以下代码只会在机枪兵进入 1 到 10 号区域中的任意一个区域时触发一次,不会因分别进入每个区域而触发 10 次for (var i = $L("Location 1"); i <= $L("Location 10"); i++) {once ( Bring(P1, AtLeast, 1, "Terran Marine", i) ) {println("机枪兵进入区域 {}", i);}}
-
-
流程控制
-
for 循环
for 循环可设定循环初始化动作表达式、循环执行条件表达式以及每轮循环后的附加动作表达式。
for (初始化动作表达式; 循环执行条件表达式; 每循环附加动作表达式) {循环中的代码;}for (var i = 0; i < 10; i++) {println("{}", i);}// 简单来说,上面的代码声明了一个计数变量 i,初始值为 0;当 i < 10 时持续循环执行,并在每次执行后将 i 自增 1。i 的作用域就是后面的代码块,大括号中的内容是每次循环需要执行的代码。var i1, i8;for (i1, i8 = 0, 0 ; i1 < 10 && i8 < 80 ; i1++, i8 += 8) {printAll("{} x 8 = {}", i1, i8);} -
while 循环
while 循环可以设定一个循环条件。条件满足时持续循环执行,直到条件不满足为止。
var i = 0;while (i < 10) {println("{}", i);i++;} -
break 跳出循环
可以使用 break 跳出一个运行时循环或 switch。
var i = 0;while (true) { // 设定一个永远成立的条件,也就是一直返回 trueprintln("{}", i);if (i >= 10) {break;}}它和上面的 while 循环等效
-
foreach 迭代器循环
编译期迭代器
py_range 和 py_enumerator 是编译期迭代器
Python 层的容器(list、tuple 等)属于编译期迭代器
使用编译期迭代器的情况下,foreach 是编译期循环,会在编译期静态展开,不能使用 break 和 continueforeach (i : py_range(5)) {simpleprint(i + 1);}// 完全等价于以下代码simpleprint(0 + 1);simpleprint(1 + 1);simpleprint(2 + 1);simpleprint(3 + 1);simpleprint(4 + 1);foreach (i : py_range(3)) {once (ElapsedTime(AtLeast, i)) {println("第 {} 秒", i);}}// 完全等价于以下代码once (ElapsedTime(AtLeast, 0)) {println("第 {} 秒", 0);}once (ElapsedTime(AtLeast, 1)) {println("第 {} 秒", 1);}once (ElapsedTime(AtLeast, 2)) {println("第 {} 秒", 2);}const arrs = py_list();foreach (x : list(50, 100, 150)) {arrs.append(EUDArray(x));}// 完全等价于以下代码const arrs = py_list();arrs[0] = EUDArray(50);arrs[1] = EUDArray(100);arrs[2] = EUDArray(150);运行时迭代器
名字以 EUDLoop 开头的迭代器函数,通常返回运行时迭代器。EUDLoopPlayer、EUDLoopRange、EUDLoopUnit、EUDLoopUnit2、EUDLoopCUnit、EUDLoopNewUnit、EUDLoopNewCUnit、EUDLoopPlayerUnit、EUDLoopPlayerCUnit
此外,EUDQueue、EUDDeque 容器也属于运行时迭代器,UnitGroup.cploop 也会返回运行时迭代器。
EUDDeque 演示
// dq3 是一个尺寸为 3 的 EUDDequeconst dq3 = EUDDeque(3)();const ret = EUDCreateVariables(6);// 如果 dq3 为空则运行时不会有任何代码被执行foreach(v : dq3) {ret[0] += v;}// 从右侧添加 1 和 2 两个值到 dq3 这个 EUDDeque 中dq3.append(1); // dq3 : (1)dq3.append(2); // dq3 : (1, 2)foreach(v : dq3) {ret[1] += v; // 3 = 1 + 2}// 从右侧添加 3 和 4 两个值到 dq3 这个 EUDDeque 中dq3.append(3); // dq3 : (1, 2, 3)dq3.append(4); // dq3 : (2, 3, 4)foreach(v : dq3) {ret[2] += v; // 9 = 2 + 3 + 4}// 从右侧添加 5 这个值到 dq3 这个 EUDDeque 中dq3.append(5); // dq3 : (3, 4, 5)foreach(v : dq3) {ret[3] += v; // 12 = 3 + 4 + 5}// 从左侧弹出一个值,这里 3 被弹出const three = dq3.popleft(); // dq3 : (4, 5)foreach(v : dq3) {ret[4] += v; // 9 = 4 + 5}// 从右侧添加 6 和 7 两个值到 dq3 这个 EUDDeque 中dq3.append(6); // dq3 : (4, 5, 6)dq3.append(7); // dq3 : (5, 6, 7)foreach(v : dq3) {ret[5] += v; // 18 = 5 + 6 + 7} -
switch 变量值多重选择分支
用于对单个值的多种状态进行条件分支判断。
普通 switch
switch (day) {case 1:DisplayText("工作日开始了");break;case 4:case 5:DisplayText("马上周末了");break;case 0, 6:DisplayText("好嗨啊!");break;default:DisplayText("期待周末");}// 上述 switch 代码可以看作以下 if 条件分支代码if (day == 1) {DisplayText("工作日开始了");} else if (day == 4 || day == 5) {DisplayText("马上周末了");} else if (day == 0 || day == 6) {DisplayText("好嗨啊!");} else {DisplayText("期待周末");}带 bitmask 的 switch
var x = 0x101;switch (x, 0xff) {case 0:// (x & 0xff) == 0break;default:// (x & 0xff) != 0break;} -
epdswitch 内存值多重选择分支
用于对单个运行时内存位置的多种值状态进行条件分支判断。
const unitId = epd + 0x64/4;epdswitch (unitId, 255) { // you can put constant epd in epdswitch too// switch branching by unit kindcase $U("Terran Marine"):// Run when unitType is marinebreak;case $U("Terran Ghost"):// Run when unitType is ghostbreak;}
-