第一个游戏脚本-循环开关门

前言

这将会是你的第一个真正意义上可以控制方块的游戏脚本!

这游戏最常见的应该就是各种门了,我们就写一个可以控制门的脚本,将从 简单 -> 进阶 两部分来讲解


循环开关门

这是最简单的一个脚本,可以做到让一扇门每过一段时间打开/关闭一次。虽然没什么实际作用,但是作为第一段游戏内脚本的理解却是非常合适的。

思路分析

在开始写我们的代码之前,我们先想一想我们需要什么

既然是一个控制门的脚本,我们第一步首先得 获取门方块,又因为是循环开关门,那就意味着每过一段时间开/关一次,所以我们的第二步就是 统计已过时间,第三步就是如果上次开/关门的时间大于我们设定的时间时(这里我们设定时间为3秒)就 执行一次开/关门。最后循环第二步和第三步即可

那么我们将其列成一个列表大致就是:

  1. 获取门方块
  2. 统计已过时间
  3. 执行一次 开门/关门
  4. 循环第 23

有了大体的思路之后,我们就可以进行接下来的操作了。

放置门方块

根据上面的思路,我们首先得放一个门方块

Image

OK,现在我们有一扇门了

重命名门

光有一个门是不够的我们还得进入终端将 滑动门-hdm- 重新命名为 完成了这一步之后,我们就可以进入下一步代码的编写了。

注意:这一步是为了统一你放置的门的名称。因为脚本里面门的名称是写死的,但是玩家放置的门可能会根据门的类型不同而有 不同的名字
因此,一定要确保刚刚放置的门的名称修改完成,否则脚本将无法获取门方块

代码实现

我们先来看一下代码,将其复制到你的 Visual Studio 2026 编译器中

using Sandbox.ModAPI.Ingame;
using XFEExtension.SpaceEngineers.ScriptingHelper;

namespace MyProject
{
    internal class Program : MyGridProgram, IProgramBase
    {
        // 注意,复制到游戏内编程块的时候应当从下面开始复制
        //------------------从此处开始复制------------------//
        
        // 定义成员变量
        private double time; // 定义时间为 double 类型
        private IMyDoor myDoor; // 定义门对象为 IMyDoor 接口

        public Program()
        {
            // 这里是 构造函数
            time = 0; // 将初始时间设置为0
            myDoor = GridTerminalSystem.GetBlockWithName("门") as IMyDoor; // 通过方块的名称获取方块
            Runtime.UpdateFrequency = UpdateFrequency.Update10; // 设置 Main函数 触发频率为每10个tick触发一次
        }

        public void Main(string argument, UpdateType updateSource)
        {
            // 这里是 主函数
            time += Runtime.TimeSinceLastRun.TotalSeconds; // 获取距离上一次 Main函数 被触发过去的时间,以秒为单位
            if (time > 3) // 如果累计的已过时间大于3秒
            {
                time = 0; // 将所有累计的时间情况,等待下一次3秒
                if (myDoor.Status == DoorStatus.Closed) // 如果门的状态是关闭的
                {
                    myDoor.OpenDoor(); // 则控制开门
                }
                else if (myDoor.Status == DoorStatus.Open) // 如果门的状态是开启的
                {
                    myDoor.CloseDoor(); // 则控制关门
                }
            }
            Echo($"已过时间:{time:F2}(s)\n当前门的状态:{myDoor.Status}"); // 输出脚本当前的执行状态,方便我们调试
        }

        public void Save()
        {
            // 这里是 保存函数
        }
        //------------------从此处结束复制------------------//
    }
}

乍一看很头疼对吧?那咱们先将其复制到游戏中看看效果

// 定义成员变量
private double time; // 定义时间为 double 类型
private IMyDoor myDoor; // 定义门对象为 IMyDoor 接口

public Program()
{
    // 这里是 构造函数
    time = 0; // 将初始时间设置为0
    myDoor = GridTerminalSystem.GetBlockWithName("门") as IMyDoor; // 通过方块的名称获取方块
    Runtime.UpdateFrequency = UpdateFrequency.Update10; // 设置 Main函数 触发频率为每10个tick触发一次
}

public void Main(string argument, UpdateType updateSource)
{
    // 这里是 主函数
    time += Runtime.TimeSinceLastRun.TotalSeconds; // 获取距离上一次 Main函数 被触发过去的时间,以秒为单位
    if (time > 3) // 如果累计的已过时间大于3秒
    {
       time = 0; // 将所有累计的时间情况,等待下一次3秒
       if (myDoor.Status == DoorStatus.Closed) // 如果门的状态是关闭的
       {
           myDoor.OpenDoor(); // 则控制开门
       }
       else if (myDoor.Status == DoorStatus.Open) // 如果门的状态是开启的
       {
           myDoor.CloseDoor(); // 则控制关门
       }
    }
    Echo($"已过时间:{time:F2}(s)\n当前门的状态:{myDoor.Status}"); // 输出脚本当前的执行状态,方便我们调试
}

