SmartBowDeviceHub.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. using System;
  2. using System.Collections;
  3. using AppUI.Manager.View;
  4. using SmartBowSDK;
  5. using UnityEngine;
  6. using WildAttack;
  7. namespace AppUI.Bluetooth
  8. {
  9. /// <summary>
  10. /// BluetoothHolder 统一入口:蓝牙、设备档案及后续红外等 SDK 能力。
  11. /// AppUI 层请通过 <see cref="ins"/> 访问,不要直接引用 BluetoothAim / AimHandler 单例。
  12. /// </summary>
  13. [DefaultExecutionOrder(-100)]
  14. public class SmartBowDeviceHub : MonoBehaviour
  15. {
  16. public static SmartBowDeviceHub ins;
  17. public SmartBowDeviceState State { get; private set; }
  18. public event Action<SmartBowDeviceState> OnStateChanged;
  19. BluetoothAim _ble;
  20. AimHandler _aim;
  21. SmartBowHelper _subscribed1P;
  22. SmartBowHelper _subscribed2P;
  23. Coroutine _postInitBatteryRefresh;
  24. bool _isScreenPositioned;
  25. [Tooltip("九轴校准数据走游戏 WebSocket/业务服(SDK Host 模式);关闭则使用 SDK 默认 Remote HTTP。")]
  26. [SerializeField] bool useHostCalibrateStorage = true;
  27. [Header("设备展示(DeviceView 等设备图/标题)")]
  28. [SerializeField] DeviceTypeDisplayEntry[] deviceDisplayEntries;
  29. public bool IsReady => _ble != null && _aim != null;
  30. /// <summary>设备档案(1P/2P 本地配置)。</summary>
  31. public AimHandler Aim => _aim;
  32. /// <summary>蓝牙连接模块;AppUI 业务请用 Hub 公开 API,勿在 View 层直接访问。</summary>
  33. public BluetoothAim Ble => _ble;
  34. public bool NeedDecryption
  35. {
  36. get => _ble != null && _ble.NeedDecryption;
  37. set { if (_ble != null) _ble.NeedDecryption = value; }
  38. }
  39. /// <summary>确保 BluetoothHolder 已实例化(DontDestroyOnLoad)。</summary>
  40. public static void EnsureReady()
  41. {
  42. if (ins != null) return;
  43. GameObject prefab = ViewManager.GetPrefabByType(UIPrefabType.SmartBowDeviceHub);
  44. if (prefab != null)
  45. Instantiate(prefab);
  46. }
  47. void Awake()
  48. {
  49. if (ins != null && ins != this)
  50. {
  51. Destroy(gameObject);
  52. return;
  53. }
  54. ins = this;
  55. DontDestroyOnLoad(gameObject);
  56. _ble = GetComponentInChildren<BluetoothAim>(true);
  57. _aim = GetComponentInChildren<AimHandler>(true);
  58. if (_ble == null || _aim == null)
  59. Debug.LogError("[SmartBowDeviceHub] 预制体缺少子节点 BluetoothAim 或 AimHandler。");
  60. }
  61. void Start()
  62. {
  63. if (_aim != null)
  64. _aim.aimDeviceInfoChangeEvent += OnDeviceInfoChanged;
  65. RegisterExistingHelpers();
  66. RefreshState();
  67. }
  68. void OnDestroy()
  69. {
  70. UnsubscribeHelper(ref _subscribed1P);
  71. UnsubscribeHelper(ref _subscribed2P);
  72. if (_aim != null)
  73. _aim.aimDeviceInfoChangeEvent -= OnDeviceInfoChanged;
  74. if (ins == this) ins = null;
  75. }
  76. void OnDeviceInfoChanged() => RefreshState();
  77. public void RegisterHelper1P(SmartBowHelper helper)
  78. {
  79. if (helper == null) return;
  80. if (_subscribed1P == helper) return;
  81. UnsubscribeHelper(ref _subscribed1P);
  82. _subscribed1P = helper;
  83. _subscribed1P.OnBluetoothStatusChanged += OnHelperStatusChanged;
  84. _subscribed1P.OnBluetoothModuleInited += OnHelperModuleInited;
  85. ConfigureHelperCalibrateStorage(_subscribed1P);
  86. SyncArrowWeights(BluetoothPlayer.FIRST_PLAYER);
  87. RefreshState();
  88. }
  89. public void RegisterHelper2P(SmartBowHelper helper)
  90. {
  91. if (helper == null) return;
  92. if (_subscribed2P == helper) return;
  93. UnsubscribeHelper(ref _subscribed2P);
  94. _subscribed2P = helper;
  95. _subscribed2P.OnBluetoothStatusChanged += OnHelperStatusChanged;
  96. _subscribed2P.OnBluetoothModuleInited += OnHelperModuleInited;
  97. ConfigureHelperCalibrateStorage(_subscribed2P);
  98. SyncArrowWeights(BluetoothPlayer.SECOND_PLAYER);
  99. RefreshState();
  100. }
  101. void RegisterExistingHelpers()
  102. {
  103. if (_ble == null) return;
  104. RegisterHelper1P(_ble.getSmartBowHelper1P());
  105. RegisterHelper2P(_ble.getSmartBowHelper2P());
  106. }
  107. void OnHelperStatusChanged(SmartBowSDK.BluetoothStatusEnum oldStatus, SmartBowSDK.BluetoothStatusEnum newStatus) => RefreshState();
  108. /// <summary>模块初始化完成(含首次电量请求)后刷新 Hub 快照,供 SideSlip / DeviceView 等订阅方更新电量。</summary>
  109. void OnHelperModuleInited()
  110. {
  111. RefreshState();
  112. if (_postInitBatteryRefresh != null)
  113. StopCoroutine(_postInitBatteryRefresh);
  114. _postInitBatteryRefresh = StartCoroutine(RefreshStateAfterBatteryDelay());
  115. }
  116. IEnumerator RefreshStateAfterBatteryDelay()
  117. {
  118. // InitWhenConnected 发完 "b" 后电量包可能略晚于 OnBluetoothModuleInited 回调
  119. yield return new WaitForSecondsRealtime(0.5f);
  120. RefreshState();
  121. yield return new WaitForSecondsRealtime(1f);
  122. RefreshState();
  123. _postInitBatteryRefresh = null;
  124. }
  125. public void NotifyStateChanged() => RefreshState();
  126. public void RefreshState()
  127. {
  128. SmartBowDeviceState next = BuildState();
  129. if (State.Equals(next)) return;
  130. State = next;
  131. try { OnStateChanged?.Invoke(State); }
  132. catch (Exception e) { Debug.LogError(e); }
  133. }
  134. SmartBowDeviceState BuildState()
  135. {
  136. SmartBowHelper helper1P = _ble != null ? _ble.getSmartBowHelper1P() : null;
  137. SmartBowHelper helper2P = _ble != null ? _ble.getSmartBowHelper2P() : null;
  138. return new SmartBowDeviceState
  139. {
  140. IsHubReady = IsReady,
  141. ActivePlayer = _ble != null ? _ble.getBLEPlayer() : BluetoothPlayer.FIRST_PLAYER,
  142. Status1P = helper1P != null ? helper1P.GetBluetoothStatus() : SmartBowSDK.BluetoothStatusEnum.None,
  143. Status2P = helper2P != null ? helper2P.GetBluetoothStatus() : SmartBowSDK.BluetoothStatusEnum.None,
  144. Battery1P = helper1P != null ? helper1P.GetBattery() : 0,
  145. Battery2P = helper2P != null ? helper2P.GetBattery() : _ble?.get2PBattery() ?? 0,
  146. IsScreenPositioned = _isScreenPositioned,
  147. };
  148. }
  149. public void SetScreenPositioned(bool positioned)
  150. {
  151. if (_isScreenPositioned == positioned) return;
  152. _isScreenPositioned = positioned;
  153. RefreshState();
  154. }
  155. public BluetoothPlayer GetActivePlayer() => _ble != null ? _ble.getBLEPlayer() : BluetoothPlayer.FIRST_PLAYER;
  156. public void SetActivePlayer(BluetoothPlayer player)
  157. {
  158. _ble?.setBLEPlayer(player);
  159. RefreshState();
  160. }
  161. public SmartBowSDK.BluetoothStatusEnum GetSdkStatus(BluetoothPlayer player)
  162. {
  163. return State.GetSdkStatus(player);
  164. }
  165. public bool IsConnected(BluetoothPlayer player) => State.IsPlayerConnected(player);
  166. public bool IsConnecting(BluetoothPlayer player) => State.IsPlayerConnecting(player);
  167. public int GetBattery(BluetoothPlayer player)
  168. {
  169. return player == BluetoothPlayer.SECOND_PLAYER ? State.Battery2P : State.Battery1P;
  170. }
  171. public string GetStatusTextKey(BluetoothPlayer player)
  172. {
  173. return State.GetBLE2StatusTextKey(GetSdkStatus(player));
  174. }
  175. public void Connect1P() => _ble?.DoConnect();
  176. public void Connect2P() => _ble?.DoConnect2P();
  177. public void ConnectActivePlayer()
  178. {
  179. if (_ble == null) return;
  180. if (GetActivePlayer() == BluetoothPlayer.SECOND_PLAYER)
  181. Connect2P();
  182. else
  183. Connect1P();
  184. }
  185. public void CancelConnecting(global::BluetoothStatusEnum status = global::BluetoothStatusEnum.ConnectFail)
  186. {
  187. _ble?.onCancelAllConnecting(status);
  188. RefreshState();
  189. }
  190. /// <summary>DeviceView 手动选型/连接时抑制 HomeView 延迟自动连接,避免与 SideSlip 冲突。</summary>
  191. public void SetSuppressHomeAutoConnect(bool suppress) => _ble?.SetSuppressHomeAutoConnect(suppress);
  192. /// <summary>兼容旧版 onCancelAllConnecting 无参调用。</summary>
  193. public void CancelConnecting() => CancelConnecting(global::BluetoothStatusEnum.ConnectFail);
  194. public void SetConnectCanceled(bool canceled)
  195. {
  196. if (_ble != null) _ble.connectCanceled = canceled;
  197. }
  198. public void Set2PNoNeedToReconnect(bool value)
  199. {
  200. if (_ble != null) _ble.bNoNeedToReconnect = value;
  201. }
  202. public SmartBowHelper GetHelper(BluetoothPlayer player)
  203. {
  204. if (_ble == null) return null;
  205. return player == BluetoothPlayer.SECOND_PLAYER ? _ble.getSmartBowHelper2P() : _ble.getSmartBowHelper1P();
  206. }
  207. public void SubscribeShooting(BluetoothPlayer player, SmartBowHelper.ShootingEvent handler)
  208. {
  209. SmartBowHelper helper = GetHelper(player);
  210. if (helper == null || handler == null) return;
  211. helper.OnShooting += handler;
  212. }
  213. public void UnsubscribeShooting(BluetoothPlayer player, SmartBowHelper.ShootingEvent handler)
  214. {
  215. SmartBowHelper helper = GetHelper(player);
  216. if (helper == null || handler == null) return;
  217. helper.OnShooting -= handler;
  218. }
  219. public void SubscribeBleDeviceState(BluetoothAim.BleDeviceEvent handler)
  220. {
  221. if (_ble == null || handler == null) return;
  222. _ble.OnBleDeviceState += handler;
  223. }
  224. public BluetoothDeviceStatus GetBleDeviceState() {
  225. if (_aim == null) return BluetoothDeviceStatus.None;
  226. return _aim.bluetoothDeviceStatus;
  227. }
  228. public BluetoothDeviceStatus SetBleDeviceState(BluetoothDeviceStatus deviceStatus)
  229. {
  230. if (_aim == null) return BluetoothDeviceStatus.None;
  231. _aim.bluetoothDeviceStatus = deviceStatus;
  232. return _aim.bluetoothDeviceStatus;
  233. }
  234. public void UnsubscribeBleDeviceState(BluetoothAim.BleDeviceEvent handler)
  235. {
  236. if (_ble == null || handler == null) return;
  237. _ble.OnBleDeviceState -= handler;
  238. }
  239. public void SubscribeDeviceAndSystemInfoEvent(BluetoothAim.DeviceAndSystemInfoEvent handler)
  240. {
  241. if (_ble == null || handler == null) return;
  242. _ble.OnDeviceAndSystemInfoEvent += handler;
  243. }
  244. public void UnsubscribeDeviceAndSystemInfoEvent(BluetoothAim.DeviceAndSystemInfoEvent handler)
  245. {
  246. if (_ble == null || handler == null) return;
  247. _ble.OnDeviceAndSystemInfoEvent -= handler;
  248. }
  249. /// <summary>触发一次当前缓存的设备类型 / 连接平台信息。</summary>
  250. public void NotifyDeviceAndSystemInfo() => _ble?.GetDeviceAndSystemInfoEvent();
  251. /// <summary>
  252. /// 将 <see cref="UserSettings.ins.actualArrowWeight"/> 与 <see cref="CommonConfig.arrowWeight"/>
  253. /// 同步到 SDK 红外射箭速度换算(1P + 2P)。
  254. /// </summary>
  255. public void SyncArrowWeights()
  256. {
  257. SyncArrowWeights(BluetoothPlayer.FIRST_PLAYER);
  258. SyncArrowWeights(BluetoothPlayer.SECOND_PLAYER);
  259. }
  260. /// <summary>
  261. /// 将当前用户箭重设置同步到指定玩家的 SDK Helper。
  262. /// </summary>
  263. public void SyncArrowWeights(BluetoothPlayer player)
  264. {
  265. SmartBowHelper helper = GetHelper(player);
  266. if (helper == null) return;
  267. float gameWeight = UserSettings.ins != null ? UserSettings.ins.actualArrowWeight : 20f;
  268. float solidWeight = CommonConfig.arrowWeight > 0f ? CommonConfig.arrowWeight : 75f;
  269. helper.SetGameArrowWeight(gameWeight);
  270. helper.SetSolidArrowWeight(solidWeight);
  271. }
  272. /**
  273. * // 分开设置
  274. hub.SetGameArrowWeight(BluetoothPlayer.FIRST_PLAYER, 27f);
  275. hub.SetSolidArrowWeight(BluetoothPlayer.FIRST_PLAYER, 75f);
  276. // 读取
  277. float game = hub.GetGameArrowWeight(BluetoothPlayer.FIRST_PLAYER);
  278. float solid = hub.GetSolidArrowWeight(BluetoothPlayer.FIRST_PLAYER);
  279. */
  280. /// <summary>设置指定玩家 SDK 游戏里箭重(克)。</summary>
  281. public void SetGameArrowWeight(BluetoothPlayer player, float gameArrowWeight)
  282. {
  283. GetHelper(player)?.SetGameArrowWeight(gameArrowWeight);
  284. }
  285. /// <summary>设置指定玩家 SDK 实体模具箭重(克)。</summary>
  286. public void SetSolidArrowWeight(BluetoothPlayer player, float solidArrowWeight)
  287. {
  288. GetHelper(player)?.SetSolidArrowWeight(solidArrowWeight);
  289. }
  290. /// <summary>读取指定玩家 SDK 游戏里箭重;Helper 未就绪时回退 UserSettings。</summary>
  291. public float GetGameArrowWeight(BluetoothPlayer player)
  292. {
  293. SmartBowHelper helper = GetHelper(player);
  294. if (helper != null) return helper.GetGameArrowWeight();
  295. return UserSettings.ins != null ? UserSettings.ins.actualArrowWeight : 20f;
  296. }
  297. /// <summary>读取指定玩家 SDK 实体模具箭重;Helper 未就绪时回退 CommonConfig。</summary>
  298. public float GetSolidArrowWeight(BluetoothPlayer player)
  299. {
  300. SmartBowHelper helper = GetHelper(player);
  301. if (helper != null) return helper.GetSolidArrowWeight();
  302. return CommonConfig.arrowWeight > 0f ? CommonConfig.arrowWeight : 75f;
  303. }
  304. public float GetArrowSpeed(BluetoothPlayer player)
  305. {
  306. SmartBowHelper helper = GetHelper(player);
  307. if (helper == null) return 0f;
  308. return helper.GetArrowSpeed();
  309. }
  310. /// <summary>模块是否初始化完成(Demo:此后再调传感器/校准 API)。</summary>
  311. public bool IsModuleInited(BluetoothPlayer player)
  312. {
  313. SmartBowHelper helper = GetHelper(player);
  314. return helper != null && helper.IsBluetoothModuleInited();
  315. }
  316. /// <summary>
  317. /// 对齐 DemoStarter.OnBluetoothModuleInited:开启九轴 + 射箭传感。
  318. /// 连接态 Connected 但 moduleInited 未完成时返回 false。
  319. /// </summary>
  320. public bool EnsureNineAxisSensorsStarted(BluetoothPlayer? player = null)
  321. {
  322. if (_ble == null) return false;
  323. BluetoothPlayer p = player ?? GetActivePlayer();
  324. SmartBowHelper helper = GetHelper(p);
  325. if (helper == null) return false;
  326. if (helper.GetBluetoothStatus() != SmartBowSDK.BluetoothStatusEnum.Connected)
  327. return false;
  328. if (!helper.IsBluetoothModuleInited())
  329. return false;
  330. SyncArrowWeights(p);
  331. helper.StartRotationSensor();
  332. helper.StartShootingSensor();
  333. return true;
  334. }
  335. public bool IsMainConnectToInfraredDevice() =>
  336. IsMainConnectToHOUYIPRO() || IsMainConnectToARTEMISPRO();
  337. /// <summary>1P 档案为 HOUYIPRO 且已连接(<see cref="CommonConfig.bDisableBluetooth"/> 时仅看设备类型)。</summary>
  338. public bool IsMainConnectToHOUYIPRO() => IsMainConnectToDeviceType(AimDeviceType.HOUYIPRO);
  339. /// <summary>1P 档案为 ARTEMISPRO 且已连接(<see cref="CommonConfig.bDisableBluetooth"/> 时仅看设备类型)。</summary>
  340. public bool IsMainConnectToARTEMISPRO() => IsMainConnectToDeviceType(AimDeviceType.ARTEMISPRO);
  341. /// <summary>1P 档案为枪类设备且已连接(<see cref="CommonConfig.bDisableBluetooth"/> 时仅看设备类型)。</summary>
  342. public bool IsMainConnectToGun()
  343. {
  344. return GetMainConnectGunType().isGun;
  345. }
  346. /// <summary>1P 枪类连接信息:是否满足枪模式 + 具体枪型。</summary>
  347. public (bool isGun, AimDeviceType gunType) GetMainConnectGunType()
  348. {
  349. AimDeviceType gunType = AimDeviceType.NONE;
  350. if (_aim == null)
  351. return (false, gunType);
  352. int type = _aim.GetDeviceType(BluetoothPlayer.FIRST_PLAYER);
  353. if (!IsGunDeviceType(type))
  354. return (false, gunType);
  355. gunType = (AimDeviceType)type;
  356. if (CommonConfig.bDisableBluetooth)
  357. return (true, gunType);
  358. return (IsConnected(BluetoothPlayer.FIRST_PLAYER), gunType);
  359. }
  360. static bool IsGunDeviceType(int type) =>
  361. type == (int)AimDeviceType.Gun
  362. || type == (int)AimDeviceType.PistolM17
  363. || type == (int)AimDeviceType.RifleM416;
  364. bool IsMainConnectToDeviceType(AimDeviceType deviceType)
  365. {
  366. if (_aim == null)
  367. return false;
  368. if (_aim.GetDeviceType(BluetoothPlayer.FIRST_PLAYER) != (int)deviceType)
  369. return false;
  370. if (CommonConfig.bDisableBluetooth)
  371. return true;
  372. return IsConnected(BluetoothPlayer.FIRST_PLAYER);
  373. }
  374. void UnsubscribeHelper(ref SmartBowHelper helper)
  375. {
  376. if (helper == null) return;
  377. SdkCalibrateHostBridge.Unbind(helper);
  378. helper.OnBluetoothStatusChanged -= OnHelperStatusChanged;
  379. helper.OnBluetoothModuleInited -= OnHelperModuleInited;
  380. helper = null;
  381. }
  382. void ConfigureHelperCalibrateStorage(SmartBowHelper helper)
  383. {
  384. if (helper == null) return;
  385. if (useHostCalibrateStorage)
  386. SdkCalibrateHostBridge.Bind(helper);
  387. else
  388. {
  389. SdkCalibrateHostBridge.Unbind(helper);
  390. helper.calibrateDataStorageMode = CalibrateDataStorageMode.Remote;
  391. }
  392. }
  393. /// <summary>按 <see cref="AimDeviceType"/> 取 DeviceView 等设备展示图。</summary>
  394. public Sprite GetDeviceDisplaySprite(AimDeviceType deviceType) =>
  395. TryGetDeviceDisplay(deviceType, out Sprite sprite, out _) ? sprite : null;
  396. /// <summary>按 <see cref="AimDeviceType"/> 取 DeviceView 等设备展示标题。</summary>
  397. public string GetDeviceDisplayName(AimDeviceType deviceType) =>
  398. TryGetDeviceDisplay(deviceType, out _, out string displayName) ? displayName : deviceType.ToString();
  399. public bool TryGetDeviceDisplay(AimDeviceType deviceType, out Sprite sprite, out string displayName)
  400. {
  401. sprite = null;
  402. displayName = null;
  403. if (deviceDisplayEntries == null) return false;
  404. for (int i = 0; i < deviceDisplayEntries.Length; i++)
  405. {
  406. DeviceTypeDisplayEntry entry = deviceDisplayEntries[i];
  407. if (entry == null || entry.deviceType != deviceType) continue;
  408. sprite = entry.sprite;
  409. displayName = string.IsNullOrEmpty(entry.displayName)
  410. ? deviceType.ToString()
  411. : entry.displayName;
  412. return sprite != null || !string.IsNullOrEmpty(displayName);
  413. }
  414. return false;
  415. }
  416. public void ResetAim(BluetoothPlayer player) => GetHelper(player)?.ResetAim();
  417. public void SetMainConnectDeviceType() => _ble.SetMainConnectDeviceType();
  418. }
  419. }