008-人体学接口设备(HID)开发指南

人体学接口设备(HID)开发指南

本章将详细介绍游戏引擎中人体学接口设备(Human Interface Device, HID)的理论与实现,提供C和C++的实例代码,帮助开发者构建高效、响应迅速的输入系统。

8.1 各种人体学接口设备

人体学接口设备是用户与游戏交互的关键媒介,包括但不限于以下几类:

8.1.1 标准输入设备

cpp

// 常见标准输入设备枚举
enum class StandardInputDevice {
    Keyboard,
    Mouse,
    Gamepad,
    Joystick,
    TouchScreen,
    MotionController
};

8.1.2 专业游戏设备

cpp

// 专业游戏输入设备
struct SpecializedGameDevice {
    std::string deviceName;
    std::string manufacturer;
    uint32_t numButtons;
    uint32_t numAxes;
    bool hasForceFeadback;
    bool hasMotionSensing;
    
    // 设备功能检测
    bool supportsFeature(GameDeviceFeature feature) const {
        // 实现检测逻辑
        return (supportedFeatures & static_cast<uint32_t>(feature)) != 0;
    }
    
private:
    uint32_t supportedFeatures;
};

8.1.3 VR/AR接口设备

cpp

// VR控制器数据结构
struct VRControllerState {
    Vector3 position;       // 3D空间位置
    Quaternion orientation; // 旋转方向
    bool triggerPressed;    // 扳机按钮
    float triggerValue;     // 扳机模拟值(0.0-1.0)
    bool touchpadPressed;   // 触摸板按下
    Vector2 touchpadPosition; // 触摸位置
    
    // 手柄震动控制
    void applyHapticFeedback(float intensity, float durationMs) {
        // 实现力反馈逻辑
    }
};

8.2 人体学接口设备的接口技术

8.2.1 USB设备接口

cpp

#include <libusb-1.0/libusb.h>

class USBDeviceInterface {
private:
    libusb_context* context;
    libusb_device_handle* deviceHandle;
    
public:
    USBDeviceInterface() : context(nullptr), deviceHandle(nullptr) {}
    
    ~USBDeviceInterface() {
        close();
    }
    
    bool initialize() {
        int result = libusb_init(&context);
        return (result == 0);
    }
    
    bool openDevice(uint16_t vendorId, uint16_t productId) {
        deviceHandle = libusb_open_device_with_vid_pid(context, vendorId, productId);
        return (deviceHandle != nullptr);
    }
    
    bool claimInterface(int interfaceNumber) {
        if (deviceHandle) {
            return (libusb_claim_interface(deviceHandle, interfaceNumber) == 0);
        }
        return false;
    }
    
    int readData(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
        int transferred = 0;
        if (deviceHandle) {
            libusb_bulk_transfer(deviceHandle, endpoint, data, length, &transferred, timeout);
        }
        return transferred;
    }
    
    void close() {
        if (deviceHandle) {
            libusb_close(deviceHandle);
            deviceHandle = nullptr;
        }
        
        if (context) {
            libusb_exit(context);
            context = nullptr;
        }
    }
};

8.2.2 蓝牙设备接口

cpp

#ifdef _WIN32
#include <windows.h>
#include <bthsdpdef.h>
#include <bluetoothapis.h>
#endif

class BluetoothDeviceInterface {
private:
    // 平台特定的蓝牙句柄
    void* deviceHandle;
    bool connected;
    
public:
    BluetoothDeviceInterface() : deviceHandle(nullptr), connected(false) {}
    
    bool scanDevices(std::vector<BluetoothDeviceInfo>& devices) {
        // 实现蓝牙设备扫描
        #ifdef _WIN32
        // Windows蓝牙设备扫描代码
        BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = {0};
        searchParams.dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS);
        searchParams.fReturnAuthenticated = TRUE;
        searchParams.fReturnConnected = TRUE;
        searchParams.fReturnRemembered = TRUE;
        searchParams.fIssueInquiry = TRUE;
        searchParams.cTimeoutMultiplier = 4;
        
        BLUETOOTH_DEVICE_INFO deviceInfo = {0};
        deviceInfo.dwSize = sizeof(BLUETOOTH_DEVICE_INFO);
        
        HBLUETOOTH_DEVICE_FIND hFind = BluetoothFindFirstDevice(&searchParams, &deviceInfo);
        if (hFind) {
            do {
                BluetoothDeviceInfo info;
                info.address = deviceInfo.Address;
                info.name = deviceInfo.szName;
                devices.push_back(info);
            } while (BluetoothFindNextDevice(hFind, &deviceInfo));
            
            BluetoothFindDeviceClose(hFind);
            return true;
        }
        #endif
        return false;
    }
    
    bool connect(const BluetoothDeviceInfo& device) {
        // 实现蓝牙设备连接
        return false; // 占位,需要实际实现
    }
    
    bool sendData(const uint8_t* data, size_t length) {
        if (!connected) return false;
        
        // 实现数据发送
        return true; // 占位,需要实际实现
    }
    
    bool receiveData(uint8_t* buffer, size_t maxLength, size_t& bytesReceived) {
        if (!connected) return false;
        
        // 实现数据接收
        return true; // 占位,需要实际实现
    }
};

8.2.3 无线接口技术

cpp

class WirelessDeviceManager {
private:
    std::unordered_map<uint32_t, WirelessDevice*> connectedDevices;
    std::thread pollingThread;
    std::atomic<bool> isRunning;
    
public:
    WirelessDeviceManager() : isRunning(false) {}
    
    ~WirelessDeviceManager() {
        stopPolling();
        for (auto& pair : connectedDevices) {
            delete pair.second;
        }
    }
    
    void startPolling() {
        if (!isRunning) {
            isRunning = true;
            pollingThread = std::thread(&WirelessDeviceManager::pollingFunction, this);
        }
    }
    
    void stopPolling() {
        if (isRunning) {
            isRunning = false;
            if (pollingThread.joinable()) {
                pollingThread.join();
            }
        }
    }
    
private:
    void pollingFunction() {
        while (isRunning) {
            for (auto& pair : connectedDevices) {
                pair.second->poll();
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(16)); // ~60Hz
        }
    }
};

8.3 输入类型

8.3.1 数字输入

cpp