public void Save()
{
    // 这里是 保存函数
}

效果预览

Image

是不是很神奇,这个门居然会每隔 3s 开/关一次,放恐怖片里面就是妥妥的整蛊道具(666这个门是桂)

那么接下来我们就要开始理解这段代码是在干什么了

代码解释

现在我们将代码拆开细分成多个部分来讲解:

  • 定义成员变量
  • Program 构造函数
  • Main 主函数

如果你看了之前的 主要函数介绍 篇就不难发现,我们的讲解顺序其实就是按照:构造函数 > 主函数 > 保存函数 来讲解的,只不过目前我们不需要用到保存函数。

定义成员变量

按照先前我们讲的思路——既然我们要 获取门方块,还要 统计已过时间。那么必然的,我们需要存储这两个东西,用什么来存储最合适呢?那当然是变量了!

不过变量和变量之间亦有区别我们这里用的是成员变量,我们先来看看成员变量分的定义吧

定义

定义在 类的内部方法的外部变量 称为 成员变量 也叫 全局变量

具体对应到我们代码中就是这两行

private double time; // 定义时间为 double 类型
private IMyDoor myDoor; // 定义门对象为 IMyDoor 接口

第一行我们定义了一个 double 双浮点类型的变量用来存储,已过的时间(以秒为单位),第二行 IMyDoor myDoor 用来保存我们通过名字获取到的门对象引用。

下面我们逐段讲解代码是如何工作的。

Program 构造函数

构造函数在脚本被加载或重编译时执行一次,用来做初始化工作。本例中我们做了三件事:

  • 将累计时间 time 初始化为 0
  • 通过 GridTerminalSystem.GetBlockWithName("门") as IMyDoor 获取名为“门”的方块并转换为 IMyDoor 接口;
  • 设置 Runtime.UpdateFrequency = UpdateFrequency.Update10,让 Main 每隔 10 个 tick 被调用一次(注意:不同的世界/服务器 tick 速率不同,这里只是示例)。

注意事项:

  • GetBlockWithName 会返回 null(例如名字不匹配或方块不存在),因此在使用 myDoor 前最好做 null 检查;
  • UpdateFrequency 可以设置为 Update1/Update10/Update100,选择合适频率以平衡响应与性能开销。

Main 主函数

Main 是脚本的入口,会按设定的频率被触发。关键点如下:

  • 使用 Runtime.TimeSinceLastRun.TotalSeconds 获取距离上一次 Main 被触发经过的秒数,累加到 time
  • time 超过阈值(例中为 3 秒)时,重置计时并根据门的当前 Status 执行开或关操作:
    • DoorStatus.Closed 则调用 myDoor.OpenDoor()
    • DoorStatus.Open 则调用 myDoor.CloseDoor()
  • 使用 Echo 输出当前状态,便于在可编程块界面调试观察。

额外建议:

  • 在操作前做 if (myDoor == null) { Echo("未找到名为 '门' 的方块"); return; } 的判断,避免脚本运行时报错;
  • 门的状态还有 Opening/Closing 等中间状态,根据需要可以做更细致的处理以避免重复调用。

Save 方法

Save 在世界保存时会被调用(例如退出或手动保存),用于将需要持久化的数据写入 Storage。本示例不需要保存数据,因此留空即可。

调试技巧与常见问题

  • 如果脚本提示 System.NullReferenceException 之类的报错,请确认方块名称是否正确、方块是否属于同一网格或有权限访问;
  • 使用 Echo 输出关键变量,方便在可编程方块界面实时观察脚本行为。

练习

  1. 修改脚本,使开关间隔由 Mainargument 参数控制(例如传入 interval=5);
  2. 将脚本改为只在接收到特定参数(例如 toggle)时才切换门,而不是循环切换;
  3. 尝试实现带传感器的自动关门:在门打开后,如果传感器在 N 秒内没有检测到实体则关闭门。

小结

本教程通过一个简单的循环开关门脚本引导你理解如何获取方块、使用 Runtime 计时、判断方块状态并执行操作。掌握这些基础后,你可以把思路扩展到控制灯光、传送器、转子等更多方块上,构建更复杂的自动化系统。