跳到主要内容

基本语法



基本语法的说明

  • 编译期(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换行符,等同于 \x0ADisplayText("你好\n星际");你好
    星际
    \t横向制表符,等同于 \x09DisplayText("你好\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 function
    return for foreach while switch epdswitch
    break continue if else once import as
    true false

    变量名、常量名不能以 Python 3 的关键字命名

    False None True
    and as assert break class continue
    def del elif else except finally
    for from global if import in
    is lambda nonlocal not or pass
    raise 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);
      else
      println("{} 大于 5", i);
    • 语法层换行符

      语法层的换行符是分号;,而不是实际换行符。

      var a;var b;
    • 索引运算符

      [] 用于通过索引访问或修改数组中的元素。

      const a = EUDArray(10);
      a[0] = 11;
      var b = a[0];
    • 赋值符

      赋值符号是单个等号=

      var a; // 声明变量 a
      var b = 3; // 声明变量 b 并赋值为 3
      a = 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 == 0
      if (!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 { // 无条件只执行一次其中的代码
      // 代码
      }

      // 以下代码执行后将只打印一次 0
      for (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) { // 设定一个永远成立的条件,也就是一直返回 true
      println("{}", i);
      if (i >= 10) {
      break;
      }
      }

      它和上面的 while 循环等效

    • foreach 迭代器循环

      编译期迭代器
      py_range 和 py_enumerator 是编译期迭代器
      Python 层的容器(list、tuple 等)属于编译期迭代器
      使用编译期迭代器的情况下,foreach 是编译期循环,会在编译期静态展开,不能使用 break 和 continue

      foreach (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 的 EUDDeque
      const 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) == 0
      break;
      default:
      // (x & 0xff) != 0
      break;
      }
    • epdswitch 内存值多重选择分支

      用于对单个运行时内存位置的多种值状态进行条件分支判断。

      const unitId = epd + 0x64/4;
      epdswitch (unitId, 255) { // you can put constant epd in epdswitch too
      // switch branching by unit kind
      case $U("Terran Marine"):
      // Run when unitType is marine
      break;
      case $U("Terran Ghost"):
      // Run when unitType is ghost
      break;
      }