// 表示二元状态的数字输入(按下/释放)
class DigitalInput {
private:
    bool currentState;
    bool previousState;
    
public:
    DigitalInput() : currentState(false), previousState(false) {}
    
    void update(bool newState) {
        previousState = currentState;
        currentState = newState;
    }
    
    // 当前是否按下
    bool isPressed() const {
        return currentState;
    }
    
    // 是否刚按下(当前帧按下,上一帧未按下)
    bool isJustPressed() const {
        return currentState && !previousState;
    }
    
    // 是否刚释放(当前帧未按下,上一帧按下)
    bool isJustReleased() const {
        return !currentState && previousState;
    }
};

8.3.2 模拟输入

cpp

// 表示连续值的模拟输入(如摇杆、触发器)
class AnalogInput {
private:
    float currentValue;
    float previousValue;
    float deadZone;
    
public:
    AnalogInput(float deadZoneValue = 0.1f) 
        : currentValue(0.0f), previousValue(0.0f), deadZone(deadZoneValue) {}
    
    void update(float newValue) {
        previousValue = currentValue;
        currentValue = newValue;
    }
    
    // 获取当前值(应用死区)
    float getValue() const {
        if (std::abs(currentValue) < deadZone) {
            return 0.0f;
        }
        
        // 重新映射值以考虑死区
        float sign = (currentValue > 0.0f) ? 1.0f : -1.0f;
        return sign * (std::abs(currentValue) - deadZone) / (1.0f - deadZone);
    }
    
    // 获取原始值(不应用死区)
    float getRawValue() const {
        return currentValue;
    }
    
    // 获取变化量
    float getDelta() const {
        return currentValue - previousValue;
    }
    
    // 设置死区
    void setDeadZone(float value) {
        deadZone = std::max(0.0f, std::min(value, 1.0f));
    }
};

8.3.3 触摸输入

cpp

// 表示触摸输入事件
struct TouchEvent {
    enum class Type {
        Begin,  // 触摸开始
        Move,   // 触摸移动
        End,    // 触摸结束
        Cancel  // 触摸取消
    };
    
    uint32_t touchId;   // 触摸点ID
    Type type;          // 触摸事件类型
    float x, y;         // 触摸坐标
    float pressure;     // 压力值(如果支持)
    float size;         // 触摸面积(如果支持)
    uint64_t timestamp; // 时间戳
};

class TouchInputManager {
private:
    std::unordered_map<uint32_t, TouchEvent> activeTouches;
    std::vector<TouchEvent> touchEvents;
    
public:
    // 处理新的触摸事件
    void processTouchEvent(const TouchEvent& event) {
        touchEvents.push_back(event);
        
        if (event.type == TouchEvent::Type::Begin) {
            activeTouches[event.touchId] = event;
        } else if (event.type == TouchEvent::Type::Move) {
            if (activeTouches.find(event.touchId) != activeTouches.end()) {
                activeTouches[event.touchId] = event;
            }
        } else if (event.type == TouchEvent::Type::End || 
                   event.type == TouchEvent::Type::Cancel) {
            activeTouches.erase(event.touchId);
        }
    }
    
    // 获取所有活跃的触摸点
    const std::unordered_map<uint32_t, TouchEvent>& getActiveTouches() const {
        return activeTouches;
    }
    
    // 获取并清除当前帧的触摸事件
    std::vector<TouchEvent> getAndClearEvents() {
        std::vector<TouchEvent> result = std::move(touchEvents);
        touchEvents.clear();
        return result;
    }
};

8.3.4 手势输入

cpp

// 表示手势类型
enum class GestureType {
    Tap,
    DoubleTap,
    LongPress,
    Swipe,
    Pinch,
    Rotate,
    Pan
};

// 手势事件数据
struct GestureEvent {
    GestureType type;
    Vector2 position;          // 手势位置
    Vector2 delta;             // 位置变化量(用于平移、滑动)
    float scale;               // 缩放比例(用于捏合)
    float rotation;            // 旋转角度(用于旋转)
    uint64_t timestamp;        // 时间戳
    
    // 特定于轻触的数据
    int tapCount;              // 轻触次数
};

class GestureRecognizer {
private:
    std::vector<TouchEvent> touchHistory;
    std::vector<GestureEvent> recognizedGestures;
    
    // 手势识别参数
    float tapMaxDistance;      // 轻触最大距离
    float tapMaxDuration;      // 轻触最大持续时间
    float doubleTapMaxDelay;   // 双击最大间隔
    float longPressMinDuration; // 长按最小持续时间
    float swipeMinDistance;    // 滑动最小距离
    float swipeMaxTime;        // 滑动最大时间
    
public:
    GestureRecognizer() {
        // 设置默认参数
        tapMaxDistance = 20.0f;
        tapMaxDuration = 300.0f; // 毫秒
        doubleTapMaxDelay = 300.0f; // 毫秒
        longPressMinDuration = 500.0f; // 毫秒
        swipeMinDistance = 50.0f;
        swipeMaxTime = 300.0f; // 毫秒
    }
    
    // 更新触摸历史并尝试识别手势
    void update(const std::vector<TouchEvent>& newTouches) {
        // 添加新的触摸事件到历史
        touchHistory.insert(touchHistory.end(), newTouches.begin(), newTouches.end());
        
        // 识别手势
        recognizeTaps();
        recognizeSwipes();
        recognizePinchAndRotate();
        
        // 清理过旧的触摸历史
        cleanupTouchHistory();
    }
    
    // 获取并清除识别的手势
    std::vector<GestureEvent> getAndClearGestures() {
        std::vector<GestureEvent> result = std::move(recognizedGestures);
        recognizedGestures.clear();
        return result;
    }
    
private:
    // 实现各种手势识别算法
    void recognizeTaps() {
        // 轻触和双击识别逻辑
    }
    
    void recognizeSwipes() {
        // 滑动识别逻辑
    }
    
    void recognizePinchAndRotate() {
        // 捏合和旋转识别逻辑
    }
    
    void cleanupTouchHistory() {
        // 移除过旧的触摸事件
        uint64_t currentTime = getCurrentTimestamp();
        
        auto it = touchHistory.begin();
        while (it != touchHistory.end()) {
            if (currentTime - it->timestamp > 1000) { // 删除1秒前的事件
                it = touchHistory.erase(it);
            } else {
                ++it;
            }
        }
    }
    
