附件系统API

附件系统API

一种简单、自动化且优化的将部件附加到对象上的方法

附件系统 API

一个独立的框架,可将零件附件到任何物体上,汽车、家具,任何东西。

■ 关于

用于在 My Summer Car / My Winter Car 中将零件吸附到物体上的独立框架

基于 OASIS,但完全重写,更加直观、更加自动化、更加优化,而且附带了指南。

  • 玩家:将 AttachmentSystem.dll 放入 Mods 文件夹的 References 文件夹中。完成。
  • **模组开发者:继续阅读。 **

■ UNITY 设置

1. Plugins 文件夹:Assets/Plugins/

  • AttachmentSystem.dll
  • PlayMaker.dll
  • UnityEngine.dll
  • (两者均来自游戏的 Managed 文件夹)

2. Editor 文件夹:Assets/Editor/

  • AttachmentPartsEditor.cs — 在检视面板中添加拖放框
  • SnapTriggerIdAutomator.cs — 添加 工具 ▸ 附件系统 菜单

3. Visual Studio:添加对 AttachmentSystem.dll 的引用,然后:

using AttachmentSystem;

■ 三个核心脚本

  • AttachmentParts:挂在零件本身(你要附着的物体)上
  • bolts:挂在固定零件的每个独立螺栓上
  • SnapTriggerId:挂在不可见的触发器碰撞体(“安装点”)上

■ 设置零件(分步指南)

1. 将 AttachmentParts 添加到你的零件 GameObject 上。 2. 设置 Trigger Ids 数组:该零件可以吸附到的安装点的字符串 ID。 3. 设置 Bolts 数组:将属于该零件的每个螺栓 GameObject 拖入。 4. (可选)填写 Required Parts 和 Blocker Parts:

  • Required Parts:在此零件可以吸附之前必须已经附着的其他零件(例如气缸盖需要先有缸体)。
  • Blocker:不能附着的零件,否则此零件被阻止(例如气门室盖阻止凸轮轴拆卸)。

5. (可选)Joint Physics:

  • useJoint = true → 零件使用真实的 FixedJoint,可在碰撞中脱落。
  • maxBreakForce → 完全拧紧时断裂所需的力。
  • unbreakableAtTightness → 设置一个紧固度值,使零件在该值下变得不可破坏(使用 -1 表示始终可破坏)。

■ 设置吸附触发器

吸附触发器是目标物体(汽车、家具……)上的一个不可见碰撞体,表示“此零件安装在这里”。

方法 A — 手动(推荐):

  1. 在零件应吸附的位置创建一个空 GameObject。
  2. 添加一个 Collider(通常是 BoxCollider),标记为 Is Trigger。
  3. 添加 SnapTriggerId 脚本。
  4. 将 Id 字段设置为唯一字符串(例如 MyCustomPart_Trigger)。
  5. 在你的零件的 AttachmentParts 中,将相同的字符串添加到 Trigger Ids 中。

方法 B — 自动(批量): 在层级视图中选中所有触发器 GameObject → 菜单 工具 ▸ 附件系统 ▸ 自动为选中对象添加 SnapTriggerId。每个触发器都会获得一个 SnapTriggerId,其 Id 设置为 GameObject 名称。

额外功能:在 AttachmentParts 检视面板上有一个“将触发器拖到此处以自动填充 ID”的框,将触发器 GameObject 拖入其中,Trigger Ids 数组会自动填充。

■ 预制体布局示例

Car (prefab)
└── Triggers
    └── MyCustomPart_Trigger  [BoxCollider (isTrigger)] + [SnapTriggerId id="MyCustomPart_Trigger"]

MyCustomPart (prefab)
├── AttachmentParts
│   ├── Trigger Ids: [ "MyCustomPart_Trigger" ]
│   └── Bolts:      [ Bolt_1, Bolt_2, Bolt_3, Bolt_4 ]
├── Bolt_1  [bolts, size=0.5, maxTightness=8]
├── Bolt_2  [bolts, size=0.5, maxTightness=8]
├── Bolt_3  [bolts, size=0.5, maxTightness=8]
└── Bolt_4  [bolts, size=0.5, maxTightness=8]

■ 设置螺栓

  1. 将 Bolt GameObject 添加为零件的子物体。
  2. 添加 bolts 脚本。
  3. 设置 Wrench Size 和 Max Tightness(原版游戏使用 8)。
  4. 将其拖入零件的 Bolts 数组中。

