
ACNH MobileSpawner 是一个专为《集合啦!动物森友会》(Animal Crossing: New Horizons)开发的多功能跨平台工具。该项目基于 Unity 引擎构建,旨在让玩家无需手动在 PC 上编辑存档,直接在游戏过程中进行实时修改。支持平台包括 Windows、Mac、Linux、Android 和 iOS。
注意:使用本工具需要 Switch 主机运行自制固件,并安装 sys-botbase 或 USB-Botbase 系统模块。
*.nhi(背包)、*.nhv(村民)和 *.nhvh(村民房屋)。192.168.0.1:6000)。// 从 UI_ACItemGrid 获取当前背包物品列表
List<Item> items = UI_ACItemGrid.LastInstanceOfItemGrid.Items;
// 设置特定背包槽位的物品
Item newItem = new Item(Item.NONE);
newItem.ItemId = 0x0A3D; // 示例物品ID
UI_ACItemGrid.LastInstanceOfItemGrid.SetItemAt(newItem, index, true);// 读取村民数据
byte[] villagerData = connection.ReadBytes(UI_Villager.CurrentVillagerAddress, Villager2.SIZE);
Villager2 villager = new Villager2(villagerData);
// 写入村民数据
Villager2 newVillager = new Villager2();
// ... 配置村民数据
connection.WriteBytes(newVillager.Data, UI_Villager.CurrentVillagerAddress);// 获取地图物品层
FieldItemManager items = ...; // 从游戏内存读取
FieldItemLayer layer = items.Layer1;
// 更新地图上的物品块
FieldItemBlock block = new FieldItemBlock(layer, x, y);
block.UpdateItem(newItem);
// 将修改写回游戏
byte[] layerData = layer.GetLayer();
connection.WriteBytes(layerData, UI_MapTerrain.CurrentMapAddress);// 冻结游戏时间
connection.WriteBytes(BitConverter.GetBytes(UI_TimeSpeed.FreezeTimeValue),
OffsetHelper.TimeAddress, RWMethod.Main);
// 修改行走速度
uint walkSpeedValue = UI_TimeSpeed.WalkSteps[2]; // 选择速度等级
connection.WriteBytes(BitConverter.GetBytes(walkSpeedValue),
OffsetHelper.WalkSpeedOffset, RWMethod.Main);以下是项目中部分关键组件的核心代码及注释:
UI_ACItemGrid.cs)using NHSE.Core;
using NHSE.Injection;
using UnityEngine;
public class UI_ACItemGrid : MonoBehaviour
{
public readonly int MAXITEMS = 40; // 背包最大容量
// 当前选中的背包槽位
public int CurrentSelected { get; private set; }
// 物品列表
[HideInInspector]
public List<Item> Items = new List<Item>();
private List<UI_ACItem> uiitems; // UI 物品组件列表
// 设置指定槽位的物品
public void SetItemAt(Item item, int index, bool updateImmediately)
{
if (index < 0 || index >= Items.Count)
return;
Items[index] = item;
uiitems[index].Assign(item);
if (updateImmediately)
WriteItemsToGame(); // 立即写入游戏
}
// 将修改后的物品列表写入游戏内存
private void WriteItemsToGame()
{
byte[] itemBytes = new byte[Items.Count * Item.SIZE];
for (int i = 0; i < Items.Count; i++)
Array.Copy(Items[i].ToBytesClass(), 0, itemBytes, i * Item.SIZE, Item.SIZE);
IRAMReadWriter connection = GetCurrentlyActiveReadWriter();
uint offset = (uint)(SysBotController.CurrentOffsetFirstPlayerUInt +
PocketInjector.shift +
((uint)OffsetHelper.PlayerSize * UI_Settings.GetPlayerIndex()));
connection.WriteBytes(itemBytes, offset);
}
}SwitchUIController.cs)using NHSE.Injection;
using System.Collections.Concurrent;
using System.Threading.Tasks;
public class SwitchUIController : MonoBehaviour
{
private ConcurrentQueue<SysBotExecution> ExecutionQueue = new ConcurrentQueue<SysBotExecution>();
// 按下按钮
public void Press(SwitchButton b, bool up)
{
ExecutionQueue.Enqueue(new SysBotExecution()
{
PType = up ? PressType.ButtonRelease : PressType.ButtonPress,
ButtonToPress = b
});
}
// 设置摇杆位置
public void SetStick(SwitchStick s, short x, short y)
{
ExecutionQueue.Enqueue(new SysBotExecution()
{
PType = PressType.Joystick,
StickToMove = s,
X = x,
Y = y
});
}
// 处理命令队列
public async Task DoSocketQueue(bool isNetwork, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
if (ExecutionQueue.TryDequeue(out var item))
{
byte[] command = Array.Empty<byte>();
switch (item.PType)
{
case PressType.ButtonPress:
command = SwitchCommand.Hold(item.ButtonToPress, isNetwork);
break;
case PressType.ButtonRelease:
command = SwitchCommand.Release(item.ButtonToPress, isNetwork);
break;
case PressType.Joystick:
command = SwitchCommand.SetStick(item.StickToMove, item.X, item.Y);
break;
}
try { Connection.CurrentConnection.SendBytes(command); } catch { }
}
await Task.Delay(1, token).ConfigureAwait(false);
}
}
}MapGraphicGenerator.cs)using NHSE.Core;
using UnityEngine;
public class MapGraphicGenerator
{
private readonly FieldItemManager ItemManager;
private readonly NHSE.Core.TerrainLayer Terrain;
public Texture2D MapBackgroundImage { get; private set; }
public MapGraphicGenerator(FieldItemManager items, NHSE.Core.TerrainLayer terrain,
ushort plazaX, ushort plazaY, Building[] buildings)
{
ItemManager = items;
Terrain = terrain;
MapBackgroundImage = new Texture2D(Terrain.MaxWidth, Terrain.MaxHeight);
// 绘制地形(河流和高度)
for (int y = 0; y < Terrain.MaxHeight; y++)
{
for (int x = 0; x < Terrain.MaxWidth; x++)
{
var pxl = Terrain.GetTileColorRGB(x, y);
MapBackgroundImage.SetPixel(x, y, new Color32(pxl.R, pxl.G, pxl.B, pxl.A));
}
}
MapBackgroundImage.Apply();
}
// 生成包含物品的地图纹理
public Texture2D GenerateItemLayerTexture(int layerIndex)
{
var layer = layerIndex == 0 ? ItemManager.Layer1 : ItemManager.Layer2;
Texture2D texture = new Texture2D(layer.MaxWidth, layer.MaxHeight);
for (int y = 0; y < layer.MaxHeight; y++)
{
for (int x = 0; x < layer.MaxWidth; x++)
{
Item item = layer.GetTile(x, y);
Color color = item.IsNone ? Color.clear : GetItemColor(item);
texture.SetPixel(x, y, color);
}
}
texture.Apply();
return texture;
}
}UI_Villager.cs)using NHSE.Core;
using System.Collections.Generic;
using UnityEngine;
public class UI_Villager : IUI_Additional
{
const int VillagersSize = Villager2.SIZE * 10; // 10个村民
private Villager2 loadedVillager;
private List<Villager2> loadedVillagerShellsList;
// 读取村民数据
public void FetchVillagerData(int villagerIndex)
{
currentlyLoadedVillagerIndex = villagerIndex;
uint address = CurrentVillagerAddress + (uint)(villagerIndex * Villager2.SIZE);
byte[] villagerData = CurrentConnection.ReadBytes(address, Villager2.SIZE);
loadedVillager = new Villager2(villagerData);
UpdateVillagerUI();
}
// 写入村民数据到游戏
public void WriteVillagerDataVillager(Villager2 villager)
{
if (currentlyLoadedVillagerIndex < 0)
return;
uint address = CurrentVillagerAddress + (uint)(currentlyLoadedVillagerIndex * Villager2.SIZE);
CurrentConnection.WriteBytes(villager.Data, address);
loadedVillager = villager;
UpdateVillagerUI();
}
// 更新村民UI显示
private void UpdateVillagerUI()
{
if (loadedVillager == null)
return;
string internalName = loadedVillager.InternalName;
VillagerName.text = GameInfo.Strings.GetVillager(internalName);
// 加载村民头像
Texture2D villagerTexture = SpriteBehaviour.PullTextureFromParser(villagerSprites, internalName);
MainVillagerTexture.texture = villagerTexture;
}
}Joystick.cs)using UnityEngine;
using UnityEngine.EventSystems;
public class Joystick : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
{
public float Horizontal { get { return (snapX) ? SnapFloat(input.x, AxisOptions.Horizontal) : input.x; } }
public float Vertical { get { return (snapY) ? SnapFloat(input.y, AxisOptions.Vertical) : input.y; } }
public Vector2 Direction { get { return new Vector2(Horizontal, Vertical); } }
public delegate void UpdateStick(Vector2 direction);
public UpdateStick OnUpdateStick;
public delegate void InteractStick(bool interacting);
public InteractStick OnInteractStick;
// 处理指针按下事件
public override void OnPointerDown(PointerEventData eventData)
{
input = Vector2.zero;
handle.anchoredPosition = Vector2.zero;
OnInteractStick?.Invoke(true);
}
// 处理拖拽事件
public override void OnDrag(PointerEventData eventData)
{
cam = null;
if (canvas.renderMode == RenderMode.ScreenSpaceCamera)
cam = canvas.worldCamera;
Vector2 position = RectTransformUtility.WorldToScreenPoint(cam, background.position);
Vector2 radius = background.sizeDelta / 2;
input = (eventData.position - position) / (radius * canvas.scaleFactor);
FormatInput();
HandleInput(input.magnitude, input.normalized, radius, cam);
OnUpdateStick?.Invoke(Direction);
}
// 处理指针释放事件
public override void OnPointerUp(PointerEventData eventData)
{
input = Vector2.zero;
handle.anchoredPosition = Vector2.zero;
OnUpdateStick?.Invoke(Direction);
OnInteractStick?.Invoke(false);
}
}免责声明:请负责任地使用本工具,不要利用内部物品破坏其他玩家的游戏体验,也不要在本地或在线游戏中使用这些物品进行交易。
/l+B+Emd3f6klEWwza4S0rESvcdto6ak5WE7ry7XXzc=
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。