    uint64_t getCurrentTimestamp() {
        return std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch()).count();
    }
};

8.4 输出类型

8.4.1 力反馈

cpp

// 力反馈效果类型
enum class ForceFeedbackType {
    Constant,   // 持续力
    Ramp,       // 渐变力
    Periodic,   // 周期性力(如正弦波)
    Condition,  // 条件力(如弹簧)
    Custom      // 自定义效果
};

// 力反馈效果基类
class ForceFeedbackEffect {
protected:
    ForceFeedbackType type;
    uint32_t duration;  // 持续时间(毫秒)
    float strength;     // 力度(0.0-1.0)
    
public:
    ForceFeedbackEffect(ForceFeedbackType effectType, uint32_t effectDuration, float effectStrength)
        : type(effectType), duration(effectDuration), strength(effectStrength) {}
    
    virtual ~ForceFeedbackEffect() {}
    
    ForceFeedbackType getType() const { return type; }
    uint32_t getDuration() const { return duration; }
    float getStrength() const { return strength; }
    
    // 获取特定时间点的力反馈强度
    virtual float getIntensityAtTime(uint32_t timeMs) const = 0;
};

// 持续力反馈效果
class ConstantForceEffect : public ForceFeedbackEffect {
public:
    ConstantForceEffect(uint32_t duration, float strength)
        : ForceFeedbackEffect(ForceFeedbackType::Constant, duration, strength) {}
    
    float getIntensityAtTime(uint32_t timeMs) const override {
        if (timeMs > duration) return 0.0f;
        return strength;
    }
};

// 震动效果
class VibrationEffect : public ForceFeedbackEffect {
private:
    float frequency;  // 震动频率(Hz)
    
public:
    VibrationEffect(uint32_t duration, float strength, float freq)
        : ForceFeedbackEffect(ForceFeedbackType::Periodic, duration, strength), 
          frequency(freq) {}
    
    float getIntensityAtTime(uint32_t timeMs) const override {
        if (timeMs > duration) return 0.0f;
        
        // 生成震动波形(正弦波)
        float phase = (timeMs / 1000.0f) * frequency * 2.0f * 3.14159f;
        return strength * std::abs(std::sin(phase));
    }
};

// 力反馈控制器
class ForceFeedbackController {
private:
    std::vector<std::unique_ptr<ForceFeedbackEffect>> activeEffects;
    bool deviceSupported;
    
public:
    ForceFeedbackController() : deviceSupported(false) {}
    
    bool initialize() {
        // 检测设备是否支持力反馈
        return checkDeviceSupport();
    }
    
    // 添加效果并开始播放
    bool playEffect(std::unique_ptr<ForceFeedbackEffect> effect) {
        if (!deviceSupported) return false;
        
        activeEffects.push_back(std::move(effect));
        return true;
    }
    
    // 停止所有效果
    void stopAllEffects() {
        activeEffects.clear();
        applyZeroForce();
    }
    
    // 更新力反馈状态
    void update() {
        if (!deviceSupported || activeEffects.empty()) return;
        
        uint32_t currentTime = getCurrentTimeMs();
        float totalIntensity = 0.0f;
        
        // 计算所有活跃效果的合成强度
        for (auto it = activeEffects.begin(); it != activeEffects.end();) {
            ForceFeedbackEffect* effect = it->get();
            uint32_t effectTime = currentTime % (effect->getDuration() + 1);
            
            float intensity = effect->getIntensityAtTime(effectTime);
            totalIntensity += intensity;
            
            // 移除已完成的效果
            if (effectTime >= effect->getDuration()) {
                it = activeEffects.erase(it);
            } else {
                ++it;
            }
        }
        
        // 限制强度范围
        totalIntensity = std::max(0.0f, std::min(totalIntensity, 1.0f));
        
        // 应用到设备
        applyForce(totalIntensity);
    }
    
private:
    bool checkDeviceSupport() {
        // 实现设备支持检查
        return false; // 占位,需要实际实现
    }
    
    void applyForce(float intensity) {
        // 实现向设备发送力反馈命令
    }
    
    void applyZeroForce() {
        applyForce(0.0f);
    }
    
    uint32_t getCurrentTimeMs() {
        return static_cast<uint32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch()).count());
    }
};

8.4.2 视觉反馈

cpp

// 控制器LED效果
class ControllerLEDEffect {
public:
    enum class Pattern {
        Solid,
        Pulse,
        Blink,
        Fade,
        Rainbow
    };
    
    ControllerLEDEffect(Pattern p, RGBColor c, uint32_t dur = 0)
        : pattern(p), color(c), duration(dur), startTime(getCurrentTimeMs()) {}
    
    RGBColor getColorAtTime(uint32_t timeMs) const {
        uint32_t elapsedTime = timeMs - startTime;
        
        switch (pattern) {
            case Pattern::Solid:
                return color;
                
            case Pattern::Pulse: {
                float phase = (elapsedTime % 1000) / 1000.0f;
                float intensity = std::sin(phase * 3.14159f);
                return color * intensity;
            }
            
            case Pattern::Blink: {
                uint32_t period = 500; // 500ms on, 500ms off
                return ((elapsedTime % (period * 2)) < period) ? color : RGBColor(0, 0, 0);
            }
            
            case Pattern::Fade: {
                if (duration > 0) {
                    float t = std::min(1.0f, elapsedTime / static_cast<float>(duration));
                    return color * (1.0f - t);
                }
                return color;
            }
            
            case Pattern::Rainbow: {
                float hue = (elapsedTime % 5000) / 5000.0f;
                return RGBColor::fromHSV(hue, 1.0f, 1.0f);
            }
            
            default:
                return color;
        }
    }
    
    bool isFinished(uint32_t currentTime) const {
        if (duration == 0) return false; // 无限持续
        return (currentTime - startTime) >= duration;
    }
    
private:
    Pattern pattern;
    RGBColor color;
    uint32_t duration;  // 持续时间(毫秒),0表示无限
    uint32_t startTime; // 开始时间
    
    uint32_t getCurrentTimeMs() const {
        return static_cast<uint32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch()).count());
    }
};

8.4.3 音频反馈

cpp

