[+] ScreenPositionAdjust supports virtual touch & ExteraMouseInput supports 2P

This commit is contained in:
Clansty
2025-08-06 14:48:12 +08:00
parent 983f887be4
commit 977c5d3af5
4 changed files with 146 additions and 82 deletions

View File

@@ -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; }

View File

@@ -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<float>("GameSystem.SinglePlayer.Radius", out var Radius))
{
dst.SetValue("GameSystem.ExteraMouseInput.Radius", Radius);
dst.Remove("GameSystem.SinglePlayer.Radius");
}
if (src.TryGetValue<bool>("GameSystem.SinglePlayer.DisplayArea", out var DisplayArea))
{
dst.SetValue("GameSystem.ExteraMouseInput.DisplayArea", DisplayArea);
dst.Remove("GameSystem.SinglePlayer.DisplayArea");
}
return dst;
}
}

View File

@@ -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<CustomCircleGraphic> circleGraphics = new List<CustomCircleGraphic>();
[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<Canvas>();
var player = __instance.transform.parent.parent.name.Equals("LeftMonitor") ? 0 : 1;
UnityEngine.Object.Destroy(__instance.transform.parent.GetComponent<GraphicRaycaster>());
MeshButtonRaycaster radiusRaycaster = __instance.transform.parent.gameObject.AddComponent<MeshButtonRaycaster>();
rectTransform = __instance.GetComponent<RectTransform>();
__instance.transform.parent.gameObject.AddComponent<MeshButtonRaycaster>();
rectTransform[player] = __instance.GetComponent<RectTransform>();
if (displayArea)
if (displayArea && player == 0)
{
__instance.StartCoroutine(UpdateInputDisplay());
circleGraphics = new List<CustomCircleGraphic>(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
/// <summary>
/// 检查点是否在多边形的顶点内
/// </summary>
@@ -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
}
}

View File

@@ -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);
/// <summary>
/// 避免一帧延迟
@@ -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<MethodBase> 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<MethodBase> 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>();
canvas.GetComponent<RectTransform>().sizeDelta = new Vector2(2160, 1920);
canvas.renderMode = RenderMode.WorldSpace;
root.AddComponent<CanvasScaler>();
root.AddComponent<GraphicRaycaster>();
root.AddComponent<DynamicRenderTextureResizer>();
root.AddComponent<AdjustController>();
Camera.main.gameObject.AddComponent<CameraUpdater>();
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>();
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<UnityEngine.UI.RawImage>();
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>();
canvas.GetComponent<RectTransform>().sizeDelta = new Vector2(2160, 1920);
canvas.renderMode = RenderMode.WorldSpace;
root.AddComponent<CanvasScaler>();
root.AddComponent<GraphicRaycaster>();
root.AddComponent<DynamicRenderTextureResizer>();
root.AddComponent<AdjustController>();
Camera.main.gameObject.AddComponent<CameraUpdater>();
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>();
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<UnityEngine.UI.RawImage>();
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;
}
}