扳手尺寸对照表(与原版 MSC 数值一致):

0.5 =  5 mm    1.0 = 10 mm    1.4 = 14 mm
0.6 =  6 mm    1.1 = 11 mm    1.5 = 15 mm
0.7 =  7 mm    1.2 = 12 mm    
0.8 =  8 mm    1.3 = 13 mm    

只有当数组中的每个螺栓都完全拧紧时,零件才被视为完全安装(并达到完整的 breakForce)。

■ 保存与加载

框架不会为你保存状态,它暴露了所有内容,以便你可以使用 MSCLoader 的 SaveLoad 进行保存。

每个零件需要保存的内容:

  • attachedTo(int)— 零件吸附到的触发器索引,如果松散则为 -1。
  • 对每个螺栓:bolts[i].tightness(int)。
  • 如果零件在世界中处于松散状态,则保存 Transform 位置/旋转。

示例:

using System;
using System.Linq;
using UnityEngine;
using MSCLoader;

[Serializable]
public class PartSave
{
    public int attachedTo = -1;
    public int[] boltTightness;
    public Vector3 pos;
    public Vector3 rot; // Eulers
}


// --- 如何保存状态 ---

void SavePartState(Mod mod, AttachmentParts myPart)
{
    PartSave data = new PartSave
    {
        attachedTo    = myPart.attachedTo,
        boltTightness = myPart.bolts != null
            ? myPart.bolts.Select(b => b != null ? b.tightness : 0).ToArray()
            : new int[0],
        pos = myPart.transform.position,
        rot = myPart.transform.eulerAngles
    };

    SaveLoad.WriteValue<PartSave>(mod, "MyCustomPartSave", data);
}


// --- 如何加载/恢复状态(OnLoad / Start) ---

void LoadPartState(Mod mod, AttachmentParts myPart)
{
    if (SaveLoad.ValueExists(mod, "MyCustomPartSave"))
    {
        PartSave loadedData = SaveLoad.ReadValue<PartSave>(mod, "MyCustomPartSave");
        if (loadedData == null) return;

        if (loadedData.attachedTo >= 0)
        {
            // 使用 ForceAttach 恢复附着槽位(启动时绕过声音/事件)
            myPart.ForceAttach(loadedData.attachedTo);

            if (myPart.bolts != null && loadedData.boltTightness != null)
            {
                for (int i = 0; i < myPart.bolts.Length; i++)
                {
                    if (i < loadedData.boltTightness.Length && myPart.bolts[i] != null)
                    {
                        myPart.bolts[i].tightness = loadedData.boltTightness[i];
                    }
                }
            }
        }
        else
        {
            // 恢复世界中的松散位置和旋转
            myPart.transform.position = loadedData.pos;
            myPart.transform.rotation = Quaternion.Euler(loadedData.rot);
        }
    }
}

■ 事件与回调

在检视面板中(UnityEvents,无需代码):

  • onAttachedEvent / onDetachedEvent
  • onFullyTightenedEvent / onFullyLoosenedEvent
  • onBreakEvent — 在碰撞中关节断裂时触发

在 C# 中(代码回调):

myPart.onAttach  += (index) => Debug.Log("Attached to " + index);
myPart.onDetach  += (index) => Debug.Log("Detached from " + index);
myPart.onBreak    += (force) => Debug.Log("Broke at " + force);

// 门控 — 返回 false 以阻止操作
myPart.canAttach = () => Game.EngineIsOff;
myPart.canDetach = () => Game.HandbrakeOn;

■ 故障排除

  • 零件无法吸附 → 检查触发器是否启用了 Is Trigger,并且 Id 完全匹配(区分大小写)。
  • 控制台显示“SnapTrigger not found” → triggerIds 中有拼写错误,或者触发器 GameObject 在加载时处于非活动状态。
  • 螺栓不显示 → 正常。螺栓在零件附着到触发器之前是隐藏的。
  • 零件穿过汽车掉落 → useJoint = true 且 breakForce 非常低。提高 maxBreakForce 或设置 unbreakableAtTightness。
  • 关于 Editor 脚本的构建错误 → Editor 脚本必须放在名为 Editor 的文件夹内。它们绝不能包含在最终 DLL 中。

就这些,这是我能做到的最“傻瓜式”的教程了,爱你们。

  • 框架:Toko
  • 灵感来源:OASIS