Files
HelloVulkan/App/editor/panels/GamepadPanel.cpp
T
atdunbg beba491bcb 阶段性合并:动画重定向、碎裂共享库、第三人称玩法、编辑器面板与 vendored ufbx
- 动画:HumanoidRig 三层架构(standard 标准槽 / dialects 方言映射 / mapper 配对),
  指/趾统一走世界增量两遍式烘焙;AnimationAttach 拆为纯算法 + facade;修正 HumanoidBasis
  行走基朝向与跨骨架去重、根运动通道
- 物理:CharacterController 角色互碰撞 / shapeOffset / 接触事件;PhysicsWorld 接触合成与
  字符冲量判定
- 渲染与材质:Model 多材质按材质拆子 mesh 与贴图路径解析、DDS 直传、GLB 内嵌纹理落盘、
  不透明通道 alpha 固定 1.0、glTF FlipUVs 对齐;SceneSerializer 支持 meshIndex 与材质覆盖
- 脚本:FractureLib 抽为共享库(HostView 桥接)、WallFracture 参数直绑、新增
  ThirdPersonCamera / Follower / WASDMove 玩法;移除 GameDirector / PlayerController / Target
- 编辑器:新增 GamepadPanel、LogPanel(LogRing),改进 Inspector / 材质 / 统计 / 视口面板
- 依赖:vendored ufbx(FBX 5.x/6.x/7.x 全版本解析,替代 assimp 对 FBX 6.1 的拒读)
2026-10-11 22:22:19 +08:00