// 控制器扬声器接口
class ControllerAudioInterface {
private:
    bool audioSupported;
    float volume;
    
public:
    ControllerAudioInterface() : audioSupported(false), volume(1.0f) {}
    
    bool initialize() {
        // 检测控制器是否支持音频输出
        return checkAudioSupport();
    }
    
    // 播放预定义的音效
    bool playSound(AudioSoundEffect effect, float volumeScale = 1.0f) {
        if (!audioSupported) return false;
        
        // 实现音效播放
        return true;
    }
    
    // 从内存缓冲区播放自定义音频
    bool playAudioBuffer(const uint8_t* buffer, size_t bufferSize, 
                        int sampleRate, int channels, float volumeScale = 1.0f) {
        if (!audioSupported) return false;
        
        // 实现音频缓冲区播放
        return true;
    }
    
    // 设置主音量
    void setVolume(float newVolume) {
        volume = std::max(0.0f, std::min(newVolume, 1.0f));
    }
    
private:
    bool checkAudioSupport() {
        // 实现控制器音频支持检查
        return false; // 占位,需要实际实现
    }
};

8.5 游戏引擎的人体学接口设备系统

8.5.1 输入管理器设计

cpp

// 定义输入类型和动作
enum class InputActionType {
    Digital,    // 数字按钮(按下/释放)
    Analog,     // 模拟输入(范围值)
    Vector2D,   // 2D向量输入(如摇杆)
    Vector3D,   // 3D向量输入(如陀螺仪)
    Gesture     // 手势输入
};

// 输入设备类型
enum class InputDeviceType {
    Keyboard,
    Mouse,
    Gamepad,
    TouchScreen,
    MotionController,
    VRController,
    Custom
};

// 输入动作映射
struct InputActionMapping {
    std::string actionName;
    InputActionType actionType;
    InputDeviceType deviceType;
    int deviceId;
    int inputId;          // 键码、按钮ID等
    float scale;          // 输入缩放
    bool inverted;        // 是否反转
    
    InputActionMapping(const std::string& name, InputActionType type,
                       InputDeviceType device, int devId, int inId,
                       float inputScale = 1.0f, bool inv = false)
        : actionName(name), actionType(type), deviceType(device),
          deviceId(devId), inputId(inId), scale(inputScale), inverted(inv) {}
};

// 输入管理器核心类
class InputManager {
private:
    std::unordered_map<std::string, std::vector<InputActionMapping>> actionMappings;
    std::unordered_map<InputDeviceType, std::vector<InputDevice*>> devices;
    
    // 当前输入状态
    std::unordered_map<std::string, DigitalInput> digitalInputs;
    std::unordered_map<std::string, AnalogInput> analogInputs;
    std::unordered_map<std::string, Vector2D> vector2DInputs;
    std::unordered_map<std::string, Vector3D> vector3DInputs;
    
    // 手势识别器
    GestureRecognizer gestureRecognizer;
    
public:
    InputManager() {}
    
    ~InputManager() {
        // 清理设备
        for (auto& devicePair : devices) {
            for (auto* device : devicePair.second) {
                delete device;
            }
        }
    }
    
    // 初始化输入系统
    bool initialize() {
        // 初始化各种输入设备
        initializeKeyboard();
        initializeMouse();
        initializeGamepads();
        initializeTouchScreen();
        
        return true;
    }
    
    // 添加输入映射
    void addActionMapping(const InputActionMapping& mapping) {
        actionMappings[mapping.actionName].push_back(mapping);
    }
    
    // 移除输入映射
    void removeActionMapping(const std::string& actionName) {
        actionMappings.erase(actionName);
    }
    
    // 检查数字输入状态
    bool isActionPressed(const std::string& actionName) const {
        auto it = digitalInputs.find(actionName);
        if (it != digitalInputs.end()) {
            return it->second.isPressed();
        }
        return false;
    }
    
    // 检查数字输入是否刚被按下
    bool isActionJustPressed(const std::string& actionName) const {
        auto it = digitalInputs.find(actionName);
        if (it != digitalInputs.end()) {
            return it->second.isJustPressed();
        }
        return false;
    }
    
    // 获取模拟输入值
    float getAnalogActionValue(const std::string& actionName) const {
        auto it = analogInputs.find(actionName);
        if (it != analogInputs.end()) {
            return it->second.getValue();
        }
        return 0.0f;
    }
    
    // 获取2D向量输入
    Vector2D getVector2DAction(const std::string& actionName) const {
        auto it = vector2DInputs.find(actionName);
        if (it != vector2DInputs.end()) {
            return it->second;
        }
        return Vector2D();
    }
    
    // 更新所有输入设备状态
    void update() {
        // 更新所有设备
        for (auto& devicePair : devices) {
            for (auto* device : devicePair.second) {
                device->update();
            }
        }
        
        // 处理触摸输入和手势
        std::vector<TouchEvent> touchEvents = getTouchEvents();
        gestureRecognizer.update(touchEvents);
        
        // 应用输入映射
        updateActionMappings();
    }
    
private:
    // 初始化各种设备
    void initializeKeyboard() {
        // 实现键盘初始化
    }
    
    void initializeMouse() {
        // 实现鼠标初始化
    }
    
    void initializeGamepads() {
        // 实现游戏手柄初始化
    }
    
    void initializeTouchScreen() {
        // 实现触摸屏初始化
    }
    
    // 获取当前帧的触摸事件
    std::vector<TouchEvent> getTouchEvents() {
        // 实现从触摸设备获取事件
        return std::vector<TouchEvent>();
    }
    
    // 更新动作映射
    void updateActionMappings() {
        // 处理每个映射
        for (const auto& mappingPair : actionMappings) {
            const std::string& actionName = mappingPair.first;
            const auto& mappings = mappingPair.second;
            
            for (const auto& mapping : mappings) {
                // 根据映射类型处理输入
                switch (mapping.actionType) {
                    case InputActionType::Digital:
                        processDigitalMapping(actionName, mapping);
                        break;
                    case InputActionType::Analog:
                        processAnalogMapping(actionName, mapping);
                        break;
                    case InputActionType::Vector2D:
                        processVector2DMapping(actionName, mapping);
                        break;
                    // 其他类型处理...
                }
            }
        }
    }
    
