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.Collections;
|
||||
using System.Collections.Generic;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
@@ -86,12 +87,14 @@ public class UI_Idols : MonoBehaviour
|
||||
private bool suppressToggleCallbacks;
|
||||
private int pendingGrowthAnimationHeroId = -1;
|
||||
private Tween idolExpBarTween;
|
||||
private Coroutine startupSkillValidationRoutine;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
BindQuitButton();
|
||||
InitializeSectionToggles();
|
||||
RebuildCards();
|
||||
BeginStartupSkillValidation();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
@@ -123,6 +126,11 @@ public class UI_Idols : MonoBehaviour
|
||||
idolExpBarTween.Kill();
|
||||
idolExpBarTween = null;
|
||||
}
|
||||
if (startupSkillValidationRoutine != null)
|
||||
{
|
||||
StopCoroutine(startupSkillValidationRoutine);
|
||||
startupSkillValidationRoutine = null;
|
||||
}
|
||||
UnbindSectionToggles();
|
||||
}
|
||||
|
||||
@@ -152,6 +160,37 @@ public class UI_Idols : MonoBehaviour
|
||||
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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
var result = new List<int>();
|
||||
@@ -365,6 +436,45 @@ public class AllyHero_SO : ScriptableObject
|
||||
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()
|
||||
{
|
||||
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
|
||||
|
||||
@@ -47,6 +47,7 @@ public class newTeamSelector : MonoBehaviour
|
||||
private static AllyHero_SO[] cachedHeroes;
|
||||
private static Dictionary<int, AllyHero_SO> cachedHeroesById;
|
||||
private Coroutine refreshSkillsRoutine;
|
||||
private Coroutine startupSkillValidationRoutine;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
@@ -65,14 +66,24 @@ public class newTeamSelector : MonoBehaviour
|
||||
{
|
||||
RefreshTeamShareCodeDisplay();
|
||||
if (instantiateOnStart)
|
||||
{
|
||||
CreateSlots();
|
||||
StartCoroutine(LoadSelectedHeroesNextFrame());
|
||||
}
|
||||
|
||||
if (startupSkillValidationRoutine != null)
|
||||
{
|
||||
StopCoroutine(startupSkillValidationRoutine);
|
||||
}
|
||||
|
||||
startupSkillValidationRoutine = StartCoroutine(StartupLoadRoutine());
|
||||
}
|
||||
|
||||
private IEnumerator LoadSelectedHeroesNextFrame()
|
||||
private IEnumerator StartupLoadRoutine()
|
||||
{
|
||||
yield return null;
|
||||
yield return AllyHeroSkillSelectionValidator.ValidateAllHeroesAsync();
|
||||
LoadSelectedHeroes();
|
||||
startupSkillValidationRoutine = null;
|
||||
}
|
||||
|
||||
public void CreateSlots()
|
||||
|
||||
@@ -33,6 +33,8 @@ public class PauseManager : MonoBehaviour
|
||||
|
||||
[Header("Buttons")]
|
||||
[SerializeField] private string pauseButtonScenePath = "artworks/NEW UI/zhuangshi (3)/Pause";
|
||||
[Tooltip("手游暂停按钮:把场景里的暂停 Button 拖到这里,点击效果等同 ESC。\n" +
|
||||
"留空则按 pauseButtonScenePath 或名为 \"Pause\" 的按钮自动查找。")]
|
||||
[SerializeField] private Button pauseTriggerButton;
|
||||
[SerializeField] private Button continueButton;
|
||||
[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()
|
||||
{
|
||||
ApplyAndroidLandscapeOrientation();
|
||||
graphicSettings.ApplySavedDisplaySettingsAtStartup(this);
|
||||
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()
|
||||
{
|
||||
audioSettingsPreloader[] preloaders = FindObjectsByType<audioSettingsPreloader>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
|
||||
@@ -18,7 +18,7 @@ MonoBehaviour:
|
||||
m_SeeAllPackageVersions: 0
|
||||
m_DismissPreviewPackagesInUse: 0
|
||||
oneTimeWarningShown: 0
|
||||
oneTimeDeprecatedPopUpShown: 0
|
||||
oneTimeDeprecatedPopUpShown: 1
|
||||
m_Registries:
|
||||
- m_Id: main
|
||||
m_Name:
|
||||
@@ -32,6 +32,6 @@ MonoBehaviour:
|
||||
m_RegistryInfoDraft:
|
||||
m_Modified: 0
|
||||
m_ErrorMessage:
|
||||
m_UserModificationsInstanceId: -890
|
||||
m_OriginalInstanceId: -892
|
||||
m_UserModificationsInstanceId: -874
|
||||
m_OriginalInstanceId: -876
|
||||
m_LoadAssets: 0
|
||||
|
||||
Reference in New Issue
Block a user