Chapter 188 · XUI 游戏与交互控件

VirtualJoystick 虚拟摇杆

触摸屏虚拟摇杆:XY 轴输出、通道按钮、死区、波纹动效、自定义外观。

188

概念

VirtualJoystick 是移动端虚拟摇杆控件:
• Base + Knob:底盘 + 摇杆头,触摸拖动
• XY 输出:-1.0 ~ 1.0 归一化向量
• DeadZone:死区过滤微小抖动
• Channel:附加通道按钮(如跳跃/攻击)
• Ripple:触摸波纹动效
• BuiltinAtlas:内置纹理图集或自定义 Surface

API 速查

函数说明
int xuiVirtualJoystickCreate(pCtx, ppW, pDesc)创建摇杆
int xuiVirtualJoystickSetChange(pW, onChange, pUser)变化回调
int xuiVirtualJoystickSetValue(pW, fX, fY, bNotify)编程设值
int xuiVirtualJoystickReset(pW, bNotify)归零
float xuiVirtualJoystickGetX(pW)X 轴值
float xuiVirtualJoystickGetY(pW)Y 轴值
float xuiVirtualJoystickGetMagnitude(pW)幅度
float xuiVirtualJoystickGetAngle(pW)角度
int xuiVirtualJoystickSetChannel(pW, iCh, bPressed, fVal, bNotify)通道
int xuiVirtualJoystickSetMetrics(pW, fRadius, fKnob, fDeadZone)尺寸/死区
int xuiVirtualJoystickSetColors(pW, iBase, iBaseAct, iKnob, ...)颜色

编译本章示例

gcc -O2 -I. -o ch188.exe ch188_main.c src\xui_*.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
.\ch188.exe

示例:移动游戏摇杆

xui_virtual_joystick_desc_t desc = { 0 };
desc.onChange = OnJoystickChange;

xui_widget pJoy;
xuiVirtualJoystickCreate(pCtx, &pJoy, &desc);

// 半径 80px,摇杆头 30px,死区 10%
xuiVirtualJoystickSetMetrics(pJoy, 80.0f, 30.0f, 0.1f);

// 半透明配色
xuiVirtualJoystickSetColors(pJoy,
    0xFFFFFF40, 0xFFFFFF60,
    0xFFFFFF80, 0xFFFFFFC0,
    0x4FD8C240, 0x4FD8C280, 0x80808040);

// 游戏循环中读取
float fX = xuiVirtualJoystickGetX(pJoy);
float fY = xuiVirtualJoystickGetY(pJoy);
// 移动角色:player.x += fX * speed * dt;

完整代码

将以下代码保存为 ch188_main.c,编译后运行即可看到虚拟摇杆控制效果:

#include "xge.h"
#include "xui.h"
#include <stdio.h>

static xui_context g_pCtx;
static xui_widget g_pJoy;

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x1A1A2EFF);
    xui_vec2_t pos;
    xgeMouseGet(&pos.x, &pos.y);
    xuiInputPointerMove(g_pCtx, pos.x, pos.y);
    if (xgeMouseDown(XGE_MOUSE_LEFT))
        xuiInputPointerDown(g_pCtx, pos.x, pos.y, 0);
    else
        xuiInputPointerUp(g_pCtx, pos.x, pos.y, 0);

    // 读取摇杆值
    float fX = xuiVirtualJoystickGetX(g_pJoy);
    float fY = xuiVirtualJoystickGetY(g_pJoy);
    if (fX != 0.0f || fY != 0.0f)
        printf("Joystick: %.2f, %.2f\r", fX, fY);

    xuiUpdate(g_pCtx, xgeGetDelta());
    xuiLayout(g_pCtx);
    xuiRender(g_pCtx, NULL, NULL, 0);
    return XGE_OK;
}

int main(void) {
    xge_desc_t d = { 0 };
    d.iWidth = 400; d.iHeight = 400;
    d.sTitle = "ch188 - VirtualJoystick";
    d.iFlags = XGE_INIT_VSYNC;
    xgeInit(&d);

    xuiCreate(&g_pCtx);
    extern const xui_proxy_t tXgeProxy;
    xui_proxy_t tXgeProxy = xuiProxyXge();
xuiSetProxy(g_pCtx, &tXgeProxy);
    xuiSetViewportSize(g_pCtx, 400, 400);

    xui_virtual_joystick_desc_t jdesc = { 0 };
    xuiVirtualJoystickCreate(g_pCtx, &g_pJoy, &jdesc);

    // 半径 80,摇杆头 30,死区 10%
    xuiVirtualJoystickSetMetrics(g_pJoy, 80.0f, 30.0f, 0.1f);

    // 半透明配色
    xuiVirtualJoystickSetColors(g_pJoy,
        0xFFFFFF40, 0xFFFFFF60,
        0xFFFFFF80, 0xFFFFFFC0,
        0x4FD8C240, 0x4FD8C280, 0x80808040);

    xui_widget pRoot = xuiGetRootWidget(g_pCtx);
    xuiWidgetAddChild(pRoot, g_pJoy);
    xuiWidgetSetRect(g_pJoy, (xui_rect_t){0, 0, 400, 400});

    xgeRun(onFrame, NULL);
    xuiDestroy(g_pCtx);
    xgeUnit();
    return 0;
}

代码讲解

  • VirtualJoystickCreate:创建虚拟摇杆,适合触屏/鼠标操控。
  • SetMetrics:配置底盘半径、摇杆头半径、死区比例。
  • GetX/GetY:每帧读取摇杆偏移(-1.0 到 1.0),用于控制角色移动。
  • DeadZone:0.1 表示 10% 范围内视为零,避免漂移。

要点与陷阱

  • DeadZone:0.1 表示 10% 范围内视为零,避免漂移。
  • Change 回调:仅在值变化时触发,非每帧调用。
  • Channel:附加按钮通道适合跳跃/射击等离散操作。
  • Surface:SetSurface 可替换底盘/摇杆头纹理。

进阶练习

  1. 双摇杆:左侧移动 + 右侧视角,分别绑定不同逻辑。
  2. 动态出现:触摸屏幕时摇杆出现在触摸位置。
VirtualJoystick 虚拟摇杆效果
图:VirtualJoystick 虚拟摇杆渲染效果