自动关门脚本

前言

这是将会是你的第一个实用性脚本,你也不希望自己飞船的门打开后还得自己关上吧(

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


自动关门脚本

思路分析

本脚本目标是在门被打开后一段延迟内自动将其关闭,减少玩家手动关门的麻烦。实现思路很直接:

  1. 将需要自动关闭的门放入一个方块组(本例组名:自动控制门
  2. 脚本每10帧检查一次这些门的状态(使用 Runtime.UpdateFrequency = Update10 优化性能减少高频占用)
  3. 当检测到门处于打开状态时,开始计时;当累计时间超过设定阈值时调用 CloseDoor() 关闭门并重置计时。
  4. 支持通过可编程方块的 CustomData 设置自动关闭的时间(秒)。

这种实现简单易懂,适合小型船坞或室内门的自动化。

放置多个门方块

还是老样子,只不过这次我们多放几个门方块(因为是编组,所以我们模拟多个门的情况)

Image

这样我们就有3个门方块了

方块编组

根据刚刚的思路,现在我们需要给门编组为 自动控制门 。不过在此之前,我们先来讲讲如何给方块编组,主要分为以下两种操作方式:

  1. 选择单个
  2. 选择到当前位置为止

选择单个

我们先来看第一种每次选择单个方块,具体操作为:

  1. 按住 Ctrl
  2. 鼠标左键选择方块
  3. 选择完成后在右侧输入编组名称并保存

Image

当然也可以用这种操作取消选择的方块

Image

选择到当前位置

这种方式适用于选择连续多个方块的情况,具体操作为:

  1. 点击要批量选取方块的起始方块
  2. 按住 Shift
  3. 点击批量选取方块的结束方块
  4. 选择完成后在右侧输入编组名称并保存

Image

经过以上操作之后,我们就成功的将所有门编组为了 自动控制门

代码实现

在完成了上述步骤之后呢,先来看一下我们根据我们的思路实现的代码:

using Sandbox.ModAPI.Ingame;
using System;
using System.Collections.Generic;
using XFEExtension.SpaceEngineers.ScriptingHelper;

namespace MyProject
{
    internal class Program : MyGridProgram, IProgramBase
    {
        // 注意,复制到游戏内编程块的时候应当从下面开始复制
        //------------------从此处开始复制------------------//
        
        // 定义成员变量
        List<IMyDoor> doorList = new List<IMyDoor>(); // 定义一个门列表,用于存储需要自动关闭的门
        double time; // 定义一个时间变量,用于记录门打开的持续时间
        double closetime = 1.2d; // 定义一个自动关闭的时间阈值,默认为1.2秒

        public Program()
        {
            // 这里是 构造函数
            Runtime.UpdateFrequency = UpdateFrequency.Update1; // 设置 Main函数 的触发频率为 Update1 —— 每帧更新一次
            var doors = GridTerminalSystem.GetBlockGroupWithName("自动控制门"); // 获取一个叫 自动控制门 的方块组
            doors.GetBlocksOfType(doorList); // 将刚刚获取的方块组中的所有门方块添加到 doorList 中
        }

        public void Main(string argument, UpdateType updateSource)
        {
            // 这里是 主函数
            if (Me.CustomData != "") // 如果自定义数据不为空,则尝试将其解析为自动关闭的时间
            {
                closetime = double.Parse(Me.CustomData); // 将自定义数据解析为 double 类型,并赋值给 closetime 变量
            }
            Echo("脚本使用说明:\n将需要自动关闭的门编组,设置名称为:自动控制门\n在自定义数据里面输入自动关门的时间\n目前自动关门时间:" + closetime.ToString() + "秒\nby XFEstudio"); // 输出脚本使用说明和当前自动关门时间
            Me.CustomName = "自动关门-XFEstudio"; // 设置编程块的名称为 自动关门-XFEstudio
            foreach (var door in doorList) // 遍历门列表中的每个门
            {
                if (door.Status == DoorStatus.Open) // 如果门的状态是打开的
                {
                    time += Runtime.TimeSinceLastRun.TotalSeconds; // 将每帧的时间增量累加到 time 变量中
                    if (time > closetime) // 如果累计的时间超过了自动关闭的时间阈值
                    {
                        door.CloseDoor(); // 尝试关闭门
                        time = 0.0d; // 重置 time 变量,以便下次门打开时重新计时
                    }
                }
            }
        }

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

        //------------------从此处结束复制------------------//
    }
}

说明:将上面的代码复制到可编程方块并编译运行,确保你所有的门已经加入名为 自动控制门 的方块组。

效果预览

Image

是不是更神奇了,这些门居然在打开后的一段时间后自己关上了(666这个门又开桂)

其实根据我们之前的思路,这段时间发生的事件大致是

  1. 玩家打开门
  2. 脚本检测到门为打开状态开始计时
  3. 当计时超过 CustomData 中设定的秒数(默认 1.2s)时,脚本调用 CloseDoor() 关闭门

实际效果是门会在短时间后自动合上,常用于防止门长时间敞开导致气压流失或视觉不美观。

代码解释

  • List<IMyDoor> doorList:用于存储需要自动关门的门引用。
  • double time:当前实现使用单一计时器记录“检测到打开到关闭”的累计时间。
  • double closetime:自动关闭阈值(秒),可通过可编程方块的 CustomData 修改。
  • 构造函数 Program():设置 Runtime.UpdateFrequency = Update10,并通过 GridTerminalSystem.GetBlockGroupWithName("自动控制门") 获取组内门并填充 doorList
  • Main:每帧先读取 CustomData 更新 closetime,然后遍历 doorList,若任一门为 Open 状态则对 time 做累加,当超过 closetime 则调用 CloseDoor() 并重置 time

常见问题与调试

  • 问:脚本没有关闭门?

    • 检查方块组名称是否正确(自动控制门),确认门在同一网格或可被脚本访问;
    • 在脚本中加入 Echo 输出 door.Statustime 辅助调试;
  • 问:多个门同时使用时行为不正确?

    • 当前实现使用单一 time 变量来为所有门计时,这会导致当多个门独立打开时计时逻辑混淆(例如门A短时间关闭,门B刚打开但 time 已部分被消耗)。见“进阶改进”。

改进——为每扇门单独计时

其实这个改进并不是那么重要,因为检测门状态几乎是实时的,而且在整体上也大致是每个门单独计时(因为基本上不会出现两扇门同时打开的情况)

不过呢,如果硬是要解决多个门同时打开时计时冲突的问题,可以为每扇门维护独立的计时器。可以使用 Dictionary<long, double> 以门的 EntityId 作为键,记录每扇门的累计打开时间:

示例改进:

Dictionary<long, double> doorTimers = new Dictionary<long, double>();

// 在构造函数或初始化时填充 doorTimers 中的键,初始值为 0

foreach (var door in doorList)
{
    long id = door.EntityId;
    if (!doorTimers.ContainsKey(id)) doorTimers[id] = 0.0;
}

// 在 Main 中对每扇门分别计时
foreach (var door in doorList)
{
    long id = door.EntityId;
    if (door.Status == DoorStatus.Open)
    {
        doorTimers[id] += Runtime.TimeSinceLastRun.TotalSeconds;
        if (doorTimers[id] >= closetime)
        {
            door.CloseDoor();
            doorTimers[id] = 0.0;
        }
    }
    else
    {
        doorTimers[id] = 0.0; // 门关着时重置该门计时
    }
}

这种改进能保证每扇门的自动关闭行为互不干扰,适用于多个门的场景(如多个机舱或走廊门)。

小结

本篇介绍了一个简单的自动关门脚本:如何获取门组、设置执行频率、通过 CustomData 控制延迟时间并在检测到门打开后延迟关闭。对于更复杂的场景,建议为每扇门维护独立计时器以避免互相影响。

练习:尝试将改进后的独立计时版本实现到你的 Program.cs 中,并测试多个门同时开启时的行为是否正常。