200 lines
7.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// GamepadPanel.cpp:手柄输入可视化回显(极简手柄视图)。
//
// 一块画布 = 手柄俯视图:顶部扳机条 + 肩键,左上左摇杆、左下十字键、
// 右上 ABXY(Xbox 语义色)、右下右摇杆,中央 Back/Guide/Start 小键。
// 按下即亮蓝(面键亮语义色),无数值无卡片——位置即实体,一眼对上
// 手里的手柄。数据源 = Window::PollGamepad(GLFW 原始轴值/按键)。
//
#include "GamepadPanel.h"
#include <Engine/Core/Window.h>
#include <imgui.h>
#include <cmath>
namespace {
// GLFW 标准布局下标
enum PadBtn {
kA = 0, kB = 1, kX = 2, kY = 3,
kLB = 4, kRB = 5, kBack = 6, kStart = 7, kGuide = 8,
kLS = 9, kRS = 10, kUp = 11, kRight = 12, kDown = 13, kLeft = 14,
};
constexpr ImU32 kLit = IM_COL32(90, 170, 250, 255); // 按下亮蓝
constexpr ImU32 kFill = IM_COL32(52, 52, 60, 255); // 暗态填充
constexpr ImU32 kEdge = IM_COL32(104, 104, 114, 255); // 暗态描边
constexpr ImU32 kTxt = IM_COL32(215, 215, 220, 255); // 标签文字
// 块状键(肩键 / Back / Start)
void Block(ImDrawList* dl, const ImVec2& c, float w, float h,
const char* label, bool pressed)
{
const ImVec2 mn(c.x - w * 0.5f, c.y - h * 0.5f);
const ImVec2 mx(c.x + w * 0.5f, c.y + h * 0.5f);
dl->AddRectFilled(mn, mx, pressed ? kLit : kFill, 5.0f);
dl->AddRect(mn, mx, pressed ? IM_COL32(160, 210, 255, 255) : kEdge,
5.0f);
const ImVec2 ts = ImGui::CalcTextSize(label);
dl->AddText(ImVec2(c.x - ts.x * 0.5f, c.y - ts.y * 0.5f), kTxt, label);
}
// 圆键(Guide)
void Circle(ImDrawList* dl, const ImVec2& c, float r, const char* label,
bool pressed)
{
dl->AddCircleFilled(c, r, pressed ? kLit : kFill);
dl->AddCircle(c, r, pressed ? IM_COL32(160, 210, 255, 255) : kEdge, 0,
1.5f);
const ImVec2 ts = ImGui::CalcTextSize(label);
dl->AddText(ImVec2(c.x - ts.x * 0.5f, c.y - ts.y * 0.5f), kTxt, label);
}
// Xbox 语义色面键
void Face(ImDrawList* dl, const ImVec2& c, float r, char label,
bool pressed)
{
ImU32 col;
switch (label)
{
case 'A': col = IM_COL32(32, 150, 40, 255); break;
case 'B': col = IM_COL32(200, 45, 45, 255); break;
case 'X': col = IM_COL32(50, 110, 220, 255); break;
default: col = IM_COL32(225, 185, 40, 255); break;
}
dl->AddCircleFilled(c, r, pressed ? col : IM_COL32(col & 0xFF,
(col >> 8) & 0xFF,
(col >> 16) & 0xFF,
60));
dl->AddCircle(c, r, pressed ? col : kEdge, 0, 2.0f);
const ImVec2 ts = ImGui::CalcTextSize(&label, &label + 1);
dl->AddText(ImVec2(c.x - ts.x * 0.5f, c.y - ts.y * 0.5f),
pressed ? IM_COL32(255, 255, 255, 255) : kTxt, &label);
}
// 摇杆画布(底座 + 死区圈 + 轴点)
void Stick(ImDrawList* dl, const ImVec2& c, float r, float ax, float ay,
float deadzone)
{
dl->AddCircleFilled(c, r, IM_COL32(32, 32, 37, 255));
dl->AddCircle(c, r, kEdge, 0, 1.5f);
dl->AddCircle(c, r * deadzone, IM_COL32(90, 140, 210, 110), 0, 1.2f);
dl->AddLine(ImVec2(c.x - r * 0.93f, c.y), ImVec2(c.x + r * 0.93f, c.y),
IM_COL32(50, 50, 58, 255), 1.0f);
dl->AddLine(ImVec2(c.x, c.y - r * 0.93f), ImVec2(c.x, c.y + r * 0.93f),
IM_COL32(50, 50, 58, 255), 1.0f);
const float mag = std::sqrt(ax * ax + ay * ay);
const float cl = (mag > 1.0f) ? 1.0f / mag : 1.0f;
const ImVec2 pt(c.x + ax * cl * (r - 8.0f),
c.y + ay * cl * (r - 8.0f));
if (mag > deadzone)
{
dl->AddCircleFilled(pt, 10.0f, IM_COL32(90, 170, 250, 70));
}
dl->AddCircleFilled(pt, 7.0f,
mag > deadzone ? kLit
: IM_COL32(140, 140, 148, 255));
}
// 十字键(真十字形,按下臂亮)
void Dpad(ImDrawList* dl, const ImVec2& c, float arm, float w,
const bool* b)
{
const ImVec2 l0(c.x - arm, c.y - w * 0.5f);
const ImVec2 l1(c.x, c.y + w * 0.5f);
const ImVec2 r0(c.x, c.y - w * 0.5f);
const ImVec2 r1(c.x + arm, c.y + w * 0.5f);
const ImVec2 u0(c.x - w * 0.5f, c.y - arm);
const ImVec2 u1(c.x + w * 0.5f, c.y);
const ImVec2 d0(c.x - w * 0.5f, c.y);
const ImVec2 d1(c.x + w * 0.5f, c.y + arm);
dl->AddRectFilled(l0, l1, b[kLeft] ? kLit : kFill, 4.0f);
dl->AddRectFilled(r0, r1, b[kRight] ? kLit : kFill, 4.0f);
dl->AddRectFilled(u0, u1, b[kUp] ? kLit : kFill, 4.0f);
dl->AddRectFilled(d0, d1, b[kDown] ? kLit : kFill, 4.0f);
dl->AddRect(l0, l1, kEdge, 4.0f);
dl->AddRect(r0, r1, kEdge, 4.0f);
dl->AddRect(u0, u1, kEdge, 4.0f);
dl->AddRect(d0, d1, kEdge, 4.0f);
}
// 线性扳机条(GLFW -1..1 → 0..1 填充)
void Trigger(ImDrawList* dl, const ImVec2& mn, const ImVec2& mx,
float raw)
{
dl->AddRectFilled(mn, mx, IM_COL32(32, 32, 37, 255), 4.0f);
const float t = (raw + 1.0f) * 0.5f;
if (t > 0.02f)
{
dl->AddRectFilled(mn, ImVec2(mn.x + (mx.x - mn.x) * t, mx.y),
IM_COL32(235, 120, 55, 255), 4.0f);
}
dl->AddRect(mn, mx, kEdge, 4.0f);
}
} // namespace
void GamepadPanel::Draw(const EditorContext& ctx)
{
if (!m_Open)
{
return;
}
if (!ImGui::Begin("Gamepad", &m_Open))
{
ImGui::End();
return;
}
Engine::GamepadState gp;
const bool connected =
ctx.app && ctx.app->GetWindow().PollGamepad(gp);
if (!connected)
{
ImGui::TextColored(ImVec4(0.60f, 0.60f, 0.60f, 1.0f),
"○ Not connected (JOYSTICK_1)");
ImGui::End();
return;
}
ImGui::TextColored(ImVec4(0.40f, 0.85f, 0.40f, 1.0f), "● Connected");
ImGui::Spacing();
// ---- 手柄画布(固定尺寸,元素 = 手柄真实位置;无背景板)----
constexpr float kW = 440.0f, kH = 262.0f;
constexpr float kDead = 0.15f; // = InputMap kRadialDeadZone
ImGui::BeginChild("pad", ImVec2(kW, kH), false);
ImDrawList* dl = ImGui::GetWindowDrawList();
const ImVec2 o = ImGui::GetWindowPos();
auto C = [&](float x, float y) { return ImVec2(o.x + x, o.y + y); };
// 顶部:线性扳机条 + 肩键
Trigger(dl, C(52, 8), C(122, 20), gp.axes[4]); // LT
Trigger(dl, C(kW - 122, 8), C(kW - 52, 20), gp.axes[5]); // RT
Block(dl, C(78, 40), 76.0f, 20.0f, "LB", gp.buttons[kLB]);
Block(dl, C(kW - 78, 40), 76.0f, 20.0f, "RB", gp.buttons[kRB]);
// 左上:左摇杆|左下:十字键(向右错开——真实手柄摇杆与十字不对齐)
Stick(dl, C(76, 118), 46.0f, gp.axes[0], gp.axes[1], kDead);
Dpad(dl, C(128, 210), 36.0f, 24.0f, gp.buttons);
// 中央:Back / Guide / Start
Block(dl, C(184, 74), 40.0f, 16.0f, "Back", gp.buttons[kBack]);
Circle(dl, C(220, 58), 13.0f, "G", gp.buttons[kGuide]);
Block(dl, C(256, 74), 40.0f, 16.0f, "Start", gp.buttons[kStart]);
// 右上:ABXY 菱形|右下:右摇杆(对称错开,向中心收)
const ImVec2 fc = C(360, 118);
Face(dl, ImVec2(fc.x, fc.y - 24), 17.0f, 'Y', gp.buttons[kY]);
Face(dl, ImVec2(fc.x - 24, fc.y), 17.0f, 'X', gp.buttons[kX]);
Face(dl, ImVec2(fc.x + 24, fc.y), 17.0f, 'B', gp.buttons[kB]);
Face(dl, ImVec2(fc.x, fc.y + 24), 17.0f, 'A', gp.buttons[kA]);
Stick(dl, C(312, 210), 46.0f, gp.axes[2], gp.axes[3], kDead);
ImGui::EndChild();
ImGui::End();
}