    // 处理数字输入映射
    void processDigitalMapping(const std::string& actionName, const InputActionMapping& mapping) {
        bool isPressed = false;
        
        // 根据设备类型获取输入状态
        if (mapping.deviceType == InputDeviceType::Keyboard) {
            isPressed = isKeyPressed(mapping.inputId);
        } else if (mapping.deviceType == InputDeviceType::Gamepad) {
            isPressed = isGamepadButtonPressed(mapping.deviceId, mapping.inputId);
        }
        
        // 更新数字输入状态
        if (digitalInputs.find(actionName) == digitalInputs.end()) {
            digitalInputs[actionName] = DigitalInput();
        }
        
        // 应用当前输入状态
        digitalInputs[actionName].update(isPressed);
    }
    
    // 处理模拟输入映射
    void processAnalogMapping(const std::string& actionName, const InputActionMapping& mapping) {
        float value = 0.0f;
        
        // 根据设备类型获取输入值
        if (mapping.deviceType == InputDeviceType::Gamepad) {
            value = getGamepadAnalogValue(mapping.deviceId, mapping.inputId);
        }
        
        // 应用缩放和反转
        value = value * mapping.scale * (mapping.inverted ? -1.0f : 1.0f);
        
        // 更新模拟输入状态
        if (analogInputs.find(actionName) == analogInputs.end()) {
            analogInputs[actionName] = AnalogInput();
        }
        
        // 应用当前输入值
        analogInputs[actionName].update(value);
    }
    
    // 处理2D向量输入映射
    void processVector2DMapping(const std::string& actionName, const InputActionMapping& mapping) {
        Vector2D value;
        
        // 根据设备类型获取输入向量
        if (mapping.deviceType == InputDeviceType::Gamepad) {
            value = getGamepadStickValue(mapping.deviceId, mapping.inputId);
        }
        
        // 应用缩放
        value.x *= mapping.scale * (mapping.inverted ? -1.0f : 1.0f);
        value.y *= mapping.scale;
        
        // 更新向量输入状态
        vector2DInputs[actionName] = value;
    }
    
    // 设备输入状态检查函数(这些需要具体实现)
    bool isKeyPressed(int keyCode) {
        // 实现键盘按键检查
        return false;
    }
    
    bool isGamepadButtonPressed(int gamepadId, int buttonId) {
        // 实现游戏手柄按钮检查
        return false;
    }
    
    float getGamepadAnalogValue(int gamepadId, int analogId) {
        // 实现游戏手柄模拟输入检查
        return 0.0f;
    }
    
    Vector2D getGamepadStickValue(int gamepadId, int stickId) {
        // 实现游戏手柄摇杆值获取
        return Vector2D();
    }
};

8.5.2 输入映射系统

cpp

// 输入映射配置
class InputMappingConfig {
private:
    std::string configName;
    std::unordered_map<std::string, std::vector<InputActionMapping>> mappings;
    
public:
    InputMappingConfig(const std::string& name) : configName(name) {}
    
    void addMapping(const InputActionMapping& mapping) {
        mappings[mapping.actionName].push_back(mapping);
    }
    
    void removeMapping(const std::string& actionName) {
        mappings.erase(actionName);
    }
    
    // 获取特定动作的所有映射
    const std::vector<InputActionMapping>& getMappingsForAction(const std::string& actionName) const {
        static const std::vector<InputActionMapping> emptyVector;
        auto it = mappings.find(actionName);
        if (it != mappings.end()) {
            return it->second;
        }
        return emptyVector;
    }
    
    // 保存映射到文件
    bool saveToFile(const std::string& filename) const {
        // 实现保存逻辑
        return false; // 占位,需要实际实现
    }
    
    // 从文件加载映射
    bool loadFromFile(const std::string& filename) {
        // 实现加载逻辑
        return false; // 占位,需要实际实现
    }
};

8.5.3 输入事件系统

cpp

// 输入事件类型
enum class InputEventType {
    ButtonPressed,
    ButtonReleased,
    AnalogChanged,
    AxisChanged,
    GestureDetected,
    DeviceConnected,
    DeviceDisconnected
};

// 输入事件基类
struct InputEvent {
    InputEventType type;
    uint64_t timestamp;
    
    InputEvent(InputEventType t) : type(t), timestamp(getCurrentTimestamp()) {}
    virtual ~InputEvent() {}
    
    static uint64_t getCurrentTimestamp() {
        return std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch()).count();
    }
};

// 按钮事件
struct ButtonEvent : public InputEvent {
    InputDeviceType deviceType;
    int deviceId;
    int buttonId;
    bool pressed;
    
    ButtonEvent(InputDeviceType device, int devId, int btn, bool p)
        : InputEvent(p ? InputEventType::ButtonPressed : InputEventType::ButtonReleased),
          deviceType(device), deviceId(devId), buttonId(btn), pressed(p) {}
};

// 模拟输入事件
struct AnalogEvent : public InputEvent {
    InputDeviceType deviceType;
    int deviceId;
    int analogId;
    float value;
    float previousValue;
    
    AnalogEvent(InputDeviceType device, int devId, int analog, float val, float prevVal)
        : InputEvent(InputEventType::AnalogChanged),
          deviceType(device), deviceId(devId), analogId(analog),
          value(val), previousValue(prevVal) {}
};

// 输入事件分发器
class InputEventDispatcher {
private:
    using EventCallback = std::function<void(const InputEvent&)>;
    std::unordered_map<InputEventType, std::vector<EventCallback>> eventHandlers;
    std::queue<std::unique_ptr<InputEvent>> eventQueue;
    std::mutex queueMutex;
    
public:
    // 注册事件处理器
    void registerHandler(InputEventType type, EventCallback handler) {
        eventHandlers[type].push_back(handler);
    }
    
    // 生成事件并添加到队列
    template<typename T, typename... Args>
    void queueEvent(Args&&... args) {
        std::lock_guard<std::mutex> lock(queueMutex);
        eventQueue.push(std::make_unique<T>(std::forward<Args>(args)...));
    }
    
    // 处理所有排队的事件
    void processEvents() {
        std::queue<std::unique_ptr<InputEvent>> currentEvents;
        
        {
            std::lock_guard<std::mutex> lock(queueMutex);
            std::swap(currentEvents, eventQueue);
        }
        
        while (!currentEvents.empty()) {
            const InputEvent& event = *currentEvents.front();
            auto it = eventHandlers.find(event.type);
            
            if (it != eventHandlers.end()) {
                for (const auto& handler : it->second) {
                    handler(event);
                }
            }
            
            currentEvents.pop();
        }
    }
};

