Compare commits
5 Commits
ea251d082d
...
ca4d33698f
| Author | SHA1 | Date | |
|---|---|---|---|
| ca4d33698f | |||
| 5e256faf03 | |||
| f4ec668bfd | |||
| 6aac14d3e5 | |||
| 4961bfb2b7 |
@ -193,6 +193,7 @@ GameObject:
|
||||
- component: {fileID: 1590262444720639052}
|
||||
- component: {fileID: 8270483239104722155}
|
||||
- component: {fileID: 4311531008057825966}
|
||||
- component: {fileID: -6194537545441247702}
|
||||
m_Layer: 5
|
||||
m_Name: UILoadUpdateWindow
|
||||
m_TagString: Untagged
|
||||
@ -319,6 +320,32 @@ MonoBehaviour:
|
||||
uuid: 052a4cdb-50cb-44b3-876f-90639a028620
|
||||
LoopCount: 1
|
||||
Name:
|
||||
--- !u!114 &-6194537545441247702
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 526598954257632073}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b880940ee89d20e4fa6f2630d7a81a14, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
openPreset: 3
|
||||
closePreset: 3
|
||||
openEase: 4
|
||||
closeEase: 3
|
||||
targetRect: {fileID: 2553447206821208227}
|
||||
canvasGroup: {fileID: 0}
|
||||
useUnscaledTime: 1
|
||||
initializeAsClosed: 1
|
||||
disableInteractionWhilePlaying: 1
|
||||
openDuration: 0.22
|
||||
closeDuration: 0.18
|
||||
slideDistance: 120
|
||||
toastDistance: 40
|
||||
closedScale: 0.94
|
||||
--- !u!1 &541431694581587512
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
8
Client/Assets/Plugins/UnityEditorDarkMode.meta
Normal file
8
Client/Assets/Plugins/UnityEditorDarkMode.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84e81e9c2b71c5f4a8e13c52a72e3e74
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
40
Client/Assets/Plugins/UnityEditorDarkMode/README.txt
Normal file
40
Client/Assets/Plugins/UnityEditorDarkMode/README.txt
Normal file
@ -0,0 +1,40 @@
|
||||
DarkMode Mod for Unity Editor on Windows
|
||||
----------------------------------------------------------------------------
|
||||
v1.1 04/08/2024
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
A fully working runtime dark mode mod for Unity Editor on Windows with:
|
||||
|
||||
- Dark title bar
|
||||
- Dark menu bar
|
||||
- Dark context menu
|
||||
- And more...
|
||||
|
||||
> This runtime mod works on Windows 11 and Windows 10 1903+.
|
||||
Tested on Unity 2019, 2020, 2021, 2022, 2023 and Unity 6.
|
||||
|
||||
Source code is available at: https://github.com/0x7c13/UnityEditor-DarkMode
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Jiaqi (0x7c13) Liu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33d8f76296a183145ae27b820db1e4b4
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@ -0,0 +1,6 @@
|
||||
menubar_textcolor = 200,200,200
|
||||
menubar_textcolor_disabled = 160,160,160
|
||||
menubar_bgcolor = 48,48,48
|
||||
menubaritem_bgcolor = 48,48,48
|
||||
menubaritem_bgcolor_hot = 62,62,62
|
||||
menubaritem_bgcolor_selected = 62,62,62
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c50fe9ef05bb7b49bf69b0c596fd1ed
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,63 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8116b2fba7c75047b30e087741eb77b
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 1
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
: Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Editor: 0
|
||||
Exclude Linux64: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: Windows
|
||||
- first:
|
||||
Standalone: Linux64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
- first:
|
||||
Standalone: OSXUniversal
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
- first:
|
||||
Standalone: Win
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
- first:
|
||||
Standalone: Win64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,18 +1,17 @@
|
||||
using System;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
public static class GlyphService
|
||||
{
|
||||
// 缓存的设备提示数组,避免内存分配
|
||||
private static readonly string[] KeyboardHints = { "Keyboard", "Mouse" };
|
||||
private static readonly string[] XboxHints = { "XInput", "Xbox", "Gamepad" };
|
||||
private static readonly string[] PlayStationHints = { "DualShock", "DualSense", "PlayStation", "Gamepad" };
|
||||
private static readonly string[] KeyboardGroupHints = { "keyboard", "mouse", "keyboard&mouse", "keyboardmouse", "kbm" };
|
||||
private static readonly string[] XboxGroupHints = { "xbox", "xinput", "gamepad", "controller" };
|
||||
private static readonly string[] PlayStationGroupHints = { "playstation", "dualshock", "dualsense", "gamepad", "controller" };
|
||||
private static readonly string[] OtherGamepadGroupHints = { "gamepad", "controller", "joystick" };
|
||||
private static readonly char[] TrimChars = { '{', '}', '<', '>', '\'', '"' };
|
||||
|
||||
/// <summary>
|
||||
/// 获取输入图标数据库实例
|
||||
/// </summary>
|
||||
private static InputGlyphDatabase _database;
|
||||
|
||||
public static InputGlyphDatabase Database
|
||||
{
|
||||
get
|
||||
@ -26,179 +25,291 @@ public static class GlyphService
|
||||
}
|
||||
}
|
||||
|
||||
private static InputGlyphDatabase _database;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取输入操作的绑定控制路径
|
||||
/// </summary>
|
||||
/// <param name="action">输入操作</param>
|
||||
/// <param name="compositePartName">复合部分名称(可选)</param>
|
||||
/// <param name="deviceOverride">设备类型覆盖(可选)</param>
|
||||
/// <returns>绑定控制路径</returns>
|
||||
public static string GetBindingControlPath(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
|
||||
public static string GetBindingControlPath(
|
||||
InputAction action,
|
||||
string compositePartName = null,
|
||||
InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
|
||||
{
|
||||
if (action == null) return string.Empty;
|
||||
var binding = GetBindingControl(action, compositePartName, deviceOverride);
|
||||
return binding.hasOverrides ? binding.effectivePath : binding.path;
|
||||
return TryGetBindingControl(action, compositePartName, deviceOverride, out InputBinding binding)
|
||||
? GetEffectivePath(binding)
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取输入操作的 TextMeshPro 标签
|
||||
/// </summary>
|
||||
/// <param name="reference">输入操作引用</param>
|
||||
/// <param name="compositePartName">复合部分名称</param>
|
||||
/// <param name="device">设备类型</param>
|
||||
/// <param name="tag">输出的 TMP 标签</param>
|
||||
/// <param name="displayFallback">显示回退文本</param>
|
||||
/// <param name="db">数据库实例(可选)</param>
|
||||
/// <returns>是否成功获取</returns>
|
||||
public static bool TryGetTMPTagForActionPath(InputAction reference, string compositePartName, InputDeviceWatcher.InputDeviceCategory device, out string tag, out string displayFallback, InputGlyphDatabase db = null)
|
||||
public static string GetBindingControlPath(
|
||||
InputActionReference actionReference,
|
||||
string compositePartName = null,
|
||||
InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
|
||||
{
|
||||
string path = GetBindingControlPath(reference, compositePartName, device);
|
||||
return TryGetTMPTagForActionPath(path, device, out tag, out displayFallback, db);
|
||||
return GetBindingControlPath(actionReference != null ? actionReference.action : null, compositePartName, deviceOverride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取输入操作的 UI Sprite
|
||||
/// </summary>
|
||||
/// <param name="reference">输入操作引用</param>
|
||||
/// <param name="compositePartName">复合部分名称</param>
|
||||
/// <param name="device">设备类型</param>
|
||||
/// <param name="sprite">输出的 Sprite</param>
|
||||
/// <param name="db">数据库实例(可选)</param>
|
||||
/// <returns>是否成功获取</returns>
|
||||
public static bool TryGetUISpriteForActionPath(InputAction reference, string compositePartName, InputDeviceWatcher.InputDeviceCategory device, out Sprite sprite, InputGlyphDatabase db = null)
|
||||
public static bool TryGetTMPTagForActionPath(
|
||||
InputAction action,
|
||||
string compositePartName,
|
||||
InputDeviceWatcher.InputDeviceCategory device,
|
||||
out string tag,
|
||||
out string displayFallback,
|
||||
InputGlyphDatabase db = null)
|
||||
{
|
||||
string path = GetBindingControlPath(reference, compositePartName, device);
|
||||
return TryGetUISpriteForActionPath(path, device, out sprite, db);
|
||||
string controlPath = GetBindingControlPath(action, compositePartName, device);
|
||||
return TryGetTMPTagForActionPath(controlPath, device, out tag, out displayFallback, db);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据控制路径尝试获取 TextMeshPro 标签
|
||||
/// </summary>
|
||||
/// <param name="controlPath">控制路径</param>
|
||||
/// <param name="device">设备类型</param>
|
||||
/// <param name="tag">输出的 TMP 标签</param>
|
||||
/// <param name="displayFallback">显示回退文本</param>
|
||||
/// <param name="db">数据库实例(可选)</param>
|
||||
/// <returns>是否成功获取</returns>
|
||||
public static bool TryGetTMPTagForActionPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device, out string tag, out string displayFallback, InputGlyphDatabase db = null)
|
||||
public static bool TryGetTMPTagForActionPath(
|
||||
InputActionReference actionReference,
|
||||
string compositePartName,
|
||||
InputDeviceWatcher.InputDeviceCategory device,
|
||||
out string tag,
|
||||
out string displayFallback,
|
||||
InputGlyphDatabase db = null)
|
||||
{
|
||||
tag = null;
|
||||
displayFallback = null;
|
||||
db = db ?? Database;
|
||||
return TryGetTMPTagForActionPath(actionReference != null ? actionReference.action : null, compositePartName, device, out tag, out displayFallback, db);
|
||||
}
|
||||
|
||||
public static bool TryGetUISpriteForActionPath(
|
||||
InputAction action,
|
||||
string compositePartName,
|
||||
InputDeviceWatcher.InputDeviceCategory device,
|
||||
out Sprite sprite,
|
||||
InputGlyphDatabase db = null)
|
||||
{
|
||||
string controlPath = GetBindingControlPath(action, compositePartName, device);
|
||||
return TryGetUISpriteForActionPath(controlPath, device, out sprite, db);
|
||||
}
|
||||
|
||||
public static bool TryGetUISpriteForActionPath(
|
||||
InputActionReference actionReference,
|
||||
string compositePartName,
|
||||
InputDeviceWatcher.InputDeviceCategory device,
|
||||
out Sprite sprite,
|
||||
InputGlyphDatabase db = null)
|
||||
{
|
||||
return TryGetUISpriteForActionPath(actionReference != null ? actionReference.action : null, compositePartName, device, out sprite, db);
|
||||
}
|
||||
|
||||
public static bool TryGetTMPTagForActionPath(
|
||||
string controlPath,
|
||||
InputDeviceWatcher.InputDeviceCategory device,
|
||||
out string tag,
|
||||
out string displayFallback,
|
||||
InputGlyphDatabase db = null)
|
||||
{
|
||||
displayFallback = GetDisplayNameFromControlPath(controlPath);
|
||||
tag = null;
|
||||
|
||||
var sprite = db.FindSprite(controlPath, device) ?? db.FindSprite(controlPath, InputDeviceWatcher.InputDeviceCategory.Keyboard);
|
||||
if (!TryGetUISpriteForActionPath(controlPath, device, out Sprite sprite, db))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var spriteName = sprite == null ? string.Empty : sprite.name;
|
||||
tag = $"<sprite name=\"{spriteName}\">";
|
||||
tag = $"<sprite name=\"{sprite.name}\">";
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据控制路径尝试获取 UI Sprite
|
||||
/// </summary>
|
||||
/// <param name="controlPath">控制路径</param>
|
||||
/// <param name="device">设备类型</param>
|
||||
/// <param name="sprite">输出的 Sprite</param>
|
||||
/// <param name="db">数据库实例(可选)</param>
|
||||
/// <returns>是否成功获取</returns>
|
||||
public static bool TryGetUISpriteForActionPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device, out Sprite sprite, InputGlyphDatabase db = null)
|
||||
public static bool TryGetUISpriteForActionPath(
|
||||
string controlPath,
|
||||
InputDeviceWatcher.InputDeviceCategory device,
|
||||
out Sprite sprite,
|
||||
InputGlyphDatabase db = null)
|
||||
{
|
||||
sprite = null;
|
||||
db = db ?? Database;
|
||||
if (string.IsNullOrEmpty(controlPath) || db == null) return false;
|
||||
sprite = db.FindSprite(controlPath, device) ?? db.FindSprite(controlPath, InputDeviceWatcher.InputDeviceCategory.Keyboard);
|
||||
return sprite != null;
|
||||
db ??= Database;
|
||||
return db != null && db.TryGetSprite(controlPath, device, out sprite);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取输入操作的绑定控制
|
||||
/// </summary>
|
||||
static InputBinding GetBindingControl(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
|
||||
public static string GetDisplayNameFromInputAction(
|
||||
InputAction action,
|
||||
string compositePartName = null,
|
||||
InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
|
||||
{
|
||||
if (action == null) return default;
|
||||
|
||||
var curCategory = deviceOverride ?? InputDeviceWatcher.CurrentCategory;
|
||||
var hints = GetDeviceHintsForCategory(curCategory);
|
||||
|
||||
foreach (var b in action.bindings)
|
||||
if (!TryGetBindingControl(action, compositePartName, deviceOverride, out InputBinding binding))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(compositePartName))
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string display = binding.ToDisplayString();
|
||||
return string.IsNullOrEmpty(display) ? GetDisplayNameFromControlPath(GetEffectivePath(binding)) : display;
|
||||
}
|
||||
|
||||
public static string GetDisplayNameFromControlPath(string controlPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(controlPath))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string humanReadable = InputControlPath.ToHumanReadableString(controlPath, InputControlPath.HumanReadableStringOptions.OmitDevice);
|
||||
if (!string.IsNullOrWhiteSpace(humanReadable))
|
||||
{
|
||||
return humanReadable;
|
||||
}
|
||||
|
||||
string[] parts = controlPath.Split('/');
|
||||
string last = parts[parts.Length - 1].Trim(TrimChars);
|
||||
return last;
|
||||
}
|
||||
|
||||
public static bool TryGetBindingControl(
|
||||
InputAction action,
|
||||
string compositePartName,
|
||||
InputDeviceWatcher.InputDeviceCategory? deviceOverride,
|
||||
out InputBinding binding)
|
||||
{
|
||||
binding = default;
|
||||
if (action == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
InputDeviceWatcher.InputDeviceCategory category = deviceOverride ?? InputDeviceWatcher.CurrentCategory;
|
||||
int bestScore = int.MinValue;
|
||||
bool requireCompositePart = !string.IsNullOrEmpty(compositePartName);
|
||||
|
||||
for (int i = 0; i < action.bindings.Count; i++)
|
||||
{
|
||||
InputBinding candidate = action.bindings[i];
|
||||
if (candidate.isComposite)
|
||||
{
|
||||
if (!b.isPartOfComposite) continue;
|
||||
if (!string.Equals(b.name, compositePartName, StringComparison.OrdinalIgnoreCase)) continue;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 替换 LINQ Any() 以避免委托分配
|
||||
if (!string.IsNullOrEmpty(b.path) && ContainsAnyHint(b.path, hints)) return b;
|
||||
if (!string.IsNullOrEmpty(b.effectivePath) && ContainsAnyHint(b.effectivePath, hints)) return b;
|
||||
if (requireCompositePart)
|
||||
{
|
||||
if (!candidate.isPartOfComposite || !string.Equals(candidate.name, compositePartName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (candidate.isPartOfComposite)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string path = GetEffectivePath(candidate);
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int score = ScoreBinding(candidate, category);
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
binding = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
return bestScore > int.MinValue;
|
||||
}
|
||||
|
||||
// 辅助方法,避免 LINQ Any() 的内存分配
|
||||
/// <summary>
|
||||
/// 检查路径是否包含任何提示字符串
|
||||
/// </summary>
|
||||
static bool ContainsAnyHint(string path, string[] hints)
|
||||
private static int ScoreBinding(InputBinding binding, InputDeviceWatcher.InputDeviceCategory category)
|
||||
{
|
||||
for (int i = 0; i < hints.Length; i++)
|
||||
int score = 0;
|
||||
string path = GetEffectivePath(binding);
|
||||
|
||||
if (MatchesBindingGroups(binding.groups, category))
|
||||
{
|
||||
if (path.IndexOf(hints[i], StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
return true;
|
||||
score += 100;
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(binding.groups))
|
||||
{
|
||||
score -= 20;
|
||||
}
|
||||
|
||||
if (MatchesControlPath(path, category))
|
||||
{
|
||||
score += 60;
|
||||
}
|
||||
|
||||
if (!binding.isPartOfComposite)
|
||||
{
|
||||
score += 5;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
private static bool MatchesBindingGroups(string groups, InputDeviceWatcher.InputDeviceCategory category)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(groups))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string[] hints = GetGroupHints(category);
|
||||
string[] tokens = groups.Split(InputBinding.Separator);
|
||||
for (int i = 0; i < tokens.Length; i++)
|
||||
{
|
||||
string token = tokens[i].Trim();
|
||||
if (ContainsAny(token, hints))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据设备类型获取设备提示字符串数组
|
||||
/// </summary>
|
||||
static string[] GetDeviceHintsForCategory(InputDeviceWatcher.InputDeviceCategory cat)
|
||||
private static bool MatchesControlPath(string path, InputDeviceWatcher.InputDeviceCategory category)
|
||||
{
|
||||
switch (cat)
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (category)
|
||||
{
|
||||
case InputDeviceWatcher.InputDeviceCategory.Keyboard:
|
||||
return KeyboardHints;
|
||||
return StartsWithDevice(path, "<Keyboard>") || StartsWithDevice(path, "<Mouse>");
|
||||
case InputDeviceWatcher.InputDeviceCategory.Xbox:
|
||||
return XboxHints;
|
||||
return StartsWithDevice(path, "<Gamepad>") || StartsWithDevice(path, "<Joystick>") || ContainsAny(path, XboxGroupHints);
|
||||
case InputDeviceWatcher.InputDeviceCategory.PlayStation:
|
||||
return PlayStationHints;
|
||||
return StartsWithDevice(path, "<Gamepad>") || StartsWithDevice(path, "<Joystick>") || ContainsAny(path, PlayStationGroupHints);
|
||||
default:
|
||||
return XboxHints;
|
||||
return StartsWithDevice(path, "<Gamepad>") || StartsWithDevice(path, "<Joystick>") || ContainsAny(path, OtherGamepadGroupHints);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 从输入操作获取显示名称
|
||||
/// </summary>
|
||||
/// <param name="action">输入操作</param>
|
||||
/// <param name="compositePartName">复合部分名称(可选)</param>
|
||||
/// <param name="deviceOverride">设备类型覆盖</param>
|
||||
/// <returns>显示名称</returns>
|
||||
public static string GetDisplayNameFromInputAction(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory deviceOverride = InputDeviceWatcher.InputDeviceCategory.Keyboard)
|
||||
private static bool StartsWithDevice(string path, string deviceTag)
|
||||
{
|
||||
if (action == null) return string.Empty;
|
||||
var binding = GetBindingControl(action, compositePartName, deviceOverride);
|
||||
return binding.ToDisplayString();
|
||||
return path.StartsWith(deviceTag, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从控制路径获取显示名称
|
||||
/// </summary>
|
||||
/// <param name="controlPath">控制路径</param>
|
||||
/// <returns>显示名称</returns>
|
||||
public static string GetDisplayNameFromControlPath(string controlPath)
|
||||
private static string[] GetGroupHints(InputDeviceWatcher.InputDeviceCategory category)
|
||||
{
|
||||
if (string.IsNullOrEmpty(controlPath)) return string.Empty;
|
||||
var parts = controlPath.Split('/');
|
||||
var last = parts[parts.Length - 1].Trim(TrimChars);
|
||||
return last;
|
||||
switch (category)
|
||||
{
|
||||
case InputDeviceWatcher.InputDeviceCategory.Keyboard:
|
||||
return KeyboardGroupHints;
|
||||
case InputDeviceWatcher.InputDeviceCategory.Xbox:
|
||||
return XboxGroupHints;
|
||||
case InputDeviceWatcher.InputDeviceCategory.PlayStation:
|
||||
return PlayStationGroupHints;
|
||||
default:
|
||||
return OtherGamepadGroupHints;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ContainsAny(string source, string[] hints)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(source) || hints == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < hints.Length; i++)
|
||||
{
|
||||
if (source.IndexOf(hints[i], StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string GetEffectivePath(InputBinding binding)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(binding.effectivePath) ? binding.path : binding.effectivePath;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using AlicizaX;
|
||||
using System;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
|
||||
public static class InputDeviceWatcher
|
||||
{
|
||||
@ -15,35 +16,108 @@ public static class InputDeviceWatcher
|
||||
Other
|
||||
}
|
||||
|
||||
static readonly float DebounceWindow = 1f;
|
||||
public static InputDeviceCategory CurrentCategory = InputDeviceCategory.Keyboard;
|
||||
public static string CurrentDeviceName = "";
|
||||
public readonly struct DeviceContext : IEquatable<DeviceContext>
|
||||
{
|
||||
public readonly InputDeviceCategory Category;
|
||||
public readonly int DeviceId;
|
||||
public readonly int VendorId;
|
||||
public readonly int ProductId;
|
||||
public readonly string DeviceName;
|
||||
public readonly string Layout;
|
||||
|
||||
public DeviceContext(
|
||||
InputDeviceCategory category,
|
||||
int deviceId,
|
||||
int vendorId,
|
||||
int productId,
|
||||
string deviceName,
|
||||
string layout)
|
||||
{
|
||||
Category = category;
|
||||
DeviceId = deviceId;
|
||||
VendorId = vendorId;
|
||||
ProductId = productId;
|
||||
DeviceName = deviceName ?? string.Empty;
|
||||
Layout = layout ?? string.Empty;
|
||||
}
|
||||
|
||||
public bool Equals(DeviceContext other)
|
||||
{
|
||||
return Category == other.Category
|
||||
&& DeviceId == other.DeviceId
|
||||
&& VendorId == other.VendorId
|
||||
&& ProductId == other.ProductId
|
||||
&& string.Equals(DeviceName, other.DeviceName, StringComparison.Ordinal)
|
||||
&& string.Equals(Layout, other.Layout, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is DeviceContext other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hashCode = (int)Category;
|
||||
hashCode = (hashCode * 397) ^ DeviceId;
|
||||
hashCode = (hashCode * 397) ^ VendorId;
|
||||
hashCode = (hashCode * 397) ^ ProductId;
|
||||
hashCode = (hashCode * 397) ^ (DeviceName != null ? DeviceName.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^ (Layout != null ? Layout.GetHashCode() : 0);
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private struct DeviceCapabilityInfo
|
||||
{
|
||||
public int vendorId;
|
||||
public int productId;
|
||||
}
|
||||
|
||||
private const float SameCategoryDebounceWindow = 0.15f;
|
||||
private const float AxisActivationThreshold = 0.5f;
|
||||
private const float StickActivationThreshold = 0.25f;
|
||||
private const string DefaultKeyboardDeviceName = "Keyboard&Mouse";
|
||||
|
||||
public static InputDeviceCategory CurrentCategory { get; private set; } = InputDeviceCategory.Keyboard;
|
||||
public static string CurrentDeviceName { get; private set; } = DefaultKeyboardDeviceName;
|
||||
public static int CurrentDeviceId { get; private set; } = -1;
|
||||
public static int CurrentVendorId { get; private set; }
|
||||
public static int CurrentProductId { get; private set; }
|
||||
public static DeviceContext CurrentContext { get; private set; } = CreateDefaultContext();
|
||||
|
||||
private static InputAction _anyInputAction;
|
||||
private static int _lastDeviceId = -1;
|
||||
private static float _lastInputTime = -Mathf.Infinity;
|
||||
|
||||
private static InputDeviceCategory _lastEmittedCategory = InputDeviceCategory.Keyboard;
|
||||
private static float _lastSwitchTime = -Mathf.Infinity;
|
||||
private static DeviceContext _lastEmittedContext = CreateDefaultContext();
|
||||
private static bool _initialized;
|
||||
|
||||
public static event Action<InputDeviceCategory> OnDeviceChanged;
|
||||
|
||||
private static bool initialized = false;
|
||||
public static event Action<DeviceContext> OnDeviceContextChanged;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
public static void Initialize()
|
||||
{
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
if (_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentCategory = InputDeviceCategory.Keyboard;
|
||||
CurrentDeviceName = "";
|
||||
_lastEmittedCategory = CurrentCategory; // 初始化同步
|
||||
_initialized = true;
|
||||
ApplyContext(CreateDefaultContext(), false);
|
||||
_lastEmittedContext = CurrentContext;
|
||||
|
||||
_anyInputAction = new InputAction("AnyDevice", InputActionType.PassThrough);
|
||||
_anyInputAction.AddBinding("<Keyboard>/anyKey");
|
||||
_anyInputAction.AddBinding("<Mouse>/leftButton");
|
||||
_anyInputAction.AddBinding("<Mouse>/rightButton");
|
||||
_anyInputAction.AddBinding("<Mouse>/middleButton");
|
||||
_anyInputAction.AddBinding("<Mouse>/scroll");
|
||||
_anyInputAction.AddBinding("<Gamepad>/*");
|
||||
_anyInputAction.AddBinding("<Joystick>/*");
|
||||
|
||||
_anyInputAction.performed += OnAnyInputPerformed;
|
||||
_anyInputAction.Enable();
|
||||
|
||||
@ -54,142 +128,311 @@ public static class InputDeviceWatcher
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
static void OnPlayModeStateChanged(PlayModeStateChange state)
|
||||
private static void OnPlayModeStateChanged(PlayModeStateChange state)
|
||||
{
|
||||
if (state == PlayModeStateChange.ExitingPlayMode)
|
||||
{
|
||||
Dispose();
|
||||
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
|
||||
}
|
||||
|
||||
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
|
||||
}
|
||||
#endif
|
||||
|
||||
public static void Dispose()
|
||||
{
|
||||
if (!initialized) return;
|
||||
CurrentCategory = InputDeviceCategory.Keyboard;
|
||||
_anyInputAction.performed -= OnAnyInputPerformed;
|
||||
_anyInputAction.Disable();
|
||||
_anyInputAction.Dispose();
|
||||
if (!_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_anyInputAction != null)
|
||||
{
|
||||
_anyInputAction.performed -= OnAnyInputPerformed;
|
||||
_anyInputAction.Disable();
|
||||
_anyInputAction.Dispose();
|
||||
_anyInputAction = null;
|
||||
}
|
||||
|
||||
InputSystem.onDeviceChange -= OnDeviceChange;
|
||||
|
||||
ApplyContext(CreateDefaultContext(), false);
|
||||
_lastEmittedContext = CurrentContext;
|
||||
_lastSwitchTime = -Mathf.Infinity;
|
||||
OnDeviceChanged = null;
|
||||
initialized = false;
|
||||
|
||||
_lastEmittedCategory = InputDeviceCategory.Keyboard;
|
||||
OnDeviceContextChanged = null;
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
private static void OnAnyInputPerformed(InputAction.CallbackContext ctx)
|
||||
private static void OnAnyInputPerformed(InputAction.CallbackContext context)
|
||||
{
|
||||
if (ctx.control == null || ctx.control.device == null) return;
|
||||
InputControl control = context.control;
|
||||
if (!IsRelevantControl(control))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var device = ctx.control.device;
|
||||
DeviceContext deviceContext = BuildContext(control.device);
|
||||
if (deviceContext.DeviceId == CurrentDeviceId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsRelevantDevice(device)) return;
|
||||
|
||||
int curId = device.deviceId;
|
||||
float now = Time.realtimeSinceStartup;
|
||||
if (deviceContext.Category == CurrentCategory && now - _lastSwitchTime < SameCategoryDebounceWindow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (curId == _lastDeviceId) return;
|
||||
if (DebounceWindow > 0f && (now - _lastInputTime) < DebounceWindow) return;
|
||||
|
||||
_lastInputTime = now;
|
||||
_lastDeviceId = curId;
|
||||
|
||||
CurrentCategory = DetermineCategoryFromDevice(device);
|
||||
CurrentDeviceName = device.displayName ?? $"Device_{curId}";
|
||||
|
||||
EmitChange();
|
||||
_lastSwitchTime = now;
|
||||
SetCurrentContext(deviceContext);
|
||||
}
|
||||
|
||||
private static void OnDeviceChange(InputDevice device, InputDeviceChange change)
|
||||
{
|
||||
if (change == InputDeviceChange.Removed || change == InputDeviceChange.Disconnected)
|
||||
if (device == null)
|
||||
{
|
||||
if (device.deviceId == _lastDeviceId)
|
||||
{
|
||||
_lastDeviceId = -1;
|
||||
_lastInputTime = -Mathf.Infinity;
|
||||
CurrentDeviceName = "";
|
||||
CurrentCategory = InputDeviceCategory.Keyboard;
|
||||
EmitChange();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (change)
|
||||
{
|
||||
case InputDeviceChange.Removed:
|
||||
case InputDeviceChange.Disconnected:
|
||||
if (device.deviceId == CurrentDeviceId)
|
||||
{
|
||||
PromoteFallbackDevice(device.deviceId);
|
||||
}
|
||||
break;
|
||||
case InputDeviceChange.Reconnected:
|
||||
case InputDeviceChange.Added:
|
||||
if (CurrentDeviceId < 0 && IsRelevantDevice(device))
|
||||
{
|
||||
SetCurrentContext(BuildContext(device));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------ 分类逻辑 --------------------
|
||||
private static InputDeviceCategory DetermineCategoryFromDevice(InputDevice device)
|
||||
private static void PromoteFallbackDevice(int removedDeviceId)
|
||||
{
|
||||
if (device == null) return InputDeviceCategory.Keyboard;
|
||||
// 重要:鼠标不再被视为键盘类(避免鼠标触发时回退到 Keyboard)
|
||||
if (device is Keyboard) return InputDeviceCategory.Keyboard;
|
||||
if (device is Mouse) return InputDeviceCategory.Other; // 明确忽略鼠标
|
||||
if (IsGamepadLike(device)) return GetGamepadCategory(device);
|
||||
for (int i = InputSystem.devices.Count - 1; i >= 0; i--)
|
||||
{
|
||||
InputDevice device = InputSystem.devices[i];
|
||||
if (device == null || device.deviceId == removedDeviceId || !device.added || !IsRelevantDevice(device))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string combined = $"{device.description.interfaceName} {device.layout} {device.description.product} {device.description.manufacturer} {device.displayName}".ToLower();
|
||||
SetCurrentContext(BuildContext(device));
|
||||
return;
|
||||
}
|
||||
|
||||
if (combined.Contains("xbox") || combined.Contains("xinput")) return InputDeviceCategory.Xbox;
|
||||
if (combined.Contains("dualshock") || combined.Contains("dualsense") || combined.Contains("playstation")) return InputDeviceCategory.PlayStation;
|
||||
|
||||
return InputDeviceCategory.Other;
|
||||
SetCurrentContext(CreateDefaultContext());
|
||||
}
|
||||
|
||||
private static bool IsGamepadLike(InputDevice device)
|
||||
private static void SetCurrentContext(DeviceContext context)
|
||||
{
|
||||
if (device is Gamepad) return true;
|
||||
if (device is Joystick) return true;
|
||||
bool categoryChanged = CurrentCategory != context.Category;
|
||||
ApplyContext(context, true);
|
||||
|
||||
var layout = (device.layout ?? "").ToLower();
|
||||
// 这里保留 controller/gamepad/joystick 的识别,但忽略 mouse/touch 等
|
||||
if (layout.Contains("mouse") || layout.Contains("touch") || layout.Contains("pen")) return false;
|
||||
return layout.Contains("gamepad") || layout.Contains("controller") || layout.Contains("joystick");
|
||||
if (!_lastEmittedContext.Equals(context))
|
||||
{
|
||||
OnDeviceContextChanged?.Invoke(context);
|
||||
if (categoryChanged)
|
||||
{
|
||||
OnDeviceChanged?.Invoke(context.Category);
|
||||
}
|
||||
|
||||
_lastEmittedContext = context;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyContext(DeviceContext context, bool log)
|
||||
{
|
||||
CurrentContext = context;
|
||||
CurrentCategory = context.Category;
|
||||
CurrentDeviceId = context.DeviceId;
|
||||
CurrentVendorId = context.VendorId;
|
||||
CurrentProductId = context.ProductId;
|
||||
CurrentDeviceName = context.DeviceName;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (log)
|
||||
{
|
||||
AlicizaX.Log.Info($"Input device -> {CurrentCategory} name={CurrentDeviceName} vid=0x{CurrentVendorId:X} pid=0x{CurrentProductId:X} id={CurrentDeviceId}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static DeviceContext BuildContext(InputDevice device)
|
||||
{
|
||||
if (device == null)
|
||||
{
|
||||
return CreateDefaultContext();
|
||||
}
|
||||
|
||||
TryParseVendorProductIds(device.description.capabilities, out int vendorId, out int productId);
|
||||
string deviceName = string.IsNullOrWhiteSpace(device.displayName) ? device.name : device.displayName;
|
||||
return new DeviceContext(
|
||||
DetermineCategoryFromDevice(device),
|
||||
device.deviceId,
|
||||
vendorId,
|
||||
productId,
|
||||
deviceName,
|
||||
device.layout);
|
||||
}
|
||||
|
||||
private static DeviceContext CreateDefaultContext()
|
||||
{
|
||||
return new DeviceContext(InputDeviceCategory.Keyboard, -1, 0, 0, DefaultKeyboardDeviceName, Keyboard.current != null ? Keyboard.current.layout : string.Empty);
|
||||
}
|
||||
|
||||
private static InputDeviceCategory DetermineCategoryFromDevice(InputDevice device)
|
||||
{
|
||||
if (device == null)
|
||||
{
|
||||
return InputDeviceCategory.Keyboard;
|
||||
}
|
||||
|
||||
if (device is Keyboard || device is Mouse)
|
||||
{
|
||||
return InputDeviceCategory.Keyboard;
|
||||
}
|
||||
|
||||
if (IsGamepadLike(device))
|
||||
{
|
||||
return GetGamepadCategory(device);
|
||||
}
|
||||
|
||||
string combined = CombineDeviceDescription(device);
|
||||
if (ContainsIgnoreCase(combined, "xbox") || ContainsIgnoreCase(combined, "xinput"))
|
||||
{
|
||||
return InputDeviceCategory.Xbox;
|
||||
}
|
||||
|
||||
if (ContainsIgnoreCase(combined, "dualshock")
|
||||
|| ContainsIgnoreCase(combined, "dualsense")
|
||||
|| ContainsIgnoreCase(combined, "playstation"))
|
||||
{
|
||||
return InputDeviceCategory.PlayStation;
|
||||
}
|
||||
|
||||
return InputDeviceCategory.Other;
|
||||
}
|
||||
|
||||
private static bool IsRelevantDevice(InputDevice device)
|
||||
{
|
||||
if (device == null) return false;
|
||||
if (device is Keyboard) return true;
|
||||
if (IsGamepadLike(device)) return true;
|
||||
return false;
|
||||
return device is Keyboard || device is Mouse || IsGamepadLike(device);
|
||||
}
|
||||
|
||||
private static bool IsRelevantControl(InputControl control)
|
||||
{
|
||||
if (control == null || control.device == null || !IsRelevantDevice(control.device) || control.synthetic)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (control)
|
||||
{
|
||||
case ButtonControl button:
|
||||
return button.IsPressed();
|
||||
case StickControl stick:
|
||||
return stick.ReadValue().sqrMagnitude >= StickActivationThreshold;
|
||||
case Vector2Control vector2:
|
||||
return vector2.ReadValue().sqrMagnitude >= StickActivationThreshold;
|
||||
case AxisControl axis:
|
||||
return Mathf.Abs(axis.ReadValue()) >= AxisActivationThreshold;
|
||||
default:
|
||||
return !control.noisy;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsGamepadLike(InputDevice device)
|
||||
{
|
||||
if (device is Gamepad || device is Joystick)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
string layout = device.layout ?? string.Empty;
|
||||
if (ContainsIgnoreCase(layout, "Mouse")
|
||||
|| ContainsIgnoreCase(layout, "Touch")
|
||||
|| ContainsIgnoreCase(layout, "Pen"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ContainsIgnoreCase(layout, "Gamepad")
|
||||
|| ContainsIgnoreCase(layout, "Controller")
|
||||
|| ContainsIgnoreCase(layout, "Joystick");
|
||||
}
|
||||
|
||||
private static InputDeviceCategory GetGamepadCategory(InputDevice device)
|
||||
{
|
||||
if (device == null) return InputDeviceCategory.Other;
|
||||
|
||||
var iface = (device.description.interfaceName ?? "").ToLower();
|
||||
if (iface.Contains("xinput")) return InputDeviceCategory.Xbox;
|
||||
|
||||
if (TryParseVidPidFromCapabilities(device.description.capabilities, out int vendorId, out int _))
|
||||
if (device == null)
|
||||
{
|
||||
if (vendorId == 0x045E || vendorId == 1118) return InputDeviceCategory.Xbox;
|
||||
if (vendorId == 0x054C || vendorId == 1356) return InputDeviceCategory.PlayStation;
|
||||
return InputDeviceCategory.Other;
|
||||
}
|
||||
|
||||
string combined = $"{device.description.interfaceName} {device.layout} {device.description.product} {device.description.manufacturer} {device.displayName}".ToLower();
|
||||
if (combined.Contains("xbox")) return InputDeviceCategory.Xbox;
|
||||
if (combined.Contains("dualshock") || combined.Contains("playstation")) return InputDeviceCategory.PlayStation;
|
||||
string interfaceName = device.description.interfaceName ?? string.Empty;
|
||||
if (ContainsIgnoreCase(interfaceName, "xinput"))
|
||||
{
|
||||
return InputDeviceCategory.Xbox;
|
||||
}
|
||||
|
||||
if (TryParseVendorProductIds(device.description.capabilities, out int vendorId, out _))
|
||||
{
|
||||
if (vendorId == 0x045E || vendorId == 1118)
|
||||
{
|
||||
return InputDeviceCategory.Xbox;
|
||||
}
|
||||
|
||||
if (vendorId == 0x054C || vendorId == 1356)
|
||||
{
|
||||
return InputDeviceCategory.PlayStation;
|
||||
}
|
||||
}
|
||||
|
||||
string combined = CombineDeviceDescription(device);
|
||||
if (ContainsIgnoreCase(combined, "xbox"))
|
||||
{
|
||||
return InputDeviceCategory.Xbox;
|
||||
}
|
||||
|
||||
if (ContainsIgnoreCase(combined, "dualshock")
|
||||
|| ContainsIgnoreCase(combined, "dualsense")
|
||||
|| ContainsIgnoreCase(combined, "playstation"))
|
||||
{
|
||||
return InputDeviceCategory.PlayStation;
|
||||
}
|
||||
|
||||
return InputDeviceCategory.Other;
|
||||
}
|
||||
|
||||
// ------------------ VID/PID 解析 --------------------
|
||||
private static bool TryParseVidPidFromCapabilities(string capabilities, out int vendorId, out int productId)
|
||||
private static string CombineDeviceDescription(InputDevice device)
|
||||
{
|
||||
return string.Concat(
|
||||
device.description.interfaceName, " ",
|
||||
device.layout, " ",
|
||||
device.description.product, " ",
|
||||
device.description.manufacturer, " ",
|
||||
device.displayName);
|
||||
}
|
||||
|
||||
private static bool TryParseVendorProductIds(string capabilities, out int vendorId, out int productId)
|
||||
{
|
||||
vendorId = 0;
|
||||
productId = 0;
|
||||
if (string.IsNullOrEmpty(capabilities)) return false;
|
||||
if (string.IsNullOrWhiteSpace(capabilities))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var decVendor = Regex.Match(capabilities, "\"vendorId\"\\s*:\\s*(\\d+)", RegexOptions.IgnoreCase);
|
||||
var decProduct = Regex.Match(capabilities, "\"productId\"\\s*:\\s*(\\d+)", RegexOptions.IgnoreCase);
|
||||
|
||||
if (decVendor.Success) int.TryParse(decVendor.Groups[1].Value, out vendorId);
|
||||
if (decProduct.Success) int.TryParse(decProduct.Groups[1].Value, out productId);
|
||||
|
||||
DeviceCapabilityInfo info = JsonUtility.FromJson<DeviceCapabilityInfo>(capabilities);
|
||||
vendorId = info.vendorId;
|
||||
productId = info.productId;
|
||||
return vendorId != 0 || productId != 0;
|
||||
}
|
||||
catch
|
||||
@ -198,45 +441,8 @@ public static class InputDeviceWatcher
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitChange()
|
||||
private static bool ContainsIgnoreCase(string source, string value)
|
||||
{
|
||||
if (CurrentCategory == _lastEmittedCategory)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int vid = GetVendorId();
|
||||
int pid = GetProductId();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
Log.Info($"输入设备变更 -> {CurrentCategory} 触发设备: {CurrentDeviceName} vid=0x{vid:X} pid=0x{pid:X}");
|
||||
#endif
|
||||
|
||||
OnDeviceChanged?.Invoke(CurrentCategory);
|
||||
_lastEmittedCategory = CurrentCategory;
|
||||
}
|
||||
|
||||
private static int GetVendorId()
|
||||
{
|
||||
foreach (var d in InputSystem.devices)
|
||||
{
|
||||
if ((d.displayName ?? "") == CurrentDeviceName &&
|
||||
TryParseVidPidFromCapabilities(d.description.capabilities, out int v, out int _))
|
||||
return v;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int GetProductId()
|
||||
{
|
||||
foreach (var d in InputSystem.devices)
|
||||
{
|
||||
if ((d.displayName ?? "") == CurrentDeviceName &&
|
||||
TryParseVidPidFromCapabilities(d.description.capabilities, out int _, out int p))
|
||||
return p;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return !string.IsNullOrEmpty(source) && source.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
using UnityEngine;
|
||||
|
||||
public abstract class InputGlyphBehaviourBase : MonoBehaviour
|
||||
{
|
||||
protected InputDeviceWatcher.InputDeviceCategory CurrentCategory { get; private set; }
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
CurrentCategory = InputDeviceWatcher.CurrentCategory;
|
||||
InputDeviceWatcher.OnDeviceChanged += HandleDeviceChanged;
|
||||
InputBindingManager.BindingsChanged += HandleBindingsChanged;
|
||||
RefreshGlyph();
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
InputDeviceWatcher.OnDeviceChanged -= HandleDeviceChanged;
|
||||
InputBindingManager.BindingsChanged -= HandleBindingsChanged;
|
||||
}
|
||||
|
||||
private void HandleDeviceChanged(InputDeviceWatcher.InputDeviceCategory category)
|
||||
{
|
||||
CurrentCategory = category;
|
||||
RefreshGlyph();
|
||||
}
|
||||
|
||||
private void HandleBindingsChanged()
|
||||
{
|
||||
RefreshGlyph();
|
||||
}
|
||||
|
||||
protected abstract void RefreshGlyph();
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a16393b81b47f1844a49d492284be475
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,10 +1,7 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.U2D;
|
||||
|
||||
|
||||
[Serializable]
|
||||
public sealed class GlyphEntry
|
||||
@ -17,205 +14,321 @@ public sealed class GlyphEntry
|
||||
public sealed class DeviceGlyphTable
|
||||
{
|
||||
public string deviceName;
|
||||
|
||||
public Texture2D spriteSheetTexture;
|
||||
|
||||
public Sprite platformIcons;
|
||||
|
||||
public List<GlyphEntry> entries = new List<GlyphEntry>();
|
||||
}
|
||||
|
||||
[CreateAssetMenu(fileName = "InputGlyphDatabase", menuName = "InputGlyphs/InputGlyphDatabase", order = 400)]
|
||||
public sealed class InputGlyphDatabase : ScriptableObject
|
||||
{
|
||||
private const string DEVICE_KEYBOARD = "Keyboard";
|
||||
private const string DEVICE_XBOX = "Xbox";
|
||||
private const string DEVICE_PLAYSTATION = "PlayStation";
|
||||
private const string DeviceKeyboard = "Keyboard";
|
||||
private const string DeviceXbox = "Xbox";
|
||||
private const string DevicePlayStation = "PlayStation";
|
||||
private const string DeviceOther = "Other";
|
||||
private static readonly InputDeviceWatcher.InputDeviceCategory[] KeyboardLookupOrder = { InputDeviceWatcher.InputDeviceCategory.Keyboard };
|
||||
private static readonly InputDeviceWatcher.InputDeviceCategory[] XboxLookupOrder =
|
||||
{
|
||||
InputDeviceWatcher.InputDeviceCategory.Xbox,
|
||||
InputDeviceWatcher.InputDeviceCategory.Other,
|
||||
InputDeviceWatcher.InputDeviceCategory.Keyboard,
|
||||
};
|
||||
private static readonly InputDeviceWatcher.InputDeviceCategory[] PlayStationLookupOrder =
|
||||
{
|
||||
InputDeviceWatcher.InputDeviceCategory.PlayStation,
|
||||
InputDeviceWatcher.InputDeviceCategory.Other,
|
||||
InputDeviceWatcher.InputDeviceCategory.Keyboard,
|
||||
};
|
||||
private static readonly InputDeviceWatcher.InputDeviceCategory[] OtherLookupOrder =
|
||||
{
|
||||
InputDeviceWatcher.InputDeviceCategory.Other,
|
||||
InputDeviceWatcher.InputDeviceCategory.Xbox,
|
||||
InputDeviceWatcher.InputDeviceCategory.Keyboard,
|
||||
};
|
||||
|
||||
public List<DeviceGlyphTable> tables = new List<DeviceGlyphTable>();
|
||||
|
||||
// 当 FindEntryByControlPath 传空 path 时返回的占位 sprite
|
||||
public Sprite placeholderSprite;
|
||||
|
||||
// 用于更快查找的缓存
|
||||
private Dictionary<string, DeviceGlyphTable> _tableCache;
|
||||
private Dictionary<(string path, InputDeviceWatcher.InputDeviceCategory device), Sprite> _spriteCache;
|
||||
private Dictionary<InputDeviceWatcher.InputDeviceCategory, Dictionary<string, Sprite>> _pathLookup;
|
||||
|
||||
/// <summary>
|
||||
/// 启用时构建缓存
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
BuildCache();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建表和精灵的查找缓存
|
||||
/// </summary>
|
||||
private void BuildCache()
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
if (_tableCache == null)
|
||||
{
|
||||
_tableCache = new Dictionary<string, DeviceGlyphTable>(tables.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tableCache.Clear();
|
||||
}
|
||||
|
||||
if (_spriteCache == null)
|
||||
{
|
||||
_spriteCache = new Dictionary<(string, InputDeviceWatcher.InputDeviceCategory), Sprite>();
|
||||
}
|
||||
else
|
||||
{
|
||||
_spriteCache.Clear();
|
||||
}
|
||||
|
||||
for (int i = 0; i < tables.Count; i++)
|
||||
{
|
||||
var table = tables[i];
|
||||
if (table != null && !string.IsNullOrEmpty(table.deviceName))
|
||||
{
|
||||
_tableCache[table.deviceName.ToLowerInvariant()] = table;
|
||||
}
|
||||
}
|
||||
BuildCache();
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 根据设备名称获取设备图标表
|
||||
/// </summary>
|
||||
/// <param name="deviceName">设备名称</param>
|
||||
/// <returns>设备图标表</returns>
|
||||
public DeviceGlyphTable GetTable(string deviceName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(deviceName)) return null;
|
||||
if (tables == null) return null;
|
||||
|
||||
// 确保缓存已构建
|
||||
if (_tableCache == null || _tableCache.Count == 0)
|
||||
if (string.IsNullOrWhiteSpace(deviceName) || tables == null)
|
||||
{
|
||||
BuildCache();
|
||||
return null;
|
||||
}
|
||||
|
||||
// 使用缓存进行 O(1) 查找
|
||||
if (_tableCache.TryGetValue(deviceName.ToLowerInvariant(), out var table))
|
||||
{
|
||||
return table;
|
||||
}
|
||||
|
||||
return null;
|
||||
EnsureCache();
|
||||
_tableCache.TryGetValue(deviceName.ToLowerInvariant(), out DeviceGlyphTable table);
|
||||
return table;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取平台图标
|
||||
/// </summary>
|
||||
/// <param name="device">设备类型</param>
|
||||
/// <returns>平台图标 Sprite</returns>
|
||||
public Sprite GetPlatformIcon(InputDeviceWatcher.InputDeviceCategory device)
|
||||
{
|
||||
var table = GetTable(device);
|
||||
if (table == null) return null;
|
||||
return table.platformIcons;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据设备类型获取设备图标表
|
||||
/// </summary>
|
||||
/// <param name="device">设备类型</param>
|
||||
/// <returns>设备图标表</returns>
|
||||
public DeviceGlyphTable GetTable(InputDeviceWatcher.InputDeviceCategory device)
|
||||
{
|
||||
// 使用常量避免字符串分配
|
||||
string name;
|
||||
switch (device)
|
||||
{
|
||||
case InputDeviceWatcher.InputDeviceCategory.Keyboard:
|
||||
name = DEVICE_KEYBOARD;
|
||||
break;
|
||||
return GetTable(DeviceKeyboard);
|
||||
case InputDeviceWatcher.InputDeviceCategory.Xbox:
|
||||
name = DEVICE_XBOX;
|
||||
break;
|
||||
return GetTable(DeviceXbox);
|
||||
case InputDeviceWatcher.InputDeviceCategory.PlayStation:
|
||||
name = DEVICE_PLAYSTATION;
|
||||
break;
|
||||
return GetTable(DevicePlayStation);
|
||||
default:
|
||||
name = DEVICE_XBOX;
|
||||
break;
|
||||
return GetTable(DeviceOther) ?? GetTable(DeviceXbox);
|
||||
}
|
||||
|
||||
return GetTable(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据控制路径和设备类型查找 Sprite
|
||||
/// </summary>
|
||||
/// <param name="controlPath">控制路径</param>
|
||||
/// <param name="device">设备类型</param>
|
||||
/// <returns>找到的 Sprite,未找到则返回占位 Sprite</returns>
|
||||
public Sprite GetPlatformIcon(InputDeviceWatcher.InputDeviceCategory device)
|
||||
{
|
||||
DeviceGlyphTable table = GetTable(device);
|
||||
return table != null ? table.platformIcons : null;
|
||||
}
|
||||
|
||||
public bool TryGetSprite(string controlPath, InputDeviceWatcher.InputDeviceCategory device, out Sprite sprite)
|
||||
{
|
||||
EnsureCache();
|
||||
string key = NormalizeControlPath(controlPath);
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
sprite = placeholderSprite;
|
||||
return sprite != null;
|
||||
}
|
||||
|
||||
InputDeviceWatcher.InputDeviceCategory[] lookupOrder = GetLookupOrder(device);
|
||||
for (int i = 0; i < lookupOrder.Length; i++)
|
||||
{
|
||||
InputDeviceWatcher.InputDeviceCategory category = lookupOrder[i];
|
||||
if (_pathLookup.TryGetValue(category, out Dictionary<string, Sprite> map) && map.TryGetValue(key, out sprite) && sprite != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
sprite = placeholderSprite;
|
||||
return sprite != null;
|
||||
}
|
||||
|
||||
public Sprite FindSprite(string controlPath, InputDeviceWatcher.InputDeviceCategory device)
|
||||
{
|
||||
if (string.IsNullOrEmpty(controlPath))
|
||||
{
|
||||
return placeholderSprite;
|
||||
}
|
||||
|
||||
// 首先检查缓存
|
||||
var cacheKey = (controlPath, device);
|
||||
if (_spriteCache != null && _spriteCache.TryGetValue(cacheKey, out var cachedSprite))
|
||||
{
|
||||
return cachedSprite ?? placeholderSprite;
|
||||
}
|
||||
|
||||
var entry = FindEntryByControlPath(controlPath, device);
|
||||
var sprite = entry?.Sprite ?? placeholderSprite;
|
||||
|
||||
// 缓存结果(包括 null 结果以避免重复查找)
|
||||
if (_spriteCache != null)
|
||||
{
|
||||
_spriteCache[cacheKey] = sprite;
|
||||
}
|
||||
|
||||
return sprite;
|
||||
return TryGetSprite(controlPath, device, out Sprite sprite) ? sprite : placeholderSprite;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据控制路径和设备类型查找图标条目
|
||||
/// </summary>
|
||||
/// <param name="controlPath">控制路径</param>
|
||||
/// <param name="device">设备类型</param>
|
||||
/// <returns>找到的图标条目,未找到则返回 null</returns>
|
||||
public GlyphEntry FindEntryByControlPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device)
|
||||
{
|
||||
var t = GetTable(device);
|
||||
if (t != null && t.entries != null)
|
||||
if (!TryGetSprite(controlPath, device, out Sprite sprite) || sprite == null)
|
||||
{
|
||||
for (int i = 0; i < t.entries.Count; ++i)
|
||||
return null;
|
||||
}
|
||||
|
||||
InputDeviceWatcher.InputDeviceCategory[] lookupOrder = GetLookupOrder(device);
|
||||
for (int i = 0; i < lookupOrder.Length; i++)
|
||||
{
|
||||
DeviceGlyphTable table = GetTable(lookupOrder[i]);
|
||||
if (table == null || table.entries == null)
|
||||
{
|
||||
var e = t.entries[i];
|
||||
if (e == null) continue;
|
||||
if (e.action == null) continue;
|
||||
continue;
|
||||
}
|
||||
|
||||
var bindings = e.action.bindings;
|
||||
int bindingCount = bindings.Count;
|
||||
if (bindingCount <= 0) continue;
|
||||
|
||||
for (int j = 0; j < bindingCount; j++)
|
||||
for (int j = 0; j < table.entries.Count; j++)
|
||||
{
|
||||
GlyphEntry entry = table.entries[j];
|
||||
if (entry != null && entry.Sprite == sprite)
|
||||
{
|
||||
var b = bindings[j];
|
||||
if (!string.IsNullOrEmpty(b.path) && string.Equals(b.path, controlPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return e;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(b.effectivePath) && string.Equals(b.effectivePath, controlPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return e;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void EnsureCache()
|
||||
{
|
||||
if (_tableCache == null || _pathLookup == null)
|
||||
{
|
||||
BuildCache();
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildCache()
|
||||
{
|
||||
_tableCache ??= new Dictionary<string, DeviceGlyphTable>(StringComparer.OrdinalIgnoreCase);
|
||||
_tableCache.Clear();
|
||||
|
||||
_pathLookup ??= new Dictionary<InputDeviceWatcher.InputDeviceCategory, Dictionary<string, Sprite>>();
|
||||
_pathLookup.Clear();
|
||||
InitializeLookup(InputDeviceWatcher.InputDeviceCategory.Keyboard);
|
||||
InitializeLookup(InputDeviceWatcher.InputDeviceCategory.Xbox);
|
||||
InitializeLookup(InputDeviceWatcher.InputDeviceCategory.PlayStation);
|
||||
InitializeLookup(InputDeviceWatcher.InputDeviceCategory.Other);
|
||||
|
||||
for (int i = 0; i < tables.Count; i++)
|
||||
{
|
||||
DeviceGlyphTable table = tables[i];
|
||||
if (table == null || string.IsNullOrWhiteSpace(table.deviceName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_tableCache[table.deviceName.ToLowerInvariant()] = table;
|
||||
InputDeviceWatcher.InputDeviceCategory category = ParseCategory(table.deviceName);
|
||||
Dictionary<string, Sprite> map = _pathLookup[category];
|
||||
RegisterEntries(table, map);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeLookup(InputDeviceWatcher.InputDeviceCategory category)
|
||||
{
|
||||
_pathLookup[category] = new Dictionary<string, Sprite>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private void RegisterEntries(DeviceGlyphTable table, Dictionary<string, Sprite> map)
|
||||
{
|
||||
if (table.entries == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < table.entries.Count; i++)
|
||||
{
|
||||
GlyphEntry entry = table.entries[i];
|
||||
if (entry == null || entry.Sprite == null || entry.action == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < entry.action.bindings.Count; j++)
|
||||
{
|
||||
RegisterBinding(map, entry.action.bindings[j].path, entry.Sprite);
|
||||
RegisterBinding(map, entry.action.bindings[j].effectivePath, entry.Sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterBinding(Dictionary<string, Sprite> map, string controlPath, Sprite sprite)
|
||||
{
|
||||
string key = NormalizeControlPath(controlPath);
|
||||
if (string.IsNullOrEmpty(key) || map.ContainsKey(key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map[key] = sprite;
|
||||
}
|
||||
|
||||
private static string NormalizeControlPath(string controlPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(controlPath))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return CanonicalizeDeviceLayout(controlPath.Trim().ToLowerInvariant());
|
||||
}
|
||||
|
||||
private static string CanonicalizeDeviceLayout(string controlPath)
|
||||
{
|
||||
int start = controlPath.IndexOf('<');
|
||||
int end = controlPath.IndexOf('>');
|
||||
if (start < 0 || end <= start + 1)
|
||||
{
|
||||
return controlPath;
|
||||
}
|
||||
|
||||
string layout = controlPath.Substring(start + 1, end - start - 1);
|
||||
string canonicalLayout = GetCanonicalLayout(layout);
|
||||
if (string.Equals(layout, canonicalLayout, StringComparison.Ordinal))
|
||||
{
|
||||
return controlPath;
|
||||
}
|
||||
|
||||
return controlPath.Substring(0, start + 1) + canonicalLayout + controlPath.Substring(end);
|
||||
}
|
||||
|
||||
private static string GetCanonicalLayout(string layout)
|
||||
{
|
||||
if (string.IsNullOrEmpty(layout))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (layout.IndexOf("keyboard", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
return "keyboard";
|
||||
}
|
||||
|
||||
if (layout.IndexOf("mouse", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
return "mouse";
|
||||
}
|
||||
|
||||
if (layout.IndexOf("joystick", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
return "joystick";
|
||||
}
|
||||
|
||||
if (layout.IndexOf("gamepad", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| layout.IndexOf("controller", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| layout.IndexOf("xinput", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| layout.IndexOf("dualshock", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| layout.IndexOf("dualsense", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
return "gamepad";
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
private static InputDeviceWatcher.InputDeviceCategory ParseCategory(string deviceName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(deviceName))
|
||||
{
|
||||
return InputDeviceWatcher.InputDeviceCategory.Other;
|
||||
}
|
||||
|
||||
if (deviceName.Equals(DeviceKeyboard, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return InputDeviceWatcher.InputDeviceCategory.Keyboard;
|
||||
}
|
||||
|
||||
if (deviceName.Equals(DeviceXbox, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return InputDeviceWatcher.InputDeviceCategory.Xbox;
|
||||
}
|
||||
|
||||
if (deviceName.Equals(DevicePlayStation, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return InputDeviceWatcher.InputDeviceCategory.PlayStation;
|
||||
}
|
||||
|
||||
return InputDeviceWatcher.InputDeviceCategory.Other;
|
||||
}
|
||||
|
||||
private static InputDeviceWatcher.InputDeviceCategory[] GetLookupOrder(InputDeviceWatcher.InputDeviceCategory device)
|
||||
{
|
||||
switch (device)
|
||||
{
|
||||
case InputDeviceWatcher.InputDeviceCategory.Keyboard:
|
||||
return KeyboardLookupOrder;
|
||||
case InputDeviceWatcher.InputDeviceCategory.Xbox:
|
||||
return XboxLookupOrder;
|
||||
case InputDeviceWatcher.InputDeviceCategory.PlayStation:
|
||||
return PlayStationLookupOrder;
|
||||
default:
|
||||
return OtherLookupOrder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,72 +1,62 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public sealed class InputGlyphImage : MonoBehaviour
|
||||
public sealed class InputGlyphImage : InputGlyphBehaviourBase
|
||||
{
|
||||
[SerializeField] private InputActionReference actionReference;
|
||||
[SerializeField] private string compositePartName;
|
||||
[SerializeField] private Image targetImage;
|
||||
[SerializeField] private bool hideIfMissing = false;
|
||||
[SerializeField] private GameObject hideTargetObject;
|
||||
|
||||
private InputDeviceWatcher.InputDeviceCategory _cachedCategory;
|
||||
private Sprite _cachedSprite;
|
||||
|
||||
/// <summary>
|
||||
/// 启用时初始化组件并订阅设备变更事件
|
||||
/// </summary>
|
||||
void OnEnable()
|
||||
protected override void OnEnable()
|
||||
{
|
||||
if (targetImage == null) targetImage = GetComponent<Image>();
|
||||
InputDeviceWatcher.OnDeviceChanged += OnDeviceChanged;
|
||||
_cachedCategory = InputDeviceWatcher.InputDeviceCategory.Keyboard;
|
||||
UpdatePrompt();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用时取消订阅设备变更事件
|
||||
/// </summary>
|
||||
void OnDisable()
|
||||
{
|
||||
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备类型变更时的回调,更新图标显示
|
||||
/// </summary>
|
||||
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
|
||||
{
|
||||
if (_cachedCategory != cat)
|
||||
if (targetImage == null)
|
||||
{
|
||||
_cachedCategory = cat;
|
||||
UpdatePrompt();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新输入提示图标,并根据配置控制目标对象的显示/隐藏
|
||||
/// </summary>
|
||||
void UpdatePrompt()
|
||||
{
|
||||
if (actionReference == null || actionReference.action == null || targetImage == null) return;
|
||||
|
||||
// 使用缓存的设备类型,避免重复查询 CurrentCategory
|
||||
if (GlyphService.TryGetUISpriteForActionPath(actionReference, "", _cachedCategory, out Sprite sprite))
|
||||
{
|
||||
if (_cachedSprite != sprite)
|
||||
{
|
||||
_cachedSprite = sprite;
|
||||
targetImage.sprite = sprite;
|
||||
}
|
||||
targetImage = GetComponent<Image>();
|
||||
}
|
||||
|
||||
if (hideTargetObject != null)
|
||||
base.OnEnable();
|
||||
}
|
||||
|
||||
protected override void RefreshGlyph()
|
||||
{
|
||||
if (actionReference == null || actionReference.action == null || targetImage == null)
|
||||
{
|
||||
bool shouldBeActive = sprite != null && !hideIfMissing;
|
||||
if (hideTargetObject.activeSelf != shouldBeActive)
|
||||
if (targetImage != null && _cachedSprite != null)
|
||||
{
|
||||
hideTargetObject.SetActive(shouldBeActive);
|
||||
_cachedSprite = null;
|
||||
targetImage.sprite = null;
|
||||
}
|
||||
|
||||
ApplyVisibility(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasSprite = GlyphService.TryGetUISpriteForActionPath(actionReference, compositePartName, CurrentCategory, out Sprite sprite);
|
||||
if (_cachedSprite != sprite)
|
||||
{
|
||||
_cachedSprite = sprite;
|
||||
targetImage.sprite = sprite;
|
||||
}
|
||||
|
||||
ApplyVisibility(hasSprite && sprite != null);
|
||||
}
|
||||
|
||||
private void ApplyVisibility(bool hasSprite)
|
||||
{
|
||||
if (hideTargetObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool shouldBeActive = !hideIfMissing || hasSprite;
|
||||
if (hideTargetObject.activeSelf != shouldBeActive)
|
||||
{
|
||||
hideTargetObject.SetActive(shouldBeActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,75 +1,54 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AlicizaX;
|
||||
using UnityEngine;
|
||||
using AlicizaX;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
[RequireComponent(typeof(TextMeshProUGUI))]
|
||||
public sealed class InputGlyphText : MonoBehaviour
|
||||
public sealed class InputGlyphText : InputGlyphBehaviourBase
|
||||
{
|
||||
[SerializeField] private InputActionReference actionReference;
|
||||
private TMP_Text textField;
|
||||
private string _oldText;
|
||||
private InputDeviceWatcher.InputDeviceCategory _cachedCategory;
|
||||
[SerializeField] private string compositePartName;
|
||||
|
||||
private TMP_Text _textField;
|
||||
private string _templateText;
|
||||
private string _cachedFormattedText;
|
||||
|
||||
/// <summary>
|
||||
/// 启用时初始化组件并订阅设备变更事件
|
||||
/// </summary>
|
||||
void OnEnable()
|
||||
protected override void OnEnable()
|
||||
{
|
||||
if (textField == null) textField = GetComponent<TMP_Text>();
|
||||
InputDeviceWatcher.OnDeviceChanged += OnDeviceChanged;
|
||||
_oldText = textField.text;
|
||||
_cachedCategory = InputDeviceWatcher.InputDeviceCategory.Keyboard;
|
||||
UpdatePrompt();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用时取消订阅设备变更事件
|
||||
/// </summary>
|
||||
void OnDisable()
|
||||
{
|
||||
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备类型变更时的回调,更新文本显示
|
||||
/// </summary>
|
||||
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
|
||||
{
|
||||
if (_cachedCategory != cat)
|
||||
if (_textField == null)
|
||||
{
|
||||
_cachedCategory = cat;
|
||||
UpdatePrompt();
|
||||
_textField = GetComponent<TMP_Text>();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_templateText) && _textField != null)
|
||||
{
|
||||
_templateText = _textField.text;
|
||||
}
|
||||
|
||||
base.OnEnable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新文本中的输入提示标签,使用 TextMeshPro 的 sprite 标签或文本回退
|
||||
/// </summary>
|
||||
void UpdatePrompt()
|
||||
protected override void RefreshGlyph()
|
||||
{
|
||||
if (actionReference == null || actionReference.action == null || textField == null) return;
|
||||
|
||||
// 使用缓存的设备类型,避免重复查询 CurrentCategory
|
||||
if (GlyphService.TryGetTMPTagForActionPath(actionReference, "", _cachedCategory, out string tag, out string displayFallback))
|
||||
if (actionReference == null || actionReference.action == null || _textField == null)
|
||||
{
|
||||
string formattedText = Utility.Text.Format(_oldText, tag);
|
||||
if (_cachedFormattedText != formattedText)
|
||||
{
|
||||
_cachedFormattedText = formattedText;
|
||||
textField.text = formattedText;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
string fallbackText = Utility.Text.Format(_oldText, displayFallback);
|
||||
if (_cachedFormattedText != fallbackText)
|
||||
string formattedText;
|
||||
if (GlyphService.TryGetTMPTagForActionPath(actionReference, compositePartName, CurrentCategory, out string tag, out string displayFallback))
|
||||
{
|
||||
_cachedFormattedText = fallbackText;
|
||||
textField.text = fallbackText;
|
||||
formattedText = Utility.Text.Format(_templateText, tag);
|
||||
}
|
||||
else
|
||||
{
|
||||
formattedText = Utility.Text.Format(_templateText, displayFallback);
|
||||
}
|
||||
|
||||
if (_cachedFormattedText != formattedText)
|
||||
{
|
||||
_cachedFormattedText = formattedText;
|
||||
_textField.text = formattedText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,21 +1,17 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
[RequireComponent(typeof(UXButton))]
|
||||
public sealed class InputGlyphUXButton : MonoBehaviour
|
||||
public sealed class InputGlyphUXButton : InputGlyphBehaviourBase
|
||||
{
|
||||
[SerializeField] private UXButton button;
|
||||
[SerializeField] private string compositePartName;
|
||||
[SerializeField] private Image targetImage;
|
||||
private InputActionReference _actionReference;
|
||||
private InputDeviceWatcher.InputDeviceCategory _cachedCategory;
|
||||
|
||||
private Sprite _cachedSprite;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// 编辑器验证,自动获取 UXButton 组件
|
||||
/// </summary>
|
||||
private void OnValidate()
|
||||
{
|
||||
if (button == null)
|
||||
@ -25,54 +21,45 @@ public sealed class InputGlyphUXButton : MonoBehaviour
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 启用时初始化组件并订阅设备变更事件
|
||||
/// </summary>
|
||||
void OnEnable()
|
||||
protected override void OnEnable()
|
||||
{
|
||||
if (button == null) button = GetComponent<UXButton>();
|
||||
if (targetImage == null) targetImage = GetComponent<Image>();
|
||||
_actionReference = button.HotKeyRefrence;
|
||||
InputDeviceWatcher.OnDeviceChanged += OnDeviceChanged;
|
||||
_cachedCategory = InputDeviceWatcher.InputDeviceCategory.Keyboard;
|
||||
UpdatePrompt();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用时取消订阅设备变更事件
|
||||
/// </summary>
|
||||
void OnDisable()
|
||||
{
|
||||
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备类型变更时的回调,更新图标显示
|
||||
/// </summary>
|
||||
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
|
||||
{
|
||||
if (_cachedCategory != cat)
|
||||
if (button == null)
|
||||
{
|
||||
_cachedCategory = cat;
|
||||
UpdatePrompt();
|
||||
button = GetComponent<UXButton>();
|
||||
}
|
||||
|
||||
if (targetImage == null)
|
||||
{
|
||||
targetImage = GetComponent<Image>();
|
||||
}
|
||||
|
||||
base.OnEnable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新按钮的输入提示图标
|
||||
/// </summary>
|
||||
void UpdatePrompt()
|
||||
protected override void RefreshGlyph()
|
||||
{
|
||||
if (_actionReference == null || _actionReference.action == null || targetImage == null) return;
|
||||
|
||||
// 使用缓存的设备类型,避免重复查询 CurrentCategory
|
||||
if (GlyphService.TryGetUISpriteForActionPath(_actionReference, "", _cachedCategory, out Sprite sprite))
|
||||
InputActionReference actionReference = button != null ? button.HotKeyRefrence : null;
|
||||
if (actionReference == null || actionReference.action == null || targetImage == null)
|
||||
{
|
||||
if (_cachedSprite != sprite)
|
||||
if (targetImage != null && _cachedSprite != null)
|
||||
{
|
||||
_cachedSprite = sprite;
|
||||
targetImage.sprite = sprite;
|
||||
_cachedSprite = null;
|
||||
targetImage.sprite = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasSprite = GlyphService.TryGetUISpriteForActionPath(actionReference, compositePartName, CurrentCategory, out Sprite sprite);
|
||||
if (!hasSprite)
|
||||
{
|
||||
sprite = null;
|
||||
}
|
||||
|
||||
if (_cachedSprite != sprite)
|
||||
{
|
||||
_cachedSprite = sprite;
|
||||
targetImage.sprite = sprite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ public class TestRebindScript : MonoBehaviour
|
||||
{
|
||||
if (btn != null) btn.onClick.AddListener(OnBtnClicked);
|
||||
InputDeviceWatcher.OnDeviceChanged += OnDeviceChanged;
|
||||
InputBindingManager.BindingsChanged += OnBindingsChanged;
|
||||
UpdateBindingText();
|
||||
|
||||
if (InputBindingManager.Instance != null)
|
||||
@ -47,6 +48,7 @@ public class TestRebindScript : MonoBehaviour
|
||||
{
|
||||
if (btn != null) btn.onClick.RemoveListener(OnBtnClicked);
|
||||
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
|
||||
InputBindingManager.BindingsChanged -= OnBindingsChanged;
|
||||
|
||||
if (InputBindingManager.Instance != null)
|
||||
{
|
||||
@ -120,6 +122,11 @@ public class TestRebindScript : MonoBehaviour
|
||||
UpdateBindingText();
|
||||
}
|
||||
|
||||
private void OnBindingsChanged()
|
||||
{
|
||||
UpdateBindingText();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前的输入操作
|
||||
/// </summary>
|
||||
@ -207,6 +214,7 @@ public class TestRebindScript : MonoBehaviour
|
||||
private void UpdateBindingText()
|
||||
{
|
||||
var action = GetAction();
|
||||
var deviceCat = InputDeviceWatcher.CurrentCategory;
|
||||
if (action == null)
|
||||
{
|
||||
bindKeyText.text = "<no action>";
|
||||
@ -215,15 +223,12 @@ public class TestRebindScript : MonoBehaviour
|
||||
}
|
||||
|
||||
|
||||
bindKeyText.text = GlyphService.GetDisplayNameFromInputAction(action, compositePartName);
|
||||
bindKeyText.text = GlyphService.GetDisplayNameFromInputAction(action, compositePartName, deviceCat);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var deviceCat = InputDeviceWatcher.CurrentCategory;
|
||||
InputActionReference refr=default;
|
||||
// string controlPath = GlyphService.GetBindingControlPath(action, compositePartName, deviceCat);
|
||||
if ( GlyphService.TryGetUISpriteForActionPath(action,compositePartName, deviceCat, out Sprite sprite))
|
||||
if (GlyphService.TryGetUISpriteForActionPath(action, compositePartName, deviceCat, out Sprite sprite))
|
||||
{
|
||||
if (targetImage != null) targetImage.sprite = sprite;
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -1 +1 @@
|
||||
Subproject commit c692b47a6249a4364c87ad2ebb6619ff7bf9f80d
|
||||
Subproject commit b9c79e16a50080ba10b08a9b70bb513b36f51d1e
|
||||
@ -14,8 +14,8 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
m_PixelRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 43
|
||||
x: -4
|
||||
y: 51
|
||||
width: 1920
|
||||
height: 997
|
||||
m_ShowMode: 4
|
||||
@ -23,7 +23,7 @@ MonoBehaviour:
|
||||
m_RootView: {fileID: 4}
|
||||
m_MinSize: {x: 875, y: 300}
|
||||
m_MaxSize: {x: 10000, y: 10000}
|
||||
m_Maximized: 1
|
||||
m_Maximized: 0
|
||||
--- !u!114 &2
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 52
|
||||
@ -34,23 +34,23 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_Name: GameView
|
||||
m_Name: AnimatorControllerTool
|
||||
m_EditorClassIdentifier:
|
||||
m_Children: []
|
||||
m_Position:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 566
|
||||
width: 497
|
||||
width: 614
|
||||
height: 381
|
||||
m_MinSize: {x: 51, y: 71}
|
||||
m_MinSize: {x: 101, y: 121}
|
||||
m_MaxSize: {x: 4001, y: 4021}
|
||||
m_ActualView: {fileID: 14}
|
||||
m_ActualView: {fileID: 15}
|
||||
m_Panes:
|
||||
- {fileID: 14}
|
||||
- {fileID: 15}
|
||||
m_Selected: 0
|
||||
m_LastSelected: 1
|
||||
m_Selected: 1
|
||||
m_LastSelected: 0
|
||||
--- !u!114 &3
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 52
|
||||
@ -70,12 +70,12 @@ MonoBehaviour:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 497
|
||||
width: 614
|
||||
height: 947
|
||||
m_MinSize: {x: 100, y: 100}
|
||||
m_MaxSize: {x: 8096, y: 16192}
|
||||
vertical: 1
|
||||
controlID: 18
|
||||
controlID: 398
|
||||
draggingID: 0
|
||||
--- !u!114 &4
|
||||
MonoBehaviour:
|
||||
@ -174,7 +174,7 @@ MonoBehaviour:
|
||||
m_MinSize: {x: 400, y: 100}
|
||||
m_MaxSize: {x: 32384, y: 16192}
|
||||
vertical: 0
|
||||
controlID: 17
|
||||
controlID: 144
|
||||
draggingID: 0
|
||||
--- !u!114 &8
|
||||
MonoBehaviour:
|
||||
@ -193,10 +193,10 @@ MonoBehaviour:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 497
|
||||
width: 614
|
||||
height: 566
|
||||
m_MinSize: {x: 200, y: 200}
|
||||
m_MaxSize: {x: 4000, y: 4000}
|
||||
m_MinSize: {x: 201, y: 221}
|
||||
m_MaxSize: {x: 4001, y: 4021}
|
||||
m_ActualView: {fileID: 16}
|
||||
m_Panes:
|
||||
- {fileID: 16}
|
||||
@ -219,9 +219,9 @@ MonoBehaviour:
|
||||
- {fileID: 11}
|
||||
m_Position:
|
||||
serializedVersion: 2
|
||||
x: 497
|
||||
x: 614
|
||||
y: 0
|
||||
width: 289
|
||||
width: 363
|
||||
height: 947
|
||||
m_MinSize: {x: 100, y: 100}
|
||||
m_MaxSize: {x: 8096, y: 16192}
|
||||
@ -245,7 +245,7 @@ MonoBehaviour:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 289
|
||||
width: 363
|
||||
height: 409
|
||||
m_MinSize: {x: 202, y: 221}
|
||||
m_MaxSize: {x: 4002, y: 4021}
|
||||
@ -271,7 +271,7 @@ MonoBehaviour:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 409
|
||||
width: 289
|
||||
width: 363
|
||||
height: 538
|
||||
m_MinSize: {x: 102, y: 121}
|
||||
m_MaxSize: {x: 4002, y: 4021}
|
||||
@ -295,9 +295,9 @@ MonoBehaviour:
|
||||
m_Children: []
|
||||
m_Position:
|
||||
serializedVersion: 2
|
||||
x: 786
|
||||
x: 977
|
||||
y: 0
|
||||
width: 474
|
||||
width: 369
|
||||
height: 947
|
||||
m_MinSize: {x: 232, y: 271}
|
||||
m_MaxSize: {x: 10002, y: 10021}
|
||||
@ -321,9 +321,9 @@ MonoBehaviour:
|
||||
m_Children: []
|
||||
m_Position:
|
||||
serializedVersion: 2
|
||||
x: 1260
|
||||
x: 1346
|
||||
y: 0
|
||||
width: 660
|
||||
width: 574
|
||||
height: 947
|
||||
m_MinSize: {x: 276, y: 71}
|
||||
m_MaxSize: {x: 4001, y: 4021}
|
||||
@ -450,10 +450,10 @@ MonoBehaviour:
|
||||
m_Tooltip:
|
||||
m_Pos:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 280
|
||||
width: 441
|
||||
height: 719
|
||||
x: -4
|
||||
y: 647
|
||||
width: 613
|
||||
height: 360
|
||||
m_SerializedDataModeController:
|
||||
m_DataMode: 0
|
||||
m_PreferredDataMode: 0
|
||||
@ -520,9 +520,9 @@ MonoBehaviour:
|
||||
m_Tooltip:
|
||||
m_Pos:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 73
|
||||
width: 496
|
||||
x: -4
|
||||
y: 81
|
||||
width: 613
|
||||
height: 545
|
||||
m_SerializedDataModeController:
|
||||
m_DataMode: 0
|
||||
@ -1114,7 +1114,7 @@ MonoBehaviour:
|
||||
m_Rotation:
|
||||
m_Target: {x: -0.21037178, y: -0.10913931, z: 0.02363893, w: -0.97122556}
|
||||
speed: 2
|
||||
m_Value: {x: -0.2103712, y: -0.10913901, z: 0.023638865, w: -0.9712229}
|
||||
m_Value: {x: -0.21037178, y: -0.10913931, z: 0.023638932, w: -0.9712256}
|
||||
m_Size:
|
||||
m_Target: 1.0281526
|
||||
speed: 2
|
||||
@ -1163,9 +1163,9 @@ MonoBehaviour:
|
||||
m_Tooltip:
|
||||
m_Pos:
|
||||
serializedVersion: 2
|
||||
x: 497
|
||||
y: 73
|
||||
width: 287
|
||||
x: 610
|
||||
y: 81
|
||||
width: 361
|
||||
height: 388
|
||||
m_SerializedDataModeController:
|
||||
m_DataMode: 0
|
||||
@ -1180,9 +1180,9 @@ MonoBehaviour:
|
||||
m_SceneHierarchy:
|
||||
m_TreeViewState:
|
||||
scrollPos: {x: 0, y: 0}
|
||||
m_SelectedIDs: 521b0000
|
||||
m_SelectedIDs: d6700000
|
||||
m_LastClickedID: 0
|
||||
m_ExpandedIDs: c058ffff
|
||||
m_ExpandedIDs: 28fbffff
|
||||
m_RenameOverlay:
|
||||
m_UserAcceptedRename: 0
|
||||
m_Name:
|
||||
@ -1226,9 +1226,9 @@ MonoBehaviour:
|
||||
m_Tooltip:
|
||||
m_Pos:
|
||||
serializedVersion: 2
|
||||
x: 497
|
||||
y: 482
|
||||
width: 287
|
||||
x: 610
|
||||
y: 490
|
||||
width: 361
|
||||
height: 517
|
||||
m_SerializedDataModeController:
|
||||
m_DataMode: 0
|
||||
@ -1260,9 +1260,9 @@ MonoBehaviour:
|
||||
m_Tooltip:
|
||||
m_Pos:
|
||||
serializedVersion: 2
|
||||
x: 786
|
||||
y: 73
|
||||
width: 472
|
||||
x: 973
|
||||
y: 81
|
||||
width: 367
|
||||
height: 926
|
||||
m_SerializedDataModeController:
|
||||
m_DataMode: 0
|
||||
@ -1285,7 +1285,7 @@ MonoBehaviour:
|
||||
m_SkipHidden: 0
|
||||
m_SearchArea: 2
|
||||
m_Folders:
|
||||
- Packages/com.alicizax.unity.framework/Runtime/UI/Manager
|
||||
- Assets/Plugins/UnityEditorDarkMode
|
||||
m_Globs: []
|
||||
m_OriginalText:
|
||||
m_ImportLogFlags: 0
|
||||
@ -1301,7 +1301,7 @@ MonoBehaviour:
|
||||
scrollPos: {x: 0, y: 0}
|
||||
m_SelectedIDs: e48c0000
|
||||
m_LastClickedID: 36068
|
||||
m_ExpandedIDs: 00000000c40a000044a8000046a8000048a800004aa800004ca800004ea8000050a8000052a8000054a8000056a8000058a800005aa800005ca800005ea8000060a8000062a8000064a8000066a8000068a800006aa800006ca800006ea8000070a8000072a8000074a8000076a8000078a800007aa800007ca800007ea8000080a8000082a8000084a8000086a8000088a800008aa800008ca800008ea8000090a8000092a8000094a8000096a8000098a800009aa800009ca800009ea80000a0a80000a2a80000a4a80000
|
||||
m_ExpandedIDs: 000000007e0200008a0d0000786d00007a6d00007c6d00007e6d0000806d0000826d0000846d0000866d0000886d00008a6d00008c6d00008e6d0000906d0000926d0000946d0000966d0000986d00009a6d00009c6d00009e6d0000a06d0000a26d0000a46d0000a66d0000a86d0000aa6d0000ac6d0000ae6d0000b06d0000b26d0000b46d0000b66d0000b86d0000ba6d0000bc6d0000be6d0000c06d0000c26d0000c46d0000c66d0000c86d0000ca6d0000cc6d0000ce6d0000d06d0000d26d0000d46d0000d66d0000d86d0000da6d0000dc6d0000de6d0000e06d0000e26d0000e46d0000e66d0000e86d0000ea6d0000ec6d0000ee6d0000f06d0000f26d0000f46d0000
|
||||
m_RenameOverlay:
|
||||
m_UserAcceptedRename: 0
|
||||
m_Name:
|
||||
@ -1326,24 +1326,24 @@ MonoBehaviour:
|
||||
m_Icon: {fileID: 0}
|
||||
m_ResourceFile:
|
||||
m_AssetTreeState:
|
||||
scrollPos: {x: 0, y: 300}
|
||||
scrollPos: {x: 0, y: 0}
|
||||
m_SelectedIDs:
|
||||
m_LastClickedID: 0
|
||||
m_ExpandedIDs: ffffffff000000007e020000c40a000044a8000046a8000048a800004aa800004ca800004ea8000050a8000052a8000054a8000056a800005aa800005ca800005ea8000060a8000062a8000064a8000066a8000068a800006aa800006ca800006ea8000070a8000072a8000074a8000076a8000078a800007aa800007ca800007ea8000080a8000082a8000084a8000086a8000088a800008aa800008ca800008ea8000090a8000092a8000094a8000096a8000098a800009aa800009ca80000a0a80000b0a8000048a90000e2a90000e6a900000aaa00000eaa000010aa000012aa000024aa000036aa000040aa00005eaa000060aa000066ab000072ad000074ad000092ad000094ad0000ffffff7f
|
||||
m_ExpandedIDs: ffffffff000000007e020000786d00007a6d00007c6d00007e6d0000806d0000826d0000846d0000866d0000886d00008a6d00008c6d00008e6d0000906d0000926d0000946d0000966d0000986d00009a6d00009c6d00009e6d0000a06d0000a26d0000a46d0000a66d0000a86d0000aa6d0000ac6d0000ae6d0000b06d0000b46d0000b66d0000b86d0000ba6d0000bc6d0000be6d0000c06d0000c26d0000c46d0000c66d0000c86d0000ca6d0000cc6d0000ce6d0000d06d0000d46d0000d66d0000d86d0000da6d0000dc6d0000de6d0000e06d0000e26d0000e66d0000e86d0000ea6d0000ec6d0000ee6d0000f06d0000f26d0000f46d0000f06f000008700000cc700000
|
||||
m_RenameOverlay:
|
||||
m_UserAcceptedRename: 0
|
||||
m_Name: TimerModule
|
||||
m_OriginalName: TimerModule
|
||||
m_Name:
|
||||
m_OriginalName:
|
||||
m_EditFieldRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 0
|
||||
height: 0
|
||||
m_UserData: 5676
|
||||
m_UserData: 0
|
||||
m_IsWaitingForDelay: 0
|
||||
m_IsRenaming: 0
|
||||
m_OriginalEventType: 0
|
||||
m_OriginalEventType: 11
|
||||
m_IsRenamingFilename: 1
|
||||
m_ClientGUIView: {fileID: 12}
|
||||
m_SearchString:
|
||||
@ -1405,9 +1405,9 @@ MonoBehaviour:
|
||||
m_Tooltip:
|
||||
m_Pos:
|
||||
serializedVersion: 2
|
||||
x: 1260
|
||||
y: 73
|
||||
width: 659
|
||||
x: 1342
|
||||
y: 81
|
||||
width: 573
|
||||
height: 926
|
||||
m_SerializedDataModeController:
|
||||
m_DataMode: 0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user