Space Engineers 可编程方块从入门到放弃

2016-4-17 14:39:01

好好的一个游戏,玩着玩着就写起了代码

游戏里的编辑器没有代码提示没有代码高亮,甚至连括号对齐都没有,于是我们首先需要搭一个VS下的开发环境,用于写代码

并不是所有C#的内置类或者游戏类均可用,文章的最后会说明

首先需要把游戏源码clone下来https://github.com/KeenSoftwareHouse/SpaceEngineers/

为了不要一不小心修改了游戏代码,可以在解决方案里新建一个Console项目,然后添加对游戏项目的引用,然后让生成出来的类继承MyGridProgram类,
并且修改Main方法,拿掉static,参数是(string args),
添加需要用到的引用,
最终代码应该长这样

using Sandbox.Common;
using Sandbox.Common.Components;
using Sandbox.Definitions;
using Sandbox.Engine;
using Sandbox.ModAPI.Ingame;
using Sandbox.ModAPI.Interfaces;
using Sandbox.Game;
using Sandbox.Game.Entities.Blocks;
using Sandbox.Game.Gui;
using Sandbox.ModAPI;

namespace Scripts
{
    class Program : MyGridProgram
    {

        public void Main(string args)
        {
        
        }
    }
}

顺着MyGridProgram类,不难找到GridTerminalSystem这个属性,它也是我们在可编程方块里唯一可用的和游戏对接的入口

它拥有六个方法

  • void GetBlocks(List blocks); 获取当前设施内的所有方块

  • void GetBlockGroups(List blockGroups); 获取当前设施内的所有组

  • void GetBlocksOfType(List blocks, Func<IMyTerminalBlock, bool> collect = null); 获取当前设施内的所有继承T类型的方块,继承关系参见http://www.spaceengineerswiki.com/Programming_Guide/Action_List

  • void SearchBlocksOfName(string name,List blocks, Func<IMyTerminalBlock, bool> collect = null); 按照名称搜索,大小写不敏感

  • IMyTerminalBlock GetBlockWithName(string name); 按照名称获取方块

  • IMyBlockGroup GetBlockGroupWithName(string name); 获取组

获取到特定方块后,可以用as方法将其转换为特定方块的Interface,比如 var block = GridTerminalSystem.GetBlockWithName("Assembler Main") as IMyAssembler;

类型转换后就能得到更多当前方块的特定属性了,比如IMyAssembler可以获取IsQueueEmpty来判断是否队列空

Action

控制门的开关、起落架的锁定、连接器的对接都是一个Action,每个方块可用的Action见http://www.spaceengineerswiki.com/Programming_Guide/Action_List

这是一个切换门开关状态的栗子

        public void SwitchDoor(string name)
        {
            var block = GridTerminalSystem.GetBlockWithName(name) as IMyDoor;
            var action = block.GetActionWithName("Open");
            action.Apply(block);
        }

如果想要切换一个分组的门开关,没有直接的方法,只能

        public void SwitchDoors(string group)
        {
             GridTerminalSystem.GetBlockGroupWithName(group).Blocks.ForEach(block => SwitchDoor(block.CustomName));
        }

Inventory

获取方块存储的方式也很简单

var mainInv = GridTerminalSystem.GetBlockWithName(main).GetInventory(0);

有些方块是不止一个存储空间的,比如Assembler,矿物区和成品区是两个Inventory,从上往下从0开始数,作为GetInventory传入的参数即可

在Inventory间传递物品

获取到的Inventory对象实现了IMyInventory接口,有
bool TransferItemTo(IMyInventory dst, int sourceItemIndex, int? targetItemIndex = null, bool? stackIfPossible = null, VRage.MyFixedPoint? amount = null);和
bool TransferItemFrom(IMyInventory sourceInventory, int sourceItemIndex, int? targetItemIndex = null, bool? stackIfPossible = null, VRage.MyFixedPoint? amount = null);
两个方法用于物品传输,前提是block之间有管道连接,若无管道连接或者目标存储已满,则返回false

判断物品种类

var inv = block.GetInventory(1);
                var items = inv.GetItems();
                if (items.Count > 0)
                {
                    foreach (var item in items)
                    {
                        if (item.Content.TypeId == typeof(MyObjectBuilder_Ingot))
                        {
                            inv.TransferItemTo(target.GetInventory(0), 0);
                        }
                    }
                }

如此可以判断是否为矿物

代码限制

只能使用
Sandbox.ModAPI.Ingame
Sandbox.ModAPI.Interfaces
Sandbox.Common.ObjectBuilders
VRageMath
VRage

这五个命名空间内的API,不能使用反射,不能使用Console对象

调试

        public void Main(string args)
        {
            Echo(args);
        }

类API

构造函数 Program(){},可以不存在

public void Main()或者public void Main(string arg)必须存在其中一个,有参数的优先