8.6 人体学接口设备使用实践

8.6.1 实现一个完整的输入系统

以下是一个将上述所有组件整合的简化版输入系统示例:

cpp

#include <iostream>
#include <vector>
#include <unordered_map>
#include <string>
#include <functional>
#include <memory>
#include <chrono>
#include <thread>
#include <mutex>
#include <queue>
#include <algorithm>
#include <cmath>

// 基本数学结构
struct Vector2D {
    float x, y;
    
    Vector2D() : x(0.0f), y(0.0f) {}
    Vector2D(float _x, float _y) : x(_x), y(_y) {}
    
    float length() const {
        return std::sqrt(x*x + y*y);
    }
    
    Vector2D normalized() const {
        float len = length();
        if (len > 0.0001f) {
            return Vector2D(x / len, y / len);
        }
        return Vector2D();
    }
    
    Vector2D operator*(float scalar) const {
        return Vector2D(x * scalar, y * scalar);
    }
};

struct Vector3D {
    float x, y, z;
    
    Vector3D() : x(0.0f), y(0.0f), z(0.0f) {}
    Vector3D(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
};

// 简单输入系统实现
class GameInputSystem {
public:
    GameInputSystem() {
        // 注册默认控制方案
        setupDefaultMappings();
    }
    
    void initialize() {
        std::cout << "Initializing game input system...\n";
        // 实际实现中会初始化各种设备
    }
    
    void update() {
        // 实际实现中会轮询设备和处理输入
        // 这里我们模拟一些输入状态
        
        // 更新输入状态
        updateInputStates();
        
        // 处理输入事件
        eventDispatcher.processEvents();
    }
    
    // 检查动作是否被触发
    bool isActionPressed(const std::string& actionName) const {
        auto it = digitalInputs.find(actionName);
        if (it != digitalInputs.end()) {
            return it->second.isPressed();
        }
        return false;
    }
    
    bool isActionJustPressed(const std::string& actionName) const {
        auto it = digitalInputs.find(actionName);
        if (it != digitalInputs.end()) {
            return it->second.isJustPressed();
        }
        return false;
    }
    
    // 获取模拟值
    float getAnalogValue(const std::string& actionName) const {
        auto it = analogInputs.find(actionName);
        if (it != analogInputs.end()) {
            return it->second.getValue();
        }
        return 0.0f;
    }
    
    // 获取向量值
    Vector2D getVector2D(const std::string& actionName) const {
        auto it = vector2DInputs.find(actionName);
        if (it != vector2DInputs.end()) {
            return it->second;
        }
        return Vector2D();
    }
    
    // 注册输入事件处理器
    void registerEventHandler(InputEventType type, std::function<void(const InputEvent&)> handler) {
        eventDispatcher.registerHandler(type, handler);
    }
    
    // 触发振动效果
    void triggerVibration(float intensity, uint32_t durationMs) {
        std::cout << "Triggering vibration: " << intensity << " for " << durationMs << "ms\n";
        
        // 在实际实现中,会向控制器发送振动命令
        if (forceFeedbackController) {
            auto effect = std::make_unique<VibrationEffect>(durationMs, intensity, 100.0f);
            forceFeedbackController->playEffect(std::move(effect));
        }
    }
    
private:
    // 数字输入状态
    std::unordered_map<std::string, DigitalInput> digitalInputs;
    
    // 模拟输入状态
    std::unordered_map<std::string, AnalogInput> analogInputs;
    
    // 向量输入状态
    std::unordered_map<std::string, Vector2D> vector2DInputs;
    
    // 输入映射
    std::unordered_map<std::string, std::vector<InputActionMapping>> actionMappings;
    
    // 事件分发器
    InputEventDispatcher eventDispatcher;
    
    // 力反馈控制器
    std::unique_ptr<ForceFeedbackController> forceFeedbackController;
    
    // 设置默认输入映射
    void setupDefaultMappings() {
        // 移动映射
        addActionMapping(InputActionMapping("MoveForward", InputActionType::Digital,
                                          InputDeviceType::Keyboard, 0, 'W'));
        addActionMapping(InputActionMapping("MoveBackward", InputActionType::Digital,
                                          InputDeviceType::Keyboard, 0, 'S'));
        addActionMapping(InputActionMapping("MoveLeft", InputActionType::Digital,
                                          InputDeviceType::Keyboard, 0, 'A'));
        addActionMapping(InputActionMapping("MoveRight", InputActionType::Digital,
                                          InputDeviceType::Keyboard, 0, 'D'));
        
        // 跳跃映射
        addActionMapping(InputActionMapping("Jump", InputActionType::Digital,
                                          InputDeviceType::Keyboard, 0, ' '));
        
        // 手柄移动映射
        addActionMapping(InputActionMapping("Move", InputActionType::Vector2D,
                                          InputDeviceType::Gamepad, 0, 0));
        
        // 手柄跳跃映射
        addActionMapping(InputActionMapping("Jump", InputActionType::Digital,
                                          InputDeviceType::Gamepad, 0, 0));
    }
    
    // 添加动作映射
    void addActionMapping(const InputActionMapping& mapping) {
        actionMappings[mapping.actionName].push_back(mapping);
    }
    
    // 更新输入状态(在实际实现中会从设备读取)
    void updateInputStates() {
        // 这里是简化的模拟,实际实现会从设备获取数据
        
        // 模拟键盘输入
        simulateKeyboardInput();
        
        // 模拟手柄输入
        simulateGamepadInput();
        
        // 更新动作映射
        for (const auto& mappingPair : actionMappings) {
            const std::string& actionName = mappingPair.first;
            
            // 处理每种输入类型
            processDigitalMappings(actionName, mappingPair.second);
            processAnalogMappings(actionName, mappingPair.second);
            processVector2DMappings(actionName, mappingPair.second);
        }
    }
    
