using System; using System.Collections; using AppUI.Manager.View; using SmartBowSDK; using UnityEngine; using WildAttack; namespace AppUI.Bluetooth { /// /// BluetoothHolder 统一入口:蓝牙、设备档案及后续红外等 SDK 能力。 /// AppUI 层请通过 访问,不要直接引用 BluetoothAim / AimHandler 单例。 /// [DefaultExecutionOrder(-100)] public class SmartBowDeviceHub : MonoBehaviour { public static SmartBowDeviceHub ins; public SmartBowDeviceState State { get; private set; } public event Action OnStateChanged; BluetoothAim _ble; AimHandler _aim; SmartBowHelper _subscribed1P; SmartBowHelper _subscribed2P; Coroutine _postInitBatteryRefresh; bool _isScreenPositioned; [Tooltip("九轴校准数据走游戏 WebSocket/业务服(SDK Host 模式);关闭则使用 SDK 默认 Remote HTTP。")] [SerializeField] bool useHostCalibrateStorage = true; [Header("设备展示(DeviceView 等设备图/标题)")] [SerializeField] DeviceTypeDisplayEntry[] deviceDisplayEntries; public bool IsReady => _ble != null && _aim != null; /// 设备档案(1P/2P 本地配置)。 public AimHandler Aim => _aim; /// 蓝牙连接模块;AppUI 业务请用 Hub 公开 API,勿在 View 层直接访问。 public BluetoothAim Ble => _ble; public bool NeedDecryption { get => _ble != null && _ble.NeedDecryption; set { if (_ble != null) _ble.NeedDecryption = value; } } /// 确保 BluetoothHolder 已实例化(DontDestroyOnLoad)。 public static void EnsureReady() { if (ins != null) return; GameObject prefab = ViewManager.GetPrefabByType(UIPrefabType.SmartBowDeviceHub); if (prefab != null) Instantiate(prefab); } void Awake() { if (ins != null && ins != this) { Destroy(gameObject); return; } ins = this; DontDestroyOnLoad(gameObject); _ble = GetComponentInChildren(true); _aim = GetComponentInChildren(true); if (_ble == null || _aim == null) Debug.LogError("[SmartBowDeviceHub] 预制体缺少子节点 BluetoothAim 或 AimHandler。"); } void Start() { if (_aim != null) _aim.aimDeviceInfoChangeEvent += OnDeviceInfoChanged; RegisterExistingHelpers(); RefreshState(); } void OnDestroy() { UnsubscribeHelper(ref _subscribed1P); UnsubscribeHelper(ref _subscribed2P); if (_aim != null) _aim.aimDeviceInfoChangeEvent -= OnDeviceInfoChanged; if (ins == this) ins = null; } void OnDeviceInfoChanged() => RefreshState(); public void RegisterHelper1P(SmartBowHelper helper) { if (helper == null) return; if (_subscribed1P == helper) return; UnsubscribeHelper(ref _subscribed1P); _subscribed1P = helper; _subscribed1P.OnBluetoothStatusChanged += OnHelperStatusChanged; _subscribed1P.OnBluetoothModuleInited += OnHelperModuleInited; ConfigureHelperCalibrateStorage(_subscribed1P); SyncArrowWeights(BluetoothPlayer.FIRST_PLAYER); RefreshState(); } public void RegisterHelper2P(SmartBowHelper helper) { if (helper == null) return; if (_subscribed2P == helper) return; UnsubscribeHelper(ref _subscribed2P); _subscribed2P = helper; _subscribed2P.OnBluetoothStatusChanged += OnHelperStatusChanged; _subscribed2P.OnBluetoothModuleInited += OnHelperModuleInited; ConfigureHelperCalibrateStorage(_subscribed2P); SyncArrowWeights(BluetoothPlayer.SECOND_PLAYER); RefreshState(); } void RegisterExistingHelpers() { if (_ble == null) return; RegisterHelper1P(_ble.getSmartBowHelper1P()); RegisterHelper2P(_ble.getSmartBowHelper2P()); } void OnHelperStatusChanged(SmartBowSDK.BluetoothStatusEnum oldStatus, SmartBowSDK.BluetoothStatusEnum newStatus) => RefreshState(); /// 模块初始化完成(含首次电量请求)后刷新 Hub 快照,供 SideSlip / DeviceView 等订阅方更新电量。 void OnHelperModuleInited() { RefreshState(); if (_postInitBatteryRefresh != null) StopCoroutine(_postInitBatteryRefresh); _postInitBatteryRefresh = StartCoroutine(RefreshStateAfterBatteryDelay()); } IEnumerator RefreshStateAfterBatteryDelay() { // InitWhenConnected 发完 "b" 后电量包可能略晚于 OnBluetoothModuleInited 回调 yield return new WaitForSecondsRealtime(0.5f); RefreshState(); yield return new WaitForSecondsRealtime(1f); RefreshState(); _postInitBatteryRefresh = null; } public void NotifyStateChanged() => RefreshState(); public void RefreshState() { SmartBowDeviceState next = BuildState(); if (State.Equals(next)) return; State = next; try { OnStateChanged?.Invoke(State); } catch (Exception e) { Debug.LogError(e); } } SmartBowDeviceState BuildState() { SmartBowHelper helper1P = _ble != null ? _ble.getSmartBowHelper1P() : null; SmartBowHelper helper2P = _ble != null ? _ble.getSmartBowHelper2P() : null; return new SmartBowDeviceState { IsHubReady = IsReady, ActivePlayer = _ble != null ? _ble.getBLEPlayer() : BluetoothPlayer.FIRST_PLAYER, Status1P = helper1P != null ? helper1P.GetBluetoothStatus() : SmartBowSDK.BluetoothStatusEnum.None, Status2P = helper2P != null ? helper2P.GetBluetoothStatus() : SmartBowSDK.BluetoothStatusEnum.None, Battery1P = helper1P != null ? helper1P.GetBattery() : 0, Battery2P = helper2P != null ? helper2P.GetBattery() : _ble?.get2PBattery() ?? 0, IsScreenPositioned = _isScreenPositioned, }; } public void SetScreenPositioned(bool positioned) { if (_isScreenPositioned == positioned) return; _isScreenPositioned = positioned; RefreshState(); } public BluetoothPlayer GetActivePlayer() => _ble != null ? _ble.getBLEPlayer() : BluetoothPlayer.FIRST_PLAYER; public void SetActivePlayer(BluetoothPlayer player) { _ble?.setBLEPlayer(player); RefreshState(); } public SmartBowSDK.BluetoothStatusEnum GetSdkStatus(BluetoothPlayer player) { return State.GetSdkStatus(player); } public bool IsConnected(BluetoothPlayer player) => State.IsPlayerConnected(player); public bool IsConnecting(BluetoothPlayer player) => State.IsPlayerConnecting(player); public int GetBattery(BluetoothPlayer player) { return player == BluetoothPlayer.SECOND_PLAYER ? State.Battery2P : State.Battery1P; } public string GetStatusTextKey(BluetoothPlayer player) { return State.GetBLE2StatusTextKey(GetSdkStatus(player)); } public void Connect1P() => _ble?.DoConnect(); public void Connect2P() => _ble?.DoConnect2P(); public void ConnectActivePlayer() { if (_ble == null) return; if (GetActivePlayer() == BluetoothPlayer.SECOND_PLAYER) Connect2P(); else Connect1P(); } public void CancelConnecting(global::BluetoothStatusEnum status = global::BluetoothStatusEnum.ConnectFail) { _ble?.onCancelAllConnecting(status); RefreshState(); } /// DeviceView 手动选型/连接时抑制 HomeView 延迟自动连接,避免与 SideSlip 冲突。 public void SetSuppressHomeAutoConnect(bool suppress) => _ble?.SetSuppressHomeAutoConnect(suppress); /// 兼容旧版 onCancelAllConnecting 无参调用。 public void CancelConnecting() => CancelConnecting(global::BluetoothStatusEnum.ConnectFail); public void SetConnectCanceled(bool canceled) { if (_ble != null) _ble.connectCanceled = canceled; } public void Set2PNoNeedToReconnect(bool value) { if (_ble != null) _ble.bNoNeedToReconnect = value; } public SmartBowHelper GetHelper(BluetoothPlayer player) { if (_ble == null) return null; return player == BluetoothPlayer.SECOND_PLAYER ? _ble.getSmartBowHelper2P() : _ble.getSmartBowHelper1P(); } public void SubscribeShooting(BluetoothPlayer player, SmartBowHelper.ShootingEvent handler) { SmartBowHelper helper = GetHelper(player); if (helper == null || handler == null) return; helper.OnShooting += handler; } public void UnsubscribeShooting(BluetoothPlayer player, SmartBowHelper.ShootingEvent handler) { SmartBowHelper helper = GetHelper(player); if (helper == null || handler == null) return; helper.OnShooting -= handler; } public void SubscribeBleDeviceState(BluetoothAim.BleDeviceEvent handler) { if (_ble == null || handler == null) return; _ble.OnBleDeviceState += handler; } public BluetoothDeviceStatus GetBleDeviceState() { if (_aim == null) return BluetoothDeviceStatus.None; return _aim.bluetoothDeviceStatus; } public BluetoothDeviceStatus SetBleDeviceState(BluetoothDeviceStatus deviceStatus) { if (_aim == null) return BluetoothDeviceStatus.None; _aim.bluetoothDeviceStatus = deviceStatus; return _aim.bluetoothDeviceStatus; } public void UnsubscribeBleDeviceState(BluetoothAim.BleDeviceEvent handler) { if (_ble == null || handler == null) return; _ble.OnBleDeviceState -= handler; } public void SubscribeDeviceAndSystemInfoEvent(BluetoothAim.DeviceAndSystemInfoEvent handler) { if (_ble == null || handler == null) return; _ble.OnDeviceAndSystemInfoEvent += handler; } public void UnsubscribeDeviceAndSystemInfoEvent(BluetoothAim.DeviceAndSystemInfoEvent handler) { if (_ble == null || handler == null) return; _ble.OnDeviceAndSystemInfoEvent -= handler; } /// 触发一次当前缓存的设备类型 / 连接平台信息。 public void NotifyDeviceAndSystemInfo() => _ble?.GetDeviceAndSystemInfoEvent(); /// /// 将 /// 同步到 SDK 红外射箭速度换算(1P + 2P)。 /// public void SyncArrowWeights() { SyncArrowWeights(BluetoothPlayer.FIRST_PLAYER); SyncArrowWeights(BluetoothPlayer.SECOND_PLAYER); } /// /// 将当前用户箭重设置同步到指定玩家的 SDK Helper。 /// public void SyncArrowWeights(BluetoothPlayer player) { SmartBowHelper helper = GetHelper(player); if (helper == null) return; float gameWeight = UserSettings.ins != null ? UserSettings.ins.actualArrowWeight : 20f; float solidWeight = CommonConfig.arrowWeight > 0f ? CommonConfig.arrowWeight : 75f; helper.SetGameArrowWeight(gameWeight); helper.SetSolidArrowWeight(solidWeight); } /** * // 分开设置 hub.SetGameArrowWeight(BluetoothPlayer.FIRST_PLAYER, 27f); hub.SetSolidArrowWeight(BluetoothPlayer.FIRST_PLAYER, 75f); // 读取 float game = hub.GetGameArrowWeight(BluetoothPlayer.FIRST_PLAYER); float solid = hub.GetSolidArrowWeight(BluetoothPlayer.FIRST_PLAYER); */ /// 设置指定玩家 SDK 游戏里箭重(克)。 public void SetGameArrowWeight(BluetoothPlayer player, float gameArrowWeight) { GetHelper(player)?.SetGameArrowWeight(gameArrowWeight); } /// 设置指定玩家 SDK 实体模具箭重(克)。 public void SetSolidArrowWeight(BluetoothPlayer player, float solidArrowWeight) { GetHelper(player)?.SetSolidArrowWeight(solidArrowWeight); } /// 读取指定玩家 SDK 游戏里箭重;Helper 未就绪时回退 UserSettings。 public float GetGameArrowWeight(BluetoothPlayer player) { SmartBowHelper helper = GetHelper(player); if (helper != null) return helper.GetGameArrowWeight(); return UserSettings.ins != null ? UserSettings.ins.actualArrowWeight : 20f; } /// 读取指定玩家 SDK 实体模具箭重;Helper 未就绪时回退 CommonConfig。 public float GetSolidArrowWeight(BluetoothPlayer player) { SmartBowHelper helper = GetHelper(player); if (helper != null) return helper.GetSolidArrowWeight(); return CommonConfig.arrowWeight > 0f ? CommonConfig.arrowWeight : 75f; } public float GetArrowSpeed(BluetoothPlayer player) { SmartBowHelper helper = GetHelper(player); if (helper == null) return 0f; return helper.GetArrowSpeed(); } /// 模块是否初始化完成(Demo:此后再调传感器/校准 API)。 public bool IsModuleInited(BluetoothPlayer player) { SmartBowHelper helper = GetHelper(player); return helper != null && helper.IsBluetoothModuleInited(); } /// /// 对齐 DemoStarter.OnBluetoothModuleInited:开启九轴 + 射箭传感。 /// 连接态 Connected 但 moduleInited 未完成时返回 false。 /// public bool EnsureNineAxisSensorsStarted(BluetoothPlayer? player = null) { if (_ble == null) return false; BluetoothPlayer p = player ?? GetActivePlayer(); SmartBowHelper helper = GetHelper(p); if (helper == null) return false; if (helper.GetBluetoothStatus() != SmartBowSDK.BluetoothStatusEnum.Connected) return false; if (!helper.IsBluetoothModuleInited()) return false; SyncArrowWeights(p); helper.StartRotationSensor(); helper.StartShootingSensor(); return true; } public bool IsMainConnectToInfraredDevice() => IsMainConnectToHOUYIPRO() || IsMainConnectToARTEMISPRO(); /// 1P 档案为 HOUYIPRO 且已连接( 时仅看设备类型)。 public bool IsMainConnectToHOUYIPRO() => IsMainConnectToDeviceType(AimDeviceType.HOUYIPRO); /// 1P 档案为 ARTEMISPRO 且已连接( 时仅看设备类型)。 public bool IsMainConnectToARTEMISPRO() => IsMainConnectToDeviceType(AimDeviceType.ARTEMISPRO); /// 1P 档案为枪类设备且已连接( 时仅看设备类型)。 public bool IsMainConnectToGun() { return GetMainConnectGunType().isGun; } /// 1P 枪类连接信息:是否满足枪模式 + 具体枪型。 public (bool isGun, AimDeviceType gunType) GetMainConnectGunType() { AimDeviceType gunType = AimDeviceType.NONE; if (_aim == null) return (false, gunType); int type = _aim.GetDeviceType(BluetoothPlayer.FIRST_PLAYER); if (!IsGunDeviceType(type)) return (false, gunType); gunType = (AimDeviceType)type; if (CommonConfig.bDisableBluetooth) return (true, gunType); return (IsConnected(BluetoothPlayer.FIRST_PLAYER), gunType); } static bool IsGunDeviceType(int type) => type == (int)AimDeviceType.Gun || type == (int)AimDeviceType.PistolM17 || type == (int)AimDeviceType.RifleM416; bool IsMainConnectToDeviceType(AimDeviceType deviceType) { if (_aim == null) return false; if (_aim.GetDeviceType(BluetoothPlayer.FIRST_PLAYER) != (int)deviceType) return false; if (CommonConfig.bDisableBluetooth) return true; return IsConnected(BluetoothPlayer.FIRST_PLAYER); } void UnsubscribeHelper(ref SmartBowHelper helper) { if (helper == null) return; SdkCalibrateHostBridge.Unbind(helper); helper.OnBluetoothStatusChanged -= OnHelperStatusChanged; helper.OnBluetoothModuleInited -= OnHelperModuleInited; helper = null; } void ConfigureHelperCalibrateStorage(SmartBowHelper helper) { if (helper == null) return; if (useHostCalibrateStorage) SdkCalibrateHostBridge.Bind(helper); else { SdkCalibrateHostBridge.Unbind(helper); helper.calibrateDataStorageMode = CalibrateDataStorageMode.Remote; } } /// 取 DeviceView 等设备展示图。 public Sprite GetDeviceDisplaySprite(AimDeviceType deviceType) => TryGetDeviceDisplay(deviceType, out Sprite sprite, out _) ? sprite : null; /// 取 DeviceView 等设备展示标题。 public string GetDeviceDisplayName(AimDeviceType deviceType) => TryGetDeviceDisplay(deviceType, out _, out string displayName) ? displayName : deviceType.ToString(); public bool TryGetDeviceDisplay(AimDeviceType deviceType, out Sprite sprite, out string displayName) { sprite = null; displayName = null; if (deviceDisplayEntries == null) return false; for (int i = 0; i < deviceDisplayEntries.Length; i++) { DeviceTypeDisplayEntry entry = deviceDisplayEntries[i]; if (entry == null || entry.deviceType != deviceType) continue; sprite = entry.sprite; displayName = string.IsNullOrEmpty(entry.displayName) ? deviceType.ToString() : entry.displayName; return sprite != null || !string.IsNullOrEmpty(displayName); } return false; } public void ResetAim(BluetoothPlayer player) => GetHelper(player)?.ResetAim(); public void SetMainConnectDeviceType() => _ble.SetMainConnectDeviceType(); } }