For the complete documentation index, see llms.txt. This page is also available as Markdown.

CSL 入门

CSL 是 All Out 定制的编程语言,旨在让编写多人游戏像制作单人游戏一样简单。

CSL 是 All Out 的自定义脚本语言。它是静态类型的,最像 Go/Odin —— 只不过 游戏状态会自动从服务器同步到客户端.

你不需要编写 RPC、SyncVar 或自定义复制。我们会帮你处理这些!

你的第一个脚本(main.csl)

当你创建新项目时,All Out 会生成一个 main.csl 在你的项目的 scripts/ 文件夹中。

import "core:ao"

// ============================================================================
// 全局生命周期
// ============================================================================

ao_before_scene_load :: proc() {
    // 注册物品定义、货币等。
    // 在空场景存在后、场景内容加载前运行。
}

ao_start :: proc() {
    // 场景开始时调用一次。
}

ao_update :: proc(dt: float) {
    // 每帧调用。
}

ao_late_update :: proc(dt: float) {
    // 在 ao_update 之后每帧调用。
}

// ============================================================================
// 玩家生命周期
// ============================================================================

Player :: class : Player_Base {
    ao_start :: method() {
    }

    ao_update :: method(dt: float) {
    }

    ao_late_update :: method(dt: float) {
    }

    ao_end :: method() {
    }
}

例如,在每个玩家加入时记录一条日志:

如果你想更深入了解这些函数何时运行,请参阅 游戏/帧生命周期.

导入

你的 main.csl 应导入 core:ao 以及你创建的任何文件夹(例如 ui/, abilities/,等等)。

导入指向文件夹,而不是单个文件。文件夹导入会包含该文件夹中的 .csl 文件。该文件夹内部的导入会按相对路径解析:

声明(变量和常量)

声明将一个名称绑定到一个值。

变量

可以 <type><expression> 省略:

常量

常量使用 :: 并且必须是编译期常量。它们可以是标量、字符串、类型、过程值、数组或复合字面量:

这是无效的(因为 a 不是编译期常量):

全局变量初始化值也必须是编译期常量。请使用 ao_before_scene_loadao_start 进行运行时初始化。

全局变量

全局变量使用与局部变量相同的声明语法。它们可以保持零初始化,或使用编译期常量进行初始化,包括结构体、数组、过程值以及 typeid 值:

全局变量是可变的,并会在脚本实例的整个生命周期内持续存在。避免将它们用于每个玩家的游戏状态。

类型

原始类型

  • 有符号整数: s8, s16, s32, s64

  • 无符号整数: u8, u16, u32, u64

  • 布尔值: bool

  • 浮点数: f32, f64

  • 别名:

    • int == s64

    • uint == u64

    • float == f32

  • 向量: v2, v3, v4

  • string

  • typeid

  • 任意

向量类型

v2 具有 .x, .y; v3 添加 .z; v4 添加 .w ——全部为 float 字段:

结构体和类

结构体是 值类型 (赋值时会复制)。类是 引用类型 (你用 new).

结构体(值类型)

类(引用类型)

类字段可以有默认值。派生类可以覆盖继承的默认值,而无需重新声明该字段:

继承

结构体/类可以从其他结构体/类继承:

过程和方法

过程(proc)

过程是普通值,可以像任何其他值一样被赋值/存储:

方法(method)

使用 method() 在结构体/类内部。方法具有一个隐式的 this 引用参数。

字段访问与方法调用

使用 . 对字段和方法都适用:

如果某个过程的第一个参数匹配接收者类型,那么它就可以像“方法”一样被调用。真正的方法以及类型上的过程值字段会优先于 CSL 回退到匹配的自由过程。

数组

CSL 有几种你会经常使用的“类数组”类型:

  • 固定数组: [4]int

  • 切片 / 托管数组: []T (通常用作数组的“只读视图”)

  • 动态数组: [..]T (可调整大小的列表)

  • 非托管数组: [^]T (用于内置 API 签名,例如 format_string, log_info,等等——将值作为 {a, b, c})

动态数组暴露 .data, .count,以及 .capacity,并对操作使用方法调用语法:

完整指南(包括移除模式),请参阅 数组和集合.

控制流

如果 / 否则

Switch

使用 default: 用于默认分支。分支支持 多个值 (以逗号分隔)以及 区间。没有 C 风格的贯穿。

.. 包括两个端点。 ..< 不包含上限端点。

While / for

基于自定义迭代器的 for 循环需要一个 next :: method() -> bool 以及一个 current 字段。

类型转换

使用 expr.(T)cast(T)expr 进行类型转换:

当目标类型已知时,你可以让 CSL 推断它:

按引用传递: ref (推荐)

当你需要修改参数时, 优先使用 ref 而不是原始指针。

回调:函数指针 + userdata(无闭包)

CSL 没有闭包。内联 proc(...) { ... } 无法捕获周围变量。

要携带上下文,请将回调与一个 userdata: Object 字段:

类型信息(将类型作为值)

typeid 值可以传递给多态过程:

最佳实践(All Out 中的 CSL)

  • 避免全局游戏状态。 多个玩家连接时——将每个玩家的状态存储在 Player 其上,而不是全局变量中。

  • 所有玩家 UI 都应绘制自 Player.ao_late_update. 将其包装在 is_local_or_server().

  • 使用 is_local() 中,仅用于玩家特定的视觉覆盖。 不要在其中更改游戏玩法或 UI 状态。

  • 默认以移动端优先。 除非你的游戏明确面向 PC,否则不要依赖键盘/鼠标输入。

  • 如果你不确定语法或 API,请打开对应的 .csl_engine 文件,位于 api_references/ 在你的项目中。

最后更新于