    // 模拟键盘输入(实际中会从系统获取)
    void simulateKeyboardInput() {
        // 这里只是简单模拟
        static bool wPressed = false;
        static bool aPressed = false;
        static bool sPressed = false;
        static bool dPressed = false;
        static bool spacePressed = false;
        
        // 模拟按键变化
        bool newWPressed = (rand() % 100) < 30;
        bool newAPressed = (rand() % 100) < 30;
        bool newSPressed = (rand() % 100) < 30;
        bool newDPressed = (rand() % 100) < 30;
        bool newSpacePressed = (rand() % 100) < 10;
        
        // 生成按键事件
        if (newWPressed != wPressed) {
            eventDispatcher.queueEvent<ButtonEvent>(InputDeviceType::Keyboard, 0, 'W', newWPressed);
            wPressed = newWPressed;
        }
        
        if (newAPressed != aPressed) {
            eventDispatcher.queueEvent<ButtonEvent>(InputDeviceType::Keyboard, 0, 'A', newAPressed);
            aPressed = newAPressed;
        }
        
        if (newSPressed != sPressed) {
            eventDispatcher.queueEvent<ButtonEvent>(InputDeviceType::Keyboard, 0, 'S', newSPressed);
            sPressed = newSPressed;
        }
        
        if (newDPressed != dPressed) {
            eventDispatcher.queueEvent<ButtonEvent>(InputDeviceType::Keyboard, 0, 'D', newDPressed);
            dPressed = newDPressed;
        }
        
        if (newSpacePressed != spacePressed) {
            eventDispatcher.queueEvent<ButtonEvent>(InputDeviceType::Keyboard, 0, ' ', newSpacePressed);
            spacePressed = newSpacePressed;
        }
    }
    
    // 模拟手柄输入
    void simulateGamepadInput() {
        // 模拟手柄摇杆
        static Vector2D lastStickPos(0.0f, 0.0f);
        
        // 随机生成摇杆位置
        float x = (((rand() % 200) - 100) / 100.0f);
        float y = (((rand() % 200) - 100) / 100.0f);
        
        // 应用死区
        Vector2D stickPos(x, y);
        float length = stickPos.length();
        if (length < 0.25f) {
            stickPos = Vector2D(0.0f, 0.0f);
        } else {
            // 正规化
            if (length > 1.0f) {
                stickPos = stickPos.normalized();
            }
        }
        
        // 如果摇杆位置变化显著,生成事件
        if (std::abs(stickPos.x - lastStickPos.x) > 0.1f || 
            std::abs(stickPos.y - lastStickPos.y) > 0.1f) {
            // 在实际实现中会生成适当的轴事件
            lastStickPos = stickPos;
        }
    }
    
    // 处理数字输入映射
    void processDigitalMappings(const std::string& actionName, 
                              const std::vector<InputActionMapping>& mappings) {
        bool isAnyPressed = false;
        
        for (const auto& mapping : mappings) {
            if (mapping.actionType != InputActionType::Digital) continue;
            
            bool pressed = false;
            
            // 根据设备类型检查按键状态
            if (mapping.deviceType == InputDeviceType::Keyboard) {
                // 在实际实现中会检查实际键盘状态
                pressed = (rand() % 100) < 30; // 模拟30%概率按下
            } else if (mapping.deviceType == InputDeviceType::Gamepad) {
                // 在实际实现中会检查实际游戏手柄状态
                pressed = (rand() % 100) < 20; // 模拟20%概率按下
            }
            
            if (pressed) {
                isAnyPressed = true;
                break;
            }
        }
        
        // 如果动作不存在,创建它
        if (digitalInputs.find(actionName) == digitalInputs.end()) {
            digitalInputs[actionName] = DigitalInput();
        }
        
        // 更新动作状态
        digitalInputs[actionName].update(isAnyPressed);
    }
    
    // 处理模拟输入映射
    void processAnalogMappings(const std::string& actionName,
                             const std::vector<InputActionMapping>& mappings) {
        float value = 0.0f;
        bool found = false;
        
        for (const auto& mapping : mappings) {
            if (mapping.actionType != InputActionType::Analog) continue;
            
            // 根据设备类型获取模拟值
            if (mapping.deviceType == InputDeviceType::Gamepad) {
                // 在实际实现中会获取实际模拟值
                value = ((rand() % 200) - 100) / 100.0f; // 模拟-1到1的值
                found = true;
                break;
            }
        }
        
        if (found) {
            // 如果动作不存在,创建它
            if (analogInputs.find(actionName) == analogInputs.end()) {
                analogInputs[actionName] = AnalogInput();
            }
            
            // 更新动作状态
            analogInputs[actionName].update(value);
        }
    }
    
