如何在Unity3D中实现玩家移动物体?适配《漫漫长夜》Melon Loader Mod
实现《漫漫长夜》可移动容器Mod方案(基于Melon Loader)
针对你需要实现的可移动带物品容器功能,以下是基于Melon Loader的无源码修改方案,适配新版游戏版本:
核心思路
通过Melon Loader内置的Harmony Hook机制,拦截游戏原有交互逻辑,替换为自定义的容器移动流程,同时维护容器与内部物品的层级关联,确保移动时物品同步跟随,且不修改游戏原生文件。
1. 识别可移动容器
首先需要确定游戏中哪些对象属于可移动容器,可以通过以下方式判断:
- 查找对象上的
Container组件(游戏原生容器标识) - 结合对象标签或预设名过滤(比如排除固定储物柜类容器)
- Hook玩家交互检测逻辑,在交互触发时判断目标对象是否符合可移动容器条件
2. 拦截交互触发逻辑
使用Harmony Hook替换原生交互逻辑,当玩家点击可移动容器时,启动自定义移动流程:
using MelonLoader; using HarmonyLib; using UnityEngine; [HarmonyPatch(typeof(PlayerInteract), "InteractWithObject")] public static class ContainerInteractPatch { static bool Prefix(GameObject targetObj) { Container targetContainer = targetObj.GetComponent<Container>(); // 自定义判断逻辑:排除固定容器(如储物柜) if (targetContainer != null && IsMovable(targetContainer)) { ContainerMovementHandler.StartMoving(targetContainer); return false; // 阻止原生交互逻辑执行 } return true; // 非容器对象正常走原生逻辑 } static bool IsMovable(Container container) { return container.m_ContainerType != ContainerType.Locker; } }
3. 同步容器与内部物品位置
移动过程中,需要确保容器内物品跟随容器同步移动,核心是维护物品与容器的父级关系:
public static class ContainerMovementHandler { private static Container _currentMovingContainer; public static bool IsMoving => _currentMovingContainer != null; public static void StartMoving(Container container) { _currentMovingContainer = container; // 将所有物品父级设为容器,保持局部位置不变 foreach (Item item in container.GetAllItems()) { if (item.gameObject != null) { item.gameObject.transform.SetParent(container.transform, false); } } // 禁用容器碰撞体避免移动时卡模型 container.GetComponent<Collider>().enabled = false; } public static void UpdateMovement() { if (!IsMoving) return; // 通过鼠标射线获取地面放置位置 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, 6f, LayerMask.GetMask("Ground"))) { // 微调位置避免穿模 Vector3 targetPos = hit.point + Vector3.up * 0.1f; _currentMovingContainer.transform.position = targetPos; } // 松开鼠标左键完成放置 if (Input.GetMouseButtonUp(0)) { StopMoving(); } } static void StopMoving() { if (_currentMovingContainer == null) return; // 恢复容器碰撞体 _currentMovingContainer.GetComponent<Collider>().enabled = true; // 将物品父级还原为游戏原生层级(避免存档异常) foreach (Item item in _currentMovingContainer.GetAllItems()) { if (item.gameObject != null) { item.gameObject.transform.SetParent(null); // 同步物品位置到游戏物品管理系统 item.UpdatePosition(item.gameObject.transform.position); } } // 保存容器位置到存档(Hook游戏存档逻辑实现) SaveContainerPosition(_currentMovingContainer); _currentMovingContainer = null; } // 自定义存档逻辑:Hook游戏存档写入方法,添加容器位置数据 static void SaveContainerPosition(Container container) { var saveManager = AccessTools.Field(typeof(SaveGameManager), "m_CurrentSaveData").GetValue(null); AccessTools.Field(saveManager.GetType(), $"ContainerPos_{container.GetInstanceID()}") .SetValue(saveManager, container.transform.position); } }
4. 挂载移动更新逻辑
在Melon Loader的Update回调中触发移动更新:
public class ContainerMovementMod : MelonMod { public override void OnUpdate() { base.OnUpdate(); ContainerMovementHandler.UpdateMovement(); } }
版本适配要点
- 反编译游戏Assembly:用dnSpy查看最新版游戏的
Container、PlayerInteract、Item类结构,替换代码中对应的字段/方法名(旧版失效多因类结构变更) - 避免硬编码依赖:使用
AccessTools.Field/AccessTools.Property访问私有成员,而非直接引用字段名 - 测试存档兼容性:移动后的容器位置需正确写入游戏存档,避免加载后位置重置
内容的提问来源于stack exchange,提问作者The Illusion
相关产品推荐
相关产品推荐

