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();
}
}