    // 处理2D向量映射
    void processVector2DMappings(const std::string& actionName,
                               const std::vector<InputActionMapping>& mappings) {
        Vector2D value(0.0f, 0.0f);
        bool found = false;
        
        for (const auto& mapping : mappings) {
            if (mapping.actionType != InputActionType::Vector2D) continue;
            
            // 根据设备类型获取向量值
            if (mapping.deviceType == InputDeviceType::Gamepad) {
                // 在实际实现中会获取实际摇杆位置
                float x = ((rand() % 200) - 100) / 100.0f;
                float y = ((rand() % 200) - 100) / 100.0f;
                
                // 应用死区
                if (std::sqrt(x*x + y*y) < 0.25f) {
                    x = y = 0.0f;
                }
                
                value = Vector2D(x, y);
                found = true;
                break;
            }
        }
        
        if (found) {
            // 更新向量值
            vector2DInputs[actionName] = value;
        }
    }
};

// 使用示例
int main() {
    GameInputSystem inputSystem;
    inputSystem.initialize();
    
    // 注册按钮事件处理器
    inputSystem.registerEventHandler(InputEventType::ButtonPressed, 
        [](const InputEvent& event) {
            const ButtonEvent& buttonEvent = static_cast<const ButtonEvent&>(event);
            std::cout << "Button pressed: " << buttonEvent.buttonId << "\n";
        });
    
    // 主循环模拟
    for (int i = 0; i < 100; ++i) {
        inputSystem.update();
        
        // 检查输入动作
        if (inputSystem.isActionJustPressed("Jump")) {
            std::cout << "Player jumped!\n";
            
            // 触发跳跃振动反馈
            inputSystem.triggerVibration(0.7f, 100);
        }
        
        if (inputSystem.isActionPressed("MoveForward")) {
            std::cout << "Moving forward\n";
        }
        
        // 获取移动向量
        Vector2D moveVector = inputSystem.getVector2D("Move");
        if (moveVector.length() > 0.1f) {
            std::cout << "Moving with gamepad: x=" << moveVector.x << ", y=" << moveVector.y << "\n";
        }
        
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    
    return 0;
}

8.6.2 示例:实现VR控制器接口

cpp

// VR控制器接口实现
class VRControllerInterface {
private:
    struct ControllerState {
        bool connected;
        Vector3D position;
        Quaternion orientation;
        std::unordered_map<int, bool> buttonStates;
        std::unordered_map<int, float> analogStates;
        Vector2D touchpadPosition;
        bool touchpadTouched;
    };
    
    ControllerState leftController;
    ControllerState rightController;
    ForceFeedbackController feedbackController;
    
public:
    VRControllerInterface() {
        leftController.connected = false;
        rightController.connected = false;
    }
    
    bool initialize() {
        // 初始化VR系统接口(依赖于具体的VR SDK)
        std::cout << "Initializing VR controller interface...\n";
        
        // 模拟连接成功
        leftController.connected = true;
        rightController.connected = true;
        
        // 初始化力反馈
        feedbackController.initialize();
        
        return true;
    }
    
    void update() {
        // 在实际实现中,会从VR SDK获取最新的控制器状态
        // 这里只是模拟更新
        
        if (leftController.connected) {
            // 更新位置和方向
            updateControllerSimulation(leftController);
        }
        
        if (rightController.connected) {
            // 更新位置和方向
            updateControllerSimulation(rightController);
        }
    }
    
    // 检查按钮状态
    bool isButtonPressed(bool isLeft, int buttonId) const {
        const ControllerState& controller = isLeft ? leftController : rightController;
        if (!controller.connected) return false;
        
        auto it = controller.buttonStates.find(buttonId);
        return (it != controller.buttonStates.end()) && it->second;
    }
    
    // 获取触摸板位置
    Vector2D getTouchpadPosition(bool isLeft) const {
        const ControllerState& controller = isLeft ? leftController : rightController;
        if (!controller.connected || !controller.touchpadTouched) {
            return Vector2D(0.0f, 0.0f);
        }
        return controller.touchpadPosition;
    }
    
    // 获取控制器位置
    Vector3D getPosition(bool isLeft) const {
        const ControllerState& controller = isLeft ? leftController : rightController;
        return controller.position;
    }
    
    // 获取控制器方向
    Quaternion getOrientation(bool isLeft) const {
        const ControllerState& controller = isLeft ? leftController : rightController;
        return controller.orientation;
    }
    
    // 触发控制器震动
    void triggerHapticPulse(bool isLeft, float intensity, uint32_t durationMs) {
        if ((isLeft && !leftController.connected) || (!isLeft && !rightController.connected)) {
            return;
        }
        
        std::cout << "Triggering haptic pulse on " 
                  << (isLeft ? "left" : "right") 
                  << " controller: " << intensity << " for " << durationMs << "ms\n";
        
        // 实际实现会调用VR SDK的力反馈函数
        auto effect = std::make_unique<VibrationEffect>(durationMs, intensity, 100.0f);
        feedbackController.playEffect(std::move(effect));
    }
    
private:
    // 模拟控制器运动
    void updateControllerSimulation(ControllerState& controller) {
        // 在实际实现中,会从VR SDK获取实际位置和方向
        // 这里只是简单模拟
        
        // 模拟位置小幅度变动
        controller.position.x += (rand() % 100 - 50) * 0.001f;
        controller.position.y += (rand() % 100 - 50) * 0.001f;
        controller.position.z += (rand() % 100 - 50) * 0.001f;
        
        // 模拟按钮状态
        for (int i = 0; i < 4; ++i) {
            bool newState = (rand() % 100) < 5; // 5%概率改变按钮状态
            if (newState != controller.buttonStates[i]) {
                controller.buttonStates[i] = newState;
                std::cout << "VR controller button " << i << " " 
                          << (newState ? "pressed" : "released") << "\n";
            }
        }
        
        // 模拟触摸板
        bool newTouched = (rand() % 100) < 30; // 30%概率触摸
        controller.touchpadTouched = newTouched;
        
        if (newTouched) {
            controller.touchpadPosition.x = ((rand() % 200) - 100) / 100.0f;
            controller.touchpadPosition.y = ((rand() % 200) - 100) / 100.0f;
        }
    }
};

// 使用示例
void vrControllerDemo() {
    VRControllerInterface vrInterface;
    if (!vrInterface.initialize()) {
        std::cout << "Failed to initialize VR controllers\n";
        return;
    }
    
    // 模拟VR交互循环
    for (int i = 0; i < 100; ++i) {
        vrInterface.update();
        
        // 检查按钮输入
        if (vrInterface.isButtonPressed(true, 0)) { // 左控制器触发键
            std::cout << "Left trigger pressed!\n";
            
            // 触发左控制器震动
            vrInterface.triggerHapticPulse(true, 0.5f, 50);
        }
        
        if (vrInterface.isButtonPressed(false, 0)) { // 右控制器触发键
            std::cout << "Right trigger pressed!\n";
            
            // 触发右控制器震动
            vrInterface.triggerHapticPulse(false, 0.5f, 50);
        }
        
        // 获取控制器位置
        Vector3D leftPos = vrInterface.getPosition(true);
        Vector3D rightPos = vrInterface.getPosition(false);
        
        // 计算控制器之间的距离
        float dx = leftPos.x - rightPos.x;
        float dy = leftPos.y - rightPos.y;
        float dz = leftPos.z - rightPos.z;
        float distance = std::sqrt(dx*dx + dy*dy + dz*dz);
        
        // 检测特殊手势
        if (distance < 0.1f) {
            std::cout << "Controllers brought close together - special action!\n";
            
            // 触发双控制器震动
            vrInterface.triggerHapticPulse(true, 1.0f, 200);
            vrInterface.triggerHapticPulse(false, 1.0f, 200);
        }
        
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

通过以上代码示例,您可以了解如何在游戏引擎中设计和实现人体学接口设备系统。这些示例涵盖了HID设备的接口技术、输入类型处理、输出反馈系统以及整合为完整的输入管理系统的方法。在实际开发中,需要针对特定平台和设备进行进一步的适配和优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小宝哥Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值