From 977c5d3af5604623184b136a89ea57da44ceca5c Mon Sep 17 00:00:00 2001 From: Clansty Date: Wed, 6 Aug 2025 14:48:12 +0800 Subject: [PATCH] [+] ScreenPositionAdjust supports virtual touch & ExteraMouseInput supports 2P --- .../Migration/ConfigMigrationManager.cs | 1 + .../Migration/ConfigMigration_V2_2_V2_3.cs | 30 ++++ ...xteraMouseInput.cs => ExteraMouseInput.cs} | 58 +++++--- AquaMai.Mods/Utils/ScreenPositionAdjust.cs | 139 ++++++++++-------- 4 files changed, 146 insertions(+), 82 deletions(-) create mode 100644 AquaMai.Config/Migration/ConfigMigration_V2_2_V2_3.cs rename AquaMai.Mods/GameSystem/{SinglePlayer.ExteraMouseInput.cs => ExteraMouseInput.cs} (89%) diff --git a/AquaMai.Config/Migration/ConfigMigrationManager.cs b/AquaMai.Config/Migration/ConfigMigrationManager.cs index eed93f79..3d5480a8 100644 --- a/AquaMai.Config/Migration/ConfigMigrationManager.cs +++ b/AquaMai.Config/Migration/ConfigMigrationManager.cs @@ -15,6 +15,7 @@ public class ConfigMigrationManager : IConfigMigrationManager new ConfigMigration_V1_0_V2_0(), new ConfigMigration_V2_0_V2_1(), new ConfigMigration_V2_1_V2_2(), + new ConfigMigration_V2_2_V2_3(), }.ToDictionary(m => m.FromVersion); public string LatestVersion { get; } diff --git a/AquaMai.Config/Migration/ConfigMigration_V2_2_V2_3.cs b/AquaMai.Config/Migration/ConfigMigration_V2_2_V2_3.cs new file mode 100644 index 00000000..4dfb39f3 --- /dev/null +++ b/AquaMai.Config/Migration/ConfigMigration_V2_2_V2_3.cs @@ -0,0 +1,30 @@ +using AquaMai.Config.Interfaces; +using Tomlet.Models; + +namespace AquaMai.Config.Migration; + +public class ConfigMigration_V2_2_V2_3 : IConfigMigration +{ + public string FromVersion => "2.2"; + public string ToVersion => "2.3"; + + public ConfigView Migrate(ConfigView src) + { + var dst = (ConfigView)src.Clone(); + dst.SetValue("Version", ToVersion); + + if (src.TryGetValue("GameSystem.SinglePlayer.Radius", out var Radius)) + { + dst.SetValue("GameSystem.ExteraMouseInput.Radius", Radius); + dst.Remove("GameSystem.SinglePlayer.Radius"); + } + + if (src.TryGetValue("GameSystem.SinglePlayer.DisplayArea", out var DisplayArea)) + { + dst.SetValue("GameSystem.ExteraMouseInput.DisplayArea", DisplayArea); + dst.Remove("GameSystem.SinglePlayer.DisplayArea"); + } + + return dst; + } +} \ No newline at end of file diff --git a/AquaMai.Mods/GameSystem/SinglePlayer.ExteraMouseInput.cs b/AquaMai.Mods/GameSystem/ExteraMouseInput.cs similarity index 89% rename from AquaMai.Mods/GameSystem/SinglePlayer.ExteraMouseInput.cs rename to AquaMai.Mods/GameSystem/ExteraMouseInput.cs index a1ac9ac2..de9a84ea 100644 --- a/AquaMai.Mods/GameSystem/SinglePlayer.ExteraMouseInput.cs +++ b/AquaMai.Mods/GameSystem/ExteraMouseInput.cs @@ -3,13 +3,18 @@ using HarmonyLib; using System; using System.Collections; using System.Collections.Generic; +using MelonLoader; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace AquaMai.Mods.GameSystem; -public partial class SinglePlayer +[ConfigSection( + defaultOn: true, + en: "Enable touch input with radius for a more realistic touchscreen experience.", + zh: "启用触摸输入和半径,以获得更真实的触摸屏体验")] +public partial class ExteraMouseInput { [ConfigEntry( en: "Touch area radius size. Adjust according to the size of your finger, you can test in Test Mode.", @@ -21,32 +26,26 @@ public partial class SinglePlayer zh: "显示触摸点(仅限触屏输入)")] public readonly static bool displayArea = false; - static RectTransform rectTransform; - static Canvas leftCanvas; + static RectTransform[] rectTransform = new RectTransform[2]; static List circleGraphics = new List(); [HarmonyPostfix] [HarmonyPatch(typeof(MouseTouchPanel), "Start")] public static void MouseTouchPanelStart(MouseTouchPanel __instance) { - if (!__instance.transform.parent.parent.name.Equals("LeftMonitor")) - { - return; - } - MelonLoader.MelonLogger.Msg("ExteraMouseInput: Start"); - leftCanvas = __instance.transform.parent.GetComponent(); + var player = __instance.transform.parent.parent.name.Equals("LeftMonitor") ? 0 : 1; UnityEngine.Object.Destroy(__instance.transform.parent.GetComponent()); - MeshButtonRaycaster radiusRaycaster = __instance.transform.parent.gameObject.AddComponent(); - rectTransform = __instance.GetComponent(); + __instance.transform.parent.gameObject.AddComponent(); + rectTransform[player] = __instance.GetComponent(); - if (displayArea) + if (displayArea && player == 0) { __instance.StartCoroutine(UpdateInputDisplay()); circleGraphics = new List(10); for (int i = 0; i < 10; i++) { GameObject go = new GameObject("CircleGraphic" + i); - go.transform.SetParent(rectTransform); + go.transform.SetParent(rectTransform[0]); go.transform.localPosition = Vector3.zero; go.transform.localRotation = Quaternion.identity; go.transform.localScale = Vector3.one; @@ -68,9 +67,9 @@ public partial class SinglePlayer for (int i = 0; i < customGraphic.vertex.Count; i++) { var localPos3 = new Vector3(customGraphic.vertex[i].x, customGraphic.vertex[i].y, 0f); - var rotatedPos3 = __instance.transform.localRotation * localPos3;//ApplyRot + var rotatedPos3 = __instance.transform.localRotation * localPos3; //ApplyRot var scaledPos3 = Vector3.Scale(rotatedPos3, __instance.transform.localScale); // ApplyScale - var finalPos3 = __instance.transform.position + scaledPos3;//ApplyPos + var finalPos3 = __instance.transform.position + scaledPos3; //ApplyPos ___vertexArray[i] = RectTransformUtility.WorldToScreenPoint( Camera.main, @@ -97,7 +96,7 @@ public partial class SinglePlayer Vector2 localPoint; // 将屏幕点转换为本地空间 - RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, touchPoint, Camera.main, out localPoint); + RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform[0], touchPoint, Camera.main, out localPoint); circleGraphics[i].rectTransform.anchoredPosition = localPoint; // 缩放半径 circleGraphics[i].radius = radius; @@ -117,13 +116,24 @@ public partial class SinglePlayer [HarmonyPatch(typeof(MeshButton), "IsPointInPolygon", new Type[] { typeof(Vector2[]), typeof(Vector2) })] public static bool IsPointInPolygon(MeshButton __instance, Vector2[] polygon, Vector2 point, ref bool __result) { + var player = __instance.transform.parent.parent.parent.name.Equals("LeftMonitor") ? 0 : 1; + RectTransformUtility.ScreenPointToLocalPointInRectangle( - rectTransform, + rectTransform[player], point, Camera.main, out var localInputPoint ); + // 防止触发另外半边的 + if (localInputPoint.x * 2 is > 1080 or < -1080) + { + __result = false; + return false; + } + + MelonLogger.Msg($"IsPointInPolygon, {localInputPoint}"); + bool isInsidePolygon = false; //检查是否在多边形顶点内 isInsidePolygon = IsVertDistance(polygon, localInputPoint, radius); @@ -154,6 +164,7 @@ public partial class SinglePlayer } #region 计算 + /// /// 检查点是否在多边形的顶点内 /// @@ -245,11 +256,14 @@ public partial class SinglePlayer Vector2 projection = segmentStart + t * segment; return Vector2.Distance(point, projection); } + #endregion + #region ⚪ + public class CustomCircleGraphic : Graphic { - public float radius = 50f; // 圆的半径 + public float radius = 50f; // 圆的半径 public int segmentCount = 64; // 圆的细分段数 protected override void OnPopulateMesh(VertexHelper vh) { @@ -279,8 +293,8 @@ public partial class SinglePlayer if (i > 0) { // 形成三角形的索引 - indices.Add(0); // 圆心 - indices.Add(i); // 当前点 + indices.Add(0); // 圆心 + indices.Add(i); // 当前点 indices.Add(i - 1); // 上一个点 } } @@ -290,7 +304,6 @@ public partial class SinglePlayer } public class MeshButtonRaycaster : BaseRaycaster { - public override Camera eventCamera => Camera.main; MeshButton[] meshButtons = new MeshButton[0]; @@ -313,5 +326,6 @@ public partial class SinglePlayer } } } + #endregion -} +} \ No newline at end of file diff --git a/AquaMai.Mods/Utils/ScreenPositionAdjust.cs b/AquaMai.Mods/Utils/ScreenPositionAdjust.cs index ab0d71f2..6b5e652d 100644 --- a/AquaMai.Mods/Utils/ScreenPositionAdjust.cs +++ b/AquaMai.Mods/Utils/ScreenPositionAdjust.cs @@ -30,6 +30,9 @@ public class ScreenPositionAdjust private static Transform[] images = new Transform[4]; private static Camera[] cameras = new Camera[4]; private static RenderTexture[] renderTextures = new RenderTexture[4]; + private static Transform[] mouseTouchPanels = new Transform[2]; + + private static readonly Vector3 mouseTouchPanelsDelta = new Vector3(0, (1920 - 1080) / 2f, 0); /// /// 避免一帧延迟 @@ -127,6 +130,8 @@ public class ScreenPositionAdjust PlayerPrefs.SetFloat($"AquaMaiScreenPositionAdjust-y:{i}", offsetY[i]); } PlayerPrefs.Save(); + mouseTouchPanels[0].position = images[0].position + mouseTouchPanelsDelta; + mouseTouchPanels[1].position = images[2].position + mouseTouchPanelsDelta; return; } } @@ -162,69 +167,83 @@ public class ScreenPositionAdjust } } - public static IEnumerable TargetMethods() + [HarmonyPatch] + public class Init { - var lateInitialize = AccessTools.Method(typeof(Main.GameMain), "LateInitialize", [typeof(MonoBehaviour), typeof(Transform), typeof(Transform)]); - if (lateInitialize is not null) return [lateInitialize]; - return [AccessTools.Method(typeof(Main.GameMain), "Initialize", [typeof(MonoBehaviour), typeof(Transform), typeof(Transform)])]; + public static IEnumerable TargetMethods() + { + return SinglePlayer.WhateverInitialize.TargetMethods(); + } + + public static void Prefix(Transform left, Transform right) + { + root = new GameObject("[AquaMai] ScreenPositionAdjust Display", [typeof(Canvas)]); + root.transform.position = new Vector3(11451, 19198, 0); + var canvas = root.GetComponent(); + canvas.GetComponent().sizeDelta = new Vector2(2160, 1920); + canvas.renderMode = RenderMode.WorldSpace; + root.AddComponent(); + root.AddComponent(); + root.AddComponent(); + root.AddComponent(); + Camera.main.gameObject.AddComponent(); + Camera.main.transform.position = new Vector3(ConfigLoader.Config.GetSectionState(typeof(SinglePlayer)).Enabled ? 11451 - 540 : 11451, 19198, -800); + + var compactDelta = 0; + if (compactMode) + { + Camera.main.orthographicSize = 540 + 225; + // 960 - 540 - 225 = 195 + compactDelta = 195; + } + + // init camera + for (int i = 0; i < 4; i++) + { + var player = i < 2 ? "1P" : "2P"; + var sub = i % 2 == 0 ? "Main" : "Sub"; + var texture = new RenderTexture((int)(1080 * GetSizeFactor()), (int)((sub == "Main" ? 1080 : 450) * GetSizeFactor()), 24, RenderTextureFormat.RGB111110Float) + { + useMipMap = false, + autoGenerateMips = false, + depth = 0, + antiAliasing = 1, + }; + + var camera = new GameObject($"[AquaMai] ScreenPositionAdjust Camera {sub} {player}").AddComponent(); + camera.transform.parent = player == "1P" ? left : right; + camera.enabled = false; + camera.targetTexture = texture; + camera.cullingMask = Camera.main.cullingMask; + camera.backgroundColor = Color.black; + camera.orthographic = true; + camera.orthographicSize = sub == "Main" ? 540 : 225; + camera.transform.localPosition = new Vector3(0, sub == "Main" ? -420 : 735, -800); + cameras[i] = camera; + + var image = new GameObject($"[AquaMai] ScreenPositionAdjust Image {sub} {player}").AddComponent(); + image.transform.parent = root.transform; + image.texture = texture; + image.rectTransform.sizeDelta = new Vector2(1080, sub == "Main" ? 1080 : 450); + image.transform.localPosition = new Vector3(player == "1P" ? -540 : 540, sub == "Main" ? -420 + compactDelta : 735 - compactDelta, 0); + images[i] = image.transform; + + offsetX[i] = PlayerPrefs.GetFloat($"AquaMaiScreenPositionAdjust-x:{i}", 0); + offsetY[i] = PlayerPrefs.GetFloat($"AquaMaiScreenPositionAdjust-y:{i}", 0); + images[i].localPosition += new Vector3(offsetX[i], offsetY[i], 0); + } + + mouseTouchPanels[0] = left.Find("MouseTouchPanel"); + mouseTouchPanels[1] = right.Find("MouseTouchPanel"); + mouseTouchPanels[0].position = images[0].position + mouseTouchPanelsDelta; + mouseTouchPanels[1].position = images[2].position + mouseTouchPanelsDelta; + } } - public static void Prefix(Transform left, Transform right) + [HarmonyPostfix] + [HarmonyPatch(typeof(MouseTouchPanel), "Start")] + public static void FixTouchPanelPlayer(ref int ___PlayerID, MouseTouchPanel __instance) { - root = new GameObject("[AquaMai] ScreenPositionAdjust Display", [typeof(Canvas)]); - root.transform.position = new Vector3(11451, 19198, 0); - var canvas = root.GetComponent(); - canvas.GetComponent().sizeDelta = new Vector2(2160, 1920); - canvas.renderMode = RenderMode.WorldSpace; - root.AddComponent(); - root.AddComponent(); - root.AddComponent(); - root.AddComponent(); - Camera.main.gameObject.AddComponent(); - Camera.main.transform.position = new Vector3(ConfigLoader.Config.GetSectionState(typeof(SinglePlayer)).Enabled ? 11451 - 540 : 11451, 19198, -800); - - var compactDelta = 0; - if (compactMode) - { - Camera.main.orthographicSize = 540 + 225; - // 960 - 540 - 225 = 195 - compactDelta = 195; - } - - // init camera - for (int i = 0; i < 4; i++) - { - var player = i < 2 ? "1P" : "2P"; - var sub = i % 2 == 0 ? "Main" : "Sub"; - var texture = new RenderTexture((int)(1080 * GetSizeFactor()), (int)((sub == "Main" ? 1080 : 450) * GetSizeFactor()), 24, RenderTextureFormat.RGB111110Float) - { - useMipMap = false, - autoGenerateMips = false, - depth = 0, - antiAliasing = 1, - }; - - var camera = new GameObject($"[AquaMai] ScreenPositionAdjust Camera {sub} {player}").AddComponent(); - camera.transform.parent = player == "1P" ? left : right; - camera.enabled = false; - camera.targetTexture = texture; - camera.cullingMask = Camera.main.cullingMask; - camera.backgroundColor = Color.black; - camera.orthographic = true; - camera.orthographicSize = sub == "Main" ? 540 : 225; - camera.transform.localPosition = new Vector3(0, sub == "Main" ? -420 : 735, -800); - cameras[i] = camera; - - var image = new GameObject($"[AquaMai] ScreenPositionAdjust Image {sub} {player}").AddComponent(); - image.transform.parent = root.transform; - image.texture = texture; - image.rectTransform.sizeDelta = new Vector2(1080, sub == "Main" ? 1080 : 450); - image.transform.localPosition = new Vector3(player == "1P" ? -540 : 540, sub == "Main" ? -420 + compactDelta : 735 - compactDelta, 0); - images[i] = image.transform; - - offsetX[i] = PlayerPrefs.GetFloat($"AquaMaiScreenPositionAdjust-x:{i}", 0); - offsetY[i] = PlayerPrefs.GetFloat($"AquaMaiScreenPositionAdjust-y:{i}", 0); - images[i].localPosition += new Vector3(offsetX[i], offsetY[i], 0); - } + ___PlayerID = __instance.transform.parent.parent.name.Equals("LeftMonitor") ? 0 : 1; } } \ No newline at end of file