gameplay手游触屏支持,一些小优化和修复
This commit is contained in:
@@ -1,24 +0,0 @@
|
|||||||
<linker>
|
|
||||||
<assembly fullname="Unity.Addressables, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" preserve="all">
|
|
||||||
<type fullname="UnityEngine.AddressableAssets.Addressables" preserve="all" />
|
|
||||||
</assembly>
|
|
||||||
<assembly fullname="Unity.Localization, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
|
|
||||||
<type fullname="UnityEngine.Localization.Locale" preserve="all" />
|
|
||||||
<type fullname="UnityEngine.Localization.Tables.SharedTableData" preserve="all" />
|
|
||||||
<type fullname="UnityEngine.Localization.Tables.StringTable" preserve="all" />
|
|
||||||
<type fullname="UnityEngine.Localization.LocaleIdentifier" preserve="nothing" serialized="true" />
|
|
||||||
<type fullname="UnityEngine.Localization.Metadata.MetadataCollection" preserve="nothing" serialized="true" />
|
|
||||||
<type fullname="UnityEngine.Localization.Tables.TableEntryData" preserve="nothing" serialized="true" />
|
|
||||||
<type fullname="UnityEngine.Localization.Tables.DistributedUIDGenerator" preserve="nothing" serialized="true" />
|
|
||||||
<type fullname="UnityEngine.Localization.Tables.SharedTableData/SharedTableEntry" preserve="nothing" serialized="true" />
|
|
||||||
</assembly>
|
|
||||||
<assembly fullname="Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" preserve="all">
|
|
||||||
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider" preserve="all" />
|
|
||||||
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider" preserve="all" />
|
|
||||||
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.InstanceProvider" preserve="all" />
|
|
||||||
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.SceneProvider" preserve="all" />
|
|
||||||
</assembly>
|
|
||||||
<assembly fullname="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
|
|
||||||
<type fullname="UnityEngine.Object" preserve="all" />
|
|
||||||
</assembly>
|
|
||||||
</linker>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 0bd49428350698946b692e1dd6c3eb5e
|
|
||||||
TextScriptImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using DG.Tweening;
|
using DG.Tweening;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -86,12 +87,14 @@ public class UI_Idols : MonoBehaviour
|
|||||||
private bool suppressToggleCallbacks;
|
private bool suppressToggleCallbacks;
|
||||||
private int pendingGrowthAnimationHeroId = -1;
|
private int pendingGrowthAnimationHeroId = -1;
|
||||||
private Tween idolExpBarTween;
|
private Tween idolExpBarTween;
|
||||||
|
private Coroutine startupSkillValidationRoutine;
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
BindQuitButton();
|
BindQuitButton();
|
||||||
InitializeSectionToggles();
|
InitializeSectionToggles();
|
||||||
RebuildCards();
|
RebuildCards();
|
||||||
|
BeginStartupSkillValidation();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
@@ -123,6 +126,11 @@ public class UI_Idols : MonoBehaviour
|
|||||||
idolExpBarTween.Kill();
|
idolExpBarTween.Kill();
|
||||||
idolExpBarTween = null;
|
idolExpBarTween = null;
|
||||||
}
|
}
|
||||||
|
if (startupSkillValidationRoutine != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(startupSkillValidationRoutine);
|
||||||
|
startupSkillValidationRoutine = null;
|
||||||
|
}
|
||||||
UnbindSectionToggles();
|
UnbindSectionToggles();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +160,37 @@ public class UI_Idols : MonoBehaviour
|
|||||||
gameObject.SetActive(false);
|
gameObject.SetActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BeginStartupSkillValidation()
|
||||||
|
{
|
||||||
|
if (!isActiveAndEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startupSkillValidationRoutine != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(startupSkillValidationRoutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
startupSkillValidationRoutine = StartCoroutine(ValidateSkillsOnStartupRoutine());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator ValidateSkillsOnStartupRoutine()
|
||||||
|
{
|
||||||
|
yield return null;
|
||||||
|
|
||||||
|
bool anyChanged = false;
|
||||||
|
yield return AllyHeroSkillSelectionValidator.ValidateAllHeroesAsync(2, changed => anyChanged = changed);
|
||||||
|
startupSkillValidationRoutine = null;
|
||||||
|
|
||||||
|
if (!anyChanged)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
RebuildCards(false);
|
||||||
|
}
|
||||||
|
|
||||||
private void InitializeSectionToggles()
|
private void InitializeSectionToggles()
|
||||||
{
|
{
|
||||||
BindSectionToggles();
|
BindSectionToggles();
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public static class AllyHeroSkillSelectionValidator
|
||||||
|
{
|
||||||
|
public static IEnumerator ValidateAllHeroesAsync(int heroesPerFrame = 2, Action<bool> onCompleted = null)
|
||||||
|
{
|
||||||
|
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||||
|
bool anyChanged = false;
|
||||||
|
int processedThisFrame = 0;
|
||||||
|
int batchSize = Mathf.Max(1, heroesPerFrame);
|
||||||
|
|
||||||
|
for (int i = 0; i < heroes.Length; i++)
|
||||||
|
{
|
||||||
|
AllyHero_SO hero = heroes[i];
|
||||||
|
if (hero != null && hero.ValidateEquippedSkillSelections())
|
||||||
|
{
|
||||||
|
anyChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
processedThisFrame++;
|
||||||
|
if (processedThisFrame >= batchSize)
|
||||||
|
{
|
||||||
|
processedThisFrame = 0;
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCompleted?.Invoke(anyChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: aebb9d085d6f9c14fa7ff27d1b4e6e03
|
||||||
@@ -189,6 +189,77 @@ public class AllyHero_SO : ScriptableObject
|
|||||||
return EquipmentSkillGroupLibrary.ResolveSkillGroup(groupID);
|
return EquipmentSkillGroupLibrary.ResolveSkillGroup(groupID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ValidateEquippedSkillSelections(bool persistIfChanged = true)
|
||||||
|
{
|
||||||
|
LoadEquippedSkillsFromLocal();
|
||||||
|
LoadEquippedEquipmentFromLocal();
|
||||||
|
|
||||||
|
if (equippedSkillGroupIDs == null)
|
||||||
|
{
|
||||||
|
equippedSkillGroupIDs = Array.Empty<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
int currentLevelId = GetEffectiveLevelForCurrentEXP()?.levelID ?? 0;
|
||||||
|
int maxSkillSlots = Mathf.Max(0, GetEffectiveSkillSlotLimit());
|
||||||
|
List<int> validatedGroupIds = new List<int>(Mathf.Min(equippedSkillGroupIDs.Length, maxSkillSlots));
|
||||||
|
HashSet<int> seenGroupIds = new HashSet<int>();
|
||||||
|
|
||||||
|
for (int i = 0; i < equippedSkillGroupIDs.Length; i++)
|
||||||
|
{
|
||||||
|
int groupId = equippedSkillGroupIDs[i];
|
||||||
|
if (groupId <= 0 || !seenGroupIds.Add(groupId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validatedGroupIds.Count >= maxSkillSlots)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkillGroup group = GetOwnedSkillGroupByID(groupId);
|
||||||
|
if (group == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int requiredLevelId = Mathf.Clamp(group.thisSkill_levelLimit, 1, 4);
|
||||||
|
if (currentLevelId < requiredLevelId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
validatedGroupIds.Add(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] validatedArray = validatedGroupIds.ToArray();
|
||||||
|
bool changed = !AreSkillGroupIdArraysEqual(equippedSkillGroupIDs, validatedArray);
|
||||||
|
if (!changed)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
equippedSkillGroupIDs = validatedArray;
|
||||||
|
|
||||||
|
if (persistIfChanged)
|
||||||
|
{
|
||||||
|
SaveEquippedSkillsToLocal();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
if (!Application.isPlaying)
|
||||||
|
{
|
||||||
|
EditorUtility.SetDirty(this);
|
||||||
|
if (persistIfChanged)
|
||||||
|
{
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public int[] GetEffectiveEquippedSkillGroupIDs()
|
public int[] GetEffectiveEquippedSkillGroupIDs()
|
||||||
{
|
{
|
||||||
var result = new List<int>();
|
var result = new List<int>();
|
||||||
@@ -365,6 +436,45 @@ public class AllyHero_SO : ScriptableObject
|
|||||||
result.Add(groupId);
|
result.Add(groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool AreSkillGroupIdArraysEqual(int[] left, int[] right)
|
||||||
|
{
|
||||||
|
int leftLength = left != null ? left.Length : 0;
|
||||||
|
int rightLength = right != null ? right.Length : 0;
|
||||||
|
if (leftLength != rightLength)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < leftLength; i++)
|
||||||
|
{
|
||||||
|
if (left[i] != right[i])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SkillGroup GetOwnedSkillGroupByID(int groupId)
|
||||||
|
{
|
||||||
|
if (skillGroups == null || skillGroups.Length == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < skillGroups.Length; i++)
|
||||||
|
{
|
||||||
|
SkillGroup group = skillGroups[i];
|
||||||
|
if (group != null && group.skillGroupID == groupId)
|
||||||
|
{
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public int GetUnlockedLevelIndex()
|
public int GetUnlockedLevelIndex()
|
||||||
{
|
{
|
||||||
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
|
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ public class newTeamSelector : MonoBehaviour
|
|||||||
private static AllyHero_SO[] cachedHeroes;
|
private static AllyHero_SO[] cachedHeroes;
|
||||||
private static Dictionary<int, AllyHero_SO> cachedHeroesById;
|
private static Dictionary<int, AllyHero_SO> cachedHeroesById;
|
||||||
private Coroutine refreshSkillsRoutine;
|
private Coroutine refreshSkillsRoutine;
|
||||||
|
private Coroutine startupSkillValidationRoutine;
|
||||||
|
|
||||||
void Awake()
|
void Awake()
|
||||||
{
|
{
|
||||||
@@ -65,14 +66,24 @@ public class newTeamSelector : MonoBehaviour
|
|||||||
{
|
{
|
||||||
RefreshTeamShareCodeDisplay();
|
RefreshTeamShareCodeDisplay();
|
||||||
if (instantiateOnStart)
|
if (instantiateOnStart)
|
||||||
|
{
|
||||||
CreateSlots();
|
CreateSlots();
|
||||||
StartCoroutine(LoadSelectedHeroesNextFrame());
|
}
|
||||||
|
|
||||||
|
if (startupSkillValidationRoutine != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(startupSkillValidationRoutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
startupSkillValidationRoutine = StartCoroutine(StartupLoadRoutine());
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator LoadSelectedHeroesNextFrame()
|
private IEnumerator StartupLoadRoutine()
|
||||||
{
|
{
|
||||||
yield return null;
|
yield return null;
|
||||||
|
yield return AllyHeroSkillSelectionValidator.ValidateAllHeroesAsync();
|
||||||
LoadSelectedHeroes();
|
LoadSelectedHeroes();
|
||||||
|
startupSkillValidationRoutine = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateSlots()
|
public void CreateSlots()
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ public class PauseManager : MonoBehaviour
|
|||||||
|
|
||||||
[Header("Buttons")]
|
[Header("Buttons")]
|
||||||
[SerializeField] private string pauseButtonScenePath = "artworks/NEW UI/zhuangshi (3)/Pause";
|
[SerializeField] private string pauseButtonScenePath = "artworks/NEW UI/zhuangshi (3)/Pause";
|
||||||
|
[Tooltip("手游暂停按钮:把场景里的暂停 Button 拖到这里,点击效果等同 ESC。\n" +
|
||||||
|
"留空则按 pauseButtonScenePath 或名为 \"Pause\" 的按钮自动查找。")]
|
||||||
[SerializeField] private Button pauseTriggerButton;
|
[SerializeField] private Button pauseTriggerButton;
|
||||||
[SerializeField] private Button continueButton;
|
[SerializeField] private Button continueButton;
|
||||||
[SerializeField] private Button replayButton;
|
[SerializeField] private Button replayButton;
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 手游触屏输入分发器:每帧遍历所有触摸点(及编辑器鼠标),用 Physics2D.OverlapPoint
|
||||||
|
/// 命中带 TrackTouchZone 的透明 Collider2D,把按下/抬起转发给 InputManager。
|
||||||
|
///
|
||||||
|
/// 为什么用这种方式而不是 UI 的 IPointerDownHandler:
|
||||||
|
/// - 轨道会平移+旋转晃动,点击区必须跟随晃动的 Collider(OverlapPoint 支持旋转过的碰撞体)。
|
||||||
|
/// - 需要多点触控:每根手指独立按住各自的轨道(靠 fingerId 配对按下/抬起)。
|
||||||
|
///
|
||||||
|
/// 判定逻辑完全不变:这里只调用现成的 PressTrack/ReleaseTrack,
|
||||||
|
/// 判定时间戳仍由 Note.HandlePress 读取 GameplayClock.NowSongTime(dspTime)。
|
||||||
|
///
|
||||||
|
/// 用法:场景里放一个空物体挂本脚本,把主相机拖到 gameplayCamera(留空则自动取 Camera.main)。
|
||||||
|
/// 5 块透明轨道点击区各挂 TrackTouchZone 并设置 trackIndex。
|
||||||
|
/// </summary>
|
||||||
|
public class TrackTouchInput : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Tooltip("渲染 gameplay 的相机。留空则自动使用 Camera.main。")]
|
||||||
|
[SerializeField] private Camera gameplayCamera;
|
||||||
|
|
||||||
|
[Tooltip("是否在编辑器/PC 上用鼠标模拟单点触摸(方便调试)。")]
|
||||||
|
[SerializeField] private bool enableMouseFallback = true;
|
||||||
|
|
||||||
|
[Tooltip("命中检测使用的层。默认 Everything;如需只检测点击区层可在此限制。")]
|
||||||
|
[SerializeField] private LayerMask hitLayers = ~0;
|
||||||
|
|
||||||
|
[Tooltip("透视相机必填:轨道点击区所在平面的世界 Z 坐标(轨道 Collider 的 Z)。\n" +
|
||||||
|
"透视相机下屏幕点转世界点依赖深度,用它把射线交到轨道平面,相机怎么晃/推拉都精确。\n" +
|
||||||
|
"正交相机可忽略此项。")]
|
||||||
|
[SerializeField] private float trackPlaneWorldZ = 0f;
|
||||||
|
|
||||||
|
[Tooltip("相机是否为正交投影。正交相机忽略深度,命中转换更简单。\n" +
|
||||||
|
"留空自动按相机 orthographic 判断;一般不用手动改。")]
|
||||||
|
[SerializeField] private bool forceOrthographicMode = false;
|
||||||
|
|
||||||
|
// fingerId -> 当前按住的轨道索引。用于在抬起/取消时精确释放对应轨道。
|
||||||
|
private readonly Dictionary<int, int> _activeTouches = new Dictionary<int, int>();
|
||||||
|
// 鼠标模拟用的"手指 id",取一个不会和真实 fingerId 冲突的值。
|
||||||
|
private const int MouseFingerId = -100;
|
||||||
|
private int _mouseHeldTrack = -1;
|
||||||
|
|
||||||
|
private Camera Cam
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (gameplayCamera == null) gameplayCamera = Camera.main;
|
||||||
|
return gameplayCamera;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
var im = InputManager.Instance;
|
||||||
|
if (im == null) return;
|
||||||
|
|
||||||
|
Camera cam = Cam;
|
||||||
|
if (cam == null) return;
|
||||||
|
|
||||||
|
ProcessTouches(im, cam);
|
||||||
|
|
||||||
|
if (enableMouseFallback && Input.touchCount == 0)
|
||||||
|
{
|
||||||
|
ProcessMouse(im, cam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessTouches(InputManager im, Camera cam)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Input.touchCount; i++)
|
||||||
|
{
|
||||||
|
Touch t = Input.GetTouch(i);
|
||||||
|
|
||||||
|
switch (t.phase)
|
||||||
|
{
|
||||||
|
case TouchPhase.Began:
|
||||||
|
{
|
||||||
|
int track = ResolveTrack(cam, t.position);
|
||||||
|
if (track >= 0)
|
||||||
|
{
|
||||||
|
_activeTouches[t.fingerId] = track;
|
||||||
|
im.PressTrack(track);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TouchPhase.Ended:
|
||||||
|
case TouchPhase.Canceled:
|
||||||
|
{
|
||||||
|
if (_activeTouches.TryGetValue(t.fingerId, out int track))
|
||||||
|
{
|
||||||
|
_activeTouches.Remove(t.fingerId);
|
||||||
|
im.ReleaseTrack(track);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Moved / Stationary:手指在轨道内保持按住即可,不重新判定归属,
|
||||||
|
// 避免手指轻微滑动跨到相邻轨道时反复 Press/Release 造成误判。
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessMouse(InputManager im, Camera cam)
|
||||||
|
{
|
||||||
|
if (Input.GetMouseButtonDown(0))
|
||||||
|
{
|
||||||
|
int track = ResolveTrack(cam, Input.mousePosition);
|
||||||
|
if (track >= 0)
|
||||||
|
{
|
||||||
|
_mouseHeldTrack = track;
|
||||||
|
im.PressTrack(track);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Input.GetMouseButtonUp(0))
|
||||||
|
{
|
||||||
|
if (_mouseHeldTrack >= 0)
|
||||||
|
{
|
||||||
|
im.ReleaseTrack(_mouseHeldTrack);
|
||||||
|
_mouseHeldTrack = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 把屏幕坐标转换为轨道平面上的世界点,用 OverlapPoint 命中轨道点击区,返回轨道索引;无命中返回 -1。
|
||||||
|
///
|
||||||
|
/// 相机移动/晃动/推拉时,每帧都用当前相机状态转换,所以命中自动跟随相机。
|
||||||
|
/// - 正交相机:ScreenToWorldPoint 忽略深度,直接取 x/y。
|
||||||
|
/// - 透视相机:从相机发射线,求与轨道平面(Z = trackPlaneWorldZ)的交点,
|
||||||
|
/// 避免因相机 Z 变化(如 cameraDash)导致命中点偏移。
|
||||||
|
/// </summary>
|
||||||
|
private int ResolveTrack(Camera cam, Vector2 screenPos)
|
||||||
|
{
|
||||||
|
Vector2 point;
|
||||||
|
|
||||||
|
if (forceOrthographicMode || cam.orthographic)
|
||||||
|
{
|
||||||
|
Vector3 world = cam.ScreenToWorldPoint(screenPos);
|
||||||
|
point = new Vector2(world.x, world.y);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 透视:射线与轨道平面(法线 +Z,过 z = trackPlaneWorldZ)求交。
|
||||||
|
Ray ray = cam.ScreenPointToRay(screenPos);
|
||||||
|
float denom = ray.direction.z;
|
||||||
|
if (Mathf.Abs(denom) < 1e-6f)
|
||||||
|
{
|
||||||
|
return -1; // 射线与平面近乎平行,无有效交点
|
||||||
|
}
|
||||||
|
float t = (trackPlaneWorldZ - ray.origin.z) / denom;
|
||||||
|
if (t < 0f)
|
||||||
|
{
|
||||||
|
return -1; // 交点在相机背后
|
||||||
|
}
|
||||||
|
Vector3 world = ray.origin + ray.direction * t;
|
||||||
|
point = new Vector2(world.x, world.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Collider2D hit = Physics2D.OverlapPoint(point, hitLayers);
|
||||||
|
if (hit == null) return -1;
|
||||||
|
|
||||||
|
TrackTouchZone zone = hit.GetComponent<TrackTouchZone>();
|
||||||
|
if (zone == null) zone = hit.GetComponentInParent<TrackTouchZone>();
|
||||||
|
return zone != null ? zone.trackIndex : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
// 组件被关闭/切场景时,释放所有还按住的轨道,避免音符卡在 held 状态。
|
||||||
|
var im = InputManager.Instance;
|
||||||
|
if (im != null)
|
||||||
|
{
|
||||||
|
foreach (var kv in _activeTouches)
|
||||||
|
{
|
||||||
|
im.ReleaseTrack(kv.Value);
|
||||||
|
}
|
||||||
|
if (_mouseHeldTrack >= 0) im.ReleaseTrack(_mouseHeldTrack);
|
||||||
|
}
|
||||||
|
_activeTouches.Clear();
|
||||||
|
_mouseHeldTrack = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 16d03d0d613217b4ebdba1b8b63bf735
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binds five pre-authored UI touch regions to the existing gameplay input path.
|
||||||
|
/// This script does not create runtime objects. You assign the region RectTransforms
|
||||||
|
/// in the inspector, and it ensures each one has an Image raycast target plus a
|
||||||
|
/// TrackTouchRegion configured with the matching track index.
|
||||||
|
/// </summary>
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public class TrackTouchRegionLayoutController : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class RegionBinding
|
||||||
|
{
|
||||||
|
[Range(0, 4)]
|
||||||
|
public int trackIndex;
|
||||||
|
|
||||||
|
[Tooltip("Pre-authored UI region to bind. Assign in inspector.")]
|
||||||
|
public RectTransform regionObject;
|
||||||
|
|
||||||
|
[Tooltip("Optional debug tint. Alpha 0 keeps the region invisible while preserving raycasts.")]
|
||||||
|
public Color debugTint = new Color(1f, 1f, 1f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Header("Behavior")]
|
||||||
|
public bool bindOnStart = true;
|
||||||
|
public bool mobileOnly = true;
|
||||||
|
|
||||||
|
[Header("Prebound Regions")]
|
||||||
|
public RegionBinding[] regions = new RegionBinding[5];
|
||||||
|
|
||||||
|
private void Reset()
|
||||||
|
{
|
||||||
|
EnsureDefaultBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
EnsureDefaultBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
if (!bindOnStart)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mobileOnly && !Application.isMobilePlatform)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ContextMenu("Apply Touch Region Bindings")]
|
||||||
|
public void ApplyBindings()
|
||||||
|
{
|
||||||
|
EnsureDefaultBindings();
|
||||||
|
|
||||||
|
for (int i = 0; i < regions.Length; i++)
|
||||||
|
{
|
||||||
|
BindRegion(regions[i], i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureDefaultBindings()
|
||||||
|
{
|
||||||
|
if (regions == null || regions.Length != 5)
|
||||||
|
{
|
||||||
|
regions = new RegionBinding[5];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < regions.Length; i++)
|
||||||
|
{
|
||||||
|
if (regions[i] == null)
|
||||||
|
{
|
||||||
|
regions[i] = new RegionBinding();
|
||||||
|
}
|
||||||
|
|
||||||
|
regions[i].trackIndex = Mathf.Clamp(i, 0, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BindRegion(RegionBinding binding, int fallbackTrackIndex)
|
||||||
|
{
|
||||||
|
if (binding == null || binding.regionObject == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int trackIndex = Mathf.Clamp(binding.trackIndex, 0, 4);
|
||||||
|
if (trackIndex != fallbackTrackIndex)
|
||||||
|
{
|
||||||
|
trackIndex = Mathf.Clamp(trackIndex, 0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Image image = binding.regionObject.GetComponent<Image>();
|
||||||
|
if (image == null)
|
||||||
|
{
|
||||||
|
image = binding.regionObject.gameObject.AddComponent<Image>();
|
||||||
|
}
|
||||||
|
|
||||||
|
image.color = binding.debugTint;
|
||||||
|
image.raycastTarget = true;
|
||||||
|
|
||||||
|
TrackTouchRegion touchRegion = binding.regionObject.GetComponent<TrackTouchRegion>();
|
||||||
|
if (touchRegion == null)
|
||||||
|
{
|
||||||
|
touchRegion = binding.regionObject.gameObject.AddComponent<TrackTouchRegion>();
|
||||||
|
}
|
||||||
|
|
||||||
|
touchRegion.trackIndex = trackIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4d4eeea52b79c624bbd827fba1ce5029
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 挂在一块"透明可点击区域"物体上,标记它对应哪条轨道(0-4)。
|
||||||
|
/// 该物体需带一个 Collider2D(推荐 BoxCollider2D,可旋转)。
|
||||||
|
///
|
||||||
|
/// 两种跟随晃动的方式:
|
||||||
|
/// A) 直接把本物体作为会晃动的轨道 GameObject 的子物体 —— Unity 自动跟随平移+旋转,
|
||||||
|
/// 不需要 followTarget,留空即可。
|
||||||
|
/// B) 无法作为子物体时,把会晃动的轨道 Transform 拖到 followTarget,
|
||||||
|
/// 本脚本会在 LateUpdate 中把自身对齐到它(位置+旋转),保证点击区跟着晃。
|
||||||
|
///
|
||||||
|
/// 判定完全不经过这里:命中检测由 TrackTouchInput 遍历触摸完成,
|
||||||
|
/// 只调用 InputManager.PressTrack/ReleaseTrack,判定时机仍走 dspTime。
|
||||||
|
/// </summary>
|
||||||
|
[RequireComponent(typeof(Collider2D))]
|
||||||
|
public class TrackTouchZone : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Tooltip("本区域控制的轨道:0=红 1=绿 2=黄 3=紫 4=蓝")]
|
||||||
|
[Range(0, 4)]
|
||||||
|
public int trackIndex = 0;
|
||||||
|
|
||||||
|
[Tooltip("可选。若本物体不是轨道的子物体,把会晃动的轨道 Transform 拖到这里,区域会每帧对齐到它(位置+旋转)。")]
|
||||||
|
public Transform followTarget;
|
||||||
|
|
||||||
|
[Tooltip("followTarget 生效时,是否同步旋转(轨道会旋转时勾上)。")]
|
||||||
|
public bool followRotation = true;
|
||||||
|
|
||||||
|
private Collider2D _collider;
|
||||||
|
|
||||||
|
public Collider2D Collider
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_collider == null) _collider = GetComponent<Collider2D>();
|
||||||
|
return _collider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
_collider = GetComponent<Collider2D>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LateUpdate()
|
||||||
|
{
|
||||||
|
// 在所有晃动逻辑(Update)之后对齐,避免落后一帧。
|
||||||
|
if (followTarget == null) return;
|
||||||
|
|
||||||
|
transform.position = new Vector3(
|
||||||
|
followTarget.position.x,
|
||||||
|
followTarget.position.y,
|
||||||
|
transform.position.z);
|
||||||
|
|
||||||
|
if (followRotation)
|
||||||
|
{
|
||||||
|
transform.rotation = followTarget.rotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 51fe12fce9a3cec41bb85f2ed4d0e5e3
|
||||||
@@ -35,10 +35,22 @@ public sealed class StartupSettingsApplier : MonoBehaviour
|
|||||||
|
|
||||||
private void ApplyStartupSettings()
|
private void ApplyStartupSettings()
|
||||||
{
|
{
|
||||||
|
ApplyAndroidLandscapeOrientation();
|
||||||
graphicSettings.ApplySavedDisplaySettingsAtStartup(this);
|
graphicSettings.ApplySavedDisplaySettingsAtStartup(this);
|
||||||
ApplySavedAudioSettings();
|
ApplySavedAudioSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyAndroidLandscapeOrientation()
|
||||||
|
{
|
||||||
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||||
|
Screen.autorotateToPortrait = false;
|
||||||
|
Screen.autorotateToPortraitUpsideDown = false;
|
||||||
|
Screen.autorotateToLandscapeLeft = true;
|
||||||
|
Screen.autorotateToLandscapeRight = true;
|
||||||
|
Screen.orientation = ScreenOrientation.AutoRotation;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
private void ApplySavedAudioSettings()
|
private void ApplySavedAudioSettings()
|
||||||
{
|
{
|
||||||
audioSettingsPreloader[] preloaders = FindObjectsByType<audioSettingsPreloader>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
audioSettingsPreloader[] preloaders = FindObjectsByType<audioSettingsPreloader>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ MonoBehaviour:
|
|||||||
m_SeeAllPackageVersions: 0
|
m_SeeAllPackageVersions: 0
|
||||||
m_DismissPreviewPackagesInUse: 0
|
m_DismissPreviewPackagesInUse: 0
|
||||||
oneTimeWarningShown: 0
|
oneTimeWarningShown: 0
|
||||||
oneTimeDeprecatedPopUpShown: 0
|
oneTimeDeprecatedPopUpShown: 1
|
||||||
m_Registries:
|
m_Registries:
|
||||||
- m_Id: main
|
- m_Id: main
|
||||||
m_Name:
|
m_Name:
|
||||||
@@ -32,6 +32,6 @@ MonoBehaviour:
|
|||||||
m_RegistryInfoDraft:
|
m_RegistryInfoDraft:
|
||||||
m_Modified: 0
|
m_Modified: 0
|
||||||
m_ErrorMessage:
|
m_ErrorMessage:
|
||||||
m_UserModificationsInstanceId: -890
|
m_UserModificationsInstanceId: -874
|
||||||
m_OriginalInstanceId: -892
|
m_OriginalInstanceId: -876
|
||||||
m_LoadAssets: 0
|
m_LoadAssets: 0
|
||||||
|
|||||||
Reference in New Issue
Block a user